| /* |
| * 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 "SkDraw.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 "SkPDFShader.h" |
| #include "SkPDFStream.h" |
| #include "SkPDFUtils.h" |
| #include "SkRect.h" |
| #include "SkString.h" |
| #include "SkTextFormatParams.h" |
| #include "SkTypeface.h" |
| #include "SkTypes.h" |
| |
| // Utility functions |
| |
| namespace { |
| |
| void emitPDFColor(SkColor color, SkWStream* result) { |
| SkASSERT(SkColorGetA(color) == 0xFF); // We handle alpha elsewhere. |
| SkScalar colorMax = SkIntToScalar(0xFF); |
| SkPDFScalar::Append( |
| SkScalarDiv(SkIntToScalar(SkColorGetR(color)), colorMax), result); |
| result->writeText(" "); |
| SkPDFScalar::Append( |
| SkScalarDiv(SkIntToScalar(SkColorGetG(color)), colorMax), result); |
| result->writeText(" "); |
| SkPDFScalar::Append( |
| SkScalarDiv(SkIntToScalar(SkColorGetB(color)), colorMax), result); |
| result->writeText(" "); |
| } |
| |
| 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) { |
| SkMatrix initialTransform; |
| initialTransform.reset(); |
| if (isForLayer) { |
| initialTransform.setTranslate(0, height); |
| initialTransform.preScale(1, -1); |
| } |
| return SkNEW_ARGS(SkPDFDevice, (width, height, initialTransform)); |
| } |
| |
| 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, |
| const SkMatrix& initialTransform) |
| : 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].fShader = NULL; |
| fGraphicStack[0].fGraphicState = NULL; |
| fGraphicStack[0].fClip.setRect(0,0, width, height); |
| fGraphicStack[0].fTransform.reset(); |
| |
| // Skia generally uses the top left as the origin but PDF natively has the |
| // origin at the bottom left. This matrix corrects for that. When layering, |
| // we specify an inverse correction to cancel this out. |
| fInitialTransform.setTranslate(0, height); |
| fInitialTransform.preScale(1, -1); |
| fInitialTransform.preConcat(initialTransform); |
| if (fInitialTransform.getType() != SkMatrix::kIdentity_Mask) { |
| SkPDFUtils::AppendTransform(fInitialTransform, &fContent); |
| } |
| } |
| |
| SkPDFDevice::~SkPDFDevice() { |
| fGraphicStateResources.unrefAll(); |
| fXObjectResources.unrefAll(); |
| fFontResources.unrefAll(); |
| fShaderResources.unrefAll(); |
| } |
| |
| void SkPDFDevice::setMatrixClip(const SkMatrix& matrix, |
| const SkRegion& region, |
| const SkClipStack&) { |
| if (region.isEmpty()) { |
| return; |
| } |
| |
| // TODO(vandebo) SkCanvas may not draw anything after doing this call, we |
| // should defer writing anything out to fContent until we actually get |
| // a draw command. In fact SkDraw contains the clip and transform, so |
| // this method should do nothing and we should have an update |
| // transform-and-clip method, like update paint. |
| |
| // 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)) { |
| SkPDFUtils::EmitPath(clipPath, &fContent); |
| |
| 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.writeText("W* n "); |
| else |
| fContent.writeText("W n "); |
| } |
| |
| fGraphicStack[fGraphicStackIndex].fClip = region; |
| } |
| setTransform(matrix); |
| } |
| |
| void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| |
| SkMatrix identityTransform; |
| identityTransform.reset(); |
| SkMatrix curTransform = setTransform(identityTransform); |
| |
| SkPaint newPaint = paint; |
| newPaint.setStyle(SkPaint::kFill_Style); |
| updateGSFromPaint(newPaint, false); |
| |
| SkRect all = SkRect::MakeWH(SkIntToScalar(this->width()), |
| SkIntToScalar(this->height())); |
| 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 || d.fClip->isEmpty()) { |
| return; |
| } |
| |
| switch (mode) { |
| case SkCanvas::kPolygon_PointMode: |
| updateGSFromPaint(paint, false); |
| SkPDFUtils::MoveTo(points[0].fX, points[0].fY, &fContent); |
| for (size_t i = 1; i < count; i++) { |
| SkPDFUtils::AppendLine(points[i].fX, points[i].fY, &fContent); |
| } |
| SkPDFUtils::StrokePath(&fContent); |
| break; |
| case SkCanvas::kLines_PointMode: |
| updateGSFromPaint(paint, false); |
| for (size_t i = 0; i < count/2; i++) { |
| SkPDFUtils::MoveTo(points[i * 2].fX, points[i * 2].fY, |
| &fContent); |
| SkPDFUtils::AppendLine(points[i * 2 + 1].fX, |
| points[i * 2 + 1].fY, &fContent); |
| SkPDFUtils::StrokePath(&fContent); |
| } |
| break; |
| case SkCanvas::kPoints_PointMode: |
| if (paint.getStrokeCap() == SkPaint::kRound_Cap) { |
| updateGSFromPaint(paint, false); |
| for (size_t i = 0; i < count; i++) { |
| SkPDFUtils::MoveTo(points[i].fX, points[i].fY, &fContent); |
| SkPDFUtils::StrokePath(&fContent); |
| } |
| } 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 (d.fClip->isEmpty()) { |
| return; |
| } |
| |
| 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; |
| SkPDFUtils::AppendRectangle(r.fLeft, bottom, r.width(), r.height(), |
| &fContent); |
| SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType, |
| &fContent); |
| } |
| |
| void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path, |
| const SkPaint& paint, const SkMatrix* prePathMatrix, |
| bool pathIsMutable) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| 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); |
| |
| SkPDFUtils::EmitPath(path, &fContent); |
| SkPDFUtils::PaintPath(paint.getStyle(), path.getFillType(), &fContent); |
| } |
| |
| void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap, |
| const SkIRect* srcRect, |
| const SkMatrix& matrix, const SkPaint& paint) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| |
| SkMatrix transform = matrix; |
| transform.postConcat(fGraphicStack[fGraphicStackIndex].fTransform); |
| internalDrawBitmap(transform, bitmap, srcRect, paint); |
| } |
| |
| void SkPDFDevice::drawSprite(const SkDraw& d, const SkBitmap& bitmap, |
| int x, int y, const SkPaint& paint) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| |
| SkMatrix matrix; |
| matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(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) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| |
| SkPaint textPaint = calculateTextPaint(paint); |
| updateGSFromPaint(textPaint, true); |
| |
| // We want the text in glyph id encoding and a writable buffer, so we end |
| // up making a copy either way. |
| size_t numGlyphs = paint.textToGlyphs(text, len, NULL); |
| uint16_t* glyphIDs = |
| (uint16_t*)sk_malloc_flags(numGlyphs * 2, |
| SK_MALLOC_TEMP | SK_MALLOC_THROW); |
| SkAutoFree autoFreeGlyphIDs(glyphIDs); |
| if (paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding) { |
| paint.textToGlyphs(text, len, glyphIDs); |
| textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); |
| } else { |
| SkASSERT((len & 1) == 0); |
| SkASSERT(len / 2 == numGlyphs); |
| memcpy(glyphIDs, text, len); |
| } |
| |
| 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.writeText("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 availableGlyphs = |
| font->glyphsToPDFFontEncoding(glyphIDs + consumedGlyphCount, |
| numGlyphs - consumedGlyphCount); |
| SkString encodedString = |
| SkPDFString::formatString(glyphIDs + consumedGlyphCount, |
| availableGlyphs, font->multiByteGlyphs()); |
| fContent.writeText(encodedString.c_str()); |
| consumedGlyphCount += availableGlyphs; |
| fContent.writeText(" Tj\n"); |
| } |
| fContent.writeText("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& d, const void* text, size_t len, |
| const SkScalar pos[], SkScalar constY, |
| int scalarsPerPos, const SkPaint& paint) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| |
| 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.writeText("BT\n"); |
| updateFont(textPaint, glyphIDs[0]); |
| for (size_t i = 0; i < numGlyphs; i++) { |
| SkPDFFont* font = fGraphicStack[fGraphicStackIndex].fFont; |
| uint16_t encodedValue = glyphIDs[i]; |
| if (font->glyphsToPDFFontEncoding(&encodedValue, 1) != 1) { |
| 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()); |
| SkString encodedString = |
| SkPDFString::formatString(&encodedValue, 1, |
| font->multiByteGlyphs()); |
| fContent.writeText(encodedString.c_str()); |
| fContent.writeText(" Tj\n"); |
| } |
| fContent.writeText("ET\n"); |
| } |
| |
| void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len, |
| const SkPath& path, const SkMatrix* matrix, |
| const SkPaint& paint) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| NOT_IMPLEMENTED("drawTextOnPath", true); |
| } |
| |
| void SkPDFDevice::drawVertices(const SkDraw& d, SkCanvas::VertexMode, |
| int vertexCount, const SkPoint verts[], |
| const SkPoint texs[], const SkColor colors[], |
| SkXfermode* xmode, const uint16_t indices[], |
| int indexCount, const SkPaint& paint) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| NOT_IMPLEMENTED("drawVerticies", true); |
| } |
| |
| void SkPDFDevice::drawDevice(const SkDraw& d, SkDevice* device, int x, int y, |
| const SkPaint& paint) { |
| if (d.fClip->isEmpty()) { |
| return; |
| } |
| |
| 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(SkIntToScalar(x), SkIntToScalar(y)); |
| SkMatrix curTransform = setTransform(matrix); |
| updateGSFromPaint(paint, false); |
| |
| SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice); |
| fXObjectResources.push(xobject); // Transfer reference. |
| fContent.writeText("/X"); |
| fContent.writeDecAsText(fXObjectResources.count() - 1); |
| fContent.writeText(" 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()); |
| } |
| |
| if (fShaderResources.count()) { |
| SkRefPtr<SkPDFDict> patterns = new SkPDFDict(); |
| patterns->unref(); // SkRefPtr and new both took a reference. |
| for (int i = 0; i < fShaderResources.count(); i++) { |
| SkString nameString("P"); |
| nameString.appendS32(i); |
| patterns->insert(nameString.c_str(), |
| new SkPDFObjRef(fShaderResources[i]))->unref(); |
| } |
| fResourceDict->insert("Pattern", patterns.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() + |
| fShaderResources.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); |
| } |
| for (int i = 0; i < fShaderResources.count(); i++) { |
| resourceList->push(fShaderResources[i]); |
| fShaderResources[i]->ref(); |
| fShaderResources[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; |
| } |
| |
| SkStream* SkPDFDevice::content() const { |
| size_t offset = fContent.getOffset(); |
| char* data = (char*)sk_malloc_throw(offset + fGraphicStackIndex * 2); |
| fContent.copyTo(data); |
| for (int i = 0; i < fGraphicStackIndex; i++) { |
| data[offset++] = 'Q'; |
| data[offset++] = '\n'; |
| } |
| SkMemoryStream* result = new SkMemoryStream; |
| result->setMemoryOwned(data, offset); |
| return result; |
| } |
| |
| void SkPDFDevice::updateGSFromPaint(const SkPaint& paint, bool forText) { |
| SkASSERT(paint.getPathEffect() == NULL); |
| |
| NOT_IMPLEMENTED(paint.getMaskFilter() != NULL, false); |
| NOT_IMPLEMENTED(paint.getColorFilter() != NULL, false); |
| |
| SkPaint newPaint = paint; |
| |
| // PDF treats a shader as a color, so we only set one or the other. |
| SkRefPtr<SkPDFShader> pdfShader; |
| const SkShader* shader = newPaint.getShader(); |
| if (shader) { |
| // PDF positions patterns relative to the initial transform, so |
| // we need to apply the current transform to the shader parameters. |
| SkMatrix transform = fGraphicStack[fGraphicStackIndex].fTransform; |
| transform.postConcat(fInitialTransform); |
| |
| // PDF doesn't support kClamp_TileMode, so we simulate it by making |
| // a pattern the size of the drawing surface. |
| SkIRect bounds = fGraphicStack[fGraphicStackIndex].fClip.getBounds(); |
| pdfShader = SkPDFShader::getPDFShader(*shader, transform, bounds); |
| SkSafeUnref(pdfShader.get()); // getShader and SkRefPtr both took a ref |
| |
| // A color shader is treated as an invalid shader so we don't have |
| // to set a shader just for a color. |
| if (pdfShader.get() == NULL) { |
| newPaint.setColor(0); |
| |
| // Check for a color shader. |
| SkShader::GradientInfo gradientInfo; |
| SkColor gradientColor; |
| gradientInfo.fColors = &gradientColor; |
| gradientInfo.fColorOffsets = NULL; |
| gradientInfo.fColorCount = 1; |
| if (shader->asAGradient(&gradientInfo) == |
| SkShader::kColor_GradientType) { |
| newPaint.setColor(gradientColor); |
| } |
| } |
| } |
| |
| if (pdfShader) { |
| // pdfShader has been canonicalized so we can directly compare |
| // pointers. |
| if (fGraphicStack[fGraphicStackIndex].fShader != pdfShader.get()) { |
| int resourceIndex = fShaderResources.find(pdfShader.get()); |
| if (resourceIndex < 0) { |
| resourceIndex = fShaderResources.count(); |
| fShaderResources.push(pdfShader.get()); |
| pdfShader->ref(); |
| } |
| fContent.writeText("/Pattern CS /Pattern cs /P"); |
| fContent.writeDecAsText(resourceIndex); |
| fContent.writeText(" SCN /P"); |
| fContent.writeDecAsText(resourceIndex); |
| fContent.writeText(" scn\n"); |
| fGraphicStack[fGraphicStackIndex].fShader = pdfShader.get(); |
| } |
| } else { |
| SkColor newColor = newPaint.getColor(); |
| newColor = SkColorSetA(newColor, 0xFF); |
| if (fGraphicStack[fGraphicStackIndex].fShader || |
| fGraphicStack[fGraphicStackIndex].fColor != newColor) { |
| emitPDFColor(newColor, &fContent); |
| fContent.writeText("RG "); |
| emitPDFColor(newColor, &fContent); |
| fContent.writeText("rg\n"); |
| fGraphicStack[fGraphicStackIndex].fColor = newColor; |
| fGraphicStack[fGraphicStackIndex].fShader = 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.writeText("/G"); |
| fContent.writeDecAsText(resourceIndex); |
| fContent.writeText(" gs\n"); |
| fGraphicStack[fGraphicStackIndex].fGraphicState = newGraphicState.get(); |
| } |
| |
| if (forText) { |
| if (fGraphicStack[fGraphicStackIndex].fTextScaleX != |
| newPaint.getTextScaleX()) { |
| SkScalar scale = newPaint.getTextScaleX(); |
| SkScalar pdfScale = SkScalarMul(scale, SkIntToScalar(100)); |
| SkPDFScalar::Append(pdfScale, &fContent); |
| fContent.writeText(" 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.writeDecAsText(newPaint.getStyle()); |
| fContent.writeText(" Tr\n"); |
| fGraphicStack[fGraphicStackIndex].fTextFill = newPaint.getStyle(); |
| } |
| } |
| } |
| |
| void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID) { |
| SkTypeface* typeface = paint.getTypeface(); |
| if (fGraphicStack[fGraphicStackIndex].fTextSize != paint.getTextSize() || |
| fGraphicStack[fGraphicStackIndex].fFont == NULL || |
| fGraphicStack[fGraphicStackIndex].fFont->typeface() != typeface || |
| !fGraphicStack[fGraphicStackIndex].fFont->hasGlyph(glyphID)) { |
| int fontIndex = getFontResourceIndex(typeface, glyphID); |
| fContent.writeText("/F"); |
| fContent.writeDecAsText(fontIndex); |
| fContent.writeText(" "); |
| SkPDFScalar::Append(paint.getTextSize(), &fContent); |
| fContent.writeText(" Tf\n"); |
| fGraphicStack[fGraphicStackIndex].fTextSize = paint.getTextSize(); |
| fGraphicStack[fGraphicStackIndex].fFont = fFontResources[fontIndex]; |
| } |
| } |
| |
| int SkPDFDevice::getFontResourceIndex(SkTypeface* typeface, uint16_t glyphID) { |
| SkRefPtr<SkPDFFont> newFont = SkPDFFont::getFontResource(typeface, 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::pushGS() { |
| SkASSERT(fGraphicStackIndex < 2); |
| fContent.writeText("q\n"); |
| fGraphicStackIndex++; |
| fGraphicStack[fGraphicStackIndex] = fGraphicStack[fGraphicStackIndex - 1]; |
| } |
| |
| void SkPDFDevice::popGS() { |
| SkASSERT(fGraphicStackIndex > 0); |
| fContent.writeText("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.writeText("1 0 "); |
| SkPDFScalar::Append(0 - textSkewX, &fContent); |
| fContent.writeText(" -1 "); |
| SkPDFScalar::Append(x, &fContent); |
| fContent.writeText(" "); |
| SkPDFScalar::Append(y, &fContent); |
| fContent.writeText(" 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(SkIntToScalar(subset.width()), |
| SkIntToScalar(subset.height())); |
| scaled.postConcat(matrix); |
| SkMatrix curTransform = setTransform(scaled); |
| updateGSFromPaint(paint, false); |
| |
| fXObjectResources.push(image); // Transfer reference. |
| fContent.writeText("/X"); |
| fContent.writeDecAsText(fXObjectResources.count() - 1); |
| fContent.writeText(" 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(); |
| |
| SkPDFUtils::AppendTransform(m, &fContent); |
| fGraphicStack[fGraphicStackIndex].fTransform = m; |
| |
| return old; |
| } |