aboutsummaryrefslogtreecommitdiffstats
path: root/src/pdf
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdf')
-rw-r--r--src/pdf/SkPDFCatalog.cpp128
-rw-r--r--src/pdf/SkPDFDevice.cpp1488
-rw-r--r--src/pdf/SkPDFDocument.cpp192
-rwxr-xr-xsrc/pdf/SkPDFFont.cpp976
-rw-r--r--src/pdf/SkPDFFormXObject.cpp94
-rw-r--r--src/pdf/SkPDFGraphicState.cpp294
-rw-r--r--src/pdf/SkPDFImage.cpp382
-rw-r--r--src/pdf/SkPDFPage.cpp142
-rw-r--r--src/pdf/SkPDFShader.cpp778
-rw-r--r--src/pdf/SkPDFStream.cpp61
-rw-r--r--src/pdf/SkPDFTypes.cpp392
-rw-r--r--src/pdf/SkPDFUtils.cpp187
-rw-r--r--src/pdf/pdf_files.mk13
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