|
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 |
|
21 SkPDFArray* 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 |
|
32 SkPDFArray* 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 |
|
47 void 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 |
|
60 void 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 |
|
68 void 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 |
|
76 void 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 |
|
99 void 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 |
|
114 void 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, ¤tSegment); |
|
140 lastMovePt = args[0]; |
|
141 fillState = kEmpty_SkipFillState; |
|
142 break; |
|
143 case SkPath::kLine_Verb: |
|
144 AppendLine(args[1].fX, args[1].fY, ¤tSegment); |
|
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, ¤tSegment); |
|
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, ¤tSegment); |
|
164 fillState = kNonSingleLine_SkipFillState; |
|
165 break; |
|
166 case SkPath::kClose_Verb: |
|
167 if (fillState != kSingleLine_SkipFillState) { |
|
168 ClosePath(¤tSegment); |
|
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 |
|
188 void SkPDFUtils::ClosePath(SkWStream* content) { |
|
189 content->writeText("h\n"); |
|
190 } |
|
191 |
|
192 // static |
|
193 void 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 |
|
214 void SkPDFUtils::StrokePath(SkWStream* content) { |
|
215 SkPDFUtils::PaintPath( |
|
216 SkPaint::kStroke_Style, SkPath::kWinding_FillType, content); |
|
217 } |
|
218 |
|
219 // static |
|
220 void 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 |
|
229 void 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 |
|
238 void 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 } |