diff --git a/include/core/SkDevice.h b/include/core/SkDevice.h index c223a157a6..3932ad35c5 100644 --- a/include/core/SkDevice.h +++ b/include/core/SkDevice.h @@ -70,10 +70,10 @@ public: /** 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 0000000000..5421299226 --- /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& getResourceDict(); + + /** Get the list of resouces (PDF objects) used on this page + * @param resouceList A list to append the resouces to. + */ + void getResouces(SkTDArray* resouceList); + + /** Returns the media box for this device. + */ + SkRefPtr getMediaBox(); + + /** Returns a string with the page contents. + */ + SkString content(); + +private: + int fWidth; + int fHeight; + SkRefPtr fResourceDict; + + SkRefPtr fCurrentGraphicState; + SkColor fCurrentColor; + SkScalar fCurrentTextScaleX; + SkTDArray fGraphicStateResources; + SkTDArray 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 0000000000..7fb09d46e0 --- /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& 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 0000000000..6e5ee37926 --- /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 fStream; +}; + +#endif diff --git a/include/pdf/SkPDFTypes.h b/include/pdf/SkPDFTypes.h index e0b1041b92..2066352167 100644 --- a/include/pdf/SkPDFTypes.h +++ b/include/pdf/SkPDFTypes.h @@ -108,6 +108,27 @@ private: 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 0000000000..79f0967bfc --- /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& SkPDFDevice::getResourceDict() { + if (fResourceDict.get() == NULL) { + fResourceDict = new SkPDFDict; + fResourceDict->unref(); // SkRefPtr and new both took a reference. + + if (fGraphicStateResources.count()) { + SkRefPtr 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 name = new SkPDFName(nameString); + name->unref(); // SkRefPtr and new both took a reference. + SkRefPtr 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 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 name = new SkPDFName(nameString); + name->unref(); // SkRefPtr and new both took a reference. + SkRefPtr 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* 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 SkPDFDevice::getMediaBox() { + SkRefPtr zero = new SkPDFInt(0); + zero->unref(); // SkRefPtr and new both took a reference. + SkRefPtr width = new SkPDFInt(fWidth); + width->unref(); // SkRefPtr and new both took a reference. + SkRefPtr height = new SkPDFInt(fHeight); + height->unref(); // SkRefPtr and new both took a reference. + SkRefPtr 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 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 0000000000..d0d47b5e6f --- /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::canonicalPaints() { + // This initialization is only thread safe with gcc. + static SkTDArray 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 typeName = new SkPDFName("ExtGState"); + typeName->unref(); // SkRefPtr and new both took a reference. + insert("Type", typeName.get()); + + SkScalar maxAlpha = SkIntToScalar(0xFF); + SkRefPtr 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 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 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 typeFace = + SkPDFTypeFace::getFontForTypeFace(fPaint.getTypeFace); + SkRefPtr typeFaceRef = new SkPDFObjRef(typeFace.get()); + fontRef->unref(); // SkRefPtr and new both took a reference. + SkRefPtr fontSize = + new SkPDFScalar(fPaint.getTetSize()); + fontSize->unref(); // SkRefPtr and new both took a reference. + SkRefPtr 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 strokeWidth = + new SkPDFScalar(fPaint.getStrokeWidth()); + strokeWidth->unref(); // SkRefPtr and new both took a reference. + insert("LW", strokeWidth.get()); + + SkRefPtr strokeMiterLimit = new SkPDFScalar( + fPaint.getStrokeMiter()); + strokeMiterLimit->unref(); // SkRefPtr and new both took a reference. + insert("ML", strokeWidth.get()); + + // Turn on automatic stroke adjustment. + SkRefPtr 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 0000000000..b480089b17 --- /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 indexedName = new SkPDFName("Indexed"); + indexedName->unref(); // SkRefPtr and new both took a reference. + result->append(indexedName.get()); + + SkRefPtr 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 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 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 typeValue = new SkPDFName("XObject"); + typeValue->unref(); // SkRefPtr and new both took a reference. + insert("Type", typeValue.get()); + + SkRefPtr subTypeValue = new SkPDFName("Image"); + subTypeValue->unref(); // SkRefPtr and new both took a reference. + insert("Subtype", subTypeValue.get()); + + SkRefPtr widthValue = new SkPDFInt(bitmap.width()); + widthValue->unref(); // SkRefPtr and new both took a reference. + insert("Width", widthValue.get()); + + SkRefPtr heightValue = new SkPDFInt(bitmap.height()); + heightValue->unref(); // SkRefPtr and new both took a reference. + insert("Height", heightValue.get()); + + // if (!image mask) { + SkRefPtr 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 bitsPerCompValue = new SkPDFInt(bitsPerComp); + bitsPerCompValue->unref(); // SkRefPtr and new both took a reference. + insert("BitsPerComponent", bitsPerCompValue.get()); + + if (config == SkBitmap::kRGB_565_Config) { + SkRefPtr zeroVal = new SkPDFInt(0); + zeroVal->unref(); // SkRefPtr and new both took a reference. + SkRefPtr scale5Val = new SkPDFScalar(8.2258); // 255/2^5-1 + scale5Val->unref(); // SkRefPtr and new both took a reference. + SkRefPtr scale6Val = new SkPDFScalar(4.0476); // 255/2^6-1 + scale6Val->unref(); // SkRefPtr and new both took a reference. + SkRefPtr 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 b880d4a26a..8be4f118ec 100644 --- a/src/pdf/SkPDFStream.cpp +++ b/src/pdf/SkPDFStream.cpp @@ -35,7 +35,7 @@ void SkPDFStream::emitObject(SkWStream* stream, SkPDFCatalog* catalog, 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 @@ size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) { 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 a01fcbaf5d..27668ab287 100644 --- a/src/pdf/SkPDFTypes.cpp +++ b/src/pdf/SkPDFTypes.cpp @@ -64,6 +64,26 @@ void SkPDFInt::emitObject(SkWStream* stream, SkPDFCatalog* 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() {} diff --git a/src/pdf/pdf_files.mk b/src/pdf/pdf_files.mk index a28957f5c5..5ef776f240 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 5dcb73cd6f..6d33a010f7 100644 --- a/tests/PDFPrimitivesTest.cpp +++ b/tests/PDFPrimitivesTest.cpp @@ -175,12 +175,12 @@ static void TestPDFPrimitives(skiatest::Reporter* reporter) { SkRefPtr stream = new SkPDFStream(streamData.get()); stream->unref(); // SkRefPtr and new both took a reference. CheckObjectOutput(reporter, stream.get(), - "<> stream\nTest\nFoo\tBarendstream", + "<> stream\nTest\nFoo\tBar\nendstream", true); stream->insert(n1.get(), int42.get()); CheckObjectOutput(reporter, stream.get(), "<> stream\nTest\nFoo\tBar" - "endstream", + "\nendstream", true); TestCatalog(reporter);