1 /*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/pdf/SkPDFUtils.h"
9
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkStream.h"
13 #include "include/core/SkString.h"
14 #include "include/private/SkFixed.h"
15 #include "src/core/SkGeometry.h"
16 #include "src/core/SkPathPriv.h"
17 #include "src/image/SkImage_Base.h"
18 #include "src/pdf/SkPDFResourceDict.h"
19 #include "src/pdf/SkPDFTypes.h"
20
21 #include <cmath>
22
BlendModeName(SkBlendMode mode)23 const char* SkPDFUtils::BlendModeName(SkBlendMode mode) {
24 // PDF32000.book section 11.3.5 "Blend Mode"
25 switch (mode) {
26 case SkBlendMode::kSrcOver: return "Normal";
27 case SkBlendMode::kXor: return "Normal"; // (unsupported mode)
28 case SkBlendMode::kPlus: return "Normal"; // (unsupported mode)
29 case SkBlendMode::kScreen: return "Screen";
30 case SkBlendMode::kOverlay: return "Overlay";
31 case SkBlendMode::kDarken: return "Darken";
32 case SkBlendMode::kLighten: return "Lighten";
33 case SkBlendMode::kColorDodge: return "ColorDodge";
34 case SkBlendMode::kColorBurn: return "ColorBurn";
35 case SkBlendMode::kHardLight: return "HardLight";
36 case SkBlendMode::kSoftLight: return "SoftLight";
37 case SkBlendMode::kDifference: return "Difference";
38 case SkBlendMode::kExclusion: return "Exclusion";
39 case SkBlendMode::kMultiply: return "Multiply";
40 case SkBlendMode::kHue: return "Hue";
41 case SkBlendMode::kSaturation: return "Saturation";
42 case SkBlendMode::kColor: return "Color";
43 case SkBlendMode::kLuminosity: return "Luminosity";
44 // Other blendmodes are handled in SkPDFDevice::setUpContentEntry.
45 default: return nullptr;
46 }
47 }
48
RectToArray(const SkRect & r)49 std::unique_ptr<SkPDFArray> SkPDFUtils::RectToArray(const SkRect& r) {
50 return SkPDFMakeArray(r.left(), r.top(), r.right(), r.bottom());
51 }
52
MatrixToArray(const SkMatrix & matrix)53 std::unique_ptr<SkPDFArray> SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
54 SkScalar a[6];
55 if (!matrix.asAffine(a)) {
56 SkMatrix::SetAffineIdentity(a);
57 }
58 return SkPDFMakeArray(a[0], a[1], a[2], a[3], a[4], a[5]);
59 }
60
MoveTo(SkScalar x,SkScalar y,SkWStream * content)61 void SkPDFUtils::MoveTo(SkScalar x, SkScalar y, SkWStream* content) {
62 SkPDFUtils::AppendScalar(x, content);
63 content->writeText(" ");
64 SkPDFUtils::AppendScalar(y, content);
65 content->writeText(" m\n");
66 }
67
AppendLine(SkScalar x,SkScalar y,SkWStream * content)68 void SkPDFUtils::AppendLine(SkScalar x, SkScalar y, SkWStream* content) {
69 SkPDFUtils::AppendScalar(x, content);
70 content->writeText(" ");
71 SkPDFUtils::AppendScalar(y, content);
72 content->writeText(" l\n");
73 }
74
append_cubic(SkScalar ctl1X,SkScalar ctl1Y,SkScalar ctl2X,SkScalar ctl2Y,SkScalar dstX,SkScalar dstY,SkWStream * content)75 static void append_cubic(SkScalar ctl1X, SkScalar ctl1Y,
76 SkScalar ctl2X, SkScalar ctl2Y,
77 SkScalar dstX, SkScalar dstY, SkWStream* content) {
78 SkString cmd("y\n");
79 SkPDFUtils::AppendScalar(ctl1X, content);
80 content->writeText(" ");
81 SkPDFUtils::AppendScalar(ctl1Y, content);
82 content->writeText(" ");
83 if (ctl2X != dstX || ctl2Y != dstY) {
84 cmd.set("c\n");
85 SkPDFUtils::AppendScalar(ctl2X, content);
86 content->writeText(" ");
87 SkPDFUtils::AppendScalar(ctl2Y, content);
88 content->writeText(" ");
89 }
90 SkPDFUtils::AppendScalar(dstX, content);
91 content->writeText(" ");
92 SkPDFUtils::AppendScalar(dstY, content);
93 content->writeText(" ");
94 content->writeText(cmd.c_str());
95 }
96
append_quad(const SkPoint quad[],SkWStream * content)97 static void append_quad(const SkPoint quad[], SkWStream* content) {
98 SkPoint cubic[4];
99 SkConvertQuadToCubic(quad, cubic);
100 append_cubic(cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY,
101 cubic[3].fX, cubic[3].fY, content);
102 }
103
AppendRectangle(const SkRect & rect,SkWStream * content)104 void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) {
105 // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
106 SkScalar bottom = std::min(rect.fBottom, rect.fTop);
107
108 SkPDFUtils::AppendScalar(rect.fLeft, content);
109 content->writeText(" ");
110 SkPDFUtils::AppendScalar(bottom, content);
111 content->writeText(" ");
112 SkPDFUtils::AppendScalar(rect.width(), content);
113 content->writeText(" ");
114 SkPDFUtils::AppendScalar(rect.height(), content);
115 content->writeText(" re\n");
116 }
117
EmitPath(const SkPath & path,SkPaint::Style paintStyle,bool doConsumeDegerates,SkWStream * content,SkScalar tolerance)118 void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
119 bool doConsumeDegerates, SkWStream* content,
120 SkScalar tolerance) {
121 if (path.isEmpty() && SkPaint::kFill_Style == paintStyle) {
122 SkPDFUtils::AppendRectangle({0, 0, 0, 0}, content);
123 return;
124 }
125 // Filling a path with no area results in a drawing in PDF renderers but
126 // Chrome expects to be able to draw some such entities with no visible
127 // result, so we detect those cases and discard the drawing for them.
128 // Specifically: moveTo(X), lineTo(Y) and moveTo(X), lineTo(X), lineTo(Y).
129
130 SkRect rect;
131 bool isClosed; // Both closure and direction need to be checked.
132 SkPathDirection direction;
133 if (path.isRect(&rect, &isClosed, &direction) &&
134 isClosed &&
135 (SkPathDirection::kCW == direction ||
136 SkPathFillType::kEvenOdd == path.getFillType()))
137 {
138 SkPDFUtils::AppendRectangle(rect, content);
139 return;
140 }
141
142 enum SkipFillState {
143 kEmpty_SkipFillState,
144 kSingleLine_SkipFillState,
145 kNonSingleLine_SkipFillState,
146 };
147 SkipFillState fillState = kEmpty_SkipFillState;
148 //if (paintStyle != SkPaint::kFill_Style) {
149 // fillState = kNonSingleLine_SkipFillState;
150 //}
151 SkPoint lastMovePt = SkPoint::Make(0,0);
152 SkDynamicMemoryWStream currentSegment;
153 SkPoint args[4];
154 SkPath::Iter iter(path, false);
155 for (SkPath::Verb verb = iter.next(args);
156 verb != SkPath::kDone_Verb;
157 verb = iter.next(args)) {
158 // args gets all the points, even the implicit first point.
159 switch (verb) {
160 case SkPath::kMove_Verb:
161 MoveTo(args[0].fX, args[0].fY, ¤tSegment);
162 lastMovePt = args[0];
163 fillState = kEmpty_SkipFillState;
164 break;
165 case SkPath::kLine_Verb:
166 if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 2)) {
167 AppendLine(args[1].fX, args[1].fY, ¤tSegment);
168 if ((fillState == kEmpty_SkipFillState) && (args[0] != lastMovePt)) {
169 fillState = kSingleLine_SkipFillState;
170 break;
171 }
172 fillState = kNonSingleLine_SkipFillState;
173 }
174 break;
175 case SkPath::kQuad_Verb:
176 if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 3)) {
177 append_quad(args, ¤tSegment);
178 fillState = kNonSingleLine_SkipFillState;
179 }
180 break;
181 case SkPath::kConic_Verb:
182 if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 3)) {
183 SkAutoConicToQuads converter;
184 const SkPoint* quads = converter.computeQuads(args, iter.conicWeight(), tolerance);
185 for (int i = 0; i < converter.countQuads(); ++i) {
186 append_quad(&quads[i * 2], ¤tSegment);
187 }
188 fillState = kNonSingleLine_SkipFillState;
189 }
190 break;
191 case SkPath::kCubic_Verb:
192 if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 4)) {
193 append_cubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
194 args[3].fX, args[3].fY, ¤tSegment);
195 fillState = kNonSingleLine_SkipFillState;
196 }
197 break;
198 case SkPath::kClose_Verb:
199 ClosePath(¤tSegment);
200 currentSegment.writeToStream(content);
201 currentSegment.reset();
202 break;
203 default:
204 SkASSERT(false);
205 break;
206 }
207 }
208 if (currentSegment.bytesWritten() > 0) {
209 currentSegment.writeToStream(content);
210 }
211 }
212
ClosePath(SkWStream * content)213 void SkPDFUtils::ClosePath(SkWStream* content) {
214 content->writeText("h\n");
215 }
216
PaintPath(SkPaint::Style style,SkPathFillType fill,SkWStream * content)217 void SkPDFUtils::PaintPath(SkPaint::Style style, SkPathFillType fill, SkWStream* content) {
218 if (style == SkPaint::kFill_Style) {
219 content->writeText("f");
220 } else if (style == SkPaint::kStrokeAndFill_Style) {
221 content->writeText("B");
222 } else if (style == SkPaint::kStroke_Style) {
223 content->writeText("S");
224 }
225
226 if (style != SkPaint::kStroke_Style) {
227 NOT_IMPLEMENTED(fill == SkPathFillType::kInverseEvenOdd, false);
228 NOT_IMPLEMENTED(fill == SkPathFillType::kInverseWinding, false);
229 if (fill == SkPathFillType::kEvenOdd) {
230 content->writeText("*");
231 }
232 }
233 content->writeText("\n");
234 }
235
StrokePath(SkWStream * content)236 void SkPDFUtils::StrokePath(SkWStream* content) {
237 SkPDFUtils::PaintPath(SkPaint::kStroke_Style, SkPathFillType::kWinding, content);
238 }
239
ApplyGraphicState(int objectIndex,SkWStream * content)240 void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
241 SkPDFWriteResourceName(content, SkPDFResourceType::kExtGState, objectIndex);
242 content->writeText(" gs\n");
243 }
244
ApplyPattern(int objectIndex,SkWStream * content)245 void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
246 // Select Pattern color space (CS, cs) and set pattern object as current
247 // color (SCN, scn)
248 content->writeText("/Pattern CS/Pattern cs");
249 SkPDFWriteResourceName(content, SkPDFResourceType::kPattern, objectIndex);
250 content->writeText(" SCN");
251 SkPDFWriteResourceName(content, SkPDFResourceType::kPattern, objectIndex);
252 content->writeText(" scn\n");
253 }
254
255 // return "x/pow(10, places)", given 0<x<pow(10, places)
256 // result points to places+2 chars.
print_permil_as_decimal(int x,char * result,unsigned places)257 static size_t print_permil_as_decimal(int x, char* result, unsigned places) {
258 result[0] = '.';
259 for (int i = places; i > 0; --i) {
260 result[i] = '0' + x % 10;
261 x /= 10;
262 }
263 int j;
264 for (j = places; j > 1; --j) {
265 if (result[j] != '0') {
266 break;
267 }
268 }
269 result[j + 1] = '\0';
270 return j + 1;
271 }
272
273
int_pow(int base,unsigned exp,int acc=1)274 static constexpr int int_pow(int base, unsigned exp, int acc = 1) {
275 return exp < 1 ? acc
276 : int_pow(base * base,
277 exp / 2,
278 (exp % 2) ? acc * base : acc);
279 }
280
281
ColorToDecimalF(float value,char result[kFloatColorDecimalCount+2])282 size_t SkPDFUtils::ColorToDecimalF(float value, char result[kFloatColorDecimalCount + 2]) {
283 static constexpr int kFactor = int_pow(10, kFloatColorDecimalCount);
284 int x = sk_float_round2int(value * kFactor);
285 if (x >= kFactor || x <= 0) { // clamp to 0-1
286 result[0] = x > 0 ? '1' : '0';
287 result[1] = '\0';
288 return 1;
289 }
290 return print_permil_as_decimal(x, result, kFloatColorDecimalCount);
291 }
292
ColorToDecimal(uint8_t value,char result[5])293 size_t SkPDFUtils::ColorToDecimal(uint8_t value, char result[5]) {
294 if (value == 255 || value == 0) {
295 result[0] = value ? '1' : '0';
296 result[1] = '\0';
297 return 1;
298 }
299 // int x = 0.5 + (1000.0 / 255.0) * value;
300 int x = SkFixedRoundToInt((SK_Fixed1 * 1000 / 255) * value);
301 return print_permil_as_decimal(x, result, 3);
302 }
303
InverseTransformBBox(const SkMatrix & matrix,SkRect * bbox)304 bool SkPDFUtils::InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox) {
305 SkMatrix inverse;
306 if (!matrix.invert(&inverse)) {
307 return false;
308 }
309 inverse.mapRect(bbox);
310 return true;
311 }
312
PopulateTilingPatternDict(SkPDFDict * pattern,SkRect & bbox,std::unique_ptr<SkPDFDict> resources,const SkMatrix & matrix)313 void SkPDFUtils::PopulateTilingPatternDict(SkPDFDict* pattern,
314 SkRect& bbox,
315 std::unique_ptr<SkPDFDict> resources,
316 const SkMatrix& matrix) {
317 const int kTiling_PatternType = 1;
318 const int kColoredTilingPattern_PaintType = 1;
319 const int kConstantSpacing_TilingType = 1;
320
321 pattern->insertName("Type", "Pattern");
322 pattern->insertInt("PatternType", kTiling_PatternType);
323 pattern->insertInt("PaintType", kColoredTilingPattern_PaintType);
324 pattern->insertInt("TilingType", kConstantSpacing_TilingType);
325 pattern->insertObject("BBox", SkPDFUtils::RectToArray(bbox));
326 pattern->insertScalar("XStep", bbox.width());
327 pattern->insertScalar("YStep", bbox.height());
328 pattern->insertObject("Resources", std::move(resources));
329 if (!matrix.isIdentity()) {
330 pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix));
331 }
332 }
333
ToBitmap(const SkImage * img,SkBitmap * dst)334 bool SkPDFUtils::ToBitmap(const SkImage* img, SkBitmap* dst) {
335 SkASSERT(img);
336 SkASSERT(dst);
337 SkBitmap bitmap;
338 // TODO: support GPU images
339 if(as_IB(img)->getROPixels(nullptr, &bitmap)) {
340 SkASSERT(bitmap.dimensions() == img->dimensions());
341 SkASSERT(!bitmap.drawsNothing());
342 *dst = std::move(bitmap);
343 return true;
344 }
345 return false;
346 }
347
348 #ifdef SK_PDF_BASE85_BINARY
Base85Encode(std::unique_ptr<SkStreamAsset> stream,SkDynamicMemoryWStream * dst)349 void SkPDFUtils::Base85Encode(std::unique_ptr<SkStreamAsset> stream, SkDynamicMemoryWStream* dst) {
350 SkASSERT(dst);
351 SkASSERT(stream);
352 dst->writeText("\n");
353 int column = 0;
354 while (true) {
355 uint8_t src[4] = {0, 0, 0, 0};
356 size_t count = stream->read(src, 4);
357 SkASSERT(count < 5);
358 if (0 == count) {
359 dst->writeText("~>\n");
360 return;
361 }
362 uint32_t v = ((uint32_t)src[0] << 24) | ((uint32_t)src[1] << 16) |
363 ((uint32_t)src[2] << 8) | src[3];
364 if (v == 0 && count == 4) {
365 dst->writeText("z");
366 column += 1;
367 } else {
368 char buffer[5];
369 for (int n = 4; n > 0; --n) {
370 buffer[n] = (v % 85) + '!';
371 v /= 85;
372 }
373 buffer[0] = v + '!';
374 dst->write(buffer, count + 1);
375 column += count + 1;
376 }
377 if (column > 74) {
378 dst->writeText("\n");
379 column = 0;
380 }
381 }
382 }
383 #endif // SK_PDF_BASE85_BINARY
384
AppendTransform(const SkMatrix & matrix,SkWStream * content)385 void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) {
386 SkScalar values[6];
387 if (!matrix.asAffine(values)) {
388 SkMatrix::SetAffineIdentity(values);
389 }
390 for (SkScalar v : values) {
391 SkPDFUtils::AppendScalar(v, content);
392 content->writeText(" ");
393 }
394 content->writeText("cm\n");
395 }
396