Skip to content

WIP: Rust Experiments #852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion cetz-core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cetz-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ crate-type = ["cdylib"]
[dependencies]
ciborium = "0.2.1"
serde = "1.0.219"
serde_tuple = "1.1.0"
wasm-minimal-protocol = "0.1"
arrayvec = { version = "0.7", features = ["serde"] }

[profile.release]
lto = true
Expand Down
95 changes: 94 additions & 1 deletion cetz-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use ciborium::de::from_reader;
use ciborium::ser::into_writer;
use serde::Serialize;
use serde::Deserialize;
use serde_tuple::*;
use arrayvec::ArrayVec;
use wasm_minimal_protocol::*;

initiate_protocol!();

type Point = Vec<f64>;
type Point = ArrayVec<f64, 4>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is great. I was also thinking about something like this


fn round(x: f64, digits: u32) -> f64 {
let factor = 10.0_f64.powi(digits as i32);
Expand All @@ -16,10 +19,18 @@ fn vector_add(a: Point, b: Point) -> Point {
a.iter().zip(b.iter()).map(|(a, b)| a + b).collect()
}

fn vector_sub(a: Point, b: Point) -> Point {
a.iter().zip(b.iter()).map(|(a, b)| a - b).collect()
}

fn vector_scale(a: Point, b: f64) -> Point {
a.iter().map(|a| a * b).collect()
}

fn vector_dist(a: Point, b: Point) -> f64 {
vector_sub(a, b).into_iter().map(|x| x * x).sum::<f64>().sqrt()
}

fn cubic_point(a: Point, b: Point, c1: Point, c2: Point, t: f64) -> Point {
// (1-t)^3*a + 3*(1-t)^2*t*c1 + 3*(1-t)*t^2*c2 + t^3*b
vector_add(
Expand Down Expand Up @@ -103,3 +114,85 @@ pub fn cubic_extrema_func(input: &[u8]) -> Vec<u8> {
}
}
}

#[derive(Serialize,Deserialize)]
#[serde(untagged)]
enum Segment {
Line(String /* "l" */, Point),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ugly.

Cubic(String /* "c" */, Point, Point, Point),
}

impl Segment {
fn end_point(&self) -> Point {
match self {
Segment::Line(_, p) => p.clone(),
Segment::Cubic(_, _c1, _c2, e) => e.clone(),
}
}

fn length(&self, origin: Point) -> f64 {
match self {
Segment::Line(_, p) => vector_dist(origin, p.clone()),
Segment::Cubic(_, c1, c2, e) => 0.0, // TODO
}
}
}

#[derive(Serialize_tuple, Deserialize_tuple)]
struct SubPath {
origin: Point,
closed: bool,
segments: Vec<Segment>,
}

impl SubPath {
fn end_point(&self) -> Point {
match self.segments.last() {
Some(segment) => segment.end_point(),
None => self.origin.clone(),
}
}

fn normalize(&mut self) {
if self.closed {
if self.origin != self.end_point() {
self.segments.push(Segment::Line("l".to_string(), self.origin.clone()));
}
}
}
}

#[derive(Serialize_tuple, Deserialize_tuple)]
struct Path {
subpaths: Vec<SubPath>,
}

impl Path {
fn normalize(&mut self) {
for subpath in &mut self.subpaths {
subpath.normalize()
}
}
}

#[derive(Deserialize)]
struct PathNormalizeArgs {
path: Path
}

#[wasm_func]
pub fn path_normalize_func(input: &[u8]) -> Vec<u8> {
match from_reader::<PathNormalizeArgs, _>(input) {
Ok(mut input) => {
let mut buf = Vec::new();
input.path.normalize();
into_writer(&input.path, &mut buf).unwrap();
buf
}
Err(e) => {
let mut buf = Vec::new();
into_writer(&e.to_string(), &mut buf).unwrap();
buf
}
}
}
5 changes: 3 additions & 2 deletions src/anchor.typ
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
return pts.first()
}


return if pts.len() == 1 {
pts.first()
} else if pts.len() > 1 {
Expand Down Expand Up @@ -172,7 +171,9 @@
if out == none {
if type(anchor) in (ratio, float, int) {
assert(path-anchors, message: strfmt("Element '{}' does not support path anchors.", name))
out = path-util.point-on-path(path.segments, anchor)
let point-info = path-util.point-at(path.segments, anchor)
assert.ne(point-info, none)
out = point-info.point
} else if type(anchor) == angle {
assert(border-anchors, message: strfmt("Element '{}' does not support border anchors.", name))
out = border(callback("center"), ..radii, path, anchor)
Expand Down
26 changes: 8 additions & 18 deletions src/bezier.typ
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// This file contains functions related to bezier curve calculation
// Many functions are ports from https://github.com/Pomax/bezierjs
#import "vector.typ"
#import "aabb.typ"

#let cetz-core = plugin("../cetz-core/cetz_core.wasm")

// Map number v from range (ds, de) to (ts, te)
Expand Down Expand Up @@ -292,13 +294,13 @@
/// - c2 (vector): Control point 2
/// -> float
#let cubic-arclen(s, e, c1, c2, samples: 10) = {
samples = calc.min(2, samples)

let d = 0
let last = none
for t in range(0, samples + 1) {
let last = s
for t in range(1, samples + 1) {
let pt = cubic-point(s, e, c1, c2, t / samples)
if last != none {
d += vector.dist(last, pt)
}
d += vector.dist(last, pt)
last = pt
}
return d
Expand Down Expand Up @@ -408,19 +410,7 @@
/// - c2 (vector): Control point 2
/// -> array
#let cubic-aabb(s, e, c1, c2) = {
let (lo, hi) = (s, e)
for dim in range(lo.len()) {
if lo.at(dim) > hi.at(dim) {
(lo.at(dim), hi.at(dim)) = (hi.at(dim), lo.at(dim))
}
}
for pt in cubic-extrema(s, e, c1, c2) {
for dim in range(pt.len()) {
lo.at(dim) = calc.min(lo.at(dim), hi.at(dim), pt.at(dim))
hi.at(dim) = calc.max(lo.at(dim), hi.at(dim), pt.at(dim))
}
}
return (lo, hi)
return aabb.aabb(cubic-extrema(s, e, c1, c2) + (s, e,)).bounds
}

/// Returns a cubic bezier between points `p2` and `p3` for a catmull-rom curve through all four points.
Expand Down
41 changes: 15 additions & 26 deletions src/canvas.typ
Original file line number Diff line number Diff line change
Expand Up @@ -106,35 +106,24 @@
(y - offset-y - segment-y) * length)
}

let last-point = none
for ((kind, ..rest)) in drawable.segments {
if kind == "sub" {
// TODO: Support sub-paths by converting
// Also support move commands.
// Refactor path arrays to typst style curves.
} else if kind == "cubic" {
let pts = rest.map(transform-point)

if last-point != pts.at(0) {
vertices.push(curve.move(pts.at(0)))
for ((origin, closed, segments)) in drawable.segments {
vertices.push(curve.move(transform-point(origin)))

for ((kind, ..args)) in segments {
if kind == "l" {
for pt in args {
vertices.push(curve.line(transform-point(pt)))
}
} else if kind == "c" {
vertices.push(curve.cubic(..args.map(transform-point)))
} else {
panic(kind, args)
}
vertices.push(curve.cubic(pts.at(2), pts.at(3), pts.at(1)))
last-point = pts.at(1)
} else {
let pts = rest.map(transform-point)

if last-point != pts.at(0) {
vertices.push(curve.move(pts.at(0)))
}
for i in range(1, pts.len()) {
vertices.push(curve.line(pts.at(i)))
}
last-point = pts.last()
}
}

if (drawable.at("close", default: false)) {
vertices.push(curve.close(mode: "straight"))
if closed {
vertices.push(curve.close(mode: "straight"))
}
}

if type(drawable.stroke) == dictionary and "thickness" in drawable.stroke and type(drawable.stroke.thickness) != std.length {
Expand Down
2 changes: 1 addition & 1 deletion src/draw.typ
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#import "draw/grouping.typ": intersections, group, scope, anchor, copy-anchors, set-ctx, get-ctx, for-each-anchor, on-layer, hide, floating
#import "draw/transformations.typ": set-transform, rotate, translate, scale, set-origin, move-to, set-viewport
#import "draw/styling.typ": set-style, fill, stroke, register-mark
#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, polygon
#import "draw/shapes.typ": circle, circle-through, arc, arc-through, mark, line, grid, content, rect, bezier, bezier-through, catmull, hobby, merge-path, polygon, multi-path
#import "draw/projection.typ": ortho, on-xy, on-xz, on-yz
#import "draw/util.typ": assert-version
6 changes: 3 additions & 3 deletions src/draw/grouping.typ
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,13 @@
(bounds.low.at(1), bounds.high.at(1)) = (bounds.high.at(1), bounds.low.at(1))
let center = aabb.mid(bounds)
let (width, height, _) = aabb.size(bounds)
let path = drawable.path(
path-util.line-segment((
let path = drawable.line-strip((
(bounds.low.at(0), bounds.high.at(1)),
bounds.high,
(bounds.high.at(0), bounds.low.at(1)),
bounds.low,
)), close: true)
), close: true)

(center, width, height, path)
} else { (none,) * 4 }

Expand Down
23 changes: 17 additions & 6 deletions src/draw/projection.typ
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@
#let _sort-by-distance(drawables) = {
return drawables.sorted(key: d => {
let z = none
for ((kind, ..pts)) in d.segments {
pts = pts.map(p => p.at(2))
for ((origin, closed, segments)) in d.segments {
z = if z == none {
calc.max(..pts)
calc.max(..origin)
} else {
calc.max(z, ..pts)
calc.max(z, ..origin)
}
for ((kind, ..pts)) in segments {
pts = pts.map(p => p.at(2))
z = if z == none {
calc.max(..pts)
} else {
calc.max(z, ..pts)
}
}
}
return z
Expand All @@ -40,8 +47,12 @@
// all counter clock-wise ones.
#let _filter-cw-faces(drawables, mode: "cw") = {
return drawables.filter(d => {
let poly = polygon.from-segments(d.segments)
poly.first() != poly.last() or polygon.winding-order(poly) == mode
if d.segments != () {
let poly = polygon.from-subpath(d.segments.first())
poly.first() != poly.last() or polygon.winding-order(poly) == mode
} else {
d
}
})
}

Expand Down
Loading