blob: 72b2e1bf6a6a0dcd44281b67d848dc591f1bd3f3 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc.
*
* 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 "SkGlyphCache.h"
#include "SkPaint.h"
#include "SkPath.h"
#include "SkPDFImage.h"
#include "SkPDFGraphicState.h"
#include "SkPDFFont.h"
#include "SkPDFFormXObject.h"
#include "SkPDFTypes.h"
#include "SkPDFStream.h"
#include "SkRect.h"
#include "SkString.h"
#include "SkTextFormatParams.h"
#include "SkTypeface.h"
#include "SkTypes.h"
#define NOT_IMPLEMENTED(condition, assert) \
do { \
if (condition) { \
fprintf(stderr, "NOT_IMPLEMENTED: " #condition "\n"); \
SkDEBUGCODE(SkASSERT(!assert);) \
} \
} while(0)
// Utility functions
namespace {
SkString toPDFColor(SkColor color) {
SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere.
SkScalar colorMax = SkIntToScalar(0xFF);
SkString result;
result.appendScalar(SkScalarDiv(SkIntToScalar(SkColorGetR(color)),
colorMax));
result.append(" ");
result.appendScalar(SkScalarDiv(SkIntToScalar(SkColorGetG(color)),
colorMax));
result.append(" ");
result.appendScalar(SkScalarDiv(SkIntToScalar(SkColorGetB(color)),
colorMax));
result.append(" ");
return result;
}
SkPaint calculateTextPaint(const SkPaint& paint) {
SkPaint result = paint;
if (result.isFakeBoldText()) {
SkScalar fakeBoldScale = SkScalarInterpFunc(result.getTextSize(),
kStdFakeBoldInterpKeys,
kStdFakeBoldInterpValues,
kStdFakeBoldInterpLength);
SkScalar width = SkScalarMul(result.getTextSize(), fakeBoldScale);
if (result.getStyle() == SkPaint::kFill_Style)
result.setStyle(SkPaint::kStrokeAndFill_Style);
else
width += result.getStrokeWidth();
result.setStrokeWidth(width);
}
return result;
}
// Stolen from measure_text in SkDraw.cpp and then tweaked.
void alignText(SkDrawCacheProc glyphCacheProc, const SkPaint& paint,
const uint16_t* glyphs, size_t len, SkScalar* x, SkScalar* y,
SkScalar* width) {
if (paint.getTextAlign() == SkPaint::kLeft_Align && width == NULL)
return;
SkMatrix ident;
ident.reset();
SkAutoGlyphCache autoCache(paint, &ident);
SkGlyphCache* cache = autoCache.getCache();
const char* start = (char*)glyphs;
const char* stop = (char*)(glyphs + len);
SkFixed xAdv = 0, yAdv = 0;
// TODO(vandebo) This probably needs to take kerning into account.
while (start < stop) {
const SkGlyph& glyph = glyphCacheProc(cache, &start, 0, 0);
xAdv += glyph.fAdvanceX;
yAdv += glyph.fAdvanceY;
};
if (width)
*width = SkFixedToScalar(xAdv);
if (paint.getTextAlign() == SkPaint::kLeft_Align)
return;
SkScalar xAdj = SkFixedToScalar(xAdv);
SkScalar yAdj = SkFixedToScalar(yAdv);
if (paint.getTextAlign() == SkPaint::kCenter_Align) {
xAdj = SkScalarHalf(xAdj);
yAdj = SkScalarHalf(yAdj);
}
*x = *x - xAdj;
*y = *y - yAdj;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
SkDevice* SkPDFDeviceFactory::newDevice(SkCanvas*, SkBitmap::Config config,
int width, int height, bool isOpaque,
bool /*isForLayer*/) {
return SkNEW_ARGS(SkPDFDevice, (width, height));
}
static inline SkBitmap makeABitmap(int width, int height) {
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kNo_Config, width, height);
return bitmap;
}
SkPDFDevice::SkPDFDevice(int width, int height)
: SkDevice(NULL, makeABitmap(width, height), false),
fWidth(width),
fHeight(height),
fGraphicStackIndex(0) {
fGraphicStack[0].fColor = SK_ColorBLACK;
fGraphicStack[0].fTextSize = SK_ScalarNaN; // This has no default value.
fGraphicStack[0].fTextScaleX = SK_Scalar1;
fGraphicStack[0].fTextFill = SkPaint::kFill_Style;
fGraphicStack[0].fFont = NULL;
fGraphicStack[0].fGraphicState = NULL;
fGraphicStack[0].fClip.setRect(0,0, width, height);
fGraphicStack[0].fTransform.reset();
}
SkPDFDevice::~SkPDFDevice() {
fGraphicStateResources.unrefAll();
fXObjectResources.unrefAll();
fFontResources.unrefAll();
}
void SkPDFDevice::setMatrixClip(const SkMatrix& matrix,
const SkRegion& region,
const SkClipStack&) {
// See the comment in the header file above GraphicStackEntry.
if (region != fGraphicStack[fGraphicStackIndex].fClip) {
while (fGraphicStackIndex > 0)
popGS();
pushGS();
SkPath clipPath;
if (region.getBoundaryPath(&clipPath)) {
emitPath(clipPath);
SkPath::FillType clipFill = clipPath.getFillType();
NOT_IMPLEMENTED(clipFill == SkPath::kInverseEvenOdd_FillType,
false);
NOT_IMPLEMENTED(clipFill == SkPath::kInverseWinding_FillType,
false);
if (clipFill == SkPath::kEvenOdd_FillType)
fContent.append("W* n ");
else
fContent.append("W n ");
}
fGraphicStack[fGraphicStackIndex].fClip = region;
}
setTransform(matrix);
}
void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
SkMatrix identityTransform;
identityTransform.reset();
SkMatrix curTransform = setTransform(identityTransform);
SkPaint newPaint = paint;
newPaint.setStyle(SkPaint::kFill_Style);
updateGSFromPaint(newPaint, false);
SkRect all = SkRect::MakeWH(width() + 1, height() + 1);
drawRect(d, all, newPaint);
setTransform(curTransform);
}
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, false);
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, false);
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, false);
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()) {
// Create a path for the rectangle and apply the path effect to it.
SkPath path;
path.addRect(r);
paint.getFillPath(path, &path);
SkPaint noEffectPaint(paint);
SkSafeUnref(noEffectPaint.setPathEffect(NULL));
drawPath(d, path, noEffectPaint, NULL, true);
return;
}
updateGSFromPaint(paint, false);
// 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());
paintPath(paint.getStyle(), SkPath::kWinding_FillType);
}
void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path,
const SkPaint& paint, const SkMatrix* prePathMatrix,
bool pathIsMutable) {
NOT_IMPLEMENTED(prePathMatrix != NULL, true);
if (paint.getPathEffect()) {
// Apply the path effect to path and draw it that way.
SkPath noEffectPath;
paint.getFillPath(path, &noEffectPath);
SkPaint noEffectPaint(paint);
SkSafeUnref(noEffectPaint.setPathEffect(NULL));
drawPath(d, noEffectPath, noEffectPaint, NULL, true);
return;
}
updateGSFromPaint(paint, false);
emitPath(path);
paintPath(paint.getStyle(), path.getFillType());
}
void SkPDFDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap,
const SkIRect* srcRect,
const SkMatrix& matrix, const SkPaint& paint) {
SkMatrix transform = matrix;
transform.postConcat(fGraphicStack[fGraphicStackIndex].fTransform);
internalDrawBitmap(transform, bitmap, srcRect, paint);
}
void SkPDFDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap,
int x, int y, const SkPaint& paint) {
SkMatrix matrix;
matrix.setTranslate(x, y);
internalDrawBitmap(matrix, bitmap, NULL, paint);
}
void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
SkScalar x, SkScalar y, const SkPaint& paint) {
SkPaint textPaint = calculateTextPaint(paint);
updateGSFromPaint(textPaint, true);
// Make sure we have a glyph id encoding.
SkAutoFree glyphStorage;
uint16_t* glyphIDs;
size_t numGlyphs;
if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
numGlyphs = paint.textToGlyphs(text, len, NULL);
glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
SK_MALLOC_TEMP | SK_MALLOC_THROW);
glyphStorage.set(glyphIDs);
paint.textToGlyphs(text, len, glyphIDs);
textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
} else {
SkASSERT((len & 1) == 0);
numGlyphs = len / 2;
glyphIDs = (uint16_t*)text;
}
SkAutoFree encodedStorage(
sk_malloc_flags(numGlyphs * 2, SK_MALLOC_TEMP | SK_MALLOC_THROW));
SkScalar width;
SkScalar* widthPtr = NULL;
if (textPaint.isUnderlineText() || textPaint.isStrikeThruText())
widthPtr = &width;
SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
alignText(glyphCacheProc, textPaint, glyphIDs, numGlyphs, &x, &y, widthPtr);
fContent.append("BT\n");
setTextTransform(x, y, textPaint.getTextSkewX());
size_t consumedGlyphCount = 0;
while (numGlyphs > consumedGlyphCount) {
updateFont(textPaint, glyphIDs[consumedGlyphCount]);
SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont;
size_t encodedLength = numGlyphs * 2;
consumedGlyphCount += font->glyphsToPDFFontEncoding(
glyphIDs + consumedGlyphCount, numGlyphs - consumedGlyphCount,
encodedStorage.get(), &encodedLength);
if (font->multiByteGlyphs())
encodedLength /= 2;
fContent.append(
SkPDFString::formatString((const uint16_t*)encodedStorage.get(),
encodedLength,
font->multiByteGlyphs()));
fContent.append(" Tj\n");
}
fContent.append("ET\n");
// Draw underline and/or strikethrough if the paint has them.
// drawPosText() and drawTextOnPath() don't draw underline or strikethrough
// because the raster versions don't. Use paint instead of textPaint
// because we may have changed strokeWidth to do fakeBold text.
if (paint.isUnderlineText() || paint.isStrikeThruText()) {
SkScalar textSize = paint.getTextSize();
SkScalar height = SkScalarMul(textSize, kStdUnderline_Thickness);
if (paint.isUnderlineText()) {
SkScalar top = SkScalarMulAdd(textSize, kStdUnderline_Offset, y);
SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
drawRect(d, r, paint);
}
if (paint.isStrikeThruText()) {
SkScalar top = SkScalarMulAdd(textSize, kStdStrikeThru_Offset, y);
SkRect r = SkRect::MakeXYWH(x, top - height, width, height);
drawRect(d, r, paint);
}
}
}
void SkPDFDevice::drawPosText(const SkDraw&, const void* text, size_t len,
const SkScalar pos[], SkScalar constY,
int scalarsPerPos, const SkPaint& paint) {
SkASSERT(1 == scalarsPerPos || 2 == scalarsPerPos);
SkPaint textPaint = calculateTextPaint(paint);
updateGSFromPaint(textPaint, true);
// Make sure we have a glyph id encoding.
SkAutoFree glyphStorage;
uint16_t* glyphIDs;
size_t numGlyphs;
if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) {
numGlyphs = paint.textToGlyphs(text, len, NULL);
glyphIDs = (uint16_t*)sk_malloc_flags(numGlyphs * 2,
SK_MALLOC_TEMP | SK_MALLOC_THROW);
glyphStorage.set(glyphIDs);
paint.textToGlyphs(text, len, glyphIDs);
textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
} else {
SkASSERT((len & 1) == 0);
numGlyphs = len / 2;
glyphIDs = (uint16_t*)text;
}
SkDrawCacheProc glyphCacheProc = textPaint.getDrawCacheProc();
fContent.append("BT\n");
updateFont(textPaint, glyphIDs[0]);
for (size_t i = 0; i < numGlyphs; i++) {
SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont;
uint16_t encodedValue;
size_t encodedLength = 2;
if (font->glyphsToPDFFontEncoding(glyphIDs + i, 1, &encodedValue,
&encodedLength) == 0) {
updateFont(textPaint, glyphIDs[i]);
i--;
continue;
}
SkScalar x = pos[i * scalarsPerPos];
SkScalar y = scalarsPerPos == 1 ? constY : pos[i * scalarsPerPos + 1];
alignText(glyphCacheProc, textPaint, glyphIDs + i, 1, &x, &y, NULL);
setTextTransform(x, y, textPaint.getTextSkewX());
fContent.append(SkPDFString::formatString(&encodedValue, 1,
font->multiByteGlyphs()));
fContent.append(" Tj\n");
}
fContent.append("ET\n");
}
void SkPDFDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len,
const SkPath& path, const SkMatrix* matrix,
const SkPaint& paint) {
NOT_IMPLEMENTED("drawTextOnPath", true);
}
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) {
NOT_IMPLEMENTED("drawVerticies", true);
}
void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y,
const SkPaint& paint) {
if ((device->getDeviceCapabilities() & kVector_Capability) == 0) {
// If we somehow get a raster device, do what our parent would do.
SkDevice::drawDevice(d, device, x, y, paint);
return;
}
// Assume that a vector capable device means that it's a PDF Device.
SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
SkMatrix matrix;
matrix.setTranslate(x, y);
SkMatrix curTransform = setTransform(matrix);
updateGSFromPaint(paint, false);
SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
fXObjectResources.push(xobject); // Transfer reference.
fContent.append("/X");
fContent.appendS32(fXObjectResources.count() - 1);
fContent.append(" Do\n");
setTransform(curTransform);
}
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);
extGState->insert(
nameString.c_str(),
new SkPDFObjRef(fGraphicStateResources[i]))->unref();
}
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);
xObjects->insert(
nameString.c_str(),
new SkPDFObjRef(fXObjectResources[i]))->unref();
}
fResourceDict->insert("XObject", xObjects.get());
}
if (fFontResources.count()) {
SkRefPtr<SkPDFDict> fonts = new SkPDFDict();
fonts->unref(); // SkRefPtr and new both took a reference.
for (int i = 0; i < fFontResources.count(); i++) {
SkString nameString("F");
nameString.appendS32(i);
fonts->insert(nameString.c_str(),
new SkPDFObjRef(fFontResources[i]))->unref();
}
fResourceDict->insert("Font", fonts.get());
}
// For compatibility, add all proc sets (only used for output to PS
// devices).
const char procs[][7] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
SkRefPtr<SkPDFArray> procSets = new SkPDFArray();
procSets->unref(); // SkRefPtr and new both took a reference.
procSets->reserve(SK_ARRAY_COUNT(procs));
for (size_t i = 0; i < SK_ARRAY_COUNT(procs); i++)
procSets->append(new SkPDFName(procs[i]))->unref();
fResourceDict->insert("ProcSet", procSets.get());
}
return fResourceDict;
}
void SkPDFDevice::getResources(SkTDArray<SkPDFObject*>* resourceList) const {
resourceList->setReserve(resourceList->count() +
fGraphicStateResources.count() +
fXObjectResources.count() +
fFontResources.count());
for (int i = 0; i < fGraphicStateResources.count(); i++) {
resourceList->push(fGraphicStateResources[i]);
fGraphicStateResources[i]->ref();
fGraphicStateResources[i]->getResources(resourceList);
}
for (int i = 0; i < fXObjectResources.count(); i++) {
resourceList->push(fXObjectResources[i]);
fXObjectResources[i]->ref();
fXObjectResources[i]->getResources(resourceList);
}
for (int i = 0; i < fFontResources.count(); i++) {
resourceList->push(fFontResources[i]);
fFontResources[i]->ref();
fFontResources[i]->getResources(resourceList);
}
}
SkRefPtr<SkPDFArray> SkPDFDevice::getMediaBox() const {
SkRefPtr<SkPDFInt> zero = new SkPDFInt(0);
zero->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(new SkPDFInt(fWidth))->unref();
mediaBox->append(new SkPDFInt(fHeight))->unref();
return mediaBox;
}
SkString SkPDFDevice::content(bool flipOrigin) const {
SkString result;
// Scale and translate to move the origin from the lower left to the
// upper left.
if (flipOrigin)
result.printf("1 0 0 -1 0 %d cm\n", fHeight);
result.append(fContent);
for (int i = 0; i < fGraphicStackIndex; i++)
result.append("Q\n");
return result;
}
// Private
// TODO(vandebo) handle these cases.
#define PAINTCHECK(x,y) NOT_IMPLEMENTED(newPaint.x() y, false)
void SkPDFDevice::updateGSFromPaint(const SkPaint& newPaint, bool forText) {
SkASSERT(newPaint.getPathEffect() == NULL);
PAINTCHECK(getMaskFilter, != NULL);
PAINTCHECK(getShader, != NULL);
PAINTCHECK(getColorFilter, != NULL);
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 (fGraphicStack[fGraphicStackIndex].fGraphicState !=
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");
fGraphicStack[fGraphicStackIndex].fGraphicState = newGraphicState.get();
}
SkColor newColor = newPaint.getColor();
newColor = SkColorSetA(newColor, 0xFF);
if (fGraphicStack[fGraphicStackIndex].fColor != newColor) {
SkString colorString = toPDFColor(newColor);
fContent.append(colorString);
fContent.append("RG ");
fContent.append(colorString);
fContent.append("rg\n");
fGraphicStack[fGraphicStackIndex].fColor = newColor;
}
if (forText) {
if (fGraphicStack[fGraphicStackIndex].fTextScaleX !=
newPaint.getTextScaleX()) {
SkScalar scale = newPaint.getTextScaleX();
SkScalar pdfScale = SkScalarMul(scale, SkIntToScalar(100));
fContent.appendScalar(pdfScale);
fContent.append(" Tz\n");
fGraphicStack[fGraphicStackIndex].fTextScaleX = scale;
}
if (fGraphicStack[fGraphicStackIndex].fTextFill !=
newPaint.getStyle()) {
SK_COMPILE_ASSERT(SkPaint::kFill_Style == 0, enum_must_match_value);
SK_COMPILE_ASSERT(SkPaint::kStroke_Style == 1,
enum_must_match_value);
SK_COMPILE_ASSERT(SkPaint::kStrokeAndFill_Style == 2,
enum_must_match_value);
fContent.appendS32(newPaint.getStyle());
fContent.append(" Tr\n");
fGraphicStack[fGraphicStackIndex].fTextFill = newPaint.getStyle();
}
}
}
void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID) {
uint32_t fontID = SkTypeface::UniqueID(paint.getTypeface());
if (fGraphicStack[fGraphicStackIndex].fTextSize != paint.getTextSize() ||
fGraphicStack[fGraphicStackIndex].fFont == NULL ||
fGraphicStack[fGraphicStackIndex].fFont->fontID() != fontID ||
!fGraphicStack[fGraphicStackIndex].fFont->hasGlyph(glyphID)) {
int fontIndex = getFontResourceIndex(fontID, glyphID);
fContent.append("/F");
fContent.appendS32(fontIndex);
fContent.append(" ");
fContent.appendScalar(paint.getTextSize());
fContent.append(" Tf\n");
fGraphicStack[fGraphicStackIndex].fTextSize = paint.getTextSize();
fGraphicStack[fGraphicStackIndex].fFont = fFontResources[fontIndex];
}
}
int SkPDFDevice::getFontResourceIndex(uint32_t fontID, uint16_t glyphID) {
SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(fontID, glyphID);
newFont->unref(); // getFontResource and SkRefPtr both took a ref.
int resourceIndex = fFontResources.find(newFont.get());
if (resourceIndex < 0) {
resourceIndex = fFontResources.count();
fFontResources.push(newFont.get());
newFont->ref();
}
return resourceIndex;
}
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(" ");
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::emitPath(const SkPath& path) {
SkPoint args[4];
SkPath::Iter iter(path, false);
for (SkPath::Verb verb = iter.next(args);
verb != SkPath::kDone_Verb;
verb = iter.next(args)) {
// args gets all the points, even the implicit first point.
switch (verb) {
case SkPath::kMove_Verb:
moveTo(args[0].fX, args[0].fY);
break;
case SkPath::kLine_Verb:
appendLine(args[1].fX, args[1].fY);
break;
case SkPath::kQuad_Verb: {
// Convert quad to cubic (degree elevation). http://goo.gl/vS4i
const SkScalar three = SkIntToScalar(3);
args[1].scale(SkIntToScalar(2));
SkScalar ctl1X = SkScalarDiv(args[0].fX + args[1].fX, three);
SkScalar ctl1Y = SkScalarDiv(args[0].fY + args[1].fY, three);
SkScalar ctl2X = SkScalarDiv(args[2].fX + args[1].fX, three);
SkScalar ctl2Y = SkScalarDiv(args[2].fY + args[1].fY, three);
appendCubic(ctl1X, ctl1Y, ctl2X, ctl2Y, args[2].fX, args[2].fY);
break;
}
case SkPath::kCubic_Verb:
appendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
args[3].fX, args[3].fY);
break;
case SkPath::kClose_Verb:
closePath();
break;
case SkPath::kDone_Verb:
break;
default:
SkASSERT(false);
break;
}
}
}
void SkPDFDevice::closePath() {
fContent.append("h\n");
}
void SkPDFDevice::paintPath(SkPaint::Style style, SkPath::FillType fill) {
if (style == SkPaint::kFill_Style)
fContent.append("f");
else if (style == SkPaint::kStrokeAndFill_Style)
fContent.append("B");
else if (style == SkPaint::kStroke_Style)
fContent.append("S");
if (style != SkPaint::kStroke_Style) {
// Not supported yet.
NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
if (fill == SkPath::kEvenOdd_FillType)
fContent.append("*");
}
fContent.append("\n");
}
void SkPDFDevice::strokePath() {
paintPath(SkPaint::kStroke_Style, SkPath::kWinding_FillType);
}
void SkPDFDevice::pushGS() {
SkASSERT(fGraphicStackIndex < 2);
fContent.append("q\n");
fGraphicStackIndex++;
fGraphicStack[fGraphicStackIndex] = fGraphicStack[fGraphicStackIndex - 1];
}
void SkPDFDevice::popGS() {
SkASSERT(fGraphicStackIndex > 0);
fContent.append("Q\n");
fGraphicStackIndex--;
}
void SkPDFDevice::setTextTransform(SkScalar x, SkScalar y, SkScalar textSkewX) {
// Flip the text about the x-axis to account for origin swap and include
// the passed parameters.
fContent.append("1 0 ");
fContent.appendScalar(0 - textSkewX);
fContent.append(" -1 ");
fContent.appendScalar(x);
fContent.append(" ");
fContent.appendScalar(y);
fContent.append(" Tm\n");
}
void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
const SkBitmap& bitmap,
const SkIRect* srcRect,
const SkPaint& paint) {
SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
if (srcRect && !subset.intersect(*srcRect))
return;
SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, paint);
if (!image)
return;
SkMatrix scaled;
// Adjust for origin flip.
scaled.setScale(1, -1);
scaled.postTranslate(0, 1);
// Scale the image up from 1x1 to WxH.
scaled.postScale(subset.width(), subset.height());
scaled.postConcat(matrix);
SkMatrix curTransform = setTransform(scaled);
updateGSFromPaint(paint, false);
fXObjectResources.push(image); // Transfer reference.
fContent.append("/X");
fContent.appendS32(fXObjectResources.count() - 1);
fContent.append(" Do\n");
setTransform(curTransform);
}
SkMatrix SkPDFDevice::setTransform(const SkMatrix& m) {
SkMatrix old = fGraphicStack[fGraphicStackIndex].fTransform;
if (old == m)
return old;
if (old.getType() != SkMatrix::kIdentity_Mask) {
SkASSERT(fGraphicStackIndex > 0);
SkASSERT(fGraphicStack[fGraphicStackIndex - 1].fTransform.getType() ==
SkMatrix::kIdentity_Mask);
SkASSERT(fGraphicStack[fGraphicStackIndex].fClip ==
fGraphicStack[fGraphicStackIndex - 1].fClip);
popGS();
}
if (m.getType() == SkMatrix::kIdentity_Mask)
return old;
if (fGraphicStackIndex == 0 || fGraphicStack[fGraphicStackIndex].fClip !=
fGraphicStack[fGraphicStackIndex - 1].fClip)
pushGS();
SkScalar transform[6];
SkAssertResult(m.pdfTransform(transform));
for (size_t i = 0; i < SK_ARRAY_COUNT(transform); i++) {
fContent.appendScalar(transform[i]);
fContent.append(" ");
}
fContent.append("cm\n");
fGraphicStack[fGraphicStackIndex].fTransform = m;
return old;
}