///|
fn parse_clip_path_from_attrs(
attrs : Array[(String, String)],
ctx : SVGParseContext,
) -> Unit {
let id = match get_attr(attrs, "id") {
Some(v) => v
None => ""
}
if id.length() == 0 {
return
}
let clip_rule = match get_attr(attrs, "clip-rule") {
Some(v) => parse_fill_rule(v)
None => NonZero
}
let transform = match get_attr(attrs, "transform") {
Some(v) => parse_transform(v)
None => Transform::identity()
}
let units = match get_attr(attrs, "clipPathUnits") {
Some(v) => parse_clip_path_units(v)
None => UserSpaceOnUse
}
let clip = ClipPath::{
id,
shape: Group,
content: [],
transform,
clip_rule,
units,
}
ctx.clips.add(clip)
}
///|
fn parse_clip_path(
parser : SVGParser,
attrs : Array[(String, String)],
ctx : SVGParseContext,
tag_name : String,
) -> Unit {
let temp = SVGNode::new(Group)
apply_attributes(temp, attrs)
let style = resolve_element_computed_style(
temp,
tag_name,
attrs,
ctx,
ctx.current_style(),
true,
)
parse_children(parser, temp, tag_name, attrs, ctx, style)
let id = match get_attr(attrs, "id") {
Some(v) => v
None => ""
}
if id.length() == 0 {
return
}
let clip_rule = temp.clip_rule
let transform = match get_attr(attrs, "transform") {
Some(v) => parse_transform(v)
None => Transform::identity()
}
let units = match get_attr(attrs, "clipPathUnits") {
Some(v) => parse_clip_path_units(v)
None => UserSpaceOnUse
}
let mut shape : Shape = Group
for child in temp.children {
match child.shape {
Group => ()
_ => {
shape = child.shape
break
}
}
}
let clip = ClipPath::{
id,
shape,
content: temp.children,
transform,
clip_rule,
units,
}
ctx.clips.add(clip)
}
///|
fn parse_filter_matrix(value : String) -> FixedArray[Double]? {
let numbers = parse_number_list(value)
if numbers.length() != 20 {
return None
}
Some([
numbers[0],
numbers[1],
numbers[2],
numbers[3],
numbers[4],
numbers[5],
numbers[6],
numbers[7],
numbers[8],
numbers[9],
numbers[10],
numbers[11],
numbers[12],
numbers[13],
numbers[14],
numbers[15],
numbers[16],
numbers[17],
numbers[18],
numbers[19],
])
}
///|
priv struct FilterPrimitiveChild {
tag : String
attrs : Array[(String, String)]
}
///|
fn parse_filter_channel(value : String) -> FilterChannel {
match value {
"R" => ChannelR
"G" => ChannelG
"B" => ChannelB
_ => ChannelA
}
}
///|
fn parse_component_transfer_function(
attrs : Array[(String, String)],
) -> ComponentTransferFunction {
match get_attr(attrs, "type").unwrap_or("identity") {
"table" =>
TransferTable(
get_attr(attrs, "tableValues").map(parse_number_list).unwrap_or([]),
)
"discrete" =>
TransferDiscrete(
get_attr(attrs, "tableValues").map(parse_number_list).unwrap_or([]),
)
"linear" =>
TransferLinear(
slope=get_attr(attrs, "slope").map(parse_number).unwrap_or(1.0),
intercept=get_attr(attrs, "intercept").map(parse_number).unwrap_or(0.0),
)
"gamma" =>
TransferGamma(
amplitude=get_attr(attrs, "amplitude").map(parse_number).unwrap_or(1.0),
exponent=get_attr(attrs, "exponent").map(parse_number).unwrap_or(1.0),
offset=get_attr(attrs, "offset").map(parse_number).unwrap_or(0.0),
)
_ => TransferIdentity
}
}
///|
fn filter_child_attrs(
children : Array[FilterPrimitiveChild],
tag : String,
) -> Array[(String, String)]? {
for child in children {
if child.tag == tag {
return Some(child.attrs)
}
}
None
}
///|
fn parse_filter_light(children : Array[FilterPrimitiveChild]) -> FilterLight {
for child in children {
match child.tag {
"fePointLight" =>
return PointLight(
x=get_attr(child.attrs, "x").map(parse_number).unwrap_or(0.0),
y=get_attr(child.attrs, "y").map(parse_number).unwrap_or(0.0),
z=get_attr(child.attrs, "z").map(parse_number).unwrap_or(0.0),
)
"feSpotLight" =>
return SpotLight(
x=get_attr(child.attrs, "x").map(parse_number).unwrap_or(0.0),
y=get_attr(child.attrs, "y").map(parse_number).unwrap_or(0.0),
z=get_attr(child.attrs, "z").map(parse_number).unwrap_or(0.0),
points_at_x=get_attr(child.attrs, "pointsAtX")
.map(parse_number)
.unwrap_or(0.0),
points_at_y=get_attr(child.attrs, "pointsAtY")
.map(parse_number)
.unwrap_or(0.0),
points_at_z=get_attr(child.attrs, "pointsAtZ")
.map(parse_number)
.unwrap_or(0.0),
exponent=get_attr(child.attrs, "specularExponent")
.map(parse_number)
.unwrap_or(1.0),
limiting_cone_angle=get_attr(child.attrs, "limitingConeAngle").map(
parse_number,
),
)
"feDistantLight" =>
return DistantLight(
azimuth=get_attr(child.attrs, "azimuth")
.map(parse_number)
.unwrap_or(0.0),
elevation=get_attr(child.attrs, "elevation")
.map(parse_number)
.unwrap_or(0.0),
)
_ => ()
}
}
DistantLight(azimuth=0.0, elevation=0.0)
}
///|
fn parse_filter_lighting_color(attrs : Array[(String, String)]) -> Color {
parse_color(get_attr(attrs, "lighting-color").unwrap_or("white"))
}
///|
fn parse_filter_primitive(
tag : String,
attrs : Array[(String, String)],
children : Array[FilterPrimitiveChild],
) -> FilterGraphPrimitive? {
let input = get_attr(attrs, "in").unwrap_or("")
let result = get_attr(attrs, "result").unwrap_or("")
match tag {
"feColorMatrix" =>
if get_attr(attrs, "type").unwrap_or("matrix") != "matrix" {
None
} else {
match get_attr(attrs, "values") {
Some(value) =>
parse_filter_matrix(value).map(fn(matrix) {
GraphColorMatrix(input~, result~, matrix~)
})
None => None
}
}
"feGaussianBlur" => {
let values = get_attr(attrs, "stdDeviation")
.map(parse_number_list)
.unwrap_or([])
let radius_x = if values.length() == 0 { 0.0 } else { values[0] }
let radius_y = if values.length() < 2 { radius_x } else { values[1] }
Some(GraphGaussianBlur(input~, result~, radius_x~, radius_y~))
}
"feOffset" =>
Some(
GraphOffset(
input~,
result~,
dx=get_attr(attrs, "dx").map(parse_number).unwrap_or(0.0),
dy=get_attr(attrs, "dy").map(parse_number).unwrap_or(0.0),
),
)
"feBlend" =>
Some(
GraphBlend(
input~,
input2=get_attr(attrs, "in2").unwrap_or(""),
result~,
mode=parse_blend_mode(get_attr(attrs, "mode").unwrap_or("normal")),
),
)
"feComposite" => {
let operator = match get_attr(attrs, "operator").unwrap_or("over") {
"in" => CompositeIn
"out" => CompositeOut
"atop" => CompositeAtop
"xor" => CompositeXor
"arithmetic" => CompositeArithmetic
_ => CompositeOver
}
Some(
GraphComposite(
input~,
input2=get_attr(attrs, "in2").unwrap_or(""),
result~,
operator~,
k1=get_attr(attrs, "k1").map(parse_number).unwrap_or(0.0),
k2=get_attr(attrs, "k2").map(parse_number).unwrap_or(0.0),
k3=get_attr(attrs, "k3").map(parse_number).unwrap_or(0.0),
k4=get_attr(attrs, "k4").map(parse_number).unwrap_or(0.0),
),
)
}
"feFlood" => {
let base = parse_color(get_attr(attrs, "flood-color").unwrap_or("black"))
let opacity = get_attr(attrs, "flood-opacity")
.map(parse_number)
.unwrap_or(1.0)
let alpha = (base.a.to_double() * opacity).round().to_int()
Some(GraphFlood(result~, color={ ..base, a: alpha }))
}
"feMerge" => {
let inputs : Array[String] = []
for child in children {
if child.tag == "feMergeNode" {
inputs.push(get_attr(child.attrs, "in").unwrap_or(""))
}
}
Some(GraphMerge(result~, inputs~))
}
"feComponentTransfer" =>
Some(
GraphComponentTransfer(
input~,
result~,
red=filter_child_attrs(children, "feFuncR")
.map(parse_component_transfer_function)
.unwrap_or(TransferIdentity),
green=filter_child_attrs(children, "feFuncG")
.map(parse_component_transfer_function)
.unwrap_or(TransferIdentity),
blue=filter_child_attrs(children, "feFuncB")
.map(parse_component_transfer_function)
.unwrap_or(TransferIdentity),
alpha=filter_child_attrs(children, "feFuncA")
.map(parse_component_transfer_function)
.unwrap_or(TransferIdentity),
),
)
"feMorphology" => {
let radii = get_attr(attrs, "radius").map(parse_number_list).unwrap_or([])
let radius_x = if radii.length() == 0 { 0.0 } else { radii[0] }
let radius_y = if radii.length() < 2 { radius_x } else { radii[1] }
Some(
GraphMorphology(
input~,
result~,
radius_x~,
radius_y~,
operator=if get_attr(attrs, "operator").unwrap_or("erode") == "dilate" {
MorphologyDilate
} else {
MorphologyErode
},
),
)
}
"feConvolveMatrix" => {
let order = get_attr(attrs, "order").map(parse_number_list).unwrap_or([])
let order_x = if order.length() == 0 { 3 } else { order[0].to_int() }
let order_y = if order.length() < 2 { order_x } else { order[1].to_int() }
let kernel = get_attr(attrs, "kernelMatrix")
.map(parse_number_list)
.unwrap_or([])
let kernel_sum = kernel.fold(init=0.0, (sum, value) => sum + value)
let default_divisor = if kernel_sum.abs() <= 0.000001 {
1.0
} else {
kernel_sum
}
Some(
GraphConvolveMatrix(
input~,
result~,
order_x~,
order_y~,
kernel~,
divisor=get_attr(attrs, "divisor")
.map(parse_number)
.unwrap_or(default_divisor),
bias=get_attr(attrs, "bias").map(parse_number).unwrap_or(0.0),
target_x=get_attr(attrs, "targetX")
.map(parse_number)
.map(fn(v) { v.to_int() })
.unwrap_or(order_x / 2),
target_y=get_attr(attrs, "targetY")
.map(parse_number)
.map(fn(v) { v.to_int() })
.unwrap_or(order_y / 2),
edge_mode=match get_attr(attrs, "edgeMode").unwrap_or("duplicate") {
"wrap" => EdgeWrap
"none" => EdgeNone
_ => EdgeDuplicate
},
preserve_alpha=get_attr(attrs, "preserveAlpha").unwrap_or("false") ==
"true",
),
)
}
"feDisplacementMap" =>
Some(
GraphDisplacementMap(
input~,
input2=get_attr(attrs, "in2").unwrap_or(""),
result~,
scale=get_attr(attrs, "scale").map(parse_number).unwrap_or(0.0),
x_channel=parse_filter_channel(
get_attr(attrs, "xChannelSelector").unwrap_or("A"),
),
y_channel=parse_filter_channel(
get_attr(attrs, "yChannelSelector").unwrap_or("A"),
),
),
)
"feTurbulence" => {
let base = get_attr(attrs, "baseFrequency")
.map(parse_number_list)
.unwrap_or([])
let base_x = if base.length() == 0 { 0.0 } else { base[0] }
let base_y = if base.length() < 2 { base_x } else { base[1] }
Some(
GraphTurbulence(
result~,
base_x~,
base_y~,
octaves=get_attr(attrs, "numOctaves")
.map(parse_number)
.map(fn(v) { v.to_int() })
.unwrap_or(1),
seed=get_attr(attrs, "seed").map(parse_number).unwrap_or(0.0),
stitch=get_attr(attrs, "stitchTiles").unwrap_or("noStitch") ==
"stitch",
fractal_noise=get_attr(attrs, "type").unwrap_or("turbulence") ==
"fractalNoise",
),
)
}
"feTile" => Some(GraphTile(input~, result~))
"feImage" =>
Some(
GraphImage(
result~,
href=get_href_attr(attrs).unwrap_or(""),
x=get_attr(attrs, "x").map(parse_number).unwrap_or(0.0),
y=get_attr(attrs, "y").map(parse_number).unwrap_or(0.0),
width=get_attr(attrs, "width").map(parse_number).unwrap_or(0.0),
height=get_attr(attrs, "height").map(parse_number).unwrap_or(0.0),
),
)
"feDiffuseLighting" =>
Some(
GraphDiffuseLighting(
input~,
result~,
surface_scale=get_attr(attrs, "surfaceScale")
.map(parse_number)
.unwrap_or(1.0),
diffuse_constant=get_attr(attrs, "diffuseConstant")
.map(parse_number)
.unwrap_or(1.0),
color=parse_filter_lighting_color(attrs),
light=parse_filter_light(children),
),
)
"feSpecularLighting" =>
Some(
GraphSpecularLighting(
input~,
result~,
surface_scale=get_attr(attrs, "surfaceScale")
.map(parse_number)
.unwrap_or(1.0),
specular_constant=get_attr(attrs, "specularConstant")
.map(parse_number)
.unwrap_or(1.0),
specular_exponent=get_attr(attrs, "specularExponent")
.map(parse_number)
.unwrap_or(1.0),
color=parse_filter_lighting_color(attrs),
light=parse_filter_light(children),
),
)
_ => None
}
}
///|
fn parse_filter_primitive_children(
parser : SVGParser,
parent_tag : String,
) -> Array[FilterPrimitiveChild] {
let children : Array[FilterPrimitiveChild] = []
while !parser.is_end() {
match parser.advance_event() {
XmlStart(element) => {
let tag = element.name
children.push({ tag, attrs: element.attributes })
skip_to_end_tag(parser, tag)
}
XmlEmpty(element) =>
children.push({ tag: element.name, attrs: element.attributes })
XmlEnd(name) => if name == parent_tag { break }
XmlEof => break
_ => ()
}
}
children
}
///|
fn parse_filter_graph(
parser : SVGParser,
attrs : Array[(String, String)],
ctx : SVGParseContext,
tag_name : String,
) -> Unit {
let primitives : Array[FilterGraphPrimitive] = []
while !parser.is_end() {
match parser.advance_event() {
XmlStart(element) => {
let primitive_tag = element.name
let primitive_children = parse_filter_primitive_children(
parser, primitive_tag,
)
match
parse_filter_primitive(
primitive_tag,
element.attributes,
primitive_children,
) {
Some(primitive) => primitives.push(primitive)
None => ()
}
}
XmlEmpty(element) =>
match parse_filter_primitive(element.name, element.attributes, []) {
Some(primitive) => primitives.push(primitive)
None => ()
}
XmlEnd(name) => if name == tag_name { break }
XmlEof => break
_ => ()
}
}
register_filter_graph(attrs, ctx, primitives)
}
///|
fn register_filter_graph(
attrs : Array[(String, String)],
ctx : SVGParseContext,
primitives : Array[FilterGraphPrimitive],
) -> Unit {
let id = get_attr(attrs, "id").unwrap_or("")
if id.length() == 0 {
return
}
let units = match get_attr(attrs, "filterUnits") {
Some(value) => parse_mask_units(value)
None => ObjectBoundingBox
}
let primitive_units = match get_attr(attrs, "primitiveUnits") {
Some(value) => parse_mask_units(value)
None => UserSpaceOnUse
}
let (default_x, default_y, default_width, default_height) = match units {
ObjectBoundingBox => (-0.1, -0.1, 1.2, 1.2)
UserSpaceOnUse => (-0.1, -0.1, 1.2, 1.2)
}
let (x, x_is_percent) = match get_attr(attrs, "x") {
Some(value) => parse_mask_length(value, units)
None => (default_x, true)
}
let (y, y_is_percent) = match get_attr(attrs, "y") {
Some(value) => parse_mask_length(value, units)
None => (default_y, true)
}
let (width, width_is_percent) = match get_attr(attrs, "width") {
Some(value) => parse_mask_length(value, units)
None => (default_width, true)
}
let (height, height_is_percent) = match get_attr(attrs, "height") {
Some(value) => parse_mask_length(value, units)
None => (default_height, true)
}
ctx.filter_graphs.add({
id,
primitives,
units,
primitive_units,
x,
y,
width,
height,
x_is_percent,
y_is_percent,
width_is_percent,
height_is_percent,
})
}
///|
fn parse_mask_from_attrs(
attrs : Array[(String, String)],
ctx : SVGParseContext,
) -> Unit {
parse_mask_with_content(attrs, [], ctx)
}
///|
fn parse_mask(
parser : SVGParser,
attrs : Array[(String, String)],
ctx : SVGParseContext,
tag_name : String,
) -> Unit {
let temp = SVGNode::new(Group)
apply_attributes(temp, attrs)
let style = resolve_element_computed_style(
temp,
tag_name,
attrs,
ctx,
ctx.current_style(),
true,
)
parse_children(parser, temp, tag_name, attrs, ctx, style)
parse_mask_with_content(attrs, temp.children, ctx)
}
///|
fn parse_mask_with_content(
attrs : Array[(String, String)],
content : Array[SVGNode],
ctx : SVGParseContext,
) -> Unit {
let id = match get_attr(attrs, "id") {
Some(v) => v
None => ""
}
if id.length() == 0 {
return
}
let mask_units = match get_attr(attrs, "maskUnits") {
Some(v) => parse_mask_units(v)
None => ObjectBoundingBox
}
let mask_content_units = match get_attr(attrs, "maskContentUnits") {
Some(v) => parse_mask_units(v)
None => UserSpaceOnUse
}
let mask_type = match get_attr(attrs, "mask-type") {
Some(v) => parse_mask_type(v)
None => Luminance
}
let mut has_bounds = false
let mut x = 0.0
let mut y = 0.0
let mut width = 0.0
let mut height = 0.0
let mut x_is_percent = false
let mut y_is_percent = false
let mut width_is_percent = false
let mut height_is_percent = false
if get_attr(attrs, "x") is Some(v) {
let (val, is_percent) = parse_mask_length(v, mask_units)
x = val
x_is_percent = is_percent
has_bounds = true
}
if get_attr(attrs, "y") is Some(v) {
let (val, is_percent) = parse_mask_length(v, mask_units)
y = val
y_is_percent = is_percent
has_bounds = true
}
if get_attr(attrs, "width") is Some(v) {
let (val, is_percent) = parse_mask_length(v, mask_units)
width = val
width_is_percent = is_percent
has_bounds = true
}
if get_attr(attrs, "height") is Some(v) {
let (val, is_percent) = parse_mask_length(v, mask_units)
height = val
height_is_percent = is_percent
has_bounds = true
}
let base = if has_bounds {
Mask::with_bounds(id, content, x, y, width, height)
} else {
Mask::new(id, content)
}
let mask = if has_bounds {
{
..base,
x_is_percent,
y_is_percent,
width_is_percent,
height_is_percent,
mask_units,
mask_content_units,
mask_type,
}
} else {
{ ..base, mask_units, mask_content_units, mask_type }
}
ctx.masks.add(mask)
}