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/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;
+}