Skip to content

Commit

Permalink
Introducing the Gradient SVG elements.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbarnach committed Jul 2, 2019
1 parent ff744b1 commit 6e7e0d7
Show file tree
Hide file tree
Showing 4 changed files with 638 additions and 11 deletions.
24 changes: 24 additions & 0 deletions Sources/Vaux/Builders.swift
Expand Up @@ -236,6 +236,20 @@ public func define(@HTMLBuilder child: () -> HTML) -> HTML {
return HTMLNode(tag: "dt", child: child())
}


/// Inserts a `<dfns>` elements into the SVG, and closes with </dfns>` after the filters.
/// - Parameter filters: The `SVG` filters to define.
public func definitions(filters: [SVGFilter]) -> SVG {
return SVGNode(tag: "defs", child: MultiSVGNode(children: filters))
}

/// Inserts a `<dfns>` elements into the SVG, and closes with </dfns>` after the gradient.
/// - Parameter gradient: The gradient to define.
/// - Note: Currently only one gradient per definition is supported. Having multiple definition in the same SVG element is valid.
public func definitions(gradient: SVGGradient) -> SVG {
return SVGNode(tag: "defs", child: gradient)
}

/// Inserts a `<dfn>` element into the HTML document, and closes with `</dfn>` after the contents of the closure.
/// - Parameter child: The `HTML` content to go inside `<dfn></dfn>` element.
public func defining(@HTMLBuilder child: () -> HTML) -> HTML {
Expand Down Expand Up @@ -373,6 +387,14 @@ public func form(@HTMLBuilder child: () -> HTML) -> HTML {
return HTMLNode(tag: "form", child: child())
}

// MARK: - G

/// Inserts a `<g>` elements into the SVG, and closes with </g>` after the contents of the closure.
/// - Parameter child: The `SVG` content to go inside the `<g></g>` element.
public func group(@SVGBuilder child: () -> SVG) -> SVG {
return SVGNode(tag: "g", child: child())
}

// MARK: - H

/// Inserts a `<head>` element into the HTML document, and closes with `</head>` after the contents of the closure.
Expand Down Expand Up @@ -902,6 +924,8 @@ public func template(@HTMLBuilder child: () -> HTML) -> HTML {
return HTMLNode(tag: "template", child: child())
}

/// Inserts a `<text>` element into the SVG element., and closes with `</text>` after the contents of the closure.
/// - Parameter value: The text to draw using SVG inside `<text></text>,.
public func text(_ value: String) -> SVG {
return SVGNode(tag: "text", child: value)
}
Expand Down
108 changes: 106 additions & 2 deletions Sources/Vaux/Enums.swift
Expand Up @@ -227,6 +227,7 @@ public enum MIME: String {
}

/// Create control for SVG path element.
/// TODO: Check and fully support commands (they are too restrictive so far).
public enum SVGPathControl {
case moveto(Double, Double, Bool)
case lineto(Double, Double, Bool)
Expand Down Expand Up @@ -255,9 +256,9 @@ public enum SVGPathControl {
case let .smoothCurveto(x, y, absolute):
return "S".case(absolute) + "\(String(format: "%g", x)) \(String(format: "%g", y))"
case let .quadraticBézierCurve(x, y, z, absolute):
return "Q".case(absolute) + "\(String(format: "%g", x)) \(String(format: "%g", y))\(z == nil ? "" : " \(String(format: "%g", z!))")"
return "Q".case(absolute) + "\(String(format: "%g", x)) \(String(format: "%g", y))\(z == nil ? "" : String(format: " %g", z!))"
case let .smoothQuadraticBézierCurveto(x, y, z, absolute):
return "T".case(absolute) + "\(String(format: "%g", x)) \(String(format: "%g", y))\(z == nil ? "" : " \(String(format: "%g", z!))")"
return "T".case(absolute) + "\(String(format: "%g", x)) \(String(format: "%g", y))\(z == nil ? "" : String(format: " %g", z!))"
case let .ellipticalArc(x, y, absolute):
return "A".case(absolute) + "\(String(format: "%g", x)) \(String(format: "%g", y))"
case .closePath:
Expand All @@ -273,3 +274,106 @@ fileprivate extension String {
return absolute ? self.uppercased() : self.lowercased()
}
}

public enum SVGFilterType {
case blend([Attribute])
case colorMatrix([Attribute])
case componentTransfer([Attribute])
case composite([Attribute])
case convolveMatrix([Attribute])
case diffuseLighting([Attribute])
case displacementMap([Attribute])
case distantLight([Attribute])
case flood([Attribute])
case gaussianBlur([Attribute])
case image([Attribute])
case merge([Attribute])
case morphology([Attribute])
case offset([Attribute])
case pointLight([Attribute])
case specularLighting([Attribute])
case spotLight([Attribute])
case tile([Attribute])
case turbulence([Attribute])

var node: SVG {
switch self {
case let .blend(attributes):
return attributes.reduce(SVGNode(tag: "feBlend")) {
$0.attr($1.key, $1.value)
}
case let .colorMatrix(attributes):
return attributes.reduce(SVGNode(tag: "feColorMatrix")) {
$0.attr($1.key, $1.value)
}
case let .componentTransfer(attributes):
return attributes.reduce(SVGNode(tag: "feComponentTransfer")) {
$0.attr($1.key, $1.value)
}
case let .composite(attributes):
return attributes.reduce(SVGNode(tag: "feComposite")) {
$0.attr($1.key, $1.value)
}
case let .convolveMatrix(attributes):
return attributes.reduce(SVGNode(tag: "feConvolveMatrix")) {
$0.attr($1.key, $1.value)
}
case let .diffuseLighting(attributes):
return attributes.reduce(SVGNode(tag: "feDiffuseLighting")) {
$0.attr($1.key, $1.value)
}
case let .displacementMap(attributes):
return attributes.reduce(SVGNode(tag: "feDisplacementMap")) {
$0.attr($1.key, $1.value)
}
case let .distantLight(attributes):
return attributes.reduce(SVGNode(tag: "feDistantLight")) {
$0.attr($1.key, $1.value)
}
case let .flood(attributes):
return attributes.reduce(SVGNode(tag: "feFlood")) {
$0.attr($1.key, $1.value)
}
case let .gaussianBlur(attributes):
return attributes.reduce(SVGNode(tag: "feGaussianBlur")) {
$0.attr($1.key, $1.value)
}
case let .image(attributes):
return attributes.reduce(SVGNode(tag: "feImage")) {
$0.attr($1.key, $1.value)
}
case let .merge(attributes):
return attributes.reduce(SVGNode(tag: "feMerge")) {
$0.attr($1.key, $1.value)
}
case let .morphology(attributes):
return attributes.reduce(SVGNode(tag: "feMorphology")) {
$0.attr($1.key, $1.value)
}
case let .offset(attributes):
return attributes.reduce(SVGNode(tag: "feOffset")) {
$0.attr($1.key, $1.value)
}
case let .pointLight(attributes):
return attributes.reduce(SVGNode(tag: "fePointLight")) {
$0.attr($1.key, $1.value)
}
case let .specularLighting(attributes):
return attributes.reduce(SVGNode(tag: "feSpecularLighting")) {
$0.attr($1.key, $1.value)
}
case let .spotLight(attributes):
return attributes.reduce(SVGNode(tag: "feSpotLight")) {
$0.attr($1.key, $1.value)
}
case let .tile(attributes):
return attributes.reduce(SVGNode(tag: "feTile")) {
$0.attr($1.key, $1.value)
}
case let .turbulence(attributes):
return attributes.reduce(SVGNode(tag: "feTurbulence")) {
$0.attr($1.key, $1.value)
}
}
}
}
98 changes: 98 additions & 0 deletions Sources/Vaux/SVG/SVGNode.swift
Expand Up @@ -110,6 +110,104 @@ struct HTMLSVGNode: HTML {
func getTag() -> String? {
return node.getTag()
}
}

/// Wraps multiples SVG filters into a `<filter></filter>` element.
public struct SVGFilter: SVG {
let node: SVGNode
let attributes: [Attribute]

public init(id: String,
types: [SVGFilterType],
attributes: [Attribute] = []) {
node = SVGNode(tag: "filter", child: MultiSVGNode(children: types.compactMap{ $0.node }))
self.attributes = [Attribute(key: "id", value: id)] + attributes
}

public func renderAsHTML(into stream: HTMLOutputStream, attributes: [Attribute]) {
node.renderAsHTML(into: stream, attributes: self.attributes + attributes)
}

public func getTag() -> String? {
return node.getTag()
}
}

/// Create a SVG linear or radial gradient.
public struct SVGGradient: SVG {

let node: SVGNode
let attributes: [Attribute]

public enum GradientType {
case linear
case radial
}

/// Create a gradient with full control.
/// - Parameters:
/// - id: The identifier of the gradient. Used to be applied to a SVG element.
/// - type: The type of gradient, linear or radial.
/// - offsets: The stops for the colors, 2 are needed to get a valid gradient.
/// - attributes: A list of attributes to apply to the gradient element. Do not set `id` here.
public init(id: String,
type: GradientType,
offsets: [(offset: String, style: [StyleAttribute])],
attributes: [Attribute] = []) {
let stops = offsets.compactMap {
SVGNode(tag: "stop")
.attr("offset", $0.offset)
.style($0.style)
}
switch type {
case .linear:
node = SVGNode(tag: "linearGradient", child: MultiSVGNode(children: stops))
case .radial:
node = SVGNode(tag: "radialGradient", child: MultiSVGNode(children: stops))
}
self.attributes = [Attribute(key: "id", value: id)] + attributes
}

/// Convinent initializer to create a linear gradient.
public init(id: String,
controls: (x1: String, y1: String, x2: String, y2: String),
offsets: [(offset: String, style: [StyleAttribute])]) {
let stops = offsets.compactMap {
SVGNode(tag: "stop")
.attr("offset", $0.offset)
.style($0.style)
}
node = SVGNode(tag: "linearGradient", child: MultiSVGNode(children: stops))
attributes = [Attribute(key: "id", value: id),
Attribute(key: "x1", value: controls.x1),
Attribute(key: "y1", value: controls.y1),
Attribute(key: "x2", value: controls.x2),
Attribute(key: "y2", value: controls.y2)]
}

/// Convinent initializer to create a radial gradient.
public init(id: String,
controls: (cx: String, cy: String, r: String, fx: String, fy: String),
offsets: [(offset: String, style: [StyleAttribute])]) {
let stops = offsets.compactMap {
SVGNode(tag: "stop")
.attr("offset", $0.offset)
.style($0.style)
}
node = SVGNode(tag: "radialGradient", child: MultiSVGNode(children: stops))
attributes = [Attribute(key: "id", value: id),
Attribute(key: "cx", value: controls.cx),
Attribute(key: "cy", value: controls.cy),
Attribute(key: "r", value: controls.r),
Attribute(key: "fx", value: controls.fx),
Attribute(key: "fy", value: controls.fy)]
}

public func renderAsHTML(into stream: HTMLOutputStream, attributes: [Attribute]) {
node.renderAsHTML(into: stream, attributes: self.attributes + attributes)
}

public func getTag() -> String? {
return node.getTag()
}
}

0 comments on commit 6e7e0d7

Please sign in to comment.