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