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