xref: /aosp_15_r20/external/skia/src/pdf/SkPDFUtils.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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, &currentSegment);
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, &currentSegment);
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, &currentSegment);
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], &currentSegment);
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, &currentSegment);
209                     fillState = kNonSingleLine_SkipFillState;
210                 }
211                 break;
212             case SkPath::kClose_Verb:
213                 ClosePath(&currentSegment);
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