2 * Copyright 2011 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "src/pdf/SkPDFUtils.h"
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"
23 const char* SkPDFUtils::BlendModeName(SkBlendMode mode) {
24 // PDF32000.book section 11.3.5 "Blend 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;
49 std::unique_ptr<SkPDFArray> SkPDFUtils::RectToArray(const SkRect& r) {
50 return SkPDFMakeArray(r.left(), r.top(), r.right(), r.bottom());
53 std::unique_ptr<SkPDFArray> SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
55 if (!matrix.asAffine(a)) {
56 SkMatrix::SetAffineIdentity(a);
58 return SkPDFMakeArray(a[0], a[1], a[2], a[3], a[4], a[5]);
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");
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");
75 static void append_cubic(SkScalar ctl1X, SkScalar ctl1Y,
76 SkScalar ctl2X, SkScalar ctl2Y,
77 SkScalar dstX, SkScalar dstY, SkWStream* content) {
79 SkPDFUtils::AppendScalar(ctl1X, content);
80 content->writeText(" ");
81 SkPDFUtils::AppendScalar(ctl1Y, content);
82 content->writeText(" ");
83 if (ctl2X != dstX || ctl2Y != dstY) {
85 SkPDFUtils::AppendScalar(ctl2X, content);
86 content->writeText(" ");
87 SkPDFUtils::AppendScalar(ctl2Y, content);
88 content->writeText(" ");
90 SkPDFUtils::AppendScalar(dstX, content);
91 content->writeText(" ");
92 SkPDFUtils::AppendScalar(dstY, content);
93 content->writeText(" ");
94 content->writeText(cmd.c_str());
97 static void append_quad(const SkPoint quad[], SkWStream* content) {
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);
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);
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");
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);
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).
131 bool isClosed; // Both closure and direction need to be checked.
132 SkPathDirection direction;
133 if (path.isRect(&rect, &isClosed, &direction) &&
135 (SkPathDirection::kCW == direction ||
136 SkPathFillType::kEvenOdd == path.getFillType()))
138 SkPDFUtils::AppendRectangle(rect, content);
143 kEmpty_SkipFillState,
144 kSingleLine_SkipFillState,
145 kNonSingleLine_SkipFillState,
147 SkipFillState fillState = kEmpty_SkipFillState;
148 //if (paintStyle != SkPaint::kFill_Style) {
149 // fillState = kNonSingleLine_SkipFillState;
151 SkPoint lastMovePt = SkPoint::Make(0,0);
152 SkDynamicMemoryWStream currentSegment;
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.
160 case SkPath::kMove_Verb:
161 MoveTo(args[0].fX, args[0].fY, ¤tSegment);
162 lastMovePt = args[0];
163 fillState = kEmpty_SkipFillState;
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;
172 fillState = kNonSingleLine_SkipFillState;
175 case SkPath::kQuad_Verb:
176 if (!doConsumeDegerates || !SkPathPriv::AllPointsEq(args, 3)) {
177 append_quad(args, ¤tSegment);
178 fillState = kNonSingleLine_SkipFillState;
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);
188 fillState = kNonSingleLine_SkipFillState;
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;
198 case SkPath::kClose_Verb:
199 ClosePath(¤tSegment);
200 currentSegment.writeToStream(content);
201 currentSegment.reset();
208 if (currentSegment.bytesWritten() > 0) {
209 currentSegment.writeToStream(content);
213 void SkPDFUtils::ClosePath(SkWStream* content) {
214 content->writeText("h\n");
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");
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("*");
233 content->writeText("\n");
236 void SkPDFUtils::StrokePath(SkWStream* content) {
237 SkPDFUtils::PaintPath(SkPaint::kStroke_Style, SkPathFillType::kWinding, content);
240 void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
241 SkPDFWriteResourceName(content, SkPDFResourceType::kExtGState, objectIndex);
242 content->writeText(" gs\n");
245 void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
246 // Select Pattern color space (CS, cs) and set pattern object as current
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");
255 // return "x/pow(10, places)", given 0<x<pow(10, places)
256 // result points to places+2 chars.
257 static size_t print_permil_as_decimal(int x, char* result, unsigned places) {
259 for (int i = places; i > 0; --i) {
260 result[i] = '0' + x % 10;
264 for (j = places; j > 1; --j) {
265 if (result[j] != '0') {
269 result[j + 1] = '\0';
274 static constexpr int int_pow(int base, unsigned exp, int acc = 1) {
276 : int_pow(base * base,
278 (exp % 2) ? acc * base : acc);
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';
290 return print_permil_as_decimal(x, result, kFloatColorDecimalCount);
293 size_t SkPDFUtils::ColorToDecimal(uint8_t value, char result[5]) {
294 if (value == 255 || value == 0) {
295 result[0] = value ? '1' : '0';
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);
304 bool SkPDFUtils::InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox) {
306 if (!matrix.invert(&inverse)) {
309 inverse.mapRect(bbox);
313 void SkPDFUtils::PopulateTilingPatternDict(SkPDFDict* pattern,
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;
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));
334 bool SkPDFUtils::ToBitmap(const SkImage* img, SkBitmap* dst) {
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);
348 #ifdef SK_PDF_BASE85_BINARY
349 void SkPDFUtils::Base85Encode(std::unique_ptr<SkStreamAsset> stream, SkDynamicMemoryWStream* dst) {
352 dst->writeText("\n");
355 uint8_t src[4] = {0, 0, 0, 0};
356 size_t count = stream->read(src, 4);
359 dst->writeText("~>\n");
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) {
369 for (int n = 4; n > 0; --n) {
370 buffer[n] = (v % 85) + '!';
374 dst->write(buffer, count + 1);
378 dst->writeText("\n");
383 #endif // SK_PDF_BASE85_BINARY
385 void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) {
387 if (!matrix.asAffine(values)) {
388 SkMatrix::SetAffineIdentity(values);
390 for (SkScalar v : values) {
391 SkPDFUtils::AppendScalar(v, content);
392 content->writeText(" ");
394 content->writeText("cm\n");