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
This commit is contained in:
vandebo@chromium.org 2010-10-20 22:23:29 +00:00
parent 17f694b038
commit 9b49dc0db8
12 changed files with 1256 additions and 6 deletions

View File

@ -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(); }

155
include/pdf/SkPDFDevice.h Normal file
View File

@ -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

View File

@ -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

67
include/pdf/SkPDFImage.h Normal file
View File

@ -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

View File

@ -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.

493
src/pdf/SkPDFDevice.cpp Normal file
View File

@ -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;
}

View File

@ -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());
}

248
src/pdf/SkPDFImage.cpp Normal file
View File

@ -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);
}

View File

@ -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) {

View File

@ -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() {}

View File

@ -1,6 +1,9 @@
SOURCE := \
SkPDFCatalog.cpp \
SkPDFDevice.cpp \
SkPDFDocument.cpp \
SkPDFGraphicState.cpp \
SkPDFImage.cpp \
SkPDFPage.cpp \
SkPDFStream.cpp \
SkPDFTypes.cpp \

View File

@ -175,12 +175,12 @@ static void TestPDFPrimitives(skiatest::Reporter* reporter) {
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);