mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 17:08:12 +08:00
Bezier-rs: Convert from canvas to svg for constructors (#776)
* Convert constructor to use svg * Convert the through_points functions to svg Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
8dce2144c4
commit
e9cd792635
@@ -28,12 +28,12 @@ pub struct ToSVGOptions {
|
|||||||
|
|
||||||
impl ToSVGOptions {
|
impl ToSVGOptions {
|
||||||
/// Combine and format curve styling options for an SVG path.
|
/// Combine and format curve styling options for an SVG path.
|
||||||
pub(crate) fn formatted_curve_arguments(&self) -> String {
|
pub fn formatted_curve_arguments(&self) -> String {
|
||||||
format!(r#"stroke="{}" stroke-width="{}" fill="none""#, self.curve_stroke_color, self.curve_stroke_width)
|
format!(r#"stroke="{}" stroke-width="{}" fill="none""#, self.curve_stroke_color, self.curve_stroke_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combine and format anchor styling options an SVG circle.
|
/// Combine and format anchor styling options an SVG circle.
|
||||||
pub(crate) fn formatted_anchor_arguments(&self) -> String {
|
pub fn formatted_anchor_arguments(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
r#"r="{}", stroke="{}" stroke-width="{}" fill="{}""#,
|
r#"r="{}", stroke="{}" stroke-width="{}" fill="{}""#,
|
||||||
self.anchor_radius, self.anchor_stroke_color, self.anchor_stroke_width, self.anchor_fill
|
self.anchor_radius, self.anchor_stroke_color, self.anchor_stroke_width, self.anchor_fill
|
||||||
@@ -41,7 +41,7 @@ impl ToSVGOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Combine and format handle point styling options for an SVG circle.
|
/// Combine and format handle point styling options for an SVG circle.
|
||||||
pub(crate) fn formatted_handle_point_arguments(&self) -> String {
|
pub fn formatted_handle_point_arguments(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
r#"r="{}", stroke="{}" stroke-width="{}" fill="{}""#,
|
r#"r="{}", stroke="{}" stroke-width="{}" fill="{}""#,
|
||||||
self.handle_point_radius, self.handle_point_stroke_color, self.handle_point_stroke_width, self.handle_point_fill
|
self.handle_point_radius, self.handle_point_stroke_color, self.handle_point_stroke_width, self.handle_point_fill
|
||||||
@@ -49,7 +49,7 @@ impl ToSVGOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Combine and format handle line styling options an SVG path.
|
/// Combine and format handle line styling options an SVG path.
|
||||||
pub(crate) fn formatted_handle_line_arguments(&self) -> String {
|
pub fn formatted_handle_line_arguments(&self) -> String {
|
||||||
format!(r#"stroke="{}" stroke-width="{}" fill="none""#, self.handle_line_stroke_color, self.handle_line_stroke_width)
|
format!(r#"stroke="{}" stroke-width="{}" fill="none""#, self.handle_line_stroke_color, self.handle_line_stroke_width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,16 @@
|
|||||||
<p>This is the interactive documentation for the <b>bezier-rs</b> library. Click and drag on the endpoints of the example curves to visualize the various Bezier utilities and functions.</p>
|
<p>This is the interactive documentation for the <b>bezier-rs</b> library. Click and drag on the endpoints of the example curves to visualize the various Bezier utilities and functions.</p>
|
||||||
<h2>Beziers</h2>
|
<h2>Beziers</h2>
|
||||||
<div v-for="(feature, index) in bezierFeatures" :key="index">
|
<div v-for="(feature, index) in bezierFeatures" :key="index">
|
||||||
|
<BezierExamplePane :name="feature.name" :callback="feature.callback" :exampleOptions="feature.exampleOptions" />
|
||||||
|
</div>
|
||||||
|
<div v-for="(feature, index) in features" :key="index">
|
||||||
<ExamplePane
|
<ExamplePane
|
||||||
:template="feature.template"
|
:template="feature.template"
|
||||||
:templateOptions="feature.templateOptions"
|
:templateOptions="feature.templateOptions"
|
||||||
:name="feature.name"
|
:name="feature.name"
|
||||||
:callback="feature.callback"
|
:callback="feature.callback"
|
||||||
:createThroughPoints="feature.createThroughPoints"
|
|
||||||
:curveDegrees="feature.curveDegrees"
|
:curveDegrees="feature.curveDegrees"
|
||||||
:customPoints="feature.customPoints"
|
:customPoints="feature.customPoints"
|
||||||
:customOptions="feature.customOptions"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2>Subpaths</h2>
|
<h2>Subpaths</h2>
|
||||||
@@ -25,9 +26,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, markRaw } from "vue";
|
import { defineComponent, markRaw } from "vue";
|
||||||
|
|
||||||
|
import { WasmBezier } from "@/../wasm/pkg";
|
||||||
import { drawBezier, drawBezierHelper, drawCircle, drawCircleSector, drawCurve, drawLine, drawPoint, drawText, getContextFromCanvas, COLORS } from "@/utils/drawing";
|
import { drawBezier, drawBezierHelper, drawCircle, drawCircleSector, drawCurve, drawLine, drawPoint, drawText, getContextFromCanvas, COLORS } from "@/utils/drawing";
|
||||||
import { BezierCurveType, CircleSector, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
import { BezierCurveType, CircleSector, Point, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
|
||||||
|
|
||||||
|
import BezierExamplePane from "@/components/BezierExamplePane.vue";
|
||||||
import ExamplePane from "@/components/ExamplePane.vue";
|
import ExamplePane from "@/components/ExamplePane.vue";
|
||||||
import SliderExample from "@/components/SliderExample.vue";
|
import SliderExample from "@/components/SliderExample.vue";
|
||||||
import SubpathExamplePane from "@/components/SubpathExamplePane.vue";
|
import SubpathExamplePane from "@/components/SubpathExamplePane.vue";
|
||||||
@@ -48,30 +51,45 @@ export default defineComponent({
|
|||||||
bezierFeatures: [
|
bezierFeatures: [
|
||||||
{
|
{
|
||||||
name: "Constructor",
|
name: "Constructor",
|
||||||
// eslint-disable-next-line
|
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.to_svg(),
|
||||||
callback: (): void => {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bezier Through Points",
|
name: "Bezier Through Points",
|
||||||
// eslint-disable-next-line
|
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
|
||||||
callback: (): void => {},
|
const points: Point[] = JSON.parse(bezier.get_points());
|
||||||
curveDegrees: new Set([BezierCurveType.Quadratic, BezierCurveType.Cubic]),
|
const formattedPoints: number[][] = points.map((p) => [p.x, p.y]);
|
||||||
createThroughPoints: true,
|
if (Object.values(options).length === 1) {
|
||||||
template: markRaw(SliderExample),
|
return WasmBezier.quadratic_through_points(formattedPoints, options.t);
|
||||||
templateOptions: {
|
}
|
||||||
sliders: [
|
return WasmBezier.cubic_through_points(formattedPoints, options.t, options["midpoint separation"]);
|
||||||
{
|
|
||||||
min: 0.01,
|
|
||||||
max: 0.99,
|
|
||||||
step: 0.01,
|
|
||||||
default: 0.5,
|
|
||||||
variable: "t",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
customOptions: {
|
exampleOptions: {
|
||||||
|
[BezierCurveType.Linear]: {
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
[BezierCurveType.Quadratic]: {
|
||||||
|
customPoints: [
|
||||||
|
[30, 50],
|
||||||
|
[120, 70],
|
||||||
|
[160, 170],
|
||||||
|
],
|
||||||
|
sliderOptions: [
|
||||||
|
{
|
||||||
|
min: 0.01,
|
||||||
|
max: 0.99,
|
||||||
|
step: 0.01,
|
||||||
|
default: 0.5,
|
||||||
|
variable: "t",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
[BezierCurveType.Cubic]: {
|
[BezierCurveType.Cubic]: {
|
||||||
sliders: [
|
customPoints: [
|
||||||
|
[30, 50],
|
||||||
|
[120, 70],
|
||||||
|
[160, 170],
|
||||||
|
],
|
||||||
|
sliderOptions: [
|
||||||
{
|
{
|
||||||
min: 0.01,
|
min: 0.01,
|
||||||
max: 0.99,
|
max: 0.99,
|
||||||
@@ -89,14 +107,9 @@ export default defineComponent({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customPoints: {
|
|
||||||
[BezierCurveType.Quadratic]: [
|
|
||||||
[30, 50],
|
|
||||||
[120, 70],
|
|
||||||
[160, 170],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
features: [
|
||||||
{
|
{
|
||||||
name: "Length",
|
name: "Length",
|
||||||
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
callback: (canvas: HTMLCanvasElement, bezier: WasmBezierInstance): void => {
|
||||||
@@ -586,6 +599,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
BezierExamplePane,
|
||||||
ExamplePane,
|
ExamplePane,
|
||||||
SubpathExamplePane,
|
SubpathExamplePane,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { WasmBezier } from "@/../wasm/pkg";
|
|
||||||
|
|
||||||
import { COLORS, drawBezier, drawPoint, getContextFromCanvas, getPointSizeByIndex } from "@/utils/drawing";
|
import { COLORS, drawBezier, drawPoint, getContextFromCanvas, getPointSizeByIndex } from "@/utils/drawing";
|
||||||
import { BezierCallback, BezierPoint, BezierStyleConfig, Point, WasmBezierManipulatorKey, WasmBezierInstance } from "@/utils/types";
|
import { Callback, BezierPoint, BezierStyleConfig, Point, WasmBezierManipulatorKey, WasmBezierInstance } from "@/utils/types";
|
||||||
|
|
||||||
// Offset to increase selectable range, used to make points easier to grab
|
// Offset to increase selectable range, used to make points easier to grab
|
||||||
const FUDGE_FACTOR = 3;
|
const FUDGE_FACTOR = 3;
|
||||||
@@ -24,13 +22,13 @@ class BezierDrawing {
|
|||||||
|
|
||||||
bezier: WasmBezierInstance;
|
bezier: WasmBezierInstance;
|
||||||
|
|
||||||
callback: BezierCallback;
|
callback: Callback;
|
||||||
|
|
||||||
options: Record<string, number>;
|
options: Record<string, number>;
|
||||||
|
|
||||||
createThroughPoints: boolean;
|
createThroughPoints: boolean;
|
||||||
|
|
||||||
constructor(bezier: WasmBezierInstance, callback: BezierCallback, options: Record<string, number>, createThroughPoints = false) {
|
constructor(bezier: WasmBezierInstance, callback: Callback, options: Record<string, number>, createThroughPoints = false) {
|
||||||
this.bezier = bezier;
|
this.bezier = bezier;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@@ -120,22 +118,13 @@ class BezierDrawing {
|
|||||||
|
|
||||||
// For the create through points cases, we store a bezier where the handle is actually the point that the curve should pass through
|
// For the create through points cases, we store a bezier where the handle is actually the point that the curve should pass through
|
||||||
// This is so that we can re-use the drag and drop logic, while simply drawing the desired bezier instead
|
// This is so that we can re-use the drag and drop logic, while simply drawing the desired bezier instead
|
||||||
const actualBezierPointLength = JSON.parse(this.bezier.get_points()).length;
|
const pointsToDraw = this.points;
|
||||||
let pointsToDraw = this.points;
|
|
||||||
|
|
||||||
let styleConfig: Partial<BezierStyleConfig> = {
|
let styleConfig: Partial<BezierStyleConfig> = {
|
||||||
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_2,
|
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_2,
|
||||||
};
|
};
|
||||||
let dragIndex = this.dragIndex;
|
let dragIndex = this.dragIndex;
|
||||||
if (this.createThroughPoints) {
|
if (this.createThroughPoints) {
|
||||||
let bezierThroughPoints;
|
|
||||||
const pointList = this.points.map((p) => [p.x, p.y]);
|
|
||||||
if (actualBezierPointLength === 3) {
|
|
||||||
bezierThroughPoints = WasmBezier.quadratic_through_points(pointList, this.options.t);
|
|
||||||
} else {
|
|
||||||
bezierThroughPoints = WasmBezier.cubic_through_points(pointList, this.options.t, this.options["midpoint separation"]);
|
|
||||||
}
|
|
||||||
pointsToDraw = JSON.parse(bezierThroughPoints.get_points());
|
|
||||||
if (this.dragIndex === 1) {
|
if (this.dragIndex === 1) {
|
||||||
// Do not propagate dragIndex when the the non-endpoint is moved
|
// Do not propagate dragIndex when the the non-endpoint is moved
|
||||||
dragIndex = null;
|
dragIndex = null;
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h4 class="example-header">{{ title }}</h4>
|
||||||
|
<figure @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onMouseMove" class="example-figure" v-html="bezierSVG"></figure>
|
||||||
|
<div v-for="(slider, index) in sliderOptions" :key="index">
|
||||||
|
<div class="slider-label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ getSliderValue(sliderData[slider.variable], sliderUnits[slider.variable]) }}</div>
|
||||||
|
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
import { WasmBezier } from "@/../wasm/pkg";
|
||||||
|
import { getConstructorKey, getCurveType } from "@/utils/helpers";
|
||||||
|
import { BezierCallback, BezierCurveType, WasmBezierManipulatorKey, SliderOption } from "@/utils/types";
|
||||||
|
|
||||||
|
const SELECTABLE_RANGE = 10;
|
||||||
|
|
||||||
|
// Given the number of points in the curve, map the index of a point to the correct manipulator key
|
||||||
|
const MANIPULATOR_KEYS_FROM_BEZIER_TYPE: { [key in BezierCurveType]: WasmBezierManipulatorKey[] } = {
|
||||||
|
[BezierCurveType.Linear]: ["set_start", "set_end"],
|
||||||
|
[BezierCurveType.Quadratic]: ["set_start", "set_handle_start", "set_end"],
|
||||||
|
[BezierCurveType.Cubic]: ["set_start", "set_handle_start", "set_handle_end", "set_end"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
points: {
|
||||||
|
type: Array as PropType<Array<Array<number>>>,
|
||||||
|
required: true,
|
||||||
|
mutable: true,
|
||||||
|
},
|
||||||
|
callback: {
|
||||||
|
type: Function as PropType<BezierCallback>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sliderOptions: {
|
||||||
|
type: Object as PropType<Array<SliderOption>>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const curveType = getCurveType(this.points.length);
|
||||||
|
const manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];
|
||||||
|
|
||||||
|
const bezier = WasmBezier[getConstructorKey(curveType)](this.points);
|
||||||
|
const sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
|
||||||
|
|
||||||
|
return {
|
||||||
|
bezier,
|
||||||
|
bezierSVG: this.callback(bezier, sliderData),
|
||||||
|
manipulatorKeys,
|
||||||
|
activeIndex: undefined as number | undefined,
|
||||||
|
mutablePoints: JSON.parse(JSON.stringify(this.points)),
|
||||||
|
sliderData,
|
||||||
|
sliderUnits: Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit }))),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onMouseDown(event: MouseEvent) {
|
||||||
|
const mx = event.offsetX;
|
||||||
|
const my = event.offsetY;
|
||||||
|
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex += 1) {
|
||||||
|
const point = this.mutablePoints[pointIndex];
|
||||||
|
if (point && Math.abs(mx - point[0]) < SELECTABLE_RANGE && Math.abs(my - point[1]) < SELECTABLE_RANGE) {
|
||||||
|
this.activeIndex = pointIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onMouseUp() {
|
||||||
|
this.activeIndex = undefined;
|
||||||
|
},
|
||||||
|
onMouseMove(event: MouseEvent) {
|
||||||
|
const mx = event.offsetX;
|
||||||
|
const my = event.offsetY;
|
||||||
|
if (this.activeIndex !== undefined) {
|
||||||
|
this.bezier[this.manipulatorKeys[this.activeIndex]](mx, my);
|
||||||
|
this.mutablePoints[this.activeIndex] = [mx, my];
|
||||||
|
this.bezierSVG = this.callback(this.bezier, this.sliderData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
sliderData: {
|
||||||
|
handler() {
|
||||||
|
this.bezierSVG = this.callback(this.bezier, this.sliderData);
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.example-figure {
|
||||||
|
border: solid 1px black;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3 class="example-pane-header">{{ name }}</h3>
|
||||||
|
<div class="example-row">
|
||||||
|
<div v-for="(example, index) in examples" :key="index">
|
||||||
|
<BezierExample v-if="!example.disabled" :title="example.title" :points="example.points" :callback="callback" :sliderOptions="example.sliderOptions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
import { BezierCallback, BezierCurveType, ExampleOptions } from "@/utils/types";
|
||||||
|
|
||||||
|
import BezierExample from "@/components/BezierExample.vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
callback: {
|
||||||
|
type: Function as PropType<BezierCallback>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exampleOptions: {
|
||||||
|
type: Object as PropType<ExampleOptions>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
const exampleDefaults = {
|
||||||
|
[BezierCurveType.Linear]: {
|
||||||
|
points: [
|
||||||
|
[30, 60],
|
||||||
|
[140, 120],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[BezierCurveType.Quadratic]: {
|
||||||
|
points: [
|
||||||
|
[30, 50],
|
||||||
|
[140, 30],
|
||||||
|
[160, 170],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[BezierCurveType.Cubic]: {
|
||||||
|
points: [
|
||||||
|
[30, 30],
|
||||||
|
[60, 140],
|
||||||
|
[150, 30],
|
||||||
|
[160, 160],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
examples: Object.values(BezierCurveType).map((curveType) => {
|
||||||
|
const givenData = this.exampleOptions[curveType];
|
||||||
|
const defaultData = exampleDefaults[curveType];
|
||||||
|
return {
|
||||||
|
title: curveType,
|
||||||
|
disabled: givenData?.disabled || false,
|
||||||
|
points: givenData?.customPoints || defaultData.points,
|
||||||
|
sliderOptions: givenData?.sliderOptions || [],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
BezierExample,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.example-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-pane-header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import BezierDrawing from "@/components/BezierDrawing";
|
import BezierDrawing from "@/components/BezierDrawing";
|
||||||
import { BezierCallback, WasmBezierInstance } from "@/utils/types";
|
import { Callback, WasmBezierInstance } from "@/utils/types";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -19,7 +19,7 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
callback: {
|
callback: {
|
||||||
type: Function as PropType<BezierCallback>,
|
type: Function as PropType<Callback>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { BezierCurveType, WasmBezierConstructorKey } from "@/utils/types";
|
||||||
|
|
||||||
|
export const getCurveType = (numPoints: number): BezierCurveType => {
|
||||||
|
switch (numPoints) {
|
||||||
|
case 2:
|
||||||
|
return BezierCurveType.Linear;
|
||||||
|
case 3:
|
||||||
|
return BezierCurveType.Quadratic;
|
||||||
|
case 4:
|
||||||
|
return BezierCurveType.Cubic;
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid number of points for a bezier");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConstructorKey = (bezierCurveType: BezierCurveType): WasmBezierConstructorKey => {
|
||||||
|
switch (bezierCurveType) {
|
||||||
|
case BezierCurveType.Linear:
|
||||||
|
return "new_linear";
|
||||||
|
case BezierCurveType.Quadratic:
|
||||||
|
return "new_quadratic";
|
||||||
|
case BezierCurveType.Cubic:
|
||||||
|
return "new_cubic";
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid value for a BezierCurveType");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -14,9 +14,18 @@ export enum BezierCurveType {
|
|||||||
Cubic = "Cubic",
|
Cubic = "Cubic",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BezierCallback = (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: Point) => void;
|
export type Callback = (canvas: HTMLCanvasElement, bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: Point) => void;
|
||||||
|
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>) => string;
|
||||||
export type SubpathCallback = (subpath: WasmSubpathInstance) => string;
|
export type SubpathCallback = (subpath: WasmSubpathInstance) => string;
|
||||||
|
|
||||||
|
export type ExampleOptions = {
|
||||||
|
[key in BezierCurveType]: {
|
||||||
|
disabled: boolean;
|
||||||
|
sliderOptions: SliderOption[];
|
||||||
|
customPoints: number[][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type SliderOption = {
|
export type SliderOption = {
|
||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::svg_drawing::*;
|
use crate::svg_drawing::*;
|
||||||
use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, ProjectionOptions, ToSVGOptions};
|
use bezier_rs::{ArcStrategy, ArcsOptions, Bezier, ProjectionOptions};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
@@ -55,6 +55,10 @@ fn convert_wasm_maximize_arcs(wasm_enum_value: WasmMaximizeArcs) -> ArcStrategy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wrap_svg_tag(contents: String) -> String {
|
||||||
|
format!("{}{}{}", SVG_OPEN_TAG, contents, SVG_CLOSE_TAG)
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl WasmBezier {
|
impl WasmBezier {
|
||||||
/// Expect js_points to be a list of 2 pairs.
|
/// Expect js_points to be a list of 2 pairs.
|
||||||
@@ -75,14 +79,30 @@ impl WasmBezier {
|
|||||||
WasmBezier(Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3]))
|
WasmBezier(Bezier::from_cubic_dvec2(points[0], points[1], points[2], points[3]))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn quadratic_through_points(js_points: &JsValue, t: f64) -> WasmBezier {
|
fn draw_bezier_through_points(bezier: Bezier, through_point: DVec2) -> String {
|
||||||
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
let mut bezier_string = String::new();
|
||||||
WasmBezier(Bezier::quadratic_through_points(points[0], points[1], points[2], Some(t)))
|
bezier.to_svg(
|
||||||
|
&mut bezier_string,
|
||||||
|
CURVE_ATTRIBUTES.to_string(),
|
||||||
|
ANCHOR_ATTRIBUTES.to_string(),
|
||||||
|
HANDLE_ATTRIBUTES.to_string().replace(GRAY, RED),
|
||||||
|
HANDLE_LINE_ATTRIBUTES.to_string().replace(GRAY, RED),
|
||||||
|
);
|
||||||
|
let through_point_circle = format!(r#"<circle cx="{}" cy="{}" {}/>"#, through_point.x, through_point.y, ANCHOR_ATTRIBUTES.to_string());
|
||||||
|
|
||||||
|
wrap_svg_tag(format!("{bezier_string}{through_point_circle}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cubic_through_points(js_points: &JsValue, t: f64, midpoint_separation: f64) -> WasmBezier {
|
pub fn quadratic_through_points(js_points: &JsValue, t: f64) -> String {
|
||||||
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
||||||
WasmBezier(Bezier::cubic_through_points(points[0], points[1], points[2], Some(t), Some(midpoint_separation)))
|
let bezier = Bezier::quadratic_through_points(points[0], points[1], points[2], Some(t));
|
||||||
|
WasmBezier::draw_bezier_through_points(bezier, points[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cubic_through_points(js_points: &JsValue, t: f64, midpoint_separation: f64) -> String {
|
||||||
|
let points: [DVec2; 3] = js_points.into_serde().unwrap();
|
||||||
|
let bezier = Bezier::cubic_through_points(points[0], points[1], points[2], Some(t), Some(midpoint_separation));
|
||||||
|
WasmBezier::draw_bezier_through_points(bezier, points[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_start(&mut self, x: f64, y: f64) {
|
pub fn set_start(&mut self, x: f64, y: f64) {
|
||||||
@@ -116,7 +136,7 @@ impl WasmBezier {
|
|||||||
HANDLE_ATTRIBUTES.to_string(),
|
HANDLE_ATTRIBUTES.to_string(),
|
||||||
HANDLE_LINE_ATTRIBUTES.to_string(),
|
HANDLE_LINE_ATTRIBUTES.to_string(),
|
||||||
);
|
);
|
||||||
format!("{}{}{}", SVG_OPEN_TAG, bezier, SVG_CLOSE_TAG)
|
wrap_svg_tag(bezier)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn length(&self) -> f64 {
|
pub fn length(&self) -> f64 {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ pub const SVG_CLOSE_TAG: &str = "</svg>";
|
|||||||
|
|
||||||
// Stylistic constants
|
// Stylistic constants
|
||||||
pub const BLACK: &str = "black";
|
pub const BLACK: &str = "black";
|
||||||
|
pub const GRAY: &str = "gray";
|
||||||
|
pub const RED: &str = "red";
|
||||||
|
|
||||||
// Default attributes
|
// Default attributes
|
||||||
pub const CURVE_ATTRIBUTES: &str = "stroke=\"black\" stroke-width=\"2\" fill=\"none\"";
|
pub const CURVE_ATTRIBUTES: &str = "stroke=\"black\" stroke-width=\"2\" fill=\"none\"";
|
||||||
|
|||||||
Reference in New Issue
Block a user