/* * 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) { // 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. // TODO(vandebo) handle the paint (alpha and compositing mode). SkMatrix matrix; matrix.setTranslate(x, y); SkPDFDevice* pdfDevice = static_cast(device); SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice, matrix); fXObjectResources.push(xobject); // Transfer reference. fContent.append("/X"); fContent.appendS32(fXObjectResources.count() - 1); fContent.append(" Do\n"); } 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()); } if (fFontResources.count()) { SkRefPtr 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); SkRefPtr name = new SkPDFName(nameString); name->unref(); // SkRefPtr and new both took a reference. SkRefPtr fontRef = new SkPDFObjRef(fFontResources[i]); fontRef->unref(); // SkRefPtr and new both took a reference. fonts->insert(name.get(), fontRef.get()); } 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 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++) { SkRefPtr entry = new SkPDFName(procs[i]); entry->unref(); // SkRefPtr and new both took a reference. procSets->append(entry.get()); } fResourceDict->insert("ProcSet", procSets.get()); } return fResourceDict; } void SkPDFDevice::getResources(SkTDArray* 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 SkPDFDevice::getMediaBox() const { 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(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) { PAINTCHECK(getXfermode, != NULL); PAINTCHECK(getPathEffect, != NULL); PAINTCHECK(getMaskFilter, != NULL); PAINTCHECK(getShader, != NULL); PAINTCHECK(getColorFilter, != NULL); 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 (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 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); 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; }