| /* |
| * 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) { |
| 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(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); |
| 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; |
| 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"); |
| fActiveTransform = m; |
| } |