///|
fn render_path(
commands : Array[PathCommand],
node : SVGNode,
transform : Transform,
ctx : RenderState,
node_color : Color,
resources : RenderResources,
) -> Unit {
let polylines = path_to_polylines(commands, device_path_flatness(transform))
let closed_subpaths = path_subpath_closed_flags(commands)
let transformed_polylines : Array[Array[(Double, Double)]] = []
for polyline in polylines {
let transformed = polyline.map(fn(p) { transform.apply(p.0, p.1) })
transformed_polylines.push(transformed)
}
let fill_polylines : Array[Array[(Double, Double)]] = []
for polyline in transformed_polylines {
if polyline.length() >= 3 && polygon_area_abs(polyline) > 0.000000000001 {
fill_polylines.push(polyline)
}
}
fn draw_fill() -> Unit {
match resolve_paint_for_render(node.fill, node_color, resources) {
SolidColor(color) =>
if node.fill_opacity > 0.0 {
let fill_color = apply_opacity(
color,
node.fill_opacity * node.opacity,
)
if fill_polylines.length() > 0 {
raster_contours_coverage(
fill_polylines,
fill_color,
node.fill_rule,
ctx.setter,
ctx.width,
ctx.height,
)
}
}
LinearGrad(gradient) =>
if node.fill_opacity > 0.0 {
let bbox = get_shape_bounds(node.shape)
let inverse = transform.inverse()
let gradient_setter : ColorSink = {
set: (x, y, coverage_color) => {
if x < 0 || x >= ctx.width || y < 0 || y >= ctx.height {
return
}
let (local_x, local_y) = inverse.apply(
x.to_double() + 0.5,
y.to_double() + 0.5,
)
ctx.setter.pixel(
x,
y,
apply_coverage(
apply_opacity(
sample_linear_gradient(gradient, local_x, local_y, bbox),
node.fill_opacity * node.opacity,
),
coverage_color.a * 257,
),
)
},
}
raster_contours_coverage(
fill_polylines,
Color::white(),
node.fill_rule,
gradient_setter,
ctx.width,
ctx.height,
)
}
RadialGrad(gradient) =>
if node.fill_opacity > 0.0 {
let bbox = get_shape_bounds(node.shape)
let inverse = transform.inverse()
let gradient_setter : ColorSink = {
set: (x, y, coverage_color) => {
if x < 0 || x >= ctx.width || y < 0 || y >= ctx.height {
return
}
let (local_x, local_y) = inverse.apply(
x.to_double() + 0.5,
y.to_double() + 0.5,
)
ctx.setter.pixel(
x,
y,
apply_coverage(
apply_opacity(
sample_radial_gradient(gradient, local_x, local_y, bbox),
node.fill_opacity * node.opacity,
),
coverage_color.a * 257,
),
)
},
}
raster_contours_coverage(
fill_polylines,
Color::white(),
node.fill_rule,
gradient_setter,
ctx.width,
ctx.height,
)
}
Pattern(pattern) =>
if node.fill_opacity > 0.0 {
let bbox = get_shape_bounds(node.shape)
match
make_pattern_setter(
pattern,
bbox,
transform,
ctx,
resources,
node.fill_opacity * node.opacity,
node.image_sampling,
) {
Some(pattern_setter) =>
raster_contours_coverage(
fill_polylines,
Color::white(),
node.fill_rule,
pattern_setter,
ctx.width,
ctx.height,
)
None => ()
}
}
_ => ()
}
}
fn draw_stroke() -> Unit {
let stroke_paint = resolve_paint_for_render(
node.stroke.paint,
node_color,
resources,
)
match stroke_paint {
SolidColor(color) =>
for index, polyline in polylines {
if node.stroke_opacity > 0.0 && node.stroke.width > 0.0 {
let stroke_color = apply_opacity(
color,
node.stroke_opacity * node.opacity,
)
let closed = index < closed_subpaths.length() &&
closed_subpaths[index]
raster_affine_stroke(
polyline,
node.stroke,
transform,
stroke_color,
ctx.setter,
ctx.width,
ctx.height,
closed,
)
}
}
LinearGrad(_) | RadialGrad(_) | Pattern(_) =>
if node.stroke_opacity > 0.0 && node.stroke.width > 0.0 {
match
make_paint_server_setter(
stroke_paint,
node.shape,
transform,
ctx,
resources,
node.stroke_opacity * node.opacity,
node.image_sampling,
) {
Some(stroke_setter) =>
for index, polyline in polylines {
let closed = index < closed_subpaths.length() &&
closed_subpaths[index]
raster_affine_stroke(
polyline,
node.stroke,
transform,
Color::white(),
stroke_setter,
ctx.width,
ctx.height,
closed,
)
}
None => ()
}
}
_ => ()
}
}
fn draw_markers() -> Unit {
render_markers_for_path_commands(
commands, node, transform, ctx, resources, node_color,
)
}
render_paint_order(node.paint_order, draw_fill, draw_stroke, draw_markers)
}
///|
fn render_text(
x : Double,
y : Double,
text : String,
font_size : Double,
node : SVGNode,
transform : Transform,
ctx : RenderState,
node_color : Color,
resources : RenderResources,
) -> Unit {
let (tx, ty) = transform.apply(x, y)
let (sx, _) = transform.get_scale()
let scaled_size = (font_size * sx).to_int()
// Fill color for text
let fill_paint = resolve_paint_for_render(node.fill, node_color, resources)
let paint_server_setter = match fill_paint {
LinearGrad(_) | RadialGrad(_) | Pattern(_) =>
if node.fill_opacity > 0.0 {
make_paint_server_setter(
fill_paint,
node.shape,
transform,
ctx,
resources,
node.fill_opacity * node.opacity,
node.image_sampling,
)
} else {
None
}
_ => None
}
match ctx.text_to_paths {
Some(to_paths) => {
// Use font callback: render each character as path commands
let fill_color : Color? = match fill_paint {
SolidColor(color) =>
if node.fill_opacity > 0.0 {
Some(apply_opacity(color, node.fill_opacity * node.opacity))
} else {
None
}
LinearGrad(_) | RadialGrad(_) | Pattern(_) =>
match paint_server_setter {
Some(_) => Some(Color::white())
None => None
}
_ => None
}
let setter = paint_server_setter.unwrap_or(ctx.setter)
let mut cursor_x = tx
for i in 0..
raster_contours_coverage(
path_to_polylines(
translated,
device_path_flatness(Transform::identity()),
),
color,
NonZero,
setter,
ctx.width,
ctx.height,
)
None => ()
}
cursor_x = cursor_x + advance
}
}
None =>
// Fallback: bitmap font
match fill_paint {
SolidColor(color) =>
if node.fill_opacity > 0.0 {
let fill_color = apply_opacity(
color,
node.fill_opacity * node.opacity,
)
raster_text(
tx.round().to_int(),
ty.round().to_int(),
text,
scaled_size,
fill_color,
ctx.setter,
)
}
LinearGrad(_) | RadialGrad(_) | Pattern(_) =>
if node.fill_opacity > 0.0 {
match paint_server_setter {
Some(setter) =>
raster_text(
tx.round().to_int(),
ty.round().to_int(),
text,
scaled_size,
Color::white(),
setter,
)
None => ()
}
}
_ => ()
}
}
}
///|
/// Translate path commands by (dx, dy) offset
fn translate_path_commands(
commands : Array[PathCommand],
dx : Double,
dy : Double,
) -> Array[PathCommand] {
let result : Array[PathCommand] = []
for cmd in commands {
let translated : PathCommand = match cmd {
MoveTo(x, y) => MoveTo(x + dx, y + dy)
LineTo(x, y) => LineTo(x + dx, y + dy)
QuadraticCurveTo(cx, cy, x, y) =>
QuadraticCurveTo(cx + dx, cy + dy, x + dx, y + dy)
CurveTo(cx1, cy1, cx2, cy2, x, y) =>
CurveTo(cx1 + dx, cy1 + dy, cx2 + dx, cy2 + dy, x + dx, y + dy)
ClosePath => ClosePath
other => other
}
result.push(translated)
}
result
}
///|
/// Apply opacity to a color
fn apply_opacity(color : Color, opacity : Double) -> Color {
if opacity >= 1.0 {
color
} else if opacity <= 0.0 {
Color::transparent()
} else {
PremulColor16::from_color(color)
.scaled(unit_to_channel16(opacity))
.to_color()
}
}
///|
fn apply_coverage(color : Color, coverage : Int) -> Color {
PremulColor16::from_color(color).scaled(clamp_channel16(coverage)).to_color()
}
///|
/// Create a RenderState with default settings
fn RenderState::new(
setter : ColorSink,
width : Int,
height : Int,
) -> RenderState {
{
setter,
width,
height,
flatness: 0.5,
clip: None,
text_to_paths: None,
image_resolver: None,
target_image: None,
blend_pixel: None,
diagnostics: [],
}
}