///|
priv enum ResolvedPaint {
None
SolidColor(Color)
LinearGrad(LinearGradient)
RadialGrad(RadialGradient)
Pattern(Pattern)
}
///|
fn resolve_paint_fallback(
fallback : PaintFallback,
node_color : Color,
) -> ResolvedPaint {
match fallback {
NoPaint => None
SolidColor(color) => SolidColor(color)
CurrentColor => SolidColor(node_color)
}
}
///|
fn pattern_is_valid(pattern : Pattern) -> Bool {
if pattern.width <= 0.0 || pattern.height <= 0.0 {
return false
}
if !pattern.transform.is_invertible() {
return false
}
pattern.content.length() > 0
}
///|
fn resolve_paint_for_render(
paint : Paint,
node_color : Color,
resources : RenderResources,
) -> ResolvedPaint {
match paint {
None => None
SolidColor(color) => SolidColor(color)
LinearGrad(grad) =>
if grad.stops.length() == 0 {
None
} else {
LinearGrad(grad)
}
RadialGrad(grad) =>
if grad.stops.length() == 0 {
None
} else {
RadialGrad(grad)
}
CurrentColor => SolidColor(node_color)
PaintServerRef(id, fallback) =>
match resources.gradients.resolve(id) {
Some(Linear(grad)) =>
if !grad.transform.is_invertible() {
resolve_paint_fallback(fallback, node_color)
} else if grad.stops.length() == 0 {
None
} else {
LinearGrad(grad)
}
Some(Radial(grad)) =>
if !grad.transform.is_invertible() {
resolve_paint_fallback(fallback, node_color)
} else if grad.stops.length() == 0 {
None
} else {
RadialGrad(grad)
}
None =>
match resources.patterns.resolve(id) {
Some(pattern) =>
if pattern_is_valid(pattern) {
Pattern(pattern)
} else {
resolve_paint_fallback(fallback, node_color)
}
None => resolve_paint_fallback(fallback, node_color)
}
}
}
}
///|
fn render_paint_order(
order : PaintOrder,
draw_fill : () -> Unit,
draw_stroke : () -> Unit,
draw_markers : () -> Unit,
) -> Unit {
for item in order.order {
match item {
Fill => draw_fill()
Stroke => draw_stroke()
Markers => draw_markers()
}
}
}
///|
priv struct PatternTile {
image : Image
x : Double
y : Double
width : Double
height : Double
}
///|
fn build_pattern_tile(
pattern : Pattern,
bbox : BoundingBox,
transform : Transform,
ctx : RenderState,
resources : RenderResources,
) -> PatternTile? {
if string_array_contains(resources.active_patterns, pattern.id) {
ctx.diagnostics.push({
kind: ResourceCycle,
stage: Paint,
resource: pattern.id,
node_id: "",
})
return None
}
let bbox_width = bbox.width()
let bbox_height = bbox.height()
let (tile_x, tile_y, tile_width, tile_height) = match pattern.pattern_units {
UserSpaceOnUse => (pattern.x, pattern.y, pattern.width, pattern.height)
ObjectBoundingBox =>
(
bbox.min_x + pattern.x * bbox_width,
bbox.min_y + pattern.y * bbox_height,
pattern.width * bbox_width,
pattern.height * bbox_height,
)
}
if tile_width <= 0.0 || tile_height <= 0.0 {
return None
}
let effective_transform = transform.multiply(pattern.transform)
let (scale_x, scale_y) = effective_transform.get_scale()
let mut pixel_width = ceil_to_int(tile_width * scale_x)
let mut pixel_height = ceil_to_int(tile_height * scale_y)
if pixel_width < 1 {
pixel_width = 1
}
if pixel_height < 1 {
pixel_height = 1
}
pixel_width = min_int(pixel_width, 4096)
pixel_height = min_int(pixel_height, 4096)
let texel_count = pixel_width.to_double() * pixel_height.to_double()
if texel_count > 16000000.0 {
let reduction = (16000000.0 / texel_count).sqrt()
pixel_width = max_int(
1,
(pixel_width.to_double() * reduction).floor().to_int(),
)
pixel_height = max_int(
1,
(pixel_height.to_double() * reduction).floor().to_int(),
)
}
if ctx.width > 0 && pixel_width > ctx.width {
pixel_width = ctx.width
}
if ctx.height > 0 && pixel_height > ctx.height {
pixel_height = ctx.height
}
let cache_key = pattern.id +
":" +
bbox.min_x.to_string() +
":" +
bbox.min_y.to_string() +
":" +
bbox.max_x.to_string() +
":" +
bbox.max_y.to_string() +
":" +
transform.a.to_string() +
":" +
transform.b.to_string() +
":" +
transform.c.to_string() +
":" +
transform.d.to_string() +
":" +
transform.e.to_string() +
":" +
transform.f.to_string() +
":" +
pixel_width.to_string() +
":" +
pixel_height.to_string()
match resources.pattern_tiles.get(cache_key) {
Some(tile) => return Some(tile)
None => ()
}
let image = Image::new(pixel_width, pixel_height)
let active_patterns = resources.active_patterns.copy()
active_patterns.push(pattern.id)
let nested_resources = { ..resources, active_patterns, }
let tile_ctx = {
setter: make_image_compositing_setter(image),
width: pixel_width,
height: pixel_height,
flatness: ctx.flatness,
clip: None,
text_to_paths: ctx.text_to_paths,
image_resolver: ctx.image_resolver,
target_image: Some(image),
blend_pixel: None,
diagnostics: ctx.diagnostics,
}
let pixel_scale = Transform::scale(
pixel_width.to_double() / tile_width,
pixel_height.to_double() / tile_height,
)
let content_transform = match pattern.view_box {
Some(view_box) =>
pixel_scale.multiply(
view_box.get_transform(
tile_width,
tile_height,
pattern.preserve_aspect_ratio,
),
)
None => {
let tile_offset = Transform::translate(-tile_x, -tile_y)
match pattern.pattern_content_units {
UserSpaceOnUse => pixel_scale.multiply(tile_offset)
ObjectBoundingBox =>
pixel_scale.multiply(
tile_offset.multiply(
Transform::translate(bbox.min_x, bbox.min_y).multiply(
Transform::scale(bbox_width, bbox_height),
),
),
)
}
}
}
for child in pattern.content {
render_node(
child,
content_transform,
tile_ctx,
nested_resources,
true,
Color::black(),
)
}
let tile : PatternTile = {
image,
x: tile_x,
y: tile_y,
width: tile_width,
height: tile_height,
}
resources.pattern_tiles.set(cache_key, tile)
Some(tile)
}
///|
fn make_pattern_setter(
pattern : Pattern,
bbox : BoundingBox,
transform : Transform,
ctx : RenderState,
resources : RenderResources,
opacity : Double,
sampling : ImageSampling,
) -> ColorSink? {
let tile = match
build_pattern_tile(pattern, bbox, transform, ctx, resources) {
Some(value) => value
None => return None
}
let inverse = transform.inverse()
let pattern_inverse = pattern.transform.inverse()
Some({
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,
)
let (pattern_x, pattern_y) = pattern_inverse.apply(local_x, local_y)
let tile_u = (pattern_x - tile.x) / tile.width
let tile_v = (pattern_y - tile.y) / tile.height
let u = tile_u - tile_u.floor()
let v = tile_v - tile_v.floor()
let color = sample_raster_color(
tile.image,
u * tile.image.width.to_double(),
v * tile.image.height.to_double(),
sampling,
true,
)
if color.a > 0 {
ctx.setter.pixel(
x,
y,
apply_coverage(apply_opacity(color, opacity), coverage_color.a * 257),
)
}
},
})
}
///|