1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-27 10:15:05 +02:00
This commit is contained in:
Pomax
2020-08-23 13:33:00 -07:00
parent c6d91e2c41
commit 9349103b48
60 changed files with 5561 additions and 23694 deletions

View File

@@ -10,12 +10,7 @@ setup() {
];
this.s = this.f.map(f => plot(f) );
this.position = 0;
setSlider(`.slide-control`, v => this.setPosition(v))
}
setPosition(v) {
this.position = v;
setSlider(`.slide-control`, `position`, 0);
}
draw() {

View File

@@ -2,12 +2,7 @@ setup() {
this.degree = 15;
this.triangle = [[1], [1,1]];
this.generate();
this.position = 0;
setSlider(`.slide-control`, v => this.setPosition(v))
}
setPosition(v) {
this.position = v;
setSlider(`.slide-control`, `position`, 0)
}
binomial(n,k) {

View File

@@ -9,13 +9,9 @@ setup() {
];
this.s = this.f.map(f => plot(f) );
this.position = 0;
setSlider(`.slide-control`, v => this.setPosition(v))
setSlider(`.slide-control`, `position`, 0);
}
setPosition(v) {
this.position = v;
}
draw() {
resetTransform();

View File

@@ -1,12 +1,7 @@
setup() {
this.curve = new Bezier(this, 90, 200, 25, 100, 220, 40, 210, 240);
setMovable(this.curve.points);
this.position = 0;
setSlider(`.slide-control`, v => this.setPosition(v));
}
setPosition(v) {
this.position = v;
setSlider(`.slide-control`, `position`, 0);
}
draw() {

View File

@@ -1,10 +1,5 @@
setup() {
this.step = 5;
setSlider(`.slide-control`, v => this.setStep(v));
}
setStep(v) {
this.step = v;
setSlider(`.slide-control`, `steps`, 5);
}
draw() {
@@ -24,7 +19,7 @@ draw() {
var offset = {x:w2, y:h2};
for(let t=0, p, mod; t<=this.step; t+=0.1) {
for(let t=0, p, mod; t<=this.steps; t+=0.1) {
p = {
x: w2 + w4 * cos(t),
y: h2 + h4 * sin(t)

View File

@@ -1,12 +1,7 @@
setup() {
this.steps = 8;
this.curve = Bezier.defaultCubic(this);
setMovable(this.curve.points);
setSlider(`.slide-control`, v => this.setStep(v));
}
setStep(v) {
this.steps = v;
setSlider(`.slide-control`, `steps`, 8);
}
draw() {

View File

@@ -1,12 +1,7 @@
setup() {
this.steps = 4;
this.curve = Bezier.defaultQuadratic(this);
setMovable(this.curve.points);
setSlider(`.slide-control`, v => this.setStep(v));
}
setStep(v) {
this.steps = v;
setSlider(`.slide-control`, `steps`, 4);
}
draw() {

View File

@@ -25,12 +25,7 @@ setup() {
this.cxy = new Bezier(this, points.map(p => projectXY(p)));
this.cxz = new Bezier(this, points.map(p => projectXZ(p)));
this.cyz = new Bezier(this, points.map(p => projectYZ(p)));
this.t = 0;
setSlider(`.slide-control`, v => this.setPosition(v));
}
setPosition(v) {
this.t = v;
setSlider(`.slide-control`, `position`, 0);
}
draw() {
@@ -49,7 +44,7 @@ draw() {
line(curve.points[2].x, curve.points[2].y, curve.points[3].x, curve.points[3].y);
curve.points.forEach(p => circle(p.x, p.y, 2));
this.drawPoint(this.t);
this.drawPoint(this.position);
this.drawCubeFront();
}

View File

@@ -25,12 +25,7 @@ setup() {
this.cxy = new Bezier(this, points.map(p => projectXY(p)));
this.cxz = new Bezier(this, points.map(p => projectXZ(p)));
this.cyz = new Bezier(this, points.map(p => projectYZ(p)));
this.t = 0;
setSlider(`.slide-control`, v => this.setPosition(v));
}
setPosition(v) {
this.t = v;
setSlider(`.slide-control`, `position`, 0);
}
draw() {
@@ -49,7 +44,7 @@ draw() {
line(curve.points[2].x, curve.points[2].y, curve.points[3].x, curve.points[3].y);
curve.points.forEach(p => circle(p.x, p.y, 2));
this.drawPoint(this.t);
this.drawPoint(this.position);
this.drawCubeFront();
}

View File

@@ -1,20 +1,15 @@
setup() {
this.t = 0.5;
this.curve = Bezier.defaultCubic(this);
setMovable(this.curve.points);
setSlider(`.slide-control`, v => this.setPosition(v));
}
setPosition(v) {
this.t = v;
setSlider(`.slide-control`, `position`, 0.5);
}
draw() {
resetTransform();
clear();
let p = this.curve.get(this.t);
const struts = this.struts = this.curve.getStrutPoints(this.t);
let p = this.curve.get(this.position);
const struts = this.struts = this.curve.getStrutPoints(this.position);
const c1 = new Bezier(this, [struts[0], struts[4], struts[7], struts[9]]);
const c2 = new Bezier(this, [struts[9], struts[8], struts[6], struts[3]]);

View File

@@ -20,7 +20,7 @@ Given \left (
So let's look at that in action: the following graphic is interactive in that you can use your up and down arrow keys to increase or decrease the interpolation ratio, to see what happens. We start with three points, which gives us two lines. Linear interpolation over those lines gives us two points, between which we can again perform linear interpolation, yielding a single point. And that point —and all points we can form in this way for all ratios taken together— form our Bézier curve:
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" src="./interpolation.js">
<input type="range" min="10" max="90" step="1" value="75" class="slide-control">
<input type="range" min="10" max="90" step="1" value="25" class="slide-control">
</graphics-element>
And that brings us to the complicated maths: calculus.

View File

@@ -20,7 +20,7 @@
では、実際に見てみましょう。下の図はインタラクティブになっています。上下キーで補間の比率が増減しますので、どうなるか確かめてみましょう。最初に3点があり、それを結んで2本の直線が引かれています。この直線の上でそれぞれ線形補間を行うと、2つの点が得られます。この2点の間でさらに線形補間を行うと、1つの点を得ることができます。そして、あらゆる比率に対して同様に点を求め、それをすべて集めると、このようにベジエ曲線ができるのです。
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" src="./interpolation.js">
<input type="range" min="10" max="90" step="1" value="75" class="slide-control">
<input type="range" min="10" max="90" step="1" value="25" class="slide-control">
</graphics-element>
また、これが複雑な方の数学につながっていきます。微積分です。

View File

@@ -20,7 +20,7 @@ Given \left (
让我们来通过实际操作看一下:下面的图形都是可交互的,因此你可以通过上下键来增加或减少插值距离,来观察图形的变化。我们从三个点构成的两条线段开始。通过对各条线段进行线性插值得到两个点,对点之间的线段再进行线性插值,产生一个新的点。最终这些点——所有的点都可以通过选取不同的距离插值产生——构成了贝塞尔曲线
<graphics-element title="Linear Interpolation leading to Bézier curves" width="825" src="./interpolation.js">
<input type="range" min="10" max="90" step="1" value="75" class="slide-control">
<input type="range" min="10" max="90" step="1" value="25" class="slide-control">
</graphics-element>
这为我们引出了复杂的数学:微积分。

View File

@@ -1,12 +1,7 @@
setup() {
this.step = 25;
this.curve = Bezier.defaultQuadratic(this);
setMovable(this.curve.points);
setSlider(`.slide-control`, v => this.setStep(v))
}
setStep(v) {
this.step = 100 - v;
setSlider(`.slide-control`, `step`, 25)
}
draw() {

View File

@@ -0,0 +1,94 @@
setup() {
this.curve = new Bezier(this, 20, 250, 30, 20, 200, 250, 220, 20);
setMovable(this.curve.points);
setSlider(`.slide-control`, `position`, 0.5);
}
draw() {
resetTransform();
clear();
const curve = this.curve;
curve.drawSkeleton();
curve.drawCurve();
curve.drawPoints();
let w = this.height;
let h = this.height;
let bbox = curve.bbox();
let x = bbox.x.min + (bbox.x.max - bbox.x.min) * this.position;
if (bbox.x.min < x && x < bbox.x.max) {
setStroke("red");
line(x,0,x,h);
text(`x=${x | 0}`, x + 5, h - 30);
}
translate(w, 0);
setStroke("black");
line(0,0,0,h);
// draw x = t(x)
line(0, h-20, w, h-20);
text('0', 10, h-10);
text('⅓', 10 + (w-10)/3, h-10);
text('⅔', 10 + 2*(w-10)/3, h-10);
text('1', w-10, h-10);
let p, s = { x: 0, y: h - curve.get(0).x };
for (let step = 0.02, t = step; t < 1 + step; t += step) {
p = {x: t * w, y: h - curve.get(t).x };
line(s.x, s.y, p.x, p.y);
s = p;
}
setStroke("black");
text("↑\nx", 10, h/2);
text("t →", w/2, h-10);
if (bbox.x.min < x && x < bbox.x.max) {
setStroke("red");
line(0, h-x, w, h-x);
}
}
onMouseMove() {
if (this.cursor.down) {
redraw();
}
}
/*
whatdoesthisdo() {
clear();
this.curve.drawSkeleton(curve);
this.curve.drawCurve(curve);
let w = this.height;
let h = this.height;
let bbox = this.curve.bbox();
let x = this.cursor.x;
if (bbox.x.min < x && x < bbox.x.max) {
setStroke("red");
// The root finder is based on normal x/y coordinates,
// so we can "trick" it by giving it "t" values as x
// values, and "x" values as y values. Since it won't
// even look at the x dimension, we can also just leave it.
let roots = api.utils.roots(curve.points.map(v => {
return { x: v.x, y: v.x-x};
}));
roots = roots.filter(t => t>=0 && t<=1.0);
let t = roots[0];
let p = this.curve.get(t);
line({ x: p.x, y: p.y }, { x: p.x, y: h });
line({ x: p.x, y: p.y }, { x: 0, y: p.y });
text(`y=${p.y|0}`, { x: p.x/2, y: p.y - 5 });
text(`x=${p.x|0}`, { x: x + 5, y: h - (h-p.y)/2 });
text(`t=${((t*100)|0)/100}`, { x: x + 15, y: p.y });
}
}
*/

View File

@@ -2,11 +2,13 @@
One common task that pops up in things like CSS work, or parametric equalisers, or image leveling, or any other number of applications where Bezier curves are used as control curves in a way that there is really only ever one "y" value associated with one "x" value, you might want to cut out the middle man, as it were, and compute "y" directly based on "x". After all, the function looks simple enough, finding the "y" value should be simple too, right? Unfortunately, not really. However, it _is_ possible and as long as you have some code in place to help, it's not a lot of a work either.
We'll be tackling this problem in two stages: the first, which is the hard part, is figuring out which "t" value belongs to any given "x" value. For instance, have a look at the following graphic. On the left we have a Bezier curve that looks for all intents and purposes like it fits our criteria: every "x" has one and only one associated "y" value. On the right we see the function for just the "x" values: that's a cubic curve, but not a really crazy cubic curve. If you mouse over the graphic, you will see a red line drawn that corresponds to the `x` coordinate under your cursor: vertical in the left graphic, horizontal in the right.
We'll be tackling this problem in two stages: the first, which is the hard part, is figuring out which "t" value belongs to any given "x" value. For instance, have a look at the following graphic. On the left we have a Bezier curve that looks for all intents and purposes like it fits our criteria: every "x" has one and only one associated "y" value. On the right we see the function for just the "x" values: that's a cubic curve, but not a really crazy cubic curve. If you move the graphic's slider, you will see a red line drawn that corresponds to the `x` coordinate: this is a vertical line in the left graphic, and a horizontal line on the right.
<Graphic title="Finding t, given x=x(t). Left: our curve, right: the x=x(t) function" setup={this.tforx.setup} draw={this.tforx.draw} onMouseMove={this.onMouseMove} />
<graphics-element title="Finding t, given x=x(t). Left: our curve, right: the x=x(t) function" width="550" src="./basics.js">
<input type="range" min="0" max="1" step="0.01" class="slide-control">
</graphics-element>
Now, if you look more closely at that right graphic, you'll notice something interesting: if we treat the red line as "the x axis", then the point where the function crosses our line is really just a root for the cubic function x(t). So... can we just use root finding to get the "t" value associated with an "x" value? To which the answer should unsurprisingly be " yes, we can". Sure, we'll need to compute cubic roots, but we've already seen how to do that in the section on [finding extremities](#extremities), and we're not dealing with a complicated case: we *know* there is only root, so let's go and find it!
Now, if you look more closely at that right graphic, you'll notice something interesting: if we treat the red line as "the x axis", then the point where the function crosses our line is really just a root for the cubic function x(t) through a shifted "x-axis"... and [we've already seen](#extremities) how to calculate roots, so let's just run cubuc root finding - and not even the complicated cubic case either: because of the kind of curve we're starting with, we _know_ there is only root, simplifying the code we need!
First, let's look at the function for x(t):
@@ -26,24 +28,24 @@ Nothing special here: that's a standard cubic polynomial in "power" form (i.e. a
(-a + 3b - 3c + d)t³ + (3a - 6b + 3c)t² + (-3a + 3b)t + (a-x) = 0
\]
You might be wondering "where did all the other 'minus x' for all the other values a, b, c, and d go?" and the answer there is that they all cancel out, so the only one we actually need to subtract is the one at the end. Handy! So now we just... solve this equation: we know everything except `t`, we just need some mathematical insight to tell us how to do this.
...Of course "just" is not the right qualifier here, there is nothing "just" about finding the roots of a cubic function, but thankfully we've already covered the tool to do this in the [root finding](#extremities) section: Gerolano Cardano's solution for cubic roots. Of course, we still need to be a bit careful, because cubic roots are complicated things: you can get up to three roots back, even though we only "want" one root. In our case, only one will be both a real number (as opposed to a complex number) _and_ lie in the `t` interval [0,1], so we need to filter for that:
You might be wondering "where did all the other 'minus x' for all the other values a, b, c, and d go?" and the answer there is that they all cancel out, so the only one we actually need to subtract is the one at the end. Handy! So now we just solve this equation using Cardano's algorithm, and we're left with some rather short code:
```
double x = some value we know!
double[] roots = getTforX(x);
double t;
if (roots.length > 0) {
for (double _t: roots) {
if (_t<0 || _t>1) continue;
t = _t;
break;
}
}
// prepare our values for root finding:
x = a value we already know
xcoord = our set of bezier curve's x coordinates
foreach p in xcoord: p.x -= x
// find our root, of which we know there is exactly one:
t = getRoots(p[0], p[1], p[2], p[3])[0]
// find our answer:
y = curve.get(t).y
```
And that's it, we're done: we now have the `t` value corresponding to our `x`, and we can just evaluate our curve for that `t` value to find a coordinate that has our original, known `x`, and our unknown `y` value. Mouse over the following graphic, which performs this root finding based on a knowledge of which "x" value we're interested in.
So the procedure is fairly straight forward: pick an `x`, find the associted `t` value, evaluate our curve _for_ that `t` value, which gives us the curve's {x,y} coordinate, which means we know `y` for this `x`. Move the slider for the following graphic to see this in action:
<Graphic title="Finding y(t), by finding t, given x=x(t)" setup={this.yforx.setup} draw={this.yforx.draw} onMouseMove={this.onMouseMove} />
<graphics-element title="Finding By(t), by finding t for a given x" src="./yforx.js">
<input type="range" min="0" max="1" step="0.01" class="slide-control">
</graphics-element>

View File

@@ -1,99 +0,0 @@
var sketch = {
getCurve: api => {
if (!sketch.curve) {
sketch.curve = new api.Bezier(20, 250, 30, 20, 200, 250, 250, 20);
}
return sketch.curve;
},
onMouseMove: function(evt, api) {
api.redraw();
},
tforx: {
setup: function(api) {
api.setPanelCount(2);
api.setCurve(sketch.getCurve(api));
},
draw: function(api, curve) {
api.reset();
api.drawSkeleton(curve);
api.drawCurve(curve);
let w = api.defaultWidth;
let h = api.defaultHeight;
let bbox = curve.bbox();
let x = api.mx;
if (bbox.x.min < x && x < bbox.x.max) {
api.setColor("red");
api.drawLine({ x: x, y: 0 }, { x: x, y: h });
api.text(`x=${x | 0}`, { x: x + 5, y: h - 30 });
}
api.setColor("black");
api.drawLine({ x: w, y: 0 }, { x: w, y: h });
api.setOffset({ x: w, y: 0 });
// draw x = t(x)
api.drawLine({x:0,y:h-20}, {x:w, y:h-20});
api.text('0', {x:10,y:h-10});
api.text('⅓', {x:10 + (w-10)/3,y:h-10});
api.text('⅔', {x:10 + 2*(w-10)/3,y:h-10});
api.text('1', {x:w-10,y:h-10});
let p, s = { x: 0, y: h - curve.get(0).x };
for (let step = 0.05, t = step; t < 1 + step; t += step) {
p = {x: t * w, y: h - curve.get(t).x };
api.drawLine(s, p);
s = p;
}
api.setColor("black");
api.text("↑\nx", {x:10,y:h/2});
api.text("t →", {x:w/2,y:h-10});
if (bbox.x.min < x && x < bbox.x.max) {
api.setColor("red");
api.drawLine({ x: 0, y: h-x }, { x: w, y: h-x });
}
}
},
yforx: {
setup: function(api) {
api.setCurve(sketch.getCurve(api));
},
draw: function(api, curve) {
api.reset();
api.drawSkeleton(curve);
api.drawCurve(curve);
let w = api.defaultWidth;
let h = api.defaultHeight;
let bbox = curve.bbox();
let x = api.mx;
if (bbox.x.min < x && x < bbox.x.max) {
api.setColor("red");
// The root finder is based on normal x/y coordinates,
// so we can "trick" it by giving it "t" values as x
// values, and "x" values as y values. Since it won't
// even look at the x dimension, we can also just leave it.
let roots = api.utils.roots(curve.points.map(v => {
return { x: v.x, y: v.x-x};
}));
roots = roots.filter(t => t>=0 && t<=1.0);
let t = roots[0];
let p = curve.get(t);
api.drawLine({ x: p.x, y: p.y }, { x: p.x, y: h });
api.drawLine({ x: p.x, y: p.y }, { x: 0, y: p.y });
api.text(`y=${p.y|0}`, { x: p.x/2, y: p.y - 5 });
api.text(`x=${p.x|0}`, { x: x + 5, y: h - (h-p.y)/2 });
api.text(`t=${((t*100)|0)/100}`, { x: x + 15, y: p.y });
}
}
}
};
module.exports = sketch;

View File

@@ -0,0 +1,45 @@
setup() {
this.curve = new Bezier(this, 20, 250, 30, 20, 200, 250, 220, 20);
setMovable(this.curve.points);
setSlider(`.slide-control`, `position`, 0.5);
}
draw() {
resetTransform();
clear();
const curve = this.curve;
curve.drawSkeleton();
curve.drawCurve();
curve.drawPoints();
let w = this.height;
let h = this.height;
let bbox = curve.bbox();
// prepare our values for root finding:
let x = round(bbox.x.min + (bbox.x.max - bbox.x.min) * this.position);
let xcoords = this.curve.points.map((p,i) => ({x:i/3, y: p.x - x}));
// find our root:
let roots = Bezier.getUtils().roots(xcoords);
let t = this.position===0 ? 0 : this.position===1 ? 1 : roots[0];
// find our answer:
let y = round(this.curve.get(t).y);
setStroke("red");
line(x, y, x, h);
line(x, y, 0, y);
setTextStroke(`white`, 3);
setShadow(`white`, 5);
text(`y=${y}`, x/2, y - 5);
text(`x=${x}`, x + 5, h - (h-y)/2);
text(`t=${((t*100)|0)/100}`, x + 15, y);
}
onMouseMove() {
if (this.cursor.down) {
redraw();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -138,14 +138,35 @@ class GraphicsAPI extends BaseAPI {
points.forEach((p) => this.movable.push(p));
}
setSlider(qs, handler, redraw = true) {
/**
* Set up a slider to control a named, numerical property in the sketch.
*
* @param {String} local query selector for the type=range element.
* @param {String} propname the name of the property to control.
* @param {float} initial the initial value for this property.
* @param {boolean} redraw whether or not to redraw after update the value from the slider.
*/
setSlider(qs, propname, initial, redraw = true) {
if (typeof this[propname] !== `undefined`) {
throw new Error(`this.${propname} already exists: cannot bind slider.`);
}
let slider = this.find(qs);
if (slider) {
if (!slider) {
console.warn(`Warning: no slider found for query selector "${qs}"`);
this[propname] = initial;
return undefined;
}
slider.value = initial;
this[propname] = parseFloat(slider.value);
slider.listen(`input`, (evt) => {
handler(parseFloat(evt.target.value));
this[propname] = parseFloat(evt.target.value);
if (redraw) this.redraw();
});
}
return slider;
}

View File

@@ -1,6 +1,11 @@
import { GraphicsAPI } from "../api/graphics-api.js";
export default function performCodeSurgery(code) {
// 0. strip out block comments and whitespace
code = code.replace(/\\\*[\w\s\r\n]+?\*\\/, ``);
code = code.replace(/\r?\n(\r?\n)+/, `\n`);
// 1. ensure that anything that needs to run by first calling its super function, does so.
GraphicsAPI.superCallers.forEach((name) => {

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
"url": "https://github.com/Pomax/BezierInfo-2/issues"
},
"scripts": {
"start": "cls && run-s clean time lint:* build time && rm -f .timing",
"start": "cls && run-s clean time lint:* build pretty time && rm -f .timing",
"test": "run-s start && run-p watch server browser",
"------": "--- note that due to github's naming policy, the public dir is called 'docs' rather than 'public' ---",
"browser": "open-cli http://localhost:8000",
@@ -22,6 +22,7 @@
"clean": "rm -f .timing && rm -rf ./temp",
"lint:tools": "prettier ./src/**/*.js --write",
"lint:lib": "prettier ./docs/js/**/*.js --write",
"pretty": "echo not running `prettier ./docs/**/index.html --write` as it increases filesize by 30%",
"server": "cd docs && http-server -p 8000 --cors",
"watch": "run-p watch:*",
"watch:chapters": "chokidar \"./docs/chapters/**/*.*\" -c \"npm run build\"",

View File

@@ -64,19 +64,14 @@ async function createIndexPages(locale, localeStrings, chapters) {
const index = nunjucks.render(`index.template.html`, context);
// TODO: FIXME: Prettier is slow as hell, find an alternative that isn't...
const start = Date.now();
const data = prettier.format(index, { parser: `html` });
const end = Date.now();
//console.log(`beautification for ${locale} took ${(end - start) / 1000}s`);
// Prettification happens as an npm script action
if (locale === defaultLocale) {
fs.writeFileSync(path.join(paths.public, `index.html`), data, `utf8`);
fs.writeFileSync(path.join(paths.public, `index.html`), index, `utf8`);
} else {
let localeDir = path.join(paths.public, locale);
fs.ensureDirSync(localeDir);
fs.writeFileSync(path.join(localeDir, `index.html`), data, `utf8`);
fs.writeFileSync(path.join(localeDir, `index.html`), index, `utf8`);
}
}

View File

@@ -29,6 +29,12 @@ export default async function latexToSVG(latex, chapter, localeStrings, block) {
const srcURL = `./images/chapters/${chapter}/${hash}.svg`;
if (!fs.existsSync(SVGfilename)) {
// There is no SVG graphic for the LaTeX code yet, so we need to generate
// one, but it's possible we already tried and failed for a different locale.
// As the temp dir gets wiped on each run, we can check for the the existence
// of our TeX file: if it exists, we already unsuccessfully ran this code.
if (fs.existsSync(TeXfilename)) return fail(hash);
const PDFfilename = TeXfilename.replace(`.tex`, `.pdf`);
const PDFfilenameCropped = TeXfilename.replace(`.tex`, `-crop.pdf`);
@@ -82,48 +88,52 @@ export default async function latexToSVG(latex, chapter, localeStrings, block) {
.join(`\n`)
);
const commands = {
xetex: `xelatex -output-directory "${path.dirname(
TeXfilename
)}" "${TeXfilename}"`,
crop: `pdfcrop "${PDFfilename}"`,
svg: `pdf2svg "${PDFfilenameCropped}" "${SVGfilename}"`,
svgo: `npx svgo "${SVGfilename}"`,
};
// Finally: run the conversion
try {
process.stdout.write(
`- running xelatex for block [${chapter}:${locale}:${block}] (${hash}.tex): `
);
try {
runCmd(
`xelatex -output-directory "${path.dirname(
TeXfilename
)}" "${TeXfilename}"`,
hash
);
console.log(``);
runCmd(commands.xetex, hash);
process.stdout.write(` - cropping PDF to document content: `);
try {
runCmd(`pdfcrop "${PDFfilename}"`, hash);
console.log(``);
runCmd(commands.crop, hash);
process.stdout.write(` - converting cropped PDF to SVG: `);
try {
runCmd(`pdf2svg "${PDFfilenameCropped}" "${SVGfilename}"`, hash);
console.log(``);
runCmd(commands.svg, hash);
process.stdout.write(` - cleaning up SVG: `);
try {
runCmd(`npx svgo "${SVGfilename}"`, hash);
console.log(``);
runCmd(commands.svgo, hash);
} catch (e) {
console.log(``);
console.error(e);
if (e.cmd === commands.xetex) {
console.error(` | Error in ${TeXfilename}\n |`);
let loglines = e.output
.filter((v) => !!v)
.map((v) => v.toString("utf8").replace(/\r\n/g, `\n`))[0]
.split(`\n`)
.slice(-6); // this may depend on the xelatex version used...
loglines.splice(2, 1); // remove 'no pages of output'
// collapse and fix transcript file path
let transcript = loglines.splice(2, 3).join(``);
loglines.push(
transcript
.replace(`.log.`, `.log`)
.replace(`written on`, `written to`)
);
console.error(` | ${loglines.join(`\n | `)}\n`);
}
} catch (e) {
console.log(``);
console.error(e);
}
} catch (e) {
console.log(``);
console.error(e);
}
} catch (e) {
console.log(``);
console.error(e);
return fail(hash);
}
}
@@ -152,10 +162,11 @@ export default async function latexToSVG(latex, chapter, localeStrings, block) {
function runCmd(cmd, hash) {
try {
execSync(cmd); //, { stdio: 'inherit' });
console.log(``);
} catch (e) {
console.error(`could not run cmd: ${cmd}`);
console.log(`hash:\n${hash}`);
process.exit(1);
console.log(``);
e.cmd = cmd;
throw e;
}
}
@@ -170,3 +181,8 @@ function colorPreProcess(input) {
});
return output;
}
// Failure state HTML code is simply a script that yells in the console
function fail(hash) {
return `<script>console.log("LaTeX for ${hash} failed!");</script>`;
}

View File

@@ -55,6 +55,8 @@ async function generateFallbackImage(chapter, localeStrings, src, w, h) {
.relative(thisModuleDir, modulePath)
.split(path.sep)
.join(path.posix.sep);
try {
const { canvas } = await import(module);
fs.unlinkSync(modulePath);
@@ -66,6 +68,10 @@ async function generateFallbackImage(chapter, localeStrings, src, w, h) {
fs.writeFileSync(filePath, imageData);
console.log(`Generated fallback image for ${src} (${locale})`);
} catch (e) {
console.error(`error in ${src}`);
console.error(e);
}
return hash;
}

View File

@@ -44,7 +44,9 @@ function generateGraphicsModule(chapter, code, width, height) {
const noop = (()=>{});
const Image = CanvasBuilder.Image;
class Example extends GraphicsAPI { ${classCode} }
class Example extends GraphicsAPI {
${classCode}
}
const example = new Example(undefined, ${width}, ${height}, (w,h) => {
const canvas = CanvasBuilder.createCanvas(w,h);