skia2/experimental/PdfViewer/SkPdfRenderer.cpp

3160 lines
117 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkPdfRenderer.h"
#include "SkBitmapDevice.h"
#include "SkCanvas.h"
#include "SkDevice.h"
#include "SkForceLinking.h"
#include "SkGraphics.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkOSFile.h"
#include "SkPicture.h"
#include "SkPdfFont.h"
#include "SkPdfGraphicsState.h"
#include "SkPdfHeaders_autogen.h"
#include "SkPdfMapper_autogen.h"
#include "SkPdfNativeTokenizer.h"
#include "SkPdfRenderer.h"
#include "SkPdfReporter.h"
#include "SkPdfUtils.h"
#include "SkStream.h"
#include "SkTypeface.h"
#include "SkTArray.h"
#include "SkTDict.h"
// TODO(edisonn): #ifdef these ones, as they are used only for debugging.
extern "C" SkPdfContext* gPdfContext;
extern "C" SkBitmap* gDumpBitmap;
extern "C" SkCanvas* gDumpCanvas;
__SK_FORCE_IMAGE_DECODER_LINKING;
// TODO(edisonn): tool, show what objects were read during rendering - will help to identify
// features with incomplete implementation
// TODO(edisonn): security - validate all the user input, all pdf!
// TODO(edisonn): testability -add option to render without text, or only render text
// Helper macros to load variables from stack, and automatically check their type.
#define EXPECT_OPERANDS(name,pdfContext,n) \
bool __failed = pdfContext->fObjectStack.count() < n; \
SkPdfREPORTCODE(const char* __operator_name = name); \
SkPdfREPORTCODE((void)__operator_name); \
SkPdfReportIf(pdfContext->fObjectStack.count() < n, \
kIgnoreError_SkPdfIssueSeverity, \
kStackOverflow_SkPdfIssue, \
"Not enought parameters.", NULL, pdfContext); \
SkDEBUGCODE(int __cnt = n);
#define POP_OBJ(pdfContext,name) \
SkDEBUGCODE(__cnt--); \
SkASSERT(__cnt >= 0); \
SkPdfNativeObject* name = NULL; \
__failed = __failed || pdfContext->fObjectStack.count() == 0; \
if (pdfContext->fObjectStack.count() > 0) { \
name = pdfContext->fObjectStack.top(); \
pdfContext->fObjectStack.pop(); \
}
// TODO(edisonn): make all pop function to use name##_obj
#define POP_NUMBER(pdfContext,name) \
SkDEBUGCODE(__cnt--); \
SkASSERT(__cnt >= 0); \
double name = 0; \
SkPdfNativeObject* name##_obj = NULL; \
__failed = __failed || pdfContext->fObjectStack.count() == 0; \
if (pdfContext->fObjectStack.count() > 0) { \
name##_obj = pdfContext->fObjectStack.top(); \
pdfContext->fObjectStack.pop(); \
if (!name##_obj || !name##_obj->isNumber()) { \
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, \
__operator_name, \
name##_obj, \
SkPdfNativeObject::_kNumber_PdfObjectType, \
NULL);\
__failed = true;\
} else { \
name = name##_obj->numberValue(); \
} \
}
#define POP_INTEGER(pdfContext,name) \
SkDEBUGCODE(__cnt--); \
SkASSERT(__cnt >= 0); \
int64_t name = 0; \
__failed = __failed || pdfContext->fObjectStack.count() == 0; \
SkPdfNativeObject* name##_obj = NULL; \
if (pdfContext->fObjectStack.count() > 0) { \
name##_obj = pdfContext->fObjectStack.top(); \
pdfContext->fObjectStack.pop(); \
if (!name##_obj || !name##_obj->isInteger()) { \
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, \
__operator_name, \
name##_obj, \
SkPdfNativeObject::kInteger_PdfObjectType, \
NULL);\
__failed = true;\
} else { \
name = name##_obj->intValue(); \
} \
}
#define POP_NUMBER_INTO(pdfContext,var) \
SkDEBUGCODE(__cnt--); \
SkASSERT(__cnt >= 0); \
__failed = __failed || pdfContext->fObjectStack.count() == 0; \
if (pdfContext->fObjectStack.count() > 0) { \
SkPdfNativeObject* tmp = pdfContext->fObjectStack.top(); \
pdfContext->fObjectStack.pop(); \
if (!tmp || !tmp->isNumber()) { \
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, \
__operator_name, \
tmp, \
SkPdfNativeObject::kInteger_PdfObjectType | \
SkPdfNativeObject::kReal_PdfObjectType, \
NULL);\
__failed = true;\
} else { \
var = tmp->numberValue(); \
} \
}
#define POP_NAME(pdfContext,name) \
SkDEBUGCODE(__cnt--); \
SkASSERT(__cnt >= 0); \
SkPdfNativeObject* name = NULL; \
__failed = __failed || pdfContext->fObjectStack.count() == 0; \
if (pdfContext->fObjectStack.count() > 0) { \
SkPdfNativeObject* tmp = pdfContext->fObjectStack.top(); \
pdfContext->fObjectStack.pop(); \
if (!tmp || !tmp->isName()) { \
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, \
__operator_name, \
tmp, \
SkPdfNativeObject::kName_PdfObjectType, \
NULL);\
__failed = true;\
} else { \
name = tmp; \
} \
}
#define POP_STRING(pdfContext,name) \
SkDEBUGCODE(__cnt--); \
SkASSERT(__cnt >= 0); \
SkPdfNativeObject* name = NULL; \
__failed = __failed || pdfContext->fObjectStack.count() == 0; \
if (pdfContext->fObjectStack.count() > 0) { \
SkPdfNativeObject* tmp = pdfContext->fObjectStack.top(); \
pdfContext->fObjectStack.pop(); \
if (!tmp || !tmp->isAnyString()) { \
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, \
__operator_name, \
tmp, \
SkPdfNativeObject::kString_PdfObjectType | \
SkPdfNativeObject::kHexString_PdfObjectType, \
NULL);\
__failed = true;\
} else { \
name = tmp; \
} \
}
#define POP_ARRAY(pdfContext,name) \
SkDEBUGCODE(__cnt--); \
SkASSERT(__cnt >= 0); \
SkPdfArray* name = NULL; \
__failed = __failed || pdfContext->fObjectStack.count() == 0; \
if (pdfContext->fObjectStack.count() > 0) { \
SkPdfNativeObject* tmp = pdfContext->fObjectStack.top(); \
pdfContext->fObjectStack.pop(); \
if (!tmp || !tmp->isArray()) { \
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, \
__operator_name, \
tmp, \
SkPdfNativeObject::kArray_PdfObjectType, \
NULL);\
__failed = true;\
} else { \
name = (SkPdfArray*)tmp; \
} \
}
#define CHECK_PARAMETERS() \
SkASSERT(__cnt == 0); \
if (__failed) return kIgnoreError_SkPdfResult;
NotOwnedString strings_DeviceRGB;
NotOwnedString strings_DeviceCMYK;
class StringsInit {
public:
StringsInit() {
NotOwnedString::init(&strings_DeviceRGB, "DeviceRGB");
NotOwnedString::init(&strings_DeviceCMYK, "DeviceCMYK");
}
};
// TODO(edisonn): this will not work in chrome! Find another solution!
StringsInit gStringsInit;
// TODO(edisonn): Document PdfTokenLooper and subclasses.
class PdfTokenLooper {
protected:
PdfTokenLooper* fParent;
SkPdfNativeTokenizer* fTokenizer;
SkPdfContext* fPdfContext;
SkCanvas* fCanvas;
public:
PdfTokenLooper(PdfTokenLooper* parent,
SkPdfNativeTokenizer* tokenizer,
SkPdfContext* pdfContext,
SkCanvas* canvas)
: fParent(parent), fTokenizer(tokenizer), fPdfContext(pdfContext), fCanvas(canvas) {}
virtual ~PdfTokenLooper() {}
virtual SkPdfResult consumeToken(PdfToken& token) = 0;
virtual void loop() = 0;
void setUp(PdfTokenLooper* parent) {
fParent = parent;
fTokenizer = parent->fTokenizer;
fPdfContext = parent->fPdfContext;
fCanvas = parent->fCanvas;
}
SkPdfNativeTokenizer* tokenizer() { return fTokenizer; }
};
class PdfMainLooper : public PdfTokenLooper {
public:
PdfMainLooper(PdfTokenLooper* parent,
SkPdfNativeTokenizer* tokenizer,
SkPdfContext* pdfContext,
SkCanvas* canvas)
: PdfTokenLooper(parent, tokenizer, pdfContext, canvas) {}
virtual SkPdfResult consumeToken(PdfToken& token);
virtual void loop();
};
class PdfInlineImageLooper : public PdfTokenLooper {
public:
PdfInlineImageLooper()
: PdfTokenLooper(NULL, NULL, NULL, NULL) {}
virtual SkPdfResult consumeToken(PdfToken& token);
virtual void loop();
SkPdfResult done();
};
class PdfCompatibilitySectionLooper : public PdfTokenLooper {
public:
PdfCompatibilitySectionLooper()
: PdfTokenLooper(NULL, NULL, NULL, NULL) {}
virtual SkPdfResult consumeToken(PdfToken& token);
virtual void loop();
};
// Utilities
static void setup_bitmap(SkBitmap* bitmap, int width, int height, SkColor color = SK_ColorWHITE) {
bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
bitmap->allocPixels();
bitmap->eraseColor(color);
}
// TODO(edisonn): synonyms? /DeviceRGB and /RGB mean the same thing. Context dependent.
static int GetColorSpaceComponents(NotOwnedString& colorSpace) {
if (colorSpace.equals("DeviceCMYK")) {
return 4;
} else if (colorSpace.equals("DeviceGray") ||
colorSpace.equals("CalGray") ||
colorSpace.equals("Indexed")) {
return 1;
} else if (colorSpace.equals("DeviceRGB") ||
colorSpace.equals("CalRGB") ||
colorSpace.equals("Lab")) {
return 3;
} else {
return 0;
}
}
SkMatrix SkMatrixFromPdfMatrix(double array[6]) {
SkMatrix matrix;
matrix.setAll(SkDoubleToScalar(array[0]),
SkDoubleToScalar(array[2]),
SkDoubleToScalar(array[4]),
SkDoubleToScalar(array[1]),
SkDoubleToScalar(array[3]),
SkDoubleToScalar(array[5]),
SkDoubleToScalar(0),
SkDoubleToScalar(0),
SkDoubleToScalar(1));
return matrix;
}
SkMatrix SkMatrixFromPdfArray(SkPdfArray* pdfArray) {
double array[6];
// TODO(edisonn): security issue, ret if size() != 6
if (pdfArray == NULL) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kNullObject_SkPdfIssue,
"null array passed to build matrix", NULL, NULL);
return SkMatrix::I();
}
if (pdfArray->size() != 6) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kUnexpectedArraySize_SkPdfIssue,
"null array passed to build matrix", pdfArray, NULL);
return SkMatrix::I();
}
for (int i = 0; i < 6; i++) {
const SkPdfNativeObject* elem = pdfArray->operator [](i);
if (elem == NULL || !elem->isNumber()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, elem,
SkPdfNativeObject::_kNumber_PdfObjectType, NULL);
return SkMatrix::I();
}
array[i] = elem->numberValue();
}
return SkMatrixFromPdfMatrix(array);
}
// TODO(edisonn): debug code, used to analyze rendering when we find bugs.
extern "C" SkPdfNativeDoc* gDoc;
SkBitmap* gDumpBitmap = NULL;
SkCanvas* gDumpCanvas = NULL;
char gLastKeyword[100] = "";
int gLastOpKeyword = -1;
int gReadOp = 0;
#ifdef PDF_TRACE_DIFF_IN_PNG
char allOpWithVisualEffects[100] = ",S,s,f,F,f*,B,B*,b,b*,n,Tj,TJ,\',\",d0,d1,sh,EI,Do,EX,";
static bool hasVisualEffect(const char* pdfOp) {
return true;
if (*pdfOp == '\0') return false;
char markedPdfOp[100] = ",";
strcat(markedPdfOp, pdfOp);
strcat(markedPdfOp, ",");
return (strstr(allOpWithVisualEffects, markedPdfOp) != NULL);
}
#endif // PDF_TRACE_DIFF_IN_PNG
// TODO(edisonn): Pass SkPdfContext and SkCanvasd only with the define for instrumentation.
static bool readToken(SkPdfNativeTokenizer* fTokenizer, PdfToken* token) {
bool ret = fTokenizer->readToken(token);
gReadOp++;
gLastOpKeyword++;
#ifdef PDF_TRACE_DIFF_IN_PNG
// TODO(edisonn): this code is used to make a step by step history of all the draw operations
// so we could find the step where something is wrong.
if (gLastKeyword[0] && hasVisualEffect(gLastKeyword)) {
gDumpCanvas->flush();
SkBitmap bitmap;
setup_bitmap(&bitmap, gDumpBitmap->width(), gDumpBitmap->height());
memcpy(bitmap.getPixels(), gDumpBitmap->getPixels(), gDumpBitmap->getSize());
SkAutoTUnref<SkBaseDevice> device(SkNEW_ARGS(SkBitmapDevice, (bitmap)));
SkCanvas canvas(device);
// draw context stuff here
SkPaint blueBorder;
blueBorder.setColor(SK_ColorBLUE);
blueBorder.setStyle(SkPaint::kStroke_Style);
blueBorder.setTextSize(SkDoubleToScalar(20));
SkString str;
const SkClipStack* clipStack = gDumpCanvas->getClipStack();
if (clipStack) {
SkClipStack::Iter iter(*clipStack, SkClipStack::Iter::kBottom_IterStart);
const SkClipStack::Element* elem;
double y = 0;
int total = 0;
while ((elem = iter.next()) != NULL) {
total++;
y += 30;
switch (elem->getType()) {
case SkClipStack::Element::kRect_Type:
canvas.drawRect(elem->getRect(), blueBorder);
canvas.drawText("Rect Clip", strlen("Rect Clip"),
SkDoubleToScalar(10), SkDoubleToScalar(y), blueBorder);
break;
case SkClipStack::Element::kPath_Type:
canvas.drawPath(elem->getPath(), blueBorder);
canvas.drawText("Path Clip", strlen("Path Clip"),
SkDoubleToScalar(10), SkDoubleToScalar(y), blueBorder);
break;
case SkClipStack::Element::kEmpty_Type:
canvas.drawText("Empty Clip!!!", strlen("Empty Clip!!!"),
SkDoubleToScalar(10), SkDoubleToScalar(y), blueBorder);
break;
default:
canvas.drawText("Unkown Clip!!!", strlen("Unkown Clip!!!"),
SkDoubleToScalar(10), SkDoubleToScalar(y), blueBorder);
break;
}
}
y += 30;
str.printf("Number of clips in stack: %i", total);
canvas.drawText(str.c_str(), str.size(),
SkDoubleToScalar(10), SkDoubleToScalar(y), blueBorder);
}
const SkRegion& clipRegion = gDumpCanvas->getTotalClip();
SkPath clipPath;
if (clipRegion.getBoundaryPath(&clipPath)) {
SkPaint redBorder;
redBorder.setColor(SK_ColorRED);
redBorder.setStyle(SkPaint::kStroke_Style);
canvas.drawPath(clipPath, redBorder);
}
canvas.flush();
SkString out;
// TODO(edisonn): overlay on top of image inf about the clip , grafic state, the stack
out.appendf("/tmp/log_step_by_step/step-%i-%s.png",
gLastOpKeyword, gLastKeyword);
SkImageEncoder::EncodeFile(out.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100);
}
if (ret && token->fType == kKeyword_TokenType &&
token->fKeyword && token->fKeywordLength > 0 && token->fKeywordLength < 100) {
strncpy(gLastKeyword, token->fKeyword, token->fKeywordLength);
gLastKeyword[token->fKeywordLength] = '\0';
} else {
gLastKeyword[0] = '\0';
}
#endif
return ret;
}
// Signature for all the operations available in pdf.
typedef SkPdfResult (*PdfOperatorRenderer)(SkPdfContext*, SkCanvas*, PdfTokenLooper**);
// Map of string to function pointer for all known draw operations.
SkTDict<PdfOperatorRenderer> gPdfOps(100);
// Temp code to measure what operands fail.
template <typename T> class SkTDictWithDefaultConstructor : public SkTDict<T> {
public:
SkTDictWithDefaultConstructor() : SkTDict<T>(10) {}
};
SkTDictWithDefaultConstructor<int> gRenderStats[kCount_SkPdfResult];
const char* gRenderStatsNames[kCount_SkPdfResult] = {
"Success",
"Partially implemented",
"Not yet implemented",
"Ignore Error",
"Error",
"Unsupported/Unknown"
};
static SkPdfResult DrawText(SkPdfContext* pdfContext,
const SkPdfNativeObject* _str,
SkCanvas* canvas)
{
SkPdfFont* skfont = pdfContext->fGraphicsState.fSkFont;
if (skfont == NULL) {
skfont = SkPdfFont::Default();
}
if (_str == NULL || !_str->isAnyString()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity,
"DrawText",
_str,
SkPdfNativeObject::_kAnyString_PdfObjectType,
pdfContext);
return kIgnoreError_SkPdfResult;
}
const SkPdfString* str = (const SkPdfString*)_str;
SkUnencodedText binary(str);
SkDecodedText decoded;
if (skfont->encoding() == NULL) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingEncoding_SkPdfIssue,
"draw text", _str, pdfContext);
return kNYI_SkPdfResult;
}
skfont->encoding()->decodeText(binary, &decoded);
SkPaint paint;
// TODO(edisonn): does size 0 mean anything special?
if (pdfContext->fGraphicsState.fCurFontSize != 0) {
paint.setTextSize(SkDoubleToScalar(pdfContext->fGraphicsState.fCurFontSize));
}
// TODO(edisonn): implement font scaler
// if (fCurFont && fCurFont->GetFontScale() != 0) {
// paint.setTextScaleX(SkFloatToScalar(fCurFont->GetFontScale() / 100.0));
// }
pdfContext->fGraphicsState.applyGraphicsState(&paint, false);
skfont->drawText(decoded, &paint, pdfContext, canvas);
return kOK_SkPdfResult;
}
// TODO(edisonn): create header files with declarations!
SkPdfResult PdfOp_q(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper);
SkPdfResult PdfOp_Q(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper);
SkPdfResult PdfOp_Tw(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper);
SkPdfResult PdfOp_Tc(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper);
// TODO(edisonn): perf!!!
static SkColorTable* getGrayColortable() {
static SkColorTable* grayColortable = NULL;
if (grayColortable == NULL) {
SkPMColor* colors = new SkPMColor[256];
for (int i = 0 ; i < 256; i++) {
colors[i] = SkPreMultiplyARGB(255, i, i, i);
}
grayColortable = new SkColorTable(colors, 256);
}
return grayColortable;
}
static SkBitmap* transferImageStreamToBitmap(const unsigned char* uncompressedStream,
size_t uncompressedStreamLength,
int width, int height, int bytesPerLine,
int bpc, const SkString& colorSpace,
bool transparencyMask) {
SkBitmap* bitmap = new SkBitmap();
//int components = GetColorSpaceComponents(colorSpace);
//#define MAX_COMPONENTS 10
// TODO(edisonn): assume start of lines are aligned at 32 bits?
// Is there a faster way to load the uncompressed stream into a bitmap?
// minimal support for now
if ((colorSpace.equals("DeviceRGB") || colorSpace.equals("RGB")) && bpc == 8) {
SkColor* uncompressedStreamArgb = (SkColor*)malloc(width * height * sizeof(SkColor));
for (int h = 0 ; h < height; h++) {
long i = width * (h);
for (int w = 0 ; w < width; w++) {
uncompressedStreamArgb[i] = SkColorSetRGB(uncompressedStream[3 * w],
uncompressedStream[3 * w + 1],
uncompressedStream[3 * w + 2]);
i++;
}
uncompressedStream += bytesPerLine;
}
bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
bitmap->setPixels(uncompressedStreamArgb);
}
else if ((colorSpace.equals("DeviceGray") || colorSpace.equals("Gray")) && bpc == 8) {
unsigned char* uncompressedStreamA8 = (unsigned char*)malloc(width * height);
for (int h = 0 ; h < height; h++) {
long i = width * (h);
for (int w = 0 ; w < width; w++) {
uncompressedStreamA8[i] = transparencyMask ? 255 - uncompressedStream[w] :
uncompressedStream[w];
i++;
}
uncompressedStream += bytesPerLine;
}
bitmap->setConfig(transparencyMask ? SkBitmap::kA8_Config : SkBitmap::kIndex8_Config,
width, height);
bitmap->setPixels(uncompressedStreamA8, transparencyMask ? NULL : getGrayColortable());
}
// TODO(edisonn): pass color space and context here?
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "Color space NYI", NULL, NULL);
return bitmap;
}
// TODO(edisonn): preserve A1 format that skia knows, + fast convert from 111, 222, 444 to closest
// skia format.
// This functions returns the image, it does not look at the smask.
static SkBitmap* getImageFromObjectCore(SkPdfContext* pdfContext,
SkPdfImageDictionary* image, bool transparencyMask) {
if (image == NULL || !image->hasStream()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "Missing stream", image,
SkPdfNativeObject::_kStream_PdfObjectType, pdfContext);
return NULL;
}
int bpc = (int)image->BitsPerComponent(pdfContext->fPdfDoc);
int width = (int)image->Width(pdfContext->fPdfDoc);
int height = (int)image->Height(pdfContext->fPdfDoc);
SkString colorSpace("DeviceRGB");
bool indexed = false;
SkPMColor colors[256];
int cnt = 0;
if (image->isColorSpaceAName(pdfContext->fPdfDoc)) {
colorSpace = image->getColorSpaceAsName(pdfContext->fPdfDoc);
} else if (image->isColorSpaceAArray(pdfContext->fPdfDoc)) {
SkPdfArray* array = image->getColorSpaceAsArray(pdfContext->fPdfDoc);
if (array && array->size() == 4 && array->objAtAIndex(0)->isName("Indexed") &&
(array->objAtAIndex(1)->isName("DeviceRGB") ||
array->objAtAIndex(1)->isName("RGB")) &&
array->objAtAIndex(2)->isInteger() &&
array->objAtAIndex(3)->isHexString()
) {
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "Color space NYI",
image, pdfContext);
indexed = true;
cnt = (int)array->objAtAIndex(2)->intValue() + 1;
if (cnt > 256) {
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue,
"Color space feature NYI, cnt > 256", image, pdfContext);
return NULL;
}
NotOwnedString data = array->objAtAIndex(3)->strRef();
if (data.fBytes != (unsigned int)cnt * 3) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kIncostistentSizes_SkPdfIssue,
"Image color table mismatch color space specs", array, pdfContext);
return NULL;
}
for (int i = 0 ; i < cnt; i++) {
colors[i] = SkPreMultiplyARGB(0xff,
data.fBuffer[3 * i],
data.fBuffer[3 * i + 1],
data.fBuffer[3 * i + 2]);
}
}
}
// TODO(edisonn): implement image masks.
/* bool imageMask = image->imageMask();
if (imageMask) {
if (bpc != 0 && bpc != 1) {
// TODO(edisonn): report warning to be used in testing.
return SkBitmap();
}
bpc = 1;
}
*/
const unsigned char* uncompressedStream = NULL;
size_t uncompressedStreamLength = 0;
SkPdfStream* stream = (SkPdfStream*)image;
if (!stream || !stream->GetFilteredStreamRef(&uncompressedStream, &uncompressedStreamLength) ||
uncompressedStream == NULL || uncompressedStreamLength == 0) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "Missing stream", stream,
SkPdfNativeObject::_kStream_PdfObjectType, pdfContext);
return NULL;
}
SkPdfStreamCommonDictionary* streamDict = (SkPdfStreamCommonDictionary*)stream;
if (streamDict->has_Filter() &&
((streamDict->isFilterAName(NULL) &&
streamDict->getFilterAsName(NULL).equals("DCTDecode")) ||
(streamDict->isFilterAArray(NULL) &&
streamDict->getFilterAsArray(NULL)->size() > 0 &&
streamDict->getFilterAsArray(NULL)->objAtAIndex(0)->isName() &&
streamDict->getFilterAsArray(NULL)->objAtAIndex(0)->nameValue2()
.equals("DCTDecode")))) {
SkBitmap* bitmap = new SkBitmap();
SkImageDecoder::DecodeMemory(uncompressedStream, uncompressedStreamLength, bitmap);
return bitmap;
}
// TODO(edisonn): assumes RGB for now, since it is the only one implemented
if (indexed) {
SkBitmap* bitmap = new SkBitmap();
bitmap->setConfig(SkBitmap::kIndex8_Config, width, height);
SkColorTable* colorTable = new SkColorTable(colors, cnt);
bitmap->setPixels((void*)uncompressedStream, colorTable);
return bitmap;
}
int bytesPerLine = (int)(uncompressedStreamLength / height);
#ifdef PDF_TRACE
if (uncompressedStreamLength % height != 0) {
printf("Warning uncompressedStreamLength modulo height != 0 !!!\n");
}
#endif
SkBitmap* bitmap = transferImageStreamToBitmap(
(unsigned char*)uncompressedStream, uncompressedStreamLength,
(int)width, (int)height, bytesPerLine,
(int)bpc, colorSpace,
transparencyMask);
return bitmap;
}
static SkBitmap* getImageFromObject(SkPdfContext* pdfContext, SkPdfImageDictionary* image,
bool transparencyMask) {
if (!transparencyMask) {
if (!image->hasData(SkPdfNativeObject::kBitmap_Data)) {
SkBitmap* bitmap = getImageFromObjectCore(pdfContext, image, transparencyMask);
image->setData(bitmap, SkPdfNativeObject::kBitmap_Data);
}
return (SkBitmap*) image->data(SkPdfNativeObject::kBitmap_Data);
} else {
return getImageFromObjectCore(pdfContext, image, transparencyMask);
}
}
static SkBitmap* getSmaskFromObject(SkPdfContext* pdfContext, SkPdfImageDictionary* obj) {
SkPdfImageDictionary* sMask = obj->SMask(pdfContext->fPdfDoc);
if (sMask) {
return getImageFromObject(pdfContext, sMask, true);
}
// TODO(edisonn): implement GS SMask. Default to empty right now.
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue,
"implement GS SMask. Default to empty right now.", obj, pdfContext);
return pdfContext->fGraphicsState.fSMask;
}
static SkPdfResult doXObject_Image(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfImageDictionary* skpdfimage) {
if (skpdfimage == NULL) {
return kIgnoreError_SkPdfResult;
}
SkBitmap* image = getImageFromObject(pdfContext, skpdfimage, false);
SkBitmap* sMask = getSmaskFromObject(pdfContext, skpdfimage);
canvas->save();
canvas->setMatrix(pdfContext->fGraphicsState.fCTM);
SkScalar z = SkIntToScalar(0);
SkScalar one = SkIntToScalar(1);
SkPoint from[4] = {SkPoint::Make(z, z), SkPoint::Make(one, z),
SkPoint::Make(one, one), SkPoint::Make(z, one)};
SkPoint to[4] = {SkPoint::Make(z, one), SkPoint::Make(one, one),
SkPoint::Make(one, z), SkPoint::Make(z, z)};
SkMatrix flip;
SkAssertResult(flip.setPolyToPoly(from, to, 4));
SkMatrix solveImageFlip = pdfContext->fGraphicsState.fCTM;
solveImageFlip.preConcat(flip);
canvas->setMatrix(solveImageFlip);
#ifdef PDF_TRACE
SkPoint final[4] = {SkPoint::Make(z, z), SkPoint::Make(one, z),
SkPoint::Make(one, one), SkPoint::Make(z, one)};
solveImageFlip.mapPoints(final, 4);
printf("IMAGE rect = ");
for (int i = 0; i < 4; i++) {
printf("(%f %f) ", SkScalarToDouble(final[i].x()), SkScalarToDouble(final[i].y()));
}
printf("\n");
#endif // PDF_TRACE
SkRect dst = SkRect::MakeXYWH(SkDoubleToScalar(0.0), SkDoubleToScalar(0.0),
SkDoubleToScalar(1.0), SkDoubleToScalar(1.0));
// TODO(edisonn): soft mask type? alpha/luminosity.
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue,
"implement soft mask type", skpdfimage, pdfContext);
SkPaint paint;
pdfContext->fGraphicsState.applyGraphicsState(&paint, false);
if (!sMask || sMask->empty()) {
canvas->drawBitmapRect(*image, dst, &paint);
} else {
canvas->saveLayer(&dst, &paint);
canvas->drawBitmapRect(*image, dst, NULL);
SkPaint xfer;
xfer.setXfermodeMode(SkXfermode::kSrcOut_Mode);
canvas->drawBitmapRect(*sMask, dst, &xfer);
canvas->restore();
}
canvas->restore();
return kPartial_SkPdfResult;
}
//TODO(edisonn): options for implementing isolation and knockout
// 1) emulate them (current solution)
// PRO: simple
// CON: will need to use readPixels, which means serious perf issues
// 2) Compile a plan for an array of matrixes, compose the result at the end
// PRO: might be faster then 1, no need to readPixels
// CON: multiple drawings (but on smaller areas), pay a price at loading pdf to
// compute a pdf draw plan
// on average, a load with empty draw is 100ms on all the skps we have, for complete sites
// 3) support them natively in SkCanvas
// PRO: simple
// CON: we would still need to use a form of readPixels anyway, so perf might be the same as 1)
// 4) compile a plan using pathops, and render once without any fancy rules with backdrop
// PRO: simple, fast
// CON: pathops must be bug free first + time to compute new paths
// pay a price at loading pdf to compute a pdf draw plan
// on average, a load with empty draw is 100ms on all the skps we have, for complete sites
// 5) for knockout, render the objects in reverse order, and add every object to the clip, and any
// new draw will be cliped
static void doGroup_before(SkPdfContext* pdfContext, SkCanvas* canvas, SkRect bbox,
SkPdfTransparencyGroupDictionary* tgroup, bool page) {
SkRect bboxOrig = bbox;
SkBitmap backdrop;
bool isolatedGroup = tgroup->I(pdfContext->fPdfDoc);
// bool knockoutGroup = tgroup->K(pdfContext->fPdfDoc);
SkPaint paint;
pdfContext->fGraphicsState.applyGraphicsState(&paint, false);
canvas->saveLayer(&bboxOrig, isolatedGroup ? &paint : NULL);
}
// TODO(edisonn): non isolation should probably be implemented in skia
//static void doGroup_after(SkPdfContext* pdfContext, SkCanvas* canvas, SkRect bbox,
// SkPdfTransparencyGroupDictionary* tgroup) {
// if not isolated
// canvas->drawBitmapRect(backdrop, bboxOrig, NULL);
//}
static SkPdfResult doXObject_Form(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfType1FormDictionary* skobj) {
if (!skobj || !skobj->hasStream()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "Missing stream", skobj,
SkPdfNativeObject::_kStream_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
if (!skobj->has_BBox()) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingRequiredKey_SkPdfIssue, "BBox",
skobj, pdfContext);
return kIgnoreError_SkPdfResult;
}
PdfOp_q(pdfContext, canvas, NULL);
if (skobj->Resources(pdfContext->fPdfDoc)) {
pdfContext->fGraphicsState.fResources = skobj->Resources(pdfContext->fPdfDoc);
}
SkTraceMatrix(pdfContext->fGraphicsState.fCTM, "Current matrix");
if (skobj->has_Matrix()) {
pdfContext->fGraphicsState.fCTM.preConcat(skobj->Matrix(pdfContext->fPdfDoc));
SkMatrix matrix = pdfContext->fGraphicsState.fCTM;
matrix.preScale(SkDoubleToScalar(1), SkDoubleToScalar(-1));
pdfContext->fGraphicsState.fMatrixTm = matrix;
pdfContext->fGraphicsState.fMatrixTlm = matrix;
// TODO(edisonn): text matrixes mosltly NYI
}
SkTraceMatrix(pdfContext->fGraphicsState.fCTM, "Total matrix");
pdfContext->fGraphicsState.fContentStreamMatrix = pdfContext->fGraphicsState.fCTM;
canvas->setMatrix(pdfContext->fGraphicsState.fCTM);
SkRect bbox = skobj->BBox(pdfContext->fPdfDoc);
// TODO(edisonn): constants (AA) from settings.
canvas->clipRect(bbox, SkRegion::kIntersect_Op, false);
// This is a group?
if (skobj->has_Group()) {
SkPdfTransparencyGroupDictionary* tgroup = skobj->Group(pdfContext->fPdfDoc);
doGroup_before(pdfContext, canvas, bbox, tgroup, false);
}
SkPdfStream* stream = (SkPdfStream*)skobj;
SkPdfNativeTokenizer* tokenizer =
pdfContext->fPdfDoc->tokenizerOfStream(stream, pdfContext->fTmpPageAllocator);
if (tokenizer != NULL) {
PdfMainLooper looper(NULL, tokenizer, pdfContext, canvas);
looper.loop();
delete tokenizer;
}
if (skobj->has_Group()) {
canvas->restore();
}
PdfOp_Q(pdfContext, canvas, NULL);
return kPartial_SkPdfResult;
}
static SkPdfResult doXObject_Pattern(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfType1PatternDictionary* skobj) {
if (!skobj || !skobj->hasStream()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "Missing stream",
skobj, SkPdfNativeObject::_kStream_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
if (!skobj->has_BBox()) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingRequiredKey_SkPdfIssue, "BBox",
skobj, pdfContext);
return kIgnoreError_SkPdfResult;
}
PdfOp_q(pdfContext, canvas, NULL);
if (skobj->Resources(pdfContext->fPdfDoc)) {
pdfContext->fGraphicsState.fResources = skobj->Resources(pdfContext->fPdfDoc);
}
SkTraceMatrix(pdfContext->fGraphicsState.fContentStreamMatrix, "Current Content stream matrix");
if (skobj->has_Matrix()) {
pdfContext->fGraphicsState.fContentStreamMatrix.preConcat(
skobj->Matrix(pdfContext->fPdfDoc));
}
SkTraceMatrix(pdfContext->fGraphicsState.fContentStreamMatrix, "Total Content stream matrix");
canvas->setMatrix(pdfContext->fGraphicsState.fContentStreamMatrix);
pdfContext->fGraphicsState.fCTM = pdfContext->fGraphicsState.fContentStreamMatrix;
SkRect bbox = skobj->BBox(pdfContext->fPdfDoc);
// TODO(edisonn): constants (AA) from settings.
canvas->clipRect(bbox, SkRegion::kIntersect_Op, false);
SkPdfStream* stream = (SkPdfStream*)skobj;
SkPdfNativeTokenizer* tokenizer =
pdfContext->fPdfDoc->tokenizerOfStream(stream, pdfContext->fTmpPageAllocator);
if (tokenizer != NULL) {
PdfMainLooper looper(NULL, tokenizer, pdfContext, canvas);
looper.loop();
delete tokenizer;
}
PdfOp_Q(pdfContext, canvas, NULL);
return kPartial_SkPdfResult;
}
// TODO(edisonn): PS NYI
//static SkPdfResult doXObject_PS(SkPdfContext* pdfContext, SkCanvas* canvas,
// const SkPdfNativeObject* obj) {
// return kNYI_SkPdfResult;
//}
SkPdfResult doType3Char(SkPdfContext* pdfContext, SkCanvas* canvas, const SkPdfNativeObject* skobj,
SkRect bBox, SkMatrix matrix, double textSize) {
if (!skobj || !skobj->hasStream()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "Missing stream", skobj,
SkPdfNativeObject::_kStream_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
PdfOp_q(pdfContext, canvas, NULL);
pdfContext->fGraphicsState.fMatrixTm.preConcat(matrix);
pdfContext->fGraphicsState.fMatrixTm.preScale(SkDoubleToScalar(textSize),
SkDoubleToScalar(textSize));
pdfContext->fGraphicsState.fMatrixTlm = pdfContext->fGraphicsState.fMatrixTm;
pdfContext->fGraphicsState.fCTM = pdfContext->fGraphicsState.fMatrixTm;
pdfContext->fGraphicsState.fCTM.preScale(SkDoubleToScalar(1), SkDoubleToScalar(-1));
SkTraceMatrix(pdfContext->fGraphicsState.fCTM, "Total matrix");
canvas->setMatrix(pdfContext->fGraphicsState.fCTM);
SkRect rm = bBox;
pdfContext->fGraphicsState.fCTM.mapRect(&rm);
SkTraceRect(rm, "bbox mapped");
// TODO(edisonn): constants (AA) from settings.
canvas->clipRect(bBox, SkRegion::kIntersect_Op, false);
SkPdfStream* stream = (SkPdfStream*)skobj;
SkPdfNativeTokenizer* tokenizer =
pdfContext->fPdfDoc->tokenizerOfStream(stream, pdfContext->fTmpPageAllocator);
if (tokenizer != NULL) {
PdfMainLooper looper(NULL, tokenizer, pdfContext, canvas);
looper.loop();
delete tokenizer;
}
PdfOp_Q(pdfContext, canvas, NULL);
return kPartial_SkPdfResult;
}
// The PDF could be corrupted so a dict refers recursively to the same dict, if this happens
// we end up with a stack overflow and crash.
class CheckRecursiveRendering {
SkPdfNativeObject* fObj;
public:
CheckRecursiveRendering(SkPdfNativeObject* obj) : fObj(obj) {
SkASSERT(!obj->inRendering());
obj->startRendering();
}
~CheckRecursiveRendering() {
SkASSERT(fObj->inRendering());
fObj->doneRendering();
}
static bool IsInRendering(const SkPdfNativeObject* obj) {
return obj->inRendering();
}
};
static SkPdfResult doXObject(SkPdfContext* pdfContext, SkCanvas* canvas, SkPdfNativeObject* obj) {
if (CheckRecursiveRendering::IsInRendering(obj)) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kRecursiveReferencing_SkPdfIssue,
"Recursive reverencing is invalid in draw objects", obj, pdfContext);
return kIgnoreError_SkPdfResult;
}
CheckRecursiveRendering checkRecursion(obj);
switch (pdfContext->fPdfDoc->mapper()->mapXObjectDictionary(obj))
{
case kImageDictionary_SkPdfNativeObjectType:
return doXObject_Image(pdfContext, canvas, (SkPdfImageDictionary*)obj);
case kType1FormDictionary_SkPdfNativeObjectType:
return doXObject_Form(pdfContext, canvas, (SkPdfType1FormDictionary*)obj);
//case kObjectDictionaryXObjectPS_SkPdfNativeObjectType:
//return doXObject_PS(skxobj.asPS());
default: {
if (pdfContext->fPdfDoc->mapper()->mapType1PatternDictionary(obj) !=
kNone_SkPdfNativeObjectType) {
SkPdfType1PatternDictionary* pattern = (SkPdfType1PatternDictionary*)obj;
return doXObject_Pattern(pdfContext, canvas, pattern);
}
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "doXObject",
obj, pdfContext);
}
}
return kIgnoreError_SkPdfResult;
}
static SkPdfResult doPage(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfPageObjectDictionary* skobj) {
if (!skobj || !skobj->isContentsAStream(pdfContext->fPdfDoc)) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "Missing stream", skobj,
SkPdfNativeObject::_kStream_PdfObjectType, pdfContext);
return kNYI_SkPdfResult;
}
SkPdfStream* stream = skobj->getContentsAsStream(pdfContext->fPdfDoc);
if (!stream) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "Missing stream",
skobj, SkPdfNativeObject::_kStream_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
pdfContext->fGraphicsState.fResources = skobj->Resources(pdfContext->fPdfDoc);
if (!pdfContext->fGraphicsState.fResources) {
// It might be null because we have not implemented yet inheritance.
return kIgnoreError_SkPdfResult;
}
if (CheckRecursiveRendering::IsInRendering(skobj)) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kRecursiveReferencing_SkPdfIssue,
"Recursive reverencing is invalid in draw objects", skobj, pdfContext);
return kIgnoreError_SkPdfResult;
}
CheckRecursiveRendering checkRecursion(skobj);
PdfOp_q(pdfContext, canvas, NULL);
// TODO(edisonn): MediaBox can be inherited!!!!
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "MediaBox inheritance NYI",
NULL, pdfContext);
SkRect bbox = skobj->MediaBox(pdfContext->fPdfDoc);
if (skobj->has_Group()) {
SkPdfTransparencyGroupDictionary* tgroup = skobj->Group(pdfContext->fPdfDoc);
doGroup_before(pdfContext, canvas, bbox, tgroup, true);
} else {
canvas->save();
}
SkPdfNativeTokenizer* tokenizer =
pdfContext->fPdfDoc->tokenizerOfStream(stream, pdfContext->fTmpPageAllocator);
if (tokenizer != NULL) {
PdfMainLooper looper(NULL, tokenizer, pdfContext, canvas);
looper.loop();
delete tokenizer;
}
canvas->restore();
PdfOp_Q(pdfContext, canvas, NULL);
return kPartial_SkPdfResult;
}
SkPdfResult PdfOp_q(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
pdfContext->fStateStack.push(pdfContext->fGraphicsState);
canvas->save();
pdfContext->fObjectStack.nest();
return kOK_SkPdfResult;
}
SkPdfResult PdfOp_Q(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (pdfContext->fStateStack.count() > 0) {
pdfContext->fGraphicsState = pdfContext->fStateStack.top();
pdfContext->fStateStack.pop();
canvas->restore();
if (pdfContext->fObjectStack.nests() == 0) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kStackNestingOverflow_SkPdfIssue,
"stack nesting overflow (q/Q)", NULL, pdfContext);
return kIgnoreError_SkPdfResult;
} else {
pdfContext->fObjectStack.unnest();
}
} else {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kStackOverflow_SkPdfIssue,
"stack overflow (q/Q)", NULL, pdfContext);
return kIgnoreError_SkPdfResult;
}
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_cm(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("cm", pdfContext, 6);
POP_NUMBER(pdfContext, f);
POP_NUMBER(pdfContext, e);
POP_NUMBER(pdfContext, d);
POP_NUMBER(pdfContext, c);
POP_NUMBER(pdfContext, b);
POP_NUMBER(pdfContext, a);
CHECK_PARAMETERS();
double array[6] = {a, b, c, d, e, f};
// a b
// c d
// e f
// 0 1
// 2 3
// 4 5
// sx ky
// kx sy
// tx ty
SkMatrix matrix = SkMatrixFromPdfMatrix(array);
pdfContext->fGraphicsState.fCTM.preConcat(matrix);
#ifdef PDF_TRACE
printf("cm ");
for (int i = 0 ; i < 6 ; i++) {
printf("%f ", array[i]);
}
printf("\n");
SkTraceMatrix(pdfContext->fGraphicsState.fCTM, "cm");
#endif
return kOK_SkPdfResult;
}
//leading TL Set the text leading, Tl
//, to leading, which is a number expressed in unscaled text
//space units. Text leading is used only by the T*, ', and " operators. Initial value: 0.
static SkPdfResult PdfOp_TL(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("TL", pdfContext, 1);
POP_NUMBER(pdfContext, ty);
CHECK_PARAMETERS();
pdfContext->fGraphicsState.fTextLeading = ty;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_Td(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Td", pdfContext, 2);
POP_NUMBER(pdfContext, ty);
POP_NUMBER(pdfContext, tx);
CHECK_PARAMETERS();
double array[6] = {1, 0, 0, 1, tx, -ty};
SkMatrix matrix = SkMatrixFromPdfMatrix(array);
pdfContext->fGraphicsState.fMatrixTm.preConcat(matrix);
pdfContext->fGraphicsState.fMatrixTlm.preConcat(matrix);
return kPartial_SkPdfResult;
}
static SkPdfResult PdfOp_TD(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("TD", pdfContext, 2)
POP_NUMBER(pdfContext, ty);
POP_NUMBER(pdfContext, tx);
CHECK_PARAMETERS();
// TODO(edisonn): Create factory methods or constructors so native is hidden
SkPdfReal* _ty = pdfContext->fPdfDoc->createReal(-ty);
pdfContext->fObjectStack.push(_ty);
PdfOp_TL(pdfContext, canvas, looper);
SkPdfReal* vtx = pdfContext->fPdfDoc->createReal(tx);
pdfContext->fObjectStack.push(vtx);
SkPdfReal* vty = pdfContext->fPdfDoc->createReal(ty);
pdfContext->fObjectStack.push(vty);
SkPdfResult ret = PdfOp_Td(pdfContext, canvas, looper);
return ret;
}
static SkPdfResult PdfOp_Tm(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tm", pdfContext, 6);
POP_NUMBER(pdfContext, f);
POP_NUMBER(pdfContext, e);
POP_NUMBER(pdfContext, d);
POP_NUMBER(pdfContext, c);
POP_NUMBER(pdfContext, b);
POP_NUMBER(pdfContext, a);
CHECK_PARAMETERS();
double array[6];
array[0] = a;
array[1] = b;
array[2] = c;
array[3] = d;
array[4] = e;
array[5] = f;
SkMatrix matrix = SkMatrixFromPdfMatrix(array);
matrix.postConcat(pdfContext->fGraphicsState.fCTM);
matrix.preScale(SkDoubleToScalar(1), SkDoubleToScalar(-1));
// TODO(edisonn): NYI - Text positioning.
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue,
"Text positioning not implemented for 2+ chars", NULL, pdfContext);
pdfContext->fGraphicsState.fMatrixTm = matrix;
pdfContext->fGraphicsState.fMatrixTlm = matrix;;
return kPartial_SkPdfResult;
}
//— T* Move to the start of the next line. This operator has the same effect as the code
//0 Tl Td
//where Tl is the current leading parameter in the text state
static SkPdfResult PdfOp_T_star(SkPdfContext* pdfContext, SkCanvas* canvas,
PdfTokenLooper** looper) {
SkPdfReal* zero = pdfContext->fPdfDoc->createReal(0.0);
SkPdfReal* tl = pdfContext->fPdfDoc->createReal(pdfContext->fGraphicsState.fTextLeading);
pdfContext->fObjectStack.push(zero);
pdfContext->fObjectStack.push(tl);
SkPdfResult ret = PdfOp_Td(pdfContext, canvas, looper);
return ret;
}
static SkPdfResult PdfOp_m(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (pdfContext->fGraphicsState.fPathClosed) {
pdfContext->fGraphicsState.fPath.reset();
pdfContext->fGraphicsState.fPathClosed = false;
}
EXPECT_OPERANDS("m", pdfContext, 2);
POP_NUMBER(pdfContext, y);
POP_NUMBER(pdfContext, x);
CHECK_PARAMETERS();
pdfContext->fGraphicsState.fCurPosY = y;
pdfContext->fGraphicsState.fCurPosX = x;
pdfContext->fGraphicsState.fPath.moveTo(SkDoubleToScalar(pdfContext->fGraphicsState.fCurPosX),
SkDoubleToScalar(pdfContext->fGraphicsState.fCurPosY));
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_l(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (pdfContext->fGraphicsState.fPathClosed) {
pdfContext->fGraphicsState.fPath.reset();
pdfContext->fGraphicsState.fPathClosed = false;
}
EXPECT_OPERANDS("l", pdfContext, 2);
POP_NUMBER(pdfContext, y);
POP_NUMBER(pdfContext, x);
CHECK_PARAMETERS();
pdfContext->fGraphicsState.fCurPosY = y;
pdfContext->fGraphicsState.fCurPosX = x;
pdfContext->fGraphicsState.fPath.lineTo(SkDoubleToScalar(pdfContext->fGraphicsState.fCurPosX),
SkDoubleToScalar(pdfContext->fGraphicsState.fCurPosY));
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_c(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (pdfContext->fGraphicsState.fPathClosed) {
pdfContext->fGraphicsState.fPath.reset();
pdfContext->fGraphicsState.fPathClosed = false;
}
EXPECT_OPERANDS("c", pdfContext, 6);
POP_NUMBER(pdfContext, y3);
POP_NUMBER(pdfContext, x3);
POP_NUMBER(pdfContext, y2);
POP_NUMBER(pdfContext, x2);
POP_NUMBER(pdfContext, y1);
POP_NUMBER(pdfContext, x1);
CHECK_PARAMETERS();
pdfContext->fGraphicsState.fPath.cubicTo(SkDoubleToScalar(x1), SkDoubleToScalar(y1),
SkDoubleToScalar(x2), SkDoubleToScalar(y2),
SkDoubleToScalar(x3), SkDoubleToScalar(y3));
pdfContext->fGraphicsState.fCurPosX = x3;
pdfContext->fGraphicsState.fCurPosY = y3;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_v(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (pdfContext->fGraphicsState.fPathClosed) {
pdfContext->fGraphicsState.fPath.reset();
pdfContext->fGraphicsState.fPathClosed = false;
}
EXPECT_OPERANDS("v", pdfContext, 4);
POP_NUMBER(pdfContext, y3);
POP_NUMBER(pdfContext, x3);
POP_NUMBER(pdfContext, y2);
POP_NUMBER(pdfContext, x2);
CHECK_PARAMETERS();
double y1 = pdfContext->fGraphicsState.fCurPosY;
double x1 = pdfContext->fGraphicsState.fCurPosX;
pdfContext->fGraphicsState.fPath.cubicTo(SkDoubleToScalar(x1), SkDoubleToScalar(y1),
SkDoubleToScalar(x2), SkDoubleToScalar(y2),
SkDoubleToScalar(x3), SkDoubleToScalar(y3));
pdfContext->fGraphicsState.fCurPosX = x3;
pdfContext->fGraphicsState.fCurPosY = y3;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_y(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (pdfContext->fGraphicsState.fPathClosed) {
pdfContext->fGraphicsState.fPath.reset();
pdfContext->fGraphicsState.fPathClosed = false;
}
EXPECT_OPERANDS("y", pdfContext, 4);
POP_NUMBER(pdfContext, y3);
POP_NUMBER(pdfContext, x3);
POP_NUMBER(pdfContext, y1);
POP_NUMBER(pdfContext, x1);
CHECK_PARAMETERS();
double y2 = pdfContext->fGraphicsState.fCurPosY;
double x2 = pdfContext->fGraphicsState.fCurPosX;
pdfContext->fGraphicsState.fPath.cubicTo(SkDoubleToScalar(x1), SkDoubleToScalar(y1),
SkDoubleToScalar(x2), SkDoubleToScalar(y2),
SkDoubleToScalar(x3), SkDoubleToScalar(y3));
pdfContext->fGraphicsState.fCurPosX = x3;
pdfContext->fGraphicsState.fCurPosY = y3;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_re(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (pdfContext->fGraphicsState.fPathClosed) {
pdfContext->fGraphicsState.fPath.reset();
pdfContext->fGraphicsState.fPathClosed = false;
}
EXPECT_OPERANDS("re", pdfContext, 4);
POP_NUMBER(pdfContext, height);
POP_NUMBER(pdfContext, width);
POP_NUMBER(pdfContext, y);
POP_NUMBER(pdfContext, x);
CHECK_PARAMETERS();
pdfContext->fGraphicsState.fPath.addRect(SkDoubleToScalar(x),
SkDoubleToScalar(y),
SkDoubleToScalar(x + width),
SkDoubleToScalar(y + height));
pdfContext->fGraphicsState.fCurPosX = x;
pdfContext->fGraphicsState.fCurPosY = y + height;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_h(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
pdfContext->fGraphicsState.fPath.close();
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_fillAndStroke(SkPdfContext* pdfContext, SkCanvas* canvas,
bool fill, bool stroke, bool close, bool evenOdd) {
SkPath path = pdfContext->fGraphicsState.fPath;
if (close) {
path.close();
}
canvas->setMatrix(pdfContext->fGraphicsState.fCTM);
SkPaint paint;
SkPoint line[2];
if (fill && !stroke && path.isLine(line)) {
paint.setStyle(SkPaint::kStroke_Style);
// TODO(edisonn): implement this with patterns
pdfContext->fGraphicsState.applyGraphicsState(&paint, false);
paint.setStrokeWidth(SkDoubleToScalar(0));
canvas->drawPath(path, paint);
} else {
if (fill) {
if (strncmp((char*)pdfContext->fGraphicsState.fNonStroking.fColorSpace.fBuffer,
"Pattern", strlen("Pattern")) == 0 &&
pdfContext->fGraphicsState.fNonStroking.fPattern != NULL) {
// TODO(edisonn): we can use a shader here, like imageshader to draw fast.
PdfOp_q(pdfContext, canvas, NULL);
if (evenOdd) {
path.setFillType(SkPath::kEvenOdd_FillType);
}
canvas->clipPath(path);
if (pdfContext->fPdfDoc
->mapper()
->mapType1PatternDictionary(pdfContext->fGraphicsState
.fNonStroking
.fPattern)
!= kNone_SkPdfNativeObjectType) {
SkPdfType1PatternDictionary* pattern
= (SkPdfType1PatternDictionary*)pdfContext->fGraphicsState
.fNonStroking
.fPattern;
// TODO(edisonn): make PaintType constants
if (pattern->PaintType(pdfContext->fPdfDoc) == 1) {
// TODO(edisonn): don't use abs, iterate as asked, if the cells intersect
// it will change the result iterating in reverse
// remove then the following bounds.sort();
int xStep = abs((int)pattern->XStep(pdfContext->fPdfDoc));
int yStep = abs((int)pattern->YStep(pdfContext->fPdfDoc));
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue,
"paterns x/y step is forced to positive number",
pattern, pdfContext);
SkRect bounds = path.getBounds();
bounds.sort();
SkScalar x;
SkScalar y;
y = bounds.top();
int totalx = 0;
int totaly = 0;
while (y < bounds.bottom()) {
x = bounds.left();
totalx = 0;
while (x < bounds.right()) {
doXObject(pdfContext, canvas, pattern);
pdfContext->fGraphicsState.fContentStreamMatrix.preTranslate(
SkIntToScalar(xStep), SkIntToScalar(0));
totalx += xStep;
x += SkIntToScalar(xStep);
}
pdfContext->fGraphicsState.fContentStreamMatrix.preTranslate(
SkIntToScalar(-totalx), SkIntToScalar(0));
pdfContext->fGraphicsState.fContentStreamMatrix.preTranslate(
SkIntToScalar(0), SkIntToScalar(-yStep));
totaly += yStep;
y += SkIntToScalar(yStep);
}
pdfContext->fGraphicsState.fContentStreamMatrix.preTranslate(
SkIntToScalar(0), SkIntToScalar(totaly));
}
}
PdfOp_Q(pdfContext, canvas, NULL);
} else {
paint.setStyle(SkPaint::kFill_Style);
if (evenOdd) {
path.setFillType(SkPath::kEvenOdd_FillType);
}
pdfContext->fGraphicsState.applyGraphicsState(&paint, false);
canvas->drawPath(path, paint);
}
}
if (stroke) {
if (false && strncmp((char*)pdfContext->fGraphicsState.fNonStroking.fColorSpace.fBuffer,
"Pattern", strlen("Pattern")) == 0) {
// TODO(edisonn): implement Pattern for strokes
paint.setStyle(SkPaint::kStroke_Style);
paint.setColor(SK_ColorGREEN);
// reset it, just in case it messes up the stroke
path.setFillType(SkPath::kWinding_FillType);
canvas->drawPath(path, paint);
} else {
paint.setStyle(SkPaint::kStroke_Style);
pdfContext->fGraphicsState.applyGraphicsState(&paint, true);
// reset it, just in case it messes up the stroke
path.setFillType(SkPath::kWinding_FillType);
canvas->drawPath(path, paint);
}
}
}
pdfContext->fGraphicsState.fPath.reset();
// TODO(edisonn): implement scale/zoom
if (pdfContext->fGraphicsState.fHasClipPathToApply) {
#ifndef PDF_DEBUG_NO_CLIPING
canvas->clipPath(pdfContext->fGraphicsState.fClipPath, SkRegion::kIntersect_Op, true);
#endif
}
//pdfContext->fGraphicsState.fClipPath.reset();
pdfContext->fGraphicsState.fHasClipPathToApply = false;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_S(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, false, true, false, false);
}
static SkPdfResult PdfOp_s(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, false, true, true, false);
}
static SkPdfResult PdfOp_F(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, true, false, false, false);
}
static SkPdfResult PdfOp_f(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, true, false, false, false);
}
static SkPdfResult PdfOp_f_star(SkPdfContext* pdfContext, SkCanvas* canvas,
PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, true, false, false, true);
}
static SkPdfResult PdfOp_B(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, true, true, false, false);
}
static SkPdfResult PdfOp_B_star(SkPdfContext* pdfContext, SkCanvas* canvas,
PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, true, true, false, true);
}
static SkPdfResult PdfOp_b(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, true, true, true, false);
}
static SkPdfResult PdfOp_b_star(SkPdfContext* pdfContext, SkCanvas* canvas,
PdfTokenLooper** looper) {
return PdfOp_fillAndStroke(pdfContext, canvas, true, true, true, true);
}
static SkPdfResult PdfOp_n(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
canvas->setMatrix(pdfContext->fGraphicsState.fCTM);
if (pdfContext->fGraphicsState.fHasClipPathToApply) {
#ifndef PDF_DEBUG_NO_CLIPING
canvas->clipPath(pdfContext->fGraphicsState.fClipPath, SkRegion::kIntersect_Op, true);
#endif
}
pdfContext->fGraphicsState.fHasClipPathToApply = false;
pdfContext->fGraphicsState.fPathClosed = true;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_BT(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
pdfContext->fGraphicsState.fTextBlock = true;
SkMatrix matrix = pdfContext->fGraphicsState.fCTM;
matrix.preScale(SkDoubleToScalar(1), SkDoubleToScalar(-1));
pdfContext->fGraphicsState.fMatrixTm = matrix;
pdfContext->fGraphicsState.fMatrixTlm = matrix;
return kPartial_SkPdfResult;
}
static SkPdfResult PdfOp_ET(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
if (!pdfContext->fGraphicsState.fTextBlock) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingBT_SkPdfIssue, "ET without BT", NULL,
pdfContext);
return kIgnoreError_SkPdfResult;
}
// TODO(edisonn): anything else to be done once we are done with draw text? Like restore stack?
return kOK_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApplyFontCore(SkPdfContext* pdfContext,
const SkPdfNativeObject* fontName, double fontSize) {
#ifdef PDF_TRACE
printf("font name: %s\n", fontName->nameValue2().c_str());
#endif
if (!pdfContext->fGraphicsState.fResources->Font(pdfContext->fPdfDoc)) {
// TODO(edisonn): try to recover and draw it any way?
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingFont_SkPdfIssue,
"No font", fontName, pdfContext);
return kIgnoreError_SkPdfResult;
}
SkPdfNativeObject* objFont
= pdfContext->fGraphicsState.fResources->Font(pdfContext->fPdfDoc)->get(fontName);
objFont = pdfContext->fPdfDoc->resolveReference(objFont);
if (kNone_SkPdfNativeObjectType == pdfContext->fPdfDoc->mapper()->mapFontDictionary(objFont)) {
// TODO(edisonn): try to recover and draw it any way?
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kInvalidFont_SkPdfIssue,
"Invalid font", objFont, pdfContext);
return kIgnoreError_SkPdfResult;
}
SkPdfFontDictionary* fd = (SkPdfFontDictionary*)objFont;
SkPdfFont* skfont = SkPdfFont::fontFromPdfDictionary(pdfContext->fPdfDoc, fd);
if (skfont) {
pdfContext->fGraphicsState.fSkFont = skfont;
}
pdfContext->fGraphicsState.fCurFontSize = fontSize;
return kOK_SkPdfResult;
}
//font size Tf Set the text font, Tf
//, to font and the text font size, Tfs, to size. font is the name of a
//font resource in the Fontsubdictionary of the current resource dictionary; size is
//a number representing a scale factor. There is no initial value for either font or
//size; they must be specified explicitly using Tf before any text is shown.
static SkPdfResult PdfOp_Tf(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tf", pdfContext, 2);
POP_NUMBER(pdfContext, fontSize);
POP_NAME(pdfContext, fontName);
CHECK_PARAMETERS();
return skpdfGraphicsStateApplyFontCore(pdfContext, fontName, fontSize);
}
static SkPdfResult PdfOp_Tj(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tj", pdfContext, 1);
POP_STRING(pdfContext, str);
CHECK_PARAMETERS();
if (!pdfContext->fGraphicsState.fTextBlock) {
// TODO(edisonn): try to recover and draw it any way?
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingBT_SkPdfIssue, "Tj without BT", NULL,
pdfContext);
return kIgnoreError_SkPdfResult;
}
SkPdfResult ret = DrawText(pdfContext, str, canvas);
return ret;
}
static SkPdfResult PdfOp_quote(SkPdfContext* pdfContext, SkCanvas* canvas,
PdfTokenLooper** looper) {
if (!pdfContext->fGraphicsState.fTextBlock) {
// TODO(edisonn): try to recover and draw it any way?
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingBT_SkPdfIssue,
"' without BT", NULL, pdfContext);
return kIgnoreError_SkPdfResult;
}
PdfOp_T_star(pdfContext, canvas, looper);
// Do not pop, and push, just transfer the param to Tj
return PdfOp_Tj(pdfContext, canvas, looper);
}
static SkPdfResult PdfOp_doublequote(SkPdfContext* pdfContext, SkCanvas* canvas,
PdfTokenLooper** looper) {
if (!pdfContext->fGraphicsState.fTextBlock) {
// TODO(edisonn): try to recover and draw it any way?
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingBT_SkPdfIssue,
"\" without BT", NULL, pdfContext);
return kIgnoreError_SkPdfResult;
}
EXPECT_OPERANDS("\"", pdfContext, 3);
POP_OBJ(pdfContext, str);
POP_OBJ(pdfContext, ac);
POP_OBJ(pdfContext, aw);
CHECK_PARAMETERS();
pdfContext->fObjectStack.push(aw);
PdfOp_Tw(pdfContext, canvas, looper);
pdfContext->fObjectStack.push(ac);
PdfOp_Tc(pdfContext, canvas, looper);
pdfContext->fObjectStack.push(str);
PdfOp_quote(pdfContext, canvas, looper);
return kPartial_SkPdfResult;
}
static SkPdfResult PdfOp_TJ(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tf", pdfContext, 1);
POP_ARRAY(pdfContext, array);
CHECK_PARAMETERS();
if (!pdfContext->fGraphicsState.fTextBlock) {
// TODO(edisonn): try to recover and draw it any way?
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingBT_SkPdfIssue, "TJ without BT", NULL,
pdfContext);
return kIgnoreError_SkPdfResult;
}
if (!array->isArray()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, array,
SkPdfNativeObject::kArray_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
for( int i=0; i<static_cast<int>(array->size()); i++ )
{
if (!(*array)[i]) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity,
"element [i] is null, no element should be null",
array,
SkPdfNativeObject::_kAnyString_PdfObjectType ||
SkPdfNativeObject::_kNumber_PdfObjectType,
pdfContext);
} else if( (*array)[i]->isAnyString()) {
SkPdfNativeObject* obj = (*array)[i];
DrawText(pdfContext, obj, canvas);
} else if ((*array)[i]->isNumber()) {
double dx = (*array)[i]->numberValue();
SkMatrix matrix;
matrix.setAll(SkDoubleToScalar(1),
SkDoubleToScalar(0),
// TODO(edisonn): use writing mode, vertical/horizontal.
SkDoubleToScalar(-dx), // amount is substracted!!!
SkDoubleToScalar(0),
SkDoubleToScalar(1),
SkDoubleToScalar(0),
SkDoubleToScalar(0),
SkDoubleToScalar(0),
SkDoubleToScalar(1));
pdfContext->fGraphicsState.fMatrixTm.preConcat(matrix);
} else {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "wrong type", (*array)[i],
SkPdfNativeObject::kArray_PdfObjectType ||
SkPdfNativeObject::_kNumber_PdfObjectType,
pdfContext);
}
}
return kPartial_SkPdfResult; // TODO(edisonn): Implement fully DrawText before returing OK.
}
static SkPdfResult PdfOp_CS_cs(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfColorOperator* colorOperator) {
EXPECT_OPERANDS("CS/cs", pdfContext, 1);
POP_NAME(pdfContext, name);
CHECK_PARAMETERS();
//Next, get the ColorSpace Dictionary from the Resource Dictionary:
SkPdfDictionary* colorSpaceResource
= pdfContext->fGraphicsState.fResources->ColorSpace(pdfContext->fPdfDoc);
SkPdfNativeObject* colorSpace
= colorSpaceResource ? pdfContext->fPdfDoc
->resolveReference(colorSpaceResource->get(name)) :
name;
if (colorSpace == NULL) {
colorOperator->fColorSpace = name->strRef();
} else {
#ifdef PDF_TRACE
printf("CS = %s\n", colorSpace->toString(0, 0).c_str());
#endif // PDF_TRACE
if (colorSpace->isName()) {
colorOperator->fColorSpace = colorSpace->strRef();
} else if (colorSpace->isArray()) {
int cnt = colorSpace->size();
if (cnt == 0) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kIncostistentSizes_SkPdfIssue,
"color space has length 0", colorSpace, pdfContext);
return kIgnoreError_SkPdfResult;
}
SkPdfNativeObject* type = colorSpace->objAtAIndex(0);
type = pdfContext->fPdfDoc->resolveReference(type);
if (type->isName("ICCBased")) {
if (cnt != 2) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kIncostistentSizes_SkPdfIssue,
"ICCBased color space must have an array with 2 elements",
colorSpace, pdfContext);
return kIgnoreError_SkPdfResult;
}
SkPdfNativeObject* prop = colorSpace->objAtAIndex(1);
prop = pdfContext->fPdfDoc->resolveReference(prop);
#ifdef PDF_TRACE
printf("ICCBased prop = %s\n", prop->toString(0, 0).c_str());
#endif // PDF_TRACE
// TODO(edisonn): hack
if (prop && prop->isDictionary() && prop->get("N") &&
prop->get("N")->isInteger() && prop->get("N")->intValue() == 3) {
colorOperator->setColorSpace(&strings_DeviceRGB);
return kPartial_SkPdfResult;
}
return kNYI_SkPdfResult;
}
}
}
return kPartial_SkPdfResult;
}
static SkPdfResult PdfOp_CS(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_CS_cs(pdfContext, canvas, &pdfContext->fGraphicsState.fStroking);
}
static SkPdfResult PdfOp_cs(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_CS_cs(pdfContext, canvas, &pdfContext->fGraphicsState.fNonStroking);
}
static SkPdfResult PdfOp_SC_sc(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfColorOperator* colorOperator) {
double c[4];
// int64_t v[4];
int n = GetColorSpaceComponents(colorOperator->fColorSpace);
bool doubles = true;
if (colorOperator->fColorSpace.equals("Indexed")) {
doubles = false;
}
#ifdef PDF_TRACE
printf("color space = %s, N = %i\n", colorOperator->fColorSpace.fBuffer, n);
#endif
EXPECT_OPERANDS("SC/sc", pdfContext, n);
for (int i = n - 1; i >= 0 ; i--) {
if (doubles) {
POP_NUMBER_INTO(pdfContext, c[i]);
// } else {
// v[i] = pdfContext->fObjectStack.top()->intValue(); pdfContext->fObjectStack.pop();
}
}
CHECK_PARAMETERS();
// TODO(edisonn): Now, set that color. Only DeviceRGB supported.
// TODO(edisonn): do possible field values to enum at parsing time!
// TODO(edisonn): support also abbreviations /DeviceRGB == /RGB
if (colorOperator->fColorSpace.equals("DeviceRGB") ||
colorOperator->fColorSpace.equals("RGB")) {
colorOperator->setRGBColor(SkColorSetRGB((U8CPU)(255*c[0]),
(U8CPU)(255*c[1]),
(U8CPU)(255*c[2])));
}
return kPartial_SkPdfResult;
}
static SkPdfResult PdfOp_SC(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_SC_sc(pdfContext, canvas, &pdfContext->fGraphicsState.fStroking);
}
static SkPdfResult PdfOp_sc(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_SC_sc(pdfContext, canvas, &pdfContext->fGraphicsState.fNonStroking);
}
static SkPdfResult PdfOp_SCN_scn(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfColorOperator* colorOperator) {
if (pdfContext->fObjectStack.count() > 0 && pdfContext->fObjectStack.top()->isName()) {
SkPdfNativeObject* name = pdfContext->fObjectStack.top(); pdfContext->fObjectStack.pop();
SkPdfDictionary* patternResources
= pdfContext->fGraphicsState.fResources->Pattern(pdfContext->fPdfDoc);
if (patternResources == NULL) {
#ifdef PDF_TRACE
printf("ExtGState is NULL!\n");
#endif
return kIgnoreError_SkPdfResult;
}
colorOperator->setPatternColorSpace(
pdfContext->fPdfDoc->resolveReference(patternResources->get(name)));
}
// TODO(edisonn): SCN supports more color spaces than SCN. Read and implement spec.
PdfOp_SC_sc(pdfContext, canvas, colorOperator);
return kPartial_SkPdfResult;
}
static SkPdfResult PdfOp_SCN(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_SCN_scn(pdfContext, canvas, &pdfContext->fGraphicsState.fStroking);
}
static SkPdfResult PdfOp_scn(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_SCN_scn(pdfContext, canvas, &pdfContext->fGraphicsState.fNonStroking);
}
static SkPdfResult PdfOp_G_g(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfColorOperator* colorOperator) {
EXPECT_OPERANDS("G/g", pdfContext, 1);
POP_NUMBER(pdfContext, gray);
CHECK_PARAMETERS();
// TODO(edisonn): limit gray in [0, 1]
// TODO(edisonn): HACK - it should be device gray, but not suported right now
colorOperator->fColorSpace = strings_DeviceRGB;
colorOperator->setRGBColor(SkColorSetRGB((U8CPU)(255 * gray),
(U8CPU)(255 * gray),
(U8CPU)(255 * gray)));
return kPartial_SkPdfResult;
}
static SkPdfResult PdfOp_G(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_G_g(pdfContext, canvas, &pdfContext->fGraphicsState.fStroking);
}
static SkPdfResult PdfOp_g(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_G_g(pdfContext, canvas, &pdfContext->fGraphicsState.fNonStroking);
}
static SkPdfResult PdfOp_RG_rg(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfColorOperator* colorOperator) {
EXPECT_OPERANDS("RG/rg", pdfContext, 3);
POP_NUMBER(pdfContext, b);
POP_NUMBER(pdfContext, g);
POP_NUMBER(pdfContext, r);
CHECK_PARAMETERS();
colorOperator->fColorSpace = strings_DeviceRGB;
colorOperator->setRGBColor(SkColorSetRGB((U8CPU)(255*r), (U8CPU)(255*g), (U8CPU)(255*b)));
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_RG(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_RG_rg(pdfContext, canvas, &pdfContext->fGraphicsState.fStroking);
}
static SkPdfResult PdfOp_rg(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_RG_rg(pdfContext, canvas, &pdfContext->fGraphicsState.fNonStroking);
}
static SkPdfResult PdfOp_K_k(SkPdfContext* pdfContext, SkCanvas* canvas,
SkPdfColorOperator* colorOperator) {
// TODO(edisonn): spec has some rules about overprint, implement them.
EXPECT_OPERANDS("K/k", pdfContext, 4);
POP_NUMBER(pdfContext, k);
POP_NUMBER(pdfContext, y);
POP_NUMBER(pdfContext, m);
POP_NUMBER(pdfContext, c);
CHECK_PARAMETERS();
// TODO(edisonn): really silly quick way to remove compiler warning
if (k + y + m + c == 0) {
return kNYI_SkPdfResult;
}
//colorOperator->fColorSpace = strings_DeviceCMYK;
// TODO(edisonn): Set color.
return kNYI_SkPdfResult;
}
static SkPdfResult PdfOp_K(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_K_k(pdfContext, canvas, &pdfContext->fGraphicsState.fStroking);
}
static SkPdfResult PdfOp_k(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
return PdfOp_K_k(pdfContext, canvas, &pdfContext->fGraphicsState.fNonStroking);
}
static SkPdfResult PdfOp_W(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
pdfContext->fGraphicsState.fClipPath = pdfContext->fGraphicsState.fPath;
pdfContext->fGraphicsState.fHasClipPathToApply = true;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_W_star(SkPdfContext* pdfContext, SkCanvas* canvas,
PdfTokenLooper** looper) {
pdfContext->fGraphicsState.fClipPath = pdfContext->fGraphicsState.fPath;
pdfContext->fGraphicsState.fClipPath.setFillType(SkPath::kEvenOdd_FillType);
pdfContext->fGraphicsState.fHasClipPathToApply = true;
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_BX(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
*looper = new PdfCompatibilitySectionLooper();
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_EX(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kNullObject_SkPdfIssue,
"EX operator should not be called, it is handled in a looper, "
"unless the file is corrupted, we should assert",
NULL, pdfContext);
return kIgnoreError_SkPdfResult;
}
static SkPdfResult PdfOp_BI(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
*looper = new PdfInlineImageLooper();
return kOK_SkPdfResult;
}
static SkPdfResult PdfOp_ID(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kNullObject_SkPdfIssue,
"ID operator should not be called, it is habdled in a looper, "
"unless the file is corrupted, we should assert",
NULL, pdfContext);
return kIgnoreError_SkPdfResult;
}
static SkPdfResult PdfOp_EI(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kNullObject_SkPdfIssue,
"EI operator should not be called, it is habdled in a looper, "
"unless the file is corrupted, we should assert",
NULL, pdfContext);
return kIgnoreError_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApply_ca(SkPdfContext* pdfContext, double ca) {
pdfContext->fGraphicsState.fNonStroking.fOpacity = ca;
return kOK_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApply_CA(SkPdfContext* pdfContext, double CA) {
pdfContext->fGraphicsState.fStroking.fOpacity = CA;
return kOK_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApplyLW(SkPdfContext* pdfContext, double lineWidth) {
pdfContext->fGraphicsState.fLineWidth = lineWidth;
return kOK_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApplyLC(SkPdfContext* pdfContext, int64_t lineCap) {
pdfContext->fGraphicsState.fLineCap = (int)lineCap;
return kOK_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApplyLJ(SkPdfContext* pdfContext, int64_t lineJoin) {
pdfContext->fGraphicsState.fLineJoin = (int)lineJoin;
return kOK_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApplyML(SkPdfContext* pdfContext, double miterLimit) {
pdfContext->fGraphicsState.fMiterLimit = miterLimit;
return kOK_SkPdfResult;
}
// TODO(edisonn): test all dashing rules, not sure if they work as in skia.
/*
1) [ ] 0 No dash; solid, unbroken lines
2) [3] 0 3 units on, 3 units off, …
3) [2] 1 1 on, 2 off, 2 on, 2 off, …
4) [2 1] 0 2 on, 1 off, 2 on, 1 off, …
5) [3 5] 6 2 off, 3 on, 5 off, 3 on, 5 off, …
6) [2 3] 11 1 on, 3 off, 2 on, 3 off, 2 on, …
*/
static SkPdfResult skpdfGraphicsStateApplyD(SkPdfContext* pdfContext, SkPdfArray* intervals,
SkPdfNativeObject* phase) {
if (intervals == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, intervals,
SkPdfNativeObject::_kNumber_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
if (phase == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, phase,
SkPdfNativeObject::_kNumber_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
int cnt = intervals->size();
if (cnt >= 256) {
// TODO(edisonn): alloc memory
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue,
"dash array size unssuported, cnt > 256", intervals, pdfContext);
return kIgnoreError_SkPdfResult;
}
for (int i = 0; i < cnt; i++) {
if (!intervals->objAtAIndex(i) || !intervals->objAtAIndex(i)->isNumber()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL,
intervals->objAtAIndex(i),
SkPdfNativeObject::_kNumber_PdfObjectType, NULL);
return kIgnoreError_SkPdfResult;
}
}
double total = 0;
for (int i = 0 ; i < cnt; i++) {
pdfContext->fGraphicsState.fDashArray[i] = intervals->objAtAIndex(i)->scalarValue();
total += pdfContext->fGraphicsState.fDashArray[i];
}
if (cnt & 1) {
if (cnt == 1) {
pdfContext->fGraphicsState.fDashArray[1] = pdfContext->fGraphicsState.fDashArray[0];
cnt++;
} else {
// TODO(edisonn): report error/warning
return kNYI_SkPdfResult;
}
}
pdfContext->fGraphicsState.fDashArrayLength = cnt;
pdfContext->fGraphicsState.fDashPhase = phase->scalarValue();
if (pdfContext->fGraphicsState.fDashPhase == 0) {
// other rules, changes?
pdfContext->fGraphicsState.fDashPhase = SkDoubleToScalar(total);
}
return kOK_SkPdfResult;
}
static SkPdfResult skpdfGraphicsStateApplyD(SkPdfContext* pdfContext, SkPdfArray* dash) {
if (!dash || dash->isArray()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, dash,
SkPdfNativeObject::kArray_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
if (dash->size() != 2) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kIncostistentSizes_SkPdfIssue,
"hash array must have 2 elements", dash, pdfContext);
return kIgnoreError_SkPdfResult;
}
if (!dash->objAtAIndex(0) || !dash->objAtAIndex(0)->isArray()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, dash->objAtAIndex(0),
SkPdfNativeObject::kArray_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
if (!dash->objAtAIndex(1) || !dash->objAtAIndex(1)->isNumber()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, dash->objAtAIndex(1),
SkPdfNativeObject::_kNumber_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
return skpdfGraphicsStateApplyD(pdfContext, (SkPdfArray*)dash->objAtAIndex(0),
dash->objAtAIndex(1));
}
static void skpdfGraphicsStateApplyFont(SkPdfContext* pdfContext, SkPdfArray* fontAndSize) {
if (!fontAndSize || !fontAndSize->isArray()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, fontAndSize,
SkPdfNativeObject::kArray_PdfObjectType, pdfContext);
return;
}
if (fontAndSize->size() != 2) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kIncostistentSizes_SkPdfIssue,
"font array must have 2 elements", fontAndSize, pdfContext);
return;
}
if (!fontAndSize->objAtAIndex(0) || !fontAndSize->objAtAIndex(0)->isName()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL,
fontAndSize->objAtAIndex(0),
SkPdfNativeObject::kName_PdfObjectType, pdfContext);
return;
}
if (!fontAndSize->objAtAIndex(1) || !fontAndSize->objAtAIndex(1)->isNumber()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL,
fontAndSize->objAtAIndex(0),
SkPdfNativeObject::_kNumber_PdfObjectType, pdfContext);
return;
}
skpdfGraphicsStateApplyFontCore(pdfContext, fontAndSize->objAtAIndex(0),
fontAndSize->objAtAIndex(1)->numberValue());
}
//lineWidth w Set the line width in the graphics state (see “Line Width” on page 152).
static SkPdfResult PdfOp_w(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("w", pdfContext, 1);
POP_NUMBER(pdfContext, lw);
CHECK_PARAMETERS();
return skpdfGraphicsStateApplyLW(pdfContext, lw);
}
//lineCap J Set the line cap style in the graphics state (see “Line Cap Style” on page 153).
static SkPdfResult PdfOp_J(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
// TODO(edisonn): round/ceil to int?
EXPECT_OPERANDS("J", pdfContext, 1);
POP_NUMBER(pdfContext, lc);
CHECK_PARAMETERS();
return skpdfGraphicsStateApplyLC(pdfContext, (int)lc);
}
//lineJoin j Set the line join style in the graphics state (see “Line Join Style” on page 153).
static SkPdfResult PdfOp_j(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
// TODO(edisonn): round/ceil to int?
EXPECT_OPERANDS("j", pdfContext, 1);
POP_NUMBER(pdfContext, lj);
CHECK_PARAMETERS();
return skpdfGraphicsStateApplyLJ(pdfContext, (int)lj);
}
//miterLimit M Set the miter limit in the graphics state (see “Miter Limit” on page 153).
static SkPdfResult PdfOp_M(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("M", pdfContext, 1);
POP_NUMBER(pdfContext, ml);
CHECK_PARAMETERS();
return skpdfGraphicsStateApplyML(pdfContext, ml);
}
//dashArray dashPhase d Set the line dash pattern in the graphics state (see “Line Dash Pattern” on
//page 155).
static SkPdfResult PdfOp_d(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("d", pdfContext, 2);
POP_OBJ(pdfContext, phase);
POP_ARRAY(pdfContext, array);
CHECK_PARAMETERS();
return skpdfGraphicsStateApplyD(pdfContext, array, phase);
}
//intent ri (PDF 1.1) Set the color rendering intent in the graphics state (see “Rendering Intents”
// on page 197).
static SkPdfResult PdfOp_ri(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
pdfContext->fObjectStack.pop();
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "render intent NYI", NULL,
pdfContext);
return kNYI_SkPdfResult;
}
//flatness i Set the flatness tolerance in the graphics state (see Section 6.5.1, “Flatness
//Tolerance”). flatness is a number in the range 0 to 100; a value of 0 speci-
//fies the output devices default flatness tolerance.
static SkPdfResult PdfOp_i(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("i", pdfContext, 1);
POP_NUMBER(pdfContext, flatness);
CHECK_PARAMETERS();
if (flatness < 0 || flatness > 100) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kOutOfRange_SkPdfIssue,
"flatness must be a real in [0, 100] range", flatness_obj, pdfContext);
return kIgnoreError_SkPdfResult;
}
return kNYI_SkPdfResult;
}
SkTDict<SkXfermode::Mode> gPdfBlendModes(20);
class InitBlendModes {
public:
InitBlendModes() {
// TODO(edisonn): use the python code generator?
// TABLE 7.2 Standard separable blend modes
gPdfBlendModes.set("Normal", SkXfermode::kSrc_Mode);
gPdfBlendModes.set("Multiply", SkXfermode::kMultiply_Mode);
gPdfBlendModes.set("Screen", SkXfermode::kScreen_Mode);
gPdfBlendModes.set("Overlay", SkXfermode::kOverlay_Mode);
gPdfBlendModes.set("Darken", SkXfermode::kDarken_Mode);
gPdfBlendModes.set("Lighten", SkXfermode::kLighten_Mode);
gPdfBlendModes.set("ColorDodge", SkXfermode::kColorDodge_Mode);
gPdfBlendModes.set("ColorBurn", SkXfermode::kColorBurn_Mode);
gPdfBlendModes.set("HardLight", SkXfermode::kHardLight_Mode);
gPdfBlendModes.set("SoftLight", SkXfermode::kSoftLight_Mode);
gPdfBlendModes.set("Difference", SkXfermode::kDifference_Mode);
gPdfBlendModes.set("Exclusion", SkXfermode::kExclusion_Mode);
// TABLE 7.3 Standard nonseparable blend modes
gPdfBlendModes.set("Hue", SkXfermode::kHue_Mode);
gPdfBlendModes.set("Saturation", SkXfermode::kSaturation_Mode);
gPdfBlendModes.set("Color", SkXfermode::kColor_Mode);
gPdfBlendModes.set("Luminosity", SkXfermode::kLuminosity_Mode);
}
};
InitBlendModes _gDummyInniter;
static SkXfermode::Mode xferModeFromBlendMode(const char* blendMode, size_t len) {
SkXfermode::Mode mode = (SkXfermode::Mode)(SkXfermode::kLastMode + 1);
if (gPdfBlendModes.find(blendMode, len, &mode)) {
return mode;
}
return (SkXfermode::Mode)(SkXfermode::kLastMode + 1);
}
static void skpdfGraphicsStateApplyBM_name(SkPdfContext* pdfContext, const SkString& blendMode) {
SkXfermode::Mode mode = xferModeFromBlendMode(blendMode.c_str(), blendMode.size());
if (mode <= SkXfermode::kLastMode) {
pdfContext->fGraphicsState.fBlendModesLength = 1;
pdfContext->fGraphicsState.fBlendModes[0] = mode;
} else {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kUnknownBlendMode_SkPdfIssue,
blendMode.c_str(), NULL, pdfContext);
}
}
static void skpdfGraphicsStateApplyBM_array(SkPdfContext* pdfContext, SkPdfArray* blendModes) {
if (!blendModes || !blendModes->isArray()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, blendModes,
SkPdfNativeObject::kArray_PdfObjectType, pdfContext);
return;
}
if (blendModes->size() == 0 || blendModes->size() > 256) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kIncostistentSizes_SkPdfIssue,
"length of blendmodes, 0, is an erro, 256+, is NYI", blendModes, pdfContext);
return;
}
SkXfermode::Mode modes[256];
int cnt = blendModes->size();
for (int i = 0; i < cnt; i++) {
SkPdfNativeObject* name = blendModes->objAtAIndex(i);
if (!name || !name->isName()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, name,
SkPdfNativeObject::kName_PdfObjectType, pdfContext);
return;
}
SkXfermode::Mode mode = xferModeFromBlendMode(name->c_str(), name->lenstr());
if (mode > SkXfermode::kLastMode) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kUnknownBlendMode_SkPdfIssue, NULL, name,
pdfContext);
return;
}
}
pdfContext->fGraphicsState.fBlendModesLength = cnt;
for (int i = 0; i < cnt; i++) {
pdfContext->fGraphicsState.fBlendModes[i] = modes[i];
}
}
static void skpdfGraphicsStateApplySMask_dict(SkPdfContext* pdfContext, SkPdfDictionary* sMask) {
if (!sMask || !sMask->isName()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, sMask,
SkPdfNativeObject::kArray_PdfObjectType, pdfContext);
return;
}
if (pdfContext->fPdfDoc->mapper()->mapSoftMaskDictionary(sMask)) {
pdfContext->fGraphicsState.fSoftMaskDictionary = (SkPdfSoftMaskDictionary*)sMask;
} else if (pdfContext->fPdfDoc->mapper()->mapSoftMaskImageDictionary(sMask)) {
SkPdfSoftMaskImageDictionary* smid = (SkPdfSoftMaskImageDictionary*)sMask;
pdfContext->fGraphicsState.fSMask = getImageFromObject(pdfContext, smid, true);
} else {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity,
"Dictionary must be SoftMask, or SoftMaskImage",
sMask, SkPdfNativeObject::kDictionary_PdfObjectType, pdfContext);
}
}
static void skpdfGraphicsStateApplySMask_name(SkPdfContext* pdfContext, const SkString& sMask) {
if (sMask.equals("None")) {
pdfContext->fGraphicsState.fSoftMaskDictionary = NULL;
pdfContext->fGraphicsState.fSMask = NULL;
return;
}
SkPdfDictionary* extGStateDictionary
= pdfContext->fGraphicsState.fResources->ExtGState(pdfContext->fPdfDoc);
if (extGStateDictionary == NULL) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingExtGState_SkPdfIssue, NULL,
pdfContext->fGraphicsState.fResources, pdfContext);
return;
}
SkPdfNativeObject* obj
= pdfContext->fPdfDoc->resolveReference(extGStateDictionary->get(sMask.c_str()));
if (!obj || !obj->isDictionary()) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, obj,
SkPdfNativeObject::kDictionary_PdfObjectType, pdfContext);
return;
}
pdfContext->fGraphicsState.fSoftMaskDictionary = NULL;
pdfContext->fGraphicsState.fSMask = NULL;
skpdfGraphicsStateApplySMask_dict(pdfContext, obj->asDictionary());
}
static void skpdfGraphicsStateApplyAIS(SkPdfContext* pdfContext, bool alphaSource) {
pdfContext->fGraphicsState.fAlphaSource = alphaSource;
}
//dictName gs (PDF 1.2) Set the specified parameters in the graphics state. dictName is
//the name of a graphics state parameter dictionary in the ExtGState subdictionary of the current
//resource dictionary (see the next section).
static SkPdfResult PdfOp_gs(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("gs", pdfContext, 1);
POP_NAME(pdfContext, name);
CHECK_PARAMETERS();
SkPdfDictionary* extGStateDictionary
= pdfContext->fGraphicsState.fResources->ExtGState(pdfContext->fPdfDoc);
if (extGStateDictionary == NULL) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingExtGState_SkPdfIssue, NULL,
pdfContext->fGraphicsState.fResources, pdfContext);
return kIgnoreError_SkPdfResult;
}
SkPdfNativeObject* value
= pdfContext->fPdfDoc->resolveReference(extGStateDictionary->get(name));
if (kNone_SkPdfNativeObjectType ==
pdfContext->fPdfDoc->mapper()->mapGraphicsStateDictionary(value)) {
return kIgnoreError_SkPdfResult;
}
SkPdfGraphicsStateDictionary* gs = (SkPdfGraphicsStateDictionary*)value;
if (gs == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL,
gs, SkPdfNativeObject::kDictionary_PdfObjectType, pdfContext);
return kIgnoreError_SkPdfResult;
}
if (gs->has_LW()) {
skpdfGraphicsStateApplyLW(pdfContext, gs->LW(pdfContext->fPdfDoc));
}
if (gs->has_LC()) {
skpdfGraphicsStateApplyLC(pdfContext, gs->LC(pdfContext->fPdfDoc));
}
if (gs->has_LJ()) {
skpdfGraphicsStateApplyLJ(pdfContext, gs->LJ(pdfContext->fPdfDoc));
}
if (gs->has_ML()) {
skpdfGraphicsStateApplyML(pdfContext, gs->ML(pdfContext->fPdfDoc));
}
if (gs->has_D()) {
skpdfGraphicsStateApplyD(pdfContext, gs->D(pdfContext->fPdfDoc));
}
if (gs->has_Font()) {
skpdfGraphicsStateApplyFont(pdfContext, gs->Font(pdfContext->fPdfDoc));
}
if (gs->has_BM()) {
if (gs->isBMAName(pdfContext->fPdfDoc)) {
skpdfGraphicsStateApplyBM_name(pdfContext, gs->getBMAsName(pdfContext->fPdfDoc));
} else if (gs->isBMAArray(pdfContext->fPdfDoc)) {
skpdfGraphicsStateApplyBM_array(pdfContext, gs->getBMAsArray(pdfContext->fPdfDoc));
} else {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, "wrong type", gs->get("BM"),
SkPdfNativeObject::kArray_PdfObjectType ||
SkPdfNativeObject::kName_PdfObjectType, pdfContext);
}
}
if (gs->has_SMask()) {
if (gs->isSMaskAName(pdfContext->fPdfDoc)) {
skpdfGraphicsStateApplySMask_name(pdfContext, gs->getSMaskAsName(pdfContext->fPdfDoc));
} else if (gs->isSMaskADictionary(pdfContext->fPdfDoc)) {
skpdfGraphicsStateApplySMask_dict(pdfContext,
gs->getSMaskAsDictionary(pdfContext->fPdfDoc));
} else {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity,
"wrong type",
gs->get("BM"),
SkPdfNativeObject::kDictionary_PdfObjectType ||
SkPdfNativeObject::kName_PdfObjectType,
pdfContext);
}
}
if (gs->has_ca()) {
skpdfGraphicsStateApply_ca(pdfContext, gs->ca(pdfContext->fPdfDoc));
}
if (gs->has_CA()) {
skpdfGraphicsStateApply_CA(pdfContext, gs->CA(pdfContext->fPdfDoc));
}
if (gs->has_AIS()) {
skpdfGraphicsStateApplyAIS(pdfContext, gs->AIS(pdfContext->fPdfDoc));
}
// TODO(edisonn): make sure we loaded all those properties in graphic state.
return kOK_SkPdfResult;
}
//charSpace Tc Set the character spacing, Tc
//, to charSpace, which is a number expressed in unscaled text space units.
// Character spacing is used by the Tj, TJ, and ' operators.
//Initial value: 0.
SkPdfResult PdfOp_Tc(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tc", pdfContext, 1);
POP_NUMBER(pdfContext, charSpace);
CHECK_PARAMETERS();
pdfContext->fGraphicsState.fCharSpace = charSpace;
return kOK_SkPdfResult;
}
//wordSpace Tw Set the word spacing, T
//w
//, to wordSpace, which is a number expressed in unscaled
//text space units. Word spacing is used by the Tj, TJ, and ' operators. Initial
//value: 0.
SkPdfResult PdfOp_Tw(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tw", pdfContext, 1);
POP_NUMBER(pdfContext, wordSpace);
CHECK_PARAMETERS();
pdfContext->fGraphicsState.fWordSpace = wordSpace;
return kOK_SkPdfResult;
}
//scale Tz Set the horizontal scaling, Th
//, to (scale ˜ 100). scale is a number specifying the
//percentage of the normal width. Initial value: 100 (normal width).
static SkPdfResult PdfOp_Tz(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tz", pdfContext, 1);
POP_NUMBER(pdfContext, scale);
CHECK_PARAMETERS();
if (scale < 0) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kOutOfRange_SkPdfIssue,
"scale must a positive real number", scale_obj, pdfContext);
return kError_SkPdfResult;
}
return kNYI_SkPdfResult;
}
//render Tr Set the text rendering mode, T
//mode, to render, which is an integer. Initial value: 0.
static SkPdfResult PdfOp_Tr(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Tr", pdfContext, 1);
POP_INTEGER(pdfContext, mode);
CHECK_PARAMETERS();
if (mode < 0) { // TODO(edisonn): function/enums with supported modes
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kOutOfRange_SkPdfIssue,
"mode must a positive integer or 0", mode_obj, pdfContext);
return kError_SkPdfResult;
}
return kNYI_SkPdfResult;
}
//rise Ts Set the text rise, Trise, to rise, which is a number expressed in unscaled text space
//units. Initial value: 0.
static SkPdfResult PdfOp_Ts(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Ts", pdfContext, 1);
POP_NUMBER(pdfContext, rise);
CHECK_PARAMETERS();
if (rise < 0) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kOutOfRange_SkPdfIssue,
"rise must a positive real number", rise_obj, pdfContext);
return kNYI_SkPdfResult;
}
return kNYI_SkPdfResult;
}
//wx wy d0
static SkPdfResult PdfOp_d0(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("d0", pdfContext, 2);
POP_NUMBER(pdfContext, wy);
POP_NUMBER(pdfContext, wx);
CHECK_PARAMETERS();
if (wx < 0) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kOutOfRange_SkPdfIssue,
"wx must a positive real number", wx_obj, pdfContext);
return kError_SkPdfResult;
}
if (wy < 0) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kOutOfRange_SkPdfIssue,
"wy must a positive real number", wy_obj, pdfContext);
return kError_SkPdfResult;
}
return kNYI_SkPdfResult;
}
//wx wy llx lly urx ury d1
static SkPdfResult PdfOp_d1(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("d1", pdfContext, 6);
POP_NUMBER(pdfContext, ury);
POP_NUMBER(pdfContext, urx);
POP_NUMBER(pdfContext, lly);
POP_NUMBER(pdfContext, llx);
POP_NUMBER(pdfContext, wy);
POP_NUMBER(pdfContext, wx);
CHECK_PARAMETERS();
// TODO(edisonn): really silly quick way to remove warning
if (wx + wy + llx + lly + urx + ury) {
return kNYI_SkPdfResult;
}
return kNYI_SkPdfResult;
}
//name sh
static SkPdfResult PdfOp_sh(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("sh", pdfContext, 1);
POP_NAME(pdfContext, name);
CHECK_PARAMETERS();
if (name == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, name,
SkPdfNativeObject::kName_PdfObjectType, pdfContext);
return kError_SkPdfResult;
}
return kNYI_SkPdfResult;
}
//name Do
static SkPdfResult PdfOp_Do(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("Do", pdfContext, 1);
POP_NAME(pdfContext, name);
CHECK_PARAMETERS();
SkPdfDictionary* xObject = pdfContext->fGraphicsState.fResources->XObject(pdfContext->fPdfDoc);
if (xObject == NULL) {
SkPdfReport(kIgnoreError_SkPdfIssueSeverity, kMissingXObject_SkPdfIssue, NULL,
pdfContext->fGraphicsState.fResources, pdfContext);
return kIgnoreError_SkPdfResult;
}
SkPdfNativeObject* value = xObject->get(name);
value = pdfContext->fPdfDoc->resolveReference(value);
return doXObject(pdfContext, canvas, value);
}
//tag MP Designate a marked-content point. tag is a name object indicating the role or
//significance of the point.
static SkPdfResult PdfOp_MP(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("MP", pdfContext, 1);
POP_OBJ(pdfContext, tag);
CHECK_PARAMETERS();
if (tag == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, tag,
SkPdfNativeObject::_kObject_PdfObjectType, pdfContext);
return kNYI_SkPdfResult;
}
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "MP NYI", NULL, NULL);
return kNYI_SkPdfResult;
}
//tag properties DP Designate a marked-content point with an associated property list. tag is a
//name object indicating the role or significance of the point; properties is
//either an inline dictionary containing the property list or a name object
//associated with it in the Properties subdictionary of the current resource
//dictionary (see Section 9.5.1, “Property Lists”).
static SkPdfResult PdfOp_DP(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("DP", pdfContext, 2);
POP_OBJ(pdfContext, properties);
POP_OBJ(pdfContext, tag);
CHECK_PARAMETERS();
if (tag == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, tag,
SkPdfNativeObject::_kObject_PdfObjectType, pdfContext);
return kNYI_SkPdfResult;
}
if (properties == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, properties,
SkPdfNativeObject::_kObject_PdfObjectType, pdfContext);
return kNYI_SkPdfResult;
}
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "DP NYI", NULL, NULL);
return kNYI_SkPdfResult;
}
//tag BMC Begin a marked-content sequence terminated by a balancing EMC operator.
//tag is a name object indicating the role or significance of the sequence.
static SkPdfResult PdfOp_BMC(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("BMC", pdfContext, 1);
POP_OBJ(pdfContext, tag);
CHECK_PARAMETERS();
if (tag == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, tag,
SkPdfNativeObject::_kObject_PdfObjectType, pdfContext);
return kNYI_SkPdfResult;
}
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "BMC NYI", NULL, NULL);
return kNYI_SkPdfResult;
}
//tag properties BDC Begin a marked-content sequence with an associated property list, terminated
//by a balancing EMCoperator. tag is a name object indicating the role or significance of the
// sequence; propertiesis either an inline dictionary containing the
//property list or a name object associated with it in the Properties subdictionary of the current
//resource dictionary (see Section 9.5.1, “Property Lists”).
static SkPdfResult PdfOp_BDC(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
EXPECT_OPERANDS("BDC", pdfContext, 2);
POP_OBJ(pdfContext, properties);
POP_OBJ(pdfContext, tag);
CHECK_PARAMETERS();
if (tag == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, tag,
SkPdfNativeObject::_kObject_PdfObjectType, pdfContext);
return kNYI_SkPdfResult;
}
if (properties == NULL) {
SkPdfReportUnexpectedType(kIgnoreError_SkPdfIssueSeverity, NULL, properties,
SkPdfNativeObject::_kObject_PdfObjectType, pdfContext);
return kNYI_SkPdfResult;
}
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "BDC NYI", NULL, NULL);
return kNYI_SkPdfResult;
}
//— EMC End a marked-content sequence begun by a BMC or BDC operator.
static SkPdfResult PdfOp_EMC(SkPdfContext* pdfContext, SkCanvas* canvas, PdfTokenLooper** looper) {
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, "EMC NYI", NULL, NULL);
return kNYI_SkPdfResult;
}
static void initPdfOperatorRenderes() {
static bool gInitialized = false;
if (gInitialized) {
return;
}
gPdfOps.set("q", PdfOp_q);
gPdfOps.set("Q", PdfOp_Q);
gPdfOps.set("cm", PdfOp_cm);
gPdfOps.set("TD", PdfOp_TD);
gPdfOps.set("Td", PdfOp_Td);
gPdfOps.set("Tm", PdfOp_Tm);
gPdfOps.set("T*", PdfOp_T_star);
gPdfOps.set("m", PdfOp_m);
gPdfOps.set("l", PdfOp_l);
gPdfOps.set("c", PdfOp_c);
gPdfOps.set("v", PdfOp_v);
gPdfOps.set("y", PdfOp_y);
gPdfOps.set("h", PdfOp_h);
gPdfOps.set("re", PdfOp_re);
gPdfOps.set("S", PdfOp_S);
gPdfOps.set("s", PdfOp_s);
gPdfOps.set("f", PdfOp_f);
gPdfOps.set("F", PdfOp_F);
gPdfOps.set("f*", PdfOp_f_star);
gPdfOps.set("B", PdfOp_B);
gPdfOps.set("B*", PdfOp_B_star);
gPdfOps.set("b", PdfOp_b);
gPdfOps.set("b*", PdfOp_b_star);
gPdfOps.set("n", PdfOp_n);
gPdfOps.set("BT", PdfOp_BT);
gPdfOps.set("ET", PdfOp_ET);
gPdfOps.set("Tj", PdfOp_Tj);
gPdfOps.set("'", PdfOp_quote);
gPdfOps.set("\"", PdfOp_doublequote);
gPdfOps.set("TJ", PdfOp_TJ);
gPdfOps.set("CS", PdfOp_CS);
gPdfOps.set("cs", PdfOp_cs);
gPdfOps.set("SC", PdfOp_SC);
gPdfOps.set("SCN", PdfOp_SCN);
gPdfOps.set("sc", PdfOp_sc);
gPdfOps.set("scn", PdfOp_scn);
gPdfOps.set("G", PdfOp_G);
gPdfOps.set("g", PdfOp_g);
gPdfOps.set("RG", PdfOp_RG);
gPdfOps.set("rg", PdfOp_rg);
gPdfOps.set("K", PdfOp_K);
gPdfOps.set("k", PdfOp_k);
gPdfOps.set("W", PdfOp_W);
gPdfOps.set("W*", PdfOp_W_star);
gPdfOps.set("BX", PdfOp_BX);
gPdfOps.set("EX", PdfOp_EX);
gPdfOps.set("BI", PdfOp_BI);
gPdfOps.set("ID", PdfOp_ID);
gPdfOps.set("EI", PdfOp_EI);
gPdfOps.set("w", PdfOp_w);
gPdfOps.set("J", PdfOp_J);
gPdfOps.set("j", PdfOp_j);
gPdfOps.set("M", PdfOp_M);
gPdfOps.set("d", PdfOp_d);
gPdfOps.set("ri", PdfOp_ri);
gPdfOps.set("i", PdfOp_i);
gPdfOps.set("gs", PdfOp_gs);
gPdfOps.set("Tc", PdfOp_Tc);
gPdfOps.set("Tw", PdfOp_Tw);
gPdfOps.set("Tz", PdfOp_Tz);
gPdfOps.set("TL", PdfOp_TL);
gPdfOps.set("Tf", PdfOp_Tf);
gPdfOps.set("Tr", PdfOp_Tr);
gPdfOps.set("Ts", PdfOp_Ts);
gPdfOps.set("d0", PdfOp_d0);
gPdfOps.set("d1", PdfOp_d1);
gPdfOps.set("sh", PdfOp_sh);
gPdfOps.set("Do", PdfOp_Do);
gPdfOps.set("MP", PdfOp_MP);
gPdfOps.set("DP", PdfOp_DP);
gPdfOps.set("BMC", PdfOp_BMC);
gPdfOps.set("BDC", PdfOp_BDC);
gPdfOps.set("EMC", PdfOp_EMC);
gInitialized = true;
}
class InitPdfOps {
public:
InitPdfOps() {
initPdfOperatorRenderes();
}
};
InitPdfOps gInitPdfOps;
void reportPdfRenderStats() {
for (int i = 0 ; i < kCount_SkPdfResult; i++) {
SkTDict<int>::Iter iter(gRenderStats[i]);
const char* key;
int value = 0;
while ((key = iter.next(&value)) != NULL) {
printf("%s: %s -> count %i\n", gRenderStatsNames[i], key, value);
}
}
}
SkPdfResult PdfMainLooper::consumeToken(PdfToken& token) {
if (token.fType == kKeyword_TokenType && token.fKeywordLength < 256)
{
PdfOperatorRenderer pdfOperatorRenderer = NULL;
if (gPdfOps.find(token.fKeyword, token.fKeywordLength, &pdfOperatorRenderer) &&
pdfOperatorRenderer) {
PdfTokenLooper* childLooper = NULL;
// Main work is done by pdfOperatorRenderer(...)
SkPdfResult result = pdfOperatorRenderer(fPdfContext, fCanvas, &childLooper);
int cnt = 0;
gRenderStats[result].find(token.fKeyword, token.fKeywordLength, &cnt);
gRenderStats[result].set(token.fKeyword, token.fKeywordLength, cnt + 1);
if (childLooper) {
childLooper->setUp(this);
childLooper->loop();
delete childLooper;
}
} else {
int cnt = 0;
gRenderStats[kUnsupported_SkPdfResult].find(token.fKeyword,
token.fKeywordLength,
&cnt);
gRenderStats[kUnsupported_SkPdfResult].set(token.fKeyword,
token.fKeywordLength,
cnt + 1);
}
}
else if (token.fType == kObject_TokenType)
{
fPdfContext->fObjectStack.push( token.fObject );
}
else {
// TODO(edisonn): store the keyword as a object, so we can track the location in file,
// and report where the error was triggered
SkPdfReport(kCodeWarning_SkPdfIssueSeverity, kNYI_SkPdfIssue, token.fKeyword, NULL,
fPdfContext);
return kIgnoreError_SkPdfResult;
}
return kOK_SkPdfResult;
}
void PdfMainLooper::loop() {
PdfToken token;
while (readToken(fTokenizer, &token)) {
consumeToken(token);
}
}
SkPdfResult PdfInlineImageLooper::consumeToken(PdfToken& token) {
SkASSERT(false);
return kIgnoreError_SkPdfResult;
}
void PdfInlineImageLooper::loop() {
doXObject_Image(fPdfContext, fCanvas, fTokenizer->readInlineImage());
}
SkPdfResult PdfInlineImageLooper::done() {
return kNYI_SkPdfResult;
}
SkPdfResult PdfCompatibilitySectionLooper::consumeToken(PdfToken& token) {
return fParent->consumeToken(token);
}
void PdfCompatibilitySectionLooper::loop() {
PdfOp_q(fPdfContext, fCanvas, NULL);
PdfToken token;
while (readToken(fTokenizer, &token)) {
if (token.fType == kKeyword_TokenType && strcmp(token.fKeyword, "BX") == 0) {
PdfTokenLooper* looper = new PdfCompatibilitySectionLooper();
looper->setUp(this);
looper->loop();
delete looper;
} else {
if (token.fType == kKeyword_TokenType && strcmp(token.fKeyword, "EX") == 0) break;
fParent->consumeToken(token);
}
}
PdfOp_Q(fPdfContext, fCanvas, NULL);
}
// TODO(edisonn): for debugging - remove or put it in a #ifdef
SkPdfContext* gPdfContext = NULL;
bool SkPdfRenderer::renderPage(int page, SkCanvas* canvas, const SkRect& dst) const {
if (!fPdfDoc) {
return false;
}
if (page < 0 || page >= pages()) {
return false;
}
SkPdfContext pdfContext(fPdfDoc);
pdfContext.fOriginalMatrix = SkMatrix::I();
pdfContext.fGraphicsState.fResources = fPdfDoc->pageResources(page);
gPdfContext = &pdfContext;
SkScalar z = SkIntToScalar(0);
SkScalar w = dst.width();
SkScalar h = dst.height();
if (SkScalarTruncToInt(w) <= 0 || SkScalarTruncToInt(h) <= 0) {
return true;
}
SkScalar wp = fPdfDoc->MediaBox(page).width();
SkScalar hp = fPdfDoc->MediaBox(page).height();
SkPoint pdfSpace[4] = {SkPoint::Make(z, z),
SkPoint::Make(wp, z),
SkPoint::Make(wp, hp),
SkPoint::Make(z, hp)};
#ifdef PDF_DEBUG_3X
// Use larger image to make sure we do not draw anything outside of page
// could be used in tests.
SkPoint skiaSpace[4] = {SkPoint::Make(w+z, h+h),
SkPoint::Make(w+w, h+h),
SkPoint::Make(w+w, h+z),
SkPoint::Make(w+z, h+z)};
#else
SkPoint skiaSpace[4] = {SkPoint::Make(z, h),
SkPoint::Make(w, h),
SkPoint::Make(w, z),
SkPoint::Make(z, z)};
#endif
SkAssertResult(pdfContext.fOriginalMatrix.setPolyToPoly(pdfSpace, skiaSpace, 4));
SkTraceMatrix(pdfContext.fOriginalMatrix, "Original matrix");
pdfContext.fOriginalMatrix.postConcat(canvas->getTotalMatrix());
pdfContext.fGraphicsState.fCTM = pdfContext.fOriginalMatrix;
pdfContext.fGraphicsState.fContentStreamMatrix = pdfContext.fOriginalMatrix;
pdfContext.fGraphicsState.fMatrixTm = pdfContext.fGraphicsState.fCTM;
pdfContext.fGraphicsState.fMatrixTlm = pdfContext.fGraphicsState.fCTM;
#ifndef PDF_DEBUG_NO_PAGE_CLIPING
canvas->clipRect(dst, SkRegion::kIntersect_Op, true);
#endif
canvas->concat(pdfContext.fOriginalMatrix);
doPage(&pdfContext, canvas, fPdfDoc->page(page));
// TODO(edisonn:) erase with white before draw? Right now the caller is responsible.
// SkPaint paint;
// paint.setColor(SK_ColorWHITE);
// canvas->drawRect(rect, paint);
canvas->flush();
return true;
}
bool SkPdfRenderer::load(const SkString inputFileName) {
unload();
fPdfDoc = new SkPdfNativeDoc(inputFileName.c_str());
if (fPdfDoc->pages() == 0) {
delete fPdfDoc;
fPdfDoc = NULL;
}
return fPdfDoc != NULL;
}
bool SkPdfRenderer::load(SkStream* stream) {
unload();
// TODO(edisonn): create static function that could return NULL if there are errors
fPdfDoc = new SkPdfNativeDoc(stream);
if (fPdfDoc->pages() == 0) {
delete fPdfDoc;
fPdfDoc = NULL;
}
return fPdfDoc != NULL;
}
int SkPdfRenderer::pages() const {
return fPdfDoc != NULL ? fPdfDoc->pages() : 0;
}
void SkPdfRenderer::unload() {
delete fPdfDoc;
fPdfDoc = NULL;
}
SkRect SkPdfRenderer::MediaBox(int page) const {
SkASSERT(fPdfDoc);
return fPdfDoc->MediaBox(page);
}
size_t SkPdfRenderer::bytesUsed() const {
return fPdfDoc ? fPdfDoc->bytesUsed() : 0;
}
bool SkPDFNativeRenderToBitmap(SkStream* stream,
SkBitmap* output,
int page,
SkPdfContent content,
double dpi) {
SkASSERT(page >= 0);
SkPdfRenderer renderer;
renderer.load(stream);
if (!renderer.loaded() || page >= renderer.pages() || page < 0) {
return false;
}
SkRect rect = renderer.MediaBox(page < 0 ? 0 :page);
SkScalar width = SkScalarMul(rect.width(), SkDoubleToScalar(sqrt(dpi / 72.0)));
SkScalar height = SkScalarMul(rect.height(), SkDoubleToScalar(sqrt(dpi / 72.0)));
rect = SkRect::MakeWH(width, height);
setup_bitmap(output, (int)SkScalarToDouble(width), (int)SkScalarToDouble(height));
SkAutoTUnref<SkBaseDevice> device(SkNEW_ARGS(SkBitmapDevice, (*output)));
SkCanvas canvas(device);
return renderer.renderPage(page, &canvas, rect);
}