|
@ -304,6 +304,7 @@ The current available styles for `design`/`upright`/`italic`/`oblique` options a
|
||||||
* `v-zero-slashed`, `cv13`: Slashed Zero `0` (default).
|
* `v-zero-slashed`, `cv13`: Slashed Zero `0` (default).
|
||||||
* `v-zero-dotted`, `cv14`: Dotted Zero `0`.
|
* `v-zero-dotted`, `cv14`: Dotted Zero `0`.
|
||||||
* `v-zero-unslashed`, `cv15`: O-like `0`.
|
* `v-zero-unslashed`, `cv15`: O-like `0`.
|
||||||
|
* `v-zero-reverse-slashed`, `cv93`: Reverse-slashed `0`.
|
||||||
* Styles for `1`:
|
* Styles for `1`:
|
||||||
* `v-one-nobase`, `cv50`: `1` with bottom serif (default for Sans).
|
* `v-one-nobase`, `cv50`: `1` with bottom serif (default for Sans).
|
||||||
* `v-one-base`, `cv51`: `1` without bottom serif (default for Slab).
|
* `v-one-base`, `cv51`: `1` without bottom serif (default for Slab).
|
||||||
|
|
|
@ -4,7 +4,6 @@ import '../support/spirokit' as spirokit
|
||||||
import '../support/transform' as : Transform && [object [transformPoint tp] [unTransform utp] inverse]
|
import '../support/transform' as : Transform && [object [transformPoint tp] [unTransform utp] inverse]
|
||||||
import '../support/anchor' as Anchor
|
import '../support/anchor' as Anchor
|
||||||
import '../support/monotonic-interpolate' as smoothreg
|
import '../support/monotonic-interpolate' as smoothreg
|
||||||
import '../support/fairify' as fairify
|
|
||||||
|
|
||||||
import [mix linreg clamp fallback TempFont includeGlyphPart compsiteMarkSet] from '../support/utils'
|
import [mix linreg clamp fallback TempFont includeGlyphPart compsiteMarkSet] from '../support/utils'
|
||||||
import [calculateMetrics setFontMetrics MarksetDiv GenDivFrame] from '../meta/aesthetics'
|
import [calculateMetrics setFontMetrics MarksetDiv GenDivFrame] from '../meta/aesthetics'
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
$$include '../meta/macros.ptl'
|
$$include '../meta/macros.ptl'
|
||||||
|
|
||||||
import '../support/transform' as : Transform && [object [transformPoint tp] [unTransform utp] inverse]
|
import '../support/transform' as : Transform && [object [transformPoint tp] [unTransform utp] inverse]
|
||||||
import '../support/fairify' as fairify
|
|
||||||
|
|
||||||
import [mix linreg clamp fallback] from '../support/utils'
|
import [mix linreg clamp fallback] from '../support/utils'
|
||||||
import [designParameters] from '../meta/aesthetics'
|
import [designParameters] from '../meta/aesthetics'
|
||||||
|
|
|
@ -4211,13 +4211,11 @@ glyph-block LetterUnified-Basic : begin
|
||||||
define tension : 0.46 - 0.005 * strokeCoeff + slantCoeff * TANSLANT
|
define tension : 0.46 - 0.005 * strokeCoeff + slantCoeff * TANSLANT
|
||||||
return : alsothruthem {{0.4 tension} {0.6 (1 - tension)}} [widths.center stroke]
|
return : alsothruthem {{0.4 tension} {0.6 (1 - tension)}} [widths.center stroke]
|
||||||
|
|
||||||
sketch # S
|
define [SStroke] : begin
|
||||||
set-width WIDTH
|
|
||||||
include MarkSet.capital
|
|
||||||
local stroke : adviceBlackness2 2.875 2 CAP
|
local stroke : adviceBlackness2 2.875 2 CAP
|
||||||
local ess : ESS * stroke / STROKE
|
local ess : ESS * stroke / STROKE
|
||||||
define smooth : adviceSSmooth CAP (-1) stroke
|
define smooth : adviceSSmooth CAP (-1) stroke
|
||||||
include : dispiro
|
return : dispiro
|
||||||
widths.lhs stroke
|
widths.lhs stroke
|
||||||
g4 RIGHTSB (CAP - HOOK)
|
g4 RIGHTSB (CAP - HOOK)
|
||||||
hookstart CAPO (sw -- stroke) (swItalicAdj -- STROKE)
|
hookstart CAPO (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
|
@ -4226,34 +4224,12 @@ glyph-block LetterUnified-Basic : begin
|
||||||
g4 RIGHTSB smooth [widths 0 stroke]
|
g4 RIGHTSB smooth [widths 0 stroke]
|
||||||
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
g4 SB HOOK
|
g4 SB HOOK
|
||||||
save 'S' 'S'
|
|
||||||
save 'cyrDze' 0x405
|
|
||||||
|
|
||||||
sketch # s
|
define [RevSStroke] : begin
|
||||||
set-width WIDTH
|
|
||||||
include MarkSet.e
|
|
||||||
define stroke : adviceBlackness2 2.875 2 XH
|
|
||||||
define ess : mix stroke (ESS * stroke / STROKE) 0.5
|
|
||||||
define strokeCoeff : linreg 18 0 126 1 stroke
|
|
||||||
define smooth : adviceSSmooth XH (-1) stroke
|
|
||||||
include : dispiro
|
|
||||||
widths.lhs stroke
|
|
||||||
g4 (RIGHTSB + OX / 4) (XH - SHOOK)
|
|
||||||
hookstart XO (sw -- stroke) (swItalicAdj -- STROKE)
|
|
||||||
g4 (SB + OX / 8) (XH - smooth)
|
|
||||||
SNeck stroke (slantCoeff -- -0.01)
|
|
||||||
g4 (RIGHTSB - OX / 8) (smooth) [widths 0 stroke]
|
|
||||||
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
|
||||||
g4 (SB - OX / 4) SHOOK
|
|
||||||
save 's' 's'
|
|
||||||
save 'cyrdze' 0x455
|
|
||||||
|
|
||||||
sketch # revS
|
|
||||||
include MarkSet.capital
|
|
||||||
local stroke : adviceBlackness2 2.875 2 CAP
|
local stroke : adviceBlackness2 2.875 2 CAP
|
||||||
local ess : ESS * stroke / STROKE
|
local ess : ESS * stroke / STROKE
|
||||||
local smooth : adviceSSmooth CAP 0.5 stroke
|
local smooth : adviceSSmooth CAP 0.5 stroke
|
||||||
include : dispiro
|
return : dispiro
|
||||||
widths.rhs stroke
|
widths.rhs stroke
|
||||||
g4 SB (CAP - HOOK)
|
g4 SB (CAP - HOOK)
|
||||||
hookstart CAPO (sw -- stroke) (swItalicAdj -- STROKE)
|
hookstart CAPO (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
|
@ -4263,15 +4239,27 @@ glyph-block LetterUnified-Basic : begin
|
||||||
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
g4 RIGHTSB HOOK
|
g4 RIGHTSB HOOK
|
||||||
|
|
||||||
save 'revS' 0x1A7
|
define [sStroke] : begin
|
||||||
|
define stroke : adviceBlackness2 2.875 2 XH
|
||||||
|
define ess : mix stroke (ESS * stroke / STROKE) 0.5
|
||||||
|
define strokeCoeff : linreg 18 0 126 1 stroke
|
||||||
|
define smooth : adviceSSmooth XH (-1) stroke
|
||||||
|
return : dispiro
|
||||||
|
widths.lhs stroke
|
||||||
|
g4 (RIGHTSB + OX / 4) (XH - SHOOK)
|
||||||
|
hookstart XO (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
|
g4 (SB + OX / 8) (XH - smooth)
|
||||||
|
SNeck stroke (slantCoeff -- -0.01)
|
||||||
|
g4 (RIGHTSB - OX / 8) (smooth) [widths 0 stroke]
|
||||||
|
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
|
g4 (SB - OX / 4) SHOOK
|
||||||
|
|
||||||
sketch # revs
|
define [RevsStroke] : begin
|
||||||
include MarkSet.e
|
|
||||||
define stroke : adviceBlackness2 2.875 2 XH
|
define stroke : adviceBlackness2 2.875 2 XH
|
||||||
define ess : ESS * stroke / STROKE
|
define ess : ESS * stroke / STROKE
|
||||||
define strokeCoeff : linreg 18 0 126 1 stroke
|
define strokeCoeff : linreg 18 0 126 1 stroke
|
||||||
define smooth : adviceSSmooth XH 1 stroke
|
define smooth : adviceSSmooth XH 1 stroke
|
||||||
include : dispiro
|
return : dispiro
|
||||||
widths.rhs stroke
|
widths.rhs stroke
|
||||||
g4 SB (XH - SHOOK)
|
g4 SB (XH - SHOOK)
|
||||||
hookstart XO (sw -- stroke) (swItalicAdj -- STROKE)
|
hookstart XO (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
|
@ -4281,43 +4269,73 @@ glyph-block LetterUnified-Basic : begin
|
||||||
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
hookend O (sw -- stroke) (swItalicAdj -- STROKE)
|
||||||
g4 RIGHTSB SHOOK
|
g4 RIGHTSB SHOOK
|
||||||
|
|
||||||
|
sketch # S
|
||||||
|
include MarkSet.capital
|
||||||
|
include : SStroke
|
||||||
|
save 'S' 'S'
|
||||||
|
save 'cyrDze' 0x405
|
||||||
|
|
||||||
|
sketch # s
|
||||||
|
include MarkSet.e
|
||||||
|
include : sStroke
|
||||||
|
save 's' 's'
|
||||||
|
save 'cyrdze' 0x455
|
||||||
|
|
||||||
|
sketch # revS
|
||||||
|
include MarkSet.capital
|
||||||
|
include : RevSStroke
|
||||||
|
save 'revS' 0x1A7
|
||||||
|
|
||||||
|
sketch # revs
|
||||||
|
include MarkSet.e
|
||||||
|
include : RevsStroke
|
||||||
save 'revs' 0x1A8
|
save 'revs' 0x1A8
|
||||||
save 'cyrghe.italic'
|
save 'cyrghe.italic'
|
||||||
|
|
||||||
sketch # srtail
|
sketch # srtail
|
||||||
include glyphs.rtailBR
|
|
||||||
local stroke : adviceBlackness2 2.875 2 XH
|
|
||||||
apply-transform : Upright
|
|
||||||
apply-transform : Translate (SB + stroke * HVCONTRAST + OXHOOK) 0
|
|
||||||
apply-transform : Italify
|
|
||||||
include MarkSet.p
|
include MarkSet.p
|
||||||
include glyphs.s
|
local stroke : [sStroke].call currentGlyph
|
||||||
include : VBarLeft (SB + OXHOOK) 0 SHOOK
|
local start : utp currentGlyph.gizmo stroke.lhsKnots.(stroke.lhsKnots.length - 1)
|
||||||
|
|
||||||
|
include : create-glyph : glyph-construction
|
||||||
|
local sw : adviceBlackness2 2.875 2 XH
|
||||||
|
include glyphs.rtailBR
|
||||||
|
apply-transform : Upright
|
||||||
|
apply-transform : Translate (start.x + sw * HVCONTRAST) 0
|
||||||
|
apply-transform : Italify
|
||||||
|
|
||||||
|
include : VBarLeft start.x 0 start.y
|
||||||
|
|
||||||
save 'srtail' 0x282
|
save 'srtail' 0x282
|
||||||
|
|
||||||
sketch # Sswash
|
sketch # Sswash
|
||||||
include MarkSet.if
|
include MarkSet.if
|
||||||
include glyphs.S
|
|
||||||
local stroke : adviceBlackness2 2.875 2 CAP
|
local stroke : [SStroke].call currentGlyph
|
||||||
|
local start : utp currentGlyph.gizmo stroke.lhsKnots.(stroke.lhsKnots.length - 1)
|
||||||
|
|
||||||
|
local sw : adviceBlackness2 2.875 2 CAP
|
||||||
include : dispiro
|
include : dispiro
|
||||||
widths.lhs [adviceBlackness 4.5]
|
widths.lhs [adviceBlackness 4.5]
|
||||||
g4 (SB + OXHOOK) HOOK
|
g4 start.x start.y
|
||||||
alsothru 0.15 0.6 important
|
alsothru 0.15 0.6 important
|
||||||
flat (RIGHTSB - 1) DESCENDER [widths stroke 0]
|
flat (RIGHTSB - 1) DESCENDER [widths sw 0]
|
||||||
curl RIGHTSB DESCENDER
|
curl RIGHTSB DESCENDER
|
||||||
|
|
||||||
save 'Sswash' 0x2C7E
|
save 'Sswash' 0x2C7E
|
||||||
|
|
||||||
sketch # sswash
|
sketch # sswash
|
||||||
include MarkSet.p
|
include MarkSet.p
|
||||||
include glyphs.s
|
|
||||||
local stroke : adviceBlackness2 2.875 2 XH
|
local stroke : [sStroke].call currentGlyph
|
||||||
|
local start : utp currentGlyph.gizmo stroke.lhsKnots.(stroke.lhsKnots.length - 1)
|
||||||
|
|
||||||
|
local sw : adviceBlackness2 2.875 2 XH
|
||||||
include : dispiro
|
include : dispiro
|
||||||
widths.lhs [adviceBlackness 4.5]
|
widths.lhs [adviceBlackness 4.5]
|
||||||
g4 (SB + OXHOOK) SHOOK
|
g4 start.x start.y
|
||||||
alsothru 0.2 0.6 important
|
alsothru 0.15 0.6 important
|
||||||
flat (RIGHTSB - 1) DESCENDER [widths stroke 0]
|
flat (RIGHTSB - 1) DESCENDER [widths sw 0]
|
||||||
curl RIGHTSB DESCENDER
|
curl RIGHTSB DESCENDER
|
||||||
save 'sswash' 0x23F
|
save 'sswash' 0x23F
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,14 @@ glyph-block Numbers : begin
|
||||||
flat 0 (top * (1 - 0.77)) [widths fine fine]
|
flat 0 (top * (1 - 0.77)) [widths fine fine]
|
||||||
curl WIDTH (top * 0.77)
|
curl WIDTH (top * 0.77)
|
||||||
|
|
||||||
|
define [ZeroReverseSlashShape top] : begin
|
||||||
|
local fine : 0.5 * OVERLAYSTROKE
|
||||||
|
return : intersection
|
||||||
|
OShapeOutline (top + O) (0 - O) (SB - O) (RIGHTSB + O)
|
||||||
|
dispiro
|
||||||
|
flat 0 (top * 0.77) [widths fine fine]
|
||||||
|
curl WIDTH (top * (1 - 0.77))
|
||||||
|
|
||||||
define [ZeroDotShape top] : begin
|
define [ZeroDotShape top] : begin
|
||||||
local radius : Math.min DOTRADIUS ((RIGHTSB - SB - STROKE * 2) / 4)
|
local radius : Math.min DOTRADIUS ((RIGHTSB - SB - STROKE * 2) / 4)
|
||||||
return : Ring (top / 2 + radius) (top / 2 - radius) (MIDDLE + radius) (MIDDLE - radius)
|
return : Ring (top / 2 + radius) (top / 2 - radius) (MIDDLE + radius) (MIDDLE - radius)
|
||||||
|
@ -39,6 +47,12 @@ glyph-block Numbers : begin
|
||||||
include : ZeroSlashShape CAP
|
include : ZeroSlashShape CAP
|
||||||
save 'zero.lnum.slashed'
|
save 'zero.lnum.slashed'
|
||||||
|
|
||||||
|
sketch # zero.lnum.reverse-slashed
|
||||||
|
include MarkSet.capital
|
||||||
|
include glyphs.'zero.lnum.unslashed'
|
||||||
|
include : ZeroReverseSlashShape CAP
|
||||||
|
save 'zero.lnum.reverse-slashed'
|
||||||
|
|
||||||
sketch # zero.lnum.dotted
|
sketch # zero.lnum.dotted
|
||||||
include MarkSet.capital
|
include MarkSet.capital
|
||||||
include glyphs.'zero.lnum.unslashed'
|
include glyphs.'zero.lnum.unslashed'
|
||||||
|
@ -56,6 +70,12 @@ glyph-block Numbers : begin
|
||||||
include : ZeroSlashShape XH
|
include : ZeroSlashShape XH
|
||||||
save 'zero.onum.slashed'
|
save 'zero.onum.slashed'
|
||||||
|
|
||||||
|
sketch # zero.onum.reverse-slashed
|
||||||
|
include MarkSet.e
|
||||||
|
include glyphs.'zero.onum.unslashed'
|
||||||
|
include : ZeroReverseSlashShape XH
|
||||||
|
save 'zero.onum.reverse-slashed'
|
||||||
|
|
||||||
sketch # zero.onum.dotted
|
sketch # zero.onum.dotted
|
||||||
include MarkSet.e
|
include MarkSet.e
|
||||||
include glyphs.'zero.onum.unslashed'
|
include glyphs.'zero.onum.unslashed'
|
||||||
|
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 173 KiB |
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 398 KiB |
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 337 KiB |
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 337 KiB |
Before Width: | Height: | Size: 960 KiB After Width: | Height: | Size: 959 KiB |
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 319 KiB |
|
@ -394,6 +394,11 @@
|
||||||
<span class="sample" style="font-feature-settings:'cv15'">0</span>
|
<span class="sample" style="font-feature-settings:'cv15'">0</span>
|
||||||
<span class="sample italic" style="font-feature-settings:'cv15'">0</span>
|
<span class="sample italic" style="font-feature-settings:'cv15'">0</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="tag">cv93</span>
|
||||||
|
<span class="sample" style="font-feature-settings:'cv93'">0</span>
|
||||||
|
<span class="sample italic" style="font-feature-settings:'cv93'">0</span>
|
||||||
|
</li>
|
||||||
</ol></li>
|
</ol></li>
|
||||||
<li><ol class="group">
|
<li><ol class="group">
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -92,43 +92,49 @@ function removeMids(contour) {
|
||||||
contour = contour.filter(x => !x.rem);
|
contour = contour.filter(x => !x.rem);
|
||||||
|
|
||||||
last = contour.length - 1;
|
last = contour.length - 1;
|
||||||
for (let j = 1; j < contour.length - 1; j++) {
|
let zPre, z, zPost;
|
||||||
if (!contour[j - 1].on && contour[j].on && !contour[j + 1].on) {
|
for (let j = 1; j < contour.length; j++) {
|
||||||
const mx = contour[j - 1].x + contour[j + 1].x;
|
if (j < contour.length - 1) {
|
||||||
const my = contour[j - 1].y + contour[j + 1].y;
|
zPre = contour[j - 1];
|
||||||
const dy = contour[j - 1].y - contour[j + 1].y;
|
z = contour[j];
|
||||||
if (
|
zPost = contour[j + 1];
|
||||||
Math.abs(dy) >= 1 &&
|
} else {
|
||||||
Math.abs(contour[j].x * 2 - mx) < 1 &&
|
zPre = contour[last];
|
||||||
Math.abs(contour[j].y * 2 - my) < 1
|
z = contour[0];
|
||||||
) {
|
zPost = contour[1];
|
||||||
contour[j].rem = true;
|
}
|
||||||
|
// if (!zPre || !zPost) continue;
|
||||||
|
const mx = zPre.x + zPost.x;
|
||||||
|
const my = zPre.y + zPost.y;
|
||||||
|
const dx = zPre.x - zPost.x;
|
||||||
|
const dy = zPre.y - zPost.y;
|
||||||
|
|
||||||
|
if (!zPre.on && z.on && !zPost.on) {
|
||||||
|
if (Math.abs(dy) >= 1 && Math.abs(z.x * 2 - mx) < 1 && Math.abs(z.y * 2 - my) < 1) {
|
||||||
|
z.rem = true;
|
||||||
|
}
|
||||||
|
} else if (!zPre.rem && zPre.on && z.on && !zPost.rem && zPost.on) {
|
||||||
|
if (Math.abs(dy) >= 1 && Math.abs(z.x * 2 - mx) < 1 && Math.abs(dx) < 1) {
|
||||||
|
z.rem = true;
|
||||||
|
} else if (Math.abs(dx) >= 1 && Math.abs(z.y * 2 - my) < 1 && Math.abs(dy) < 1) {
|
||||||
|
z.rem = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!contour[last].rem && !contour[last].on && contour[0].on && !contour[1].on) {
|
|
||||||
const mx = contour[last].x + contour[1].x;
|
|
||||||
const my = contour[last].y + contour[1].y;
|
|
||||||
if (Math.abs(contour[0].x * 2 - mx) < 1 && Math.abs(contour[0].y * 2 - my) < 1) {
|
|
||||||
contour[0].rem = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contour = contour.filter(x => !x.rem);
|
contour = contour.filter(x => !x.rem);
|
||||||
const n = contour.length;
|
const n = contour.length;
|
||||||
if (n >= n0) break;
|
if (n >= n0) break;
|
||||||
}
|
}
|
||||||
return contour;
|
return contour;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extPrior(a, b) {
|
function extPrior(a, b) {
|
||||||
return a.y < b.y || (a.y === b.y && ((a.on && !b.on) || (a.on === b.on && a.x < b.x)));
|
return a.y < b.y || (a.y === b.y && ((a.on && !b.on) || (a.on === b.on && a.x < b.x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function canonicalStart(_points) {
|
function canonicalStart(_points) {
|
||||||
const points = _points.reverse().map(z => {
|
const points = _points.reverse();
|
||||||
z.x = Math.round(z.x * 1024) / 1024;
|
|
||||||
z.y = Math.round(z.y * 1024) / 1024;
|
|
||||||
return z;
|
|
||||||
});
|
|
||||||
let jm = 0;
|
let jm = 0;
|
||||||
for (var j = 0; j < points.length * 2; j++) {
|
for (var j = 0; j < points.length * 2; j++) {
|
||||||
if (extPrior(points[j % points.length], points[jm])) {
|
if (extPrior(points[j % points.length], points[jm])) {
|
||||||
|
@ -138,93 +144,8 @@ function canonicalStart(_points) {
|
||||||
return points.slice(jm).concat(points.slice(0, jm));
|
return points.slice(jm).concat(points.slice(0, jm));
|
||||||
}
|
}
|
||||||
|
|
||||||
function colinear(x1, y1, x2, y2, x3, y3, err) {
|
function cleanupQuadContour(c) {
|
||||||
const det = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
|
return canonicalStart(removeMids(c));
|
||||||
return det <= err && det >= -err;
|
|
||||||
}
|
|
||||||
|
|
||||||
function inspan(a, b, c) {
|
|
||||||
if (a > c) return inspan(c, b, a);
|
|
||||||
return a <= b && b <= c;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle(z1, z2, z3, z4, err) {
|
|
||||||
if (
|
|
||||||
colinear(z1.x, z1.y, z2.x, z2.y, z4.x, z4.y, err) &&
|
|
||||||
colinear(z1.x, z1.y, z3.x, z3.y, z4.x, z4.y, err) &&
|
|
||||||
inspan(z1.x, z2.x, z4.x) &&
|
|
||||||
inspan(z1.y, z2.y, z4.y) &&
|
|
||||||
inspan(z1.x, z3.x, z4.x) &&
|
|
||||||
inspan(z1.y, z3.y, z4.y)
|
|
||||||
) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const curve = new typoGeom.Curve.Bez3(z1, z2, z3, z4);
|
|
||||||
const offPoints = typoGeom.Quadify.auto(curve, err);
|
|
||||||
const ans = [];
|
|
||||||
for (const z of offPoints) {
|
|
||||||
ans.push(Point.offFrom(z));
|
|
||||||
}
|
|
||||||
return ans;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertContourToTt(contour, err) {
|
|
||||||
if (contour.length === 0) return [];
|
|
||||||
if (contour.length === 1) return [contour[0]];
|
|
||||||
err = err || 1 / 4;
|
|
||||||
|
|
||||||
const newContour = [];
|
|
||||||
let z0 = contour[0];
|
|
||||||
newContour.push(Point.cornerFrom(z0));
|
|
||||||
|
|
||||||
for (let j = 1; j < contour.length; j++) {
|
|
||||||
const z = contour[j];
|
|
||||||
if (z.on) {
|
|
||||||
newContour.push(Point.cornerFrom(z));
|
|
||||||
z0 = z;
|
|
||||||
} else if (z.cubic) {
|
|
||||||
const z1 = z;
|
|
||||||
const z2 = contour[j + 1];
|
|
||||||
const z3 = contour[j + 2];
|
|
||||||
const quadZs = handle(z0, z1, z2, z3, err);
|
|
||||||
for (const z of quadZs) newContour.push(z);
|
|
||||||
newContour.push(Point.cornerFrom(z3));
|
|
||||||
z0 = z3;
|
|
||||||
j += 2;
|
|
||||||
} else {
|
|
||||||
const zc = z;
|
|
||||||
let zf = contour[j + 1] ? contour[j + 1] : contour[0];
|
|
||||||
if (!zf.on) {
|
|
||||||
zf = Point.cornerFromXY(mix(zc.x, zf.x, 0.5), mix(zc.y, zf.y, 0.5));
|
|
||||||
}
|
|
||||||
newContour.push(Point.offFrom(zc));
|
|
||||||
newContour.push(Point.cornerFrom(zf));
|
|
||||||
z0 = zf;
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newContour;
|
|
||||||
}
|
|
||||||
|
|
||||||
function byFirstPointCoord(a, b) {
|
|
||||||
if (!a.length) return -1;
|
|
||||||
if (!b.length) return 1;
|
|
||||||
let z1 = a[0];
|
|
||||||
let z2 = b[0];
|
|
||||||
return z1.y !== z2.y
|
|
||||||
? z1.y - z2.y
|
|
||||||
: z1.x !== z2.x
|
|
||||||
? z1.x - z2.x
|
|
||||||
: byFirstPointCoord(a.slice(1), b.slice(1));
|
|
||||||
}
|
|
||||||
function convertContourListToTt(contours, err) {
|
|
||||||
err = err || 1 / 4;
|
|
||||||
let ans = [];
|
|
||||||
for (let c of contours) {
|
|
||||||
ans.push(canonicalStart(removeMids(convertContourToTt(c, err))));
|
|
||||||
}
|
|
||||||
return ans.sort(byFirstPointCoord);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertContourToCubic(contour) {
|
function convertContourToCubic(contour) {
|
||||||
|
@ -243,34 +164,67 @@ function convertContourToCubic(contour) {
|
||||||
const z1 = z;
|
const z1 = z;
|
||||||
const z2 = contour[j + 1];
|
const z2 = contour[j + 1];
|
||||||
const z3 = contour[j + 2];
|
const z3 = contour[j + 2];
|
||||||
|
|
||||||
newContour.push(Point.cubicOffFrom(z1));
|
newContour.push(Point.cubicOffFrom(z1));
|
||||||
newContour.push(Point.cubicOffFrom(z2));
|
newContour.push(Point.cubicOffFrom(z2));
|
||||||
newContour.push(Point.cornerFrom(z3));
|
newContour.push(Point.cornerFrom(z3));
|
||||||
|
|
||||||
z0 = z3;
|
z0 = z3;
|
||||||
j += 2;
|
j += 2;
|
||||||
} else {
|
} else {
|
||||||
const zc = z;
|
const zc = z;
|
||||||
let zf = contour[j + 1] ? contour[j + 1] : contour[0];
|
let zf = contour[j + 1] || contour[0];
|
||||||
if (!zf.on) {
|
const zfIsCorner = zf.on;
|
||||||
zf = Point.cornerFromXY(mix(zc.x, zf.x, 0.5), mix(zc.y, zf.y, 0.5));
|
if (!zfIsCorner) zf = Point.cornerFrom(zc).mix(0.5, zf);
|
||||||
}
|
|
||||||
|
|
||||||
const x1 = mix(z0.x, zc.x, 2 / 3);
|
newContour.push(Point.cubicOffFrom(z0).mix(2 / 3, zc));
|
||||||
const y1 = mix(z0.y, zc.y, 2 / 3);
|
newContour.push(Point.cubicOffFrom(zf).mix(2 / 3, zc));
|
||||||
const x2 = mix(zf.x, zc.x, 2 / 3);
|
|
||||||
const y2 = mix(zf.y, zc.y, 2 / 3);
|
|
||||||
|
|
||||||
newContour.push(Point.cubicOffFromXY(x1, y1));
|
|
||||||
newContour.push(Point.cubicOffFromXY(x2, y2));
|
|
||||||
newContour.push(Point.cornerFrom(zf));
|
newContour.push(Point.cornerFrom(zf));
|
||||||
|
|
||||||
z0 = zf;
|
z0 = zf;
|
||||||
j++;
|
if (zfIsCorner) j++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newContour;
|
return newContour;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.convertContourToTt = convertContourToTt;
|
function autoCubify(arc, err) {
|
||||||
|
const MaxSegments = 16;
|
||||||
|
const Hits = 64;
|
||||||
|
let offPoints = [];
|
||||||
|
for (let nSeg = 1; nSeg <= MaxSegments; nSeg++) {
|
||||||
|
const perSegHits = Math.ceil(Hits / nSeg);
|
||||||
|
offPoints.length = 0;
|
||||||
|
let good = true;
|
||||||
|
out: for (let s = 0; s < nSeg; s++) {
|
||||||
|
const tBefore = s / nSeg;
|
||||||
|
const tAfter = (s + 1) / nSeg;
|
||||||
|
const z0 = Point.cornerFrom(arc.eval(tBefore));
|
||||||
|
const z3 = Point.cornerFrom(arc.eval(tAfter));
|
||||||
|
const z1 = Point.cubicOffFrom(z0).addScale(1 / (3 * nSeg), arc.derivative(tBefore));
|
||||||
|
const z2 = Point.cubicOffFrom(z3).addScale(-1 / (3 * nSeg), arc.derivative(tAfter));
|
||||||
|
|
||||||
|
if (s > 0) offPoints.push(z0);
|
||||||
|
offPoints.push(z1, z2);
|
||||||
|
|
||||||
|
const bezArc = new typoGeom.Curve.Bez3(z0, z1, z2, z3);
|
||||||
|
|
||||||
|
for (let k = 1; k < perSegHits; k++) {
|
||||||
|
const tk = k / perSegHits;
|
||||||
|
const zTest = arc.eval(mix(tBefore, tAfter, tk));
|
||||||
|
const zBez = bezArc.eval(tk);
|
||||||
|
if (Math.hypot(zTest.x - zBez.x, zTest.y - zBez.y) > err) {
|
||||||
|
good = false;
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (good) break;
|
||||||
|
}
|
||||||
|
return offPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.cleanupQuadContour = cleanupQuadContour;
|
||||||
exports.convertContourToCubic = convertContourToCubic;
|
exports.convertContourToCubic = convertContourToCubic;
|
||||||
exports.convertContourListToTt = convertContourListToTt;
|
exports.autoCubify = autoCubify;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Transform = require("./transform.js");
|
const Transform = require("./transform");
|
||||||
const typoGeom = require("typo-geom");
|
const typoGeom = require("typo-geom");
|
||||||
|
const Point = require("./point");
|
||||||
|
const curveUtil = require("./curve-util");
|
||||||
|
|
||||||
const SMALL = 1e-4;
|
const SMALL = 1e-6;
|
||||||
|
|
||||||
function solveTS(a, b, c, out, flag) {
|
function solveTS(a, b, c, out, flag) {
|
||||||
const delta = b * b - 4 * a * c;
|
const delta = b * b - 4 * a * c;
|
||||||
|
@ -43,7 +45,7 @@ function fineAllExtrema(z1, z2, z3, z4) {
|
||||||
|
|
||||||
return exs.sort(ASCEND);
|
return exs.sort(ASCEND);
|
||||||
}
|
}
|
||||||
function mix(z1, z2, t) {
|
function bez1(z1, z2, t) {
|
||||||
if (t <= 0) return z1;
|
if (t <= 0) return z1;
|
||||||
if (t >= 1) return z2;
|
if (t >= 1) return z2;
|
||||||
let x = (1 - t) * z1.x + t * z2.x,
|
let x = (1 - t) * z1.x + t * z2.x,
|
||||||
|
@ -74,10 +76,10 @@ function bez3(z1, z2, z3, z4, t) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function splitBefore(z1, z2, z3, z4, t) {
|
function splitBefore(z1, z2, z3, z4, t) {
|
||||||
return [z1, mix(z1, z2, t), bez2(z1, z2, z3, t), bez3(z1, z2, z3, z4, t)];
|
return [z1, bez1(z1, z2, t), bez2(z1, z2, z3, t), bez3(z1, z2, z3, z4, t)];
|
||||||
}
|
}
|
||||||
function splitAfter(z1, z2, z3, z4, t) {
|
function splitAfter(z1, z2, z3, z4, t) {
|
||||||
return [bez3(z1, z2, z3, z4, t), bez2(z2, z3, z4, t), mix(z3, z4, t), z4];
|
return [bez3(z1, z2, z3, z4, t), bez2(z2, z3, z4, t), bez1(z3, z4, t), z4];
|
||||||
}
|
}
|
||||||
function splitAtExtrema(z1, z2, z3, z4, curve) {
|
function splitAtExtrema(z1, z2, z3, z4, curve) {
|
||||||
const ts = fineAllExtrema(z1, z2, z3, z4);
|
const ts = fineAllExtrema(z1, z2, z3, z4);
|
||||||
|
@ -109,41 +111,33 @@ function veryClose(z1, z2) {
|
||||||
return (z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y) <= SMALL;
|
return (z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y) <= SMALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitCurve(sourceCurve) {
|
function toSpansForm(sourceCurve, fSplit) {
|
||||||
const curve = [sourceCurve[0]];
|
const curve = [sourceCurve[0]];
|
||||||
let last = sourceCurve[0];
|
let last = sourceCurve[0];
|
||||||
for (let j = 1; j < sourceCurve.length; j++) {
|
for (let j = 1; j < sourceCurve.length; j++) {
|
||||||
if (sourceCurve[j].on) {
|
if (sourceCurve[j].on) {
|
||||||
const z1 = last,
|
const z1 = last,
|
||||||
z4 = sourceCurve[j];
|
z4 = sourceCurve[j];
|
||||||
// const z2 = mix(z1, z4, 1 / 3);
|
|
||||||
//const z3 = mix(z1, z4, 2 / 3);
|
|
||||||
if (!veryClose(z1, z4)) {
|
if (!veryClose(z1, z4)) {
|
||||||
curve.push(z4);
|
curve.push(z4);
|
||||||
// splitAtExtrema(z1, z2, z3, z4, ANGLES, curve);
|
|
||||||
last = z4;
|
last = z4;
|
||||||
}
|
}
|
||||||
} else if (sourceCurve[j].cubic) {
|
} else if (sourceCurve[j].cubic) {
|
||||||
const z1 = last,
|
const z1 = last,
|
||||||
z2 = sourceCurve[j],
|
z2 = sourceCurve[j],
|
||||||
z3 = sourceCurve[j + 1],
|
z3 = sourceCurve[(j + 1) % sourceCurve.length],
|
||||||
z4 = sourceCurve[j + 2];
|
z4 = sourceCurve[(j + 2) % sourceCurve.length];
|
||||||
if (!(veryClose(z1, z2) && veryClose(z2, z3) && veryClose(z3, z4))) {
|
if (!(veryClose(z1, z2) && veryClose(z2, z3) && veryClose(z3, z4))) {
|
||||||
|
if (fSplit) {
|
||||||
splitAtExtrema(z1, z2, z3, z4, curve);
|
splitAtExtrema(z1, z2, z3, z4, curve);
|
||||||
|
} else {
|
||||||
|
curve.push(z2, z3, z4);
|
||||||
|
}
|
||||||
last = z4;
|
last = z4;
|
||||||
}
|
}
|
||||||
j += 2;
|
j += 2;
|
||||||
} else {
|
} else {
|
||||||
const z1 = last,
|
throw new Error("Unreachable.");
|
||||||
zm = sourceCurve[j],
|
|
||||||
z4 = sourceCurve[j + 1];
|
|
||||||
if (!(veryClose(z1, zm) && veryClose(zm, z4))) {
|
|
||||||
const z2 = mix(zm, z1, 1 / 3);
|
|
||||||
const z3 = mix(zm, z4, 1 / 3);
|
|
||||||
splitAtExtrema(z1, z2, z3, z4, curve);
|
|
||||||
last = z4;
|
|
||||||
}
|
|
||||||
j += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return curve;
|
return curve;
|
||||||
|
@ -157,35 +151,47 @@ function dot(z1, z2, z3) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function markCorners(curve) {
|
function markCorners(curve) {
|
||||||
|
for (const z of curve) z.mark = 0;
|
||||||
for (let j = 0; j < curve.length; j++) {
|
for (let j = 0; j < curve.length; j++) {
|
||||||
if (!curve[j].on) continue;
|
if (!curve[j].on) continue;
|
||||||
const z1 = curve[j],
|
const z1 = curve[j],
|
||||||
z0 = curve[(j - 1 + curve.length) % curve.length],
|
z0 = curve[(j - 1 + curve.length) % curve.length],
|
||||||
z2 = curve[(j + 1) % curve.length];
|
z2 = curve[(j + 1) % curve.length];
|
||||||
if (Math.abs(cross(z1, z0, z2)) < 1e-6) {
|
|
||||||
|
const almostLinear = Math.abs(cross(z1, z0, z2)) < SMALL;
|
||||||
|
const inBetween = dot(z1, z0, z2) < 0;
|
||||||
|
if (z0.on && z2.on && almostLinear && inBetween) {
|
||||||
|
z1.mark = 0;
|
||||||
|
} else if (z0.on || z2.on) {
|
||||||
|
z1.mark = 1;
|
||||||
|
} else if (almostLinear && inBetween) {
|
||||||
// Z0 -- Z1 -- Z2 are linear
|
// Z0 -- Z1 -- Z2 are linear
|
||||||
if (!z0.on && !z2.on && dot(z1, z0, z2) < 0) {
|
const angle = Math.abs(Math.atan2(z2.y - z0.y, z2.x - z0.x)) % Math.PI;
|
||||||
const angle0 = Math.atan2(z2.y - z0.y, z2.x - z0.x);
|
if (Math.abs(angle) <= SMALL || Math.abs(angle - Math.PI) <= SMALL) {
|
||||||
const angle = Math.abs(((angle0 / Math.PI) * 2) % 1);
|
z1.mark = 4;
|
||||||
if (
|
} else if (Math.abs(angle - Math.PI / 2) <= SMALL) {
|
||||||
Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL ||
|
z1.mark = 2;
|
||||||
angle <= SMALL ||
|
|
||||||
angle >= 1 - SMALL
|
|
||||||
) {
|
|
||||||
z1.mark = true; // curve extremum
|
|
||||||
}
|
|
||||||
} else if (z0.on && z2.on && dot(z1, z0, z2) < 0) {
|
|
||||||
// Colinear on-knots
|
|
||||||
// Remove
|
|
||||||
} else {
|
|
||||||
z1.mark = true; // also corner
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
z1.mark = true; // corner
|
z1.mark = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canonicalStart(curve) {
|
||||||
|
let jm = 0,
|
||||||
|
rank = 0;
|
||||||
|
for (let j = 0; j < curve.length; j++) {
|
||||||
|
const zRank = (curve[j].on ? 1 : 0) + (2 * curve[j].mark || 0);
|
||||||
|
if (zRank > rank) {
|
||||||
|
jm = j;
|
||||||
|
rank = zRank;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toSpansForm(curve.slice(jm).concat(curve.slice(0, jm)), false);
|
||||||
|
}
|
||||||
|
|
||||||
class BezierCurveCluster {
|
class BezierCurveCluster {
|
||||||
constructor(zs) {
|
constructor(zs) {
|
||||||
let segments = [];
|
let segments = [];
|
||||||
|
@ -195,9 +201,7 @@ class BezierCurveCluster {
|
||||||
if (zs[j].on) {
|
if (zs[j].on) {
|
||||||
const z1 = last,
|
const z1 = last,
|
||||||
z4 = zs[j];
|
z4 = zs[j];
|
||||||
const z2 = mix(z1, z4, 1 / 3);
|
const seg = new typoGeom.Curve.StraightSegment(z1, z4);
|
||||||
const z3 = mix(z1, z4, 2 / 3);
|
|
||||||
const seg = new typoGeom.Curve.Bez3(z1, z2, z3, z4);
|
|
||||||
segments.push(seg);
|
segments.push(seg);
|
||||||
lengths.push(this.measureLength(seg));
|
lengths.push(this.measureLength(seg));
|
||||||
last = z4;
|
last = z4;
|
||||||
|
@ -212,16 +216,7 @@ class BezierCurveCluster {
|
||||||
last = z4;
|
last = z4;
|
||||||
j += 2;
|
j += 2;
|
||||||
} else {
|
} else {
|
||||||
const z1 = last,
|
throw new Error("Unreachable.");
|
||||||
zm = zs[j],
|
|
||||||
z4 = zs[j + 1];
|
|
||||||
const z2 = mix(zm, z1, 1 / 3);
|
|
||||||
const z3 = mix(zm, z4, 1 / 3);
|
|
||||||
const seg = new typoGeom.Curve.Bez3(z1, z2, z3, z4);
|
|
||||||
segments.push(seg);
|
|
||||||
lengths.push(this.measureLength(seg));
|
|
||||||
last = z4;
|
|
||||||
j += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +276,7 @@ class BezierCurveCluster {
|
||||||
return det < err * err && det > -err * err;
|
return det < err * err && det > -err * err;
|
||||||
}
|
}
|
||||||
isAlmostLinear(err) {
|
isAlmostLinear(err) {
|
||||||
const N = 16;
|
const N = 64;
|
||||||
let z0 = this.eval(0);
|
let z0 = this.eval(0);
|
||||||
let z1 = this.eval(1);
|
let z1 = this.eval(1);
|
||||||
for (let k = 1; k < N; k++) {
|
for (let k = 1; k < N; k++) {
|
||||||
|
@ -292,60 +287,72 @@ class BezierCurveCluster {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCurve(curve) {
|
const QuadBuilder = {
|
||||||
let exitPoints = [];
|
corner(sink, gizmo, z) {
|
||||||
|
sink.push(Transform.transformPoint(gizmo, Point.cornerFrom(z)).round(1024));
|
||||||
|
},
|
||||||
|
arc(sink, gizmo, arc) {
|
||||||
|
if (arc.isAlmostLinear(1 / 4)) return;
|
||||||
|
const offPoints = typoGeom.Quadify.auto(arc, 1 / 4);
|
||||||
|
if (!offPoints) return;
|
||||||
|
for (const z of offPoints) {
|
||||||
|
sink.push(Transform.transformPoint(gizmo, Point.offFrom(z)).round(1024));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
split: false,
|
||||||
|
canonicalStart: true,
|
||||||
|
duplicateStart: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpiroBuilder = {
|
||||||
|
corner(sink, gizmo, z) {
|
||||||
|
sink.push(Transform.transformPoint(gizmo, Point.cornerFrom(z)));
|
||||||
|
},
|
||||||
|
arc(sink, gizmo, arc) {
|
||||||
|
if (arc.isAlmostLinear(1 / 4)) return;
|
||||||
|
const offPoints = curveUtil.autoCubify(arc, 1 / 4);
|
||||||
|
for (const z of offPoints) {
|
||||||
|
sink.push(Transform.transformPoint(gizmo, z));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
split: true,
|
||||||
|
canonicalStart: false,
|
||||||
|
duplicateStart: false
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildCurve(curve, gizmo, builder) {
|
||||||
|
let sink = [];
|
||||||
for (let j = 0; j < curve.length; j++) {
|
for (let j = 0; j < curve.length; j++) {
|
||||||
if (!curve[j].mark) continue;
|
if (!curve[j].mark) continue;
|
||||||
|
builder.corner(sink, gizmo, curve[j]);
|
||||||
|
|
||||||
let k = j;
|
let k = j;
|
||||||
for (; k < curve.length && (k === j || !curve[k].mark); k++);
|
for (; k < curve.length && (k === j || !curve[k].mark); k++);
|
||||||
exitPoints.push(curve[j]);
|
|
||||||
const pts = curve.slice(j, k + 1);
|
const pts = curve.slice(j, k + 1);
|
||||||
let nPtsOffPoints = 0;
|
if (pts.length > 1) builder.arc(sink, gizmo, new BezierCurveCluster(pts));
|
||||||
for (const z of pts) {
|
|
||||||
if (!z.on) nPtsOffPoints += 1;
|
|
||||||
}
|
|
||||||
if (nPtsOffPoints > 0) {
|
|
||||||
const curve = new BezierCurveCluster(pts);
|
|
||||||
if (curve.isAlmostLinear(1)) continue;
|
|
||||||
const offPoints = typoGeom.Quadify.auto(curve, 1 / 4);
|
|
||||||
if (!offPoints) continue;
|
|
||||||
for (let k = 0; k < offPoints.length; k++) {
|
|
||||||
const z = offPoints[k];
|
|
||||||
if (k > 0) {
|
|
||||||
const z0 = offPoints[k - 1];
|
|
||||||
exitPoints.push({
|
|
||||||
x: (z.x + z0.x) / 2,
|
|
||||||
y: (z.y + z0.y) / 2,
|
|
||||||
on: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
exitPoints.push({
|
|
||||||
x: z.x,
|
|
||||||
y: z.y,
|
|
||||||
cubic: false,
|
|
||||||
on: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j = k - 1;
|
j = k - 1;
|
||||||
}
|
}
|
||||||
return exitPoints;
|
return sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function(sourceCurve, gizmo) {
|
function fairifyImpl(sourceCubicContour, gizmo, builder) {
|
||||||
for (let j = 0; j < sourceCurve.length; j++) {
|
for (let j = 0; j < sourceCubicContour.length; j++) {
|
||||||
if (!isFinite(sourceCurve[j].x)) sourceCurve[j].x = 0;
|
if (!isFinite(sourceCubicContour[j].x)) sourceCubicContour[j].x = 0;
|
||||||
if (!isFinite(sourceCurve[j].y)) sourceCurve[j].y = 0;
|
if (!isFinite(sourceCubicContour[j].y)) sourceCubicContour[j].y = 0;
|
||||||
sourceCurve[j] = Transform.unTransform(gizmo, sourceCurve[j]);
|
sourceCubicContour[j] = Transform.unTransform(gizmo, sourceCubicContour[j]);
|
||||||
}
|
}
|
||||||
const curve = splitCurve(sourceCurve);
|
let splitContour = toSpansForm(sourceCubicContour, builder.split);
|
||||||
markCorners(curve);
|
markCorners(splitContour);
|
||||||
const builtCurve = buildCurve(curve);
|
if (builder.canonicalStart) {
|
||||||
const ans = [];
|
splitContour = canonicalStart(splitContour);
|
||||||
for (let j = 0; j < builtCurve.length; j++) {
|
markCorners(splitContour);
|
||||||
if (builtCurve[j] && !builtCurve[j].remove) {
|
|
||||||
ans.push(Transform.transformPoint(gizmo, builtCurve[j]));
|
|
||||||
}
|
}
|
||||||
|
return buildCurve(splitContour, gizmo, builder);
|
||||||
}
|
}
|
||||||
return ans;
|
|
||||||
|
exports.fairifyQuad = function(sourceCubicContour, gizmo) {
|
||||||
|
return fairifyImpl(sourceCubicContour, gizmo, QuadBuilder);
|
||||||
|
};
|
||||||
|
exports.fairifySpiro = function(sourceCubicContour, gizmo) {
|
||||||
|
return fairifyImpl(sourceCubicContour, gizmo, SpiroBuilder);
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,41 @@ export all : class Point
|
||||||
this.cubic = cubic || false
|
this.cubic = cubic || false
|
||||||
this.subdivided = subdivided || false
|
this.subdivided = subdivided || false
|
||||||
|
|
||||||
|
public [add z2] : new Point
|
||||||
|
this.x + z2.x
|
||||||
|
this.y + z2.y
|
||||||
|
* this.on
|
||||||
|
* this.cubic
|
||||||
|
* this.subdivided
|
||||||
|
|
||||||
|
public [addScale scale z2] : new Point
|
||||||
|
this.x + scale * z2.x
|
||||||
|
this.y + scale * z2.y
|
||||||
|
* this.on
|
||||||
|
* this.cubic
|
||||||
|
* this.subdivided
|
||||||
|
|
||||||
|
public [mix scale z2] : new Point
|
||||||
|
this.x + scale * (z2.x - this.x)
|
||||||
|
this.y + scale * (z2.y - this.y)
|
||||||
|
* this.on
|
||||||
|
* this.cubic
|
||||||
|
* this.subdivided
|
||||||
|
|
||||||
|
public [scale t] : new Point
|
||||||
|
t * this.x
|
||||||
|
t * this.y
|
||||||
|
* this.on
|
||||||
|
* this.cubic
|
||||||
|
* this.subdivided
|
||||||
|
|
||||||
|
public [round d] : new Point
|
||||||
|
[Math.round (this.x * d)] / d
|
||||||
|
[Math.round (this.y * d)] / d
|
||||||
|
* this.on
|
||||||
|
* this.cubic
|
||||||
|
* this.subdivided
|
||||||
|
|
||||||
static [from z on cubic subdivided] : new Point
|
static [from z on cubic subdivided] : new Point
|
||||||
* z.x
|
* z.x
|
||||||
* z.y
|
* z.y
|
||||||
|
|
|
@ -4,6 +4,8 @@ const Glyph = require("./glyph");
|
||||||
const autoRef = require("./autoref");
|
const autoRef = require("./autoref");
|
||||||
const caryllShapeOps = require("caryll-shapeops");
|
const caryllShapeOps = require("caryll-shapeops");
|
||||||
const curveUtil = require("./curve-util");
|
const curveUtil = require("./curve-util");
|
||||||
|
const { fairifyQuad } = require("./fairify");
|
||||||
|
const Transform = require("./transform");
|
||||||
|
|
||||||
function regulateGlyph(g, skew) {
|
function regulateGlyph(g, skew) {
|
||||||
if (!g.contours) return;
|
if (!g.contours) return;
|
||||||
|
@ -13,31 +15,11 @@ function regulateGlyph(g, skew) {
|
||||||
const contour = g.contours[k];
|
const contour = g.contours[k];
|
||||||
for (let p = 0; p < contour.length; p++) {
|
for (let p = 0; p < contour.length; p++) {
|
||||||
contour[p].x += contour[p].y * skew;
|
contour[p].x += contour[p].y * skew;
|
||||||
if (!contour[p].on) continue;
|
|
||||||
contour[p].x = Math.round(contour[p].x);
|
|
||||||
}
|
|
||||||
let offJ = null,
|
|
||||||
mx = null;
|
|
||||||
for (let p = 0; p < contour.length; p++) {
|
|
||||||
if (!contour[p].on) continue;
|
|
||||||
if (offJ) {
|
|
||||||
const origX = contour[p].x;
|
|
||||||
const rx = Math.round(contour[p].x * 4) / 4;
|
|
||||||
const origX0 = mx;
|
|
||||||
const rx0 = contour[offJ - 1].x;
|
|
||||||
if (origX === origX0) continue;
|
|
||||||
for (let pOff = offJ; pOff < p; pOff++) {
|
|
||||||
contour[pOff].x =
|
|
||||||
((contour[pOff].x - origX0) / (origX - origX0)) * (rx - rx0) + rx0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mx = contour[p].x;
|
|
||||||
contour[p].x = Math.round(contour[p].x * 4) / 4;
|
|
||||||
offJ = p + 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.contours = simplifyContours(g.contours);
|
g.contours = simplifyContours(g.contours);
|
||||||
|
|
||||||
for (let k = 0; k < g.contours.length; k++) {
|
for (let k = 0; k < g.contours.length; k++) {
|
||||||
const contour = g.contours[k];
|
const contour = g.contours[k];
|
||||||
for (let p = 0; p < contour.length; p++) {
|
for (let p = 0; p < contour.length; p++) {
|
||||||
|
@ -47,16 +29,17 @@ function regulateGlyph(g, skew) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifyContours(contours) {
|
function simplifyContours(contours) {
|
||||||
|
const gizmo = Transform.Id();
|
||||||
const source = [];
|
const source = [];
|
||||||
for (const contour of contours) {
|
for (const contour of contours) {
|
||||||
if (contour.length > 2) source.push(curveUtil.convertContourToCubic(contour));
|
if (contour.length > 2) source.push(curveUtil.convertContourToCubic(contour));
|
||||||
}
|
}
|
||||||
const simplified = curveUtil.convertContourListToTt(
|
const simplified = caryllShapeOps.removeOverlap(source, 1, 1 << 17, true);
|
||||||
caryllShapeOps.removeOverlap(source, 1, 1 << 17, true)
|
|
||||||
);
|
|
||||||
const result = [];
|
const result = [];
|
||||||
for (const contour of simplified) {
|
for (const contour of simplified) {
|
||||||
if (contour.length > 2) result.push(contour);
|
if (contour.length <= 2) continue;
|
||||||
|
result.push(curveUtil.cleanupQuadContour(fairifyQuad(contour, gizmo)));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'spiro' as SpiroJs
|
import 'spiro' as SpiroJs
|
||||||
import './spiroexpand' as SpiroExpansionContext
|
import './spiroexpand' as SpiroExpansionContext
|
||||||
import './fairify' as fairify
|
import [fairifySpiro] from './fairify'
|
||||||
import 'caryll-shapeops' as ShapeOps
|
import 'caryll-shapeops' as ShapeOps
|
||||||
import './curve-util' as CurveUtil
|
import './curve-util' as CurveUtil
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ export : define [SetupBuilders args] : begin
|
||||||
lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner'
|
lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner'
|
||||||
SpiroJs.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION
|
SpiroJs.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION
|
||||||
foreach [j : range 0 g.contours.length] : begin
|
foreach [j : range 0 g.contours.length] : begin
|
||||||
set g.contours.(j) : fairify g.contours.(j) globalTransform
|
set g.contours.(j) : fairifySpiro g.contours.(j) globalTransform
|
||||||
set g.knots knots
|
set g.knots knots
|
||||||
set g.lhsKnots lhs
|
set g.lhsKnots lhs
|
||||||
set g.rhsKnots rhs
|
set g.rhsKnots rhs
|
||||||
|
@ -204,7 +204,7 @@ export : define [SetupBuilders args] : begin
|
||||||
SpiroJs.spiroToBezierOnContext knots closed g QUAD PRECISION
|
SpiroJs.spiroToBezierOnContext knots closed g QUAD PRECISION
|
||||||
foreach af [items-of lastafs] : if af : af.call g
|
foreach af [items-of lastafs] : if af : af.call g
|
||||||
foreach [j : range 0 g.contours.length] : begin
|
foreach [j : range 0 g.contours.length] : begin
|
||||||
set g.contours.(j) : fairify g.contours.(j) (g.fairGizmo || g.gizmo)
|
set g.contours.(j) : fairifySpiro g.contours.(j) (g.fairGizmo || g.gizmo)
|
||||||
if [not dontinc] : this.include g
|
if [not dontinc] : this.include g
|
||||||
return g
|
return g
|
||||||
|
|
||||||
|
|
|
@ -357,6 +357,12 @@ sampler = '0'
|
||||||
description = 'O-like `0`'
|
description = 'O-like `0`'
|
||||||
zero = 'unslashed'
|
zero = 'unslashed'
|
||||||
|
|
||||||
|
[simple.v-zero-reverse-slashed]
|
||||||
|
tag = 'cv93'
|
||||||
|
sampler = '0'
|
||||||
|
description = 'Reverse-slashed `0`'
|
||||||
|
zero = 'reverse-slashed'
|
||||||
|
|
||||||
[simple.v-one-nobase]
|
[simple.v-one-nobase]
|
||||||
tag = "cv50"
|
tag = "cv50"
|
||||||
sampler = '1'
|
sampler = '1'
|
||||||
|
|