mirror of
https://github.com/exyte/Macaw.git
synced 2024-11-12 18:18:35 +03:00
Fix #34: Implement DropShadow effect rendering
This commit is contained in:
parent
411fe986ea
commit
7a608c7c0c
@ -28,7 +28,7 @@ public extension MacawView {
|
||||
)
|
||||
|
||||
context.cgContext = ctx
|
||||
renderer?.render(force: false, opacity: node.opacity)
|
||||
renderer?.render(in: ctx, force: false, opacity: node.opacity)
|
||||
|
||||
ctx.endPDFPage()
|
||||
}
|
||||
|
@ -5,4 +5,8 @@ open class Effect {
|
||||
public init(input: Effect?) {
|
||||
self.input = input
|
||||
}
|
||||
|
||||
public static func dropShadow(dx: Double = 5, dy: Double = 5, radius: Double = 5) -> Effect? {
|
||||
return AlphaEffect(input: OffsetEffect(dx: dx, dy: dy, input: GaussianBlur(radius: radius, input: nil)))
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,10 @@ class GroupRenderer: NodeRenderer {
|
||||
fileprivate var renderers: [NodeRenderer] = []
|
||||
let renderingInterval: RenderingInterval?
|
||||
|
||||
init(group: Group, ctx: RenderContext, animationCache: AnimationCache?, interval: RenderingInterval? = .none) {
|
||||
init(group: Group, animationCache: AnimationCache?, interval: RenderingInterval? = .none) {
|
||||
self.group = group
|
||||
self.renderingInterval = interval
|
||||
super.init(node: group, ctx: ctx, animationCache: animationCache)
|
||||
super.init(node: group, animationCache: animationCache)
|
||||
updateRenderers()
|
||||
}
|
||||
|
||||
@ -35,9 +35,9 @@ class GroupRenderer: NodeRenderer {
|
||||
return group
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
|
||||
renderers.forEach { renderer in
|
||||
renderer.render(force: force, opacity: opacity)
|
||||
renderer.render(in: context, force: force, opacity: opacity, useAlphaOnly: useAlphaOnly)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,12 +62,12 @@ class GroupRenderer: NodeRenderer {
|
||||
|
||||
if let updatedRenderers = group?.contents.compactMap ({ child -> NodeRenderer? in
|
||||
guard let interval = renderingInterval else {
|
||||
return RenderUtils.createNodeRenderer(child, context: ctx, animationCache: animationCache)
|
||||
return RenderUtils.createNodeRenderer(child, animationCache: animationCache)
|
||||
}
|
||||
|
||||
let index = AnimationUtils.absoluteIndex(child, useCache: true)
|
||||
if index > interval.from && index < interval.to {
|
||||
return RenderUtils.createNodeRenderer(child, context: ctx, animationCache: animationCache, interval: interval)
|
||||
return RenderUtils.createNodeRenderer(child, animationCache: animationCache, interval: interval)
|
||||
}
|
||||
|
||||
return .none
|
||||
|
@ -13,9 +13,9 @@ class ImageRenderer: NodeRenderer {
|
||||
|
||||
var renderedPaths: [CGPath] = [CGPath]()
|
||||
|
||||
init(image: Image, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
init(image: Image, animationCache: AnimationCache?) {
|
||||
self.image = image
|
||||
super.init(node: image, ctx: ctx, animationCache: animationCache)
|
||||
super.init(node: image, animationCache: animationCache)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
@ -37,7 +37,7 @@ class ImageRenderer: NodeRenderer {
|
||||
observe(image.hVar)
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
@ -52,10 +52,10 @@ class ImageRenderer: NodeRenderer {
|
||||
|
||||
if let mImage = mImage {
|
||||
let rect = getRect(mImage)
|
||||
ctx.cgContext!.scaleBy(x: 1.0, y: -1.0)
|
||||
ctx.cgContext!.translateBy(x: 0.0, y: -1.0 * rect.height)
|
||||
ctx.cgContext!.setAlpha(CGFloat(opacity))
|
||||
ctx.cgContext!.draw(mImage.cgImage!, in: rect)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: 0.0, y: -1.0 * rect.height)
|
||||
context.setAlpha(CGFloat(opacity))
|
||||
context.draw(mImage.cgImage!, in: rect)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,15 +11,12 @@ struct RenderingInterval {
|
||||
|
||||
class NodeRenderer {
|
||||
|
||||
let ctx: RenderContext
|
||||
|
||||
fileprivate let onNodeChange: () -> Void
|
||||
fileprivate let disposables = GroupDisposable()
|
||||
fileprivate var active = false
|
||||
weak var animationCache: AnimationCache?
|
||||
|
||||
init(node: Node, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
self.ctx = ctx
|
||||
init(node: Node, animationCache: AnimationCache?) {
|
||||
self.animationCache = animationCache
|
||||
|
||||
onNodeChange = {
|
||||
@ -30,8 +27,6 @@ class NodeRenderer {
|
||||
if isAnimating {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.view?.setNeedsDisplay()
|
||||
}
|
||||
|
||||
addObservers()
|
||||
@ -69,22 +64,56 @@ class NodeRenderer {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
|
||||
final public func render(force: Bool, opacity: Double) {
|
||||
ctx.cgContext!.saveGState()
|
||||
final public func render(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
|
||||
context.saveGState()
|
||||
defer {
|
||||
ctx.cgContext!.restoreGState()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
let newOpacity = node.opacity * opacity
|
||||
|
||||
ctx.cgContext!.concatenate(node.place.toCG())
|
||||
applyClip()
|
||||
directRender(force: force, opacity: node.opacity * opacity)
|
||||
context.concatenate(node.place.toCG())
|
||||
applyClip(in: context)
|
||||
|
||||
// no effects, just draw as usual
|
||||
guard let effect = node.effect else {
|
||||
directRender(in: context, force: force, opacity: newOpacity, useAlphaOnly: useAlphaOnly)
|
||||
return
|
||||
}
|
||||
|
||||
var effects = [Effect]()
|
||||
var next: Effect? = effect
|
||||
while next != nil {
|
||||
effects.append(next!)
|
||||
next = next?.input
|
||||
}
|
||||
|
||||
let offset = effects.first { $0 is OffsetEffect } as? OffsetEffect
|
||||
let otherEffects = effects.filter { !($0 is OffsetEffect) }
|
||||
let useAlphaOnly = otherEffects.contains { effect -> Bool in
|
||||
effect is AlphaEffect
|
||||
}
|
||||
|
||||
// move to offset
|
||||
let move = Transform(m11: 1, m12: 0, m21: 0, m22: 1, dx: offset?.dx ?? 0, dy: offset?.dy ?? 0)
|
||||
context.concatenate(move.toCG())
|
||||
|
||||
if otherEffects.isEmpty {
|
||||
// just draw offset shape
|
||||
directRender(in: context, force: force, opacity: newOpacity, useAlphaOnly: useAlphaOnly)
|
||||
} else {
|
||||
// apply other effects to offset shape and draw it
|
||||
applyEffects(otherEffects, context: context, opacity: opacity, useAlphaOnly: useAlphaOnly)
|
||||
}
|
||||
|
||||
// move back and draw the shape itself
|
||||
context.concatenate(move.invert()!.toCG())
|
||||
directRender(in: context, force: force, opacity: newOpacity)
|
||||
}
|
||||
|
||||
final func directRender(force: Bool = true, opacity: Double = 1.0) {
|
||||
final func directRender(in context: CGContext, force: Bool = true, opacity: Double = 1.0, useAlphaOnly: Bool = false) {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
@ -97,10 +126,62 @@ class NodeRenderer {
|
||||
} else {
|
||||
self.addObservers()
|
||||
}
|
||||
doRender(force, opacity: opacity)
|
||||
doRender(in: context, force: force, opacity: opacity, useAlphaOnly: useAlphaOnly)
|
||||
}
|
||||
|
||||
func doRender(_ force: Bool, opacity: Double) {
|
||||
fileprivate func applyEffects(_ effects: [Effect], context: CGContext, opacity: Double, useAlphaOnly: Bool = false) {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
for effect in effects {
|
||||
if let blur = effect as? GaussianBlur {
|
||||
guard let bounds = node.bounds() else {
|
||||
return
|
||||
}
|
||||
let shadowInset = min(blur.radius * 6 + 1, 150)
|
||||
guard let shapeImage = renderToImage(bounds: bounds, inset: shadowInset, useAlphaOnly: useAlphaOnly)?.cgImage else {
|
||||
return
|
||||
}
|
||||
guard let filteredImage = applyBlur(shapeImage, blur: blur) else {
|
||||
return
|
||||
}
|
||||
context.draw(filteredImage, in: CGRect(x: bounds.x - shadowInset / 2, y: bounds.y - shadowInset / 2, width: bounds.w + shadowInset, height: bounds.h + shadowInset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func applyBlur(_ image: CGImage, blur: GaussianBlur) -> CGImage? {
|
||||
let image = CIImage(cgImage: image)
|
||||
guard let filter = CIFilter(name: "CIGaussianBlur") else {
|
||||
return .none
|
||||
}
|
||||
filter.setDefaults()
|
||||
filter.setValue(Int(blur.radius), forKey: kCIInputRadiusKey)
|
||||
filter.setValue(image, forKey: kCIInputImageKey)
|
||||
|
||||
let context = CIContext(options: nil)
|
||||
let imageRef = context.createCGImage(filter.outputImage!, from: image.extent)
|
||||
return imageRef
|
||||
}
|
||||
|
||||
func renderToImage(bounds: Rect, inset: Double, useAlphaOnly: Bool = false) -> UIImage? {
|
||||
MGraphicsBeginImageContextWithOptions(CGSize(width: bounds.w + inset, height: bounds.h + inset), false, 1)
|
||||
|
||||
guard let tempContext = MGraphicsGetCurrentContext() else {
|
||||
return .none
|
||||
}
|
||||
|
||||
// flip y-axis and leave space for the blur
|
||||
tempContext.translateBy(x: CGFloat(inset / 2 - bounds.x), y: CGFloat(bounds.h + inset / 2 + bounds.y))
|
||||
tempContext.scaleBy(x: 1, y: -1)
|
||||
directRender(in: tempContext, force: false, opacity: 1.0, useAlphaOnly: useAlphaOnly)
|
||||
|
||||
let img = MGraphicsGetImageFromCurrentImageContext()
|
||||
MGraphicsEndImageContext()
|
||||
return img
|
||||
}
|
||||
|
||||
func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
|
||||
fatalError("Unsupported")
|
||||
}
|
||||
|
||||
@ -118,7 +199,7 @@ class NodeRenderer {
|
||||
}
|
||||
|
||||
ctx.concatenate(place.toCG())
|
||||
applyClip()
|
||||
applyClip(in: ctx)
|
||||
let loc = location.applying(inverted.toCG())
|
||||
let result = doFindNodeAt(location: CGPoint(x: loc.x, y: loc.y), ctx: ctx)
|
||||
return result
|
||||
@ -131,12 +212,12 @@ class NodeRenderer {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func applyClip() {
|
||||
private func applyClip(in context: CGContext) {
|
||||
guard let node = node() else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let clip = node.clip, let context = ctx.cgContext else {
|
||||
guard let clip = node.clip else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -40,18 +40,17 @@ class RenderUtils {
|
||||
|
||||
class func createNodeRenderer(
|
||||
_ node: Node,
|
||||
context: RenderContext,
|
||||
animationCache: AnimationCache?,
|
||||
interval: RenderingInterval? = .none
|
||||
) -> NodeRenderer {
|
||||
if let group = node as? Group {
|
||||
return GroupRenderer(group: group, ctx: context, animationCache: animationCache, interval: interval)
|
||||
return GroupRenderer(group: group, animationCache: animationCache, interval: interval)
|
||||
} else if let shape = node as? Shape {
|
||||
return ShapeRenderer(shape: shape, ctx: context, animationCache: animationCache)
|
||||
return ShapeRenderer(shape: shape, animationCache: animationCache)
|
||||
} else if let text = node as? Text {
|
||||
return TextRenderer(text: text, ctx: context, animationCache: animationCache)
|
||||
return TextRenderer(text: text, animationCache: animationCache)
|
||||
} else if let image = node as? Image {
|
||||
return ImageRenderer(image: image, ctx: context, animationCache: animationCache)
|
||||
return ImageRenderer(image: image, animationCache: animationCache)
|
||||
}
|
||||
fatalError("Unsupported node: \(node)")
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ class ShapeRenderer: NodeRenderer {
|
||||
|
||||
weak var shape: Shape?
|
||||
|
||||
init(shape: Shape, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
init(shape: Shape, animationCache: AnimationCache?) {
|
||||
self.shape = shape
|
||||
super.init(node: shape, ctx: ctx, animationCache: animationCache)
|
||||
super.init(node: shape, animationCache: animationCache)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
@ -31,122 +31,34 @@ class ShapeRenderer: NodeRenderer {
|
||||
observe(shape.strokeVar)
|
||||
}
|
||||
|
||||
fileprivate func drawShape(in context: CGContext, opacity: Double) {
|
||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
|
||||
guard let shape = shape else {
|
||||
return
|
||||
}
|
||||
setGeometry(shape.form, ctx: context)
|
||||
var fillRule = FillRule.nonzero
|
||||
if let path = shape.form as? Path {
|
||||
fillRule = path.fillRule
|
||||
}
|
||||
drawPath(shape.fill, stroke: shape.stroke, ctx: context, opacity: opacity, fillRule: fillRule)
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
guard let shape = shape, let context = ctx.cgContext else {
|
||||
return
|
||||
}
|
||||
if shape.fill == nil && shape.stroke == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// no effects, just draw as usual
|
||||
guard let effect = shape.effect else {
|
||||
drawShape(in: context, opacity: opacity)
|
||||
setGeometry(shape.form, ctx: context)
|
||||
|
||||
var fillRule = FillRule.nonzero
|
||||
if let path = shape.form as? Path {
|
||||
fillRule = path.fillRule
|
||||
}
|
||||
|
||||
if !useAlphaOnly {
|
||||
drawPath(shape.fill, stroke: shape.stroke, ctx: context, opacity: opacity, fillRule: fillRule)
|
||||
return
|
||||
}
|
||||
|
||||
var effects = [Effect]()
|
||||
var next: Effect? = effect
|
||||
while next != nil {
|
||||
effects.append(next!)
|
||||
next = next?.input
|
||||
}
|
||||
let color = shape.fill != nil ? shape.fill as! Color : .black
|
||||
let fill = Color.black.with(a: Double(color.a()) / 255.0)
|
||||
|
||||
let offset = effects.first { $0 is OffsetEffect }
|
||||
let otherEffects = effects.filter { !($0 is OffsetEffect) }
|
||||
if let offset = offset as? OffsetEffect {
|
||||
let move = Transform(m11: 1, m12: 0, m21: 0, m22: 1, dx: offset.dx, dy: offset.dy)
|
||||
context.concatenate(move.toCG())
|
||||
let strokeColor = shape.stroke != nil ? shape.stroke?.fill as! Color : .black
|
||||
let newStrokeColor = Color.black.with(a: Double(strokeColor.a()) / 255.0)
|
||||
let stroke = shape.stroke != nil ? Stroke(fill: newStrokeColor, width: shape.stroke!.width, cap: shape.stroke!.cap, join: shape.stroke!.join, dashes: shape.stroke!.dashes, offset: shape.stroke!.offset) : nil
|
||||
|
||||
if otherEffects.isEmpty {
|
||||
// draw offset shape
|
||||
drawShape(in: context, opacity: opacity)
|
||||
} else {
|
||||
// apply other effects to offset shape
|
||||
applyEffects(otherEffects, opacity: opacity)
|
||||
}
|
||||
|
||||
// move back and draw the shape itself
|
||||
context.concatenate(move.invert()!.toCG())
|
||||
drawShape(in: context, opacity: opacity)
|
||||
} else {
|
||||
// draw the shape
|
||||
drawShape(in: context, opacity: opacity)
|
||||
|
||||
// apply other effects to shape
|
||||
applyEffects(otherEffects, opacity: opacity)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func applyEffects(_ effects: [Effect], opacity: Double) {
|
||||
guard let shape = shape, let context = ctx.cgContext else {
|
||||
return
|
||||
}
|
||||
for effect in effects {
|
||||
if let blur = effect as? GaussianBlur {
|
||||
let shadowInset = min(blur.radius * 6 + 1, 150)
|
||||
guard let shapeImage = saveToImage(shape: shape, shadowInset: shadowInset, opacity: opacity)?.cgImage else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let filteredImage = applyBlur(shapeImage, blur: blur) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let bounds = shape.bounds() else {
|
||||
return
|
||||
}
|
||||
context.draw(filteredImage, in: CGRect(x: bounds.x - shadowInset / 2, y: bounds.y - shadowInset / 2, width: bounds.w + shadowInset, height: bounds.h + shadowInset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func applyBlur(_ image: CGImage, blur: GaussianBlur) -> CGImage? {
|
||||
let image = CIImage(cgImage: image)
|
||||
guard let filter = CIFilter(name: "CIGaussianBlur") else {
|
||||
return .none
|
||||
}
|
||||
filter.setDefaults()
|
||||
filter.setValue(Int(blur.radius), forKey: kCIInputRadiusKey)
|
||||
filter.setValue(image, forKey: kCIInputImageKey)
|
||||
|
||||
let context = CIContext(options: nil)
|
||||
let imageRef = context.createCGImage(filter.outputImage!, from: image.extent)
|
||||
return imageRef
|
||||
}
|
||||
|
||||
fileprivate func saveToImage(shape: Shape, shadowInset: Double, opacity: Double) -> MImage? {
|
||||
guard let size = shape.bounds() else {
|
||||
return .none
|
||||
}
|
||||
MGraphicsBeginImageContextWithOptions(CGSize(width: size.w + shadowInset, height: size.h + shadowInset), false, 1)
|
||||
|
||||
guard let tempContext = MGraphicsGetCurrentContext() else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if shape.fill != nil || shape.stroke != nil {
|
||||
// flip y-axis and leave space for the blur
|
||||
tempContext.translateBy(x: CGFloat(shadowInset / 2 - size.x), y: CGFloat(size.h + shadowInset / 2 + size.y))
|
||||
tempContext.scaleBy(x: 1, y: -1)
|
||||
drawShape(in: tempContext, opacity: opacity)
|
||||
}
|
||||
|
||||
let img = MGraphicsGetImageFromCurrentImageContext()
|
||||
MGraphicsEndImageContext()
|
||||
return img
|
||||
drawPath(fill, stroke: stroke, ctx: context, opacity: opacity, fillRule: fillRule)
|
||||
}
|
||||
|
||||
override func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? {
|
||||
@ -183,10 +95,10 @@ class ShapeRenderer: NodeRenderer {
|
||||
|
||||
fileprivate func setGeometry(_ locus: Locus, ctx: CGContext) {
|
||||
if let rect = locus as? Rect {
|
||||
ctx.addRect(newCGRect(rect))
|
||||
ctx.addRect(rect.toCG())
|
||||
} else if let round = locus as? RoundRect {
|
||||
let corners = CGSize(width: CGFloat(round.rx), height: CGFloat(round.ry))
|
||||
let path = MBezierPath(roundedRect: newCGRect(round.rect), byRoundingCorners:
|
||||
let path = MBezierPath(roundedRect: round.rect.toCG(), byRoundingCorners:
|
||||
MRectCorner.allCorners, cornerRadii: corners).cgPath
|
||||
ctx.addPath(path)
|
||||
} else if let circle = locus as? Circle {
|
||||
@ -205,10 +117,6 @@ class ShapeRenderer: NodeRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func newCGRect(_ rect: Rect) -> CGRect {
|
||||
return CGRect(x: CGFloat(rect.x), y: CGFloat(rect.y), width: CGFloat(rect.w), height: CGFloat(rect.h))
|
||||
}
|
||||
|
||||
fileprivate func drawPath(_ fill: Fill?, stroke: Stroke?, ctx: CGContext?, opacity: Double, fillRule: FillRule) {
|
||||
var shouldStrokePath = false
|
||||
if fill is Gradient || stroke?.fill is Gradient {
|
||||
|
@ -9,9 +9,9 @@ import AppKit
|
||||
class TextRenderer: NodeRenderer {
|
||||
weak var text: Text?
|
||||
|
||||
init(text: Text, ctx: RenderContext, animationCache: AnimationCache?) {
|
||||
init(text: Text, animationCache: AnimationCache?) {
|
||||
self.text = text
|
||||
super.init(node: text, ctx: ctx, animationCache: animationCache)
|
||||
super.init(node: text, animationCache: animationCache)
|
||||
}
|
||||
|
||||
override func node() -> Node? {
|
||||
@ -33,7 +33,7 @@ class TextRenderer: NodeRenderer {
|
||||
observe(text.baselineVar)
|
||||
}
|
||||
|
||||
override func doRender(_ force: Bool, opacity: Double) {
|
||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, useAlphaOnly: Bool = false) {
|
||||
guard let text = text else {
|
||||
return
|
||||
}
|
||||
@ -52,7 +52,7 @@ class TextRenderer: NodeRenderer {
|
||||
}
|
||||
attributes[NSAttributedStringKey.strokeWidth] = stroke.width as NSObject?
|
||||
}
|
||||
MGraphicsPushContext(ctx.cgContext!)
|
||||
MGraphicsPushContext(context)
|
||||
message.draw(in: getBounds(font), withAttributes: attributes)
|
||||
MGraphicsPopContext()
|
||||
}
|
||||
|
@ -34,10 +34,10 @@ open class SVGParser {
|
||||
}
|
||||
|
||||
let availableStyleAttributes = ["stroke", "stroke-width", "stroke-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
|
||||
"fill", "fill-rule", "text-anchor", "clip-path", "fill-opacity",
|
||||
"stop-color", "stop-opacity",
|
||||
"font-family", "font-size",
|
||||
"font-weight", "opacity", "color", "visibility"]
|
||||
"fill", "fill-rule", "fill-opacity", "clip-path",
|
||||
"opacity", "color", "stop-color", "stop-opacity",
|
||||
"font-family", "font-size", "font-weight", "text-anchor",
|
||||
"visibility"]
|
||||
|
||||
fileprivate let xmlString: String
|
||||
fileprivate let initialPosition: Transform
|
||||
@ -159,10 +159,11 @@ open class SVGParser {
|
||||
}
|
||||
|
||||
fileprivate func parseNode(_ node: XMLIndexer, groupStyle: [String: String] = [:]) -> Node? {
|
||||
var result: Node? = nil
|
||||
if let element = node.element {
|
||||
switch element.name {
|
||||
case "g":
|
||||
return parseGroup(node, groupStyle: groupStyle)
|
||||
result = parseGroup(node, groupStyle: groupStyle)
|
||||
case "clipPath":
|
||||
if let id = element.allAttributes["id"]?.text, let clip = parseClip(node) {
|
||||
self.defClip[id] = clip
|
||||
@ -171,10 +172,14 @@ open class SVGParser {
|
||||
// do nothing - it was parsed on first iteration
|
||||
return .none
|
||||
default:
|
||||
return parseElement(node, groupStyle: groupStyle)
|
||||
result = parseElement(node, groupStyle: groupStyle)
|
||||
}
|
||||
|
||||
if let result = result, let filterString = element.allAttributes["filter"]?.text ?? groupStyle["filter"], let filterId = parseIdFromUrl(filterString), let effect = defEffects[filterId] {
|
||||
result.effect = effect
|
||||
}
|
||||
}
|
||||
return .none
|
||||
return result
|
||||
}
|
||||
|
||||
fileprivate var styleTable: [String: [String: String]] = [:]
|
||||
@ -240,10 +245,6 @@ open class SVGParser {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let filterString = element.allAttributes["filter"]?.text ?? nodeStyle["filter"], let filterId = parseIdFromUrl(filterString), let effect = defEffects[filterId] {
|
||||
parsedNode.effect = effect
|
||||
}
|
||||
|
||||
return parsedNode
|
||||
}
|
||||
|
||||
@ -1051,7 +1052,14 @@ open class SVGParser {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return effects.first?.value
|
||||
|
||||
if let effect = effects["SourceAlpha"] {
|
||||
return AlphaEffect(input: effect)
|
||||
}
|
||||
if let effect = effects[defaultSource] {
|
||||
return effect
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate func parseMask(_ mask: XMLIndexer) -> Shape? {
|
||||
|
@ -22,7 +22,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
nodesMap.add(node, view: self)
|
||||
self.renderer?.dispose()
|
||||
if let cache = animationCache {
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, animationCache: cache)
|
||||
}
|
||||
|
||||
if let _ = superview {
|
||||
@ -93,7 +93,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
initializeView()
|
||||
|
||||
if let cache = self.animationCache {
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, animationCache: cache)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
self.node = node
|
||||
nodesMap.add(node, view: self)
|
||||
if let cache = self.animationCache {
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, animationCache: cache)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
self.node = node
|
||||
nodesMap.add(node, view: self)
|
||||
if let cache = self.animationCache {
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, context: context, animationCache: cache)
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, animationCache: cache)
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,7 +186,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
return
|
||||
}
|
||||
ctx.concatenate(layoutHelper.getTransform(node, contentLayout, bounds.size.toMacaw()))
|
||||
renderer.render(force: false, opacity: node.opacity)
|
||||
renderer.render(in: ctx, force: false, opacity: node.opacity)
|
||||
}
|
||||
|
||||
private func localContext( _ callback: (CGContext) -> Void) {
|
||||
|
@ -35,8 +35,8 @@ class ShapeLayer: CAShapeLayer {
|
||||
ctx.concatenate(renderTransform)
|
||||
}
|
||||
|
||||
let renderer = RenderUtils.createNodeRenderer(node, context: renderContext, animationCache: animationCache, interval: renderingInterval)
|
||||
renderer.directRender(force: isForceRenderingEnabled)
|
||||
let renderer = RenderUtils.createNodeRenderer(node, animationCache: animationCache, interval: renderingInterval)
|
||||
renderer.directRender(in: ctx, force: isForceRenderingEnabled)
|
||||
renderer.dispose()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user