// Port of rough.js 4.6.6 src/fillers/: filler.ts, scan-line-hachure.ts and
// the pattern fillers.
///|
/// `getFiller(o, helper).fillPolygons(polygonList, o)`.
fn fill_polygons(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> OpSet {
match o.fill_style {
Zigzag => zigzag_fill_polygons(polygon_list, o)
CrossHatch => hatch_fill_polygons(polygon_list, o)
Dots => dot_fill_polygons(polygon_list, o)
Dashed => dashed_fill_polygons(polygon_list, o)
ZigzagLine => zigzag_line_fill_polygons(polygon_list, o)
// `solid` never reaches the pattern fillers in the generator; rough.js'
// getFiller maps it (like any unknown name) to the hachure filler.
Hachure | Solid => hachure_fill_polygons(polygon_list, o)
}
}
///|
/// The gap of the zigzag and dots fillers:
/// `max(hachureGap < 0 ? strokeWidth * 4 : hachureGap, 0.1)`.
fn filler_gap(o : ResolvedOptions) -> Double {
let mut gap = o.hachure_gap
if gap < 0.0 {
gap = o.stroke_width * 4.0
}
@jsmath.max(gap, 0.1)
}
///|
/// `ZigZagFiller.fillPolygons` (zigzag-filler.ts).
fn zigzag_fill_polygons(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> OpSet {
let gap = filler_gap(o)
let o2 = { ..o, hachure_gap: gap, }
let lines = polygon_hachure_lines(polygon_list, o2)
let zigzag_angle = @jsmath.PI / 180.0 * o.hachure_angle
let zigzag_lines = []
let dgx = gap * 0.5 * @jsmath.cos(zigzag_angle)
let dgy = gap * 0.5 * @jsmath.sin(zigzag_angle)
for line in lines {
let (p1, p2) = line
if truthy(@geometry.line_length(p1, p2)) {
zigzag_lines.push((Point::new(p1.x - dgx, p1.y + dgy), p2.copy()))
zigzag_lines.push((Point::new(p1.x + dgx, p1.y - dgy), p2.copy()))
}
}
{ kind: FillSketch, ops: render_lines(zigzag_lines, o), }
}
///|
/// `HatchFiller.fillPolygons` (hatch-filler.ts): hachures at the angle and
/// at the angle + 90. The second pass sees the polygons as the first pass
/// left them (rotated there and back by hachure-fill).
fn hatch_fill_polygons(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> OpSet {
let set = hachure_fill_polygons(polygon_list, o)
let o2 = { ..o, hachure_angle: o.hachure_angle + 90.0, }
let set2 = hachure_fill_polygons(polygon_list, o2)
{ kind: set.kind, ops: [..set.ops, ..set2.ops], }
}
///|
/// `DotFiller.fillPolygons` (dot-filler.ts). The dot positions come from
/// `Math.random()`, i.e. from the options' random source.
fn dot_fill_polygons(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> OpSet {
let o = { ..o, hachure_angle: 0.0, }
let lines = polygon_hachure_lines(polygon_list, o)
let ops = []
let gap = filler_gap(o)
let mut fweight = o.fill_weight
if fweight < 0.0 {
fweight = o.stroke_width / 2.0
}
let ro = gap / 4.0
for line in lines {
let length = @geometry.line_length(line.0, line.1)
let dl = length / gap
let count = dl.ceil() - 1.0
let offset = length - count * gap
let x = (line.0.x + line.1.x) / 2.0 - gap / 4.0
let min_y = @jsmath.min(line.0.y, line.1.y)
for i = 0.0; i < count; i = i + 1.0 {
let y = min_y + offset + i * gap
let cx = x - ro + o.source.fallback() * 2.0 * ro
let cy = y - ro + o.source.fallback() * 2.0 * ro
let el = ellipse(cx, cy, fweight, fweight, o)
ops.append(el.ops)
}
}
{ kind: FillSketch, ops, }
}
///|
/// `DashedFiller.fillPolygons` (dashed-filler.ts).
fn dashed_fill_polygons(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> OpSet {
let lines = polygon_hachure_lines(polygon_list, o)
let default_gap = if o.hachure_gap < 0.0 {
o.stroke_width * 4.0
} else {
o.hachure_gap
}
let offset = if o.dash_offset < 0.0 { default_gap } else { o.dash_offset }
let gap = if o.dash_gap < 0.0 { default_gap } else { o.dash_gap }
let ops = []
for line in lines {
let length = @geometry.line_length(line.0, line.1)
let count = (length / (offset + gap)).floor()
let start_offset = (length + gap - count * (offset + gap)) / 2.0
let (p1, p2) = if line.0.x > line.1.x { (line.1, line.0) } else { line }
let alpha = @jsmath.atan((p2.y - p1.y) / (p2.x - p1.x))
// rough.js never finishes when offset + gap is 0 (count is Infinity).
if count.is_inf() {
continue
}
for i = 0.0; i < count; i = i + 1.0 {
let lstart = i * (offset + gap)
let lend = lstart + offset
let cos = @jsmath.cos(alpha)
let sin = @jsmath.sin(alpha)
let start_x = p1.x + lstart * cos + start_offset * cos
let start_y = p1.y + lstart * sin + start_offset * sin
let end_x = p1.x + lend * cos + start_offset * cos
let end_y = p1.y + lend * sin + start_offset * sin
ops.append(double_line_fill_ops(start_x, start_y, end_x, end_y, o))
}
}
{ kind: FillSketch, ops, }
}
///|
/// `ZigZagLineFiller.fillPolygons` (zigzag-line-filler.ts).
fn zigzag_line_fill_polygons(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> OpSet {
let gap = if o.hachure_gap < 0.0 {
o.stroke_width * 4.0
} else {
o.hachure_gap
}
let zo = if o.zigzag_offset < 0.0 { gap } else { o.zigzag_offset }
let o = { ..o, hachure_gap: gap + zo, }
let lines = polygon_hachure_lines(polygon_list, o)
let ops = []
for line in lines {
let length = @geometry.line_length(line.0, line.1)
let count = @jsmath.round(length / (2.0 * zo))
let (p1, p2) = if line.0.x > line.1.x { (line.1, line.0) } else { line }
let alpha = @jsmath.atan((p2.y - p1.y) / (p2.x - p1.x))
// rough.js never finishes when zigzagOffset is 0 (count is Infinity).
if count.is_inf() {
continue
}
for i = 0.0; i < count; i = i + 1.0 {
let lstart = i * 2.0 * zo
let lend = (i + 1.0) * 2.0 * zo
let dz = (2.0 * (zo * zo)).sqrt()
let start_x = p1.x + lstart * @jsmath.cos(alpha)
let start_y = p1.y + lstart * @jsmath.sin(alpha)
let end_x = p1.x + lend * @jsmath.cos(alpha)
let end_y = p1.y + lend * @jsmath.sin(alpha)
let middle_x = start_x + dz * @jsmath.cos(alpha + @jsmath.PI / 4.0)
let middle_y = start_y + dz * @jsmath.sin(alpha + @jsmath.PI / 4.0)
ops.append(double_line_fill_ops(start_x, start_y, middle_x, middle_y, o))
ops.append(double_line_fill_ops(middle_x, middle_y, end_x, end_y, o))
}
}
{ kind: FillSketch, ops, }
}
///|
/// `polygonHachureLines(polygonList, o)` of scan-line-hachure.ts.
fn polygon_hachure_lines(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> Array[@hachure.Line] {
let angle = o.hachure_angle + 90.0
let mut gap = o.hachure_gap
if gap < 0.0 {
gap = o.stroke_width * 4.0
}
gap = @jsmath.round(@jsmath.max(gap, 0.1))
let mut skip_offset = 1.0
if o.roughness >= 1.0 {
// `(o.randomizer?.next() || Math.random()) > 0.7`: no randomizer is
// created here, and a 0 from it falls back to Math.random().
let r = match o.randomizer {
Some(randomizer) => randomizer.next()
None => 0.0
}
let r = if truthy(r) { r } else { o.source.fallback() }
if r > 0.7 {
skip_offset = gap
}
}
@hachure.hachure_lines(
polygon_list,
gap,
angle,
hachure_step_offset=js_or(skip_offset, 1.0),
)
}
///|
/// `HachureFiller.renderLines`.
fn render_lines(lines : Array[@hachure.Line], o : ResolvedOptions) -> Array[Op] {
let ops = []
for line in lines {
ops.append(double_line_fill_ops(line.0.x, line.0.y, line.1.x, line.1.y, o))
}
ops
}
///|
/// `HachureFiller._fillPolygons`.
fn hachure_fill_polygons(
polygon_list : Array[Array[Point]],
o : ResolvedOptions,
) -> OpSet {
let lines = polygon_hachure_lines(polygon_list, o)
{ kind: FillSketch, ops: render_lines(lines, o), }
}