Home | History | Annotate | Download | only in model
      1 /*
      2  * Copyright 2016 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 "SkCanvas.h"
      9 #include "SkPath.h"
     10 #include "SkSVGAttribute.h"
     11 #include "SkSVGNode.h"
     12 #include "SkSVGRenderContext.h"
     13 #include "SkSVGTypes.h"
     14 
     15 namespace {
     16 
     17 SkScalar length_size_for_type(const SkSize& viewport, SkSVGLengthContext::LengthType t) {
     18     switch (t) {
     19     case SkSVGLengthContext::LengthType::kHorizontal:
     20         return viewport.width();
     21     case SkSVGLengthContext::LengthType::kVertical:
     22         return viewport.height();
     23     case SkSVGLengthContext::LengthType::kOther:
     24         return SkScalarSqrt(viewport.width() * viewport.height());
     25     }
     26 
     27     SkASSERT(false);  // Not reached.
     28     return 0;
     29 }
     30 
     31 // Multipliers for DPI-relative units.
     32 constexpr SkScalar kINMultiplier = 1.00f;
     33 constexpr SkScalar kPTMultiplier = kINMultiplier / 72.272f;
     34 constexpr SkScalar kPCMultiplier = kPTMultiplier * 12;
     35 constexpr SkScalar kMMMultiplier = kINMultiplier / 25.4f;
     36 constexpr SkScalar kCMMultiplier = kMMMultiplier * 10;
     37 
     38 } // anonymous ns
     39 
     40 SkScalar SkSVGLengthContext::resolve(const SkSVGLength& l, LengthType t) const {
     41     switch (l.unit()) {
     42     case SkSVGLength::Unit::kNumber:
     43         // Fall through.
     44     case SkSVGLength::Unit::kPX:
     45         return l.value();
     46     case SkSVGLength::Unit::kPercentage:
     47         return l.value() * length_size_for_type(fViewport, t) / 100;
     48     case SkSVGLength::Unit::kCM:
     49         return l.value() * fDPI * kCMMultiplier;
     50     case SkSVGLength::Unit::kMM:
     51         return l.value() * fDPI * kMMMultiplier;
     52     case SkSVGLength::Unit::kIN:
     53         return l.value() * fDPI * kINMultiplier;
     54     case SkSVGLength::Unit::kPT:
     55         return l.value() * fDPI * kPTMultiplier;
     56     case SkSVGLength::Unit::kPC:
     57         return l.value() * fDPI * kPCMultiplier;
     58     default:
     59         SkDebugf("unsupported unit type: <%d>\n", l.unit());
     60         return 0;
     61     }
     62 }
     63 
     64 SkRect SkSVGLengthContext::resolveRect(const SkSVGLength& x, const SkSVGLength& y,
     65                                        const SkSVGLength& w, const SkSVGLength& h) const {
     66     return SkRect::MakeXYWH(
     67         this->resolve(x, SkSVGLengthContext::LengthType::kHorizontal),
     68         this->resolve(y, SkSVGLengthContext::LengthType::kVertical),
     69         this->resolve(w, SkSVGLengthContext::LengthType::kHorizontal),
     70         this->resolve(h, SkSVGLengthContext::LengthType::kVertical));
     71 }
     72 
     73 namespace {
     74 
     75 SkPaint::Cap toSkCap(const SkSVGLineCap& cap) {
     76     switch (cap.type()) {
     77     case SkSVGLineCap::Type::kButt:
     78         return SkPaint::kButt_Cap;
     79     case SkSVGLineCap::Type::kRound:
     80         return SkPaint::kRound_Cap;
     81     case SkSVGLineCap::Type::kSquare:
     82         return SkPaint::kSquare_Cap;
     83     default:
     84         SkASSERT(false);
     85         return SkPaint::kButt_Cap;
     86     }
     87 }
     88 
     89 SkPaint::Join toSkJoin(const SkSVGLineJoin& join) {
     90     switch (join.type()) {
     91     case SkSVGLineJoin::Type::kMiter:
     92         return SkPaint::kMiter_Join;
     93     case SkSVGLineJoin::Type::kRound:
     94         return SkPaint::kRound_Join;
     95     case SkSVGLineJoin::Type::kBevel:
     96         return SkPaint::kBevel_Join;
     97     default:
     98         SkASSERT(false);
     99         return SkPaint::kMiter_Join;
    100     }
    101 }
    102 
    103 void applySvgPaint(const SkSVGRenderContext& ctx, const SkSVGPaint& svgPaint, SkPaint* p) {
    104     switch (svgPaint.type()) {
    105     case SkSVGPaint::Type::kColor:
    106         p->setColor(SkColorSetA(svgPaint.color(), p->getAlpha()));
    107         break;
    108     case SkSVGPaint::Type::kIRI: {
    109         const auto* node = ctx.findNodeById(svgPaint.iri());
    110         if (!node || !node->asPaint(ctx, p)) {
    111             p->setColor(SK_ColorTRANSPARENT);
    112         }
    113         break;
    114     }
    115     case SkSVGPaint::Type::kCurrentColor:
    116         SkDebugf("unimplemented 'currentColor' paint type");
    117         // Fall through.
    118     case SkSVGPaint::Type::kNone:
    119         // Fall through.
    120     case SkSVGPaint::Type::kInherit:
    121         break;
    122     }
    123 }
    124 
    125 inline uint8_t opacity_to_alpha(SkScalar o) {
    126     return SkTo<uint8_t>(SkScalarRoundToInt(o * 255));
    127 }
    128 
    129 // Commit the selected attribute to the paint cache.
    130 template <SkSVGAttribute>
    131 void commitToPaint(const SkSVGPresentationAttributes&,
    132                    const SkSVGRenderContext&,
    133                    SkSVGPresentationContext*);
    134 
    135 template <>
    136 void commitToPaint<SkSVGAttribute::kFill>(const SkSVGPresentationAttributes& attrs,
    137                                           const SkSVGRenderContext& ctx,
    138                                           SkSVGPresentationContext* pctx) {
    139     applySvgPaint(ctx, *attrs.fFill.get(), &pctx->fFillPaint);
    140 }
    141 
    142 template <>
    143 void commitToPaint<SkSVGAttribute::kStroke>(const SkSVGPresentationAttributes& attrs,
    144                                             const SkSVGRenderContext& ctx,
    145                                             SkSVGPresentationContext* pctx) {
    146     applySvgPaint(ctx, *attrs.fStroke.get(), &pctx->fStrokePaint);
    147 }
    148 
    149 template <>
    150 void commitToPaint<SkSVGAttribute::kFillOpacity>(const SkSVGPresentationAttributes& attrs,
    151                                                  const SkSVGRenderContext&,
    152                                                  SkSVGPresentationContext* pctx) {
    153     pctx->fFillPaint.setAlpha(opacity_to_alpha(*attrs.fFillOpacity.get()));
    154 }
    155 
    156 template <>
    157 void commitToPaint<SkSVGAttribute::kStrokeLineCap>(const SkSVGPresentationAttributes& attrs,
    158                                                    const SkSVGRenderContext&,
    159                                                    SkSVGPresentationContext* pctx) {
    160     const auto& cap = *attrs.fStrokeLineCap.get();
    161     if (cap.type() != SkSVGLineCap::Type::kInherit) {
    162         pctx->fStrokePaint.setStrokeCap(toSkCap(cap));
    163     }
    164 }
    165 
    166 template <>
    167 void commitToPaint<SkSVGAttribute::kStrokeLineJoin>(const SkSVGPresentationAttributes& attrs,
    168                                                     const SkSVGRenderContext&,
    169                                                     SkSVGPresentationContext* pctx) {
    170     const auto& join = *attrs.fStrokeLineJoin.get();
    171     if (join.type() != SkSVGLineJoin::Type::kInherit) {
    172         pctx->fStrokePaint.setStrokeJoin(toSkJoin(join));
    173     }
    174 }
    175 
    176 template <>
    177 void commitToPaint<SkSVGAttribute::kStrokeOpacity>(const SkSVGPresentationAttributes& attrs,
    178                                                    const SkSVGRenderContext&,
    179                                                    SkSVGPresentationContext* pctx) {
    180     pctx->fStrokePaint.setAlpha(opacity_to_alpha(*attrs.fStrokeOpacity.get()));
    181 }
    182 
    183 template <>
    184 void commitToPaint<SkSVGAttribute::kStrokeWidth>(const SkSVGPresentationAttributes& attrs,
    185                                                  const SkSVGRenderContext& ctx,
    186                                                  SkSVGPresentationContext* pctx) {
    187     auto strokeWidth = ctx.lengthContext().resolve(*attrs.fStrokeWidth.get(),
    188                                                    SkSVGLengthContext::LengthType::kOther);
    189     pctx->fStrokePaint.setStrokeWidth(strokeWidth);
    190 }
    191 
    192 template <>
    193 void commitToPaint<SkSVGAttribute::kFillRule>(const SkSVGPresentationAttributes&,
    194                                               const SkSVGRenderContext&,
    195                                               SkSVGPresentationContext*) {
    196     // Not part of the SkPaint state; applied to the path at render time.
    197 }
    198 
    199 } // anonymous ns
    200 
    201 SkSVGPresentationContext::SkSVGPresentationContext()
    202     : fInherited(SkSVGPresentationAttributes::MakeInitial()) {
    203 
    204     fFillPaint.setStyle(SkPaint::kFill_Style);
    205     fStrokePaint.setStyle(SkPaint::kStroke_Style);
    206 
    207     // TODO: drive AA off presentation attrs also (shape-rendering?)
    208     fFillPaint.setAntiAlias(true);
    209     fStrokePaint.setAntiAlias(true);
    210 
    211     // Commit initial values to the paint cache.
    212     SkCanvas dummyCanvas(0, 0);
    213     SkSVGRenderContext dummy(&dummyCanvas, SkSVGIDMapper(), SkSVGLengthContext(SkSize::Make(0, 0)),
    214                              *this);
    215 
    216     commitToPaint<SkSVGAttribute::kFill>(fInherited, dummy, this);
    217     commitToPaint<SkSVGAttribute::kFillOpacity>(fInherited, dummy, this);
    218     commitToPaint<SkSVGAttribute::kStroke>(fInherited, dummy, this);
    219     commitToPaint<SkSVGAttribute::kStrokeLineCap>(fInherited, dummy, this);
    220     commitToPaint<SkSVGAttribute::kStrokeLineJoin>(fInherited, dummy, this);
    221     commitToPaint<SkSVGAttribute::kStrokeOpacity>(fInherited, dummy, this);
    222     commitToPaint<SkSVGAttribute::kStrokeWidth>(fInherited, dummy, this);
    223 }
    224 
    225 SkSVGRenderContext::SkSVGRenderContext(SkCanvas* canvas,
    226                                        const SkSVGIDMapper& mapper,
    227                                        const SkSVGLengthContext& lctx,
    228                                        const SkSVGPresentationContext& pctx)
    229     : fIDMapper(mapper)
    230     , fLengthContext(lctx)
    231     , fPresentationContext(pctx)
    232     , fCanvas(canvas)
    233     , fCanvasSaveCount(canvas->getSaveCount()) {}
    234 
    235 SkSVGRenderContext::SkSVGRenderContext(const SkSVGRenderContext& other)
    236     : SkSVGRenderContext(other.fCanvas,
    237                          other.fIDMapper,
    238                          *other.fLengthContext,
    239                          *other.fPresentationContext) {}
    240 
    241 SkSVGRenderContext::~SkSVGRenderContext() {
    242     fCanvas->restoreToCount(fCanvasSaveCount);
    243 }
    244 
    245 const SkSVGNode* SkSVGRenderContext::findNodeById(const SkString& id) const {
    246     const auto* v = fIDMapper.find(id);
    247     return v ? v->get() : nullptr;
    248 }
    249 
    250 void SkSVGRenderContext::applyPresentationAttributes(const SkSVGPresentationAttributes& attrs,
    251                                                      uint32_t flags) {
    252 
    253 #define ApplyLazyInheritedAttribute(ATTR)                                               \
    254     do {                                                                                \
    255         /* All attributes should be defined on the inherited context. */                \
    256         SkASSERT(fPresentationContext->fInherited.f ## ATTR.isValid());                 \
    257         const auto* value = attrs.f ## ATTR.getMaybeNull();                             \
    258         if (value && *value != *fPresentationContext->fInherited.f ## ATTR.get()) {     \
    259             /* Update the local attribute value */                                      \
    260             fPresentationContext.writable()->fInherited.f ## ATTR.set(*value);          \
    261             /* Update the cached paints */                                              \
    262             commitToPaint<SkSVGAttribute::k ## ATTR>(attrs, *this,    \
    263                                                      fPresentationContext.writable());  \
    264         }                                                                               \
    265     } while (false)
    266 
    267     ApplyLazyInheritedAttribute(Fill);
    268     ApplyLazyInheritedAttribute(FillOpacity);
    269     ApplyLazyInheritedAttribute(FillRule);
    270     ApplyLazyInheritedAttribute(Stroke);
    271     ApplyLazyInheritedAttribute(StrokeLineCap);
    272     ApplyLazyInheritedAttribute(StrokeLineJoin);
    273     ApplyLazyInheritedAttribute(StrokeOpacity);
    274     ApplyLazyInheritedAttribute(StrokeWidth);
    275 
    276 #undef ApplyLazyInheritedAttribute
    277 
    278     // Uninherited attributes.  Only apply to the current context.
    279 
    280     if (auto* opacity = attrs.fOpacity.getMaybeNull()) {
    281         this->applyOpacity(opacity->value(), flags);
    282     }
    283 
    284     if (auto* clip = attrs.fClipPath.getMaybeNull()) {
    285         this->applyClip(*clip);
    286     }
    287 }
    288 
    289 void SkSVGRenderContext::applyOpacity(SkScalar opacity, uint32_t flags) {
    290     if (opacity >= 1) {
    291         return;
    292     }
    293 
    294     const bool hasFill   = SkToBool(this->fillPaint());
    295     const bool hasStroke = SkToBool(this->strokePaint());
    296 
    297     // We can apply the opacity as paint alpha iif it only affects one atomic draw.
    298     // For now, this means a) the target node doesn't have any descendants, and
    299     // b) it only has a stroke or a fill (but not both).  Going forward, we may need
    300     // to refine this heuristic (e.g. to accommodate markers).
    301     if ((flags & kLeaf) && (hasFill ^ hasStroke)) {
    302         auto* pctx = fPresentationContext.writable();
    303         if (hasFill) {
    304             pctx->fFillPaint.setAlpha(
    305                 SkScalarRoundToInt(opacity * pctx->fFillPaint.getAlpha()));
    306         } else {
    307             pctx->fStrokePaint.setAlpha(
    308                 SkScalarRoundToInt(opacity * pctx->fStrokePaint.getAlpha()));
    309         }
    310     } else {
    311         // Expensive, layer-based fall back.
    312         SkPaint opacityPaint;
    313         opacityPaint.setAlpha(opacity_to_alpha(opacity));
    314         // Balanced in the destructor, via restoreToCount().
    315         fCanvas->saveLayer(nullptr, &opacityPaint);
    316     }
    317 }
    318 
    319 void SkSVGRenderContext::applyClip(const SkSVGClip& clip) {
    320     if (clip.type() != SkSVGClip::Type::kIRI) {
    321         return;
    322     }
    323 
    324     const SkSVGNode* clipNode = this->findNodeById(clip.iri());
    325     if (!clipNode || clipNode->tag() != SkSVGTag::kClipPath) {
    326         return;
    327     }
    328 
    329     const SkPath clipPath = clipNode->asPath(*this);
    330 
    331     // We use the computed clip path in two ways:
    332     //
    333     //   - apply to the current canvas, for drawing
    334     //   - track in the presentation context, for asPath() composition
    335     //
    336     // TODO: the two uses are exclusive, avoid canvas churn when non needed.
    337 
    338     // Only save if needed
    339     if (fCanvas->getSaveCount() == fCanvasSaveCount) {
    340         fCanvas->save();
    341     }
    342 
    343     fCanvas->clipPath(clipPath, true);
    344     fClipPath.set(clipPath);
    345 }
    346 
    347 const SkPaint* SkSVGRenderContext::fillPaint() const {
    348     const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fFill.get()->type();
    349     return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fFillPaint : nullptr;
    350 }
    351 
    352 const SkPaint* SkSVGRenderContext::strokePaint() const {
    353     const SkSVGPaint::Type paintType = fPresentationContext->fInherited.fStroke.get()->type();
    354     return paintType != SkSVGPaint::Type::kNone ? &fPresentationContext->fStrokePaint : nullptr;
    355 }
    356