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, &currentSegment);
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, &currentSegment);
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, &currentSegment);
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], &currentSegment);
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, &currentSegment);
194                     fillState = kNonSingleLine_SkipFillState;
195                 }
196                 break;
197             case SkPath::kClose_Verb:
198                 ClosePath(&currentSegment);
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