#include "SkPDFTypes.h"
#include "SkPDFUtils.h"
#include "SkRect.h"
+#include "SkRRect.h"
#include "SkString.h"
#include "SkTextFormatParams.h"
#include "SkTemplates.h"
};
#endif
+#define DPI_FOR_RASTER_SCALE_ONE 72
+
// Utility functions
static void emit_pdf_color(SkColor color, SkWStream* result) {
*y = *y - yAdj;
}
-static size_t max_glyphid_for_typeface(SkTypeface* typeface) {
+static int max_glyphid_for_typeface(SkTypeface* typeface) {
SkAutoResolveDefaultTypeface autoResolve(typeface);
typeface = autoResolve.get();
return typeface->countGlyphs() - 1;
fMatrix.reset();
}
-bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& b) {
- return fColor == b.fColor &&
- fShaderIndex == b.fShaderIndex &&
- fGraphicStateIndex == b.fGraphicStateIndex &&
- fMatrix == b.fMatrix &&
- fClipStack == b.fClipStack &&
- (fTextScaleX == 0 ||
- b.fTextScaleX == 0 ||
- (fTextScaleX == b.fTextScaleX && fTextFill == b.fTextFill));
+bool GraphicStateEntry::compareInitialState(const GraphicStateEntry& cur) {
+ return fColor == cur.fColor &&
+ fShaderIndex == cur.fShaderIndex &&
+ fGraphicStateIndex == cur.fGraphicStateIndex &&
+ fMatrix == cur.fMatrix &&
+ fClipStack == cur.fClipStack &&
+ (fTextScaleX == 0 ||
+ (fTextScaleX == cur.fTextScaleX && fTextFill == cur.fTextFill));
}
class GraphicStackState {
fLastMarginContentEntry(NULL),
fClipStack(NULL),
fEncoder(NULL),
- fRasterDpi(SkFloatToScalar(72.0f)) {
+ fRasterDpi(72.0f) {
// Just report that PDF does not supports perspective in the
// initial transform.
NOT_IMPLEMENTED(initialTransform.hasPerspective(), true);
fLastMarginContentEntry(NULL),
fClipStack(NULL),
fEncoder(NULL),
- fRasterDpi(SkFloatToScalar(72.0f)) {
+ fRasterDpi(72.0f) {
fInitialTransform.reset();
this->init();
}
&content.entry()->fContent);
}
+void SkPDFDevice::drawRRect(const SkDraw& draw, const SkRRect& rrect,
+ const SkPaint& paint) {
+ SkPath path;
+ path.addRRect(rrect);
+ this->drawPath(draw, path, paint, NULL, true);
+}
+
void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
const SkPaint& paint, const SkMatrix* prePathMatrix,
bool pathIsMutable) {
if (d.fClip->isEmpty()) {
return;
}
- NOT_IMPLEMENTED("drawVerticies", true);
+ // TODO: implement drawVertices
}
void SkPDFDevice::drawDevice(const SkDraw& d, SkBaseDevice* device,
return entry;
}
-void SkPDFDevice::finishContentEntry(const SkXfermode::Mode xfermode,
+void SkPDFDevice::finishContentEntry(SkXfermode::Mode xfermode,
SkPDFFormXObject* dst,
SkPath* shape) {
if (xfermode != SkXfermode::kClear_Mode &&
xfermode != SkXfermode::kSrc_Mode &&
+ xfermode != SkXfermode::kDstOver_Mode &&
xfermode != SkXfermode::kSrcIn_Mode &&
xfermode != SkXfermode::kDstIn_Mode &&
xfermode != SkXfermode::kSrcOut_Mode &&
SkASSERT(!dst);
return;
}
+ if (xfermode == SkXfermode::kDstOver_Mode) {
+ SkASSERT(!dst);
+ ContentEntry* firstContentEntry = getContentEntries()->get();
+ if (firstContentEntry->fContent.getOffset() == 0) {
+ // For DstOver, an empty content entry was inserted before the rest
+ // of the content entries. If nothing was drawn, it needs to be
+ // removed.
+ SkAutoTDelete<ContentEntry>* contentEntries = getContentEntries();
+ contentEntries->reset(firstContentEntry->fNext.detach());
+ }
+ return;
+ }
if (!dst) {
SkASSERT(xfermode == SkXfermode::kSrc_Mode ||
xfermode == SkXfermode::kSrcOut_Mode);
ContentEntry* contentEntries = getContentEntries()->get();
SkASSERT(dst);
SkASSERT(!contentEntries->fNext.get());
- // We have to make a copy of these here because changing the current
- // content into a form-xobject will destroy them.
+ // Changing the current content into a form-xobject will destroy the clip
+ // objects which is fine since the xobject will already be clipped. However
+ // if source has shape, we need to clip it too, so a copy of the clip is
+ // saved.
SkClipStack clipStack = contentEntries->fState.fClipStack;
SkRegion clipRegion = contentEntries->fState.fClipRegion;
+ SkMatrix identity;
+ identity.reset();
+ SkPaint stockPaint;
+
SkAutoTUnref<SkPDFFormXObject> srcFormXObject;
if (isContentEmpty()) {
- SkASSERT(xfermode == SkXfermode::kClear_Mode);
+ // If nothing was drawn and there's no shape, then the draw was a
+ // no-op, but dst needs to be restored for that to be true.
+ // If there is shape, then an empty source with Src, SrcIn, SrcOut,
+ // DstIn, DstAtop or Modulate reduces to Clear and DstOut or SrcAtop
+ // reduces to Dst.
+ if (shape == NULL || xfermode == SkXfermode::kDstOut_Mode ||
+ xfermode == SkXfermode::kSrcATop_Mode) {
+ ScopedContentEntry content(this, &fExistingClipStack,
+ fExistingClipRegion, identity,
+ stockPaint);
+ SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst),
+ &content.entry()->fContent);
+ return;
+ } else {
+ xfermode = SkXfermode::kClear_Mode;
+ }
} else {
SkASSERT(!fContentEntries->fNext.get());
srcFormXObject.reset(createFormXObjectFromDevice());
}
- SkMatrix identity;
- identity.reset();
-
// TODO(vandebo) srcFormXObject may contain alpha, but here we want it
// without alpha.
if (xfermode == SkXfermode::kSrcATop_Mode) {
// the non-transparent parts of the device and the outlines (shape) of
// all images and devices drawn.
drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst,
- &clipStack, clipRegion,
+ &fExistingClipStack, fExistingClipRegion,
SkXfermode::kSrcOver_Mode, true);
} else {
SkAutoTUnref<SkPDFFormXObject> dstMaskStorage;
dstMaskStorage.reset(createFormXObjectFromDevice());
dstMask = dstMaskStorage.get();
}
- drawFormXObjectWithMask(addXObjectResource(dst), dstMask, &clipStack,
- clipRegion, SkXfermode::kSrcOver_Mode, true);
+ drawFormXObjectWithMask(addXObjectResource(dst), dstMask,
+ &fExistingClipStack, fExistingClipRegion,
+ SkXfermode::kSrcOver_Mode, true);
}
- SkPaint stockPaint;
-
if (xfermode == SkXfermode::kClear_Mode) {
return;
} else if (xfermode == SkXfermode::kSrc_Mode ||
xfermode == SkXfermode::kDstATop_Mode) {
- ScopedContentEntry content(this, &clipStack, clipRegion, identity,
- stockPaint);
+ ScopedContentEntry content(this, &fExistingClipStack,
+ fExistingClipRegion, identity, stockPaint);
if (content.entry()) {
SkPDFUtils::DrawFormXObject(
this->addXObjectResource(srcFormXObject.get()),
return;
}
} else if (xfermode == SkXfermode::kSrcATop_Mode) {
- ScopedContentEntry content(this, &clipStack, clipRegion, identity,
- stockPaint);
+ ScopedContentEntry content(this, &fExistingClipStack,
+ fExistingClipRegion, identity, stockPaint);
if (content.entry()) {
SkPDFUtils::DrawFormXObject(this->addXObjectResource(dst),
&content.entry()->fContent);
xfermode == SkXfermode::kDstATop_Mode ||
xfermode == SkXfermode::kModulate_Mode);
- ScopedContentEntry inShapeContentEntry(this, &fExistingClipStack,
- fExistingClipRegion, identity,
- stockPaint);
- if (!inShapeContentEntry.entry()) {
- return;
- }
-
if (xfermode == SkXfermode::kSrcIn_Mode ||
xfermode == SkXfermode::kSrcOut_Mode ||
xfermode == SkXfermode::kSrcATop_Mode) {
drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()), dst,
- &clipStack, clipRegion,
+ &fExistingClipStack, fExistingClipRegion,
SkXfermode::kSrcOver_Mode,
xfermode == SkXfermode::kSrcOut_Mode);
} else {
SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
if (xfermode == SkXfermode::kModulate_Mode) {
drawFormXObjectWithMask(addXObjectResource(srcFormXObject.get()),
- dst, &clipStack, clipRegion,
+ dst, &fExistingClipStack,
+ fExistingClipRegion,
SkXfermode::kSrcOver_Mode, false);
mode = SkXfermode::kMultiply_Mode;
}
drawFormXObjectWithMask(addXObjectResource(dst), srcFormXObject.get(),
- &clipStack, clipRegion, mode,
+ &fExistingClipStack, fExistingClipRegion, mode,
xfermode == SkXfermode::kDstOut_Mode);
}
}
// Rasterize the bitmap using perspective in a new bitmap.
if (origMatrix.hasPerspective()) {
+ if (fRasterDpi == 0) {
+ return;
+ }
SkBitmap* subsetBitmap;
if (srcRect) {
if (!origBitmap.extractSubset(&tmpSubsetBitmap, *srcRect)) {
}
srcRect = NULL;
- // Transform the bitmap in the new space.
+ // Transform the bitmap in the new space, without taking into
+ // account the initial transform.
SkPath perspectiveOutline;
perspectiveOutline.addRect(
SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
// Retrieve the bounds of the new shape.
SkRect bounds = perspectiveOutline.getBounds();
- // TODO(edisonn): add DPI settings. Currently 1 pixel/point, which does
- // not look great, but it is not producing large PDFs.
+ // Transform the bitmap in the new space, taking into
+ // account the initial transform.
+ SkMatrix total = origMatrix;
+ total.postConcat(fInitialTransform);
+ total.postScale(SkIntToScalar(fRasterDpi) /
+ SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE),
+ SkIntToScalar(fRasterDpi) /
+ SkIntToScalar(DPI_FOR_RASTER_SCALE_ONE));
+ SkPath physicalPerspectiveOutline;
+ physicalPerspectiveOutline.addRect(
+ SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
+ SkIntToScalar(subsetBitmap->height())));
+ physicalPerspectiveOutline.transform(total);
+
+ SkScalar scaleX = physicalPerspectiveOutline.getBounds().width() /
+ bounds.width();
+ SkScalar scaleY = physicalPerspectiveOutline.getBounds().height() /
+ bounds.height();
// TODO(edisonn): A better approach would be to use a bitmap shader
// (in clamp mode) and draw a rect over the entire bounding box. Then
// the image. Avoiding alpha will reduce the pdf size and generation
// CPU time some.
- perspectiveBitmap.setConfig(SkBitmap::kARGB_8888_Config,
- SkScalarCeilToInt(bounds.width()),
- SkScalarCeilToInt(bounds.height()));
- perspectiveBitmap.allocPixels();
+ const int w = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().width());
+ const int h = SkScalarCeilToInt(physicalPerspectiveOutline.getBounds().height());
+ if (!perspectiveBitmap.allocPixels(SkImageInfo::MakeN32Premul(w, h))) {
+ return;
+ }
perspectiveBitmap.eraseColor(SK_ColorTRANSPARENT);
SkBitmapDevice device(perspectiveBitmap);
SkMatrix offsetMatrix = origMatrix;
offsetMatrix.postTranslate(-deltaX, -deltaY);
+ offsetMatrix.postScale(scaleX, scaleY);
// Translate the draw in the new canvas, so we perfectly fit the
// shape in the bitmap.
// Make sure the final bits are in the bitmap.
canvas.flush();
- // In the new space, we use the identity matrix translated.
- matrix.setTranslate(deltaX, deltaY);
+ // In the new space, we use the identity matrix translated
+ // and scaled to reflect DPI.
+ matrix.setScale(1 / scaleX, 1 / scaleY);
+ matrix.postTranslate(deltaX, deltaY);
+
perspectiveBounds.setRect(
SkIRect::MakeXYWH(SkScalarFloorToInt(bounds.x()),
SkScalarFloorToInt(bounds.y()),
return false;
}
-bool SkPDFDevice::allowImageFilter(SkImageFilter*) {
+bool SkPDFDevice::allowImageFilter(const SkImageFilter*) {
return false;
}