diff --git a/src/entityToPolyline.js b/src/entityToPolyline.js
index 07a24d3..55a728f 100644
--- a/src/entityToPolyline.js
+++ b/src/entityToPolyline.js
@@ -72,7 +72,7 @@ const interpolateEllipse = (cx, cy, rx, ry, start, end, rotationAngle) => {
* @param knots the knot vector
* @returns the polyline
*/
-const interpolateBSpline = (controlPoints, degree, knots, interpolationsPerSplineSegment) => {
+export const interpolateBSpline = (controlPoints, degree, knots, interpolationsPerSplineSegment) => {
const polyline = []
const controlPointsForLib = controlPoints.map(function (p) {
return [p.x, p.y]
diff --git a/src/toSVG.js b/src/toSVG.js
index 964e229..4472483 100644
--- a/src/toSVG.js
+++ b/src/toSVG.js
@@ -7,6 +7,7 @@ import getRGBForEntity from './getRGBForEntity'
import logger from './util/logger'
import rotate from './util/rotate'
import rgbToColorAttribute from './util/rgbToColorAttribute'
+import toPiecewiseBezier from './util/toPiecewiseBezier'
import transformBoundingBoxAndElement from './transformBoundingBoxAndElement'
const addFlipXIfApplicable = (entity, { bbox, element }) => {
@@ -136,6 +137,32 @@ const arc = (entity) => {
return transformBoundingBoxAndElement(bbox, element, entity.transforms)
}
+export const piecewiseToPaths = (k, controlPoints) => {
+ const nSegments = (controlPoints.length - 1) / (k - 1)
+ const paths = []
+ for (let i = 0; i < nSegments; ++i) {
+ const cp = controlPoints.slice(i * (k - 1))
+ if (k === 4) {
+ paths.push(``)
+ } else if (k === 3) {
+ paths.push(``)
+ }
+ }
+ return paths
+}
+
+const bezier = (entity) => {
+ let bbox = new Box2()
+ entity.controlPoints.forEach(p => {
+ bbox = bbox.expandByPoint(p)
+ })
+ const k = entity.degree + 1
+ const piecewise = toPiecewiseBezier(k, entity.controlPoints, entity.knots)
+ const paths = piecewiseToPaths(k, piecewise.controlPoints)
+ let element = `${paths.join('')}`
+ return transformBoundingBoxAndElement(bbox, element, entity.transforms)
+}
+
/**
* Switcth the appropriate function on entity type. CIRCLE, ARC and ELLIPSE
* produce native SVG elements, the rest produce interpolated polylines.
@@ -150,7 +177,17 @@ const entityToBoundsAndElement = (entity) => {
return arc(entity)
case 'LINE':
case 'LWPOLYLINE':
- case 'SPLINE':
+ case 'SPLINE': {
+ if ((entity.degree === 2) || (entity.degree === 3)) {
+ try {
+ return bezier(entity)
+ } catch (err) {
+ return polyline(entity)
+ }
+ } else {
+ return polyline(entity)
+ }
+ }
case 'POLYLINE': {
return polyline(entity)
}
diff --git a/src/util/insertKnot.js b/src/util/insertKnot.js
new file mode 100644
index 0000000..c22dd57
--- /dev/null
+++ b/src/util/insertKnot.js
@@ -0,0 +1,62 @@
+/**
+ * Knot insertion is known as "Boehm's algorithm"
+ *
+ * https://math.stackexchange.com/questions/417859/convert-a-b-spline-into-bezier-curves
+ * code adapted from http://preserve.mactech.com/articles/develop/issue_25/schneider.html
+ */
+export default (k, controlPoints, knots, newKnot) => {
+ const x = knots
+ const b = controlPoints
+ const n = controlPoints.length
+ let i = 0
+ let foundIndex = false
+ for (let j = 0; j < n + k; j++) {
+ if (newKnot > x[j] && newKnot <= x[j + 1]) {
+ i = j
+ foundIndex = true
+ break
+ }
+ }
+ if (!foundIndex) {
+ throw new Error('invalid new knot')
+ }
+
+ const xHat = []
+ for (let j = 0; j < n + k + 1; j++) {
+ if (j <= i) {
+ xHat[j] = x[j]
+ } else if (j === i + 1) {
+ xHat[j] = newKnot
+ } else {
+ xHat[j] = x[j - 1]
+ }
+ }
+
+ let alpha
+ const bHat = []
+ for (let j = 0; j < n + 1; j++) {
+ if (j <= i - k + 1) {
+ alpha = 1
+ } else if (i - k + 2 <= j && j <= i) {
+ if (x[j + k - 1] - x[j] === 0) {
+ alpha = 0
+ } else {
+ alpha = (newKnot - x[j]) / (x[j + k - 1] - x[j])
+ }
+ } else {
+ alpha = 0
+ }
+
+ if (alpha === 0) {
+ bHat[j] = b[j - 1]
+ } else if (alpha === 1) {
+ bHat[j] = b[j]
+ } else {
+ bHat[j] = {
+ x: (1 - alpha) * b[j - 1].x + alpha * b[j].x,
+ y: (1 - alpha) * b[j - 1].y + alpha * b[j].y
+ }
+ }
+ }
+ return { controlPoints: bHat, knots: xHat }
+}
diff --git a/src/util/toPiecewiseBezier.js b/src/util/toPiecewiseBezier.js
new file mode 100644
index 0000000..9647fd2
--- /dev/null
+++ b/src/util/toPiecewiseBezier.js
@@ -0,0 +1,62 @@
+import insertKnot from './insertKnot'
+
+/**
+ * For a pinned spline, the knots have to be repeated k times
+ * (where k is the order), at both the beginning and the end
+ */
+export const checkPinned = (k, knots) => {
+ // Pinned at the start
+ for (let i = 1; i < k; ++i) {
+ if (knots[i] !== knots[0]) {
+ throw Error(`not pinned. order: ${k} knots: ${knots}`)
+ }
+ }
+ // Pinned at the end
+ for (let i = knots.length - 2; i > knots.length - k - 1; --i) {
+ if (knots[i] !== knots[knots.length - 1]) {
+ throw Error(`not pinned. order: ${k} knots: ${knots}`)
+ }
+ }
+}
+
+const multiplicity = (knots, index) => {
+ let m = 1
+ for (let i = index + 1; i < knots.length; ++i) {
+ if (knots[i] === knots[index]) {
+ ++m
+ } else {
+ break
+ }
+ }
+ return m
+}
+
+/**
+ * https://saccade.com/writing/graphics/KnotVectors.pdf
+ * A quadratic piecewise Bézier knot vector with seven control points
+ * will look like this [0 0 0 1 1 2 2 3 3 3]. In general, in a
+ * piecewise Bézier knot vector the first k knots are the same,
+ * then each subsequent group of k-1 knots is the same,
+ * until you get to the end.
+ */
+export const computeInsertions = (k, knots) => {
+ const inserts = []
+ let i = k
+ while (i < knots.length - k) {
+ const knot = knots[i]
+ const m = multiplicity(knots, i)
+ for (let j = 0; j < k - m - 1; ++j) {
+ inserts.push(knot)
+ }
+ i = i + m
+ }
+ return inserts
+}
+
+export default (k, controlPoints, knots) => {
+ checkPinned(k, knots)
+ const insertions = computeInsertions(k, knots)
+ return insertions.reduce((acc, tNew) => {
+ return insertKnot(k, acc.controlPoints, acc.knots, tNew)
+ }, { controlPoints, knots })
+}
diff --git a/test/functional/toBezier.html b/test/functional/toBezier.html
new file mode 100644
index 0000000..e6c6412
--- /dev/null
+++ b/test/functional/toBezier.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/functional/toBezier.test.js b/test/functional/toBezier.test.js
new file mode 100644
index 0000000..00b171b
--- /dev/null
+++ b/test/functional/toBezier.test.js
@@ -0,0 +1,79 @@
+import React from 'react'
+import { render } from 'react-dom'
+import { HashRouter } from 'react-router-dom'
+
+import { interpolateBSpline } from '../../src/entityToPolyline'
+import toPiecewiseBezier from '../../src/util/toPiecewiseBezier'
+import { piecewiseToPaths } from '../../src/toSVG'
+
+const controlPoints = [
+ { x: 0, y: 0 },
+ { x: 10, y: 0 },
+ { x: 10, y: 10 },
+ { x: 0, y: 10 },
+ { x: 0, y: 20 },
+ { x: 10, y: 20 }
+]
+const k = 4
+const knots = [0, 0, 0, 0, 1, 2, 3, 3, 3, 3]
+const viewBox = '-1 -21 12 22'
+
+// const controlPoints = [
+// { x: 0, y: 0 },
+// { x: 122.4178296875701, y: -38.53600688262475 },
+// { x: 77.52934654015353, y: 149.4771453152231 },
+// { x: 200, y: 100 }
+// ]
+// const k = 3
+// const knots = [0, 0, 0, 0.5, 1, 1, 1]
+// const viewBox = '-1 -160 200 200'
+
+const interpolated0 = interpolateBSpline(controlPoints, k - 1, knots)
+
+const polylineToPath = (polyline) => {
+ const d = polyline.reduce(function (acc, point, i) {
+ acc += (i === 0) ? 'M' : 'L'
+ acc += point[0] + ',' + point[1]
+ return acc
+ }, '')
+ return
+}
+
+const result = toPiecewiseBezier(k, controlPoints, knots)
+const interpolated1 = interpolateBSpline(result.controlPoints, k - 1, result.knots)
+const paths = piecewiseToPaths(k, result.controlPoints, k.knots)
+
+render(
+
+
+
+
+
+, document.getElementById('contents'))
diff --git a/test/functional/toPolylines.test.js b/test/functional/toPolylines.test.js
index be09e2a..e2aa270 100644
--- a/test/functional/toPolylines.test.js
+++ b/test/functional/toPolylines.test.js
@@ -74,7 +74,8 @@ const names = [
'issue28',
'issue29',
'issue39',
- 'issue42'
+ 'issue42',
+ 'splineA'
]
const dxfs = names.map(name => require(`../resources/${name}.dxf`))
const svgs = dxfs.map(contents => toSVG(new Helper(contents).toPolylines()))
diff --git a/test/functional/toSVG.test.js b/test/functional/toSVG.test.js
index fdf2151..24161fd 100644
--- a/test/functional/toSVG.test.js
+++ b/test/functional/toSVG.test.js
@@ -29,7 +29,8 @@ const names = [
'issue28',
'issue29',
'issue39',
- 'issue42'
+ 'issue42',
+ 'splineA'
]
const dxfs = names.map(name => require(`../resources/${name}.dxf`))
const svgs = dxfs.map(contents => new Helper(contents).toSVG())
diff --git a/test/functional/webpack.config.js b/test/functional/webpack.config.js
index 36f69c7..5da6d98 100644
--- a/test/functional/webpack.config.js
+++ b/test/functional/webpack.config.js
@@ -17,6 +17,11 @@ module.exports = {
`webpack-dev-server/client?http://localhost:${port}`,
'webpack/hot/dev-server',
path.resolve(__dirname, 'toSVG.test.js')
+ ],
+ 'toBezier.test': [
+ `webpack-dev-server/client?http://localhost:${port}`,
+ 'webpack/hot/dev-server',
+ path.resolve(__dirname, 'toBezier.test.js')
]
},
output: {
diff --git a/test/resources/splineA.dxf b/test/resources/splineA.dxf
new file mode 100644
index 0000000..b86254a
--- /dev/null
+++ b/test/resources/splineA.dxf
@@ -0,0 +1,2758 @@
+999
+dxfrw 0.6.3
+ 0
+SECTION
+ 2
+HEADER
+ 9
+$ACADVER
+ 1
+AC1021
+ 9
+$DWGCODEPAGE
+ 3
+ANSI_1252
+ 9
+$INSBASE
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$EXTMIN
+ 10
+-40.01848471883363
+ 20
+-12.5
+ 30
+0
+ 9
+$EXTMAX
+ 10
+312.5
+ 20
+211.7119175361894
+ 30
+0
+ 9
+$LIMMIN
+ 10
+0
+ 20
+0
+ 9
+$LIMMAX
+ 10
+420
+ 20
+297
+ 9
+$ORTHOMODE
+ 70
+ 0
+ 9
+$REGENMODE
+ 70
+ 1
+ 9
+$FILLMODE
+ 70
+ 1
+ 9
+$QTEXTMODE
+ 70
+ 0
+ 9
+$MIRRTEXT
+ 70
+ 0
+ 9
+$LTSCALE
+ 40
+1
+ 9
+$ATTMODE
+ 70
+ 0
+ 9
+$TEXTSIZE
+ 40
+2.5
+ 9
+$TRACEWID
+ 40
+15.68
+ 9
+$TEXTSTYLE
+ 7
+STANDARD
+ 9
+$CLAYER
+ 8
+0
+ 9
+$CELTYPE
+ 6
+BYLAYER
+ 9
+$CECOLOR
+ 62
+ 256
+ 9
+$CELTSCALE
+ 40
+1
+ 9
+$DISPSILH
+ 70
+ 0
+ 9
+$DIMSCALE
+ 40
+2.5
+ 9
+$DIMASZ
+ 40
+2.5
+ 9
+$DIMEXO
+ 40
+0.625
+ 9
+$DIMDLI
+ 40
+3.75
+ 9
+$DIMRND
+ 40
+0
+ 9
+$DIMDLE
+ 40
+0
+ 9
+$DIMEXE
+ 40
+1.25
+ 9
+$DIMTP
+ 40
+0
+ 9
+$DIMTM
+ 40
+0
+ 9
+$DIMTXT
+ 40
+2.5
+ 9
+$DIMCEN
+ 40
+2.5
+ 9
+$DIMTSZ
+ 40
+0
+ 9
+$DIMTOL
+ 70
+ 0
+ 9
+$DIMLIM
+ 70
+ 0
+ 9
+$DIMTIH
+ 70
+ 0
+ 9
+$DIMTOH
+ 70
+ 0
+ 9
+$DIMSE1
+ 70
+ 0
+ 9
+$DIMSE2
+ 70
+ 0
+ 9
+$DIMTAD
+ 70
+ 1
+ 9
+$DIMZIN
+ 70
+ 8
+ 9
+$DIMBLK
+ 1
+
+ 9
+$DIMASO
+ 70
+ 1
+ 9
+$DIMSHO
+ 70
+ 1
+ 9
+$DIMPOST
+ 1
+
+ 9
+$DIMAPOST
+ 1
+
+ 9
+$DIMALT
+ 70
+ 0
+ 9
+$DIMALTD
+ 70
+ 3
+ 9
+$DIMALTF
+ 40
+0.03937
+ 9
+$DIMLFAC
+ 40
+1
+ 9
+$DIMTOFL
+ 70
+ 1
+ 9
+$DIMTVP
+ 40
+0
+ 9
+$DIMTIX
+ 70
+ 0
+ 9
+$DIMSOXD
+ 70
+ 0
+ 9
+$DIMSAH
+ 70
+ 0
+ 9
+$DIMBLK1
+ 1
+
+ 9
+$DIMBLK2
+ 1
+
+ 9
+$DIMSTYLE
+ 2
+STANDARD
+ 9
+$DIMCLRD
+ 70
+ 0
+ 9
+$DIMCLRE
+ 70
+ 0
+ 9
+$DIMCLRT
+ 70
+ 0
+ 9
+$DIMTFAC
+ 40
+1
+ 9
+$DIMGAP
+ 40
+0.625
+ 9
+$DIMJUST
+ 70
+ 0
+ 9
+$DIMSD1
+ 70
+ 0
+ 9
+$DIMSD2
+ 70
+ 0
+ 9
+$DIMTOLJ
+ 70
+ 0
+ 9
+$DIMTZIN
+ 70
+ 8
+ 9
+$DIMALTZ
+ 70
+ 0
+ 9
+$DIMALTTZ
+ 70
+ 0
+ 9
+$DIMUPT
+ 70
+ 0
+ 9
+$DIMDEC
+ 70
+ 2
+ 9
+$DIMTDEC
+ 70
+ 2
+ 9
+$DIMALTU
+ 70
+ 2
+ 9
+$DIMALTTD
+ 70
+ 3
+ 9
+$DIMTXSTY
+ 7
+STANDARD
+ 9
+$DIMAUNIT
+ 70
+ 0
+ 9
+$DIMADEC
+ 70
+ 0
+ 9
+$DIMALTRND
+ 40
+0
+ 9
+$DIMAZIN
+ 70
+ 0
+ 9
+$DIMDSEP
+ 70
+ 44
+ 9
+$DIMATFIT
+ 70
+ 3
+ 9
+$DIMFRAC
+ 70
+ 0
+ 9
+$DIMLDRBLK
+ 1
+STANDARD
+ 9
+$DIMLUNIT
+ 70
+ 2
+ 9
+$DIMLWD
+ 70
+ -2
+ 9
+$DIMLWE
+ 70
+ -2
+ 9
+$DIMTMOVE
+ 70
+ 0
+ 9
+$DIMFXL
+ 40
+1
+ 9
+$DIMFXLON
+ 70
+ 0
+ 9
+$DIMJOGANG
+ 40
+0.7854
+ 9
+$DIMTFILL
+ 70
+ 0
+ 9
+$DIMTFILLCLR
+ 70
+ 0
+ 9
+$DIMARCSYM
+ 70
+ 0
+ 9
+$DIMLTYPE
+ 6
+
+ 9
+$DIMLTEX1
+ 6
+
+ 9
+$DIMLTEX2
+ 6
+
+ 9
+$LUNITS
+ 70
+ 2
+ 9
+$LUPREC
+ 70
+ 4
+ 9
+$SKETCHINC
+ 40
+1
+ 9
+$FILLETRAD
+ 40
+0
+ 9
+$AUNITS
+ 70
+ 0
+ 9
+$AUPREC
+ 70
+ 2
+ 9
+$MENU
+ 1
+.
+ 9
+$ELEVATION
+ 40
+0
+ 9
+$PELEVATION
+ 40
+0
+ 9
+$THICKNESS
+ 40
+0
+ 9
+$LIMCHECK
+ 70
+ 0
+ 9
+$CHAMFERA
+ 40
+0
+ 9
+$CHAMFERB
+ 40
+0
+ 9
+$CHAMFERC
+ 40
+0
+ 9
+$CHAMFERD
+ 40
+0
+ 9
+$SKPOLY
+ 70
+ 0
+ 9
+$USRTIMER
+ 70
+ 1
+ 9
+$ANGBASE
+ 50
+0
+ 9
+$ANGDIR
+ 70
+ 0
+ 9
+$PDMODE
+ 70
+ 34
+ 9
+$PDSIZE
+ 40
+0
+ 9
+$PLINEWID
+ 40
+0
+ 9
+$SPLFRAME
+ 70
+ 0
+ 9
+$SPLINETYPE
+ 70
+ 2
+ 9
+$SPLINESEGS
+ 70
+ 8
+ 9
+$HANDSEED
+ 5
+20000
+ 9
+$SURFTAB1
+ 70
+ 6
+ 9
+$SURFTAB2
+ 70
+ 6
+ 9
+$SURFTYPE
+ 70
+ 6
+ 9
+$SURFU
+ 70
+ 6
+ 9
+$SURFV
+ 70
+ 6
+ 9
+$UCSBASE
+ 2
+
+ 9
+$UCSNAME
+ 2
+
+ 9
+$UCSORG
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$UCSXDIR
+ 10
+1
+ 20
+0
+ 30
+0
+ 9
+$UCSYDIR
+ 10
+0
+ 20
+1
+ 30
+0
+ 9
+$UCSORTHOREF
+ 2
+
+ 9
+$UCSORTHOVIEW
+ 70
+ 0
+ 9
+$UCSORGTOP
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$UCSORGBOTTOM
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$UCSORGLEFT
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$UCSORGRIGHT
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$UCSORGFRONT
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$UCSORGBACK
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PUCSBASE
+ 2
+
+ 9
+$PUCSNAME
+ 2
+
+ 9
+$PUCSORG
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PUCSXDIR
+ 10
+1
+ 20
+0
+ 30
+0
+ 9
+$PUCSYDIR
+ 10
+0
+ 20
+1
+ 30
+0
+ 9
+$PUCSORTHOREF
+ 2
+
+ 9
+$PUCSORTHOVIEW
+ 70
+ 0
+ 9
+$PUCSORGTOP
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PUCSORGBOTTOM
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PUCSORGLEFT
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PUCSORGRIGHT
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PUCSORGFRONT
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PUCSORGBACK
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$USERI1
+ 70
+ 0
+ 9
+$USERI2
+ 70
+ 0
+ 9
+$USERI3
+ 70
+ 0
+ 9
+$USERI4
+ 70
+ 0
+ 9
+$USERI5
+ 70
+ 0
+ 9
+$USERR1
+ 40
+0
+ 9
+$USERR2
+ 40
+0
+ 9
+$USERR3
+ 40
+0
+ 9
+$USERR4
+ 40
+0
+ 9
+$USERR5
+ 40
+0
+ 9
+$WORLDVIEW
+ 70
+ 1
+ 9
+$SHADEDGE
+ 70
+ 3
+ 9
+$SHADEDIF
+ 70
+ 70
+ 9
+$TILEMODE
+ 70
+ 1
+ 9
+$MAXACTVP
+ 70
+ 64
+ 9
+$PINSBASE
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PLIMCHECK
+ 70
+ 0
+ 9
+$PEXTMIN
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$PEXTMAX
+ 10
+0
+ 20
+0
+ 30
+0
+ 9
+$GRIDMODE
+ 70
+ 1
+ 9
+$SNAPSTYLE
+ 70
+ 0
+ 9
+$PLIMMIN
+ 10
+0
+ 20
+0
+ 9
+$PLIMMAX
+ 10
+210
+ 20
+297
+ 9
+$UNITMODE
+ 70
+ 0
+ 9
+$VISRETAIN
+ 70
+ 1
+ 9
+$PLINEGEN
+ 70
+ 0
+ 9
+$PSLTSCALE
+ 70
+ 1
+ 9
+$TREEDEPTH
+ 70
+ 3020
+ 9
+$CMLSTYLE
+ 2
+Standard
+ 9
+$CMLJUST
+ 70
+ 0
+ 9
+$CMLSCALE
+ 40
+20
+ 9
+$PROXYGRAPHICS
+ 70
+ 1
+ 9
+$MEASUREMENT
+ 70
+ 1
+ 9
+$CELWEIGHT
+370
+ -1
+ 9
+$ENDCAPS
+280
+ 0
+ 9
+$JOINSTYLE
+280
+ 0
+ 9
+$LWDISPLAY
+290
+ 0
+ 9
+$INSUNITS
+ 70
+ 4
+ 9
+$HYPERLINKBASE
+ 1
+
+ 9
+$STYLESHEET
+ 1
+
+ 9
+$XEDIT
+290
+ 1
+ 9
+$CEPSNTYPE
+380
+ 0
+ 9
+$PSTYLEMODE
+290
+ 1
+ 9
+$EXTNAMES
+290
+ 1
+ 9
+$PSVPSCALE
+ 40
+1
+ 9
+$OLESTARTUP
+290
+ 0
+ 9
+$SORTENTS
+280
+ 127
+ 9
+$INDEXCTL
+280
+ 0
+ 9
+$HIDETEXT
+280
+ 1
+ 9
+$XCLIPFRAME
+290
+ 0
+ 9
+$HALOGAP
+280
+ 0
+ 9
+$OBSCOLOR
+ 70
+ 257
+ 9
+$OBSLTYPE
+280
+ 0
+ 9
+$INTERSECTIONDISPLAY
+280
+ 0
+ 9
+$INTERSECTIONCOLOR
+ 70
+ 257
+ 9
+$DIMASSOC
+280
+ 1
+ 9
+$PROJECTNAME
+ 1
+
+ 9
+$CAMERADISPLAY
+290
+ 0
+ 9
+$LENSLENGTH
+ 40
+50
+ 9
+$CAMERAHEIGHT
+ 40
+0
+ 9
+$STEPSPERSEC
+ 40
+2
+ 9
+$STEPSIZE
+ 40
+50
+ 9
+$3DDWFPREC
+ 40
+2
+ 9
+$PSOLWIDTH
+ 40
+5
+ 9
+$PSOLHEIGHT
+ 40
+80
+ 9
+$LOFTANG1
+ 40
+1.570796326794897
+ 9
+$LOFTANG2
+ 40
+1.570796326794897
+ 9
+$LOFTMAG1
+ 40
+0
+ 9
+$LOFTMAG2
+ 40
+0
+ 9
+$LOFTPARAM
+ 70
+ 7
+ 9
+$LOFTNORMALS
+280
+ 1
+ 9
+$LATITUDE
+ 40
+1
+ 9
+$LONGITUDE
+ 40
+1
+ 9
+$NORTHDIRECTION
+ 40
+0
+ 9
+$TIMEZONE
+ 70
+-8000
+ 9
+$LIGHTGLYPHDISPLAY
+280
+ 1
+ 9
+$TILEMODELIGHTSYNCH
+280
+ 1
+ 9
+$SOLIDHIST
+280
+ 1
+ 9
+$SHOWHIST
+280
+ 1
+ 9
+$DWFFRAME
+280
+ 2
+ 9
+$DGNFRAME
+280
+ 0
+ 9
+$REALWORLDSCALE
+290
+ 1
+ 9
+$INTERFERECOLOR
+ 62
+ 1
+ 9
+$CSHADOW
+280
+ 0
+ 9
+$SHADOWPLANELOCATION
+ 40
+0
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+CLASSES
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+TABLES
+ 0
+TABLE
+ 2
+VPORT
+ 5
+8
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 1
+ 0
+VPORT
+ 5
+31
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbViewportTableRecord
+ 2
+*ACTIVE
+ 70
+ 0
+ 10
+0
+ 20
+0
+ 11
+1
+ 21
+1
+ 12
+212.457870755768
+ 22
+101.319633546261
+ 13
+0
+ 23
+0
+ 14
+10
+ 24
+10
+ 15
+10
+ 25
+10
+ 16
+0
+ 26
+0
+ 36
+1
+ 17
+0
+ 27
+0
+ 37
+0
+ 40
+373.524752125185
+ 41
+1.756152125279642
+ 42
+50
+ 43
+0
+ 44
+0
+ 50
+0
+ 51
+0
+ 71
+ 0
+ 72
+ 100
+ 73
+ 1
+ 74
+ 3
+ 75
+ 0
+ 76
+ 1
+ 77
+ 0
+ 78
+ 0
+281
+ 0
+ 65
+ 1
+110
+0
+120
+0
+130
+0
+111
+1
+121
+0
+131
+0
+112
+0
+122
+1
+132
+0
+ 79
+ 0
+146
+0
+348
+10020
+ 60
+ 7
+ 61
+ 5
+292
+1
+282
+ 1
+141
+0
+142
+0
+ 63
+ 250
+421
+3358443
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+LTYPE
+ 5
+5
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 4
+ 0
+LTYPE
+ 5
+14
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+ByBlock
+ 70
+ 0
+ 3
+
+ 72
+ 65
+ 73
+ 0
+ 40
+0
+ 0
+LTYPE
+ 5
+15
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+ByLayer
+ 70
+ 0
+ 3
+
+ 72
+ 65
+ 73
+ 0
+ 40
+0
+ 0
+LTYPE
+ 5
+16
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+Continuous
+ 70
+ 0
+ 3
+Solid line
+ 72
+ 65
+ 73
+ 0
+ 40
+0
+ 0
+LTYPE
+ 5
+32
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DOT
+ 70
+ 0
+ 3
+Dot . . . . . . . . . . . . . . . . . . . . . .
+ 72
+ 65
+ 73
+ 2
+ 40
+6.35
+ 49
+0
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 0
+LTYPE
+ 5
+33
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DOTTINY
+ 70
+ 0
+ 3
+Dot (.15x) .....................................
+ 72
+ 65
+ 73
+ 2
+ 40
+0.9525
+ 49
+0
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 0
+LTYPE
+ 5
+34
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DOT2
+ 70
+ 0
+ 3
+Dot (.5x) .....................................
+ 72
+ 65
+ 73
+ 2
+ 40
+3.175
+ 49
+0
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 0
+LTYPE
+ 5
+35
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DOTX2
+ 70
+ 0
+ 3
+Dot (2x) . . . . . . . . . . . . .
+ 72
+ 65
+ 73
+ 2
+ 40
+12.7
+ 49
+0
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 0
+LTYPE
+ 5
+36
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHED
+ 70
+ 0
+ 3
+Dashed _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ 72
+ 65
+ 73
+ 2
+ 40
+19.05
+ 49
+12.7
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 0
+LTYPE
+ 5
+37
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHEDTINY
+ 70
+ 0
+ 3
+Dashed (.15x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ 72
+ 65
+ 73
+ 2
+ 40
+2.8575
+ 49
+1.905
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 0
+LTYPE
+ 5
+38
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHED2
+ 70
+ 0
+ 3
+Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ 72
+ 65
+ 73
+ 2
+ 40
+9.524999999999999
+ 49
+6.35
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 0
+LTYPE
+ 5
+39
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHEDX2
+ 70
+ 0
+ 3
+Dashed (2x) ____ ____ ____ ____ ____ ___
+ 72
+ 65
+ 73
+ 2
+ 40
+38.09999999999999
+ 49
+25.4
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 0
+LTYPE
+ 5
+3A
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHDOT
+ 70
+ 0
+ 3
+Dash dot __ . __ . __ . __ . __ . __ . __ . __
+ 72
+ 65
+ 73
+ 4
+ 40
+25.4
+ 49
+12.7
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 0
+LTYPE
+ 5
+3B
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHDOTTINY
+ 70
+ 0
+ 3
+Dash dot (.15x) _._._._._._._._._._._._._._._.
+ 72
+ 65
+ 73
+ 4
+ 40
+3.81
+ 49
+1.905
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 0
+LTYPE
+ 5
+3C
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHDOT2
+ 70
+ 0
+ 3
+Dash dot (.5x) _._._._._._._._._._._._._._._.
+ 72
+ 65
+ 73
+ 4
+ 40
+12.7
+ 49
+6.35
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 0
+LTYPE
+ 5
+3D
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DASHDOTX2
+ 70
+ 0
+ 3
+Dash dot (2x) ____ . ____ . ____ . ___
+ 72
+ 65
+ 73
+ 4
+ 40
+50.8
+ 49
+25.4
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 0
+LTYPE
+ 5
+3E
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DIVIDE
+ 70
+ 0
+ 3
+Divide ____ . . ____ . . ____ . . ____ . . ____
+ 72
+ 65
+ 73
+ 6
+ 40
+31.75
+ 49
+12.7
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 0
+LTYPE
+ 5
+3F
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DIVIDETINY
+ 70
+ 0
+ 3
+Divide (.15x) __..__..__..__..__..__..__..__.._
+ 72
+ 65
+ 73
+ 6
+ 40
+4.7625
+ 49
+1.905
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 0
+LTYPE
+ 5
+40
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DIVIDE2
+ 70
+ 0
+ 3
+Divide (.5x) __..__..__..__..__..__..__..__.._
+ 72
+ 65
+ 73
+ 6
+ 40
+15.875
+ 49
+6.35
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 0
+LTYPE
+ 5
+41
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+DIVIDEX2
+ 70
+ 0
+ 3
+Divide (2x) ________ . . ________ . . _
+ 72
+ 65
+ 73
+ 6
+ 40
+63.5
+ 49
+25.4
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 0
+LTYPE
+ 5
+42
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+BORDER
+ 70
+ 0
+ 3
+Border __ __ . __ __ . __ __ . __ __ . __ __ .
+ 72
+ 65
+ 73
+ 6
+ 40
+44.45
+ 49
+12.7
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 49
+12.7
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 0
+LTYPE
+ 5
+43
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+BORDERTINY
+ 70
+ 0
+ 3
+Border (.15x) __.__.__.__.__.__.__.__.__.__.__.
+ 72
+ 65
+ 73
+ 6
+ 40
+6.6675
+ 49
+1.905
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 49
+1.905
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 0
+LTYPE
+ 5
+44
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+BORDER2
+ 70
+ 0
+ 3
+Border (.5x) __.__.__.__.__.__.__.__.__.__.__.
+ 72
+ 65
+ 73
+ 6
+ 40
+22.225
+ 49
+6.35
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 49
+6.35
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 0
+LTYPE
+ 5
+45
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+BORDERX2
+ 70
+ 0
+ 3
+Border (2x) ____ ____ . ____ ____ . ___
+ 72
+ 65
+ 73
+ 6
+ 40
+88.89999999999999
+ 49
+25.4
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 49
+25.4
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 49
+0
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 0
+LTYPE
+ 5
+46
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+CENTER
+ 70
+ 0
+ 3
+Center ____ _ ____ _ ____ _ ____ _ ____ _ ____
+ 72
+ 65
+ 73
+ 4
+ 40
+50.8
+ 49
+31.75
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 49
+6.35
+ 74
+ 0
+ 49
+-6.35
+ 74
+ 0
+ 0
+LTYPE
+ 5
+47
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+CENTERTINY
+ 70
+ 0
+ 3
+Center (.15x) ___ _ ___ _ ___ _ ___ _ ___ _ ___
+ 72
+ 65
+ 73
+ 4
+ 40
+7.619999999999999
+ 49
+4.7625
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 49
+0.9525
+ 74
+ 0
+ 49
+-0.9525
+ 74
+ 0
+ 0
+LTYPE
+ 5
+48
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+CENTER2
+ 70
+ 0
+ 3
+Center (.5x) ___ _ ___ _ ___ _ ___ _ ___ _ ___
+ 72
+ 65
+ 73
+ 4
+ 40
+28.575
+ 49
+19.05
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 49
+3.175
+ 74
+ 0
+ 49
+-3.175
+ 74
+ 0
+ 0
+LTYPE
+ 5
+49
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+ 2
+CENTERX2
+ 70
+ 0
+ 3
+Center (2x) ________ __ ________ __ _____
+ 72
+ 65
+ 73
+ 4
+ 40
+101.6
+ 49
+63.5
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 49
+12.7
+ 74
+ 0
+ 49
+-12.7
+ 74
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+LAYER
+ 5
+2
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 1
+ 0
+LAYER
+ 5
+10
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+ 2
+0
+ 70
+ 0
+ 62
+ 7
+ 6
+CONTINUOUS
+370
+ 0
+390
+F
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+STYLE
+ 5
+3
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 3
+ 0
+STYLE
+ 5
+4A
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+ 2
+Standard
+ 70
+ 0
+ 40
+0
+ 41
+1
+ 50
+0
+ 71
+ 0
+ 42
+1
+ 3
+txt
+ 4
+
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+VIEW
+ 5
+6
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+UCS
+ 5
+7
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+APPID
+ 5
+9
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 1
+ 0
+APPID
+ 5
+12
+330
+9
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+ 2
+ACAD
+ 70
+ 0
+ 0
+APPID
+ 5
+4B
+330
+9
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+ 2
+LibreCad
+ 70
+ 0
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+DIMSTYLE
+ 5
+A
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 1
+100
+AcDbDimStyleTable
+ 71
+ 1
+ 0
+DIMSTYLE
+105
+4C
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+ 2
+Standard
+ 70
+ 0
+ 40
+1
+ 41
+2.5
+ 42
+0.625
+ 43
+0.38
+ 44
+1.25
+ 45
+0
+ 46
+0
+ 47
+0
+ 48
+0
+ 49
+1
+140
+2.5
+141
+0.09
+142
+2.5
+143
+25.4
+144
+1
+145
+0
+146
+1
+147
+0.625
+148
+0
+ 71
+ 0
+ 72
+ 0
+ 73
+ 0
+ 74
+ 1
+ 75
+ 0
+ 76
+ 0
+ 77
+ 0
+ 78
+ 1
+ 79
+ 0
+170
+ 0
+171
+ 2
+172
+ 0
+173
+ 0
+174
+ 0
+175
+ 0
+176
+ 0
+177
+ 0
+178
+ 0
+179
+ 0
+271
+ 2
+272
+ 4
+273
+ 2
+274
+ 2
+275
+ 0
+276
+ 0
+277
+ 2
+278
+ 0
+279
+ 0
+280
+ 0
+281
+ 0
+282
+ 0
+283
+ 1
+284
+ 0
+285
+ 0
+286
+ 0
+288
+ 0
+289
+ 3
+340
+standard
+341
+
+371
+ -2
+372
+ -2
+ 0
+ENDTAB
+ 0
+TABLE
+ 2
+BLOCK_RECORD
+ 5
+1
+330
+0
+100
+AcDbSymbolTable
+ 70
+ 2
+ 0
+BLOCK_RECORD
+ 5
+1F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Model_Space
+ 70
+ 0
+280
+ 1
+281
+ 0
+ 0
+BLOCK_RECORD
+ 5
+1E
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+ 2
+*Paper_Space
+ 70
+ 0
+280
+ 1
+281
+ 0
+ 0
+ENDTAB
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+BLOCKS
+ 0
+BLOCK
+ 5
+20
+330
+1F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Model_Space
+ 70
+ 0
+ 10
+0
+ 20
+0
+ 30
+0
+ 3
+*Model_Space
+ 1
+
+ 0
+ENDBLK
+ 5
+21
+330
+1F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+BLOCK
+ 5
+1C
+330
+1B
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockBegin
+ 2
+*Paper_Space
+ 70
+ 0
+ 10
+0
+ 20
+0
+ 30
+0
+ 3
+*Paper_Space
+ 1
+
+ 0
+ENDBLK
+ 5
+1D
+330
+1F
+100
+AcDbEntity
+ 8
+0
+100
+AcDbBlockEnd
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+ENTITIES
+ 0
+SPLINE
+ 5
+4D
+100
+AcDbEntity
+ 8
+0
+ 6
+ByLayer
+ 62
+ 256
+370
+ -1
+100
+AcDbSpline
+210
+0
+220
+0
+230
+0
+ 70
+ 8
+ 71
+ 2
+ 72
+ 7
+ 73
+ 4
+ 74
+ 0
+ 42
+1e-07
+ 43
+1e-07
+ 40
+0
+ 40
+0
+ 40
+0
+ 40
+0.5
+ 40
+1
+ 40
+1
+ 40
+1
+ 10
+0
+ 20
+0
+ 30
+0
+ 10
+122.4178296875701
+ 20
+-38.53600688262475
+ 30
+0
+ 10
+77.52934654015353
+ 20
+149.4771453152231
+ 30
+0
+ 10
+200
+ 20
+100
+ 30
+0
+ 0
+ENDSEC
+ 0
+SECTION
+ 2
+OBJECTS
+ 0
+DICTIONARY
+ 5
+C
+330
+0
+100
+AcDbDictionary
+281
+ 1
+ 3
+ACAD_GROUP
+350
+D
+ 0
+DICTIONARY
+ 5
+D
+330
+C
+100
+AcDbDictionary
+281
+ 1
+ 0
+ENDSEC
+ 0
+EOF
diff --git a/test/unit/insertKnot.test.js b/test/unit/insertKnot.test.js
new file mode 100644
index 0000000..9c5199a
--- /dev/null
+++ b/test/unit/insertKnot.test.js
@@ -0,0 +1,35 @@
+import expect from 'expect'
+
+import insertKnot from '../../src/util/insertKnot'
+
+describe('Insert knot', () => {
+ it('throws error if knot is invalid', () => {
+ const controlPoints = [
+ { x: 0, y: 0 },
+ { x: 10, y: 0 },
+ { x: 10, y: 10 },
+ { x: 0, y: 10 },
+ { x: 0, y: 20 },
+ { x: 10, y: 20 }
+ ]
+ const k = 4
+ const knots = [0, 0, 0, 0, 1, 2, 3, 3, 3, 3]
+ expect(() => {
+ insertKnot(k, controlPoints, knots, 4)
+ }).toThrow(/^invalid new knot$/)
+ expect(() => {
+ insertKnot(k, controlPoints, knots, -1)
+ }).toThrow(/^invalid new knot$/)
+ const newSpline = insertKnot(k, controlPoints, knots, 1)
+ expect(newSpline.controlPoints).toEqual([
+ { x: 0, y: 0 },
+ { x: 10, y: 0 },
+ { x: 10, y: 5 },
+ { x: 6.666666666666668, y: 10 },
+ { x: 0, y: 10 },
+ { x: 0, y: 20 },
+ { x: 10, y: 20 }
+ ])
+ expect(newSpline.knots).toEqual([0, 0, 0, 0, 1, 1, 2, 3, 3, 3, 3])
+ })
+})
diff --git a/test/unit/toPiecewiseBezier.test.js b/test/unit/toPiecewiseBezier.test.js
new file mode 100644
index 0000000..319d171
--- /dev/null
+++ b/test/unit/toPiecewiseBezier.test.js
@@ -0,0 +1,47 @@
+import expect from 'expect'
+
+import { checkPinned, computeInsertions } from '../../src/util/toPiecewiseBezier'
+
+describe('Spline conversion to piecewise bezier', () => {
+ it('checks that the spline is pinned', () => {
+ expect(checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1]))
+ expect(() => {
+ checkPinned(4, [0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1])
+ }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/)
+ expect(() => {
+ checkPinned(4, [0, 1, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1])
+ }).toThrow(/^not pinned. order: 4 knots: 0,1,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/)
+ expect(() => {
+ checkPinned(4, [0, 0, 1, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1])
+ }).toThrow(/^not pinned. order: 4 knots: 0,0,1,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/)
+ expect(() => {
+ checkPinned(4, [0, 0, 0, 1, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1])
+ }).toThrow(/^not pinned. order: 4 knots: 0,0,0,1,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/)
+
+ expect(() => {
+ checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 0.9, 1, 1, 1])
+ }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,0.9,1,1,1$/)
+ expect(() => {
+ checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 0.9, 1, 1])
+ }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,1,0.9,1,1$/)
+ expect(() => {
+ checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 0.9, 1])
+ }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,0.9,1$/)
+ expect(() => {
+ checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 0.9])
+ }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,0.9$/)
+ })
+
+ it('computes the knots to be inserted for a piecewise bezier', () => {
+ expect(computeInsertions(4, [0, 0, 0, 0, 1, 2, 2, 2, 2]))
+ .toEqual([1, 1])
+ expect(computeInsertions(4, [0, 0, 0, 0, 0.5, 2, 2, 2, 2]))
+ .toEqual([0.5, 0.5])
+ expect(computeInsertions(4, [0, 0, 0, 0, 0.5, 0.5, 0.5, 2, 2, 2, 2]))
+ .toEqual([])
+ expect(computeInsertions(3, [0, 0, 0, 1, 1, 2, 2, 2]))
+ .toEqual([])
+ expect(computeInsertions(3, [0, 0, 0, 1, 2, 2, 2]))
+ .toEqual([1])
+ })
+})