diff options
Diffstat (limited to 'src/pdf')
-rw-r--r-- | src/pdf/SkPDFCatalog.cpp | 128 | ||||
-rw-r--r-- | src/pdf/SkPDFDevice.cpp | 1488 | ||||
-rw-r--r-- | src/pdf/SkPDFDocument.cpp | 192 | ||||
-rwxr-xr-x | src/pdf/SkPDFFont.cpp | 976 | ||||
-rw-r--r-- | src/pdf/SkPDFFormXObject.cpp | 94 | ||||
-rw-r--r-- | src/pdf/SkPDFGraphicState.cpp | 294 | ||||
-rw-r--r-- | src/pdf/SkPDFImage.cpp | 382 | ||||
-rw-r--r-- | src/pdf/SkPDFPage.cpp | 142 | ||||
-rw-r--r-- | src/pdf/SkPDFShader.cpp | 778 | ||||
-rw-r--r-- | src/pdf/SkPDFStream.cpp | 61 | ||||
-rw-r--r-- | src/pdf/SkPDFTypes.cpp | 392 | ||||
-rw-r--r-- | src/pdf/SkPDFUtils.cpp | 187 | ||||
-rw-r--r-- | src/pdf/pdf_files.mk | 13 |
13 files changed, 5127 insertions, 0 deletions
diff --git a/src/pdf/SkPDFCatalog.cpp b/src/pdf/SkPDFCatalog.cpp new file mode 100644 index 0000000..afa9d9a --- /dev/null +++ b/src/pdf/SkPDFCatalog.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFCatalog.h" +#include "SkPDFTypes.h" +#include "SkStream.h" +#include "SkTypes.h" + +SkPDFCatalog::SkPDFCatalog() + : fFirstPageCount(0), + fNextObjNum(1), + fNextFirstPageObjNum(0) { +} + +SkPDFCatalog::~SkPDFCatalog() {} + +SkPDFObject* SkPDFCatalog::addObject(SkPDFObject* obj, bool onFirstPage) { + SkASSERT(findObjectIndex(obj) == -1); + SkASSERT(fNextFirstPageObjNum == 0); + if (onFirstPage) + fFirstPageCount++; + + struct Rec newEntry(obj, onFirstPage); + fCatalog.append(1, &newEntry); + return obj; +} + +size_t SkPDFCatalog::setFileOffset(SkPDFObject* obj, size_t offset) { + int objIndex = assignObjNum(obj) - 1; + SkASSERT(fCatalog[objIndex].fObjNumAssigned); + SkASSERT(fCatalog[objIndex].fFileOffset == 0); + fCatalog[objIndex].fFileOffset = offset; + + return obj->getOutputSize(this, true); +} + +void SkPDFCatalog::emitObjectNumber(SkWStream* stream, SkPDFObject* obj) { + stream->writeDecAsText(assignObjNum(obj)); + stream->writeText(" 0"); // Generation number is always 0. +} + +size_t SkPDFCatalog::getObjectNumberSize(SkPDFObject* obj) { + SkDynamicMemoryWStream buffer; + emitObjectNumber(&buffer, obj); + return buffer.getOffset(); +} + +int SkPDFCatalog::findObjectIndex(SkPDFObject* obj) const { + for (int i = 0; i < fCatalog.count(); i++) { + if (fCatalog[i].fObject == obj) + return i; + } + return -1; +} + +int SkPDFCatalog::assignObjNum(SkPDFObject* obj) { + int pos = findObjectIndex(obj); + // If this assert fails, it means you probably forgot to add an object + // to the resource list. + SkASSERT(pos >= 0); + uint32_t currentIndex = pos; + if (fCatalog[currentIndex].fObjNumAssigned) + return currentIndex + 1; + + // First assignment. + if (fNextFirstPageObjNum == 0) + fNextFirstPageObjNum = fCatalog.count() - fFirstPageCount + 1; + + uint32_t objNum; + if (fCatalog[currentIndex].fOnFirstPage) { + objNum = fNextFirstPageObjNum; + fNextFirstPageObjNum++; + } else { + objNum = fNextObjNum; + fNextObjNum++; + } + + // When we assign an object an object number, we put it in that array + // offset (minus 1 because object number 0 is reserved). + SkASSERT(!fCatalog[objNum - 1].fObjNumAssigned); + if (objNum - 1 != currentIndex) + SkTSwap(fCatalog[objNum - 1], fCatalog[currentIndex]); + fCatalog[objNum - 1].fObjNumAssigned = true; + return objNum; +} + +int32_t SkPDFCatalog::emitXrefTable(SkWStream* stream, bool firstPage) { + int first = -1; + int last = fCatalog.count() - 1; + // TODO(vandebo) support linearized format. + //int last = fCatalog.count() - fFirstPageCount - 1; + //if (firstPage) { + // first = fCatalog.count() - fFirstPageCount; + // last = fCatalog.count() - 1; + //} + + stream->writeText("xref\n"); + stream->writeDecAsText(first + 1); + stream->writeText(" "); + stream->writeDecAsText(last - first + 1); + stream->writeText("\n"); + + if (first == -1) { + stream->writeText("0000000000 65535 f \n"); + first++; + } + for (int i = first; i <= last; i++) { + SkASSERT(fCatalog[i].fFileOffset > 0); + SkASSERT(fCatalog[i].fFileOffset <= 9999999999LL); + stream->writeBigDecAsText(fCatalog[i].fFileOffset, 10); + stream->writeText(" 00000 n \n"); + } + + return fCatalog.count() + 1; +} diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp new file mode 100644 index 0000000..c6ddf39 --- /dev/null +++ b/src/pdf/SkPDFDevice.cpp @@ -0,0 +1,1488 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFDevice.h" + +#include "SkColor.h" +#include "SkClipStack.h" +#include "SkDraw.h" +#include "SkGlyphCache.h" +#include "SkPaint.h" +#include "SkPath.h" +#include "SkPDFFont.h" +#include "SkPDFFormXObject.h" +#include "SkPDFGraphicState.h" +#include "SkPDFImage.h" +#include "SkPDFShader.h" +#include "SkPDFStream.h" +#include "SkPDFTypes.h" +#include "SkPDFUtils.h" +#include "SkRect.h" +#include "SkString.h" +#include "SkTextFormatParams.h" +#include "SkTypeface.h" +#include "SkTypes.h" + +// Utility functions + +static void emit_pdf_color(SkColor color, SkWStream* result) { + SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere. + SkScalar colorMax = SkIntToScalar(0xFF); + SkPDFScalar::Append( + SkScalarDiv(SkIntToScalar(SkColorGetR(color)), colorMax), result); + result->writeText(" "); + SkPDFScalar::Append( + SkScalarDiv(SkIntToScalar(SkColorGetG(color)), colorMax), result); + result->writeText(" "); + SkPDFScalar::Append( + SkScalarDiv(SkIntToScalar(SkColorGetB(color)), colorMax), result); + result->writeText(" "); +} + +static SkPaint calculate_text_paint(const SkPaint& paint) { + SkPaint result = paint; + if (result.isFakeBoldText()) { + SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(), + kStdFakeBoldInterpKeys, + kStdFakeBoldInterpValues, + kStdFakeBoldInterpLength); + SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale); + if (result.getStyle() == SkPaint::kFill_Style) + result.setStyle(SkPaint::kStrokeAndFill_Style); + else + width += result.getStrokeWidth(); + result.setStrokeWidth(width); + } + return result; +} + +// Stolen from measure_text in SkDraw.cpp and then tweaked. +static void align_text(SkDrawCacheProc glyphCacheProc, const SkPaint& paint, + const uint16_t* glyphs, size_t len, SkScalar* x, + SkScalar* y, SkScalar* width) { + if (paint.getTextAlign() == SkPaint::kLeft_Align && width == NULL) + return; + + SkMatrix ident; + ident.reset(); + SkAutoGlyphCache autoCache(paint, &ident); + SkGlyphCache* cache = autoCache.getCache(); + + const char* start = (char*)glyphs; + const char* stop = (char*)(glyphs + len); + SkFixed xAdv = 0, yAdv = 0; + + // TODO(vandebo) This probably needs to take kerning into account. + while (start < stop) { + const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0); + xAdv += glyph.fAdvanceX; + yAdv += glyph.fAdvanceY; + }; + if (width) + *width = SkFixedToScalar(xAdv); + if (paint.getTextAlign() == SkPaint::kLeft_Align) + return; + + SkScalar xAdj = SkFixedToScalar(xAdv); + SkScalar yAdj = SkFixedToScalar(yAdv); + if (paint.getTextAlign() == SkPaint::kCenter_Align) { + xAdj = SkScalarHalf(xAdj); + yAdj = SkScalarHalf(yAdj); + } + *x = *x - xAdj; + *y = *y - yAdj; +} + +static void set_text_transform(SkScalar x, SkScalar y, SkScalar textSkewX, + SkWStream* content) { + // Flip the text about the x-axis to account for origin swap and include + // the passed parameters. + content->writeText("1 0 "); + SkPDFScalar::Append(0 - textSkewX, content); + content->writeText(" -1 "); + SkPDFScalar::Append(x, content); + content->writeText(" "); + SkPDFScalar::Append(y, content); + content->writeText(" Tm\n"); +} + +// It is important to not confuse GraphicStateEntry with SkPDFGraphicState, the +// later being our representation of an object in the PDF file. +struct GraphicStateEntry { + GraphicStateEntry(); + + // Compare the fields we care about when setting up a new content entry. + bool compareInitialState(const GraphicStateEntry& b); + + SkMatrix fMatrix; + // We can't do set operations on Paths, though PDF natively supports + // intersect. If the clip stack does anything other than intersect, + // we have to fall back to the region. Treat fClipStack as authoritative. + // See http://code.google.com/p/skia/issues/detail?id=221 + SkClipStack fClipStack; + SkRegion fClipRegion; + + // When emitting the content entry, we will ensure the graphic state + // is set to these values first. + SkColor fColor; + SkScalar fTextScaleX; // Zero means we don't care what the value is. + SkPaint::Style fTextFill; // Only if TextScaleX is non-zero. + int fShaderIndex; + int fGraphicStateIndex; + + // We may change the font (i.e. for Type1 support) within a + // ContentEntry. This is the one currently in effect, or NULL if none. + SkPDFFont* fFont; + // In PDF, text size has no default value. It is only valid if fFont is + // not NULL. + SkScalar fTextSize; +}; + +GraphicStateEntry::GraphicStateEntry() : fColor(SK_ColorBLACK), + fTextScaleX(SK_Scalar1), + fTextFill(SkPaint::kFill_Style), + fShaderIndex(-1), + fGraphicStateIndex(-1), + fFont(NULL), + fTextSize(SK_ScalarNaN) { + fMatrix.reset(); +} + +bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& b) { + return fColor == b.fColor && + fShaderIndex == b.fShaderIndex && + fGraphicStateIndex == b.fGraphicStateIndex && + fMatrix == b.fMatrix && + fClipStack == b.fClipStack && + (fTextScaleX == 0 || + b.fTextScaleX == 0 || + (fTextScaleX == b.fTextScaleX && fTextFill == b.fTextFill)); +} + +class GraphicStackState { +public: + GraphicStackState(const SkClipStack& existingClipStack, + const SkRegion& existingClipRegion, + SkWStream* contentStream) + : fStackDepth(0), + fContentStream(contentStream) { + fEntries[0].fClipStack = existingClipStack; + fEntries[0].fClipRegion = existingClipRegion; + } + + void updateClip(const SkClipStack& clipStack, const SkRegion& clipRegion, + const SkIPoint& translation); + void updateMatrix(const SkMatrix& matrix); + void updateDrawingState(const GraphicStateEntry& state); + + void drainStack(); + +private: + void push(); + void pop(); + GraphicStateEntry* currentEntry() { return &fEntries[fStackDepth]; } + + // Conservative limit on save depth, see impl. notes in PDF 1.4 spec. + static const int kMaxStackDepth = 12; + GraphicStateEntry fEntries[kMaxStackDepth + 1]; + int fStackDepth; + SkWStream* fContentStream; +}; + +void GraphicStackState::drainStack() { + while (fStackDepth) { + pop(); + } +} + +void GraphicStackState::push() { + SkASSERT(fStackDepth < kMaxStackDepth); + fContentStream->writeText("q\n"); + fStackDepth++; + fEntries[fStackDepth] = fEntries[fStackDepth - 1]; +} + +void GraphicStackState::pop() { + SkASSERT(fStackDepth > 0); + fContentStream->writeText("Q\n"); + fStackDepth--; +} + +// This function initializes iter to be an interator on the "stack" argument +// and then skips over the leading entries as specified in prefix. It requires +// and asserts that "prefix" will be a prefix to "stack." +static void skip_clip_stack_prefix(const SkClipStack& prefix, + const SkClipStack& stack, + SkClipStack::B2FIter* iter) { + SkClipStack::B2FIter prefixIter(prefix); + iter->reset(stack); + + const SkClipStack::B2FIter::Clip* prefixEntry; + const SkClipStack::B2FIter::Clip* iterEntry; + + int count = 0; + for (prefixEntry = prefixIter.next(); prefixEntry; + prefixEntry = prefixIter.next(), count++) { + iterEntry = iter->next(); + SkASSERT(iterEntry); + // Because of SkClipStack does internal intersection, the last clip + // entry may differ. + if(*prefixEntry != *iterEntry) { + SkASSERT(prefixEntry->fOp == SkRegion::kIntersect_Op); + SkASSERT(iterEntry->fOp == SkRegion::kIntersect_Op); + SkASSERT((iterEntry->fRect == NULL) == + (prefixEntry->fRect == NULL)); + SkASSERT((iterEntry->fPath == NULL) == + (prefixEntry->fPath == NULL)); + // We need to back up the iterator by one but don't have that + // function, so reset and go forward by one less. + iter->reset(stack); + for (int i = 0; i < count; i++) { + iter->next(); + } + prefixEntry = prefixIter.next(); + break; + } + } + + SkASSERT(prefixEntry == NULL); +} + +static void emit_clip(SkPath* clipPath, SkRect* clipRect, + SkWStream* contentStream) { + SkASSERT(clipPath || clipRect); + + SkPath::FillType clipFill; + if (clipPath) { + SkPDFUtils::EmitPath(*clipPath, contentStream); + clipFill = clipPath->getFillType(); + } else if (clipRect) { + SkPDFUtils::AppendRectangle(*clipRect, contentStream); + clipFill = SkPath::kWinding_FillType; + } + + NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType, false); + NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType, false); + if (clipFill == SkPath::kEvenOdd_FillType) { + contentStream->writeText("W* n\n"); + } else { + contentStream->writeText("W n\n"); + } +} + +// TODO(vandebo) Take advantage of SkClipStack::getSaveCount(), the PDF +// graphic state stack, and the fact that we can know all the clips used +// on the page to optimize this. +void GraphicStackState::updateClip(const SkClipStack& clipStack, + const SkRegion& clipRegion, + const SkIPoint& translation) { + if (clipStack == currentEntry()->fClipStack) { + return; + } + + while (fStackDepth > 0) { + pop(); + if (clipStack == currentEntry()->fClipStack) { + return; + } + } + push(); + + // gsState->initialEntry()->fClipStack/Region specifies the clip that has + // already been applied. (If this is a top level device, then it specifies + // a clip to the content area. If this is a layer, then it specifies + // the clip in effect when the layer was created.) There's no need to + // reapply that clip; SKCanvas's SkDrawIter will draw anything outside the + // initial clip on the parent layer. (This means there's a bug if the user + // expands the clip and then uses any xfer mode that uses dst: + // http://code.google.com/p/skia/issues/detail?id=228 ) + SkClipStack::B2FIter iter; + skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter); + + // If the clip stack does anything other than intersect or if it uses + // an inverse fill type, we have to fall back to the clip region. + bool needRegion = false; + const SkClipStack::B2FIter::Clip* clipEntry; + for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) { + if (clipEntry->fOp != SkRegion::kIntersect_Op || + (clipEntry->fPath && clipEntry->fPath->isInverseFillType())) { + needRegion = true; + break; + } + } + + if (needRegion) { + SkPath clipPath; + SkAssertResult(clipRegion.getBoundaryPath(&clipPath)); + emit_clip(&clipPath, NULL, fContentStream); + } else { + skip_clip_stack_prefix(fEntries[0].fClipStack, clipStack, &iter); + SkMatrix transform; + transform.setTranslate(translation.fX, translation.fY); + const SkClipStack::B2FIter::Clip* clipEntry; + for (clipEntry = iter.next(); clipEntry; clipEntry = iter.next()) { + SkASSERT(clipEntry->fOp == SkRegion::kIntersect_Op); + if (clipEntry->fRect) { + SkRect translatedClip; + transform.mapRect(&translatedClip, *clipEntry->fRect); + emit_clip(NULL, &translatedClip, fContentStream); + } else if (clipEntry->fPath) { + SkPath translatedPath; + clipEntry->fPath->transform(transform, &translatedPath); + emit_clip(&translatedPath, NULL, fContentStream); + } else { + SkASSERT(false); + } + } + } + currentEntry()->fClipStack = clipStack; + currentEntry()->fClipRegion = clipRegion; +} + +void GraphicStackState::updateMatrix(const SkMatrix& matrix) { + if (matrix == currentEntry()->fMatrix) { + return; + } + + if (currentEntry()->fMatrix.getType() != SkMatrix::kIdentity_Mask) { + SkASSERT(fStackDepth > 0); + SkASSERT(fEntries[fStackDepth].fClipStack == + fEntries[fStackDepth -1].fClipStack); + pop(); + + SkASSERT(currentEntry()->fMatrix.getType() == SkMatrix::kIdentity_Mask); + } + if (matrix.getType() == SkMatrix::kIdentity_Mask) { + return; + } + + push(); + SkPDFUtils::AppendTransform(matrix, fContentStream); + currentEntry()->fMatrix = matrix; +} + +void GraphicStackState::updateDrawingState(const GraphicStateEntry& state) { + // PDF treats a shader as a color, so we only set one or the other. + if (state.fShaderIndex >= 0) { + if (state.fShaderIndex != currentEntry()->fShaderIndex) { + fContentStream->writeText("/Pattern CS /Pattern cs /P"); + fContentStream->writeDecAsText(state.fShaderIndex); + fContentStream->writeText(" SCN /P"); + fContentStream->writeDecAsText(state.fShaderIndex); + fContentStream->writeText(" scn\n"); + currentEntry()->fShaderIndex = state.fShaderIndex; + } + } else { + if (state.fColor != currentEntry()->fColor || + currentEntry()->fShaderIndex >= 0) { + emit_pdf_color(state.fColor, fContentStream); + fContentStream->writeText("RG "); + emit_pdf_color(state.fColor, fContentStream); + fContentStream->writeText("rg\n"); + currentEntry()->fColor = state.fColor; + currentEntry()->fShaderIndex = -1; + } + } + + if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) { + SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream); + currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex; + } + + if (state.fTextScaleX) { + if (state.fTextScaleX != currentEntry()->fTextScaleX) { + SkScalar pdfScale = SkScalarMul(state.fTextScaleX, + SkIntToScalar(100)); + SkPDFScalar::Append(pdfScale, fContentStream); + fContentStream->writeText(" Tz\n"); + currentEntry()->fTextScaleX = state.fTextScaleX; + } + if (state.fTextFill != currentEntry()->fTextFill) { + SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value); + SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1, + enum_must_match_value); + SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2, + enum_must_match_value); + fContentStream->writeDecAsText(state.fTextFill); + fContentStream->writeText(" Tr\n"); + currentEntry()->fTextFill = state.fTextFill; + } + } +} + +struct ContentEntry { + GraphicStateEntry fState; + SkDynamicMemoryWStream fContent; + SkTScopedPtr<ContentEntry> fNext; +}; + +// A helper class to automatically finish a ContentEntry at the end of a +// drawing method and maintain the state needed between set up and finish. +class ScopedContentEntry { +public: + ScopedContentEntry(SkPDFDevice* device, const SkDraw& draw, + const SkPaint& paint, bool hasText = false) + : fDevice(device), + fContentEntry(NULL), + fXfermode(SkXfermode::kSrcOver_Mode) { + init(draw.fClipStack, *draw.fClip, *draw.fMatrix, paint, hasText); + } + ScopedContentEntry(SkPDFDevice* device, const SkClipStack* clipStack, + const SkRegion& clipRegion, const SkMatrix& matrix, + const SkPaint& paint, bool hasText = false) + : fDevice(device), + fContentEntry(NULL), + fXfermode(SkXfermode::kSrcOver_Mode) { + init(clipStack, clipRegion, matrix, paint, hasText); + } + + ~ScopedContentEntry() { + if (fContentEntry) { + fDevice->finishContentEntry(fXfermode, fDstFormXObject.get()); + } + } + + ContentEntry* entry() { return fContentEntry; } +private: + SkPDFDevice* fDevice; + ContentEntry* fContentEntry; + SkXfermode::Mode fXfermode; + SkRefPtr<SkPDFFormXObject> fDstFormXObject; + + void init(const SkClipStack* clipStack, const SkRegion& clipRegion, + const SkMatrix& matrix, const SkPaint& paint, bool hasText) { + if (paint.getXfermode()) { + paint.getXfermode()->asMode(&fXfermode); + } + fContentEntry = fDevice->setUpContentEntry(clipStack, clipRegion, + matrix, paint, hasText, + &fDstFormXObject); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +SkDevice* SkPDFDeviceFactory::newDevice(SkCanvas* c, SkBitmap::Config config, + int width, int height, bool isOpaque, + bool isForLayer) { + SkMatrix initialTransform; + initialTransform.reset(); + SkISize size = SkISize::Make(width, height); + if (isForLayer) { + return SkNEW_ARGS(SkPDFDevice, (size, c->getTotalClipStack(), + c->getTotalClip())); + } else { + return SkNEW_ARGS(SkPDFDevice, (size, size, initialTransform)); + } +} + +static inline SkBitmap makeContentBitmap(const SkISize& contentSize, + const SkMatrix* initialTransform) { + SkBitmap bitmap; + if (initialTransform) { + // Compute the size of the drawing area. + SkVector drawingSize; + SkMatrix inverse; + drawingSize.set(contentSize.fWidth, contentSize.fHeight); + initialTransform->invert(&inverse); + inverse.mapVectors(&drawingSize, 1); + SkISize size = SkSize::Make(drawingSize.fX, drawingSize.fY).toRound(); + bitmap.setConfig(SkBitmap::kNo_Config, abs(size.fWidth), + abs(size.fHeight)); + } else { + bitmap.setConfig(SkBitmap::kNo_Config, abs(contentSize.fWidth), + abs(contentSize.fHeight)); + } + + return bitmap; +} + +SkPDFDevice::SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize, + const SkMatrix& initialTransform) + : SkDevice(NULL, makeContentBitmap(contentSize, &initialTransform), false), + fPageSize(pageSize), + fContentSize(contentSize), + fLastContentEntry(NULL) { + // Skia generally uses the top left as the origin but PDF natively has the + // origin at the bottom left. This matrix corrects for that. But that only + // needs to be done once, we don't do it when layering. + fInitialTransform.setTranslate(0, pageSize.fHeight); + fInitialTransform.preScale(1, -1); + fInitialTransform.preConcat(initialTransform); + + SkIRect existingClip = SkIRect::MakeWH(this->width(), this->height()); + fExistingClipStack.clipDevRect(existingClip); + fExistingClipRegion.setRect(existingClip); + + this->init(); +} + +SkPDFDevice::SkPDFDevice(const SkISize& layerSize, + const SkClipStack& existingClipStack, + const SkRegion& existingClipRegion) + : SkDevice(NULL, makeContentBitmap(layerSize, NULL), false), + fPageSize(layerSize), + fContentSize(layerSize), + fExistingClipStack(existingClipStack), + fExistingClipRegion(existingClipRegion), + fLastContentEntry(NULL) { + fInitialTransform.reset(); + this->init(); +} + +SkPDFDevice::~SkPDFDevice() { + this->cleanUp(); +} + +void SkPDFDevice::init() { + fResourceDict = NULL; + fContentEntries.reset(); + fLastContentEntry = NULL; +} + +SkDeviceFactory* SkPDFDevice::onNewDeviceFactory() { + return SkNEW(SkPDFDeviceFactory); +} + +void SkPDFDevice::cleanUp() { + fGraphicStateResources.unrefAll(); + fXObjectResources.unrefAll(); + fFontResources.unrefAll(); + fShaderResources.unrefAll(); +} + +void SkPDFDevice::clear(SkColor color) { + this->cleanUp(); + this->init(); + + SkPaint paint; + paint.setColor(color); + paint.setStyle(SkPaint::kFill_Style); + SkMatrix identity; + identity.reset(); + ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion, + identity, paint); + internalDrawPaint(paint, content.entry()); +} + +void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) { + SkPaint newPaint = paint; + newPaint.setStyle(SkPaint::kFill_Style); + ScopedContentEntry content(this, d, newPaint); + internalDrawPaint(newPaint, content.entry()); +} + +void SkPDFDevice::internalDrawPaint(const SkPaint& paint, + ContentEntry* contentEntry) { + if (!contentEntry) { + return; + } + SkRect bbox = SkRect::MakeWH(SkIntToScalar(this->width()), + SkIntToScalar(this->height())); + SkMatrix totalTransform = fInitialTransform; + totalTransform.preConcat(contentEntry->fState.fMatrix); + SkMatrix inverse; + inverse.reset(); + totalTransform.invert(&inverse); + inverse.mapRect(&bbox); + + SkPDFUtils::AppendRectangle(bbox, &contentEntry->fContent); + SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType, + &contentEntry->fContent); +} + +void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode, + size_t count, const SkPoint* points, + const SkPaint& passedPaint) { + if (count == 0) { + return; + } + + // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath. + // We only use this when there's a path effect because of the overhead + // of multiple calls to setUpContentEntry it causes. + if (passedPaint.getPathEffect()) { + if (d.fClip->isEmpty()) { + return; + } + SkDraw pointDraw(d); + pointDraw.fDevice = this; + pointDraw.drawPoints(mode, count, points, passedPaint, true); + return; + } + + const SkPaint* paint = &passedPaint; + SkPaint modifiedPaint; + + if (mode == SkCanvas::kPoints_PointMode && + paint->getStrokeCap() != SkPaint::kRound_Cap) { + modifiedPaint = *paint; + paint = &modifiedPaint; + if (paint->getStrokeWidth()) { + // PDF won't draw a single point with square/butt caps because the + // orientation is ambiguous. Draw a rectangle instead. + modifiedPaint.setStyle(SkPaint::kFill_Style); + SkScalar strokeWidth = paint->getStrokeWidth(); + SkScalar halfStroke = SkScalarHalf(strokeWidth); + for (size_t i = 0; i < count; i++) { + SkRect r = SkRect::MakeXYWH(points[i].fX, points[i].fY, 0, 0); + r.inset(-halfStroke, -halfStroke); + drawRect(d, r, modifiedPaint); + } + return; + } else { + modifiedPaint.setStrokeCap(SkPaint::kRound_Cap); + } + } + + ScopedContentEntry content(this, d, *paint); + if (!content.entry()) { + return; + } + + switch (mode) { + case SkCanvas::kPolygon_PointMode: + SkPDFUtils::MoveTo(points[0].fX, points[0].fY, + &content.entry()->fContent); + for (size_t i = 1; i < count; i++) { + SkPDFUtils::AppendLine(points[i].fX, points[i].fY, + &content.entry()->fContent); + } + SkPDFUtils::StrokePath(&content.entry()->fContent); + break; + case SkCanvas::kLines_PointMode: + for (size_t i = 0; i < count/2; i++) { + SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY, + &content.entry()->fContent); + SkPDFUtils::AppendLine(points[i * 2 + 1].fX, + points[i * 2 + 1].fY, + &content.entry()->fContent); + SkPDFUtils::StrokePath(&content.entry()->fContent); + } + break; + case SkCanvas::kPoints_PointMode: + SkASSERT(paint->getStrokeCap() == SkPaint::kRound_Cap); + for (size_t i = 0; i < count; i++) { + SkPDFUtils::MoveTo(points[i].fX, points[i].fY, + &content.entry()->fContent); + SkPDFUtils::ClosePath(&content.entry()->fContent); + SkPDFUtils::StrokePath(&content.entry()->fContent); + } + break; + default: + SkASSERT(false); + } +} + +void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r, + const SkPaint& paint) { + if (paint.getPathEffect()) { + if (d.fClip->isEmpty()) { + return; + } + SkPath path; + path.addRect(r); + drawPath(d, path, paint, NULL, true); + return; + } + + ScopedContentEntry content(this, d, paint); + if (!content.entry()) { + return; + } + SkPDFUtils::AppendRectangle(r, &content.entry()->fContent); + SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType, + &content.entry()->fContent); +} + +void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath, + const SkPaint& paint, const SkMatrix* prePathMatrix, + bool pathIsMutable) { + SkPath modifiedPath; + SkPath* pathPtr = const_cast<SkPath*>(&origPath); + + SkMatrix matrix = *d.fMatrix; + if (prePathMatrix) { + if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) { + if (!pathIsMutable) { + pathPtr = &modifiedPath; + pathIsMutable = true; + } + origPath.transform(*prePathMatrix, pathPtr); + } else { + if (!matrix.preConcat(*prePathMatrix)) { + return; + } + } + } + + if (paint.getPathEffect()) { + if (d.fClip->isEmpty()) { + return; + } + if (!pathIsMutable) { + pathPtr = &modifiedPath; + pathIsMutable = true; + } + bool fill = paint.getFillPath(origPath, pathPtr); + + SkPaint noEffectPaint(paint); + noEffectPaint.setPathEffect(NULL); + if (fill) { + noEffectPaint.setStyle(SkPaint::kFill_Style); + } else { + noEffectPaint.setStyle(SkPaint::kStroke_Style); + noEffectPaint.setStrokeWidth(0); + } + drawPath(d, *pathPtr, noEffectPaint, NULL, true); + return; + } + + ScopedContentEntry content(this, d, paint); + if (!content.entry()) { + return; + } + SkPDFUtils::EmitPath(*pathPtr, &content.entry()->fContent); + SkPDFUtils::PaintPath(paint.getStyle(), pathPtr->getFillType(), + &content.entry()->fContent); +} + +void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap, + const SkIRect* srcRect, const SkMatrix& matrix, + const SkPaint& paint) { + if (d.fClip->isEmpty()) { + return; + } + + SkMatrix transform = matrix; + transform.postConcat(*d.fMatrix); + internalDrawBitmap(transform, d.fClipStack, *d.fClip, bitmap, srcRect, + paint); +} + +void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) { + if (d.fClip->isEmpty()) { + return; + } + + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y)); + internalDrawBitmap(matrix, d.fClipStack, *d.fClip, bitmap, NULL, paint); +} + +void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len, + SkScalar x, SkScalar y, const SkPaint& paint) { + SkPaint textPaint = calculate_text_paint(paint); + ScopedContentEntry content(this, d, textPaint, true); + if (!content.entry()) { + return; + } + + // We want the text in glyph id encoding and a writable buffer, so we end + // up making a copy either way. + size_t numGlyphs = paint.textToGlyphs(text, len, NULL); + uint16_t* glyphIDs = + (uint16_t*)sk_malloc_flags(numGlyphs * 2, + SK_MALLOC_TEMP | SK_MALLOC_THROW); + SkAutoFree autoFreeGlyphIDs(glyphIDs); + if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) { + paint.textToGlyphs(text, len, glyphIDs); + textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + } else { + SkASSERT((len & 1) == 0); + SkASSERT(len / 2 == numGlyphs); + memcpy(glyphIDs, text, len); + } + + SkScalar width; + SkScalar* widthPtr = NULL; + if (textPaint.isUnderlineText() || textPaint.isStrikeThruText()) + widthPtr = &width; + + SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc(); + align_text(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y, + widthPtr); + content.entry()->fContent.writeText("BT\n"); + set_text_transform(x, y, textPaint.getTextSkewX(), + &content.entry()->fContent); + size_t consumedGlyphCount = 0; + while (numGlyphs > consumedGlyphCount) { + updateFont(textPaint, glyphIDs[consumedGlyphCount], content.entry()); + SkPDFFont* font = content.entry()->fState.fFont; + size_t availableGlyphs = + font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount, + numGlyphs - consumedGlyphCount); + SkString encodedString = + SkPDFString::formatString(glyphIDs + consumedGlyphCount, + availableGlyphs, font->multiByteGlyphs()); + content.entry()->fContent.writeText(encodedString.c_str()); + consumedGlyphCount += availableGlyphs; + content.entry()->fContent.writeText(" Tj\n"); + } + content.entry()->fContent.writeText("ET\n"); + + // Draw underline and/or strikethrough if the paint has them. + // drawPosText() and drawTextOnPath() don't draw underline or strikethrough + // because the raster versions don't. Use paint instead of textPaint + // because we may have changed strokeWidth to do fakeBold text. + if (paint.isUnderlineText() || paint.isStrikeThruText()) { + SkScalar textSize = paint.getTextSize(); + SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness); + + if (paint.isUnderlineText()) { + SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y); + SkRect r = SkRect::MakeXYWH(x, top - height, width, height); + drawRect(d, r, paint); + } + if (paint.isStrikeThruText()) { + SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y); + SkRect r = SkRect::MakeXYWH(x, top - height, width, height); + drawRect(d, r, paint); + } + } +} + +void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len, + const SkScalar pos[], SkScalar constY, + int scalarsPerPos, const SkPaint& paint) { + SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos); + SkPaint textPaint = calculate_text_paint(paint); + ScopedContentEntry content(this, d, textPaint, true); + if (!content.entry()) { + return; + } + + // Make sure we have a glyph id encoding. + SkAutoFree glyphStorage; + uint16_t* glyphIDs; + size_t numGlyphs; + if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) { + numGlyphs = paint.textToGlyphs(text, len, NULL); + glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2, + SK_MALLOC_TEMP | SK_MALLOC_THROW); + glyphStorage.set(glyphIDs); + paint.textToGlyphs(text, len, glyphIDs); + textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + } else { + SkASSERT((len & 1) == 0); + numGlyphs = len / 2; + glyphIDs = (uint16_t*)text; + } + + SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc(); + content.entry()->fContent.writeText("BT\n"); + updateFont(textPaint, glyphIDs[0], content.entry()); + for (size_t i = 0; i < numGlyphs; i++) { + SkPDFFont* font = content.entry()->fState.fFont; + uint16_t encodedValue = glyphIDs[i]; + if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) { + updateFont(textPaint, glyphIDs[i], content.entry()); + i--; + continue; + } + SkScalar x = pos[i * scalarsPerPos]; + SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1]; + align_text(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y, NULL); + set_text_transform(x, y, textPaint.getTextSkewX(), + &content.entry()->fContent); + SkString encodedString = + SkPDFString::formatString(&encodedValue, 1, + font->multiByteGlyphs()); + content.entry()->fContent.writeText(encodedString.c_str()); + content.entry()->fContent.writeText(" Tj\n"); + } + content.entry()->fContent.writeText("ET\n"); +} + +void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) { + if (d.fClip->isEmpty()) { + return; + } + NOT_IMPLEMENTED("drawTextOnPath", true); +} + +void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode, + int vertexCount, const SkPoint verts[], + const SkPoint texs[], const SkColor colors[], + SkXfermode* xmode, const uint16_t indices[], + int indexCount, const SkPaint& paint) { + if (d.fClip->isEmpty()) { + return; + } + NOT_IMPLEMENTED("drawVerticies", true); +} + +void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y, + const SkPaint& paint) { + if ((device->getDeviceCapabilities() & kVector_Capability) == 0) { + // If we somehow get a raster device, do what our parent would do. + SkDevice::drawDevice(d, device, x, y, paint); + return; + } + + // Assume that a vector capable device means that it's a PDF Device. + SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device); + if (pdfDevice->isContentEmpty()) { + return; + } + + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y)); + ScopedContentEntry content(this, d.fClipStack, *d.fClip, matrix, paint); + if (!content.entry()) { + return; + } + + SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice); + fXObjectResources.push(xobject); // Transfer reference. + SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1, + &content.entry()->fContent); +} + +const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() { + if (fResourceDict.get() == NULL) { + fResourceDict = new SkPDFDict; + fResourceDict->unref(); // SkRefPtr and new both took a reference. + + if (fGraphicStateResources.count()) { + SkRefPtr<SkPDFDict> extGState = new SkPDFDict(); + extGState->unref(); // SkRefPtr and new both took a reference. + for (int i = 0; i < fGraphicStateResources.count(); i++) { + SkString nameString("G"); + nameString.appendS32(i); + extGState->insert( + nameString.c_str(), + new SkPDFObjRef(fGraphicStateResources[i]))->unref(); + } + fResourceDict->insert("ExtGState", extGState.get()); + } + + if (fXObjectResources.count()) { + SkRefPtr<SkPDFDict> xObjects = new SkPDFDict(); + xObjects->unref(); // SkRefPtr and new both took a reference. + for (int i = 0; i < fXObjectResources.count(); i++) { + SkString nameString("X"); + nameString.appendS32(i); + xObjects->insert( + nameString.c_str(), + new SkPDFObjRef(fXObjectResources[i]))->unref(); + } + fResourceDict->insert("XObject", xObjects.get()); + } + + if (fFontResources.count()) { + SkRefPtr<SkPDFDict> fonts = new SkPDFDict(); + fonts->unref(); // SkRefPtr and new both took a reference. + for (int i = 0; i < fFontResources.count(); i++) { + SkString nameString("F"); + nameString.appendS32(i); + fonts->insert(nameString.c_str(), + new SkPDFObjRef(fFontResources[i]))->unref(); + } + fResourceDict->insert("Font", fonts.get()); + } + + if (fShaderResources.count()) { + SkRefPtr<SkPDFDict> patterns = new SkPDFDict(); + patterns->unref(); // SkRefPtr and new both took a reference. + for (int i = 0; i < fShaderResources.count(); i++) { + SkString nameString("P"); + nameString.appendS32(i); + patterns->insert(nameString.c_str(), + new SkPDFObjRef(fShaderResources[i]))->unref(); + } + fResourceDict->insert("Pattern", patterns.get()); + } + + // For compatibility, add all proc sets (only used for output to PS + // devices). + const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"}; + SkRefPtr<SkPDFArray> procSets = new SkPDFArray(); + procSets->unref(); // SkRefPtr and new both took a reference. + procSets->reserve(SK_ARRAY_COUNT(procs)); + for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++) + procSets->append(new SkPDFName(procs[i]))->unref(); + fResourceDict->insert("ProcSet", procSets.get()); + } + return fResourceDict; +} + +void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const { + resourceList->setReserve(resourceList->count() + + fGraphicStateResources.count() + + fXObjectResources.count() + + fFontResources.count() + + fShaderResources.count()); + for (int i = 0; i < fGraphicStateResources.count(); i++) { + resourceList->push(fGraphicStateResources[i]); + fGraphicStateResources[i]->ref(); + fGraphicStateResources[i]->getResources(resourceList); + } + for (int i = 0; i < fXObjectResources.count(); i++) { + resourceList->push(fXObjectResources[i]); + fXObjectResources[i]->ref(); + fXObjectResources[i]->getResources(resourceList); + } + for (int i = 0; i < fFontResources.count(); i++) { + resourceList->push(fFontResources[i]); + fFontResources[i]->ref(); + fFontResources[i]->getResources(resourceList); + } + for (int i = 0; i < fShaderResources.count(); i++) { + resourceList->push(fShaderResources[i]); + fShaderResources[i]->ref(); + fShaderResources[i]->getResources(resourceList); + } +} + +const SkTDArray<SkPDFFont*>& SkPDFDevice::getFontResources() const { + return fFontResources; +} + +SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const { + SkRefPtr<SkPDFInt> zero = new SkPDFInt(0); + zero->unref(); // SkRefPtr and new both took a reference. + + SkRefPtr<SkPDFArray> mediaBox = new SkPDFArray(); + mediaBox->unref(); // SkRefPtr and new both took a reference. + mediaBox->reserve(4); + mediaBox->append(zero.get()); + mediaBox->append(zero.get()); + mediaBox->append(new SkPDFInt(fPageSize.fWidth))->unref(); + mediaBox->append(new SkPDFInt(fPageSize.fHeight))->unref(); + return mediaBox; +} + +SkStream* SkPDFDevice::content() const { + SkDynamicMemoryWStream data; + if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) { + SkPDFUtils::AppendTransform(fInitialTransform, &data); + } + // If the content area is the entire page, then we don't need to clip + // the content area (PDF area clips to the page size). Otherwise, + // we have to clip to the content area; we've already applied the + // initial transform, so just clip to the device size. + if (fPageSize != fContentSize) { + SkRect r = SkRect::MakeWH(this->width(), this->height()); + emit_clip(NULL, &r, &data); + } + + GraphicStackState gsState(fExistingClipStack, fExistingClipRegion, &data); + for (ContentEntry* entry = fContentEntries.get(); + entry != NULL; + entry = entry->fNext.get()) { + SkIPoint translation = this->getOrigin(); + translation.negate(); + gsState.updateClip(entry->fState.fClipStack, entry->fState.fClipRegion, + translation); + gsState.updateMatrix(entry->fState.fMatrix); + gsState.updateDrawingState(entry->fState); + data.write(entry->fContent.getStream(), entry->fContent.getOffset()); + } + gsState.drainStack(); + + SkMemoryStream* result = new SkMemoryStream; + result->setMemoryOwned(data.detach(), data.getOffset()); + return result; +} + +void SkPDFDevice::createFormXObjectFromDevice( + SkRefPtr<SkPDFFormXObject>* xobject) { + *xobject = new SkPDFFormXObject(this); + (*xobject)->unref(); // SkRefPtr and new both took a reference. + cleanUp(); // Reset this device to have no content. + init(); +} + +void SkPDFDevice::clearClipFromContent(const SkClipStack* clipStack, + const SkRegion& clipRegion) { + if (clipRegion.isEmpty() || isContentEmpty()) { + return; + } + SkRefPtr<SkPDFFormXObject> curContent; + createFormXObjectFromDevice(&curContent); + + // Redraw what we already had, but with the clip as a mask. + drawFormXObjectWithClip(curContent.get(), clipStack, clipRegion, true); +} + +void SkPDFDevice::drawFormXObjectWithClip(SkPDFFormXObject* xobject, + const SkClipStack* clipStack, + const SkRegion& clipRegion, + bool invertClip) { + if (clipRegion.isEmpty() && !invertClip) { + return; + } + + // Create the mask. + SkMatrix identity; + identity.reset(); + SkDraw draw; + draw.fMatrix = &identity; + draw.fClip = &clipRegion; + draw.fClipStack = clipStack; + SkPaint stockPaint; + this->drawPaint(draw, stockPaint); + SkRefPtr<SkPDFFormXObject> maskFormXObject; + createFormXObjectFromDevice(&maskFormXObject); + SkRefPtr<SkPDFGraphicState> sMaskGS = + SkPDFGraphicState::getSMaskGraphicState(maskFormXObject.get(), + invertClip); + sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref. + + // Draw the xobject with the clip as a mask. + ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion, + identity, stockPaint); + if (!content.entry()) { + return; + } + SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()), + &content.entry()->fContent); + SkPDFUtils::DrawFormXObject(fXObjectResources.count(), + &content.entry()->fContent); + fXObjectResources.push(xobject); + xobject->ref(); + + sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState(); + sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref. + SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()), + &content.entry()->fContent); +} + +ContentEntry* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack, + const SkRegion& clipRegion, + const SkMatrix& matrix, + const SkPaint& paint, + bool hasText, + SkRefPtr<SkPDFFormXObject>* dst) { + if (clipRegion.isEmpty()) { + return NULL; + } + + // The clip stack can come from an SkDraw where it is technically optional. + SkClipStack synthesizedClipStack; + if (clipStack == NULL) { + if (clipRegion == fExistingClipRegion) { + clipStack = &fExistingClipStack; + } else { + // GraphicStackState::updateClip expects the clip stack to have + // fExistingClip as a prefix, so start there, then set the clip + // to the passed region. + synthesizedClipStack = fExistingClipStack; + SkPath clipPath; + clipRegion.getBoundaryPath(&clipPath); + synthesizedClipStack.clipDevPath(clipPath, SkRegion::kReplace_Op); + clipStack = &synthesizedClipStack; + } + } + + SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode; + if (paint.getXfermode()) { + paint.getXfermode()->asMode(&xfermode); + } + + if (xfermode == SkXfermode::kClear_Mode || + xfermode == SkXfermode::kSrc_Mode) { + this->clearClipFromContent(clipStack, clipRegion); + } else if (xfermode == SkXfermode::kSrcIn_Mode || + xfermode == SkXfermode::kDstIn_Mode || + xfermode == SkXfermode::kSrcOut_Mode || + xfermode == SkXfermode::kDstOut_Mode) { + // For the following modes, we use both source and destination, but + // we use one as a smask for the other, so we have to make form xobjects + // out of both of them: SrcIn, DstIn, SrcOut, DstOut. + if (isContentEmpty()) { + return NULL; + } else { + createFormXObjectFromDevice(dst); + } + } + // TODO(vandebo) Figure out how/if we can handle the following modes: + // SrcAtop, DestAtop, Xor, Plus. + + // These xfer modes don't draw source at all. + if (xfermode == SkXfermode::kClear_Mode || + xfermode == SkXfermode::kDst_Mode) { + return NULL; + } + + ContentEntry* entry; + SkTScopedPtr<ContentEntry> newEntry; + if (fLastContentEntry && fLastContentEntry->fContent.getOffset() == 0) { + entry = fLastContentEntry; + } else { + newEntry.reset(new ContentEntry); + entry = newEntry.get(); + } + + populateGraphicStateEntryFromPaint(matrix, *clipStack, clipRegion, paint, + hasText, &entry->fState); + if (fLastContentEntry && xfermode != SkXfermode::kDstOver_Mode && + entry->fState.compareInitialState(fLastContentEntry->fState)) { + return fLastContentEntry; + } + + if (!fLastContentEntry) { + fContentEntries.reset(entry); + fLastContentEntry = entry; + } else if (xfermode == SkXfermode::kDstOver_Mode) { + entry->fNext.reset(fContentEntries.release()); + fContentEntries.reset(entry); + } else { + fLastContentEntry->fNext.reset(entry); + fLastContentEntry = entry; + } + newEntry.release(); + return entry; +} + +void SkPDFDevice::finishContentEntry(const SkXfermode::Mode xfermode, + SkPDFFormXObject* dst) { + if (xfermode != SkXfermode::kSrcIn_Mode && + xfermode != SkXfermode::kDstIn_Mode && + xfermode != SkXfermode::kSrcOut_Mode && + xfermode != SkXfermode::kDstOut_Mode) { + SkASSERT(!dst); + return; + } + SkASSERT(dst); + SkASSERT(!fContentEntries->fNext.get()); + + // We have to make a copy of these here because changing the current + // content into a form xobject will destroy them. + SkClipStack clipStack = fContentEntries->fState.fClipStack; + SkRegion clipRegion = fContentEntries->fState.fClipRegion; + + SkRefPtr<SkPDFFormXObject> srcFormXObject; + if (!isContentEmpty()) { + createFormXObjectFromDevice(&srcFormXObject); + } + + drawFormXObjectWithClip(dst, &clipStack, clipRegion, true); + + // We've redrawn dst minus the clip area, if there's no src, we're done. + if (!srcFormXObject.get()) { + return; + } + + SkMatrix identity; + identity.reset(); + SkPaint stockPaint; + ScopedContentEntry inClipContentEntry(this, &fExistingClipStack, + fExistingClipRegion, identity, + stockPaint); + if (!inClipContentEntry.entry()) { + return; + } + + SkRefPtr<SkPDFGraphicState> sMaskGS; + if (xfermode == SkXfermode::kSrcIn_Mode || + xfermode == SkXfermode::kSrcOut_Mode) { + sMaskGS = SkPDFGraphicState::getSMaskGraphicState( + dst, xfermode == SkXfermode::kSrcOut_Mode); + fXObjectResources.push(srcFormXObject.get()); + srcFormXObject->ref(); + } else { + sMaskGS = SkPDFGraphicState::getSMaskGraphicState( + srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode); + // dst already added to fXObjectResources in drawFormXObjectWithClip. + } + sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref. + SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()), + &inClipContentEntry.entry()->fContent); + + SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1, + &inClipContentEntry.entry()->fContent); + + sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState(); + sMaskGS->unref(); // SkRefPtr and getSMaskGraphicState both took a ref. + SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()), + &inClipContentEntry.entry()->fContent); +} + +bool SkPDFDevice::isContentEmpty() { + if (!fContentEntries.get() || fContentEntries->fContent.getOffset() == 0) { + SkASSERT(!fContentEntries.get() || !fContentEntries->fNext.get()); + return true; + } + return false; +} + + +void SkPDFDevice::populateGraphicStateEntryFromPaint( + const SkMatrix& matrix, + const SkClipStack& clipStack, + const SkRegion& clipRegion, + const SkPaint& paint, + bool hasText, + GraphicStateEntry* entry) { + SkASSERT(paint.getPathEffect() == NULL); + + NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false); + NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false); + + entry->fMatrix = matrix; + entry->fClipStack = clipStack; + entry->fClipRegion = clipRegion; + + // PDF treats a shader as a color, so we only set one or the other. + SkRefPtr<SkPDFShader> pdfShader; + const SkShader* shader = paint.getShader(); + SkColor color = paint.getColor(); + if (shader) { + // PDF positions patterns relative to the initial transform, so + // we need to apply the current transform to the shader parameters. + SkMatrix transform = matrix; + transform.postConcat(fInitialTransform); + + // PDF doesn't support kClamp_TileMode, so we simulate it by making + // a pattern the size of the current clip. + SkIRect bounds = clipRegion.getBounds(); + pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds); + SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref + + // A color shader is treated as an invalid shader so we don't have + // to set a shader just for a color. + if (pdfShader.get() == NULL) { + entry->fColor = 0; + color = 0; + + // Check for a color shader. + SkShader::GradientInfo gradientInfo; + SkColor gradientColor; + gradientInfo.fColors = &gradientColor; + gradientInfo.fColorOffsets = NULL; + gradientInfo.fColorCount = 1; + if (shader->asAGradient(&gradientInfo) == + SkShader::kColor_GradientType) { + entry->fColor = SkColorSetA(gradientColor, 0xFF); + color = gradientColor; + } + } + } + + if (pdfShader) { + // pdfShader has been canonicalized so we can directly compare + // pointers. + int resourceIndex = fShaderResources.find(pdfShader.get()); + if (resourceIndex < 0) { + resourceIndex = fShaderResources.count(); + fShaderResources.push(pdfShader.get()); + pdfShader->ref(); + } + entry->fShaderIndex = resourceIndex; + } else { + entry->fShaderIndex = -1; + entry->fColor = SkColorSetA(paint.getColor(), 0xFF); + color = paint.getColor(); + } + + SkRefPtr<SkPDFGraphicState> newGraphicState; + if (color == paint.getColor()) { + newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(paint); + } else { + SkPaint newPaint = paint; + newPaint.setColor(color); + newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint); + } + newGraphicState->unref(); // getGraphicState and SkRefPtr both took a ref. + int resourceIndex = addGraphicStateResource(newGraphicState.get()); + entry->fGraphicStateIndex = resourceIndex; + + if (hasText) { + entry->fTextScaleX = paint.getTextScaleX(); + entry->fTextFill = paint.getStyle(); + } else { + entry->fTextScaleX = 0; + } +} + +int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) { + // Assumes that gs has been canonicalized (so we can directly compare + // pointers). + int result = fGraphicStateResources.find(gs); + if (result < 0) { + result = fGraphicStateResources.count(); + fGraphicStateResources.push(gs); + gs->ref(); + } + return result; +} + +void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID, + ContentEntry* contentEntry) { + SkTypeface* typeface = paint.getTypeface(); + if (contentEntry->fState.fFont == NULL || + contentEntry->fState.fTextSize != paint.getTextSize() || + !contentEntry->fState.fFont->hasGlyph(glyphID)) { + int fontIndex = getFontResourceIndex(typeface, glyphID); + contentEntry->fContent.writeText("/F"); + contentEntry->fContent.writeDecAsText(fontIndex); + contentEntry->fContent.writeText(" "); + SkPDFScalar::Append(paint.getTextSize(), &contentEntry->fContent); + contentEntry->fContent.writeText(" Tf\n"); + contentEntry->fState.fFont = fFontResources[fontIndex]; + } +} + +int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) { + SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(typeface, glyphID); + newFont->unref(); // getFontResource and SkRefPtr both took a ref. + int resourceIndex = fFontResources.find(newFont.get()); + if (resourceIndex < 0) { + resourceIndex = fFontResources.count(); + fFontResources.push(newFont.get()); + newFont->ref(); + } + return resourceIndex; +} + +void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix, + const SkClipStack* clipStack, + const SkRegion& clipRegion, + const SkBitmap& bitmap, + const SkIRect* srcRect, + const SkPaint& paint) { + SkMatrix scaled; + // Adjust for origin flip. + scaled.setScale(1, -1); + scaled.postTranslate(0, 1); + // Scale the image up from 1x1 to WxH. + SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height()); + scaled.postScale(SkIntToScalar(subset.width()), + SkIntToScalar(subset.height())); + scaled.postConcat(matrix); + ScopedContentEntry content(this, clipStack, clipRegion, scaled, paint); + if (!content.entry()) { + return; + } + + if (srcRect && !subset.intersect(*srcRect)) { + return; + } + + SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint); + if (!image) { + return; + } + + fXObjectResources.push(image); // Transfer reference. + SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1, + &content.entry()->fContent); +} diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp new file mode 100644 index 0000000..95370b4 --- /dev/null +++ b/src/pdf/SkPDFDocument.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFDevice.h" +#include "SkPDFDocument.h" +#include "SkPDFPage.h" +#include "SkStream.h" + +// Add the resources, starting at firstIndex to the catalog, removing any dupes. +// A hash table would be really nice here. +void addResourcesToCatalog(int firstIndex, bool firstPage, + SkTDArray<SkPDFObject*>* resourceList, + SkPDFCatalog* catalog) { + for (int i = firstIndex; i < resourceList->count(); i++) { + int index = resourceList->find((*resourceList)[i]); + if (index != i) { + (*resourceList)[i]->unref(); + resourceList->removeShuffle(i); + i--; + } else { + catalog->addObject((*resourceList)[i], firstPage); + } + } +} + +SkPDFDocument::SkPDFDocument() : fXRefFileOffset(0) { + fDocCatalog = new SkPDFDict("Catalog"); + fDocCatalog->unref(); // SkRefPtr and new both took a reference. + fCatalog.addObject(fDocCatalog.get(), true); +} + +SkPDFDocument::~SkPDFDocument() { + fPages.safeUnrefAll(); + + // The page tree has both child and parent pointers, so it creates a + // reference cycle. We must clear that cycle to properly reclaim memory. + for (int i = 0; i < fPageTree.count(); i++) + fPageTree[i]->clear(); + fPageTree.safeUnrefAll(); + fPageResources.safeUnrefAll(); +} + +bool SkPDFDocument::emitPDF(SkWStream* stream) { + if (fPages.isEmpty()) + return false; + + // We haven't emitted the document before if fPageTree is empty. + if (fPageTree.count() == 0) { + SkPDFDict* pageTreeRoot; + SkPDFPage::generatePageTree(fPages, &fCatalog, &fPageTree, + &pageTreeRoot); + fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); + + /* TODO(vandebo) output intent + SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); + outputIntent->unref(); // SkRefPtr and new both took a reference. + outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); + outputIntent->insert("OutputConditionIdentifier", + new SkPDFString("sRGB"))->unref(); + SkRefPtr<SkPDFArray> intentArray = new SkPDFArray; + intentArray->unref(); // SkRefPtr and new both took a reference. + intentArray->append(outputIntent.get()); + fDocCatalog->insert("OutputIntent", intentArray.get()); + */ + + bool first_page = true; + for (int i = 0; i < fPages.count(); i++) { + int resourceCount = fPageResources.count(); + fPages[i]->finalizePage(&fCatalog, first_page, &fPageResources); + addResourcesToCatalog(resourceCount, first_page, &fPageResources, + &fCatalog); + if (i == 0) { + first_page = false; + fSecondPageFirstResourceIndex = fPageResources.count(); + } + } + + // Figure out the size of things and inform the catalog of file offsets. + off_t fileOffset = headerSize(); + fileOffset += fCatalog.setFileOffset(fDocCatalog.get(), fileOffset); + fileOffset += fCatalog.setFileOffset(fPages[0], fileOffset); + fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset); + for (int i = 0; i < fSecondPageFirstResourceIndex; i++) + fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset); + if (fPages.count() > 1) { + // TODO(vandebo) For linearized format, save the start of the + // first page xref table and calculate the size. + } + + for (int i = 0; i < fPageTree.count(); i++) + fileOffset += fCatalog.setFileOffset(fPageTree[i], fileOffset); + + for (int i = 1; i < fPages.count(); i++) + fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset); + + for (int i = fSecondPageFirstResourceIndex; + i < fPageResources.count(); + i++) + fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset); + + fXRefFileOffset = fileOffset; + } + + emitHeader(stream); + fDocCatalog->emitObject(stream, &fCatalog, true); + fPages[0]->emitObject(stream, &fCatalog, true); + fPages[0]->emitPage(stream, &fCatalog); + for (int i = 0; i < fSecondPageFirstResourceIndex; i++) + fPageResources[i]->emitObject(stream, &fCatalog, true); + // TODO(vandebo) support linearized format + //if (fPages.size() > 1) { + // // TODO(vandebo) save the file offset for the first page xref table. + // fCatalog.emitXrefTable(stream, true); + //} + + for (int i = 0; i < fPageTree.count(); i++) + fPageTree[i]->emitObject(stream, &fCatalog, true); + + for (int i = 1; i < fPages.count(); i++) + fPages[i]->emitPage(stream, &fCatalog); + + for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++) + fPageResources[i]->emitObject(stream, &fCatalog, true); + + int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1); + emitFooter(stream, objCount); + return true; +} + +bool SkPDFDocument::appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice) { + if (fPageTree.count() != 0) + return false; + + SkPDFPage* page = new SkPDFPage(pdfDevice); + fPages.push(page); // Reference from new passed to fPages. + // The rest of the pages will be added to the catalog along with the rest + // of the page tree. But the first page has to be marked as such, so we + // handle it here. + if (fPages.count() == 1) + fCatalog.addObject(page, true); + return true; +} + +const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() { + return fPages; +} + +void SkPDFDocument::emitHeader(SkWStream* stream) { + stream->writeText("%PDF-1.4\n%"); + // The PDF spec recommends including a comment with four bytes, all + // with their high bits set. This is "Skia" with the high bits set. + stream->write32(0xD3EBE9E1); + stream->writeText("\n"); +} + +size_t SkPDFDocument::headerSize() { + SkDynamicMemoryWStream buffer; + emitHeader(&buffer); + return buffer.getOffset(); +} + +void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { + if (fTrailerDict.get() == NULL) { + fTrailerDict = new SkPDFDict(); + fTrailerDict->unref(); // SkRefPtr and new both took a reference. + + // TODO(vandebo) Linearized format will take a Prev entry too. + // TODO(vandebo) PDF/A requires an ID entry. + fTrailerDict->insert("Size", new SkPDFInt(objCount))->unref(); + fTrailerDict->insert("Root", + new SkPDFObjRef(fDocCatalog.get()))->unref(); + } + + stream->writeText("trailer\n"); + fTrailerDict->emitObject(stream, &fCatalog, false); + stream->writeText("\nstartxref\n"); + stream->writeBigDecAsText(fXRefFileOffset); + stream->writeText("\n%%EOF"); +} diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp new file mode 100755 index 0000000..277ed12 --- /dev/null +++ b/src/pdf/SkPDFFont.cpp @@ -0,0 +1,976 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> + +#include "SkFontHost.h" +#include "SkGlyphCache.h" +#include "SkPaint.h" +#include "SkPDFDevice.h" +#include "SkPDFFont.h" +#include "SkPDFStream.h" +#include "SkPDFTypes.h" +#include "SkPDFUtils.h" +#include "SkRefCnt.h" +#include "SkScalar.h" +#include "SkStream.h" +#include "SkTypeface.h" +#include "SkTypes.h" +#include "SkUtils.h" + +namespace { + +bool parsePFBSection(const uint8_t** src, size_t* len, int sectionType, + size_t* size) { + // PFB sections have a two or six bytes header. 0x80 and a one byte + // section type followed by a four byte section length. Type one is + // an ASCII section (includes a length), type two is a binary section + // (includes a length) and type three is an EOF marker with no length. + const uint8_t* buf = *src; + if (*len < 2 || buf[0] != 0x80 || buf[1] != sectionType) + return false; + if (buf[1] == 3) + return true; + if (*len < 6) + return false; + + *size = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24); + size_t consumed = *size + 6; + if (consumed > *len) + return false; + *src = *src + consumed; + *len = *len - consumed; + return true; +} + +bool parsePFB(const uint8_t* src, size_t size, size_t* headerLen, + size_t* dataLen, size_t* trailerLen) { + const uint8_t* srcPtr = src; + size_t remaining = size; + + return parsePFBSection(&srcPtr, &remaining, 1, headerLen) && + parsePFBSection(&srcPtr, &remaining, 2, dataLen) && + parsePFBSection(&srcPtr, &remaining, 1, trailerLen) && + parsePFBSection(&srcPtr, &remaining, 3, NULL); +} + +/* The sections of a PFA file are implicitly defined. The body starts + * after the line containing "eexec," and the trailer starts with 512 + * literal 0's followed by "cleartomark" (plus arbitrary white space). + * + * This function assumes that src is NUL terminated, but the NUL + * termination is not included in size. + * + */ +bool parsePFA(const char* src, size_t size, size_t* headerLen, + size_t* hexDataLen, size_t* dataLen, size_t* trailerLen) { + const char* end = src + size; + + const char* dataPos = strstr(src, "eexec"); + if (!dataPos) + return false; + dataPos += strlen("eexec"); + while ((*dataPos == '\n' || *dataPos == '\r' || *dataPos == ' ') && + dataPos < end) + dataPos++; + *headerLen = dataPos - src; + + const char* trailerPos = strstr(dataPos, "cleartomark"); + if (!trailerPos) + return false; + int zeroCount = 0; + for (trailerPos--; trailerPos > dataPos && zeroCount < 512; trailerPos--) { + if (*trailerPos == '\n' || *trailerPos == '\r' || *trailerPos == ' ') { + continue; + } else if (*trailerPos == '0') { + zeroCount++; + } else { + return false; + } + } + if (zeroCount != 512) + return false; + + *hexDataLen = trailerPos - src - *headerLen; + *trailerLen = size - *headerLen - *hexDataLen; + + // Verify that the data section is hex encoded and count the bytes. + int nibbles = 0; + for (; dataPos < trailerPos; dataPos++) { + if (isspace(*dataPos)) + continue; + if (!isxdigit(*dataPos)) + return false; + nibbles++; + } + *dataLen = (nibbles + 1) / 2; + + return true; +} + +int8_t hexToBin(uint8_t c) { + if (!isxdigit(c)) + return -1; + if (c <= '9') return c - '0'; + if (c <= 'F') return c - 'A' + 10; + if (c <= 'f') return c - 'a' + 10; + return -1; +} + +SkStream* handleType1Stream(SkStream* srcStream, size_t* headerLen, + size_t* dataLen, size_t* trailerLen) { + // srcStream may be backed by a file or a unseekable fd, so we may not be + // able to use skip(), rewind(), or getMemoryBase(). read()ing through + // the input only once is doable, but very ugly. Furthermore, it'd be nice + // if the data was NUL terminated so that we can use strstr() to search it. + // Make as few copies as possible given these constraints. + SkDynamicMemoryWStream dynamicStream; + SkRefPtr<SkMemoryStream> staticStream; + const uint8_t* src; + size_t srcLen; + if ((srcLen = srcStream->getLength()) > 0) { + staticStream = new SkMemoryStream(srcLen + 1); + staticStream->unref(); // new and SkRefPtr both took a ref. + src = (const uint8_t*)staticStream->getMemoryBase(); + if (srcStream->getMemoryBase() != NULL) { + memcpy((void *)src, srcStream->getMemoryBase(), srcLen); + } else { + size_t read = 0; + while (read < srcLen) { + size_t got = srcStream->read((void *)staticStream->getAtPos(), + srcLen - read); + if (got == 0) + return NULL; + read += got; + staticStream->seek(read); + } + } + ((uint8_t *)src)[srcLen] = 0; + } else { + static const size_t bufSize = 4096; + uint8_t buf[bufSize]; + size_t amount; + while ((amount = srcStream->read(buf, bufSize)) > 0) + dynamicStream.write(buf, amount); + amount = 0; + dynamicStream.write(&amount, 1); // NULL terminator. + // getStream makes another copy, but we couldn't do any better. + src = (const uint8_t*)dynamicStream.getStream(); + srcLen = dynamicStream.getOffset() - 1; + } + + if (parsePFB(src, srcLen, headerLen, dataLen, trailerLen)) { + SkMemoryStream* result = + new SkMemoryStream(*headerLen + *dataLen + *trailerLen); + memcpy((char*)result->getAtPos(), src + 6, *headerLen); + result->seek(*headerLen); + memcpy((char*)result->getAtPos(), src + 6 + *headerLen + 6, *dataLen); + result->seek(*headerLen + *dataLen); + memcpy((char*)result->getAtPos(), src + 6 + *headerLen + 6 + *dataLen, + *trailerLen); + result->rewind(); + return result; + } + + // A PFA has to be converted for PDF. + size_t hexDataLen; + if (parsePFA((const char*)src, srcLen, headerLen, &hexDataLen, dataLen, + trailerLen)) { + SkMemoryStream* result = + new SkMemoryStream(*headerLen + *dataLen + *trailerLen); + memcpy((char*)result->getAtPos(), src, *headerLen); + result->seek(*headerLen); + + const uint8_t* hexData = src + *headerLen; + const uint8_t* trailer = hexData + hexDataLen; + size_t outputOffset = 0; + uint8_t dataByte = 0; // To hush compiler. + bool highNibble = true; + for (; hexData < trailer; hexData++) { + char curNibble = hexToBin(*hexData); + if (curNibble < 0) + continue; + if (highNibble) { + dataByte = curNibble << 4; + highNibble = false; + } else { + dataByte |= curNibble; + highNibble = true; + ((char *)result->getAtPos())[outputOffset++] = dataByte; + } + } + if (!highNibble) + ((char *)result->getAtPos())[outputOffset++] = dataByte; + SkASSERT(outputOffset == *dataLen); + result->seek(*headerLen + outputOffset); + + memcpy((char *)result->getAtPos(), src + *headerLen + hexDataLen, + *trailerLen); + result->rewind(); + return result; + } + + return NULL; +} + +// scale from em-units to base-1000, returning as a SkScalar +SkScalar scaleFromFontUnits(int16_t val, uint16_t emSize) { + SkScalar scaled = SkIntToScalar(val); + if (emSize == 1000) { + return scaled; + } else { + return SkScalarMulDiv(scaled, 1000, emSize); + } +} + +void setGlyphWidthAndBoundingBox(SkScalar width, SkIRect box, + SkWStream* content) { + // Specify width and bounding box for the glyph. + SkPDFScalar::Append(width, content); + content->writeText(" 0 "); + content->writeDecAsText(box.fLeft); + content->writeText(" "); + content->writeDecAsText(box.fTop); + content->writeText(" "); + content->writeDecAsText(box.fRight); + content->writeText(" "); + content->writeDecAsText(box.fBottom); + content->writeText(" d1\n"); +} + +SkPDFArray* makeFontBBox(SkIRect glyphBBox, uint16_t emSize) { + SkPDFArray* bbox = new SkPDFArray; + bbox->reserve(4); + bbox->append(new SkPDFScalar(scaleFromFontUnits(glyphBBox.fLeft, + emSize)))->unref(); + bbox->append(new SkPDFScalar(scaleFromFontUnits(glyphBBox.fBottom, + emSize)))->unref(); + bbox->append(new SkPDFScalar(scaleFromFontUnits(glyphBBox.fRight, + emSize)))->unref(); + bbox->append(new SkPDFScalar(scaleFromFontUnits(glyphBBox.fTop, + emSize)))->unref(); + return bbox; +} + +SkPDFArray* appendWidth(const int16_t& width, uint16_t emSize, + SkPDFArray* array) { + array->append(new SkPDFScalar(scaleFromFontUnits(width, emSize)))->unref(); + return array; +} + +SkPDFArray* appendVerticalAdvance( + const SkAdvancedTypefaceMetrics::VerticalMetric& advance, + uint16_t emSize, SkPDFArray* array) { + appendWidth(advance.fVerticalAdvance, emSize, array); + appendWidth(advance.fOriginXDisp, emSize, array); + appendWidth(advance.fOriginYDisp, emSize, array); + return array; +} + +template <typename Data> +SkPDFArray* composeAdvanceData( + SkAdvancedTypefaceMetrics::AdvanceMetric<Data>* advanceInfo, + uint16_t emSize, + SkPDFArray* (*appendAdvance)(const Data& advance, uint16_t emSize, + SkPDFArray* array), + Data* defaultAdvance) { + SkPDFArray* result = new SkPDFArray(); + for (; advanceInfo != NULL; advanceInfo = advanceInfo->fNext.get()) { + switch (advanceInfo->fType) { + case SkAdvancedTypefaceMetrics::WidthRange::kDefault: { + SkASSERT(advanceInfo->fAdvance.count() == 1); + *defaultAdvance = advanceInfo->fAdvance[0]; + break; + } + case SkAdvancedTypefaceMetrics::WidthRange::kRange: { + SkRefPtr<SkPDFArray> advanceArray = new SkPDFArray(); + advanceArray->unref(); // SkRefPtr and new both took a ref. + for (int j = 0; j < advanceInfo->fAdvance.count(); j++) + appendAdvance(advanceInfo->fAdvance[j], emSize, + advanceArray.get()); + result->append(new SkPDFInt(advanceInfo->fStartId))->unref(); + result->append(advanceArray.get()); + break; + } + case SkAdvancedTypefaceMetrics::WidthRange::kRun: { + SkASSERT(advanceInfo->fAdvance.count() == 1); + result->append(new SkPDFInt(advanceInfo->fStartId))->unref(); + result->append(new SkPDFInt(advanceInfo->fEndId))->unref(); + appendAdvance(advanceInfo->fAdvance[0], emSize, result); + break; + } + } + } + return result; +} + +} // namespace + +static void append_tounicode_header(SkDynamicMemoryWStream* cmap) { + // 12 dict begin: 12 is an Adobe-suggested value. Shall not change. + // It's there to prevent old version Adobe Readers from malfunctioning. + const char* kHeader = + "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n"; + cmap->writeText(kHeader); + + // The /CIDSystemInfo must be consistent to the one in + // SkPDFFont::populateCIDFont(). + // We can not pass over the system info object here because the format is + // different. This is not a reference object. + const char* kSysInfo = + "/CIDSystemInfo\n" + "<< /Registry (Adobe)\n" + "/Ordering (UCS)\n" + "/Supplement 0\n" + ">> def\n"; + cmap->writeText(kSysInfo); + + // The CMapName must be consistent to /CIDSystemInfo above. + // /CMapType 2 means ToUnicode. + // We specify codespacerange from 0x0000 to 0xFFFF because we convert our + // code table from unsigned short (16-bits). Codespace range just tells the + // PDF processor the valid range. It does not matter whether a complete + // mapping is provided or not. + const char* kTypeInfo = + "/CMapName /Adobe-Identity-UCS def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n" + "<0000> <FFFF>\n" + "endcodespacerange\n"; + cmap->writeText(kTypeInfo); +} + +static void append_cmap_bfchar_table(uint16_t* glyph_id, SkUnichar* unicode, + size_t count, + SkDynamicMemoryWStream* cmap) { + cmap->writeDecAsText(count); + cmap->writeText(" beginbfchar\n"); + for (size_t i = 0; i < count; ++i) { + cmap->writeText("<"); + cmap->writeHexAsText(glyph_id[i], 4); + cmap->writeText("> <"); + cmap->writeHexAsText(unicode[i], 4); + cmap->writeText(">\n"); + } + cmap->writeText("endbfchar\n"); +} + +static void append_cmap_footer(SkDynamicMemoryWStream* cmap) { + const char* kFooter = + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end"; + cmap->writeText(kFooter); +} + +// Generate <bfchar> table according to PDF spec 1.4 and Adobe Technote 5014. +static void append_cmap_bfchar_sections( + const SkTDArray<SkUnichar>& glyphUnicode, + SkDynamicMemoryWStream* cmap) { + // PDF spec defines that every bf* list can have at most 100 entries. + const size_t kMaxEntries = 100; + uint16_t glyphId[kMaxEntries]; + SkUnichar unicode[kMaxEntries]; + size_t index = 0; + for (int i = 0; i < glyphUnicode.count(); i++) { + if (glyphUnicode[i]) { + glyphId[index] = i; + unicode[index] = glyphUnicode[i]; + ++index; + } + if (index == kMaxEntries) { + append_cmap_bfchar_table(glyphId, unicode, index, cmap); + index = 0; + } + } + + if (index) { + append_cmap_bfchar_table(glyphId, unicode, index, cmap); + } +} + +/* Font subset design: It would be nice to be able to subset fonts + * (particularly type 3 fonts), but it's a lot of work and not a priority. + * + * Resources are canonicalized and uniqueified by pointer so there has to be + * some additional state indicating which subset of the font is used. It + * must be maintained at the page granularity and then combined at the document + * granularity. a) change SkPDFFont to fill in its state on demand, kind of + * like SkPDFGraphicState. b) maintain a per font glyph usage class in each + * page/pdf device. c) in the document, retrieve the per font glyph usage + * from each page and combine it and ask for a resource with that subset. + */ + +SkPDFFont::~SkPDFFont() { + SkAutoMutexAcquire lock(canonicalFontsMutex()); + int index; + if (find(SkTypeface::UniqueID(fTypeface.get()), fFirstGlyphID, &index)) { + canonicalFonts().removeShuffle(index); +#ifdef SK_DEBUG + SkASSERT(!fDescendant); + } else { + SkASSERT(fDescendant); +#endif + } + fResources.unrefAll(); +} + +void SkPDFFont::getResources(SkTDArray<SkPDFObject*>* resourceList) { + resourceList->setReserve(resourceList->count() + fResources.count()); + for (int i = 0; i < fResources.count(); i++) { + resourceList->push(fResources[i]); + fResources[i]->ref(); + fResources[i]->getResources(resourceList); + } +} + +SkTypeface* SkPDFFont::typeface() { + return fTypeface.get(); +} + +SkAdvancedTypefaceMetrics::FontType SkPDFFont::getType() { + return fType; +} + +bool SkPDFFont::hasGlyph(uint16_t id) { + return (id >= fFirstGlyphID && id <= fLastGlyphID) || id == 0; +} + +bool SkPDFFont::multiByteGlyphs() { + return fMultiByteGlyphs; +} + +size_t SkPDFFont::glyphsToPDFFontEncoding(uint16_t* glyphIDs, + size_t numGlyphs) { + // A font with multibyte glyphs will support all glyph IDs in a single font. + if (fMultiByteGlyphs) { + return numGlyphs; + } + + for (size_t i = 0; i < numGlyphs; i++) { + if (glyphIDs[i] == 0) { + continue; + } + if (glyphIDs[i] < fFirstGlyphID || glyphIDs[i] > fLastGlyphID) { + return i; + } + glyphIDs[i] -= (fFirstGlyphID - 1); + } + + return numGlyphs; +} + +// static +SkPDFFont* SkPDFFont::getFontResource(SkTypeface* typeface, uint16_t glyphID) { + SkAutoMutexAcquire lock(canonicalFontsMutex()); + const uint32_t fontID = SkTypeface::UniqueID(typeface); + int index; + if (find(fontID, glyphID, &index)) { + canonicalFonts()[index].fFont->ref(); + return canonicalFonts()[index].fFont; + } + + SkRefPtr<SkAdvancedTypefaceMetrics> fontInfo; + SkPDFDict* fontDescriptor = NULL; + if (index >= 0) { + SkPDFFont* relatedFont = canonicalFonts()[index].fFont; + SkASSERT(relatedFont->fFontInfo.get()); + fontInfo = relatedFont->fFontInfo; + fontDescriptor = relatedFont->fDescriptor.get(); + } else { + SkAdvancedTypefaceMetrics::PerGlyphInfo info; + info = SkAdvancedTypefaceMetrics::kHAdvance_PerGlyphInfo; + info = SkTBitOr<SkAdvancedTypefaceMetrics::PerGlyphInfo>( + info, SkAdvancedTypefaceMetrics::kGlyphNames_PerGlyphInfo); + info = SkTBitOr<SkAdvancedTypefaceMetrics::PerGlyphInfo>( + info, SkAdvancedTypefaceMetrics::kToUnicode_PerGlyphInfo); + fontInfo = SkFontHost::GetAdvancedTypefaceMetrics(fontID, info); + SkSafeUnref(fontInfo.get()); // SkRefPtr and Get both took a reference. + } + + SkPDFFont* font = new SkPDFFont(fontInfo.get(), typeface, glyphID, false, + fontDescriptor); + FontRec newEntry(font, fontID, font->fFirstGlyphID); + index = canonicalFonts().count(); + canonicalFonts().push(newEntry); + return font; // Return the reference new SkPDFFont() created. +} + +// static +SkTDArray<SkPDFFont::FontRec>& SkPDFFont::canonicalFonts() { + // This initialization is only thread safe with gcc. + static SkTDArray<FontRec> gCanonicalFonts; + return gCanonicalFonts; +} + +// static +SkMutex& SkPDFFont::canonicalFontsMutex() { + // This initialization is only thread safe with gcc. + static SkMutex gCanonicalFontsMutex; + return gCanonicalFontsMutex; +} + +// static +bool SkPDFFont::find(uint32_t fontID, uint16_t glyphID, int* index) { + // TODO(vandebo) optimize this, do only one search? + FontRec search(NULL, fontID, glyphID); + *index = canonicalFonts().find(search); + if (*index >= 0) + return true; + search.fGlyphID = 0; + *index = canonicalFonts().find(search); + return false; +} + +SkPDFFont::SkPDFFont(class SkAdvancedTypefaceMetrics* fontInfo, + SkTypeface* typeface, + uint16_t glyphID, + bool descendantFont, + SkPDFDict* fontDescriptor) + : SkPDFDict("Font"), + fTypeface(typeface), + fType(fontInfo ? fontInfo->fType : + SkAdvancedTypefaceMetrics::kNotEmbeddable_Font), +#ifdef SK_DEBUG + fDescendant(descendantFont), +#endif + fMultiByteGlyphs(false), + fFirstGlyphID(1), + fLastGlyphID(fontInfo ? fontInfo->fLastGlyphID : 0), + fFontInfo(fontInfo), + fDescriptor(fontDescriptor) { + if (fontInfo && fontInfo->fMultiMaster) { + NOT_IMPLEMENTED(true, true); + fType = SkAdvancedTypefaceMetrics::kOther_Font; + } + if (fType == SkAdvancedTypefaceMetrics::kType1CID_Font || + fType == SkAdvancedTypefaceMetrics::kTrueType_Font) { + if (descendantFont) { + populateCIDFont(); + } else { + populateType0Font(); + } + // No need to hold onto the font info for fonts types that + // support multibyte glyphs. + fFontInfo = NULL; + return; + } + + if (fType == SkAdvancedTypefaceMetrics::kType1_Font && + populateType1Font(glyphID)) { + return; + } + + SkASSERT(fType == SkAdvancedTypefaceMetrics::kType1_Font || + fType == SkAdvancedTypefaceMetrics::kCFF_Font || + fType == SkAdvancedTypefaceMetrics::kOther_Font || + fType == SkAdvancedTypefaceMetrics::kNotEmbeddable_Font); + populateType3Font(glyphID); +} + +void SkPDFFont::populateType0Font() { + fMultiByteGlyphs = true; + + insert("Subtype", new SkPDFName("Type0"))->unref(); + insert("BaseFont", new SkPDFName(fFontInfo->fFontName))->unref(); + insert("Encoding", new SkPDFName("Identity-H"))->unref(); + + SkRefPtr<SkPDFArray> descendantFonts = new SkPDFArray(); + descendantFonts->unref(); // SkRefPtr and new took a reference. + + // Pass ref new created to fResources. + fResources.push( + new SkPDFFont(fFontInfo.get(), fTypeface.get(), 1, true, NULL)); + descendantFonts->append(new SkPDFObjRef(fResources.top()))->unref(); + insert("DescendantFonts", descendantFonts.get()); + + populateToUnicodeTable(); +} + +void SkPDFFont::populateToUnicodeTable() { + if (fFontInfo.get() == NULL || + fFontInfo->fGlyphToUnicode.begin() == NULL) { + return; + } + + SkDynamicMemoryWStream cmap; + append_tounicode_header(&cmap); + append_cmap_bfchar_sections(fFontInfo->fGlyphToUnicode, &cmap); + append_cmap_footer(&cmap); + SkRefPtr<SkMemoryStream> cmapStream = new SkMemoryStream(); + cmapStream->unref(); // SkRefPtr and new took a reference. + cmapStream->setMemoryOwned(cmap.detach(), cmap.getOffset()); + SkRefPtr<SkPDFStream> pdfCmap = new SkPDFStream(cmapStream.get()); + fResources.push(pdfCmap.get()); // Pass reference from new. + insert("ToUnicode", new SkPDFObjRef(pdfCmap.get()))->unref(); +} + +void SkPDFFont::populateCIDFont() { + fMultiByteGlyphs = true; + insert("BaseFont", new SkPDFName(fFontInfo->fFontName))->unref(); + + if (fFontInfo->fType == SkAdvancedTypefaceMetrics::kType1CID_Font) { + insert("Subtype", new SkPDFName("CIDFontType0"))->unref(); + } else if (fFontInfo->fType == SkAdvancedTypefaceMetrics::kTrueType_Font) { + insert("Subtype", new SkPDFName("CIDFontType2"))->unref(); + insert("CIDToGIDMap", new SkPDFName("Identity"))->unref(); + } else { + SkASSERT(false); + } + + SkRefPtr<SkPDFDict> sysInfo = new SkPDFDict; + sysInfo->unref(); // SkRefPtr and new both took a reference. + sysInfo->insert("Registry", new SkPDFString("Adobe"))->unref(); + sysInfo->insert("Ordering", new SkPDFString("Identity"))->unref(); + sysInfo->insert("Supplement", new SkPDFInt(0))->unref(); + insert("CIDSystemInfo", sysInfo.get()); + + addFontDescriptor(0); + + if (fFontInfo->fGlyphWidths.get()) { + int16_t defaultWidth = 0; + SkRefPtr<SkPDFArray> widths = + composeAdvanceData(fFontInfo->fGlyphWidths.get(), + fFontInfo->fEmSize, &appendWidth, &defaultWidth); + widths->unref(); // SkRefPtr and compose both took a reference. + if (widths->size()) + insert("W", widths.get()); + if (defaultWidth != 0) { + insert("DW", new SkPDFScalar(scaleFromFontUnits( + defaultWidth, fFontInfo->fEmSize)))->unref(); + } + } + if (fFontInfo->fVerticalMetrics.get()) { + struct SkAdvancedTypefaceMetrics::VerticalMetric defaultAdvance; + defaultAdvance.fVerticalAdvance = 0; + defaultAdvance.fOriginXDisp = 0; + defaultAdvance.fOriginYDisp = 0; + SkRefPtr<SkPDFArray> advances = + composeAdvanceData(fFontInfo->fVerticalMetrics.get(), + fFontInfo->fEmSize, &appendVerticalAdvance, + &defaultAdvance); + advances->unref(); // SkRefPtr and compose both took a ref. + if (advances->size()) + insert("W2", advances.get()); + if (defaultAdvance.fVerticalAdvance || + defaultAdvance.fOriginXDisp || + defaultAdvance.fOriginYDisp) { + insert("DW2", appendVerticalAdvance(defaultAdvance, + fFontInfo->fEmSize, + new SkPDFArray))->unref(); + } + } +} + +bool SkPDFFont::populateType1Font(int16_t glyphID) { + SkASSERT(!fFontInfo->fVerticalMetrics.get()); + SkASSERT(fFontInfo->fGlyphWidths.get()); + + adjustGlyphRangeForSingleByteEncoding(glyphID); + + int16_t defaultWidth = 0; + const SkAdvancedTypefaceMetrics::WidthRange* widthRangeEntry = NULL; + const SkAdvancedTypefaceMetrics::WidthRange* widthEntry; + for (widthEntry = fFontInfo.get()->fGlyphWidths.get(); + widthEntry != NULL; + widthEntry = widthEntry->fNext.get()) { + switch (widthEntry->fType) { + case SkAdvancedTypefaceMetrics::WidthRange::kDefault: + defaultWidth = widthEntry->fAdvance[0]; + break; + case SkAdvancedTypefaceMetrics::WidthRange::kRun: + SkASSERT(false); + break; + case SkAdvancedTypefaceMetrics::WidthRange::kRange: + SkASSERT(widthRangeEntry == NULL); + widthRangeEntry = widthEntry; + break; + } + } + + if (!addFontDescriptor(defaultWidth)) + return false; + + insert("Subtype", new SkPDFName("Type1"))->unref(); + insert("BaseFont", new SkPDFName(fFontInfo->fFontName))->unref(); + + addWidthInfoFromRange(defaultWidth, widthRangeEntry); + + SkRefPtr<SkPDFDict> encoding = new SkPDFDict("Encoding"); + encoding->unref(); // SkRefPtr and new both took a reference. + insert("Encoding", encoding.get()); + + SkRefPtr<SkPDFArray> encDiffs = new SkPDFArray; + encDiffs->unref(); // SkRefPtr and new both took a reference. + encoding->insert("Differences", encDiffs.get()); + + encDiffs->reserve(fLastGlyphID - fFirstGlyphID + 2); + encDiffs->append(new SkPDFInt(1))->unref(); + for (int gID = fFirstGlyphID; gID <= fLastGlyphID; gID++) { + encDiffs->append( + new SkPDFName(fFontInfo->fGlyphNames->get()[gID]))->unref(); + } + + if (fFontInfo->fLastGlyphID <= 255) + fFontInfo = NULL; + return true; +} + +void SkPDFFont::populateType3Font(int16_t glyphID) { + SkPaint paint; + paint.setTypeface(fTypeface.get()); + paint.setTextSize(1000); + SkAutoGlyphCache autoCache(paint, NULL); + SkGlyphCache* cache = autoCache.getCache(); + // If fLastGlyphID isn't set (because there is not fFontInfo), look it up. + if (fLastGlyphID == 0) { + fLastGlyphID = cache->getGlyphCount() - 1; + } + + adjustGlyphRangeForSingleByteEncoding(glyphID); + + insert("Subtype", new SkPDFName("Type3"))->unref(); + // Flip about the x-axis and scale by 1/1000. + SkMatrix fontMatrix; + fontMatrix.setScale(SkScalarInvert(1000), -SkScalarInvert(1000)); + insert("FontMatrix", SkPDFUtils::MatrixToArray(fontMatrix))->unref(); + + SkRefPtr<SkPDFDict> charProcs = new SkPDFDict; + charProcs->unref(); // SkRefPtr and new both took a reference. + insert("CharProcs", charProcs.get()); + + SkRefPtr<SkPDFDict> encoding = new SkPDFDict("Encoding"); + encoding->unref(); // SkRefPtr and new both took a reference. + insert("Encoding", encoding.get()); + + SkRefPtr<SkPDFArray> encDiffs = new SkPDFArray; + encDiffs->unref(); // SkRefPtr and new both took a reference. + encoding->insert("Differences", encDiffs.get()); + encDiffs->reserve(fLastGlyphID - fFirstGlyphID + 2); + encDiffs->append(new SkPDFInt(1))->unref(); + + SkRefPtr<SkPDFArray> widthArray = new SkPDFArray(); + widthArray->unref(); // SkRefPtr and new both took a ref. + + SkIRect bbox = SkIRect::MakeEmpty(); + for (int gID = fFirstGlyphID; gID <= fLastGlyphID; gID++) { + SkString characterName; + characterName.printf("gid%d", gID); + encDiffs->append(new SkPDFName(characterName))->unref(); + + const SkGlyph& glyph = cache->getGlyphIDMetrics(gID); + widthArray->append(new SkPDFScalar(SkFixedToScalar(glyph.fAdvanceX)))->unref(); + SkIRect glyphBBox = SkIRect::MakeXYWH(glyph.fLeft, glyph.fTop, + glyph.fWidth, glyph.fHeight); + bbox.join(glyphBBox); + + SkDynamicMemoryWStream content; + setGlyphWidthAndBoundingBox(SkFixedToScalar(glyph.fAdvanceX), glyphBBox, + &content); + const SkPath* path = cache->findPath(glyph); + if (path) { + SkPDFUtils::EmitPath(*path, &content); + SkPDFUtils::PaintPath(paint.getStyle(), path->getFillType(), + &content); + } + SkRefPtr<SkMemoryStream> glyphStream = new SkMemoryStream(); + glyphStream->unref(); // SkRefPtr and new both took a ref. + glyphStream->setMemoryOwned(content.detach(), content.getOffset()); + + SkRefPtr<SkPDFStream> glyphDescription = + new SkPDFStream(glyphStream.get()); + // SkRefPtr and new both ref()'d charProcs, pass one. + fResources.push(glyphDescription.get()); + charProcs->insert(characterName.c_str(), + new SkPDFObjRef(glyphDescription.get()))->unref(); + } + + insert("FontBBox", makeFontBBox(bbox, 1000))->unref(); + insert("FirstChar", new SkPDFInt(fFirstGlyphID))->unref(); + insert("LastChar", new SkPDFInt(fLastGlyphID))->unref(); + insert("Widths", widthArray.get()); + insert("CIDToGIDMap", new SkPDFName("Identity"))->unref(); + + if (fFontInfo && fFontInfo->fLastGlyphID <= 255) + fFontInfo = NULL; + + populateToUnicodeTable(); +} + +bool SkPDFFont::addFontDescriptor(int16_t defaultWidth) { + if (fDescriptor.get() != NULL) { + fResources.push(fDescriptor.get()); + fDescriptor->ref(); + insert("FontDescriptor", new SkPDFObjRef(fDescriptor.get()))->unref(); + return true; + } + + fDescriptor = new SkPDFDict("FontDescriptor"); + fDescriptor->unref(); // SkRefPtr and new both took a ref. + + switch (fFontInfo->fType) { + case SkAdvancedTypefaceMetrics::kType1_Font: { + size_t header SK_INIT_TO_AVOID_WARNING; + size_t data SK_INIT_TO_AVOID_WARNING; + size_t trailer SK_INIT_TO_AVOID_WARNING; + SkRefPtr<SkStream> rawFontData = + SkFontHost::OpenStream(SkTypeface::UniqueID(fTypeface.get())); + rawFontData->unref(); // SkRefPtr and OpenStream both took a ref. + SkStream* fontData = handleType1Stream(rawFontData.get(), &header, + &data, &trailer); + if (fontData == NULL) + return false; + SkRefPtr<SkPDFStream> fontStream = new SkPDFStream(fontData); + // SkRefPtr and new both ref()'d fontStream, pass one. + fResources.push(fontStream.get()); + fontStream->insert("Length1", new SkPDFInt(header))->unref(); + fontStream->insert("Length2", new SkPDFInt(data))->unref(); + fontStream->insert("Length3", new SkPDFInt(trailer))->unref(); + fDescriptor->insert("FontFile", + new SkPDFObjRef(fontStream.get()))->unref(); + break; + } + case SkAdvancedTypefaceMetrics::kTrueType_Font: { + SkRefPtr<SkStream> fontData = + SkFontHost::OpenStream(SkTypeface::UniqueID(fTypeface.get())); + fontData->unref(); // SkRefPtr and OpenStream both took a ref. + SkRefPtr<SkPDFStream> fontStream = new SkPDFStream(fontData.get()); + // SkRefPtr and new both ref()'d fontStream, pass one. + fResources.push(fontStream.get()); + + fontStream->insert("Length1", + new SkPDFInt(fontData->getLength()))->unref(); + fDescriptor->insert("FontFile2", + new SkPDFObjRef(fontStream.get()))->unref(); + break; + } + case SkAdvancedTypefaceMetrics::kCFF_Font: + case SkAdvancedTypefaceMetrics::kType1CID_Font: { + SkRefPtr<SkStream> fontData = + SkFontHost::OpenStream(SkTypeface::UniqueID(fTypeface.get())); + fontData->unref(); // SkRefPtr and OpenStream both took a ref. + SkRefPtr<SkPDFStream> fontStream = new SkPDFStream(fontData.get()); + // SkRefPtr and new both ref()'d fontStream, pass one. + fResources.push(fontStream.get()); + + if (fFontInfo->fType == SkAdvancedTypefaceMetrics::kCFF_Font) { + fontStream->insert("Subtype", new SkPDFName("Type1C"))->unref(); + } else { + fontStream->insert("Subtype", + new SkPDFName("CIDFontType0c"))->unref(); + } + fDescriptor->insert("FontFile3", + new SkPDFObjRef(fontStream.get()))->unref(); + break; + } + default: + SkASSERT(false); + } + + const uint16_t emSize = fFontInfo->fEmSize; + fResources.push(fDescriptor.get()); + fDescriptor->ref(); + insert("FontDescriptor", new SkPDFObjRef(fDescriptor.get()))->unref(); + + fDescriptor->insert("FontName", new SkPDFName( + fFontInfo->fFontName))->unref(); + fDescriptor->insert("Flags", new SkPDFInt(fFontInfo->fStyle))->unref(); + fDescriptor->insert("Ascent", new SkPDFScalar( + scaleFromFontUnits(fFontInfo->fAscent, emSize)))->unref(); + fDescriptor->insert("Descent", new SkPDFScalar( + scaleFromFontUnits(fFontInfo->fDescent, emSize)))->unref(); + fDescriptor->insert("StemV", new SkPDFScalar( + scaleFromFontUnits(fFontInfo->fStemV, emSize)))->unref(); + fDescriptor->insert("CapHeight", new SkPDFScalar( + scaleFromFontUnits(fFontInfo->fCapHeight, emSize)))->unref(); + fDescriptor->insert("ItalicAngle", new SkPDFInt( + fFontInfo->fItalicAngle))->unref(); + fDescriptor->insert("FontBBox", makeFontBBox(fFontInfo->fBBox, + fFontInfo->fEmSize))->unref(); + + if (defaultWidth > 0) { + fDescriptor->insert("MissingWidth", new SkPDFScalar( + scaleFromFontUnits(defaultWidth, emSize)))->unref(); + } + return true; +} +void SkPDFFont::addWidthInfoFromRange( + int16_t defaultWidth, + const SkAdvancedTypefaceMetrics::WidthRange* widthRangeEntry) { + SkRefPtr<SkPDFArray> widthArray = new SkPDFArray(); + widthArray->unref(); // SkRefPtr and new both took a ref. + int firstChar = 0; + if (widthRangeEntry) { + const uint16_t emSize = fFontInfo->fEmSize; + int startIndex = fFirstGlyphID - widthRangeEntry->fStartId; + int endIndex = startIndex + fLastGlyphID - fFirstGlyphID + 1; + if (startIndex < 0) + startIndex = 0; + if (endIndex > widthRangeEntry->fAdvance.count()) + endIndex = widthRangeEntry->fAdvance.count(); + if (widthRangeEntry->fStartId == 0) { + appendWidth(widthRangeEntry->fAdvance[0], emSize, widthArray.get()); + } else { + firstChar = startIndex + widthRangeEntry->fStartId; + } + for (int i = startIndex; i < endIndex; i++) + appendWidth(widthRangeEntry->fAdvance[i], emSize, widthArray.get()); + } else { + appendWidth(defaultWidth, 1000, widthArray.get()); + } + insert("FirstChar", new SkPDFInt(firstChar))->unref(); + insert("LastChar", + new SkPDFInt(firstChar + widthArray->size() - 1))->unref(); + insert("Widths", widthArray.get()); +} + +void SkPDFFont::adjustGlyphRangeForSingleByteEncoding(int16_t glyphID) { + // Single byte glyph encoding supports a max of 255 glyphs. + fFirstGlyphID = glyphID - (glyphID - 1) % 255; + if (fLastGlyphID > fFirstGlyphID + 255 - 1) { + fLastGlyphID = fFirstGlyphID + 255 - 1; + } +} + + +bool SkPDFFont::FontRec::operator==(const SkPDFFont::FontRec& b) const { + if (fFontID != b.fFontID) + return false; + if (fFont != NULL && b.fFont != NULL) { + return fFont->fFirstGlyphID == b.fFont->fFirstGlyphID && + fFont->fLastGlyphID == b.fFont->fLastGlyphID; + } + if (fGlyphID == 0 || b.fGlyphID == 0) + return true; + + if (fFont != NULL) { + return fFont->fFirstGlyphID <= b.fGlyphID && + b.fGlyphID <= fFont->fLastGlyphID; + } else if (b.fFont != NULL) { + return b.fFont->fFirstGlyphID <= fGlyphID && + fGlyphID <= b.fFont->fLastGlyphID; + } + return fGlyphID == b.fGlyphID; +} + +SkPDFFont::FontRec::FontRec(SkPDFFont* font, uint32_t fontID, uint16_t glyphID) + : fFont(font), + fFontID(fontID), + fGlyphID(glyphID) { +} diff --git a/src/pdf/SkPDFFormXObject.cpp b/src/pdf/SkPDFFormXObject.cpp new file mode 100644 index 0000000..40a5564 --- /dev/null +++ b/src/pdf/SkPDFFormXObject.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFFormXObject.h" + +#include "SkMatrix.h" +#include "SkPDFCatalog.h" +#include "SkPDFDevice.h" +#include "SkPDFUtils.h" +#include "SkStream.h" +#include "SkTypes.h" + +SkPDFFormXObject::SkPDFFormXObject(SkPDFDevice* device) { + // We don't want to keep around device because we'd have two copies + // of content, so reference or copy everything we need (content and + // resources). + device->getResources(&fResources); + + SkRefPtr<SkStream> content = device->content(); + content->unref(); // SkRefPtr and content() both took a reference. + fStream = new SkPDFStream(content.get()); + fStream->unref(); // SkRefPtr and new both took a reference. + + insert("Type", new SkPDFName("XObject"))->unref(); + insert("Subtype", new SkPDFName("Form"))->unref(); + insert("BBox", device->getMediaBox().get()); + insert("Resources", device->getResourceDict().get()); + + // We invert the initial transform and apply that to the xobject so that + // it doesn't get applied twice. We can't just undo it because it's + // embedded in things like shaders and images. + if (!device->initialTransform().isIdentity()) { + SkMatrix inverse; + inverse.reset(); + device->initialTransform().invert(&inverse); + insert("Matrix", SkPDFUtils::MatrixToArray(inverse))->unref(); + } + + // Right now SkPDFFormXObject is only used for saveLayer, which implies + // isolated blending. Do this conditionally if that changes. + SkRefPtr<SkPDFDict> group = new SkPDFDict("Group"); + group->unref(); // SkRefPtr and new both took a reference. + group->insert("S", new SkPDFName("Transparency"))->unref(); + group->insert("I", new SkPDFBool(true))->unref(); // Isolated. + insert("Group", group.get()); +} + +SkPDFFormXObject::~SkPDFFormXObject() { + fResources.unrefAll(); +} + +void SkPDFFormXObject::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + fStream->emitObject(stream, catalog, indirect); +} + +size_t SkPDFFormXObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + + return fStream->getOutputSize(catalog, indirect); +} + +void SkPDFFormXObject::getResources(SkTDArray<SkPDFObject*>* resourceList) { + resourceList->setReserve(resourceList->count() + fResources.count()); + for (int i = 0; i < fResources.count(); i++) { + resourceList->push(fResources[i]); + fResources[i]->ref(); + } +} + +SkPDFObject* SkPDFFormXObject::insert(SkPDFName* key, SkPDFObject* value) { + return fStream->insert(key, value); +} + +SkPDFObject* SkPDFFormXObject::insert(const char key[], SkPDFObject* value) { + return fStream->insert(key, value); +} diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp new file mode 100644 index 0000000..b08bf24 --- /dev/null +++ b/src/pdf/SkPDFGraphicState.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFFormXObject.h" +#include "SkPDFGraphicState.h" +#include "SkPDFUtils.h" +#include "SkStream.h" +#include "SkTypes.h" + +static const char* blend_mode_from_xfermode(SkXfermode::Mode mode) { + switch (mode) { + case SkXfermode::kSrcOver_Mode: return "Normal"; + case SkXfermode::kMultiply_Mode: return "Multiply"; + case SkXfermode::kScreen_Mode: return "Screen"; + case SkXfermode::kOverlay_Mode: return "Overlay"; + case SkXfermode::kDarken_Mode: return "Darken"; + case SkXfermode::kLighten_Mode: return "Lighten"; + case SkXfermode::kColorDodge_Mode: return "ColorDodge"; + case SkXfermode::kColorBurn_Mode: return "ColorBurn"; + case SkXfermode::kHardLight_Mode: return "HardLight"; + case SkXfermode::kSoftLight_Mode: return "SoftLight"; + case SkXfermode::kDifference_Mode: return "Difference"; + case SkXfermode::kExclusion_Mode: return "Exclusion"; + + // These are handled in SkPDFDevice::setUpContentEntry. + case SkXfermode::kClear_Mode: + case SkXfermode::kSrc_Mode: + case SkXfermode::kDst_Mode: + case SkXfermode::kDstOver_Mode: + case SkXfermode::kSrcIn_Mode: + case SkXfermode::kDstIn_Mode: + case SkXfermode::kSrcOut_Mode: + case SkXfermode::kDstOut_Mode: + return "Normal"; + + // TODO(vandebo) Figure out if we can support more of these modes. + case SkXfermode::kSrcATop_Mode: + case SkXfermode::kDstATop_Mode: + case SkXfermode::kXor_Mode: + case SkXfermode::kPlus_Mode: + return NULL; + } + return NULL; +} + +SkPDFGraphicState::~SkPDFGraphicState() { + SkAutoMutexAcquire lock(canonicalPaintsMutex()); + if (!fSMask) { + int index = find(fPaint); + SkASSERT(index >= 0); + canonicalPaints().removeShuffle(index); + } + fResources.unrefAll(); +} + +void SkPDFGraphicState::getResources(SkTDArray<SkPDFObject*>* resourceList) { + resourceList->setReserve(resourceList->count() + fResources.count()); + for (int i = 0; i < fResources.count(); i++) { + resourceList->push(fResources[i]); + fResources[i]->ref(); + fResources[i]->getResources(resourceList); + } +} + +void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + populateDict(); + SkPDFDict::emitObject(stream, catalog, indirect); +} + +// static +size_t SkPDFGraphicState::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + populateDict(); + return SkPDFDict::getOutputSize(catalog, indirect); +} + +// static +SkTDArray<SkPDFGraphicState::GSCanonicalEntry>& +SkPDFGraphicState::canonicalPaints() { + // This initialization is only thread safe with gcc. + static SkTDArray<SkPDFGraphicState::GSCanonicalEntry> gCanonicalPaints; + return gCanonicalPaints; +} + +// static +SkMutex& SkPDFGraphicState::canonicalPaintsMutex() { + // This initialization is only thread safe with gcc. + static SkMutex gCanonicalPaintsMutex; + return gCanonicalPaintsMutex; +} + +// static +SkPDFGraphicState* SkPDFGraphicState::getGraphicStateForPaint( + const SkPaint& paint) { + SkAutoMutexAcquire lock(canonicalPaintsMutex()); + int index = find(paint); + if (index >= 0) { + canonicalPaints()[index].fGraphicState->ref(); + return canonicalPaints()[index].fGraphicState; + } + GSCanonicalEntry newEntry(new SkPDFGraphicState(paint)); + canonicalPaints().push(newEntry); + return newEntry.fGraphicState; +} + +// static +SkPDFObject* SkPDFGraphicState::GetInvertFunction() { + // This assumes that canonicalPaintsMutex is held. + static SkPDFStream* invertFunction = NULL; + if (!invertFunction) { + // Acrobat crashes if we use a type 0 function, kpdf crashes if we use + // a type 2 function, so we use a type 4 function. + SkRefPtr<SkPDFArray> domainAndRange = new SkPDFArray; + domainAndRange->unref(); // SkRefPtr and new both took a reference. + domainAndRange->reserve(2); + domainAndRange->append(new SkPDFInt(0))->unref(); + domainAndRange->append(new SkPDFInt(1))->unref(); + + static const char psInvert[] = "{1 exch sub}"; + SkRefPtr<SkMemoryStream> psInvertStream = + new SkMemoryStream(&psInvert, strlen(psInvert), true); + psInvertStream->unref(); // SkRefPtr and new both took a reference. + + invertFunction = new SkPDFStream(psInvertStream.get()); + invertFunction->insert("FunctionType", new SkPDFInt(4))->unref(); + invertFunction->insert("Domain", domainAndRange.get()); + invertFunction->insert("Range", domainAndRange.get()); + } + return invertFunction; +} + +// static +SkPDFGraphicState* SkPDFGraphicState::getSMaskGraphicState( + SkPDFFormXObject* sMask, bool invert) { + // The practical chances of using the same mask more than once are unlikely + // enough that it's not worth canonicalizing. + SkAutoMutexAcquire lock(canonicalPaintsMutex()); + + SkRefPtr<SkPDFDict> sMaskDict = new SkPDFDict("Mask"); + sMaskDict->unref(); // SkRefPtr and new both took a reference. + sMaskDict->insert("S", new SkPDFName("Alpha"))->unref(); + sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref(); + + SkPDFGraphicState* result = new SkPDFGraphicState; + result->fPopulated = true; + result->fSMask = true; + result->insert("Type", new SkPDFName("ExtGState"))->unref(); + result->insert("SMask", sMaskDict.get()); + result->fResources.push(sMask); + sMask->ref(); + + if (invert) { + SkPDFObject* invertFunction = GetInvertFunction(); + result->fResources.push(invertFunction); + invertFunction->ref(); + sMaskDict->insert("TR", new SkPDFObjRef(invertFunction))->unref(); + } + + return result; +} + +// static +SkPDFGraphicState* SkPDFGraphicState::getNoSMaskGraphicState() { + SkAutoMutexAcquire lock(canonicalPaintsMutex()); + static SkPDFGraphicState* noSMaskGS = NULL; + if (!noSMaskGS) { + noSMaskGS = new SkPDFGraphicState; + noSMaskGS->fPopulated = true; + noSMaskGS->fSMask = true; + noSMaskGS->insert("Type", new SkPDFName("ExtGState"))->unref(); + noSMaskGS->insert("SMask", new SkPDFName("None"))->unref(); + } + noSMaskGS->ref(); + return noSMaskGS; +} + +// static +int SkPDFGraphicState::find(const SkPaint& paint) { + GSCanonicalEntry search(&paint); + return canonicalPaints().find(search); +} + +SkPDFGraphicState::SkPDFGraphicState() + : fPopulated(false), + fSMask(false) { +} + +SkPDFGraphicState::SkPDFGraphicState(const SkPaint& paint) + : fPaint(paint), + fPopulated(false), + fSMask(false) { +} + +// populateDict and operator== have to stay in sync with each other. +void SkPDFGraphicState::populateDict() { + if (!fPopulated) { + fPopulated = true; + insert("Type", new SkPDFName("ExtGState"))->unref(); + + SkRefPtr<SkPDFScalar> alpha = + new SkPDFScalar(fPaint.getAlpha() * SkScalarInvert(0xFF)); + alpha->unref(); // SkRefPtr and new both took a reference. + insert("CA", alpha.get()); + insert("ca", alpha.get()); + + SK_COMPILE_ASSERT(SkPaint::kButt_Cap == 0, paint_cap_mismatch); + SK_COMPILE_ASSERT(SkPaint::kRound_Cap == 1, paint_cap_mismatch); + SK_COMPILE_ASSERT(SkPaint::kSquare_Cap == 2, paint_cap_mismatch); + SK_COMPILE_ASSERT(SkPaint::kCapCount == 3, paint_cap_mismatch); + SkASSERT(fPaint.getStrokeCap() >= 0 && fPaint.getStrokeCap() <= 2); + insert("LC", new SkPDFInt(fPaint.getStrokeCap()))->unref(); + + SK_COMPILE_ASSERT(SkPaint::kMiter_Join == 0, paint_join_mismatch); + SK_COMPILE_ASSERT(SkPaint::kRound_Join == 1, paint_join_mismatch); + SK_COMPILE_ASSERT(SkPaint::kBevel_Join == 2, paint_join_mismatch); + SK_COMPILE_ASSERT(SkPaint::kJoinCount == 3, paint_join_mismatch); + SkASSERT(fPaint.getStrokeJoin() >= 0 && fPaint.getStrokeJoin() <= 2); + insert("LJ", new SkPDFInt(fPaint.getStrokeJoin()))->unref(); + + insert("LW", new SkPDFScalar(fPaint.getStrokeWidth()))->unref(); + insert("ML", new SkPDFScalar(fPaint.getStrokeMiter()))->unref(); + insert("SA", new SkPDFBool(true))->unref(); // Auto stroke adjustment. + + SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode; + // If asMode fails, default to kSrcOver_Mode. + if (fPaint.getXfermode()) + fPaint.getXfermode()->asMode(&xfermode); + // If we don't support the mode, just use kSrcOver_Mode. + if (xfermode < 0 || xfermode > SkXfermode::kLastMode || + blend_mode_from_xfermode(xfermode) == NULL) { + xfermode = SkXfermode::kSrcOver_Mode; + NOT_IMPLEMENTED("unsupported xfermode", false); + } + insert("BM", + new SkPDFName(blend_mode_from_xfermode(xfermode)))->unref(); + } +} + +// We're only interested in some fields of the SkPaint, so we have a custom +// operator== function. +bool SkPDFGraphicState::GSCanonicalEntry::operator==( + const SkPDFGraphicState::GSCanonicalEntry& gs) const { + const SkPaint* a = fPaint; + const SkPaint* b = gs.fPaint; + SkASSERT(a != NULL); + SkASSERT(b != NULL); + + if (SkColorGetA(a->getColor()) != SkColorGetA(b->getColor()) || + a->getStrokeCap() != b->getStrokeCap() || + a->getStrokeJoin() != b->getStrokeJoin() || + a->getStrokeWidth() != b->getStrokeWidth() || + a->getStrokeMiter() != b->getStrokeMiter()) { + return false; + } + + SkXfermode::Mode aXfermodeName = SkXfermode::kSrcOver_Mode; + SkXfermode* aXfermode = a->getXfermode(); + if (aXfermode) { + aXfermode->asMode(&aXfermodeName); + } + if (aXfermodeName < 0 || aXfermodeName > SkXfermode::kLastMode || + blend_mode_from_xfermode(aXfermodeName) == NULL) { + aXfermodeName = SkXfermode::kSrcOver_Mode; + } + const char* aXfermodeString = blend_mode_from_xfermode(aXfermodeName); + SkASSERT(aXfermodeString != NULL); + + SkXfermode::Mode bXfermodeName = SkXfermode::kSrcOver_Mode; + SkXfermode* bXfermode = b->getXfermode(); + if (bXfermode) { + bXfermode->asMode(&bXfermodeName); + } + if (bXfermodeName < 0 || bXfermodeName > SkXfermode::kLastMode || + blend_mode_from_xfermode(bXfermodeName) == NULL) { + bXfermodeName = SkXfermode::kSrcOver_Mode; + } + const char* bXfermodeString = blend_mode_from_xfermode(bXfermodeName); + SkASSERT(bXfermodeString != NULL); + + return strcmp(aXfermodeString, bXfermodeString) == 0; +} diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp new file mode 100644 index 0000000..be69f7f --- /dev/null +++ b/src/pdf/SkPDFImage.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFImage.h" + +#include "SkBitmap.h" +#include "SkColor.h" +#include "SkColorPriv.h" +#include "SkPaint.h" +#include "SkPackBits.h" +#include "SkPDFCatalog.h" +#include "SkRect.h" +#include "SkStream.h" +#include "SkString.h" +#include "SkUnPreMultiply.h" + +namespace { + +void extractImageData(const SkBitmap& bitmap, const SkIRect& srcRect, + SkStream** imageData, SkStream** alphaData) { + SkMemoryStream* image = NULL; + SkMemoryStream* alpha = NULL; + bool hasAlpha = false; + bool isTransparent = false; + + bitmap.lockPixels(); + switch (bitmap.getConfig()) { + case SkBitmap::kIndex8_Config: { + const int rowBytes = srcRect.width(); + image = new SkMemoryStream(rowBytes * srcRect.height()); + uint8_t* dst = (uint8_t*)image->getMemoryBase(); + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + memcpy(dst, bitmap.getAddr8(srcRect.fLeft, y), rowBytes); + dst += rowBytes; + } + break; + } + case SkBitmap::kRLE_Index8_Config: { + const int rowBytes = srcRect.width(); + image = new SkMemoryStream(rowBytes * srcRect.height()); + uint8_t* dst = (uint8_t*)image->getMemoryBase(); + const SkBitmap::RLEPixels* rle = + (const SkBitmap::RLEPixels*)bitmap.getPixels(); + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + SkPackBits::Unpack8(dst, srcRect.fLeft, rowBytes, + rle->packedAtY(y)); + dst += rowBytes; + } + break; + } + case SkBitmap::kARGB_4444_Config: { + isTransparent = true; + const int rowBytes = (srcRect.width() * 3 + 1) / 2; + const int alphaRowBytes = (srcRect.width() + 1) / 2; + image = new SkMemoryStream(rowBytes * srcRect.height()); + alpha = new SkMemoryStream(alphaRowBytes * srcRect.height()); + uint8_t* dst = (uint8_t*)image->getMemoryBase(); + uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + uint16_t* src = bitmap.getAddr16(0, y); + int x; + for (x = srcRect.fLeft; x + 1 < srcRect.fRight; x += 2) { + dst[0] = (SkGetPackedR4444(src[x]) << 4) | + SkGetPackedG4444(src[x]); + dst[1] = (SkGetPackedB4444(src[x]) << 4) | + SkGetPackedR4444(src[x + 1]); + dst[2] = (SkGetPackedG4444(src[x + 1]) << 4) | + SkGetPackedB4444(src[x + 1]); + dst += 3; + alphaDst[0] = (SkGetPackedA4444(src[x]) << 4) | + SkGetPackedA4444(src[x + 1]); + if (alphaDst[0] != 0xFF) + hasAlpha = true; + if (alphaDst[0]) + isTransparent = false; + alphaDst++; + } + if (srcRect.width() & 1) { + dst[0] = (SkGetPackedR4444(src[x]) << 4) | + SkGetPackedG4444(src[x]); + dst[1] = (SkGetPackedB4444(src[x]) << 4); + dst += 2; + alphaDst[0] = (SkGetPackedA4444(src[x]) << 4); + if (alphaDst[0] != 0xF0) + hasAlpha = true; + if (alphaDst[0] & 0xF0) + isTransparent = false; + alphaDst++; + } + } + break; + } + case SkBitmap::kRGB_565_Config: { + const int rowBytes = srcRect.width() * 3; + image = new SkMemoryStream(rowBytes * srcRect.height()); + uint8_t* dst = (uint8_t*)image->getMemoryBase(); + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + uint16_t* src = bitmap.getAddr16(0, y); + for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { + dst[0] = SkGetPackedR16(src[x]); + dst[1] = SkGetPackedG16(src[x]); + dst[2] = SkGetPackedB16(src[x]); + dst += 3; + } + } + break; + } + case SkBitmap::kARGB_8888_Config: { + isTransparent = true; + const int rowBytes = srcRect.width() * 3; + image = new SkMemoryStream(rowBytes * srcRect.height()); + alpha = new SkMemoryStream(srcRect.width() * srcRect.height()); + uint8_t* dst = (uint8_t*)image->getMemoryBase(); + uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + uint32_t* src = bitmap.getAddr32(0, y); + for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { + dst[0] = SkGetPackedR32(src[x]); + dst[1] = SkGetPackedG32(src[x]); + dst[2] = SkGetPackedB32(src[x]); + dst += 3; + alphaDst[0] = SkGetPackedA32(src[x]); + if (alphaDst[0] != 0xFF) + hasAlpha = true; + if (alphaDst[0]) + isTransparent = false; + alphaDst++; + } + } + break; + } + case SkBitmap::kA1_Config: { + isTransparent = true; + image = new SkMemoryStream(1); + ((uint8_t*)image->getMemoryBase())[0] = 0; + + const int alphaRowBytes = (srcRect.width() + 7) / 8; + alpha = new SkMemoryStream(alphaRowBytes * srcRect.height()); + uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); + int offset1 = srcRect.fLeft % 8; + int offset2 = 8 - offset1; + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + uint8_t* src = bitmap.getAddr1(0, y); + // This may read up to one byte after src, but the potentially + // invalid bits are never used for computation. + for (int x = srcRect.fLeft; x < srcRect.fRight; x += 8) { + if (offset1) { + alphaDst[0] = src[x / 8] << offset1 | + src[x / 8 + 1] >> offset2; + } else { + alphaDst[0] = src[x / 8]; + } + if (x + 7 < srcRect.fRight && alphaDst[0] != 0xFF) + hasAlpha = true; + if (x + 7 < srcRect.fRight && alphaDst[0]) + isTransparent = false; + alphaDst++; + } + // Calculate the mask of bits we're interested in within the + // last byte of alphaDst. + // width mod 8 == 1 -> 0x80 ... width mod 8 == 7 -> 0xFE + uint8_t mask = ~((1 << (8 - (srcRect.width() % 8))) - 1); + if (srcRect.width() % 8 && (alphaDst[-1] & mask) != mask) + hasAlpha = true; + if (srcRect.width() % 8 && (alphaDst[-1] & mask)) + isTransparent = false; + } + break; + } + case SkBitmap::kA8_Config: { + isTransparent = true; + image = new SkMemoryStream(1); + ((uint8_t*)image->getMemoryBase())[0] = 0; + + const int alphaRowBytes = srcRect.width(); + alpha = new SkMemoryStream(alphaRowBytes * srcRect.height()); + uint8_t* alphaDst = (uint8_t*)alpha->getMemoryBase(); + for (int y = srcRect.fTop; y < srcRect.fBottom; y++) { + uint8_t* src = bitmap.getAddr8(0, y); + for (int x = srcRect.fLeft; x < srcRect.fRight; x++) { + alphaDst[0] = src[x]; + if (alphaDst[0] != 0xFF) + hasAlpha = true; + if (alphaDst[0]) + isTransparent = false; + alphaDst++; + } + } + break; + } + default: + SkASSERT(false); + } + bitmap.unlockPixels(); + + if (isTransparent) { + SkSafeUnref(image); + } else { + *imageData = image; + } + + if (isTransparent || !hasAlpha) { + SkSafeUnref(alpha); + } else { + *alphaData = alpha; + } +} + +SkPDFArray* makeIndexedColorSpace(SkColorTable* table) { + SkPDFArray* result = new SkPDFArray(); + result->reserve(4); + result->append(new SkPDFName("Indexed"))->unref(); + result->append(new SkPDFName("DeviceRGB"))->unref();; + result->append(new SkPDFInt(table->count() - 1))->unref(); + + // Potentially, this could be represented in fewer bytes with a stream. + // Max size as a string is 1.5k. + SkString index; + for (int i = 0; i < table->count(); i++) { + char buf[3]; + SkColor color = SkUnPreMultiply::PMColorToColor((*table)[i]); + buf[0] = SkGetPackedR32(color); + buf[1] = SkGetPackedG32(color); + buf[2] = SkGetPackedB32(color); + index.append(buf, 3); + } + result->append(new SkPDFString(index))->unref(); + return result; +} + +}; // namespace + +// static +SkPDFImage* SkPDFImage::CreateImage(const SkBitmap& bitmap, + const SkIRect& srcRect, + const SkPaint& paint) { + if (bitmap.getConfig() == SkBitmap::kNo_Config) + return NULL; + + SkStream* imageData = NULL; + SkStream* alphaData = NULL; + extractImageData(bitmap, srcRect, &imageData, &alphaData); + SkAutoUnref unrefImageData(imageData); + SkAutoUnref unrefAlphaData(alphaData); + if (!imageData) { + SkASSERT(!alphaData); + return NULL; + } + + SkPDFImage* image = + new SkPDFImage(imageData, bitmap, srcRect, false, paint); + + if (alphaData != NULL) { + image->addSMask(new SkPDFImage(alphaData, bitmap, srcRect, true, + paint))->unref(); + } + return image; +} + +SkPDFImage::~SkPDFImage() { + fResources.unrefAll(); +} + +SkPDFImage* SkPDFImage::addSMask(SkPDFImage* mask) { + fResources.push(mask); + mask->ref(); + insert("SMask", new SkPDFObjRef(mask))->unref(); + return mask; +} + +void SkPDFImage::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + fStream->emitObject(stream, catalog, indirect); +} + +size_t SkPDFImage::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + + return fStream->getOutputSize(catalog, indirect); +} + +void SkPDFImage::getResources(SkTDArray<SkPDFObject*>* resourceList) { + if (fResources.count()) { + resourceList->setReserve(resourceList->count() + fResources.count()); + for (int i = 0; i < fResources.count(); i++) { + resourceList->push(fResources[i]); + fResources[i]->ref(); + fResources[i]->getResources(resourceList); + } + } +} + +SkPDFImage::SkPDFImage(SkStream* imageData, const SkBitmap& bitmap, + const SkIRect& srcRect, bool doingAlpha, + const SkPaint& paint) { + fStream = new SkPDFStream(imageData); + fStream->unref(); // SkRefPtr and new both took a reference. + + SkBitmap::Config config = bitmap.getConfig(); + bool alphaOnly = (config == SkBitmap::kA1_Config || + config == SkBitmap::kA8_Config); + + insert("Type", new SkPDFName("XObject"))->unref(); + insert("Subtype", new SkPDFName("Image"))->unref(); + + if (!doingAlpha && alphaOnly) { + // For alpha only images, we stretch a single pixel of black for + // the color/shape part. + SkRefPtr<SkPDFInt> one = new SkPDFInt(1); + one->unref(); // SkRefPtr and new both took a reference. + insert("Width", one.get()); + insert("Height", one.get()); + } else { + insert("Width", new SkPDFInt(srcRect.width()))->unref(); + insert("Height", new SkPDFInt(srcRect.height()))->unref(); + } + + // if (!image mask) { + if (doingAlpha || alphaOnly) { + insert("ColorSpace", new SkPDFName("DeviceGray"))->unref(); + } else if (config == SkBitmap::kIndex8_Config || + config == SkBitmap::kRLE_Index8_Config) { + insert("ColorSpace", + makeIndexedColorSpace(bitmap.getColorTable()))->unref(); + } else { + insert("ColorSpace", new SkPDFName("DeviceRGB"))->unref(); + } + // } + + int bitsPerComp = 8; + if (config == SkBitmap::kARGB_4444_Config) + bitsPerComp = 4; + else if (doingAlpha && config == SkBitmap::kA1_Config) + bitsPerComp = 1; + insert("BitsPerComponent", new SkPDFInt(bitsPerComp))->unref(); + + if (config == SkBitmap::kRGB_565_Config) { + SkRefPtr<SkPDFInt> zeroVal = new SkPDFInt(0); + zeroVal->unref(); // SkRefPtr and new both took a reference. + SkRefPtr<SkPDFScalar> scale5Val = + new SkPDFScalar(8.2258f); // 255/2^5-1 + scale5Val->unref(); // SkRefPtr and new both took a reference. + SkRefPtr<SkPDFScalar> scale6Val = + new SkPDFScalar(4.0476f); // 255/2^6-1 + scale6Val->unref(); // SkRefPtr and new both took a reference. + SkRefPtr<SkPDFArray> decodeValue = new SkPDFArray(); + decodeValue->unref(); // SkRefPtr and new both took a reference. + decodeValue->reserve(6); + decodeValue->append(zeroVal.get()); + decodeValue->append(scale5Val.get()); + decodeValue->append(zeroVal.get()); + decodeValue->append(scale6Val.get()); + decodeValue->append(zeroVal.get()); + decodeValue->append(scale5Val.get()); + insert("Decode", decodeValue.get()); + } +} + +SkPDFObject* SkPDFImage::insert(SkPDFName* key, SkPDFObject* value) { + return fStream->insert(key, value); +} + +SkPDFObject* SkPDFImage::insert(const char key[], SkPDFObject* value) { + return fStream->insert(key, value); +} diff --git a/src/pdf/SkPDFPage.cpp b/src/pdf/SkPDFPage.cpp new file mode 100644 index 0000000..2a8183d --- /dev/null +++ b/src/pdf/SkPDFPage.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFCatalog.h" +#include "SkPDFDevice.h" +#include "SkPDFPage.h" +#include "SkStream.h" + +SkPDFPage::SkPDFPage(const SkRefPtr<SkPDFDevice>& content) + : SkPDFDict("Page"), + fDevice(content) { +} + +SkPDFPage::~SkPDFPage() {} + +void SkPDFPage::finalizePage(SkPDFCatalog* catalog, bool firstPage, + SkTDArray<SkPDFObject*>* resourceObjects) { + if (fContentStream.get() == NULL) { + insert("Resources", fDevice->getResourceDict().get()); + insert("MediaBox", fDevice->getMediaBox().get()); + + SkRefPtr<SkStream> content = fDevice->content(); + content->unref(); // SkRefPtr and content() both took a reference. + fContentStream = new SkPDFStream(content.get()); + fContentStream->unref(); // SkRefPtr and new both took a reference. + insert("Contents", new SkPDFObjRef(fContentStream.get()))->unref(); + } + catalog->addObject(fContentStream.get(), firstPage); + fDevice->getResources(resourceObjects); +} + +off_t SkPDFPage::getPageSize(SkPDFCatalog* catalog, off_t fileOffset) { + SkASSERT(fContentStream.get() != NULL); + catalog->setFileOffset(fContentStream.get(), fileOffset); + return fContentStream->getOutputSize(catalog, true); +} + +void SkPDFPage::emitPage(SkWStream* stream, SkPDFCatalog* catalog) { + SkASSERT(fContentStream.get() != NULL); + fContentStream->emitObject(stream, catalog, true); +} + +// static +void SkPDFPage::generatePageTree(const SkTDArray<SkPDFPage*>& pages, + SkPDFCatalog* catalog, + SkTDArray<SkPDFDict*>* pageTree, + SkPDFDict** rootNode) { + // PDF wants a tree describing all the pages in the document. We arbitrary + // choose 8 (kNodeSize) as the number of allowed children. The internal + // nodes have type "Pages" with an array of children, a parent pointer, and + // the number of leaves below the node as "Count." The leaves are passed + // into the method, have type "Page" and need a parent pointer. This method + // builds the tree bottom up, skipping internal nodes that would have only + // one child. + static const int kNodeSize = 8; + + SkRefPtr<SkPDFName> kidsName = new SkPDFName("Kids"); + kidsName->unref(); // SkRefPtr and new both took a reference. + SkRefPtr<SkPDFName> countName = new SkPDFName("Count"); + countName->unref(); // SkRefPtr and new both took a reference. + SkRefPtr<SkPDFName> parentName = new SkPDFName("Parent"); + parentName->unref(); // SkRefPtr and new both took a reference. + + // curNodes takes a reference to its items, which it passes to pageTree. + SkTDArray<SkPDFDict*> curNodes; + curNodes.setReserve(pages.count()); + for (int i = 0; i < pages.count(); i++) { + SkSafeRef(pages[i]); + curNodes.push(pages[i]); + } + + // nextRoundNodes passes its references to nodes on to curNodes. + SkTDArray<SkPDFDict*> nextRoundNodes; + nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize); + + int treeCapacity = kNodeSize; + do { + for (int i = 0; i < curNodes.count(); ) { + if (i > 0 && i + 1 == curNodes.count()) { + nextRoundNodes.push(curNodes[i]); + break; + } + + SkPDFDict* newNode = new SkPDFDict("Pages"); + SkRefPtr<SkPDFObjRef> newNodeRef = new SkPDFObjRef(newNode); + newNodeRef->unref(); // SkRefPtr and new both took a reference. + + SkRefPtr<SkPDFArray> kids = new SkPDFArray; + kids->unref(); // SkRefPtr and new both took a reference. + kids->reserve(kNodeSize); + + int count = 0; + for (; i < curNodes.count() && count < kNodeSize; i++, count++) { + curNodes[i]->insert(parentName.get(), newNodeRef.get()); + kids->append(new SkPDFObjRef(curNodes[i]))->unref(); + + // TODO(vandebo) put the objects in strict access order. + // Probably doesn't matter because they are so small. + if (curNodes[i] != pages[0]) { + pageTree->push(curNodes[i]); // Transfer reference. + catalog->addObject(curNodes[i], false); + } else { + SkSafeUnref(curNodes[i]); + } + } + + newNode->insert(kidsName.get(), kids.get()); + int pageCount = treeCapacity; + if (count < kNodeSize) { + pageCount = pages.count() % treeCapacity; + } + newNode->insert(countName.get(), new SkPDFInt(pageCount))->unref(); + nextRoundNodes.push(newNode); // Transfer reference. + } + + curNodes = nextRoundNodes; + nextRoundNodes.rewind(); + treeCapacity *= kNodeSize; + } while(curNodes.count() > 1); + + pageTree->push(curNodes[0]); // Transfer reference. + catalog->addObject(curNodes[0], false); + if (rootNode) + *rootNode = curNodes[0]; +} + +const SkTDArray<SkPDFFont*>& SkPDFPage::getFontResources() const { + return fDevice->getFontResources(); +} diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp new file mode 100644 index 0000000..6845e09 --- /dev/null +++ b/src/pdf/SkPDFShader.cpp @@ -0,0 +1,778 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFShader.h" + +#include "SkCanvas.h" +#include "SkPDFCatalog.h" +#include "SkPDFDevice.h" +#include "SkPDFTypes.h" +#include "SkPDFUtils.h" +#include "SkScalar.h" +#include "SkStream.h" +#include "SkTemplates.h" +#include "SkThread.h" +#include "SkTypes.h" + +static void transformBBox(const SkMatrix& matrix, SkRect* bbox) { + SkMatrix inverse; + inverse.reset(); + matrix.invert(&inverse); + inverse.mapRect(bbox); +} + +static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) { + SkVector vec = pts[1] - pts[0]; + SkScalar mag = vec.length(); + SkScalar inv = mag ? SkScalarInvert(mag) : 0; + + vec.scale(inv); + matrix->setSinCos(vec.fY, vec.fX); + matrix->preTranslate(pts[0].fX, pts[0].fY); + matrix->preScale(mag, mag); +} + +/* Assumes t + startOffset is on the stack and does a linear interpolation on t + between startOffset and endOffset from prevColor to curColor (for each color + component), leaving the result in component order on the stack. + @param range endOffset - startOffset + @param curColor[components] The current color components. + @param prevColor[components] The previous color components. + @param result The result ps function. + */ +static void interpolateColorCode(SkScalar range, SkScalar* curColor, + SkScalar* prevColor, int components, + SkString* result) { + // Figure out how to scale each color component. + SkAutoSTMalloc<4, SkScalar> multiplierAlloc(components); + SkScalar *multiplier = multiplierAlloc.get(); + for (int i = 0; i < components; i++) { + multiplier[i] = SkScalarDiv(curColor[i] - prevColor[i], range); + } + + // Calculate when we no longer need to keep a copy of the input parameter t. + // If the last component to use t is i, then dupInput[0..i - 1] = true + // and dupInput[i .. components] = false. + SkAutoSTMalloc<4, bool> dupInputAlloc(components); + bool *dupInput = dupInputAlloc.get(); + dupInput[components - 1] = false; + for (int i = components - 2; i >= 0; i--) { + dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0; + } + + if (!dupInput[0] && multiplier[0] == 0) { + result->append("pop "); + } + + for (int i = 0; i < components; i++) { + // If the next components needs t, make a copy. + if (dupInput[i]) { + result->append("dup "); + } + + if (multiplier[i] == 0) { + result->appendScalar(prevColor[i]); + result->append(" "); + } else { + if (multiplier[i] != 1) { + result->appendScalar(multiplier[i]); + result->append(" mul "); + } + if (prevColor[i] != 0) { + result->appendScalar(prevColor[i]); + result->append(" add "); + } + } + + if (dupInput[i]) { + result->append("exch\n"); + } + } +} + +/* Generate Type 4 function code to map t=[0,1) to the passed gradient, + clamping at the edges of the range. The generated code will be of the form: + if (t < 0) { + return colorData[0][r,g,b]; + } else { + if (t < info.fColorOffsets[1]) { + return linearinterpolation(colorData[0][r,g,b], + colorData[1][r,g,b]); + } else { + if (t < info.fColorOffsets[2]) { + return linearinterpolation(colorData[1][r,g,b], + colorData[2][r,g,b]); + } else { + + ... } else { + return colorData[info.fColorCount - 1][r,g,b]; + } + ... + } + } + */ +static void gradientFunctionCode(const SkShader::GradientInfo& info, + SkString* result) { + /* We want to linearly interpolate from the previous color to the next. + Scale the colors from 0..255 to 0..1 and determine the multipliers + for interpolation. + C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}. + */ + static const int kColorComponents = 3; + typedef SkScalar ColorTuple[kColorComponents]; + SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount); + ColorTuple *colorData = colorDataAlloc.get(); + const SkScalar scale = SkScalarInvert(SkIntToScalar(255)); + for (int i = 0; i < info.fColorCount; i++) { + colorData[i][0] = SkScalarMul(SkColorGetR(info.fColors[i]), scale); + colorData[i][1] = SkScalarMul(SkColorGetG(info.fColors[i]), scale); + colorData[i][2] = SkScalarMul(SkColorGetB(info.fColors[i]), scale); + } + + // Clamp the initial color. + result->append("dup 0 le {pop "); + result->appendScalar(colorData[0][0]); + result->append(" "); + result->appendScalar(colorData[0][1]); + result->append(" "); + result->appendScalar(colorData[0][2]); + result->append(" }\n"); + + // The gradient colors. + for (int i = 1 ; i < info.fColorCount; i++) { + result->append("{dup "); + result->appendScalar(info.fColorOffsets[i]); + result->append(" le {"); + if (info.fColorOffsets[i - 1] != 0) { + result->appendScalar(info.fColorOffsets[i - 1]); + result->append(" sub\n"); + } + + interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1], + colorData[i], colorData[i - 1], kColorComponents, + result); + result->append("}\n"); + } + + // Clamp the final color. + result->append("{pop "); + result->appendScalar(colorData[info.fColorCount - 1][0]); + result->append(" "); + result->appendScalar(colorData[info.fColorCount - 1][1]); + result->append(" "); + result->appendScalar(colorData[info.fColorCount - 1][2]); + + for (int i = 0 ; i < info.fColorCount; i++) { + result->append("} ifelse\n"); + } +} + +/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */ +static void tileModeCode(SkShader::TileMode mode, SkString* result) { + if (mode == SkShader::kRepeat_TileMode) { + result->append("dup truncate sub\n"); // Get the fractional part. + result->append("dup 0 le {1 add} if\n"); // Map (-1,0) => (0,1) + return; + } + + if (mode == SkShader::kMirror_TileMode) { + // Map t mod 2 into [0, 1, 1, 0]. + // Code Stack + result->append("abs " // Map negative to positive. + "dup " // t.s t.s + "truncate " // t.s t + "dup " // t.s t t + "cvi " // t.s t T + "2 mod " // t.s t (i mod 2) + "1 eq " // t.s t true|false + "3 1 roll " // true|false t.s t + "sub " // true|false 0.s + "exch " // 0.s true|false + "{1 exch sub} if\n"); // 1 - 0.s|0.s + } +} + +static SkString linearCode(const SkShader::GradientInfo& info) { + SkString function("{pop\n"); // Just ditch the y value. + tileModeCode(info.fTileMode, &function); + gradientFunctionCode(info, &function); + function.append("}"); + return function; +} + +static SkString radialCode(const SkShader::GradientInfo& info) { + SkString function("{"); + // Find the distance from the origin. + function.append("dup " // x y y + "mul " // x y^2 + "exch " // y^2 x + "dup " // y^2 x x + "mul " // y^2 x^2 + "add " // y^2+x^2 + "sqrt\n"); // sqrt(y^2+x^2) + + tileModeCode(info.fTileMode, &function); + gradientFunctionCode(info, &function); + function.append("}"); + return function; +} + +/* The math here is all based on the description in Two_Point_Radial_Gradient, + with one simplification, the coordinate space has been scaled so that + Dr = 1. This means we don't need to scale the entire equation by 1/Dr^2. + */ +static SkString twoPointRadialCode(const SkShader::GradientInfo& info) { + SkScalar dx = info.fPoint[0].fX - info.fPoint[1].fX; + SkScalar dy = info.fPoint[0].fY - info.fPoint[1].fY; + SkScalar sr = info.fRadius[0]; + SkScalar a = SkScalarMul(dx, dx) + SkScalarMul(dy, dy) - SK_Scalar1; + bool posRoot = info.fRadius[1] > info.fRadius[0]; + + // We start with a stack of (x y), copy it and then consume one copy in + // order to calculate b and the other to calculate c. + SkString function("{"); + function.append("2 copy "); + + // Calculate -b and b^2. + function.appendScalar(dy); + function.append(" mul exch "); + function.appendScalar(dx); + function.append(" mul add "); + function.appendScalar(sr); + function.append(" sub 2 mul neg dup dup mul\n"); + + // Calculate c + function.append("4 2 roll dup mul exch dup mul add "); + function.appendScalar(SkScalarMul(sr, sr)); + function.append(" sub\n"); + + // Calculate the determinate + function.appendScalar(SkScalarMul(SkIntToScalar(4), a)); + function.append(" mul sub abs sqrt\n"); + + // And then the final value of t. + if (posRoot) { + function.append("sub "); + } else { + function.append("add "); + } + function.appendScalar(SkScalarMul(SkIntToScalar(2), a)); + function.append(" div\n"); + + tileModeCode(info.fTileMode, &function); + gradientFunctionCode(info, &function); + function.append("}"); + return function; +} + +static SkString sweepCode(const SkShader::GradientInfo& info) { + SkString function("{exch atan 360 div\n"); + tileModeCode(info.fTileMode, &function); + gradientFunctionCode(info, &function); + function.append("}"); + return function; +} + +SkPDFShader::~SkPDFShader() { + SkAutoMutexAcquire lock(canonicalShadersMutex()); + ShaderCanonicalEntry entry(this, fState.get()); + int index = canonicalShaders().find(entry); + SkASSERT(index >= 0); + canonicalShaders().removeShuffle(index); + fResources.unrefAll(); +} + +void SkPDFShader::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + fContent->emitObject(stream, catalog, indirect); +} + +size_t SkPDFShader::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + + return fContent->getOutputSize(catalog, indirect); +} + +void SkPDFShader::getResources(SkTDArray<SkPDFObject*>* resourceList) { + resourceList->setReserve(resourceList->count() + fResources.count()); + for (int i = 0; i < fResources.count(); i++) { + resourceList->push(fResources[i]); + fResources[i]->ref(); + } +} + +// static +SkPDFShader* SkPDFShader::getPDFShader(const SkShader& shader, + const SkMatrix& matrix, + const SkIRect& surfaceBBox) { + SkRefPtr<SkPDFShader> pdfShader; + SkAutoMutexAcquire lock(canonicalShadersMutex()); + SkAutoTDelete<State> shaderState(new State(shader, matrix, surfaceBBox)); + + ShaderCanonicalEntry entry(NULL, shaderState.get()); + int index = canonicalShaders().find(entry); + if (index >= 0) { + SkPDFShader* result = canonicalShaders()[index].fPDFShader; + result->ref(); + return result; + } + // The PDFShader takes ownership of the shaderSate. + pdfShader = new SkPDFShader(shaderState.detach()); + // Check for a valid shader. + if (pdfShader->fContent.get() == NULL) { + pdfShader->unref(); + return NULL; + } + entry.fPDFShader = pdfShader.get(); + canonicalShaders().push(entry); + return pdfShader.get(); // return the reference that came from new. +} + +// static +SkTDArray<SkPDFShader::ShaderCanonicalEntry>& SkPDFShader::canonicalShaders() { + // This initialization is only thread safe with gcc. + static SkTDArray<ShaderCanonicalEntry> gCanonicalShaders; + return gCanonicalShaders; +} + +// static +SkMutex& SkPDFShader::canonicalShadersMutex() { + // This initialization is only thread safe with gcc. + static SkMutex gCanonicalShadersMutex; + return gCanonicalShadersMutex; +} + +// static +SkPDFObject* SkPDFShader::rangeObject() { + // This initialization is only thread safe with gcc. + static SkPDFArray* range = NULL; + // This method is only used with canonicalShadersMutex, so it's safe to + // populate domain. + if (range == NULL) { + range = new SkPDFArray; + range->reserve(6); + range->append(new SkPDFInt(0))->unref(); + range->append(new SkPDFInt(1))->unref(); + range->append(new SkPDFInt(0))->unref(); + range->append(new SkPDFInt(1))->unref(); + range->append(new SkPDFInt(0))->unref(); + range->append(new SkPDFInt(1))->unref(); + } + return range; +} + +SkPDFShader::SkPDFShader(State* state) : fState(state) { + if (fState.get()->fType == SkShader::kNone_GradientType) { + doImageShader(); + } else { + doFunctionShader(); + } +} + +void SkPDFShader::doFunctionShader() { + SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL; + SkPoint transformPoints[2]; + + // Depending on the type of the gradient, we want to transform the + // coordinate space in different ways. + const SkShader::GradientInfo* info = &fState.get()->fInfo; + transformPoints[0] = info->fPoint[0]; + transformPoints[1] = info->fPoint[1]; + switch (fState.get()->fType) { + case SkShader::kLinear_GradientType: + codeFunction = &linearCode; + break; + case SkShader::kRadial_GradientType: + transformPoints[1] = transformPoints[0]; + transformPoints[1].fX += info->fRadius[0]; + codeFunction = &radialCode; + break; + case SkShader::kRadial2_GradientType: { + // Bail out if the radii are the same. Not setting fContent will + // cause the higher level code to detect the resulting object + // as invalid. + if (info->fRadius[0] == info->fRadius[1]) { + return; + } + transformPoints[1] = transformPoints[0]; + SkScalar dr = info->fRadius[1] - info->fRadius[0]; + transformPoints[1].fX += dr; + codeFunction = &twoPointRadialCode; + break; + } + case SkShader::kSweep_GradientType: + transformPoints[1] = transformPoints[0]; + transformPoints[1].fX += 1; + codeFunction = &sweepCode; + break; + case SkShader::kColor_GradientType: + case SkShader::kNone_GradientType: + SkASSERT(false); + return; + } + + // Move any scaling (assuming a unit gradient) or translation + // (and rotation for linear gradient), of the final gradient from + // info->fPoints to the matrix (updating bbox appropriately). Now + // the gradient can be drawn on on the unit segment. + SkMatrix mapperMatrix; + unitToPointsMatrix(transformPoints, &mapperMatrix); + SkMatrix finalMatrix = fState.get()->fCanvasTransform; + finalMatrix.preConcat(mapperMatrix); + finalMatrix.preConcat(fState.get()->fShaderTransform); + SkRect bbox; + bbox.set(fState.get()->fBBox); + transformBBox(finalMatrix, &bbox); + + SkRefPtr<SkPDFArray> domain = new SkPDFArray; + domain->unref(); // SkRefPtr and new both took a reference. + domain->reserve(4); + domain->append(new SkPDFScalar(bbox.fLeft))->unref(); + domain->append(new SkPDFScalar(bbox.fRight))->unref(); + domain->append(new SkPDFScalar(bbox.fTop))->unref(); + domain->append(new SkPDFScalar(bbox.fBottom))->unref(); + + SkString functionCode; + // The two point radial gradient further references fState.get()->fInfo + // in translating from x, y coordinates to the t parameter. So, we have + // to transform the points and radii according to the calculated matrix. + if (fState.get()->fType == SkShader::kRadial2_GradientType) { + SkShader::GradientInfo twoPointRadialInfo = *info; + SkMatrix inverseMapperMatrix; + mapperMatrix.invert(&inverseMapperMatrix); + inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2); + twoPointRadialInfo.fRadius[0] = + inverseMapperMatrix.mapRadius(info->fRadius[0]); + twoPointRadialInfo.fRadius[1] = + inverseMapperMatrix.mapRadius(info->fRadius[1]); + functionCode = codeFunction(twoPointRadialInfo); + } else { + functionCode = codeFunction(*info); + } + + SkRefPtr<SkPDFStream> function = makePSFunction(functionCode, domain.get()); + // Pass one reference to fResources, SkRefPtr and new both took a reference. + fResources.push(function.get()); + + SkRefPtr<SkPDFDict> pdfShader = new SkPDFDict; + pdfShader->unref(); // SkRefPtr and new both took a reference. + pdfShader->insert("ShadingType", new SkPDFInt(1))->unref(); + pdfShader->insert("ColorSpace", new SkPDFName("DeviceRGB"))->unref(); + pdfShader->insert("Domain", domain.get()); + pdfShader->insert("Function", new SkPDFObjRef(function.get()))->unref(); + + fContent = new SkPDFDict("Pattern"); + fContent->unref(); // SkRefPtr and new both took a reference. + fContent->insert("PatternType", new SkPDFInt(2))->unref(); + fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref(); + fContent->insert("Shading", pdfShader.get()); +} + +// SkShader* shader, SkMatrix matrix, const SkRect& surfaceBBox +void SkPDFShader::doImageShader() { + fState.get()->fImage.lockPixels(); + + SkMatrix finalMatrix = fState.get()->fCanvasTransform; + finalMatrix.preConcat(fState.get()->fShaderTransform); + SkRect surfaceBBox; + surfaceBBox.set(fState.get()->fBBox); + transformBBox(finalMatrix, &surfaceBBox); + + SkMatrix unflip; + unflip.setTranslate(0, SkScalarRound(surfaceBBox.height())); + unflip.preScale(1, -1); + SkISize size = SkISize::Make(SkScalarRound(surfaceBBox.width()), + SkScalarRound(surfaceBBox.height())); + SkPDFDevice pattern(size, size, unflip); + SkCanvas canvas(&pattern); + canvas.translate(-surfaceBBox.fLeft, -surfaceBBox.fTop); + finalMatrix.preTranslate(surfaceBBox.fLeft, surfaceBBox.fTop); + + const SkBitmap* image = &fState.get()->fImage; + int width = image->width(); + int height = image->height(); + SkShader::TileMode tileModes[2]; + tileModes[0] = fState.get()->fImageTileModes[0]; + tileModes[1] = fState.get()->fImageTileModes[1]; + + canvas.drawBitmap(*image, 0, 0); + SkRect patternBBox = SkRect::MakeXYWH(-surfaceBBox.fLeft, -surfaceBBox.fTop, + width, height); + + // Tiling is implied. First we handle mirroring. + if (tileModes[0] == SkShader::kMirror_TileMode) { + SkMatrix xMirror; + xMirror.setScale(-1, 1); + xMirror.postTranslate(2 * width, 0); + canvas.drawBitmapMatrix(*image, xMirror); + patternBBox.fRight += width; + } + if (tileModes[1] == SkShader::kMirror_TileMode) { + SkMatrix yMirror; + yMirror.setScale(1, -1); + yMirror.postTranslate(0, 2 * height); + canvas.drawBitmapMatrix(*image, yMirror); + patternBBox.fBottom += height; + } + if (tileModes[0] == SkShader::kMirror_TileMode && + tileModes[1] == SkShader::kMirror_TileMode) { + SkMatrix mirror; + mirror.setScale(-1, -1); + mirror.postTranslate(2 * width, 2 * height); + canvas.drawBitmapMatrix(*image, mirror); + } + + // Then handle Clamping, which requires expanding the pattern canvas to + // cover the entire surfaceBBox. + + // If both x and y are in clamp mode, we start by filling in the corners. + // (Which are just a rectangles of the corner colors.) + if (tileModes[0] == SkShader::kClamp_TileMode && + tileModes[1] == SkShader::kClamp_TileMode) { + SkPaint paint; + SkRect rect; + rect = SkRect::MakeLTRB(surfaceBBox.fLeft, surfaceBBox.fTop, 0, 0); + if (!rect.isEmpty()) { + paint.setColor(image->getColor(0, 0)); + canvas.drawRect(rect, paint); + } + + rect = SkRect::MakeLTRB(width, surfaceBBox.fTop, surfaceBBox.fRight, 0); + if (!rect.isEmpty()) { + paint.setColor(image->getColor(width - 1, 0)); + canvas.drawRect(rect, paint); + } + + rect = SkRect::MakeLTRB(width, height, surfaceBBox.fRight, + surfaceBBox.fBottom); + if (!rect.isEmpty()) { + paint.setColor(image->getColor(width - 1, height - 1)); + canvas.drawRect(rect, paint); + } + + rect = SkRect::MakeLTRB(surfaceBBox.fLeft, height, 0, + surfaceBBox.fBottom); + if (!rect.isEmpty()) { + paint.setColor(image->getColor(0, height - 1)); + canvas.drawRect(rect, paint); + } + } + + // Then expand the left, right, top, then bottom. + if (tileModes[0] == SkShader::kClamp_TileMode) { + SkIRect subset = SkIRect::MakeXYWH(0, 0, 1, height); + if (surfaceBBox.fLeft < 0) { + SkBitmap left; + SkAssertResult(image->extractSubset(&left, subset)); + + SkMatrix leftMatrix; + leftMatrix.setScale(-surfaceBBox.fLeft, 1); + leftMatrix.postTranslate(surfaceBBox.fLeft, 0); + canvas.drawBitmapMatrix(left, leftMatrix); + + if (tileModes[1] == SkShader::kMirror_TileMode) { + leftMatrix.postScale(1, -1); + leftMatrix.postTranslate(0, 2 * height); + canvas.drawBitmapMatrix(left, leftMatrix); + } + patternBBox.fLeft = 0; + } + + if (surfaceBBox.fRight > width) { + SkBitmap right; + subset.offset(width - 1, 0); + SkAssertResult(image->extractSubset(&right, subset)); + + SkMatrix rightMatrix; + rightMatrix.setScale(surfaceBBox.fRight - width, 1); + rightMatrix.postTranslate(width, 0); + canvas.drawBitmapMatrix(right, rightMatrix); + + if (tileModes[1] == SkShader::kMirror_TileMode) { + rightMatrix.postScale(1, -1); + rightMatrix.postTranslate(0, 2 * height); + canvas.drawBitmapMatrix(right, rightMatrix); + } + patternBBox.fRight = surfaceBBox.width(); + } + } + + if (tileModes[1] == SkShader::kClamp_TileMode) { + SkIRect subset = SkIRect::MakeXYWH(0, 0, width, 1); + if (surfaceBBox.fTop < 0) { + SkBitmap top; + SkAssertResult(image->extractSubset(&top, subset)); + + SkMatrix topMatrix; + topMatrix.setScale(1, -surfaceBBox.fTop); + topMatrix.postTranslate(0, surfaceBBox.fTop); + canvas.drawBitmapMatrix(top, topMatrix); + + if (tileModes[0] == SkShader::kMirror_TileMode) { + topMatrix.postScale(-1, 1); + topMatrix.postTranslate(2 * width, 0); + canvas.drawBitmapMatrix(top, topMatrix); + } + patternBBox.fTop = 0; + } + + if (surfaceBBox.fBottom > height) { + SkBitmap bottom; + subset.offset(0, height - 1); + SkAssertResult(image->extractSubset(&bottom, subset)); + + SkMatrix bottomMatrix; + bottomMatrix.setScale(1, surfaceBBox.fBottom - height); + bottomMatrix.postTranslate(0, height); + canvas.drawBitmapMatrix(bottom, bottomMatrix); + + if (tileModes[0] == SkShader::kMirror_TileMode) { + bottomMatrix.postScale(-1, 1); + bottomMatrix.postTranslate(2 * width, 0); + canvas.drawBitmapMatrix(bottom, bottomMatrix); + } + patternBBox.fBottom = surfaceBBox.height(); + } + } + + SkRefPtr<SkPDFArray> patternBBoxArray = new SkPDFArray; + patternBBoxArray->unref(); // SkRefPtr and new both took a reference. + patternBBoxArray->reserve(4); + patternBBoxArray->append(new SkPDFScalar(patternBBox.fLeft))->unref(); + patternBBoxArray->append(new SkPDFScalar(patternBBox.fTop))->unref(); + patternBBoxArray->append(new SkPDFScalar(patternBBox.fRight))->unref(); + patternBBoxArray->append(new SkPDFScalar(patternBBox.fBottom))->unref(); + + // Put the canvas into the pattern stream (fContent). + SkRefPtr<SkStream> content = pattern.content(); + content->unref(); // SkRefPtr and content() both took a reference. + pattern.getResources(&fResources); + + fContent = new SkPDFStream(content.get()); + fContent->unref(); // SkRefPtr and new both took a reference. + fContent->insert("Type", new SkPDFName("Pattern"))->unref(); + fContent->insert("PatternType", new SkPDFInt(1))->unref(); + fContent->insert("PaintType", new SkPDFInt(1))->unref(); + fContent->insert("TilingType", new SkPDFInt(1))->unref(); + fContent->insert("BBox", patternBBoxArray.get()); + fContent->insert("XStep", new SkPDFScalar(patternBBox.width()))->unref(); + fContent->insert("YStep", new SkPDFScalar(patternBBox.height()))->unref(); + fContent->insert("Resources", pattern.getResourceDict().get()); + fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref(); + + fState.get()->fImage.unlockPixels(); +} + +SkPDFStream* SkPDFShader::makePSFunction(const SkString& psCode, + SkPDFArray* domain) { + SkRefPtr<SkMemoryStream> funcStream = + new SkMemoryStream(psCode.c_str(), psCode.size(), true); + funcStream->unref(); // SkRefPtr and new both took a reference. + + SkPDFStream* result = new SkPDFStream(funcStream.get()); + result->insert("FunctionType", new SkPDFInt(4))->unref(); + result->insert("Domain", domain); + result->insert("Range", rangeObject()); + return result; +} + +bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const { + if (fType != b.fType || + fCanvasTransform != b.fCanvasTransform || + fShaderTransform != b.fShaderTransform || + fBBox != b.fBBox) { + return false; + } + + if (fType == SkShader::kNone_GradientType) { + if (fPixelGeneration != b.fPixelGeneration || + fPixelGeneration == 0 || + fImageTileModes[0] != b.fImageTileModes[0] || + fImageTileModes[1] != b.fImageTileModes[1]) { + return false; + } + } else { + if (fInfo.fColorCount != b.fInfo.fColorCount || + memcmp(fInfo.fColors, b.fInfo.fColors, + sizeof(SkColor) * fInfo.fColorCount) != 0 || + memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets, + sizeof(SkScalar) * fInfo.fColorCount) != 0 || + fInfo.fPoint[0] != b.fInfo.fPoint[0] || + fInfo.fTileMode != b.fInfo.fTileMode) { + return false; + } + + switch (fType) { + case SkShader::kLinear_GradientType: + if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) { + return false; + } + break; + case SkShader::kRadial_GradientType: + if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) { + return false; + } + break; + case SkShader::kRadial2_GradientType: + if (fInfo.fPoint[1] != b.fInfo.fPoint[1] || + fInfo.fRadius[0] != b.fInfo.fRadius[0] || + fInfo.fRadius[1] != b.fInfo.fRadius[1]) { + return false; + } + break; + case SkShader::kSweep_GradientType: + case SkShader::kNone_GradientType: + case SkShader::kColor_GradientType: + break; + } + } + return true; +} + +SkPDFShader::State::State(const SkShader& shader, + const SkMatrix& canvasTransform, const SkIRect& bbox) + : fCanvasTransform(canvasTransform), + fBBox(bbox) { + + fInfo.fColorCount = 0; + fInfo.fColors = NULL; + fInfo.fColorOffsets = NULL; + shader.getLocalMatrix(&fShaderTransform); + + fType = shader.asAGradient(&fInfo); + + if (fType == SkShader::kNone_GradientType) { + SkShader::BitmapType bitmapType; + SkMatrix matrix; + bitmapType = shader.asABitmap(&fImage, &matrix, fImageTileModes, NULL); + if (bitmapType != SkShader::kDefault_BitmapType) { + fImage.reset(); + return; + } + SkASSERT(matrix.isIdentity()); + fPixelGeneration = fImage.getGenerationID(); + } else { + fColorData.set(sk_malloc_throw( + fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar)))); + fInfo.fColors = (SkColor*)fColorData.get(); + fInfo.fColorOffsets = (SkScalar*)(fInfo.fColors + fInfo.fColorCount); + shader.asAGradient(&fInfo); + } +} diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp new file mode 100644 index 0000000..b1bd5ff --- /dev/null +++ b/src/pdf/SkPDFStream.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkFlate.h" +#include "SkPDFCatalog.h" +#include "SkPDFStream.h" +#include "SkStream.h" + +SkPDFStream::SkPDFStream(SkStream* stream) { + if (SkFlate::HaveFlate()) + SkAssertResult(SkFlate::Deflate(stream, &fCompressedData)); + + if (SkFlate::HaveFlate() && + fCompressedData.getOffset() < stream->getLength()) { + fLength = fCompressedData.getOffset(); + insert("Filter", new SkPDFName("FlateDecode"))->unref(); + } else { + fCompressedData.reset(); + fPlainData = stream; + fLength = fPlainData->getLength(); + } + insert("Length", new SkPDFInt(fLength))->unref(); +} + +SkPDFStream::~SkPDFStream() { +} + +void SkPDFStream::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + this->INHERITED::emitObject(stream, catalog, false); + stream->writeText(" stream\n"); + if (fPlainData.get()) + stream->write(fPlainData->getMemoryBase(), fLength); + else + stream->write(fCompressedData.getStream(), fLength); + stream->writeText("\nendstream"); +} + +size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + + return this->INHERITED::getOutputSize(catalog, false) + + strlen(" stream\n\nendstream") + fLength; +} diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp new file mode 100644 index 0000000..b9420eb --- /dev/null +++ b/src/pdf/SkPDFTypes.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPDFCatalog.h" +#include "SkPDFTypes.h" +#include "SkStream.h" + +#ifdef SK_BUILD_FOR_WIN + #define SNPRINTF _snprintf +#else + #define SNPRINTF snprintf +#endif + +SkPDFObject::SkPDFObject() {} +SkPDFObject::~SkPDFObject() {} + +size_t SkPDFObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + SkDynamicMemoryWStream buffer; + emitObject(&buffer, catalog, indirect); + return buffer.getOffset(); +} + +void SkPDFObject::getResources(SkTDArray<SkPDFObject*>* resourceList) {} + +void SkPDFObject::emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog) { + catalog->emitObjectNumber(stream, this); + stream->writeText(" obj\n"); + emitObject(stream, catalog, false); + stream->writeText("\nendobj\n"); +} + +SkPDFObjRef::SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {} +SkPDFObjRef::~SkPDFObjRef() {} + +size_t SkPDFObject::getIndirectOutputSize(SkPDFCatalog* catalog) { + return catalog->getObjectNumberSize(this) + strlen(" obj\n") + + this->getOutputSize(catalog, false) + strlen("\nendobj\n"); +} + +void SkPDFObjRef::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + SkASSERT(!indirect); + catalog->emitObjectNumber(stream, fObj.get()); + stream->writeText(" R"); +} + +size_t SkPDFObjRef::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + SkASSERT(!indirect); + return catalog->getObjectNumberSize(fObj.get()) + strlen(" R"); +} + +SkPDFInt::SkPDFInt(int32_t value) : fValue(value) {} +SkPDFInt::~SkPDFInt() {} + +void SkPDFInt::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + stream->writeDecAsText(fValue); +} + +SkPDFBool::SkPDFBool(bool value) : fValue(value) {} +SkPDFBool::~SkPDFBool() {} + +void SkPDFBool::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + SkASSERT(!indirect); + if (fValue) { + stream->writeText("true"); + } else { + stream->writeText("false"); + } +} + +size_t SkPDFBool::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + SkASSERT(!indirect); + if (fValue) + return strlen("true"); + return strlen("false"); +} + +SkPDFScalar::SkPDFScalar(SkScalar value) : fValue(value) {} +SkPDFScalar::~SkPDFScalar() {} + +void SkPDFScalar::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + Append(fValue, stream); +} + +// static +void SkPDFScalar::Append(SkScalar value, SkWStream* stream) { + // The range of reals in PDF/A is the same as SkFixed: +/- 32,767 and + // +/- 1/65,536 (though integers can range from 2^31 - 1 to -2^31). + // When using floats that are outside the whole value range, we can use + // integers instead. + + +#if defined(SK_SCALAR_IS_FIXED) + stream->writeScalarAsText(value); + return; +#endif // SK_SCALAR_IS_FIXED + +#if !defined(SK_ALLOW_LARGE_PDF_SCALARS) + if (value > 32767 || value < -32767) { + stream->writeDecAsText(SkScalarRound(value)); + return; + } + + char buffer[SkStrAppendScalar_MaxSize]; + char* end = SkStrAppendFixed(buffer, SkScalarToFixed(value)); + stream->write(buffer, end - buffer); + return; +#endif // !SK_ALLOW_LARGE_PDF_SCALARS + +#if defined(SK_SCALAR_IS_FLOAT) && defined(SK_ALLOW_LARGE_PDF_SCALARS) + // Floats have 24bits of significance, so anything outside that range is + // no more precise than an int. (Plus PDF doesn't support scientific + // notation, so this clamps to SK_Max/MinS32). + if (value > (1 << 24) || value < -(1 << 24)) { + stream->writeDecAsText(value); + return; + } + // Continue to enforce the PDF limits for small floats. + if (value < 1.0f/65536 && value > -1.0f/65536) { + stream->writeDecAsText(0); + return; + } + // SkStrAppendFloat might still use scientific notation, so use snprintf + // directly.. + static const int kFloat_MaxSize = 19; + char buffer[kFloat_MaxSize]; + int len = SNPRINTF(buffer, kFloat_MaxSize, "%#.8f", value); + // %f always prints trailing 0s, so strip them. + for (; buffer[len - 1] == '0' && len > 0; len--) { + buffer[len - 1] = '\0'; + } + if (buffer[len - 1] == '.') { + buffer[len - 1] = '\0'; + } + stream->writeText(buffer); + return; +#endif // SK_SCALAR_IS_FLOAT && SK_ALLOW_LARGE_PDF_SCALARS +} + +SkPDFString::SkPDFString(const char value[]) + : fValue(formatString(value, strlen(value))) { +} + +SkPDFString::SkPDFString(const SkString& value) + : fValue(formatString(value.c_str(), value.size())) { +} + +SkPDFString::SkPDFString(const uint16_t* value, size_t len, bool wideChars) + : fValue(formatString(value, len, wideChars)) { +} + +SkPDFString::~SkPDFString() {} + +void SkPDFString::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + stream->write(fValue.c_str(), fValue.size()); +} + +size_t SkPDFString::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + return fValue.size(); +} + +// static +SkString SkPDFString::formatString(const char* input, size_t len) { + return doFormatString(input, len, false, false); +} + +SkString SkPDFString::formatString(const uint16_t* input, size_t len, + bool wideChars) { + return doFormatString(input, len, true, wideChars); +} + +// static +SkString SkPDFString::doFormatString(const void* input, size_t len, + bool wideInput, bool wideOutput) { + SkASSERT(len <= kMaxLen); + const uint16_t* win = (const uint16_t*) input; + const char* cin = (const char*) input; + + if (wideOutput) { + SkASSERT(wideInput); + SkString result; + result.append("<"); + for (size_t i = 0; i < len; i++) + result.appendHex(win[i], 4); + result.append(">"); + return result; + } + + // 7-bit clean is a heuristic to decide what string format to use; + // a 7-bit clean string should require little escaping. + bool sevenBitClean = true; + for (size_t i = 0; i < len; i++) { + SkASSERT(!wideInput || !(win[i] & ~0xFF)); + char val = wideInput ? win[i] : cin[i]; + if (val > '~' || val < ' ') { + sevenBitClean = false; + break; + } + } + + SkString result; + if (sevenBitClean) { + result.append("("); + for (size_t i = 0; i < len; i++) { + SkASSERT(!wideInput || !(win[i] & ~0xFF)); + char val = wideInput ? win[i] : cin[i]; + if (val == '\\' || val == '(' || val == ')') + result.append("\\"); + result.append(&val, 1); + } + result.append(")"); + } else { + result.append("<"); + for (size_t i = 0; i < len; i++) { + SkASSERT(!wideInput || !(win[i] & ~0xFF)); + unsigned char val = wideInput ? win[i] : cin[i]; + result.appendHex(val, 2); + } + result.append(">"); + } + + return result; +} + +SkPDFName::SkPDFName(const char name[]) : fValue(formatName(SkString(name))) {} +SkPDFName::SkPDFName(const SkString& name) : fValue(formatName(name)) {} +SkPDFName::~SkPDFName() {} + +void SkPDFName::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + SkASSERT(!indirect); + stream->write(fValue.c_str(), fValue.size()); +} + +size_t SkPDFName::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + SkASSERT(!indirect); + return fValue.size(); +} + +// static +SkString SkPDFName::formatName(const SkString& input) { + SkASSERT(input.size() <= kMaxLen); + + SkString result("/"); + for (size_t i = 0; i < input.size(); i++) { + if (input[i] & 0x80 || input[i] < '!' || input[i] == '#') { + result.append("#"); + result.appendHex(input[i], 2); + } else { + result.append(input.c_str() + i, 1); + } + } + + return result; +} + +SkPDFArray::SkPDFArray() {} +SkPDFArray::~SkPDFArray() { + fValue.safeUnrefAll(); +} + +void SkPDFArray::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + stream->writeText("["); + for (int i = 0; i < fValue.count(); i++) { + fValue[i]->emitObject(stream, catalog, false); + if (i + 1 < fValue.count()) + stream->writeText(" "); + } + stream->writeText("]"); +} + +size_t SkPDFArray::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + + size_t result = strlen("[]"); + if (fValue.count()) + result += fValue.count() - 1; + for (int i = 0; i < fValue.count(); i++) + result += fValue[i]->getOutputSize(catalog, false); + return result; +} + +void SkPDFArray::reserve(int length) { + SkASSERT(length <= kMaxLen); + fValue.setReserve(length); +} + +SkPDFObject* SkPDFArray::setAt(int offset, SkPDFObject* value) { + SkASSERT(offset < fValue.count()); + SkSafeUnref(fValue[offset]); + fValue[offset] = value; + SkSafeRef(fValue[offset]); + return value; +} + +SkPDFObject* SkPDFArray::append(SkPDFObject* value) { + SkASSERT(fValue.count() < kMaxLen); + SkSafeRef(value); + fValue.push(value); + return value; +} + +SkPDFDict::SkPDFDict() {} + +SkPDFDict::SkPDFDict(const char type[]) { + insert("Type", new SkPDFName(type))->unref(); +} + +SkPDFDict::~SkPDFDict() { + clear(); +} + +void SkPDFDict::emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + if (indirect) + return emitIndirectObject(stream, catalog); + + stream->writeText("<<"); + for (int i = 0; i < fValue.count(); i++) { + fValue[i].key->emitObject(stream, catalog, false); + stream->writeText(" "); + fValue[i].value->emitObject(stream, catalog, false); + stream->writeText("\n"); + } + stream->writeText(">>"); +} + +size_t SkPDFDict::getOutputSize(SkPDFCatalog* catalog, bool indirect) { + if (indirect) + return getIndirectOutputSize(catalog); + + size_t result = strlen("<<>>") + (fValue.count() * 2); + for (int i = 0; i < fValue.count(); i++) { + result += fValue[i].key->getOutputSize(catalog, false); + result += fValue[i].value->getOutputSize(catalog, false); + } + return result; +} + +SkPDFObject* SkPDFDict::insert(SkPDFName* key, SkPDFObject* value) { + struct Rec* newEntry = fValue.append(); + newEntry->key = key; + SkSafeRef(newEntry->key); + newEntry->value = value; + SkSafeRef(newEntry->value); + return value; +} + +SkPDFObject* SkPDFDict::insert(const char key[], SkPDFObject* value) { + SkRefPtr<SkPDFName> keyName = new SkPDFName(key); + keyName->unref(); // SkRefPtr and new both took a reference. + return insert(keyName.get(), value); +} + +void SkPDFDict::clear() { + for (int i = 0; i < fValue.count(); i++) { + SkSafeUnref(fValue[i].key); + SkSafeUnref(fValue[i].value); + } + fValue.reset(); +} diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp new file mode 100644 index 0000000..a838427 --- /dev/null +++ b/src/pdf/SkPDFUtils.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkPaint.h" +#include "SkPath.h" +#include "SkPDFUtils.h" +#include "SkStream.h" +#include "SkString.h" +#include "SkPDFTypes.h" + +// static +SkPDFArray* SkPDFUtils::MatrixToArray(const SkMatrix& matrix) { + SkScalar values[6]; + SkAssertResult(matrix.pdfTransform(values)); + + SkPDFArray* result = new SkPDFArray; + result->reserve(6); + for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) { + result->append(new SkPDFScalar(values[i]))->unref(); + } + return result; +} + +// static +void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) { + SkScalar values[6]; + SkAssertResult(matrix.pdfTransform(values)); + for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) { + SkPDFScalar::Append(values[i], content); + content->writeText(" "); + } + content->writeText("cm\n"); +} + +// static +void SkPDFUtils::MoveTo(SkScalar x, SkScalar y, SkWStream* content) { + SkPDFScalar::Append(x, content); + content->writeText(" "); + SkPDFScalar::Append(y, content); + content->writeText(" m\n"); +} + +// static +void SkPDFUtils::AppendLine(SkScalar x, SkScalar y, SkWStream* content) { + SkPDFScalar::Append(x, content); + content->writeText(" "); + SkPDFScalar::Append(y, content); + content->writeText(" l\n"); +} + +// static +void SkPDFUtils::AppendCubic(SkScalar ctl1X, SkScalar ctl1Y, + SkScalar ctl2X, SkScalar ctl2Y, + SkScalar dstX, SkScalar dstY, SkWStream* content) { + SkString cmd("y\n"); + SkPDFScalar::Append(ctl1X, content); + content->writeText(" "); + SkPDFScalar::Append(ctl1Y, content); + content->writeText(" "); + if (ctl2X != dstX || ctl2Y != dstY) { + cmd.set("c\n"); + SkPDFScalar::Append(ctl2X, content); + content->writeText(" "); + SkPDFScalar::Append(ctl2Y, content); + content->writeText(" "); + } + SkPDFScalar::Append(dstX, content); + content->writeText(" "); + SkPDFScalar::Append(dstY, content); + content->writeText(" "); + content->writeText(cmd.c_str()); +} + +// static +void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) { + // Skia has 0,0 at top left, pdf at bottom left. Do the right thing. + SkScalar bottom = SkMinScalar(rect.fBottom, rect.fTop); + + SkPDFScalar::Append(rect.fLeft, content); + content->writeText(" "); + SkPDFScalar::Append(bottom, content); + content->writeText(" "); + SkPDFScalar::Append(rect.width(), content); + content->writeText(" "); + SkPDFScalar::Append(rect.height(), content); + content->writeText(" re\n"); +} + +// static +void SkPDFUtils::EmitPath(const SkPath& path, SkWStream* content) { + SkPoint args[4]; + SkPath::Iter iter(path, false); + for (SkPath::Verb verb = iter.next(args); + verb != SkPath::kDone_Verb; + verb = iter.next(args)) { + // args gets all the points, even the implicit first point. + switch (verb) { + case SkPath::kMove_Verb: + MoveTo(args[0].fX, args[0].fY, content); + break; + case SkPath::kLine_Verb: + AppendLine(args[1].fX, args[1].fY, content); + break; + case SkPath::kQuad_Verb: { + // Convert quad to cubic (degree elevation). http://goo.gl/vS4i + const SkScalar three = SkIntToScalar(3); + args[1].scale(SkIntToScalar(2)); + SkScalar ctl1X = SkScalarDiv(args[0].fX + args[1].fX, three); + SkScalar ctl1Y = SkScalarDiv(args[0].fY + args[1].fY, three); + SkScalar ctl2X = SkScalarDiv(args[2].fX + args[1].fX, three); + SkScalar ctl2Y = SkScalarDiv(args[2].fY + args[1].fY, three); + AppendCubic(ctl1X, ctl1Y, ctl2X, ctl2Y, args[2].fX, args[2].fY, + content); + break; + } + case SkPath::kCubic_Verb: + AppendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY, + args[3].fX, args[3].fY, content); + break; + case SkPath::kClose_Verb: + ClosePath(content); + break; + case SkPath::kDone_Verb: + break; + default: + SkASSERT(false); + break; + } + } +} + +// static +void SkPDFUtils::ClosePath(SkWStream* content) { + content->writeText("h\n"); +} + +// static +void SkPDFUtils::PaintPath(SkPaint::Style style, SkPath::FillType fill, + SkWStream* content) { + if (style == SkPaint::kFill_Style) + content->writeText("f"); + else if (style == SkPaint::kStrokeAndFill_Style) + content->writeText("B"); + else if (style == SkPaint::kStroke_Style) + content->writeText("S"); + + if (style != SkPaint::kStroke_Style) { + NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false); + NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false); + if (fill == SkPath::kEvenOdd_FillType) + content->writeText("*"); + } + content->writeText("\n"); +} + +// static +void SkPDFUtils::StrokePath(SkWStream* content) { + SkPDFUtils::PaintPath( + SkPaint::kStroke_Style, SkPath::kWinding_FillType, content); +} + +// static +void SkPDFUtils::DrawFormXObject(int objectIndex, SkWStream* content) { + content->writeText("/X"); + content->writeDecAsText(objectIndex); + content->writeText(" Do\n"); +} + +// static +void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) { + content->writeText("/G"); + content->writeDecAsText(objectIndex); + content->writeText(" gs\n"); +} diff --git a/src/pdf/pdf_files.mk b/src/pdf/pdf_files.mk new file mode 100644 index 0000000..c9b4fb4 --- /dev/null +++ b/src/pdf/pdf_files.mk @@ -0,0 +1,13 @@ +SOURCE := \ + SkPDFCatalog.cpp \ + SkPDFDevice.cpp \ + SkPDFDocument.cpp \ + SkPDFFont.cpp \ + SkPDFFormXObject.cpp \ + SkPDFGraphicState.cpp \ + SkPDFImage.cpp \ + SkPDFPage.cpp \ + SkPDFShader.cpp \ + SkPDFStream.cpp \ + SkPDFTypes.cpp \ + SkPDFUtils.cpp |