• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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/SkPDFGradientShader.h"
9 
10 #include "include/core/SkPaint.h"
11 #include "include/core/SkPathTypes.h"
12 #include "include/core/SkSpan.h"
13 #include "include/core/SkStream.h"
14 #include "include/core/SkTileMode.h"
15 #include "include/private/base/SkTemplates.h"
16 #include "include/private/base/SkTo.h"
17 #include "src/core/SkChecksum.h"
18 #include "src/core/SkTHash.h"
19 #include "src/pdf/SkPDFDocumentPriv.h"
20 #include "src/pdf/SkPDFFormXObject.h"
21 #include "src/pdf/SkPDFGraphicState.h"
22 #include "src/pdf/SkPDFResourceDict.h"
23 #include "src/pdf/SkPDFTypes.h"
24 #include "src/pdf/SkPDFUtils.h"
25 
26 #include <cmath>
27 #include <cstddef>
28 #include <utility>
29 #include <vector>
30 
31 using namespace skia_private;
32 
hash(const SkShaderBase::GradientInfo & v)33 static uint32_t hash(const SkShaderBase::GradientInfo& v) {
34     uint32_t buffer[] = {
35         (uint32_t)v.fColorCount,
36         SkChecksum::Hash32(v.fColors, v.fColorCount * sizeof(SkColor)),
37         SkChecksum::Hash32(v.fColorOffsets, v.fColorCount * sizeof(SkScalar)),
38         SkChecksum::Hash32(v.fPoint, 2 * sizeof(SkPoint)),
39         SkChecksum::Hash32(v.fRadius, 2 * sizeof(SkScalar)),
40         (uint32_t)v.fTileMode,
41         v.fGradientFlags,
42     };
43     return SkChecksum::Hash32(buffer, sizeof(buffer));
44 }
45 
hash(const SkPDFGradientShader::Key & k)46 static uint32_t hash(const SkPDFGradientShader::Key& k) {
47     uint32_t buffer[] = {
48         (uint32_t)k.fType,
49         hash(k.fInfo),
50         SkChecksum::Hash32(&k.fCanvasTransform, sizeof(SkMatrix)),
51         SkChecksum::Hash32(&k.fShaderTransform, sizeof(SkMatrix)),
52         SkChecksum::Hash32(&k.fBBox, sizeof(SkIRect))
53     };
54     return SkChecksum::Hash32(buffer, sizeof(buffer));
55 }
56 
unit_to_points_matrix(const SkPoint pts[2],SkMatrix * matrix)57 static void unit_to_points_matrix(const SkPoint pts[2], SkMatrix* matrix) {
58     SkVector    vec = pts[1] - pts[0];
59     SkScalar    mag = vec.length();
60     SkScalar    inv = mag ? SkScalarInvert(mag) : 0;
61 
62     vec.scale(inv);
63     matrix->setSinCos(vec.fY, vec.fX);
64     matrix->preScale(mag, mag);
65     matrix->postTranslate(pts[0].fX, pts[0].fY);
66 }
67 
68 static const int kColorComponents = 3;
69 typedef uint8_t ColorTuple[kColorComponents];
70 
71 /* Assumes t - startOffset is on the stack and does a linear interpolation on t
72    between startOffset and endOffset from prevColor to curColor (for each color
73    component), leaving the result in component order on the stack. It assumes
74    there are always 3 components per color.
75    @param range       endOffset - startOffset
76    @param beginColor  The previous color.
77    @param endColor    The current color.
78    @param result      The result ps function.
79  */
interpolate_color_code(SkScalar range,SkColor beginColor,SkColor endColor,SkDynamicMemoryWStream * result)80 static void interpolate_color_code(SkScalar range, SkColor beginColor, SkColor endColor,
81                                    SkDynamicMemoryWStream* result) {
82     SkASSERT(range != SkIntToScalar(0));
83 
84     /* Linearly interpolate from the previous color to the current.
85        Scale the colors from 0..255 to 0..1 and determine the multipliers for interpolation.
86        C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}.
87      */
88 
89     ColorTuple curColor = { SkTo<uint8_t>(SkColorGetR(endColor)),
90                             SkTo<uint8_t>(SkColorGetG(endColor)),
91                             SkTo<uint8_t>(SkColorGetB(endColor)) };
92 
93     ColorTuple prevColor = { SkTo<uint8_t>(SkColorGetR(beginColor)),
94                              SkTo<uint8_t>(SkColorGetG(beginColor)),
95                              SkTo<uint8_t>(SkColorGetB(beginColor)) };
96 
97     // Figure out how to scale each color component.
98     SkScalar multiplier[kColorComponents];
99     for (int i = 0; i < kColorComponents; i++) {
100         static const SkScalar kColorScale = SkScalarInvert(255);
101         multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range;
102     }
103 
104     // Calculate when we no longer need to keep a copy of the input parameter t.
105     // If the last component to use t is i, then dupInput[0..i - 1] = true
106     // and dupInput[i .. components] = false.
107     bool dupInput[kColorComponents];
108     dupInput[kColorComponents - 1] = false;
109     for (int i = kColorComponents - 2; i >= 0; i--) {
110         dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
111     }
112 
113     if (!dupInput[0] && multiplier[0] == 0) {
114         result->writeText("pop ");
115     }
116 
117     for (int i = 0; i < kColorComponents; i++) {
118         // If the next components needs t and this component will consume a
119         // copy, make another copy.
120         if (dupInput[i] && multiplier[i] != 0) {
121             result->writeText("dup ");
122         }
123 
124         if (multiplier[i] == 0) {
125             SkPDFUtils::AppendColorComponent(prevColor[i], result);
126             result->writeText(" ");
127         } else {
128             if (multiplier[i] != 1) {
129                 SkPDFUtils::AppendScalar(multiplier[i], result);
130                 result->writeText(" mul ");
131             }
132             if (prevColor[i] != 0) {
133                 SkPDFUtils::AppendColorComponent(prevColor[i], result);
134                 result->writeText(" add ");
135             }
136         }
137 
138         if (dupInput[i]) {
139             result->writeText("exch ");
140         }
141     }
142 }
143 
write_gradient_ranges(const SkShaderBase::GradientInfo & info,SkSpan<size_t> rangeEnds,bool top,bool first,SkDynamicMemoryWStream * result)144 static void write_gradient_ranges(const SkShaderBase::GradientInfo& info, SkSpan<size_t> rangeEnds,
145                                   bool top, bool first, SkDynamicMemoryWStream* result) {
146     SkASSERT(!rangeEnds.empty());
147 
148     size_t rangeEndIndex = rangeEnds[rangeEnds.size() - 1];
149     SkScalar rangeEnd = info.fColorOffsets[rangeEndIndex];
150 
151     // Each range check tests 0 < t <= end.
152     if (top) {
153         SkASSERT(first);
154         // t may have been set to 0 to signal that the answer has already been found.
155         result->writeText("dup dup 0 gt exch ");  // In Preview 11.0 (1033.3) `0. 0 ne` is true.
156         SkPDFUtils::AppendScalar(rangeEnd, result);
157         result->writeText(" le and {\n");
158     } else if (first) {
159         // After the top level check, only t <= end needs to be tested on if (lo) side.
160         result->writeText("dup ");
161         SkPDFUtils::AppendScalar(rangeEnd, result);
162         result->writeText(" le {\n");
163     } else {
164         // The else (hi) side.
165         result->writeText("{\n");
166     }
167 
168     if (rangeEnds.size() == 1) {
169         // Set the stack to [r g b].
170         size_t rangeBeginIndex = rangeEndIndex - 1;
171         SkScalar rangeBegin = info.fColorOffsets[rangeBeginIndex];
172         SkPDFUtils::AppendScalar(rangeBegin, result);
173         result->writeText(" sub ");  // consume t, put t - startOffset on the stack.
174         interpolate_color_code(rangeEnd - rangeBegin,
175                                info.fColors[rangeBeginIndex], info.fColors[rangeEndIndex], result);
176         result->writeText("\n");
177     } else {
178         size_t loCount = rangeEnds.size() / 2;
179         SkSpan<size_t> loSpan = rangeEnds.subspan(0, loCount);
180         write_gradient_ranges(info, loSpan, false, true, result);
181 
182         SkSpan<size_t> hiSpan = rangeEnds.subspan(loCount, rangeEnds.size() - loCount);
183         write_gradient_ranges(info, hiSpan, false, false, result);
184     }
185 
186     if (top) {
187         // Put 0 on the stack for t once here instead of after every call to interpolate_color_code.
188         result->writeText("0} if\n");
189     } else if (first) {
190         result->writeText("}");  // The else (hi) side will come next.
191     } else {
192         result->writeText("} ifelse\n");
193     }
194 }
195 
196 /* Generate Type 4 function code to map t to the passed gradient, clamping at the ends.
197    The types integer, real, and boolean are available.
198    There are no string, array, procedure, variable, or name types available.
199 
200    The generated code will be of the following form with all values hard coded.
201 
202   if (t <= 0) {
203     ret = color[0];
204     t = 0;
205   }
206   if (t > 0 && t <= stop[4]) {
207     if (t <= stop[2]) {
208       if (t <= stop[1]) {
209         ret = interp(t - stop[0], stop[1] - stop[0], color[0], color[1]);
210       } else {
211         ret = interp(t - stop[1], stop[2] - stop[1], color[1], color[2]);
212       }
213     } else {
214       if (t <= stop[3] {
215         ret = interp(t - stop[2], stop[3] - stop[2], color[2], color[3]);
216       } else {
217         ret = interp(t - stop[3], stop[4] - stop[3], color[3], color[4]);
218       }
219     }
220     t = 0;
221   }
222   if (t > 0) {
223     ret = color[4];
224   }
225 
226    which in PDF will be represented like
227 
228   dup 0 le {pop 0 0 0 0} if
229   dup dup 0 gt exch 1 le and {
230     dup .5 le {
231       dup .25 le {
232         0 sub 2 mul 0 0
233       }{
234         .25 sub .5 exch 2 mul 0
235       } ifelse
236     }{
237       dup .75 le {
238         .5 sub .5 exch .5 exch 2 mul
239       }{
240         .75 sub dup 2 mul .5 add exch dup 2 mul .5 add exch 2 mul .5 add
241       } ifelse
242     } ifelse
243   0} if
244   0 gt {1 1 1} if
245  */
gradient_function_code(const SkShaderBase::GradientInfo & info,SkDynamicMemoryWStream * result)246 static void gradient_function_code(const SkShaderBase::GradientInfo& info,
247                                    SkDynamicMemoryWStream* result) {
248     // While looking for a hit the stack is [t].
249     // After finding a hit the stack is [r g b 0].
250     // The 0 is consumed just before returning.
251 
252     // The initial range has no previous and contains a solid color.
253     // Any t <= 0 will be handled by this initial range, so later t == 0 indicates a hit was found.
254     result->writeText("dup 0 le {pop ");
255     SkPDFUtils::AppendColorComponent(SkColorGetR(info.fColors[0]), result);
256     result->writeText(" ");
257     SkPDFUtils::AppendColorComponent(SkColorGetG(info.fColors[0]), result);
258     result->writeText(" ");
259     SkPDFUtils::AppendColorComponent(SkColorGetB(info.fColors[0]), result);
260     result->writeText(" 0} if\n");
261 
262     // Optimize out ranges which don't make any visual difference.
263     AutoSTMalloc<4, size_t> rangeEnds(info.fColorCount);
264     size_t rangeEndsCount = 0;
265     for (int i = 1; i < info.fColorCount; ++i) {
266         // Ignoring the alpha, is this range the same solid color as the next range?
267         // This optimizes gradients where sometimes only the color or only the alpha is changing.
268         auto eqIgnoringAlpha = [](SkColor a, SkColor b) {
269             return SkColorSetA(a, 0x00) == SkColorSetA(b, 0x00);
270         };
271         bool constantColorBothSides =
272             eqIgnoringAlpha(info.fColors[i-1], info.fColors[i]) &&// This range is a solid color.
273             i != info.fColorCount-1 &&                            // This is not the last range.
274             eqIgnoringAlpha(info.fColors[i], info.fColors[i+1]);  // Next range is same solid color.
275 
276         // Does this range have zero size?
277         bool degenerateRange = info.fColorOffsets[i-1] == info.fColorOffsets[i];
278 
279         if (!degenerateRange && !constantColorBothSides) {
280             rangeEnds[rangeEndsCount] = i;
281             ++rangeEndsCount;
282         }
283     }
284 
285     // If a cap on depth is needed, loop here.
286     write_gradient_ranges(info, SkSpan(rangeEnds.get(), rangeEndsCount), true, true, result);
287 
288     // Clamp the final color.
289     result->writeText("0 gt {");
290     SkPDFUtils::AppendColorComponent(SkColorGetR(info.fColors[info.fColorCount - 1]), result);
291     result->writeText(" ");
292     SkPDFUtils::AppendColorComponent(SkColorGetG(info.fColors[info.fColorCount - 1]), result);
293     result->writeText(" ");
294     SkPDFUtils::AppendColorComponent(SkColorGetB(info.fColors[info.fColorCount - 1]), result);
295     result->writeText("} if\n");
296 }
297 
createInterpolationFunction(const ColorTuple & color1,const ColorTuple & color2)298 static std::unique_ptr<SkPDFDict> createInterpolationFunction(const ColorTuple& color1,
299                                                     const ColorTuple& color2) {
300     auto retval = SkPDFMakeDict();
301 
302     auto c0 = SkPDFMakeArray();
303     c0->appendColorComponent(color1[0]);
304     c0->appendColorComponent(color1[1]);
305     c0->appendColorComponent(color1[2]);
306     retval->insertObject("C0", std::move(c0));
307 
308     auto c1 = SkPDFMakeArray();
309     c1->appendColorComponent(color2[0]);
310     c1->appendColorComponent(color2[1]);
311     c1->appendColorComponent(color2[2]);
312     retval->insertObject("C1", std::move(c1));
313 
314     retval->insertObject("Domain", SkPDFMakeArray(0, 1));
315 
316     retval->insertInt("FunctionType", 2);
317     retval->insertScalar("N", 1.0f);
318 
319     return retval;
320 }
321 
gradientStitchCode(const SkShaderBase::GradientInfo & info)322 static std::unique_ptr<SkPDFDict> gradientStitchCode(const SkShaderBase::GradientInfo& info) {
323     auto retval = SkPDFMakeDict();
324 
325     // normalize color stops
326     int colorCount = info.fColorCount;
327     std::vector<SkColor>  colors(info.fColors, info.fColors + colorCount);
328     std::vector<SkScalar> colorOffsets(info.fColorOffsets, info.fColorOffsets + colorCount);
329 
330     int i = 1;
331     while (i < colorCount - 1) {
332         // ensure stops are in order
333         if (colorOffsets[i - 1] > colorOffsets[i]) {
334             colorOffsets[i] = colorOffsets[i - 1];
335         }
336 
337         // remove points that are between 2 coincident points
338         if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) {
339             colorCount -= 1;
340             colors.erase(colors.begin() + i);
341             colorOffsets.erase(colorOffsets.begin() + i);
342         } else {
343             i++;
344         }
345     }
346     // find coincident points and slightly move them over
347     for (i = 1; i < colorCount - 1; i++) {
348         if (colorOffsets[i - 1] == colorOffsets[i]) {
349             colorOffsets[i] += 0.00001f;
350         }
351     }
352     // check if last 2 stops coincide
353     if (colorOffsets[i - 1] == colorOffsets[i]) {
354         colorOffsets[i - 1] -= 0.00001f;
355     }
356 
357     AutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount);
358     ColorTuple *colorData = colorDataAlloc.get();
359     for (int idx = 0; idx < colorCount; idx++) {
360         colorData[idx][0] = SkColorGetR(colors[idx]);
361         colorData[idx][1] = SkColorGetG(colors[idx]);
362         colorData[idx][2] = SkColorGetB(colors[idx]);
363     }
364 
365     // no need for a stitch function if there are only 2 stops.
366     if (colorCount == 2)
367         return createInterpolationFunction(colorData[0], colorData[1]);
368 
369     auto encode = SkPDFMakeArray();
370     auto bounds = SkPDFMakeArray();
371     auto functions = SkPDFMakeArray();
372 
373     retval->insertObject("Domain", SkPDFMakeArray(0, 1));
374     retval->insertInt("FunctionType", 3);
375 
376     for (int idx = 1; idx < colorCount; idx++) {
377         if (idx > 1) {
378             bounds->appendScalar(colorOffsets[idx-1]);
379         }
380 
381         encode->appendScalar(0);
382         encode->appendScalar(1.0f);
383 
384         functions->appendObject(createInterpolationFunction(colorData[idx-1], colorData[idx]));
385     }
386 
387     retval->insertObject("Encode", std::move(encode));
388     retval->insertObject("Bounds", std::move(bounds));
389     retval->insertObject("Functions", std::move(functions));
390 
391     return retval;
392 }
393 
394 /* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
tileModeCode(SkTileMode mode,SkDynamicMemoryWStream * result)395 static void tileModeCode(SkTileMode mode, SkDynamicMemoryWStream* result) {
396     if (mode == SkTileMode::kRepeat) {
397         result->writeText("dup truncate sub\n");  // Get the fractional part.
398         result->writeText("dup 0 le {1 add} if\n");  // Map (-1,0) => (0,1)
399         return;
400     }
401 
402     if (mode == SkTileMode::kMirror) {
403         // In Preview 11.0 (1033.3) `a n mod r eq` (with a and n both integers, r integer or real)
404         // early aborts the function when false would be put on the stack.
405         // Work around this by re-writing `t 2 mod 1 eq` as `t 2 mod 0 gt`.
406 
407         // Map t mod 2 into [0, 1, 1, 0].
408         //                Code                 Stack t
409         result->writeText("abs "                 // +t
410                           "dup "                 // +t.s +t.s
411                           "truncate "            // +t.s +t
412                           "dup "                 // +t.s +t +t
413                           "cvi "                 // +t.s +t +T
414                           "2 mod "               // +t.s +t (+T mod 2)
415               /*"1 eq "*/ "0 gt "                // +t.s +t true|false
416                           "3 1 roll "            // true|false +t.s +t
417                           "sub "                 // true|false 0.s
418                           "exch "                // 0.s true|false
419                           "{1 exch sub} if\n");  // 1 - 0.s|0.s
420     }
421 }
422 
423 /**
424  *  Returns PS function code that applies inverse perspective
425  *  to a x, y point.
426  *  The function assumes that the stack has at least two elements,
427  *  and that the top 2 elements are numeric values.
428  *  After executing this code on a PS stack, the last 2 elements are updated
429  *  while the rest of the stack is preserved intact.
430  *  inversePerspectiveMatrix is the inverse perspective matrix.
431  */
apply_perspective_to_coordinates(const SkMatrix & inversePerspectiveMatrix,SkDynamicMemoryWStream * code)432 static void apply_perspective_to_coordinates(const SkMatrix& inversePerspectiveMatrix,
433                                              SkDynamicMemoryWStream* code) {
434     if (!inversePerspectiveMatrix.hasPerspective()) {
435         return;
436     }
437 
438     // Perspective matrix should be:
439     // 1   0  0
440     // 0   1  0
441     // p0 p1 p2
442 
443     const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
444     const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
445     const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];
446 
447     // y = y / (p2 + p0 x + p1 y)
448     // x = x / (p2 + p0 x + p1 y)
449 
450     // Input on stack: x y
451     code->writeText(" dup ");             // x y y
452     SkPDFUtils::AppendScalar(p1, code);   // x y y p1
453     code->writeText(" mul "               // x y y*p1
454                     " 2 index ");         // x y y*p1 x
455     SkPDFUtils::AppendScalar(p0, code);   // x y y p1 x p0
456     code->writeText(" mul ");             // x y y*p1 x*p0
457     SkPDFUtils::AppendScalar(p2, code);   // x y y p1 x*p0 p2
458     code->writeText(" add "               // x y y*p1 x*p0+p2
459                     "add "                // x y y*p1+x*p0+p2
460                     "3 1 roll "           // y*p1+x*p0+p2 x y
461                     "2 index "            // z x y y*p1+x*p0+p2
462                     "div "                // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
463                     "3 1 roll "           // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
464                     "exch "               // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
465                     "div "                // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
466                     "exch\n");            // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
467 }
468 
linearCode(const SkShaderBase::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)469 static void linearCode(const SkShaderBase::GradientInfo& info,
470                        const SkMatrix& perspectiveRemover,
471                        SkDynamicMemoryWStream* function) {
472     function->writeText("{");
473 
474     apply_perspective_to_coordinates(perspectiveRemover, function);
475 
476     function->writeText("pop\n");  // Just ditch the y value.
477     tileModeCode((SkTileMode)info.fTileMode, function);
478     gradient_function_code(info, function);
479     function->writeText("}");
480 }
481 
radialCode(const SkShaderBase::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)482 static void radialCode(const SkShaderBase::GradientInfo& info,
483                        const SkMatrix& perspectiveRemover,
484                        SkDynamicMemoryWStream* function) {
485     function->writeText("{");
486 
487     apply_perspective_to_coordinates(perspectiveRemover, function);
488 
489     // Find the distance from the origin.
490     function->writeText("dup "      // x y y
491                     "mul "      // x y^2
492                     "exch "     // y^2 x
493                     "dup "      // y^2 x x
494                     "mul "      // y^2 x^2
495                     "add "      // y^2+x^2
496                     "sqrt\n");  // sqrt(y^2+x^2)
497 
498     tileModeCode((SkTileMode)info.fTileMode, function);
499     gradient_function_code(info, function);
500     function->writeText("}");
501 }
502 
503 /* Conical gradient shader, based on the Canvas spec for radial gradients
504    See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
505  */
twoPointConicalCode(const SkShaderBase::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)506 static void twoPointConicalCode(const SkShaderBase::GradientInfo& info,
507                                 const SkMatrix& perspectiveRemover,
508                                 SkDynamicMemoryWStream* function) {
509     SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
510     SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
511     SkScalar r0 = info.fRadius[0];
512     SkScalar dr = info.fRadius[1] - info.fRadius[0];
513     SkScalar a = dx * dx + dy * dy - dr * dr;
514 
515     // First compute t, if the pixel falls outside the cone, then we'll end
516     // with 'false' on the stack, otherwise we'll push 'true' with t below it
517 
518     // We start with a stack of (x y), copy it and then consume one copy in
519     // order to calculate b and the other to calculate c.
520     function->writeText("{");
521 
522     apply_perspective_to_coordinates(perspectiveRemover, function);
523 
524     function->writeText("2 copy ");
525 
526     // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
527     SkPDFUtils::AppendScalar(dy, function);
528     function->writeText(" mul exch ");
529     SkPDFUtils::AppendScalar(dx, function);
530     function->writeText(" mul add ");
531     SkPDFUtils::AppendScalar(r0 * dr, function);
532     function->writeText(" add -2 mul dup dup mul\n");
533 
534     // c = x^2 + y^2 + radius0^2
535     function->writeText("4 2 roll dup mul exch dup mul add ");
536     SkPDFUtils::AppendScalar(r0 * r0, function);
537     function->writeText(" sub dup 4 1 roll\n");
538 
539     // Contents of the stack at this point: c, b, b^2, c
540 
541     // if a = 0, then we collapse to a simpler linear case
542     if (a == 0) {
543 
544         // t = -c/b
545         function->writeText("pop pop div neg dup ");
546 
547         // compute radius(t)
548         SkPDFUtils::AppendScalar(dr, function);
549         function->writeText(" mul ");
550         SkPDFUtils::AppendScalar(r0, function);
551         function->writeText(" add\n");
552 
553         // if r(t) < 0, then it's outside the cone
554         function->writeText("0 lt {pop false} {true} ifelse\n");
555 
556     } else {
557 
558         // quadratic case: the Canvas spec wants the largest
559         // root t for which radius(t) > 0
560 
561         // compute the discriminant (b^2 - 4ac)
562         SkPDFUtils::AppendScalar(a * 4, function);
563         function->writeText(" mul sub dup\n");
564 
565         // if d >= 0, proceed
566         function->writeText("0 ge {\n");
567 
568         // an intermediate value we'll use to compute the roots:
569         // q = -0.5 * (b +/- sqrt(d))
570         function->writeText("sqrt exch dup 0 lt {exch -1 mul} if");
571         function->writeText(" add -0.5 mul dup\n");
572 
573         // first root = q / a
574         SkPDFUtils::AppendScalar(a, function);
575         function->writeText(" div\n");
576 
577         // second root = c / q
578         function->writeText("3 1 roll div\n");
579 
580         // put the larger root on top of the stack
581         function->writeText("2 copy gt {exch} if\n");
582 
583         // compute radius(t) for larger root
584         function->writeText("dup ");
585         SkPDFUtils::AppendScalar(dr, function);
586         function->writeText(" mul ");
587         SkPDFUtils::AppendScalar(r0, function);
588         function->writeText(" add\n");
589 
590         // if r(t) > 0, we have our t, pop off the smaller root and we're done
591         function->writeText(" 0 gt {exch pop true}\n");
592 
593         // otherwise, throw out the larger one and try the smaller root
594         function->writeText("{pop dup\n");
595         SkPDFUtils::AppendScalar(dr, function);
596         function->writeText(" mul ");
597         SkPDFUtils::AppendScalar(r0, function);
598         function->writeText(" add\n");
599 
600         // if r(t) < 0, push false, otherwise the smaller root is our t
601         function->writeText("0 le {pop false} {true} ifelse\n");
602         function->writeText("} ifelse\n");
603 
604         // d < 0, clear the stack and push false
605         function->writeText("} {pop pop pop false} ifelse\n");
606     }
607 
608     // if the pixel is in the cone, proceed to compute a color
609     function->writeText("{");
610     tileModeCode((SkTileMode)info.fTileMode, function);
611     gradient_function_code(info, function);
612 
613     // otherwise, just write black
614     // TODO: Correctly draw gradients_local_persepective, need to mask out this black
615     // The "gradients" gm works as falls into the 8.7.4.5.4 "Type 3 (Radial) Shadings" case.
616     function->writeText("} {0 0 0} ifelse }");
617 }
618 
sweepCode(const SkShaderBase::GradientInfo & info,const SkMatrix & perspectiveRemover,SkDynamicMemoryWStream * function)619 static void sweepCode(const SkShaderBase::GradientInfo& info,
620                       const SkMatrix& perspectiveRemover,
621                       SkDynamicMemoryWStream* function) {
622     // TODO: Correctly draw tilemode_gradient, requires access to the "bias" and "scale"
623     function->writeText("{exch atan 360 div\n");
624     tileModeCode((SkTileMode)info.fTileMode, function);
625     gradient_function_code(info, function);
626     function->writeText("}");
627 }
628 
629 
630 // catch cases where the inner just touches the outer circle
631 // and make the inner circle just inside the outer one to match raster
FixUpRadius(const SkPoint & p1,SkScalar & r1,const SkPoint & p2,SkScalar & r2)632 static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) {
633     // detect touching circles
634     SkScalar distance = SkPoint::Distance(p1, p2);
635     SkScalar subtractRadii = fabs(r1 - r2);
636     if (fabs(distance - subtractRadii) < 0.002f) {
637         if (r1 > r2) {
638             r1 += 0.002f;
639         } else {
640             r2 += 0.002f;
641         }
642     }
643 }
644 
645 // Finds affine and persp such that in = affine * persp.
646 // but it returns the inverse of perspective matrix.
split_perspective(const SkMatrix in,SkMatrix * affine,SkMatrix * perspectiveInverse)647 static bool split_perspective(const SkMatrix in, SkMatrix* affine,
648                               SkMatrix* perspectiveInverse) {
649     const SkScalar p2 = in[SkMatrix::kMPersp2];
650 
651     if (SkScalarNearlyZero(p2)) {
652         return false;
653     }
654 
655     const SkScalar zero = SkIntToScalar(0);
656     const SkScalar one = SkIntToScalar(1);
657 
658     const SkScalar sx = in[SkMatrix::kMScaleX];
659     const SkScalar kx = in[SkMatrix::kMSkewX];
660     const SkScalar tx = in[SkMatrix::kMTransX];
661     const SkScalar ky = in[SkMatrix::kMSkewY];
662     const SkScalar sy = in[SkMatrix::kMScaleY];
663     const SkScalar ty = in[SkMatrix::kMTransY];
664     const SkScalar p0 = in[SkMatrix::kMPersp0];
665     const SkScalar p1 = in[SkMatrix::kMPersp1];
666 
667     // Perspective matrix would be:
668     // 1  0  0
669     // 0  1  0
670     // p0 p1 p2
671     // But we need the inverse of persp.
672     perspectiveInverse->setAll(one,          zero,       zero,
673                                zero,         one,        zero,
674                                -p0/p2,     -p1/p2,     1/p2);
675 
676     affine->setAll(sx - p0 * tx / p2,       kx - p1 * tx / p2,      tx / p2,
677                    ky - p0 * ty / p2,       sy - p1 * ty / p2,      ty / p2,
678                    zero,                    zero,                   one);
679 
680     return true;
681 }
682 
make_ps_function(std::unique_ptr<SkStreamAsset> psCode,std::unique_ptr<SkPDFArray> domain,std::unique_ptr<SkPDFObject> range,SkPDFDocument * doc)683 static SkPDFIndirectReference make_ps_function(std::unique_ptr<SkStreamAsset> psCode,
684                                                std::unique_ptr<SkPDFArray> domain,
685                                                std::unique_ptr<SkPDFObject> range,
686                                                SkPDFDocument* doc) {
687     std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
688     dict->insertInt("FunctionType", 4);
689     dict->insertObject("Domain", std::move(domain));
690     dict->insertObject("Range", std::move(range));
691     return SkPDFStreamOut(std::move(dict), std::move(psCode), doc);
692 }
693 
make_function_shader(SkPDFDocument * doc,const SkPDFGradientShader::Key & state)694 static SkPDFIndirectReference make_function_shader(SkPDFDocument* doc,
695                                                    const SkPDFGradientShader::Key& state) {
696     SkPoint transformPoints[2];
697     const SkShaderBase::GradientInfo& info = state.fInfo;
698     SkMatrix finalMatrix = state.fCanvasTransform;
699     finalMatrix.preConcat(state.fShaderTransform);
700 
701     bool doStitchFunctions = (state.fType == SkShaderBase::GradientType::kLinear ||
702                               state.fType == SkShaderBase::GradientType::kRadial ||
703                               state.fType == SkShaderBase::GradientType::kConical) &&
704                              (info.fTileMode == SkTileMode::kClamp ||
705                               info.fTileMode == SkTileMode::kDecal) &&
706                              !finalMatrix.hasPerspective();
707 
708     enum class ShadingType : int32_t {
709         Function = 1,
710         Axial = 2,
711         Radial = 3,
712         FreeFormGouraudTriangleMesh = 4,
713         LatticeFormGouraudTriangleMesh = 5,
714         CoonsPatchMesh = 6,
715         TensorProductPatchMesh = 7,
716     } shadingType;
717 
718     auto pdfShader = SkPDFMakeDict();
719     if (doStitchFunctions) {
720         pdfShader->insertObject("Function", gradientStitchCode(info));
721 
722         if (info.fTileMode == SkTileMode::kClamp) {
723             auto extend = SkPDFMakeArray();
724             extend->reserve(2);
725             extend->appendBool(true);
726             extend->appendBool(true);
727             pdfShader->insertObject("Extend", std::move(extend));
728         }
729 
730         std::unique_ptr<SkPDFArray> coords;
731         switch (state.fType) {
732             case SkShaderBase::GradientType::kLinear: {
733                 shadingType = ShadingType::Axial;
734                 const SkPoint& pt1 = info.fPoint[0];
735                 const SkPoint& pt2 = info.fPoint[1];
736                 coords = SkPDFMakeArray(pt1.x(), pt1.y(),
737                                         pt2.x(), pt2.y());
738             } break;
739             case SkShaderBase::GradientType::kRadial: {
740                 shadingType = ShadingType::Radial;
741                 const SkPoint& pt1 = info.fPoint[0];
742                 coords = SkPDFMakeArray(pt1.x(), pt1.y(), 0,
743                                         pt1.x(), pt1.y(), info.fRadius[0]);
744             } break;
745             case SkShaderBase::GradientType::kConical: {
746                 shadingType = ShadingType::Radial;
747                 SkScalar r1 = info.fRadius[0];
748                 SkScalar r2 = info.fRadius[1];
749                 SkPoint pt1 = info.fPoint[0];
750                 SkPoint pt2 = info.fPoint[1];
751                 FixUpRadius(pt1, r1, pt2, r2);
752 
753                 coords = SkPDFMakeArray(pt1.x(), pt1.y(), r1,
754                                         pt2.x(), pt2.y(), r2);
755                 break;
756             }
757             case SkShaderBase::GradientType::kSweep:
758             case SkShaderBase::GradientType::kNone:
759             default:
760                 SkASSERT(false);
761                 return SkPDFIndirectReference();
762         }
763         pdfShader->insertObject("Coords", std::move(coords));
764     } else {
765         shadingType = ShadingType::Function;
766 
767         // Transform the coordinate space for the type of gradient.
768         transformPoints[0] = info.fPoint[0];
769         transformPoints[1] = info.fPoint[1];
770         switch (state.fType) {
771             case SkShaderBase::GradientType::kLinear:
772                 break;
773             case SkShaderBase::GradientType::kRadial:
774                 transformPoints[1] = transformPoints[0];
775                 transformPoints[1].fX += info.fRadius[0];
776                 break;
777             case SkShaderBase::GradientType::kConical: {
778                 transformPoints[1] = transformPoints[0];
779                 transformPoints[1].fX += SK_Scalar1;
780                 break;
781             }
782             case SkShaderBase::GradientType::kSweep:
783                 transformPoints[1] = transformPoints[0];
784                 transformPoints[1].fX += SK_Scalar1;
785                 break;
786             case SkShaderBase::GradientType::kNone:
787             default:
788                 return SkPDFIndirectReference();
789         }
790 
791         // Move any scaling (assuming a unit gradient) or translation
792         // (and rotation for linear gradient), of the final gradient from
793         // info.fPoints to the matrix (updating bbox appropriately).  Now
794         // the gradient can be drawn on on the unit segment.
795         SkMatrix mapperMatrix;
796         unit_to_points_matrix(transformPoints, &mapperMatrix);
797 
798         finalMatrix.preConcat(mapperMatrix);
799 
800         // Preserves as much as possible in the final matrix, and only removes
801         // the perspective. The inverse of the perspective is stored in
802         // perspectiveInverseOnly matrix and has 3 useful numbers
803         // (p0, p1, p2), while everything else is either 0 or 1.
804         // In this way the shader will handle it eficiently, with minimal code.
805         SkMatrix perspectiveInverseOnly = SkMatrix::I();
806         if (finalMatrix.hasPerspective()) {
807             if (!split_perspective(finalMatrix,
808                                    &finalMatrix, &perspectiveInverseOnly)) {
809                 return SkPDFIndirectReference();
810             }
811         }
812 
813         SkRect bbox;
814         bbox.set(state.fBBox);
815         if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) {
816             return SkPDFIndirectReference();
817         }
818 
819         SkDynamicMemoryWStream functionCode;
820         switch (state.fType) {
821             case SkShaderBase::GradientType::kLinear:
822                 linearCode(info, perspectiveInverseOnly, &functionCode);
823                 break;
824             case SkShaderBase::GradientType::kRadial:
825                 radialCode(info, perspectiveInverseOnly, &functionCode);
826                 break;
827             case SkShaderBase::GradientType::kConical: {
828                 // The two point radial gradient further references state.fInfo
829                 // in translating from x, y coordinates to the t parameter. So, we have
830                 // to transform the points and radii according to the calculated matrix.
831                 SkShaderBase::GradientInfo infoCopy = info;
832                 SkMatrix inverseMapperMatrix;
833                 if (!mapperMatrix.invert(&inverseMapperMatrix)) {
834                     return SkPDFIndirectReference();
835                 }
836                 inverseMapperMatrix.mapPoints(infoCopy.fPoint, 2);
837                 infoCopy.fRadius[0] = inverseMapperMatrix.mapRadius(info.fRadius[0]);
838                 infoCopy.fRadius[1] = inverseMapperMatrix.mapRadius(info.fRadius[1]);
839                 twoPointConicalCode(infoCopy, perspectiveInverseOnly, &functionCode);
840             } break;
841             case SkShaderBase::GradientType::kSweep:
842                 sweepCode(info, perspectiveInverseOnly, &functionCode);
843                 break;
844             default:
845                 SkASSERT(false);
846         }
847         pdfShader->insertObject(
848                 "Domain", SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom()));
849 
850         auto domain = SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom());
851         std::unique_ptr<SkPDFArray> rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1);
852         pdfShader->insertRef("Function",
853                              make_ps_function(functionCode.detachAsStream(), std::move(domain),
854                                               std::move(rangeObject), doc));
855     }
856 
857     pdfShader->insertInt("ShadingType", SkToS32(shadingType));
858     pdfShader->insertName("ColorSpace", "DeviceRGB");
859 
860     SkPDFDict pdfFunctionShader("Pattern");
861     pdfFunctionShader.insertInt("PatternType", 2);
862     pdfFunctionShader.insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix));
863     pdfFunctionShader.insertObject("Shading", std::move(pdfShader));
864     return doc->emit(pdfFunctionShader);
865 }
866 
867 static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
868                                               SkPDFGradientShader::Key key,
869                                               bool keyHasAlpha);
870 
get_gradient_resource_dict(SkPDFIndirectReference functionShader,SkPDFIndirectReference gState)871 static std::unique_ptr<SkPDFDict> get_gradient_resource_dict(SkPDFIndirectReference functionShader,
872                                                    SkPDFIndirectReference gState) {
873     std::vector<SkPDFIndirectReference> patternShaders;
874     if (functionShader != SkPDFIndirectReference()) {
875         patternShaders.push_back(functionShader);
876     }
877     std::vector<SkPDFIndirectReference> graphicStates;
878     if (gState != SkPDFIndirectReference()) {
879         graphicStates.push_back(gState);
880     }
881     return SkPDFMakeResourceDict(std::move(graphicStates),
882                                  std::move(patternShaders),
883                                  std::vector<SkPDFIndirectReference>(),
884                                  std::vector<SkPDFIndirectReference>());
885 }
886 
887 // Creates a content stream which fills the pattern P0 across bounds.
888 // @param gsIndex A graphics state resource index to apply, or <0 if no
889 // graphics state to apply.
create_pattern_fill_content(int gsIndex,int patternIndex,SkRect & bounds)890 static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex,
891                                                                   int patternIndex,
892                                                                   SkRect& bounds) {
893     SkDynamicMemoryWStream content;
894     if (gsIndex >= 0) {
895         SkPDFUtils::ApplyGraphicState(gsIndex, &content);
896     }
897     SkPDFUtils::ApplyPattern(patternIndex, &content);
898     SkPDFUtils::AppendRectangle(bounds, &content);
899     SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPathFillType::kEvenOdd, &content);
900     return content.detachAsStream();
901 }
902 
gradient_has_alpha(const SkPDFGradientShader::Key & key)903 static bool gradient_has_alpha(const SkPDFGradientShader::Key& key) {
904     SkASSERT(key.fType != SkShaderBase::GradientType::kNone);
905     for (int i = 0; i < key.fInfo.fColorCount; i++) {
906         if ((SkAlpha)SkColorGetA(key.fInfo.fColors[i]) != SK_AlphaOPAQUE) {
907             return true;
908         }
909     }
910     return false;
911 }
912 
913 // warning: does not set fHash on new key.  (Both callers need to change fields.)
clone_key(const SkPDFGradientShader::Key & k)914 static SkPDFGradientShader::Key clone_key(const SkPDFGradientShader::Key& k) {
915     SkPDFGradientShader::Key clone = {
916         k.fType,
917         k.fInfo,  // change pointers later.
918         std::unique_ptr<SkColor[]>(new SkColor[k.fInfo.fColorCount]),
919         std::unique_ptr<SkScalar[]>(new SkScalar[k.fInfo.fColorCount]),
920         k.fCanvasTransform,
921         k.fShaderTransform,
922         k.fBBox, 0};
923     clone.fInfo.fColors = clone.fColors.get();
924     clone.fInfo.fColorOffsets = clone.fStops.get();
925     for (int i = 0; i < clone.fInfo.fColorCount; i++) {
926         clone.fInfo.fColorOffsets[i] = k.fInfo.fColorOffsets[i];
927         clone.fInfo.fColors[i] = k.fInfo.fColors[i];
928     }
929     return clone;
930 }
931 
create_smask_graphic_state(SkPDFDocument * doc,const SkPDFGradientShader::Key & state)932 static SkPDFIndirectReference create_smask_graphic_state(SkPDFDocument* doc,
933                                                      const SkPDFGradientShader::Key& state) {
934     SkASSERT(state.fType != SkShaderBase::GradientType::kNone);
935     SkPDFGradientShader::Key luminosityState = clone_key(state);
936     for (int i = 0; i < luminosityState.fInfo.fColorCount; i++) {
937         SkAlpha alpha = SkColorGetA(luminosityState.fInfo.fColors[i]);
938         luminosityState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
939     }
940     luminosityState.fHash = hash(luminosityState);
941 
942     SkASSERT(!gradient_has_alpha(luminosityState));
943     SkPDFIndirectReference luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false);
944     std::unique_ptr<SkPDFDict> resources = get_gradient_resource_dict(luminosityShader,
945                                                             SkPDFIndirectReference());
946     SkRect bbox = SkRect::Make(state.fBBox);
947     SkPDFIndirectReference alphaMask =
948             SkPDFMakeFormXObject(doc,
949                                  create_pattern_fill_content(-1, luminosityShader.fValue, bbox),
950                                  SkPDFUtils::RectToArray(bbox),
951                                  std::move(resources),
952                                  SkMatrix::I(),
953                                  "DeviceRGB");
954     return SkPDFGraphicState::GetSMaskGraphicState(
955             alphaMask, false, SkPDFGraphicState::kLuminosity_SMaskMode, doc);
956 }
957 
make_alpha_function_shader(SkPDFDocument * doc,const SkPDFGradientShader::Key & state)958 static SkPDFIndirectReference make_alpha_function_shader(SkPDFDocument* doc,
959                                                          const SkPDFGradientShader::Key& state) {
960     SkASSERT(state.fType != SkShaderBase::GradientType::kNone);
961     SkPDFGradientShader::Key opaqueState = clone_key(state);
962     for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) {
963         opaqueState.fInfo.fColors[i] = SkColorSetA(opaqueState.fInfo.fColors[i], SK_AlphaOPAQUE);
964     }
965     opaqueState.fHash = hash(opaqueState);
966 
967     SkASSERT(!gradient_has_alpha(opaqueState));
968     SkRect bbox = SkRect::Make(state.fBBox);
969     SkPDFIndirectReference colorShader = find_pdf_shader(doc, std::move(opaqueState), false);
970     if (!colorShader) {
971         return SkPDFIndirectReference();
972     }
973     // Create resource dict with alpha graphics state as G0 and
974     // pattern shader as P0, then write content stream.
975     SkPDFIndirectReference alphaGsRef = create_smask_graphic_state(doc, state);
976 
977     std::unique_ptr<SkPDFDict> resourceDict = get_gradient_resource_dict(colorShader, alphaGsRef);
978 
979     std::unique_ptr<SkStreamAsset> colorStream =
980             create_pattern_fill_content(alphaGsRef.fValue, colorShader.fValue, bbox);
981     std::unique_ptr<SkPDFDict> alphaFunctionShader = SkPDFMakeDict();
982     SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader.get(), bbox,
983                                  std::move(resourceDict), SkMatrix::I());
984     return SkPDFStreamOut(std::move(alphaFunctionShader), std::move(colorStream), doc);
985 }
986 
make_key(const SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & bbox)987 static SkPDFGradientShader::Key make_key(const SkShader* shader,
988                                          const SkMatrix& canvasTransform,
989                                          const SkIRect& bbox) {
990     SkPDFGradientShader::Key key = {
991          SkShaderBase::GradientType::kNone,
992          {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0}, SkTileMode::kClamp, 0},
993          nullptr,
994          nullptr,
995          canvasTransform,
996          SkPDFUtils::GetShaderLocalMatrix(shader),
997          bbox, 0};
998     key.fType = as_SB(shader)->asGradient(&key.fInfo);
999     SkASSERT(SkShaderBase::GradientType::kNone != key.fType);
1000     SkASSERT(key.fInfo.fColorCount > 0);
1001     key.fColors.reset(new SkColor[key.fInfo.fColorCount]);
1002     key.fStops.reset(new SkScalar[key.fInfo.fColorCount]);
1003     key.fInfo.fColors = key.fColors.get();
1004     key.fInfo.fColorOffsets = key.fStops.get();
1005     as_SB(shader)->asGradient(&key.fInfo);
1006     key.fHash = hash(key);
1007     return key;
1008 }
1009 
find_pdf_shader(SkPDFDocument * doc,SkPDFGradientShader::Key key,bool keyHasAlpha)1010 static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
1011                                               SkPDFGradientShader::Key key,
1012                                               bool keyHasAlpha) {
1013     SkASSERT(gradient_has_alpha(key) == keyHasAlpha);
1014     auto& gradientPatternMap = doc->fGradientPatternMap;
1015     if (SkPDFIndirectReference* ptr = gradientPatternMap.find(key)) {
1016         return *ptr;
1017     }
1018     SkPDFIndirectReference pdfShader;
1019     if (keyHasAlpha) {
1020         pdfShader = make_alpha_function_shader(doc, key);
1021     } else {
1022         pdfShader = make_function_shader(doc, key);
1023     }
1024     gradientPatternMap.set(std::move(key), pdfShader);
1025     return pdfShader;
1026 }
1027 
Make(SkPDFDocument * doc,SkShader * shader,const SkMatrix & canvasTransform,const SkIRect & bbox)1028 SkPDFIndirectReference SkPDFGradientShader::Make(SkPDFDocument* doc,
1029                                              SkShader* shader,
1030                                              const SkMatrix& canvasTransform,
1031                                              const SkIRect& bbox) {
1032     SkASSERT(shader);
1033     SkASSERT(as_SB(shader)->asGradient() != SkShaderBase::GradientType::kNone);
1034     SkPDFGradientShader::Key key = make_key(shader, canvasTransform, bbox);
1035     bool alpha = gradient_has_alpha(key);
1036     return find_pdf_shader(doc, std::move(key), alpha);
1037 }
1038