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