1
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkData.h"
11#include "SkGeometry.h"
12#include "SkPaint.h"
13#include "SkPath.h"
14#include "SkPDFResourceDict.h"
15#include "SkPDFUtils.h"
16#include "SkStream.h"
17#include "SkString.h"
18#include "SkPDFTypes.h"
19
20//static
21SkPDFArray* SkPDFUtils::RectToArray(const SkRect& rect) {
22    SkPDFArray* result = new SkPDFArray();
23    result->reserve(4);
24    result->appendScalar(rect.fLeft);
25    result->appendScalar(rect.fTop);
26    result->appendScalar(rect.fRight);
27    result->appendScalar(rect.fBottom);
28    return result;
29}
30
31// static
32SkPDFArray* SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
33    SkScalar values[6];
34    if (!matrix.asAffine(values)) {
35        SkMatrix::SetAffineIdentity(values);
36    }
37
38    SkPDFArray* result = new SkPDFArray;
39    result->reserve(6);
40    for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
41        result->appendScalar(values[i]);
42    }
43    return result;
44}
45
46// static
47void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) {
48    SkScalar values[6];
49    if (!matrix.asAffine(values)) {
50        SkMatrix::SetAffineIdentity(values);
51    }
52    for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
53        SkPDFScalar::Append(values[i], content);
54        content->writeText(" ");
55    }
56    content->writeText("cm\n");
57}
58
59// static
60void SkPDFUtils::MoveTo(SkScalar x, SkScalar y, SkWStream* content) {
61    SkPDFScalar::Append(x, content);
62    content->writeText(" ");
63    SkPDFScalar::Append(y, content);
64    content->writeText(" m\n");
65}
66
67// static
68void SkPDFUtils::AppendLine(SkScalar x, SkScalar y, SkWStream* content) {
69    SkPDFScalar::Append(x, content);
70    content->writeText(" ");
71    SkPDFScalar::Append(y, content);
72    content->writeText(" l\n");
73}
74
75// static
76void SkPDFUtils::AppendCubic(SkScalar ctl1X, SkScalar ctl1Y,
77                             SkScalar ctl2X, SkScalar ctl2Y,
78                             SkScalar dstX, SkScalar dstY, SkWStream* content) {
79    SkString cmd("y\n");
80    SkPDFScalar::Append(ctl1X, content);
81    content->writeText(" ");
82    SkPDFScalar::Append(ctl1Y, content);
83    content->writeText(" ");
84    if (ctl2X != dstX || ctl2Y != dstY) {
85        cmd.set("c\n");
86        SkPDFScalar::Append(ctl2X, content);
87        content->writeText(" ");
88        SkPDFScalar::Append(ctl2Y, content);
89        content->writeText(" ");
90    }
91    SkPDFScalar::Append(dstX, content);
92    content->writeText(" ");
93    SkPDFScalar::Append(dstY, content);
94    content->writeText(" ");
95    content->writeText(cmd.c_str());
96}
97
98// static
99void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) {
100    // Skia has 0,0 at top left, pdf at bottom left.  Do the right thing.
101    SkScalar bottom = SkMinScalar(rect.fBottom, rect.fTop);
102
103    SkPDFScalar::Append(rect.fLeft, content);
104    content->writeText(" ");
105    SkPDFScalar::Append(bottom, content);
106    content->writeText(" ");
107    SkPDFScalar::Append(rect.width(), content);
108    content->writeText(" ");
109    SkPDFScalar::Append(rect.height(), content);
110    content->writeText(" re\n");
111}
112
113// static
114void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
115                          SkWStream* content) {
116    // Filling a path with no area results in a drawing in PDF renderers but
117    // Chrome expects to be able to draw some such entities with no visible
118    // result, so we detect those cases and discard the drawing for them.
119    // Specifically: moveTo(X), lineTo(Y) and moveTo(X), lineTo(X), lineTo(Y).
120    enum SkipFillState {
121        kEmpty_SkipFillState         = 0,
122        kSingleLine_SkipFillState    = 1,
123        kNonSingleLine_SkipFillState = 2,
124    };
125    SkipFillState fillState = kEmpty_SkipFillState;
126    if (paintStyle != SkPaint::kFill_Style) {
127        fillState = kNonSingleLine_SkipFillState;
128    }
129    SkPoint lastMovePt = SkPoint::Make(0,0);
130    SkDynamicMemoryWStream currentSegment;
131    SkPoint args[4];
132    SkPath::Iter iter(path, false);
133    for (SkPath::Verb verb = iter.next(args);
134         verb != SkPath::kDone_Verb;
135         verb = iter.next(args)) {
136        // args gets all the points, even the implicit first point.
137        switch (verb) {
138            case SkPath::kMove_Verb:
139                MoveTo(args[0].fX, args[0].fY, &currentSegment);
140                lastMovePt = args[0];
141                fillState = kEmpty_SkipFillState;
142                break;
143            case SkPath::kLine_Verb:
144                AppendLine(args[1].fX, args[1].fY, &currentSegment);
145                if (fillState == kEmpty_SkipFillState) {
146                   if (args[0] != lastMovePt) {
147                       fillState = kSingleLine_SkipFillState;
148                   }
149                } else if (fillState == kSingleLine_SkipFillState) {
150                    fillState = kNonSingleLine_SkipFillState;
151                }
152                break;
153            case SkPath::kQuad_Verb: {
154                SkPoint cubic[4];
155                SkConvertQuadToCubic(args, cubic);
156                AppendCubic(cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY,
157                            cubic[3].fX, cubic[3].fY, &currentSegment);
158                fillState = kNonSingleLine_SkipFillState;
159                break;
160            }
161            case SkPath::kCubic_Verb:
162                AppendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
163                            args[3].fX, args[3].fY, &currentSegment);
164                fillState = kNonSingleLine_SkipFillState;
165                break;
166            case SkPath::kClose_Verb:
167                if (fillState != kSingleLine_SkipFillState) {
168                    ClosePath(&currentSegment);
169                    SkData* data = currentSegment.copyToData();
170                    content->write(data->data(), data->size());
171                    data->unref();
172                }
173                currentSegment.reset();
174                break;
175            default:
176                SkASSERT(false);
177                break;
178        }
179    }
180    if (currentSegment.bytesWritten() > 0) {
181        SkData* data = currentSegment.copyToData();
182        content->write(data->data(), data->size());
183        data->unref();
184    }
185}
186
187// static
188void SkPDFUtils::ClosePath(SkWStream* content) {
189    content->writeText("h\n");
190}
191
192// static
193void SkPDFUtils::PaintPath(SkPaint::Style style, SkPath::FillType fill,
194                           SkWStream* content) {
195    if (style == SkPaint::kFill_Style) {
196        content->writeText("f");
197    } else if (style == SkPaint::kStrokeAndFill_Style) {
198        content->writeText("B");
199    } else if (style == SkPaint::kStroke_Style) {
200        content->writeText("S");
201    }
202
203    if (style != SkPaint::kStroke_Style) {
204        NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
205        NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
206        if (fill == SkPath::kEvenOdd_FillType) {
207            content->writeText("*");
208        }
209    }
210    content->writeText("\n");
211}
212
213// static
214void SkPDFUtils::StrokePath(SkWStream* content) {
215    SkPDFUtils::PaintPath(
216        SkPaint::kStroke_Style, SkPath::kWinding_FillType, content);
217}
218
219// static
220void SkPDFUtils::DrawFormXObject(int objectIndex, SkWStream* content) {
221    content->writeText("/");
222    content->writeText(SkPDFResourceDict::getResourceName(
223            SkPDFResourceDict::kXObject_ResourceType,
224            objectIndex).c_str());
225    content->writeText(" Do\n");
226}
227
228// static
229void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
230    content->writeText("/");
231    content->writeText(SkPDFResourceDict::getResourceName(
232            SkPDFResourceDict::kExtGState_ResourceType,
233            objectIndex).c_str());
234    content->writeText(" gs\n");
235}
236
237// static
238void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
239    // Select Pattern color space (CS, cs) and set pattern object as current
240    // color (SCN, scn)
241    SkString resourceName = SkPDFResourceDict::getResourceName(
242            SkPDFResourceDict::kPattern_ResourceType,
243            objectIndex);
244    content->writeText("/Pattern CS/Pattern cs/");
245    content->writeText(resourceName.c_str());
246    content->writeText(" SCN/");
247    content->writeText(resourceName.c_str());
248    content->writeText(" scn\n");
249}
250