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