First pieces of SkPDFDevice. Supports:

Matrix transforms.
Rendering bitmaps.
Basic paint parameters.
Rendering rectangles, points, lines, polygons.
Render a paint to the page.

Review URL: http://codereview.appspot.com/2584041

git-svn-id: http://skia.googlecode.com/svn/trunk@614 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkDevice.h b/include/core/SkDevice.h
index c223a15..3932ad3 100644
--- a/include/core/SkDevice.h
+++ b/include/core/SkDevice.h
@@ -70,10 +70,10 @@
 
     /** Return the width of the device (in pixels).
     */
-    int width() const { return fBitmap.width(); }
+    virtual int width() const { return fBitmap.width(); }
     /** Return the height of the device (in pixels).
     */
-    int height() const { return fBitmap.height(); }
+    virtual int height() const { return fBitmap.height(); }
     /** Return the bitmap config of the device's pixels
     */
     SkBitmap::Config config() const { return fBitmap.getConfig(); }
diff --git a/include/pdf/SkPDFDevice.h b/include/pdf/SkPDFDevice.h
new file mode 100644
index 0000000..5421299
--- /dev/null
+++ b/include/pdf/SkPDFDevice.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef SkPDFDevice_DEFINED
+#define SkPDFDevice_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkDevice.h"
+#include "SkString.h"
+
+class SkPDFArray;
+class SkPDFDevice;
+class SkPDFDict;
+class SkPDFGraphicState;
+class SkPDFObject;
+class SkPDFStream;
+
+class SkPDFDeviceFactory : public SkDeviceFactory {
+    virtual SkDevice* newDevice(SkBitmap::Config config, int width, int height,
+                                bool isOpaque, bool isForLayer);
+    virtual uint32_t getDeviceCapabilities() { return 0; }
+};
+
+/** \class SkPDFDevice
+
+    The drawing context for the PDF backend.
+*/
+class SkPDFDevice : public SkDevice {
+public:
+    /** Create a PDF drawing context with the given width and height.
+     *  72 points/in means letter paper is 612x792.
+     *  @param width  Page width in points.
+     *  @param height Page height in points.
+     */
+    SkPDFDevice(int width, int height);
+    virtual ~SkPDFDevice();
+
+    virtual SkDeviceFactory* getDeviceFactory() {
+        return SkNEW(SkPDFDeviceFactory);
+    }
+
+    virtual int width() const { return fWidth; };
+
+    virtual int height() const { return fHeight; };
+
+    /** Called with the correct matrix and clip before this device is drawn
+        to using those settings. If your subclass overrides this, be sure to
+        call through to the base class as well.
+    */
+    virtual void setMatrixClip(const SkMatrix&, const SkRegion&);
+
+    /** These are called inside the per-device-layer loop for each draw call.
+     When these are called, we have already applied any saveLayer operations,
+     and are handling any looping from the paint, and any effects from the
+     DrawFilter.
+     */
+    virtual void drawPaint(const SkDraw&, const SkPaint& paint);
+    virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode,
+                            size_t count, const SkPoint[],
+                            const SkPaint& paint);
+    virtual void drawRect(const SkDraw&, const SkRect& r, const SkPaint& paint);
+    virtual void drawPath(const SkDraw&, const SkPath& path,
+                          const SkPaint& paint);
+    virtual void drawBitmap(const SkDraw&, const SkBitmap& bitmap,
+                            const SkMatrix& matrix, const SkPaint& paint);
+    virtual void drawSprite(const SkDraw&, const SkBitmap& bitmap, int x, int y,
+                            const SkPaint& paint);
+    virtual void drawText(const SkDraw&, const void* text, size_t len,
+                          SkScalar x, SkScalar y, const SkPaint& paint);
+    virtual void drawPosText(const SkDraw&, const void* text, size_t len,
+                             const SkScalar pos[], SkScalar constY,
+                             int scalarsPerPos, const SkPaint& paint);
+    virtual void drawTextOnPath(const SkDraw&, const void* text, size_t len,
+                                const SkPath& path, const SkMatrix* matrix,
+                                const SkPaint& paint);
+    virtual void drawVertices(const SkDraw&, SkCanvas::VertexMode,
+                              int vertexCount, const SkPoint verts[],
+                              const SkPoint texs[], const SkColor colors[],
+                              SkXfermode* xmode, const uint16_t indices[],
+                              int indexCount, const SkPaint& paint);
+    virtual void drawDevice(const SkDraw&, SkDevice*, int x, int y,
+                            const SkPaint&);
+
+    // PDF specific methods.
+
+    /** Returns a reference to the resource dictionary for this device.
+     */
+    const SkRefPtr<SkPDFDict>& getResourceDict();
+
+    /** Get the list of resouces (PDF objects) used on this page
+     *  @param resouceList A list to append the resouces to.
+     */
+    void getResouces(SkTDArray<SkPDFObject*>* resouceList);
+
+    /** Returns the media box for this device.
+     */
+    SkRefPtr<SkPDFArray> getMediaBox();
+
+    /** Returns a string with the page contents.
+     */
+    SkString content();
+
+private:
+    int fWidth;
+    int fHeight;
+    SkRefPtr<SkPDFDict> fResourceDict;
+
+    SkRefPtr<SkPDFGraphicState> fCurrentGraphicState;
+    SkColor fCurrentColor;
+    SkScalar fCurrentTextScaleX;
+    SkTDArray<SkPDFGraphicState*> fGraphicStateResources;
+    SkTDArray<SkPDFObject*> fXObjectResources;
+
+    SkString fContent;
+
+    // The last requested transforms from SkCanvas (setMatrixClip)
+    SkMatrix fCurTransform;
+
+    // The transform currently in effect in the PDF content stream.
+    SkMatrix fActiveTransform;
+
+    void updateGSFromPaint(const SkPaint& newPaint, SkString* textStaetUpdate);
+
+    void moveTo(SkScalar x, SkScalar y);
+    void appendLine(SkScalar x, SkScalar y);
+    void appendCubic(SkScalar ctl1X, SkScalar ctl1Y,
+                     SkScalar ctl2X, SkScalar ctl2Y,
+                     SkScalar dstX, SkScalar dstY);
+    void appendRectangle(SkScalar x, SkScalar y, SkScalar w, SkScalar h);
+    void closePath();
+    void strokePath();
+    void internalDrawBitmap(const SkMatrix& matrix, const SkBitmap& bitmap,
+                            const SkPaint& paint);
+
+    void setTransform(const SkMatrix& matrix);
+    void setNoTransform();
+    void applyTempTransform(const SkMatrix& matrix);
+    void removeTempTransform();
+    void applyTransform(const SkMatrix& matrix);
+};
+
+#endif
diff --git a/include/pdf/SkPDFGraphicState.h b/include/pdf/SkPDFGraphicState.h
new file mode 100644
index 0000000..7fb09d4
--- /dev/null
+++ b/include/pdf/SkPDFGraphicState.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifndef SkPDFGraphicState_DEFINED
+#define SkPDFGraphicState_DEFINED
+
+#include "SkPaint.h"
+#include "SkPDFTypes.h"
+#include "SkTemplates.h"
+#include "SkThread.h"
+
+/** \class SkPDFGraphicState
+    SkPaint objects roughly correspond to graphic state dictionaries that can
+    be installed.  So that a given dictionary is only output to the pdf file
+    once, we want to canonicalize them.  Static methods in this class manage
+    a weakly referenced set of SkPDFGraphicState objects: when the last
+    reference to a SkPDFGraphicState is removed, it removes itself from the
+    static set of objects.
+
+*/
+class SkPDFGraphicState : public SkPDFDict {
+public:
+    virtual ~SkPDFGraphicState();
+
+    // Override emitObject and getOutputSize so that we can populate
+    // the dictionary on demand.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+    /** Get the graphic state for the passed SkPaint. The reference count of
+     *  the object is incremented and it is the caller's responsibility to
+     *  unreference it when done.  This is needed to accommodate the weak
+     *  reference pattern used when the returned object is new and has no
+     *  other references.
+     *  @param paint  The SkPaint to emulate.
+     */
+    static SkPDFGraphicState* getGraphicStateForPaint(const SkPaint& paint);
+
+private:
+    const SkPaint fPaint;
+    bool fPopulated;
+
+    class GSCanonicalEntry {
+    public:
+        SkPDFGraphicState* fGraphicState;
+        const SkPaint* fPaint;
+
+        bool operator==(const GSCanonicalEntry& b) const;
+        explicit GSCanonicalEntry(SkPDFGraphicState* gs)
+            : fGraphicState(gs),
+              fPaint(&gs->fPaint) {}
+        explicit GSCanonicalEntry(const SkPaint* paint) : fPaint(paint) {}
+    };
+
+    // This should be made a hash table if performance is a problem.
+    static SkTDArray<GSCanonicalEntry>& canonicalPaints();
+    static SkMutex& canonicalPaintsMutex();
+
+    explicit SkPDFGraphicState(const SkPaint& paint);
+
+    void populateDict();
+
+    static int find(const SkPaint& paint);
+};
+
+#endif
diff --git a/include/pdf/SkPDFImage.h b/include/pdf/SkPDFImage.h
new file mode 100644
index 0000000..6e5ee37
--- /dev/null
+++ b/include/pdf/SkPDFImage.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef SkPDFImage_DEFINED
+#define SkPDFImage_DEFINED
+
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+
+class SkBitmap;
+class SkPaint;
+class SkPDFCatalog;
+
+/** \class SkPDFImage
+
+    An image XObject.
+*/
+
+// We could play the same trick here as is done in SkPDFGraphicState, storing
+// a copy of the Bitmap object (not the pixels), the pixel generation number,
+// and settings used from the paint to canonicalize image objects.
+class SkPDFImage : public SkPDFObject {
+public:
+    /** Create a PDF image XObject. Entries for the image properties are
+     *  automatically added to the stream dictionary.
+     *  @param bitmap  The image to use.
+     *  @param paint   Used to calculate alpha, masks, etc.
+     */
+    SkPDFImage(const SkBitmap& bitmap, const SkPaint& paint);
+    virtual ~SkPDFImage();
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+    /** Add the value to the stream dictionary with the given key.
+     *  @param key   The key for this dictionary entry.
+     *  @param value The value for this dictionary entry.
+     */
+    void insert(SkPDFName* key, SkPDFObject* value);
+
+    /** Add the value to the stream dictionary with the given key.
+     *  @param key   The text of the key for this dictionary entry.
+     *  @param value The value for this dictionary entry.
+     */
+    void insert(const char key[], SkPDFObject* value);
+
+private:
+    SkRefPtr<SkPDFStream> fStream;
+};
+
+#endif
diff --git a/include/pdf/SkPDFTypes.h b/include/pdf/SkPDFTypes.h
index e0b1041..2066352 100644
--- a/include/pdf/SkPDFTypes.h
+++ b/include/pdf/SkPDFTypes.h
@@ -108,6 +108,27 @@
     int32_t fValue;
 };
 
+/** \class SkPDFBool
+
+    An boolean value in a PDF.
+*/
+class SkPDFBool : public SkPDFObject {
+public:
+    /** Create a PDF boolean.
+     *  @param value true or false.
+     */
+    explicit SkPDFBool(bool value);
+    virtual ~SkPDFBool();
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+private:
+    bool fValue;
+};
+
 /** \class SkPDFScalar
 
     A real number object in a PDF.
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
new file mode 100644
index 0000000..79f0967
--- /dev/null
+++ b/src/pdf/SkPDFDevice.cpp
@@ -0,0 +1,493 @@
+/*
+ * 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 "SkPDFDevice.h"
+
+#include "SkColor.h"
+#include "SkPaint.h"
+#include "SkPDFImage.h"
+#include "SkPDFGraphicState.h"
+#include "SkPDFTypes.h"
+#include "SkPDFStream.h"
+#include "SkRect.h"
+#include "SkString.h"
+
+// Utility functions
+
+namespace {
+
+SkString toPDFColor(SkColor color) {
+    SkASSERT(SkColorGetA(color) == 0xFF);  // We handle alpha elsewhere.
+    SkScalar colorMax = SkIntToScalar(0xFF);
+    SkString result;
+    result.appendScalar(SkIntToScalar(SkColorGetR(color))/colorMax);
+    result.append(" ");
+    result.appendScalar(SkIntToScalar(SkColorGetG(color))/colorMax);
+    result.append(" ");
+    result.appendScalar(SkIntToScalar(SkColorGetB(color))/colorMax);
+    result.append(" ");
+    return result;
+}
+
+SkString StyleAndFillToPaintOperator(SkPaint::Style style,
+                                     SkPath::FillType fillType) {
+    SkString result;
+    if (style == SkPaint::kFill_Style)
+        result.append("f");
+    else if (style == SkPaint::kStrokeAndFill_Style)
+        result.append("B");
+    else if (style == SkPaint::kStroke_Style)
+        return SkString("S\n");
+
+    // Not supported yet.
+    SkASSERT(fillType != SkPath::kInverseEvenOdd_FillType);
+    SkASSERT(fillType != SkPath::kInverseWinding_FillType);
+    if (fillType == SkPath::kEvenOdd_FillType)
+        result.append("*");
+    result.append("\n");
+    return result;
+}
+
+}  // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+SkDevice* SkPDFDeviceFactory::newDevice(SkBitmap::Config config,
+                                        int width, int height,
+                                        bool isOpaque, bool isForLayer) {
+    return SkNEW_ARGS(SkPDFDevice, (width, height));
+}
+
+SkPDFDevice::SkPDFDevice(int width, int height)
+    : fWidth(width),
+      fHeight(height),
+      fCurrentColor(0),
+      fCurrentTextScaleX(SK_Scalar1) {
+    // Scale and translate to move the origin from the lower left to the upper
+    // left.
+    fCurTransform.setTranslate(0, height);
+    fCurTransform.preScale(1, -1);
+    fActiveTransform.reset();
+    applyTransform(fCurTransform);
+
+    fContent.append("q\n");
+    fCurTransform.reset();
+    fActiveTransform.reset();
+}
+
+SkPDFDevice::~SkPDFDevice() {
+    fGraphicStateResources.unrefAll();
+    fXObjectResources.unrefAll();
+}
+
+void SkPDFDevice::setMatrixClip(const SkMatrix& matrix,
+                                const SkRegion& region) {
+    // TODO(vandebo) handle clipping
+    setTransform(matrix);
+    fCurTransform = matrix;
+}
+
+void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
+    setNoTransform();
+
+    SkPaint newPaint = paint;
+    newPaint.setStyle(SkPaint::kFill_Style);
+    updateGSFromPaint(newPaint, NULL);
+
+    SkRect all = SkRect::MakeWH(width() + 1, height() + 1);
+    drawRect(d, all, newPaint);
+    setTransform(fCurTransform);
+}
+
+void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
+                             size_t count, const SkPoint* points,
+                             const SkPaint& paint) {
+    if (count == 0)
+        return;
+
+    switch (mode) {
+        case SkCanvas::kPolygon_PointMode:
+            updateGSFromPaint(paint, NULL);
+            moveTo(points[0].fX, points[0].fY);
+            for (size_t i = 1; i < count; i++)
+                appendLine(points[i].fX, points[i].fY);
+            strokePath();
+            break;
+        case SkCanvas::kLines_PointMode:
+            updateGSFromPaint(paint, NULL);
+            for (size_t i = 0; i < count/2; i++) {
+                moveTo(points[i * 2].fX, points[i * 2].fY);
+                appendLine(points[i * 2 + 1].fX, points[i * 2 + 1].fY);
+                strokePath();
+            }
+            break;
+        case SkCanvas::kPoints_PointMode:
+            if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
+                updateGSFromPaint(paint, NULL);
+                for (size_t i = 0; i < count; i++) {
+                    moveTo(points[i].fX, points[i].fY);
+                    strokePath();
+                }
+            } else {
+                // PDF won't draw a single point with square/butt caps because
+                // the orientation is ambiguous.  Draw a rectangle instead.
+                SkPaint newPaint = paint;
+                newPaint.setStyle(SkPaint::kFill_Style);
+                SkScalar strokeWidth = paint.getStrokeWidth();
+                SkScalar halfStroke = strokeWidth * SK_ScalarHalf;
+                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, newPaint);
+                }
+            }
+            break;
+        default:
+            SkASSERT(false);
+    }
+}
+
+void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
+                           const SkPaint& paint) {
+    if (paint.getPathEffect()) {
+        // Draw a path instead.
+        SkPath path;
+        path.addRect(r);
+        paint.getFillPath(path, &path);
+
+        SkPaint no_effect_paint(paint);
+        SkSafeUnref(no_effect_paint.setPathEffect(NULL));
+        drawPath(d, path, no_effect_paint);
+        return;
+    }
+    updateGSFromPaint(paint, NULL);
+
+    // Skia has 0,0 at top left, pdf at bottom left.  Do the right thing.
+    SkScalar bottom = r.fBottom < r.fTop ? r.fBottom : r.fTop;
+    appendRectangle(r.fLeft, bottom, r.width(), r.height());
+    fContent.append(StyleAndFillToPaintOperator(paint.getStyle(),
+                                                SkPath::kWinding_FillType));
+}
+
+void SkPDFDevice::drawPath(const SkDraw&, const SkPath& path,
+                           const SkPaint& paint) {
+    SkASSERT(false);
+}
+
+void SkPDFDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap,
+                             const SkMatrix& matrix, const SkPaint& paint) {
+    SkMatrix scaled;
+    // Adjust for origin flip.
+    scaled.setScale(1, -1);
+    scaled.postTranslate(0, 1);
+    scaled.postConcat(fCurTransform);
+    // Scale the image up from 1x1 to WxH.
+    scaled.postScale(bitmap.width(), bitmap.height());
+    scaled.postConcat(matrix);
+    internalDrawBitmap(scaled, bitmap, paint);
+}
+
+void SkPDFDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap,
+                             int x, int y, 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.
+    scaled.postScale(bitmap.width(), -bitmap.height());
+    scaled.postTranslate(x, y);
+    internalDrawBitmap(scaled, bitmap, paint);
+}
+
+void SkPDFDevice::drawText(const SkDraw&, const void* text, size_t len,
+                           SkScalar x, SkScalar y, const SkPaint& paint) {
+    SkASSERT(false);
+}
+
+void SkPDFDevice::drawPosText(const SkDraw&, const void* text, size_t len,
+                              const SkScalar pos[], SkScalar constY,
+                              int scalarsPerPos, const SkPaint& paint) {
+    SkASSERT(false);
+}
+
+void SkPDFDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len,
+                                 const SkPath& path, const SkMatrix* matrix,
+                                 const SkPaint& paint) {
+    SkASSERT(false);
+}
+
+void SkPDFDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode,
+                               int vertexCount, const SkPoint verts[],
+                               const SkPoint texs[], const SkColor colors[],
+                               SkXfermode* xmode, const uint16_t indices[],
+                               int indexCount, const SkPaint& paint) {
+    SkASSERT(false);
+}
+
+void SkPDFDevice::drawDevice(const SkDraw&, SkDevice*, int x, int y,
+                             const SkPaint&) {
+    SkASSERT(false);
+}
+
+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);
+                SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
+                name->unref();  // SkRefPtr and new both took a reference.
+                SkRefPtr<SkPDFObjRef> gsRef =
+                    new SkPDFObjRef(fGraphicStateResources[i]);
+                gsRef->unref();  // SkRefPtr and new both took a reference.
+                extGState->insert(name.get(), gsRef.get());
+            }
+            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);
+                SkRefPtr<SkPDFName> name = new SkPDFName(nameString);
+                name->unref();  // SkRefPtr and new both took a reference.
+                SkRefPtr<SkPDFObjRef> xObjRef =
+                    new SkPDFObjRef(fXObjectResources[i]);
+                xObjRef->unref();  // SkRefPtr and new both took a reference.
+                xObjects->insert(name.get(), xObjRef.get());
+            }
+            fResourceDict->insert("XObject", xObjects.get());
+        }
+    }
+    return fResourceDict;
+}
+
+void SkPDFDevice::getResouces(SkTDArray<SkPDFObject*>* resouceList) {
+    resouceList->setReserve(resouceList->count() +
+                            fGraphicStateResources.count() +
+                            fXObjectResources.count());
+    for (int i = 0; i < fGraphicStateResources.count(); i++) {
+        resouceList->push(fGraphicStateResources[i]);
+        fGraphicStateResources[i]->ref();
+    }
+    for (int i = 0; i < fXObjectResources.count(); i++) {
+        resouceList->push(fXObjectResources[i]);
+        fXObjectResources[i]->ref();
+    }
+}
+
+SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() {
+    SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
+    zero->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> width = new SkPDFInt(fWidth);
+    width->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> height = new SkPDFInt(fHeight);
+    height->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(width.get());
+    mediaBox->append(height.get());
+    return mediaBox;
+}
+
+SkString SkPDFDevice::content() {
+    SkString result = fContent;
+    result.append("Q");
+    return result;
+}
+
+// Private
+
+// TODO(vandebo) handle these cases.
+#define PAINTCHECK(x,y) do {                             \
+                          if(newPaint.x() y) {           \
+                              printf("!!" #x #y "\n");   \
+                              SkASSERT(false);           \
+                          }                              \
+                        } while(0)
+
+void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint,
+                                    SkString* textStateUpdate) {
+    PAINTCHECK(getXfermode, != NULL);
+    PAINTCHECK(getPathEffect, != NULL);
+    PAINTCHECK(getMaskFilter, != NULL);
+    PAINTCHECK(getShader, != NULL);
+    PAINTCHECK(getColorFilter, != NULL);
+    PAINTCHECK(isFakeBoldText, == true);
+    PAINTCHECK(isUnderlineText, == true);
+    PAINTCHECK(isStrikeThruText, == true);
+    PAINTCHECK(getTextSkewX, != 0);
+
+    SkRefPtr<SkPDFGraphicState> newGraphicState =
+        SkPDFGraphicState::getGraphicStateForPaint(newPaint);
+    newGraphicState->unref();  // getGraphicState and SkRefPtr both took a ref.
+    // newGraphicState has been canonicalized so we can directly compare
+    // pointers.
+    if (fCurrentGraphicState.get() != newGraphicState.get()) {
+        int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
+        if (resourceIndex < 0) {
+            resourceIndex = fGraphicStateResources.count();
+            fGraphicStateResources.push(newGraphicState.get());
+            newGraphicState->ref();
+        }
+        fContent.append("/G");
+        fContent.appendS32(resourceIndex);
+        fContent.append(" gs\n");
+        fCurrentGraphicState = newGraphicState;
+    }
+
+    SkColor newColor = newPaint.getColor();
+    newColor = SkColorSetA(newColor, 0xFF);
+    if (fCurrentColor != newColor) {
+        SkString colorString = toPDFColor(newColor);
+        fContent.append(colorString);
+        fContent.append("RG ");
+        fContent.append(colorString);
+        fContent.append("rg\n");
+        fCurrentColor = newColor;
+    }
+
+    if (textStateUpdate != NULL &&
+            fCurrentTextScaleX != newPaint.getTextScaleX()) {
+        SkScalar scale = newPaint.getTextScaleX();
+        SkScalar pdfScale = scale * 100;
+        textStateUpdate->appendScalar(pdfScale);
+        textStateUpdate->append(" Tz\n");
+        fCurrentTextScaleX = scale;
+    }
+}
+
+void SkPDFDevice::moveTo(SkScalar x, SkScalar y) {
+    fContent.appendScalar(x);
+    fContent.append(" ");
+    fContent.appendScalar(y);
+    fContent.append(" m\n");
+}
+
+void SkPDFDevice::appendLine(SkScalar x, SkScalar y) {
+    fContent.appendScalar(x);
+    fContent.append(" ");
+    fContent.appendScalar(y);
+    fContent.append(" l\n");
+}
+
+void SkPDFDevice::appendCubic(SkScalar ctl1X, SkScalar ctl1Y,
+                              SkScalar ctl2X, SkScalar ctl2Y,
+                              SkScalar dstX, SkScalar dstY) {
+    SkString cmd("y\n");
+    fContent.appendScalar(ctl1X);
+    fContent.append(" ");
+    fContent.appendScalar(ctl1Y);
+    fContent.append(" ");
+    if (ctl2X != dstX || ctl2Y != dstY) {
+        cmd.set("c\n");
+        fContent.appendScalar(ctl2X);
+        fContent.append(" ");
+        fContent.appendScalar(ctl2Y);
+        fContent.append(" ");
+    }
+    fContent.appendScalar(dstX);
+    fContent.append(" ");
+    fContent.appendScalar(dstY);
+    fContent.append(cmd);
+}
+
+void SkPDFDevice::appendRectangle(SkScalar x, SkScalar y,
+                                  SkScalar w, SkScalar h) {
+    fContent.appendScalar(x);
+    fContent.append(" ");
+    fContent.appendScalar(y);
+    fContent.append(" ");
+    fContent.appendScalar(w);
+    fContent.append(" ");
+    fContent.appendScalar(h);
+    fContent.append(" re\n");
+}
+
+void SkPDFDevice::closePath() {
+    fContent.append("h\n");
+}
+
+void SkPDFDevice::strokePath() {
+    fContent.append(StyleAndFillToPaintOperator(SkPaint::kStroke_Style,
+                                                SkPath::kWinding_FillType));
+}
+
+void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
+                                     const SkBitmap& bitmap,
+                                     const SkPaint& paint) {
+    setTransform(matrix);
+    SkPDFImage* image = new SkPDFImage(bitmap, paint);
+    fXObjectResources.push(image);  // Transfer reference.
+    fContent.append("/X");
+    fContent.appendS32(fXObjectResources.count() - 1);
+    fContent.append(" Do\n");
+    setTransform(fCurTransform);
+}
+
+void SkPDFDevice::setTransform(const SkMatrix& m) {
+    setNoTransform();
+    applyTransform(m);
+}
+
+void SkPDFDevice::setNoTransform() {
+    if (fActiveTransform.getType() == SkMatrix::kIdentity_Mask)
+        return;
+    fContent.append("Q q ");  // Restore the default transform and save it.
+    fCurrentGraphicState = NULL;
+    fActiveTransform.reset();
+}
+
+void SkPDFDevice::applyTempTransform(const SkMatrix& m) {
+    fContent.append("q ");
+    applyTransform(m);
+}
+
+void SkPDFDevice::removeTempTransform() {
+    fContent.append("Q\n");
+    fActiveTransform = fCurTransform;
+}
+
+void SkPDFDevice::applyTransform(const SkMatrix& m) {
+    if (m == fActiveTransform)
+        return;
+    SkASSERT((m.getType() & SkMatrix::kPerspective_Mask) == 0);
+
+    fContent.appendScalar(m[SkMatrix::kMScaleX]);
+    fContent.append(" ");
+    fContent.appendScalar(m[SkMatrix::kMSkewY]);
+    fContent.append(" ");
+    fContent.appendScalar(m[SkMatrix::kMSkewX]);
+    fContent.append(" ");
+    fContent.appendScalar(m[SkMatrix::kMScaleY]);
+    fContent.append(" ");
+    fContent.appendScalar(m[SkMatrix::kMTransX]);
+    fContent.append(" ");
+    fContent.appendScalar(m[SkMatrix::kMTransY]);
+    fContent.append(" cm\n");
+    fActiveTransform = m;
+}
diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp
new file mode 100644
index 0000000..d0d47b5
--- /dev/null
+++ b/src/pdf/SkPDFGraphicState.cpp
@@ -0,0 +1,163 @@
+/*
+ * 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 "SkPDFGraphicState.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+
+SkPDFGraphicState::~SkPDFGraphicState() {
+    SkAutoMutexAcquire lock(canonicalPaintsMutex());
+    int index = find(fPaint);
+    SkASSERT(index >= 0);
+    canonicalPaints().removeShuffle(index);
+}
+
+void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                                   bool indirect) {
+    populateDict();
+    SkPDFDict::emitObject(stream, catalog, indirect);
+}
+
+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
+int SkPDFGraphicState::find(const SkPaint& paint) {
+    GSCanonicalEntry search(&paint);
+    return canonicalPaints().find(search);
+}
+
+SkPDFGraphicState::SkPDFGraphicState(const SkPaint& paint)
+    : fPaint(paint),
+      fPopulated(false) {
+}
+
+// populateDict and operator== have to stay in sync with each other.
+void SkPDFGraphicState::populateDict() {
+    if (!fPopulated) {
+        fPopulated = true;
+        SkRefPtr<SkPDFName> typeName = new SkPDFName("ExtGState");
+        typeName->unref();  // SkRefPtr and new both took a reference.
+        insert("Type", typeName.get());
+
+        SkScalar maxAlpha = SkIntToScalar(0xFF);
+        SkRefPtr<SkPDFScalar> alpha =
+            new SkPDFScalar(SkColorGetA(fPaint.getColor())/maxAlpha);
+        alpha->unref();  // SkRefPtr and new both took a reference.
+        insert("CA", alpha.get());
+        insert("ca", alpha.get());
+
+        SkASSERT(SkPaint::kButt_Cap == 0);
+        SkASSERT(SkPaint::kRound_Cap == 1);
+        SkASSERT(SkPaint::kSquare_Cap == 2);
+        SkASSERT(fPaint.getStrokeCap() >= 0 && fPaint.getStrokeCap() <= 2);
+        SkRefPtr<SkPDFInt> strokeCap = new SkPDFInt(fPaint.getStrokeCap());
+        strokeCap->unref();  // SkRefPtr and new both took a reference.
+        insert("LC", strokeCap.get());
+
+        SkASSERT(SkPaint::kMiter_Join == 0);
+        SkASSERT(SkPaint::kRound_Join == 1);
+        SkASSERT(SkPaint::kBevel_Join == 2);
+        SkASSERT(fPaint.getStrokeJoin() >= 0 && fPaint.getStrokeJoin() <= 2);
+        SkRefPtr<SkPDFInt> strokeJoin = new SkPDFInt(fPaint.getStrokeJoin());
+        strokeJoin->unref();  // SkRefPtr and new both took a reference.
+        insert("LJ", strokeJoin.get());
+
+        /* TODO(vandebo) Font.
+        if (fPaint.getTypeFace() != NULL) {
+            SkRefPtr<SkPDFTypeFace> typeFace =
+                SkPDFTypeFace::getFontForTypeFace(fPaint.getTypeFace);
+            SkRefPtr<SkPDFObjRef> typeFaceRef = new SkPDFObjRef(typeFace.get());
+            fontRef->unref();  // SkRefPtr and new both took a reference.
+            SkRefPtr<SkPDFScalar> fontSize =
+                new SkPDFScalar(fPaint.getTetSize());
+            fontSize->unref();  // SkRefPtr and new both took a reference.
+            SkRefPtr<SkPDFArray> font = new SkPDFArray();
+            font->unref();  // SkRefPtr and new both took a reference.
+            font->reserve(2);
+            font->append(typeFaceRef.get());
+            font->append(fontSize.get());
+            insert("LJ", font.get());
+        }
+        */
+
+        SkRefPtr<SkPDFScalar> strokeWidth =
+            new SkPDFScalar(fPaint.getStrokeWidth());
+        strokeWidth->unref();  // SkRefPtr and new both took a reference.
+        insert("LW", strokeWidth.get());
+
+        SkRefPtr<SkPDFScalar> strokeMiterLimit = new SkPDFScalar(
+            fPaint.getStrokeMiter());
+        strokeMiterLimit->unref();  // SkRefPtr and new both took a reference.
+        insert("ML", strokeWidth.get());
+
+        // Turn on automatic stroke adjustment.
+        SkRefPtr<SkPDFBool> trueVal = new SkPDFBool(true);
+        trueVal->unref();  // SkRefPtr and new both took a reference.
+        insert("SA", trueVal.get());
+    }
+}
+
+// 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);
+    SkTypeface* aFace = a->getTypeface();
+    SkTypeface* bFace = b->getTypeface();
+    return SkColorGetA(a->getColor()) == SkColorGetA(b->getColor()) &&
+           a->getStrokeCap() == b->getStrokeCap() &&
+           a->getStrokeJoin() == b->getStrokeJoin() &&
+           a->getTextSize() == b->getTextSize() &&
+           a->getStrokeWidth() == b->getStrokeWidth() &&
+           a->getStrokeMiter() == b->getStrokeMiter() &&
+           (aFace == NULL) == (bFace == NULL) &&
+           (aFace == NULL || aFace->uniqueID() == bFace->uniqueID());
+}
diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp
new file mode 100644
index 0000000..b480089
--- /dev/null
+++ b/src/pdf/SkPDFImage.cpp
@@ -0,0 +1,248 @@
+/*
+ * 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 "SkStream.h"
+#include "SkString.h"
+#include "SkUnPreMultiply.h"
+
+namespace {
+
+SkMemoryStream* extractImageData(const SkBitmap& bitmap) {
+    SkMemoryStream* result;
+
+    switch (bitmap.getConfig()) {
+        case SkBitmap::kIndex8_Config:
+            result = new SkMemoryStream(bitmap.getPixels(), bitmap.getSize(),
+                                        true);
+            break;
+        case SkBitmap::kRLE_Index8_Config: {
+            result = new SkMemoryStream(bitmap.getSize());
+            const SkBitmap::RLEPixels* rle =
+                (const SkBitmap::RLEPixels*)bitmap.getPixels();
+            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            const int width = bitmap.width();
+            for (int y = 0; y < bitmap.height(); y++) {
+                SkPackBits::Unpack8(rle->packedAtY(y), width, dst);
+                dst += width;
+            }
+            break;
+        }
+        case SkBitmap::kARGB_4444_Config: {
+            const int width = bitmap.width();
+            const int rowBytes = (width * 3 + 1) / 2;
+            result = new SkMemoryStream(rowBytes * bitmap.height());
+            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            for (int y = 0; y < bitmap.height(); y++) {
+                uint16_t* src = bitmap.getAddr16(0, y);
+                for (int x = 0; x < width; x += 2) {
+                    dst[0] = (SkGetPackedR4444(src[0]) << 4) |
+                        SkGetPackedG4444(src[0]);
+                    dst[1] = (SkGetPackedB4444(src[0]) << 4) |
+                        SkGetPackedR4444(src[1]);
+                    dst[2] = (SkGetPackedG4444(src[1]) << 4) |
+                        SkGetPackedB4444(src[1]);
+                    src += 2;
+                    dst += 3;
+                }
+                if (width & 1) {
+                    dst[0] = (SkGetPackedR4444(src[0]) << 4) |
+                        SkGetPackedG4444(src[0]);
+                    dst[1] = (SkGetPackedB4444(src[0]) << 4);
+                }
+            }
+            break;
+        }
+        case SkBitmap::kRGB_565_Config: {
+            const int width = bitmap.width();
+            const int rowBytes = width * 3;
+            result = new SkMemoryStream(rowBytes * bitmap.height());
+            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            for (int y = 0; y < bitmap.height(); y++) {
+                uint16_t* src = bitmap.getAddr16(0, y);
+                for (int x = 0; x < width; x++) {
+                    dst[0] = SkGetPackedR16(src[0]);
+                    dst[1] = SkGetPackedG16(src[0]);
+                    dst[2] = SkGetPackedB16(src[0]);
+                    src++;
+                    dst += 3;
+                }
+            }
+            break;
+        }
+        case SkBitmap::kARGB_8888_Config: {
+            const int width = bitmap.width();
+            const int rowBytes = width * 3;
+            result = new SkMemoryStream(rowBytes * bitmap.height());
+            uint8_t* dst = (uint8_t*)result->getMemoryBase();
+            for (int y = 0; y < bitmap.height(); y++) {
+                uint32_t* src = bitmap.getAddr32(0, y);
+                for (int x = 0; x < width; x++) {
+                    dst[0] = SkGetPackedR32(src[0]);
+                    dst[1] = SkGetPackedG32(src[0]);
+                    dst[2] = SkGetPackedB32(src[0]);
+                    src++;
+                    dst += 3;
+                }
+            }
+            break;
+        }
+        default:
+            SkASSERT(false);
+    }
+    return result;
+}
+
+SkPDFArray* makeIndexedColorSpace(SkColorTable* table) {
+    SkPDFArray* result = new SkPDFArray();
+    result->reserve(4);
+    SkRefPtr<SkPDFName> indexedName = new SkPDFName("Indexed");
+    indexedName->unref();  // SkRefPtr and new both took a reference.
+    result->append(indexedName.get());
+
+    SkRefPtr<SkPDFName> rgbName = new SkPDFName("DeviceRGB");
+    rgbName->unref();  // SkRefPtr and new both took a reference.
+    result->append(rgbName.get());
+
+    rgbName->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> countValue = new SkPDFInt(table->count() - 1);
+    result->append(countValue.get());
+
+    // 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);
+    }
+    SkRefPtr<SkPDFString> indexValue = new SkPDFString(index);
+    indexValue->unref();  // SkRefPtr and new both took a reference.
+    result->append(indexValue.get());
+    return result;
+}
+
+};  // namespace
+
+SkPDFImage::SkPDFImage(const SkBitmap& bitmap, const SkPaint& paint) {
+    SkBitmap::Config config = bitmap.getConfig();
+
+    // TODO(vandebo) Handle alpha and alpha only images correctly.
+    SkASSERT(config == SkBitmap::kRGB_565_Config ||
+             config == SkBitmap::kARGB_4444_Config ||
+             config == SkBitmap::kARGB_8888_Config ||
+             config == SkBitmap::kIndex8_Config ||
+             config == SkBitmap::kRLE_Index8_Config);
+
+    SkMemoryStream* image_data = extractImageData(bitmap);
+    SkAutoUnref image_data_unref(image_data);
+    fStream = new SkPDFStream(image_data);
+    fStream->unref();  // SkRefPtr and new both took a reference.
+
+    SkRefPtr<SkPDFName> typeValue = new SkPDFName("XObject");
+    typeValue->unref();  // SkRefPtr and new both took a reference.
+    insert("Type", typeValue.get());
+
+    SkRefPtr<SkPDFName> subTypeValue = new SkPDFName("Image");
+    subTypeValue->unref();  // SkRefPtr and new both took a reference.
+    insert("Subtype", subTypeValue.get());
+
+    SkRefPtr<SkPDFInt> widthValue = new SkPDFInt(bitmap.width());
+    widthValue->unref();  // SkRefPtr and new both took a reference.
+    insert("Width", widthValue.get());
+
+    SkRefPtr<SkPDFInt> heightValue = new SkPDFInt(bitmap.height());
+    heightValue->unref();  // SkRefPtr and new both took a reference.
+    insert("Height", heightValue.get());
+
+    // if (!image mask) {
+    SkRefPtr<SkPDFObject> colorSpaceValue;
+    if (config == SkBitmap::kIndex8_Config ||
+        config == SkBitmap::kRLE_Index8_Config) {
+        colorSpaceValue = makeIndexedColorSpace(bitmap.getColorTable());
+    } else {
+        colorSpaceValue = new SkPDFName("DeviceRGB");
+    }
+    colorSpaceValue->unref();  // SkRefPtr and new both took a reference.
+    insert("ColorSpace", colorSpaceValue.get());
+    // }
+
+    int bitsPerComp = bitmap.bytesPerPixel() * 2;
+    if (bitsPerComp == 0) {
+        SkASSERT(config == SkBitmap::kA1_Config);
+        bitsPerComp = 1;
+    } else if (bitsPerComp == 2 ||
+               (bitsPerComp == 4 && config == SkBitmap::kRGB_565_Config)) {
+        bitsPerComp = 8;
+    }
+    SkRefPtr<SkPDFInt> bitsPerCompValue = new SkPDFInt(bitsPerComp);
+    bitsPerCompValue->unref();  // SkRefPtr and new both took a reference.
+    insert("BitsPerComponent", bitsPerCompValue.get());
+
+    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.2258);  // 255/2^5-1
+        scale5Val->unref();  // SkRefPtr and new both took a reference.
+        SkRefPtr<SkPDFScalar> scale6Val = new SkPDFScalar(4.0476);  // 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());
+    }
+}
+
+SkPDFImage::~SkPDFImage() {}
+
+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::insert(SkPDFName* key, SkPDFObject* value) {
+    fStream->insert(key, value);
+}
+
+void SkPDFImage::insert(const char key[], SkPDFObject* value) {
+    fStream->insert(key, value);
+}
diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp
index b880d4a..8be4f11 100644
--- a/src/pdf/SkPDFStream.cpp
+++ b/src/pdf/SkPDFStream.cpp
@@ -35,7 +35,7 @@
     fDict.emitObject(stream, catalog, false);
     stream->writeText(" stream\n");
     stream->write(fData->getMemoryBase(), fData->read(NULL, 0));
-    stream->writeText("endstream");
+    stream->writeText("\nendstream");
 }
 
 size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
@@ -43,7 +43,7 @@
         return getIndirectOutputSize(catalog);
 
     return fDict.getOutputSize(catalog, false) +
-        strlen(" stream\nendstream") + fData->read(NULL, 0);
+        strlen(" stream\n\nendstream") + fData->read(NULL, 0);
 }
 
 void SkPDFStream::insert(SkPDFName* key, SkPDFObject* value) {
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index a01fcba..27668ab 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -64,6 +64,26 @@
     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() {}
 
diff --git a/src/pdf/pdf_files.mk b/src/pdf/pdf_files.mk
index a28957f..5ef776f 100644
--- a/src/pdf/pdf_files.mk
+++ b/src/pdf/pdf_files.mk
@@ -1,6 +1,9 @@
 SOURCE := \
     SkPDFCatalog.cpp \
+    SkPDFDevice.cpp \
     SkPDFDocument.cpp \
+    SkPDFGraphicState.cpp \
+    SkPDFImage.cpp \
     SkPDFPage.cpp \
     SkPDFStream.cpp \
     SkPDFTypes.cpp \
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 5dcb73c..6d33a01 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -175,12 +175,12 @@
     SkRefPtr<SkPDFStream> stream = new SkPDFStream(streamData.get());
     stream->unref();  // SkRefPtr and new both took a reference.
     CheckObjectOutput(reporter, stream.get(),
-                      "<</Length 12\n>> stream\nTest\nFoo\tBarendstream",
+                      "<</Length 12\n>> stream\nTest\nFoo\tBar\nendstream",
                       true);
     stream->insert(n1.get(), int42.get());
     CheckObjectOutput(reporter, stream.get(),
                       "<</Length 12\n/n1 42\n>> stream\nTest\nFoo\tBar"
-                      "endstream",
+                      "\nendstream",
                       true);
 
     TestCatalog(reporter);