• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &currentSegment);
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, &currentSegment);
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, &currentSegment);
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], &currentSegment);
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, &currentSegment);
195                     fillState = kNonSingleLine_SkipFillState;
196                 }
197                 break;
198             case SkPath::kClose_Verb:
199                 ClosePath(&currentSegment);
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