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 "SkData.h"
9#include "SkPaint.h"
10#include "SkPDFCanon.h"
11#include "SkPDFFormXObject.h"
12#include "SkPDFGraphicState.h"
13#include "SkPDFUtils.h"
14
15static const char* as_pdf_blend_mode_name(SkBlendMode mode) {
16    // PDF32000.book section 11.3.5 "Blend Mode"
17    switch (mode) {
18        case SkBlendMode::kScreen:      return "Screen";
19        case SkBlendMode::kOverlay:     return "Overlay";
20        case SkBlendMode::kDarken:      return "Darken";
21        case SkBlendMode::kLighten:     return "Lighten";
22        case SkBlendMode::kColorDodge:  return "ColorDodge";
23        case SkBlendMode::kColorBurn:   return "ColorBurn";
24        case SkBlendMode::kHardLight:   return "HardLight";
25        case SkBlendMode::kSoftLight:   return "SoftLight";
26        case SkBlendMode::kDifference:  return "Difference";
27        case SkBlendMode::kExclusion:   return "Exclusion";
28        case SkBlendMode::kMultiply:    return "Multiply";
29        case SkBlendMode::kHue:         return "Hue";
30        case SkBlendMode::kSaturation:  return "Saturation";
31        case SkBlendMode::kColor:       return "Color";
32        case SkBlendMode::kLuminosity:  return "Luminosity";
33        // Other blendmodes are either unsupported or handled in
34        // SkPDFDevice::setUpContentEntry.
35        default:                        return "Normal";
36    }
37}
38
39static int to_stroke_cap(uint8_t cap) {
40    // PDF32000.book section 8.4.3.3 "Line Cap Style"
41    switch ((SkPaint::Cap)cap) {
42        case SkPaint::kButt_Cap:   return 0;
43        case SkPaint::kRound_Cap:  return 1;
44        case SkPaint::kSquare_Cap: return 2;
45        default: SkASSERT(false);  return 0;
46    }
47}
48
49static int to_stroke_join(uint8_t join) {
50    // PDF32000.book section 8.4.3.4 "Line Join Style"
51    switch ((SkPaint::Join)join) {
52        case SkPaint::kMiter_Join: return 0;
53        case SkPaint::kRound_Join: return 1;
54        case SkPaint::kBevel_Join: return 2;
55        default: SkASSERT(false);  return 0;
56    }
57}
58
59// If a SkXfermode is unsupported in PDF, this function returns
60// SrcOver, otherwise, it returns that Xfermode as a Mode.
61static uint8_t pdf_blend_mode(SkBlendMode mode) {
62    switch (mode) {
63        case SkBlendMode::kSrcOver:
64        case SkBlendMode::kMultiply:
65        case SkBlendMode::kScreen:
66        case SkBlendMode::kOverlay:
67        case SkBlendMode::kDarken:
68        case SkBlendMode::kLighten:
69        case SkBlendMode::kColorDodge:
70        case SkBlendMode::kColorBurn:
71        case SkBlendMode::kHardLight:
72        case SkBlendMode::kSoftLight:
73        case SkBlendMode::kDifference:
74        case SkBlendMode::kExclusion:
75        case SkBlendMode::kHue:
76        case SkBlendMode::kSaturation:
77        case SkBlendMode::kColor:
78        case SkBlendMode::kLuminosity:
79            return SkToU8((unsigned)mode);
80        default:
81            return SkToU8((unsigned)SkBlendMode::kSrcOver);
82    }
83}
84
85sk_sp<SkPDFDict> SkPDFGraphicState::GetGraphicStateForPaint(SkPDFCanon* canon,
86                                                            const SkPaint& p) {
87    SkASSERT(canon);
88    if (SkPaint::kFill_Style == p.getStyle()) {
89        SkPDFFillGraphicState fillKey = {p.getAlpha(), pdf_blend_mode(p.getBlendMode())};
90        auto& fillMap = canon->fFillGSMap;
91        if (sk_sp<SkPDFDict>* statePtr = fillMap.find(fillKey)) {
92            return *statePtr;
93        }
94        auto state = sk_make_sp<SkPDFDict>();
95        state->reserve(2);
96        state->insertScalar("ca", fillKey.fAlpha / 255.0f);
97        state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode));
98        fillMap.set(fillKey, state);
99        return state;
100    } else {
101        SkPDFStrokeGraphicState strokeKey = {
102            p.getStrokeWidth(), p.getStrokeMiter(),
103            SkToU8(p.getStrokeCap()), SkToU8(p.getStrokeJoin()),
104            p.getAlpha(), pdf_blend_mode(p.getBlendMode())};
105        auto& sMap = canon->fStrokeGSMap;
106        if (sk_sp<SkPDFDict>* statePtr = sMap.find(strokeKey)) {
107            return *statePtr;
108        }
109        auto state = sk_make_sp<SkPDFDict>();
110        state->reserve(8);
111        state->insertScalar("CA", strokeKey.fAlpha / 255.0f);
112        state->insertScalar("ca", strokeKey.fAlpha / 255.0f);
113        state->insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap));
114        state->insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin));
115        state->insertScalar("LW", strokeKey.fStrokeWidth);
116        state->insertScalar("ML", strokeKey.fStrokeMiter);
117        state->insertBool("SA", true);  // SA = Auto stroke adjustment.
118        state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode));
119        sMap.set(strokeKey, state);
120        return state;
121    }
122}
123
124////////////////////////////////////////////////////////////////////////////////
125
126static sk_sp<SkPDFStream> make_invert_function() {
127    // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
128    // a type 2 function, so we use a type 4 function.
129    auto domainAndRange = sk_make_sp<SkPDFArray>();
130    domainAndRange->reserve(2);
131    domainAndRange->appendInt(0);
132    domainAndRange->appendInt(1);
133
134    static const char psInvert[] = "{1 exch sub}";
135    // Do not copy the trailing '\0' into the SkData.
136    auto invertFunction = sk_make_sp<SkPDFStream>(
137            SkData::MakeWithoutCopy(psInvert, strlen(psInvert)));
138    invertFunction->dict()->insertInt("FunctionType", 4);
139    invertFunction->dict()->insertObject("Domain", domainAndRange);
140    invertFunction->dict()->insertObject("Range", std::move(domainAndRange));
141    return invertFunction;
142}
143
144sk_sp<SkPDFDict> SkPDFGraphicState::GetSMaskGraphicState(
145        sk_sp<SkPDFObject> sMask,
146        bool invert,
147        SkPDFSMaskMode sMaskMode,
148        SkPDFCanon* canon) {
149    // The practical chances of using the same mask more than once are unlikely
150    // enough that it's not worth canonicalizing.
151    auto sMaskDict = sk_make_sp<SkPDFDict>("Mask");
152    if (sMaskMode == kAlpha_SMaskMode) {
153        sMaskDict->insertName("S", "Alpha");
154    } else if (sMaskMode == kLuminosity_SMaskMode) {
155        sMaskDict->insertName("S", "Luminosity");
156    }
157    sMaskDict->insertObjRef("G", std::move(sMask));
158    if (invert) {
159        // Instead of calling SkPDFGraphicState::MakeInvertFunction,
160        // let the canon deduplicate this object.
161        sk_sp<SkPDFStream>& invertFunction = canon->fInvertFunction;
162        if (!invertFunction) {
163            invertFunction = make_invert_function();
164        }
165        sMaskDict->insertObjRef("TR", invertFunction);
166    }
167    auto result = sk_make_sp<SkPDFDict>("ExtGState");
168    result->insertObject("SMask", std::move(sMaskDict));
169    return result;
170}
171