refactor: ♻️ refactor vector add add test specs
This commit is contained in:
@ -329,9 +329,9 @@ describe('Dom', () => {
|
||||
set(node, value) {
|
||||
if (typeof value === 'number') {
|
||||
node.setAttribute('foo', value > 0 ? '1' : '-1')
|
||||
return true
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return value
|
||||
},
|
||||
})
|
||||
div.attr('foo', 'bar')
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Attributes } from './attributes'
|
||||
|
||||
export abstract class AttributesBase {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
attr(k?: any, v?: any) {
|
||||
return this
|
||||
return Attributes.prototype.attr.call(this, k, v)
|
||||
}
|
||||
}
|
||||
|
@ -204,9 +204,11 @@ export namespace Core {
|
||||
) {
|
||||
const hook = Hook.get(name)
|
||||
if (hook && hook.set) {
|
||||
if (hook.set(node, value) !== false) {
|
||||
const ret = hook.set(node, value)
|
||||
if (ret === false) {
|
||||
return
|
||||
}
|
||||
value = ret // eslint-disable-line
|
||||
}
|
||||
|
||||
const special = Special.get(name)
|
||||
|
@ -29,9 +29,9 @@ export namespace Hook {
|
||||
Object.keys(attributeValue).forEach((key) =>
|
||||
Style.style(node, key, attributeValue[key]),
|
||||
)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return attributeValue
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -5,29 +5,29 @@ import { Dom } from './dom'
|
||||
import { Fragment } from '../vector/fragment/fragment'
|
||||
|
||||
describe('Dom', () => {
|
||||
describe('first()', () => {
|
||||
describe('firstChild()', () => {
|
||||
it('should return the first child', () => {
|
||||
const g = new G()
|
||||
const rect = g.rect()
|
||||
g.circle(100)
|
||||
expect(g.first()).toBe(rect)
|
||||
expect(g.firstChild()).toBe(rect)
|
||||
})
|
||||
|
||||
it('should return `null` if no first child exists', () => {
|
||||
expect(new G().first()).toBe(null)
|
||||
expect(new G().firstChild()).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('last()', () => {
|
||||
describe('lastChild()', () => {
|
||||
it('should return the last child of the element', () => {
|
||||
const g = new G()
|
||||
g.rect()
|
||||
const rect = g.rect()
|
||||
expect(g.last()).toBe(rect)
|
||||
expect(g.lastChild()).toBe(rect)
|
||||
})
|
||||
|
||||
it('should return `null` if no last child exists', () => {
|
||||
expect(new G().last()).toBe(null)
|
||||
expect(new G().lastChild()).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -14,14 +14,14 @@ export class Dom<TElement extends Element = Element> extends Primer<TElement> {
|
||||
/**
|
||||
* Returns the first child of the element.
|
||||
*/
|
||||
first<T extends Dom = Dom>(): T | null {
|
||||
firstChild<T extends Dom = Dom>(): T | null {
|
||||
return Dom.adopt<T>(this.node.firstChild)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last child of the element.
|
||||
*/
|
||||
last<T extends Dom = Dom>(): T | null {
|
||||
lastChild<T extends Dom = Dom>(): T | null {
|
||||
return Dom.adopt<T>(this.node.lastChild)
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ describe('Dom', () => {
|
||||
|
||||
div.add(span)
|
||||
const div2 = div.clone(true)
|
||||
const span2 = div2.first()!
|
||||
const span2 = div2.firstChild()!
|
||||
|
||||
expect(div2.attr(Affix.PERSIST_ATTR_NAME)).toEqual('{"foo":"bar"}')
|
||||
expect(span2.attr(Affix.PERSIST_ATTR_NAME)).toEqual('{"a":1}')
|
||||
|
286
packages/x6-vector/src/dom/transform/transform.test.ts
Normal file
286
packages/x6-vector/src/dom/transform/transform.test.ts
Normal file
@ -0,0 +1,286 @@
|
||||
import sinon from 'sinon'
|
||||
import { Matrix } from '../../struct/matrix'
|
||||
import { Dom } from '../dom'
|
||||
|
||||
describe('Dom', () => {
|
||||
describe('transform()', () => {
|
||||
it('should act as full getter with no argument', () => {
|
||||
const dom = new Dom().attr('transform', 'translate(10, 20) rotate(45)')
|
||||
const actual = dom.transform()
|
||||
const expected = new Matrix().rotate(45).translate(10, 20).decompose()
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('should return a single transformation value when string was passed', () => {
|
||||
const dom = new Dom().attr('transform', 'translate(10, 20) rotate(45)')
|
||||
expect(dom.transform('rotate')).toBe(45)
|
||||
expect(dom.transform('translateX')).toBe(10)
|
||||
expect(dom.transform('translateY')).toBe(20)
|
||||
})
|
||||
|
||||
it('should set the transformation with an object', () => {
|
||||
const dom = new Dom().transform({ rotate: 45, translate: [10, 20] })
|
||||
expect(dom.transform('rotate')).toBe(45)
|
||||
expect(dom.transform('translateX')).toBe(10)
|
||||
expect(dom.transform('translateY')).toBe(20)
|
||||
})
|
||||
|
||||
it('should perform a relative transformation', () => {
|
||||
const dom = new Dom()
|
||||
.transform({ rotate: 45, translate: [10, 20] })
|
||||
.transform({ rotate: 10 }, true)
|
||||
expect(dom.transform('rotate')).toBeCloseTo(55, 5) // rounding errors
|
||||
expect(dom.transform('translateX')).toBe(10)
|
||||
expect(dom.transform('translateY')).toBe(20)
|
||||
})
|
||||
|
||||
it('should perform a relative transformation with other matrix', () => {
|
||||
const dom = new Dom()
|
||||
.transform({ rotate: 45, translate: [10, 20] })
|
||||
.transform({ rotate: 10 }, new Matrix().rotate(30))
|
||||
expect(dom.transform('rotate')).toBeCloseTo(40, 5) // rounding errors
|
||||
expect(dom.transform('translateX')).toBe(0)
|
||||
expect(dom.transform('translateY')).toBe(0)
|
||||
})
|
||||
|
||||
it('should perform a relative transformation with other element', () => {
|
||||
const ref = new Dom().transform({ rotate: 30 })
|
||||
const dom = new Dom()
|
||||
.transform({ rotate: 45, translate: [10, 20] })
|
||||
.transform({ rotate: 10 }, ref)
|
||||
expect(dom.transform('rotate')).toBeCloseTo(40, 5) // rounding errors
|
||||
expect(dom.transform('translateX')).toBe(0)
|
||||
expect(dom.transform('translateY')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('untransform()', () => {
|
||||
it('should return itself', () => {
|
||||
const dom = new Dom()
|
||||
expect(dom.untransform()).toBe(dom)
|
||||
})
|
||||
|
||||
it('should delete the transform attribute', () => {
|
||||
const dom = new Dom()
|
||||
expect(dom.untransform().attr('transform') as any).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe('matrixify()', () => {
|
||||
it('should get an empty matrix when there is not transformations', () => {
|
||||
expect(new Dom().matrixify()).toEqual(new Matrix())
|
||||
})
|
||||
|
||||
it('should reduce all transformations of the transform list into one matrix [1]', () => {
|
||||
const dom = new Dom().attr('transform', 'matrix(1, 0, 1, 1, 0, 1)')
|
||||
expect(dom.matrixify()).toEqual(new Matrix(1, 0, 1, 1, 0, 1))
|
||||
})
|
||||
|
||||
it('should reduce all transformations of the transform list into one matrix [2]', () => {
|
||||
const dom = new Dom().attr('transform', 'translate(10, 20) rotate(45)')
|
||||
expect(dom.matrixify()).toEqual(new Matrix().rotate(45).translate(10, 20))
|
||||
})
|
||||
|
||||
it('should reduce all transformations of the transform list into one matrix [3]', () => {
|
||||
const dom = new Dom().attr(
|
||||
'transform',
|
||||
'translate(10, 20) rotate(45) skew(1,2) skewX(10) skewY(20)',
|
||||
)
|
||||
expect(dom.matrixify()).toEqual(
|
||||
new Matrix()
|
||||
.skewY(20)
|
||||
.skewX(10)
|
||||
.skew(1, 2)
|
||||
.rotate(45)
|
||||
.translate(10, 20),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('matrix()', () => {
|
||||
it('should get transform as a matrix', () => {
|
||||
expect(new Dom().matrixify()).toEqual(new Matrix())
|
||||
const dom = new Dom().transform({ rotate: 45, translate: [10, 20] })
|
||||
expect(dom.matrix()).toEqual(new Matrix().rotate(45).translate(10, 20))
|
||||
})
|
||||
|
||||
it('should transform element with matrix', () => {
|
||||
expect(
|
||||
new Dom().matrix(new Matrix().translate(10, 20)).attr('transform'),
|
||||
).toEqual('matrix(1,0,0,1,10,20)')
|
||||
|
||||
expect(new Dom().matrix(1, 0, 0, 1, 10, 20).attr('transform')).toEqual(
|
||||
'matrix(1,0,0,1,10,20)',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('rotate()', () => {
|
||||
it('should rotate element', () => {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.rotate(1, 2, 3)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ rotate: 1, ox: 2, oy: 3 }, true])
|
||||
})
|
||||
})
|
||||
|
||||
describe('skew()', () => {
|
||||
it('should skew element with no argument', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.skew()
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([
|
||||
{ skew: undefined, ox: undefined, oy: undefined },
|
||||
true,
|
||||
])
|
||||
})
|
||||
|
||||
it('should skew element with one argument', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.skew(5)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([
|
||||
{ skew: 5, ox: undefined, oy: undefined },
|
||||
true,
|
||||
])
|
||||
})
|
||||
|
||||
it('should skew element with two argument', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.skew(5, 6)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([
|
||||
{ skew: [5, 6], ox: undefined, oy: undefined },
|
||||
true,
|
||||
])
|
||||
})
|
||||
|
||||
it('should skew element with three arguments', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.skew(5, 6, 7)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ skew: 5, ox: 6, oy: 7 }, true])
|
||||
})
|
||||
|
||||
it('should skew element with four arguments', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.skew(5, 6, 7, 8)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ skew: [5, 6], ox: 7, oy: 8 }, true])
|
||||
})
|
||||
})
|
||||
|
||||
describe('shear()', () => {
|
||||
it('should shear element', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.shear(1, 2, 3)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ shear: 1, ox: 2, oy: 3 }, true])
|
||||
})
|
||||
})
|
||||
|
||||
describe('scale()', () => {
|
||||
it('should scale element with no argument', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.scale()
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([
|
||||
{ scale: undefined, ox: undefined, oy: undefined },
|
||||
true,
|
||||
])
|
||||
})
|
||||
|
||||
it('should scale element with one argument', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.scale(5)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([
|
||||
{ scale: 5, ox: undefined, oy: undefined },
|
||||
true,
|
||||
])
|
||||
})
|
||||
|
||||
it('should scale element with two argument', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.scale(5, 6)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([
|
||||
{ scale: [5, 6], ox: undefined, oy: undefined },
|
||||
true,
|
||||
])
|
||||
})
|
||||
|
||||
it('should scale element with three arguments', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.scale(5, 6, 7)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ scale: 5, ox: 6, oy: 7 }, true])
|
||||
})
|
||||
|
||||
it('should scale element with four arguments', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.scale(5, 6, 7, 8)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ scale: [5, 6], ox: 7, oy: 8 }, true])
|
||||
})
|
||||
})
|
||||
|
||||
describe('translate()', () => {
|
||||
it('should translate element', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.translate(1, 2)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ translate: [1, 2] }, true])
|
||||
})
|
||||
})
|
||||
|
||||
describe('relative()', () => {
|
||||
it('should relative element', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.relative(1, 2)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ relative: [1, 2] }, true])
|
||||
})
|
||||
})
|
||||
|
||||
describe('flip()', () => {
|
||||
it('should flip element', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.flip('x', 2)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ flip: 'x', origin: 2 }, true])
|
||||
})
|
||||
|
||||
it('should sets flip to "both" when calling without anything', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.flip()
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ flip: 'both', origin: 'center' }, true])
|
||||
})
|
||||
|
||||
it('should set flip to both and origin to number when called with origin only', function () {
|
||||
const dom = new Dom()
|
||||
const spy = sinon.spy(dom, 'transform')
|
||||
dom.flip(5)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([{ flip: 'both', origin: 5 }, true])
|
||||
})
|
||||
})
|
||||
})
|
@ -72,9 +72,10 @@ export class Transform<TElement extends Element>
|
||||
}
|
||||
|
||||
matrix(): Matrix
|
||||
matrix(m: Matrix | Matrix.MatrixLike): this
|
||||
matrix(a: number, b: number, c: number, d: number, e: number, f: number): this
|
||||
matrix(
|
||||
a?: number,
|
||||
a?: Matrix | Matrix.MatrixLike | number,
|
||||
b?: number,
|
||||
c?: number,
|
||||
d?: number,
|
||||
@ -85,17 +86,19 @@ export class Transform<TElement extends Element>
|
||||
return new Matrix(this)
|
||||
}
|
||||
|
||||
return this.attr(
|
||||
'transform',
|
||||
new Matrix(
|
||||
a,
|
||||
b as number,
|
||||
c as number,
|
||||
d as number,
|
||||
e as number,
|
||||
f as number,
|
||||
).toString(),
|
||||
)
|
||||
const m =
|
||||
typeof a === 'number'
|
||||
? new Matrix(
|
||||
a,
|
||||
b as number,
|
||||
c as number,
|
||||
d as number,
|
||||
e as number,
|
||||
f as number,
|
||||
)
|
||||
: new Matrix(a)
|
||||
|
||||
return this.attr('transform', m.toString())
|
||||
}
|
||||
|
||||
rotate(angle: number): this
|
||||
@ -104,12 +107,22 @@ export class Transform<TElement extends Element>
|
||||
return this.transform({ rotate: angle, ox: cx, oy: cy }, true)
|
||||
}
|
||||
|
||||
skew(): this
|
||||
skew(s: number): this
|
||||
skew(x: number, y: number): this
|
||||
skew(s: number, cx: number, cy: number): this
|
||||
skew(x: number, y: number, cx: number, cy: number): this
|
||||
skew(x: number, y: number, cx?: number, cy?: number) {
|
||||
skew(x?: number, y?: number, cx?: number, cy?: number) {
|
||||
return arguments.length === 1 || arguments.length === 3
|
||||
? this.transform({ skew: x, ox: y, oy: cx }, true)
|
||||
: this.transform({ skew: [x, y], ox: cx, oy: cy }, true)
|
||||
: this.transform(
|
||||
{
|
||||
skew: typeof x === 'undefined' ? undefined : [x, y as number],
|
||||
ox: cx,
|
||||
oy: cy,
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
shear(lam: number): this
|
||||
@ -118,12 +131,22 @@ export class Transform<TElement extends Element>
|
||||
return this.transform({ shear: lam, ox: cx, oy: cy }, true)
|
||||
}
|
||||
|
||||
scale(): this
|
||||
scale(s: number): this
|
||||
scale(x: number, y: number): this
|
||||
scale(x: number, y: number, cx: number, cy: number): this
|
||||
scale(x: number, y: number, cx?: number, cy?: number) {
|
||||
scale(s: number, cx: number, cy: number): this
|
||||
scale(x?: number, y?: number, cx?: number, cy?: number) {
|
||||
return arguments.length === 1 || arguments.length === 3
|
||||
? this.transform({ scale: x, ox: y, oy: cx }, true)
|
||||
: this.transform({ scale: [x, y], ox: cx, oy: cy }, true)
|
||||
: this.transform(
|
||||
{
|
||||
scale: typeof x === 'undefined' ? undefined : [x, y as number],
|
||||
ox: cx,
|
||||
oy: cy,
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
translate(x: number, y: number) {
|
||||
@ -137,7 +160,7 @@ export class Transform<TElement extends Element>
|
||||
flip(origin?: number | [number, number] | Point.PointLike): this
|
||||
flip(
|
||||
direction: 'both' | 'x' | 'y',
|
||||
origin?: number | [number, number] | Point.PointLike,
|
||||
origin?: number | [number, number] | Point.PointLike | 'center',
|
||||
): this
|
||||
flip(
|
||||
direction:
|
||||
@ -147,7 +170,7 @@ export class Transform<TElement extends Element>
|
||||
| number
|
||||
| [number, number]
|
||||
| Point.PointLike = 'both',
|
||||
origin?: number | [number, number] | Point.PointLike,
|
||||
origin: number | [number, number] | Point.PointLike | 'center' = 'center',
|
||||
) {
|
||||
if (typeof direction !== 'string') {
|
||||
origin = direction // eslint-disable-line
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Global } from '../global'
|
||||
import { Box } from './box'
|
||||
import { Matrix } from './matrix'
|
||||
|
||||
@ -88,26 +87,6 @@ describe('Box', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('addOffset()', () => {
|
||||
it('should return a new instance', () => {
|
||||
Global.withWindow({ pageXOffset: 50, pageYOffset: 25 } as any, () => {
|
||||
const box = new Box(100, 100, 100, 100)
|
||||
const box2 = box.addOffset()
|
||||
|
||||
expect(box2).toBeInstanceOf(Box)
|
||||
expect(box2).not.toBe(box)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add the current page offset to the box', () => {
|
||||
Global.withWindow({ pageXOffset: 50, pageYOffset: 25 } as any, () => {
|
||||
const box = new Box(100, 100, 100, 100).addOffset()
|
||||
|
||||
expect(box.toArray()).toEqual([150, 125, 100, 100])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('merge()', () => {
|
||||
it('should merge various bounding boxes', () => {
|
||||
const box1 = new Box(50, 50, 100, 100)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Global } from '../global'
|
||||
import { Matrix } from './matrix'
|
||||
import { Point } from './point'
|
||||
import { Matrix } from './matrix'
|
||||
|
||||
export class Box implements Box.BoxLike {
|
||||
x: number
|
||||
@ -67,13 +66,6 @@ export class Box implements Box.BoxLike {
|
||||
return this
|
||||
}
|
||||
|
||||
addOffset() {
|
||||
// offset by window scroll position, because getBoundingClientRect changes when window is scrolled
|
||||
this.x += Global.window.pageXOffset
|
||||
this.y += Global.window.pageYOffset
|
||||
return new Box(this)
|
||||
}
|
||||
|
||||
isNull() {
|
||||
return Box.isNull(this)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export class Matrix implements Matrix.MatrixLike {
|
||||
constructor(element: Matrix.Matrixifiable | null)
|
||||
constructor(array: Matrix.MatrixArray)
|
||||
constructor(matrix: Matrix.MatrixLike)
|
||||
constructor(options: Matrix.TransformOptions)
|
||||
constructor(options: Matrix.TransformOptionsStrict)
|
||||
constructor(
|
||||
a?:
|
||||
| number
|
||||
@ -44,7 +44,7 @@ export class Matrix implements Matrix.MatrixLike {
|
||||
| Matrix.Matrixifiable
|
||||
| Matrix.MatrixArray
|
||||
| Matrix.MatrixLike
|
||||
| Matrix.TransformOptions
|
||||
| Matrix.TransformOptionsStrict
|
||||
| null,
|
||||
b?: number,
|
||||
c?: number,
|
||||
@ -67,7 +67,7 @@ export class Matrix implements Matrix.MatrixLike {
|
||||
: typeof a === 'object' && Matrix.isMatrixLike(a)
|
||||
? a
|
||||
: typeof a === 'object'
|
||||
? new Matrix().transform(a as Matrix.TransformOptions)
|
||||
? new Matrix().transform(a as Matrix.TransformOptionsStrict)
|
||||
: typeof a === 'number'
|
||||
? Matrix.toMatrixLike([
|
||||
a,
|
||||
@ -380,7 +380,7 @@ export class Matrix implements Matrix.MatrixLike {
|
||||
return this.skew(0, y, cx, cy)
|
||||
}
|
||||
|
||||
transform(o: Matrix.MatrixLike | Matrix.TransformOptions) {
|
||||
transform(o: Matrix.MatrixLike | Matrix.TransformOptionsStrict) {
|
||||
if (Matrix.isMatrixLike(o)) {
|
||||
const matrix = new Matrix(o)
|
||||
return matrix.multiplyO(this)
|
||||
@ -452,7 +452,7 @@ export namespace Matrix {
|
||||
}
|
||||
|
||||
export namespace Matrix {
|
||||
export interface TransformOptions {
|
||||
export interface TransformOptionsStrict {
|
||||
flip?: 'both' | 'x' | 'y' | boolean
|
||||
skew?: number | [number, number]
|
||||
skewX?: number
|
||||
@ -491,9 +491,14 @@ export namespace Matrix {
|
||||
relativeY?: number
|
||||
}
|
||||
|
||||
export interface TransformOptions
|
||||
extends Omit<TransformOptionsStrict, 'origin'> {
|
||||
origin?: number | [number, number] | { x: number; y: number } | 'center'
|
||||
}
|
||||
|
||||
export type Transform = ReturnType<typeof Matrix.prototype.decompose>
|
||||
|
||||
export function formatTransforms(o: TransformOptions) {
|
||||
export function formatTransforms(o: TransformOptionsStrict) {
|
||||
const flipBoth = o.flip === 'both' || o.flip === true
|
||||
const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1
|
||||
const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1
|
||||
|
@ -14,7 +14,18 @@ export class ContainerExtension<
|
||||
text?: string | Attributes | null,
|
||||
attrs?: Attributes | null,
|
||||
) {
|
||||
return Text.create(text, attrs).appendTo(this)
|
||||
const instance = new Text().appendTo(this)
|
||||
if (text) {
|
||||
if (typeof text === 'string') {
|
||||
instance.text(text)
|
||||
if (attrs) {
|
||||
instance.attr(attrs)
|
||||
}
|
||||
} else {
|
||||
instance.attr(text)
|
||||
}
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
plain<Attributes extends SVGTextAttributes>(attrs?: Attributes | null): Text
|
||||
@ -26,17 +37,17 @@ export class ContainerExtension<
|
||||
text?: string | Attributes | null,
|
||||
attrs?: Attributes,
|
||||
) {
|
||||
const t = Text.create()
|
||||
const instance = new Text().appendTo(this)
|
||||
if (text) {
|
||||
if (typeof text === 'string') {
|
||||
t.plain(text)
|
||||
instance.plain(text)
|
||||
if (attrs) {
|
||||
t.attr(attrs)
|
||||
instance.attr(attrs)
|
||||
}
|
||||
} else {
|
||||
t.attr(text)
|
||||
instance.attr(text)
|
||||
}
|
||||
}
|
||||
return t
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,30 @@
|
||||
import { UnitNumber } from '../../struct/unit-number'
|
||||
import { AttributesBase } from '../../dom/attributes'
|
||||
|
||||
export class AttrOverride extends AttributesBase {
|
||||
export class Overrides extends AttributesBase {
|
||||
attr(attr: any, value: any) {
|
||||
if (attr === 'leading') {
|
||||
return this.leading(value)
|
||||
if (typeof value === 'undefined') {
|
||||
return this.leading()
|
||||
}
|
||||
this.leading(value)
|
||||
}
|
||||
|
||||
const ret = super.attr(attr, value)
|
||||
|
||||
if (attr === 'font-size' || attr === 'x') {
|
||||
this.rebuild()
|
||||
if (typeof value !== 'undefined') {
|
||||
if (attr === 'font-size' || attr === 'x') {
|
||||
this.rebuild()
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
export interface AttrOverride extends AttrOverride.Depends {}
|
||||
export interface Overrides extends Overrides.Depends {}
|
||||
|
||||
export namespace AttrOverride {
|
||||
export namespace Overrides {
|
||||
export interface Depends {
|
||||
leading(): number
|
||||
leading(value: UnitNumber.Raw): this
|
@ -3,16 +3,15 @@ import { UnitNumber } from '../../struct/unit-number'
|
||||
import { Adopter } from '../../dom/common/adopter'
|
||||
import { TSpan } from '../tspan/tspan'
|
||||
import { TextBase } from './base'
|
||||
import { AttrOverride } from './override'
|
||||
import { SVGTextAttributes } from './types'
|
||||
import { Overrides } from './overrides'
|
||||
|
||||
@Text.mixin(AttrOverride)
|
||||
@Text.mixin(Overrides)
|
||||
@Text.register('Text')
|
||||
export class Text<
|
||||
TSVGTextElement extends SVGTextElement | SVGTextPathElement = SVGTextElement
|
||||
>
|
||||
extends TextBase<TSVGTextElement>
|
||||
implements AttrOverride.Depends {
|
||||
implements Overrides.Depends {
|
||||
public affixes: Record<string | number, any> & {
|
||||
leading: number
|
||||
}
|
||||
@ -21,8 +20,8 @@ export class Text<
|
||||
|
||||
restoreAffix() {
|
||||
super.restoreAffix()
|
||||
if (this.affixes.leading == null) {
|
||||
this.affixes.leading = 1.3
|
||||
if (this.leading() == null) {
|
||||
this.leading(1.3)
|
||||
}
|
||||
return this
|
||||
}
|
||||
@ -31,10 +30,10 @@ export class Text<
|
||||
leading(value: UnitNumber.Raw): this
|
||||
leading(value?: UnitNumber.Raw) {
|
||||
if (value == null) {
|
||||
return this.affixes.leading
|
||||
return this.affix('leading')
|
||||
}
|
||||
|
||||
this.affixes.leading = UnitNumber.create(value).valueOf()
|
||||
this.affix('leading', UnitNumber.create(value).valueOf())
|
||||
return this.rebuild()
|
||||
}
|
||||
|
||||
@ -123,34 +122,3 @@ export class Text<
|
||||
return this.tspan(text).newLine()
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Text {
|
||||
export function create<Attributes extends SVGTextAttributes>(
|
||||
attrs?: Attributes | null,
|
||||
): Text
|
||||
export function create<Attributes extends SVGTextAttributes>(
|
||||
text: string,
|
||||
attrs?: Attributes | null,
|
||||
): Text
|
||||
export function create<Attributes extends SVGTextAttributes>(
|
||||
text?: string | Attributes | null,
|
||||
attrs?: Attributes | null,
|
||||
): Text
|
||||
export function create<Attributes extends SVGTextAttributes>(
|
||||
text?: string | Attributes | null,
|
||||
attrs?: Attributes | null,
|
||||
) {
|
||||
const t = new Text()
|
||||
if (text) {
|
||||
if (typeof text === 'string') {
|
||||
t.text(text)
|
||||
if (attrs) {
|
||||
t.attr(attrs)
|
||||
}
|
||||
} else {
|
||||
t.attr(text)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export class ContainerExtension<
|
||||
path: string | Path,
|
||||
attrs?: Attributes,
|
||||
) {
|
||||
const instance = text instanceof Text ? text : Text.create(text)
|
||||
const instance = text instanceof Text ? text : new Text().text(text)
|
||||
const textPath = instance.path(path)
|
||||
if (attrs) {
|
||||
textPath.attr(attrs)
|
||||
@ -74,7 +74,7 @@ export class PathExtension<
|
||||
text: string | Text,
|
||||
attrs?: Attributes,
|
||||
) {
|
||||
const instance = text instanceof Text ? text : Text.create(text)
|
||||
const instance = text instanceof Text ? text : new Text().text(text)
|
||||
if (!instance.parent()) {
|
||||
this.after(instance)
|
||||
}
|
||||
|
@ -9,12 +9,22 @@ export class TextExtension<
|
||||
text = '',
|
||||
attrs?: Attributes | null,
|
||||
) {
|
||||
const tspan = TSpan.create(text, attrs)
|
||||
const tspan = new TSpan().appendTo(this)
|
||||
if (text != null) {
|
||||
if (typeof text === 'string') {
|
||||
tspan.text(text)
|
||||
if (attrs) {
|
||||
tspan.attr(attrs)
|
||||
}
|
||||
} else {
|
||||
tspan.attr(text)
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.building) {
|
||||
this.clear()
|
||||
}
|
||||
|
||||
return tspan.appendTo(this)
|
||||
return tspan
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Global } from '../../global'
|
||||
import { Text } from '../text/text'
|
||||
import { TextBase } from '../text/base'
|
||||
import { SVGTSpanAttributes } from './types'
|
||||
|
||||
@TSpan.register('Tspan')
|
||||
export class TSpan extends TextBase<SVGTSpanElement> {
|
||||
@ -30,7 +29,7 @@ export class TSpan extends TextBase<SVGTSpanElement> {
|
||||
const fontSize = Global.window
|
||||
.getComputedStyle(this.node)
|
||||
.getPropertyValue('font-size')
|
||||
const dy = text.affixes.leading * Number.parseFloat(fontSize)
|
||||
const dy = text.leading() * Number.parseFloat(fontSize)
|
||||
return this.dy(index ? dy : 0).attr('x', text.x())
|
||||
}
|
||||
|
||||
@ -54,31 +53,3 @@ export class TSpan extends TextBase<SVGTSpanElement> {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TSpan {
|
||||
export function create(): TSpan
|
||||
export function create<Attributes extends SVGTSpanAttributes>(
|
||||
attrs: Attributes | null,
|
||||
): TSpan
|
||||
export function create<Attributes extends SVGTSpanAttributes>(
|
||||
text: string,
|
||||
attrs?: Attributes | null,
|
||||
): TSpan
|
||||
export function create<Attributes extends SVGTSpanAttributes>(
|
||||
text?: string | Attributes | null,
|
||||
attrs?: Attributes | null,
|
||||
) {
|
||||
const tspan = new TSpan()
|
||||
if (text != null) {
|
||||
if (typeof text === 'string') {
|
||||
tspan.text(text)
|
||||
if (attrs) {
|
||||
tspan.attr(attrs)
|
||||
}
|
||||
} else {
|
||||
tspan.attr(text)
|
||||
}
|
||||
}
|
||||
return tspan
|
||||
}
|
||||
}
|
||||
|
92
packages/x6-vector/src/vector/vector/bbox.test.ts
Normal file
92
packages/x6-vector/src/vector/vector/bbox.test.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Global } from '../../global'
|
||||
import { Box } from '../../struct/box'
|
||||
import { Matrix } from '../../struct/matrix'
|
||||
import { Rect } from '../rect/rect'
|
||||
import { Svg } from '../svg/svg'
|
||||
|
||||
describe('Vector', () => {
|
||||
describe('bbox()', () => {
|
||||
it('should returns the bounding box of the element', () => {
|
||||
const svg = new Svg().appendTo(document.body)
|
||||
const rect = svg.rect().size(100, 200).move(20, 30)
|
||||
|
||||
expect(rect.bbox()).toBeInstanceOf(Box)
|
||||
expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200])
|
||||
svg.remove()
|
||||
})
|
||||
|
||||
it('should return the bounding box of the element even if the node is not in the dom', () => {
|
||||
const rect = new Rect().size(100, 200).move(20, 30)
|
||||
expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200])
|
||||
})
|
||||
|
||||
it('should throw error when it is not possible to get a bbox', () => {
|
||||
const spy = spyOn(
|
||||
Global.window.SVGGraphicsElement.prototype,
|
||||
'getBBox',
|
||||
).and.callFake(() => {
|
||||
throw new Error('No BBox for you')
|
||||
})
|
||||
const rect = new Rect()
|
||||
expect(() => rect.bbox()).toThrow()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('rbox()', () => {
|
||||
it('should return the BoundingClientRect of the element', () => {
|
||||
document.body.style.margin = '0px'
|
||||
document.body.style.padding = '0px'
|
||||
|
||||
const svg = new Svg().appendTo(document.body)
|
||||
const rect = new Rect()
|
||||
.size(100, 200)
|
||||
.move(20, 30)
|
||||
.addTo(svg)
|
||||
.attr(
|
||||
'transform',
|
||||
new Matrix({ scale: 2, translate: [40, 50] }).toString(),
|
||||
)
|
||||
|
||||
expect(rect.rbox()).toBeInstanceOf(Box)
|
||||
expect(rect.rbox().toArray()).toEqual([80, 110, 200, 400])
|
||||
svg.remove()
|
||||
|
||||
document.body.style.margin = ''
|
||||
document.body.style.padding = ''
|
||||
})
|
||||
|
||||
it('should return the rbox box of the element in the coordinate system of the passed element', () => {
|
||||
const svg = new Svg().appendTo(document.body)
|
||||
const group = svg.group().translate(1, 1)
|
||||
const rect = new Rect()
|
||||
.size(100, 200)
|
||||
.move(20, 30)
|
||||
.addTo(svg)
|
||||
.attr(
|
||||
'transform',
|
||||
new Matrix({ scale: 2, translate: [40, 50] }).toString(),
|
||||
)
|
||||
|
||||
expect(rect.rbox(group)).toBeInstanceOf(Box)
|
||||
expect(rect.rbox(group).toArray()).toEqual([79, 109, 200, 400])
|
||||
svg.remove()
|
||||
})
|
||||
|
||||
it('should throw error when element is not in dom', () => {
|
||||
expect(() => new Rect().rbox()).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('containsPoint()', () => {
|
||||
it('checks if a point is in the elements borders', () => {
|
||||
const svg = new Svg()
|
||||
const rect = svg.rect(100, 100)
|
||||
|
||||
expect(rect.containsPoint(50, 50)).toBe(true)
|
||||
expect(rect.containsPoint({ x: 50, y: 50 })).toBe(true)
|
||||
|
||||
expect(rect.containsPoint(101, 101)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
@ -2,6 +2,8 @@ import { withSvgContext } from '../../util'
|
||||
import { Box } from '../../struct/box'
|
||||
import { Base } from '../common/base'
|
||||
import { Transform } from './transform'
|
||||
import { Global } from '../../global'
|
||||
import { Point } from '../../struct/point'
|
||||
|
||||
export class BBox<
|
||||
TSVGElement extends SVGElement = SVGElement
|
||||
@ -50,11 +52,18 @@ export class BBox<
|
||||
|
||||
// Else we want it in absolute screen coordinates
|
||||
// Therefore we need to add the scrollOffset
|
||||
return rbox.addOffset()
|
||||
rbox.x += Global.window.pageXOffset
|
||||
rbox.y += Global.window.pageYOffset
|
||||
|
||||
return rbox
|
||||
}
|
||||
|
||||
inside(x: number, y: number) {
|
||||
containsPoint(p: Point.PointLike): boolean
|
||||
containsPoint(x: number, y: number): boolean
|
||||
containsPoint(arg1: number | Point.PointLike, arg2?: number) {
|
||||
const box = this.bbox()
|
||||
const x = typeof arg1 === 'number' ? arg1 : arg1.x
|
||||
const y = typeof arg1 === 'number' ? (arg2 as number) : arg1.y
|
||||
return (
|
||||
x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height
|
||||
)
|
||||
|
162
packages/x6-vector/src/vector/vector/fillstroke.test.ts
Normal file
162
packages/x6-vector/src/vector/vector/fillstroke.test.ts
Normal file
@ -0,0 +1,162 @@
|
||||
import { Pattern } from '../pattern/pattern'
|
||||
import { Rect } from '../rect/rect'
|
||||
import { Svg } from '../svg/svg'
|
||||
|
||||
describe('Vector', () => {
|
||||
describe('fill()', () => {
|
||||
describe('setter', () => {
|
||||
it('should return itself', () => {
|
||||
const rect = new Rect()
|
||||
expect(rect.fill('black')).toBe(rect)
|
||||
})
|
||||
|
||||
it('should set a fill color', () => {
|
||||
const rect = new Rect()
|
||||
expect(rect.fill('black').attr('fill')).toBe('black')
|
||||
})
|
||||
|
||||
it('should remove fill when pass `null`', () => {
|
||||
const rect = new Rect()
|
||||
expect(rect.fill('black').attr('fill')).toBe('black')
|
||||
expect(rect.fill(null).attr('fill')).toEqual('#000000')
|
||||
})
|
||||
|
||||
it('should set fill with color object', () => {
|
||||
const rect = new Rect()
|
||||
expect(rect.fill({ r: 1, g: 1, b: 1 }).attr('fill')).toBe(
|
||||
'rgba(1,1,1,1)',
|
||||
)
|
||||
})
|
||||
|
||||
it('should set a fill pattern when pattern given', () => {
|
||||
const svg = new Svg()
|
||||
const pattern = svg.pattern()
|
||||
const rect = svg.rect(100, 100)
|
||||
expect(rect.fill(pattern).attr('fill')).toBe(pattern.url())
|
||||
})
|
||||
|
||||
it('should set a fill pattern when image given', () => {
|
||||
const svg = new Svg()
|
||||
const image = svg.image('http://via.placeholder.com/120x80')
|
||||
const rect = svg.rect(100, 100)
|
||||
expect(rect.fill(image).attr('fill')).toBe(
|
||||
image.parent<Pattern>()!.url(),
|
||||
)
|
||||
})
|
||||
|
||||
it('should set a fill pattern when image url given', () => {
|
||||
const svg = new Svg()
|
||||
const rect = svg.rect()
|
||||
rect.fill(
|
||||
'https://www.centerforempathy.org/wp-content/uploads/2019/11/placeholder.png',
|
||||
)
|
||||
const defs = svg.defs()
|
||||
const pattern = defs.firstChild<Pattern>()!
|
||||
|
||||
expect(rect.fill()).toEqual(pattern.url())
|
||||
})
|
||||
|
||||
it('should set an object of fill properties', () => {
|
||||
const rect = new Rect()
|
||||
expect(
|
||||
rect
|
||||
.fill({
|
||||
color: 'black',
|
||||
opacity: 0.5,
|
||||
rule: 'evenodd',
|
||||
})
|
||||
.attr(),
|
||||
).toEqual({
|
||||
fill: 'black',
|
||||
fillOpacity: 0.5,
|
||||
fillRule: 'evenodd',
|
||||
} as any)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getter', () => {
|
||||
it('should return fill color', () => {
|
||||
const rect = new Rect().fill('black')
|
||||
expect(rect.fill()).toBe('black')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('stroke()', () => {
|
||||
describe('setter', () => {
|
||||
it('should return itself', () => {
|
||||
const rect = new Rect()
|
||||
expect(rect.stroke('black')).toBe(rect)
|
||||
})
|
||||
|
||||
it('should set a stroke color', () => {
|
||||
const rect = new Rect()
|
||||
expect(rect.stroke('black').attr('stroke')).toBe('black')
|
||||
})
|
||||
|
||||
it('should sets a stroke pattern when pattern given', () => {
|
||||
const svg = new Svg()
|
||||
const pattern = svg.pattern()
|
||||
const rect = svg.rect(100, 100)
|
||||
expect(rect.stroke(pattern).attr('stroke')).toBe(pattern.url())
|
||||
})
|
||||
|
||||
it('should set a stroke pattern when image given', () => {
|
||||
const svg = new Svg()
|
||||
const image = svg.image('http://via.placeholder.com/120x80')
|
||||
const rect = svg.rect(100, 100)
|
||||
expect(rect.stroke(image).attr('stroke')).toBe(
|
||||
image.parent<Pattern>()!.url(),
|
||||
)
|
||||
})
|
||||
|
||||
it('should set an object of stroke properties', () => {
|
||||
const rect = new Rect()
|
||||
expect(
|
||||
rect
|
||||
.stroke({
|
||||
color: 'black',
|
||||
width: 2,
|
||||
opacity: 0.5,
|
||||
linecap: 'butt',
|
||||
linejoin: 'miter',
|
||||
miterlimit: 10,
|
||||
dasharray: '2 2',
|
||||
dashoffset: 15,
|
||||
})
|
||||
.attr(),
|
||||
).toEqual({
|
||||
stroke: 'black',
|
||||
strokeWidth: 2,
|
||||
strokeOpacity: 0.5,
|
||||
strokeLinecap: 'butt',
|
||||
strokeLinejoin: 'miter',
|
||||
strokeMiterlimit: 10,
|
||||
strokeDasharray: '2 2',
|
||||
strokeDashoffset: 15,
|
||||
} as any)
|
||||
})
|
||||
|
||||
it('should set stroke dasharray with array passed', () => {
|
||||
const rect = new Rect().stroke({ dasharray: [2, 2] })
|
||||
expect(rect.attr()).toEqual({ strokeDasharray: '2 2' } as any)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getter', () => {
|
||||
it('should return stroke color', () => {
|
||||
const rect = new Rect().stroke('black')
|
||||
expect(rect.stroke()).toBe('black')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('opacity()', () => {
|
||||
it('should get/set opacity', () => {
|
||||
const rect = new Rect()
|
||||
expect(rect.opacity()).toEqual(1)
|
||||
rect.opacity(0.5)
|
||||
expect(rect.opacity()).toEqual(0.5)
|
||||
})
|
||||
})
|
||||
})
|
@ -1,11 +1,13 @@
|
||||
import { Color } from '../../struct/color'
|
||||
import { Base } from '../common/base'
|
||||
import { Image } from '../image/image'
|
||||
import { Vector } from './vector'
|
||||
|
||||
export class FillStroke<
|
||||
TSVGElement extends SVGElement = SVGElement
|
||||
> extends Base<TSVGElement> {
|
||||
fill(): string
|
||||
fill(color: string | Color | Color.RGBALike | Base | null): this
|
||||
fill(color: string | Color | Color.RGBALike | Vector | null): this
|
||||
fill(attrs: {
|
||||
color?: string
|
||||
opacity?: number
|
||||
@ -17,7 +19,7 @@ export class FillStroke<
|
||||
| string
|
||||
| Color
|
||||
| Color.RGBALike
|
||||
| Base
|
||||
| Vector
|
||||
| {
|
||||
color?: string
|
||||
opacity?: number
|
||||
@ -33,7 +35,7 @@ export class FillStroke<
|
||||
}
|
||||
|
||||
stroke(): string
|
||||
stroke(color: string | Color | Color.RGBALike | Base | null): this
|
||||
stroke(color: string | Color | Color.RGBALike | Vector | null): this
|
||||
stroke(attrs: {
|
||||
color?: string
|
||||
width?: number
|
||||
@ -41,8 +43,8 @@ export class FillStroke<
|
||||
linecap?: 'butt' | 'round' | 'square'
|
||||
linejoin?: 'arcs' | 'bevel' | 'miter' | 'miter-clip' | 'round'
|
||||
miterlimit?: number
|
||||
dasharray?: string
|
||||
dashoffset?: string
|
||||
dasharray?: string | number[]
|
||||
dashoffset?: string | number
|
||||
}): this
|
||||
stroke(
|
||||
value?:
|
||||
@ -50,7 +52,7 @@ export class FillStroke<
|
||||
| string
|
||||
| Color
|
||||
| Color.RGBALike
|
||||
| Base
|
||||
| Vector
|
||||
| {
|
||||
color?: string
|
||||
width?: number
|
||||
@ -58,8 +60,8 @@ export class FillStroke<
|
||||
linecap?: 'butt' | 'round' | 'square'
|
||||
linejoin?: 'arcs' | 'bevel' | 'miter' | 'miter-clip' | 'round'
|
||||
miterlimit?: number
|
||||
dasharray?: string
|
||||
dashoffset?: string
|
||||
dasharray?: string | number[]
|
||||
dashoffset?: string | number
|
||||
},
|
||||
) {
|
||||
if (typeof value === 'undefined') {
|
||||
@ -101,14 +103,17 @@ export namespace FillStroke {
|
||||
| string
|
||||
| Color
|
||||
| Color.RGBALike
|
||||
| Base<T>
|
||||
| Record<string, string | number>
|
||||
| Vector
|
||||
| Record<string, string | number | number[]>
|
||||
| null,
|
||||
) {
|
||||
if (value === null) {
|
||||
elem.attr(type, null)
|
||||
} else if (typeof value === 'string' || value instanceof Base) {
|
||||
elem.attr(type, value.toString())
|
||||
} else if (typeof value === 'string' || value instanceof Vector) {
|
||||
elem.attr(
|
||||
type,
|
||||
value instanceof Image ? (value as any) : value.toString(),
|
||||
)
|
||||
} else if (value instanceof Color || Color.isRgbLike(value)) {
|
||||
const color = new Color(value)
|
||||
elem.attr(type, color.toString())
|
||||
@ -118,7 +123,7 @@ export namespace FillStroke {
|
||||
const k = names[i]
|
||||
const v = value[k]
|
||||
if (v != null) {
|
||||
elem.attr(prefix(type, k), v)
|
||||
elem.attr(prefix(type, k), Array.isArray(v) ? v.join(' ') : v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
72
packages/x6-vector/src/vector/vector/font.test.ts
Normal file
72
packages/x6-vector/src/vector/vector/font.test.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import sinon from 'sinon'
|
||||
import { Svg } from '../svg/svg'
|
||||
import { Text } from '../text/text'
|
||||
|
||||
describe('Vector', () => {
|
||||
describe('font()', () => {
|
||||
let svg: Svg
|
||||
let txt: Text
|
||||
|
||||
beforeEach(() => {
|
||||
svg = new Svg().appendTo(document.body)
|
||||
txt = svg.text('Some text')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
svg.remove()
|
||||
})
|
||||
|
||||
it('should set leading when given', function () {
|
||||
const spy = spyOn(txt, 'leading')
|
||||
txt.font({ leading: 3 })
|
||||
expect(spy).toHaveBeenCalledWith(3)
|
||||
})
|
||||
|
||||
it('should sets text-anchor when anchor given', function () {
|
||||
const spy = sinon.spy(txt, 'attr')
|
||||
txt.font({ anchor: 'start' })
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual(['text-anchor', 'start'])
|
||||
})
|
||||
|
||||
it('should set all font properties via attr()', function () {
|
||||
const spy = spyOn(txt, 'attr')
|
||||
txt.font({
|
||||
size: 20,
|
||||
family: 'Verdana',
|
||||
weight: 'bold',
|
||||
stretch: 'wider',
|
||||
variant: 'small-caps',
|
||||
style: 'italic',
|
||||
})
|
||||
expect(spy).toHaveBeenCalledWith('font-size', 20)
|
||||
expect(spy).toHaveBeenCalledWith('font-family', 'Verdana')
|
||||
expect(spy).toHaveBeenCalledWith('font-weight', 'bold')
|
||||
expect(spy).toHaveBeenCalledWith('font-stretch', 'wider')
|
||||
expect(spy).toHaveBeenCalledWith('font-variant', 'small-caps')
|
||||
expect(spy).toHaveBeenCalledWith('font-style', 'italic')
|
||||
})
|
||||
|
||||
it('should redirect all other stuff directly to attr()', function () {
|
||||
const spy = spyOn(txt, 'attr')
|
||||
txt.font({
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
} as any)
|
||||
expect(spy).toHaveBeenCalledWith('foo', 'bar')
|
||||
expect(spy).toHaveBeenCalledWith('bar', 'baz')
|
||||
})
|
||||
|
||||
it('should set key value pair', function () {
|
||||
const spy = spyOn(txt, 'attr')
|
||||
txt.font('size', 20)
|
||||
expect(spy).toHaveBeenCalledWith('font-size', 20)
|
||||
})
|
||||
|
||||
it('should get value if called with one parameter', function () {
|
||||
const spy = spyOn(txt, 'attr')
|
||||
txt.font('size')
|
||||
expect(spy).toHaveBeenCalledWith('font-size', undefined)
|
||||
})
|
||||
})
|
||||
})
|
49
packages/x6-vector/src/vector/vector/font.ts
Normal file
49
packages/x6-vector/src/vector/vector/font.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { Base } from '../common/base'
|
||||
import { Text } from '../text/text'
|
||||
|
||||
export class FontStyle<
|
||||
TSVGElement extends SVGElement = SVGElement
|
||||
> extends Base<TSVGElement> {
|
||||
font(key: string): string | number
|
||||
font(attrs: FontStyle.Attributes): this
|
||||
font(key: string, value: string | number | null | undefined): this
|
||||
font(a: FontStyle.Attributes | string, v?: string | number) {
|
||||
if (typeof a === 'object') {
|
||||
Object.keys(a).forEach((key: keyof FontStyle.Attributes) =>
|
||||
this.font(key, a[key]),
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
if (a === 'leading') {
|
||||
const text = (this as any) as Text // eslint-disable-line
|
||||
if (text.leading) {
|
||||
return text.leading(v)
|
||||
}
|
||||
}
|
||||
|
||||
return a === 'anchor'
|
||||
? this.attr('text-anchor', v)
|
||||
: a === 'size' ||
|
||||
a === 'family' ||
|
||||
a === 'weight' ||
|
||||
a === 'stretch' ||
|
||||
a === 'variant' ||
|
||||
a === 'style'
|
||||
? this.attr(`font-${a}`, v)
|
||||
: this.attr(a, v)
|
||||
}
|
||||
}
|
||||
|
||||
export namespace FontStyle {
|
||||
export interface Attributes {
|
||||
leading?: number
|
||||
anchor?: string | number | null
|
||||
size?: string | number | null
|
||||
family?: string | null
|
||||
weight?: string | number | null
|
||||
stretch?: string | null
|
||||
variant?: string | null
|
||||
style?: string | null
|
||||
}
|
||||
}
|
@ -2,9 +2,11 @@ import { applyMixins } from '../../util'
|
||||
import { ElementExtension as StyleExtension } from '../style/exts'
|
||||
import { ElementExtension as MaskExtension } from '../mask/exts'
|
||||
import { ElementExtension as ClipPathExtension } from '../clippath/exts'
|
||||
import { Vector } from './vector'
|
||||
import { Base } from '../common/base'
|
||||
import { Vector } from './vector'
|
||||
import { Overrides } from './overrides'
|
||||
import { BBox } from './bbox'
|
||||
import { FontStyle } from './font'
|
||||
import { Transform } from './transform'
|
||||
import { FillStroke } from './fillstroke'
|
||||
|
||||
@ -13,6 +15,7 @@ declare module './vector' {
|
||||
extends Base<TSVGElement>,
|
||||
FillStroke<TSVGElement>,
|
||||
BBox<TSVGElement>,
|
||||
FontStyle<TSVGElement>,
|
||||
Transform<TSVGElement>,
|
||||
MaskExtension<TSVGElement>,
|
||||
StyleExtension<TSVGElement>,
|
||||
@ -22,7 +25,9 @@ declare module './vector' {
|
||||
applyMixins(
|
||||
Vector,
|
||||
Base,
|
||||
Overrides,
|
||||
BBox,
|
||||
FontStyle,
|
||||
Transform,
|
||||
FillStroke,
|
||||
MaskExtension,
|
||||
|
55
packages/x6-vector/src/vector/vector/overrides.ts
Normal file
55
packages/x6-vector/src/vector/vector/overrides.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Util } from '../../dom/attributes/util'
|
||||
import { AttributesBase } from '../../dom/attributes/base'
|
||||
|
||||
export const defaultAttributes = {
|
||||
// fill and stroke
|
||||
fillOpacity: 1,
|
||||
strokeOpacity: 1,
|
||||
strokeWidth: 0,
|
||||
strokeLinejoin: 'miter',
|
||||
strokeLinecap: 'butt',
|
||||
fill: '#000000',
|
||||
stroke: '#000000',
|
||||
opacity: 1,
|
||||
|
||||
// position
|
||||
x: 0,
|
||||
y: 0,
|
||||
cx: 0,
|
||||
cy: 0,
|
||||
|
||||
// size
|
||||
width: 0,
|
||||
height: 0,
|
||||
|
||||
// radius
|
||||
r: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
|
||||
// gradient
|
||||
offset: 0,
|
||||
stopOpacity: 1,
|
||||
stopColor: '#000000',
|
||||
|
||||
// text
|
||||
textAnchor: 'start',
|
||||
}
|
||||
|
||||
export class Overrides extends AttributesBase {
|
||||
attr(attributeName: any, attributeValue: any) {
|
||||
if (
|
||||
typeof attributeName === 'string' &&
|
||||
typeof attributeValue === 'undefined'
|
||||
) {
|
||||
const val = super.attr(attributeName)
|
||||
if (typeof val === 'undefined') {
|
||||
return defaultAttributes[
|
||||
Util.camelCase(attributeName) as keyof typeof defaultAttributes
|
||||
]
|
||||
}
|
||||
return val
|
||||
}
|
||||
return super.attr(attributeName, attributeValue)
|
||||
}
|
||||
}
|
124
packages/x6-vector/src/vector/vector/transform.test.ts
Normal file
124
packages/x6-vector/src/vector/vector/transform.test.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import sinon from 'sinon'
|
||||
import { Global } from '../../global'
|
||||
import { Matrix } from '../../struct/matrix'
|
||||
import { Point } from '../../struct/point'
|
||||
import { G } from '../g/g'
|
||||
import { Rect } from '../rect/rect'
|
||||
import { Svg } from '../svg/svg'
|
||||
|
||||
describe('Vector', () => {
|
||||
describe('toParent()', () => {
|
||||
it('should return itself', () => {
|
||||
const svg = new Svg()
|
||||
const g = svg.group()
|
||||
const rect = g.rect(100, 100)
|
||||
expect(rect.toParent(svg)).toBe(rect)
|
||||
})
|
||||
|
||||
it('should do nothing if the parent is the the current element', () => {
|
||||
const svg = new Svg().appendTo(document.body)
|
||||
const g = svg.group()
|
||||
const parent = g.parent()
|
||||
const index = g.index()
|
||||
g.toParent(g)
|
||||
expect(g.parent()).toBe(parent)
|
||||
expect(g.index()).toBe(index)
|
||||
svg.remove()
|
||||
})
|
||||
|
||||
it('should move the element to a different container without changing its visual representation [1]', () => {
|
||||
const svg = new Svg().appendTo(document.body)
|
||||
const g = svg.group().matrix(1, 0, 1, 1, 0, 1)
|
||||
const rect = g.rect(100, 100)
|
||||
rect.toParent(svg)
|
||||
expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 0, 1))
|
||||
expect(rect.parent()).toBe(svg)
|
||||
svg.remove()
|
||||
})
|
||||
|
||||
it('should move the element to a different container without changing its visual representation [2]', () => {
|
||||
const svg = new Svg().appendTo(document.body)
|
||||
const g = svg.group().translate(10, 20)
|
||||
const rect = g.rect(100, 100)
|
||||
const g2 = svg.group().rotate(10)
|
||||
rect.toParent(g2)
|
||||
const actual = rect.matrix()
|
||||
const expected = new Matrix().translate(10, 20).rotate(-10)
|
||||
|
||||
const factors = 'abcdef'.split('')
|
||||
// funny enough the dom seems to shorten the floats and precision gets lost
|
||||
factors.forEach((prop: 'a') =>
|
||||
expect(actual[prop]).toBeCloseTo(expected[prop], 5),
|
||||
)
|
||||
|
||||
svg.remove()
|
||||
})
|
||||
|
||||
it('should insert the element at the specified position', () => {
|
||||
const svg = new Svg()
|
||||
const g = svg.group()
|
||||
const rect = g.rect(100, 100)
|
||||
svg.rect(100, 100)
|
||||
svg.rect(100, 100)
|
||||
expect(rect.toParent(svg, 2).index()).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toRoot()', () => {
|
||||
it('should call `toParent()` with root node', () => {
|
||||
const svg = new Svg()
|
||||
const g = svg.group().matrix(1, 0, 1, 1, 0, 1)
|
||||
const rect = g.rect(100, 100)
|
||||
const spy = sinon.spy(rect, 'toParent')
|
||||
rect.toRoot(3)
|
||||
expect(spy.callCount).toEqual(1)
|
||||
expect(spy.args[0]).toEqual([svg, 3])
|
||||
})
|
||||
|
||||
it('should do nothing when the element do not have a root', () => {
|
||||
const g = new G()
|
||||
const rect = g.rect()
|
||||
rect.toRoot()
|
||||
expect(rect.parent()).toBe(g)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toLocalPoint()', () => {
|
||||
it('should transform a screen point into the coordinate system of the element', () => {
|
||||
const rect = new Rect()
|
||||
spyOn(rect, 'screenCTM').and.callFake(
|
||||
() => new Matrix(1, 0, 0, 1, 20, 20),
|
||||
)
|
||||
expect(rect.toLocalPoint({ x: 10, y: 10 })).toEqual(new Point(-10, -10))
|
||||
expect(rect.toLocalPoint(10, 10)).toEqual(new Point(-10, -10))
|
||||
})
|
||||
})
|
||||
|
||||
describe('ctm()', () => {
|
||||
it('should return the native ctm wrapped into a matrix', () => {
|
||||
const rect = new Rect()
|
||||
const spy = sinon.spy(rect.node, 'getCTM')
|
||||
rect.ctm()
|
||||
expect(spy.callCount).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('screenCTM()', () => {
|
||||
it('should return the native screenCTM wrapped into a matrix for a normal element', () => {
|
||||
const rect = new Rect()
|
||||
const spy = sinon.spy(rect.node, 'getScreenCTM')
|
||||
rect.screenCTM()
|
||||
expect(spy.callCount).toEqual(1)
|
||||
})
|
||||
|
||||
it('should do extra work for nested svgs because firefox needs it', () => {
|
||||
const spy = sinon.spy(
|
||||
Global.window.SVGGraphicsElement.prototype,
|
||||
'getScreenCTM',
|
||||
)
|
||||
const svg = new Svg().nested()
|
||||
svg.screenCTM()
|
||||
expect(spy.callCount).toEqual(1)
|
||||
})
|
||||
})
|
||||
})
|
@ -6,6 +6,11 @@ import { Base } from '../common/base'
|
||||
export class Transform<
|
||||
TSVGElement extends SVGElement = SVGElement
|
||||
> extends Base<TSVGElement> {
|
||||
/**
|
||||
* Moves an element to a different parent (similar to addTo), but without
|
||||
* changing its visual representation. All transformations are merged and
|
||||
* applied to the element.
|
||||
*/
|
||||
toParent(parent: Transform, index?: number): this {
|
||||
if (this !== parent) {
|
||||
const ctm = this.screenCTM()
|
||||
@ -17,6 +22,11 @@ export class Transform<
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an element to the root svg (similar to addTo), but without
|
||||
* changing its visual representation. All transformations are merged and
|
||||
* applied to the element.
|
||||
*/
|
||||
toRoot(index?: number): this {
|
||||
const root = this.root()
|
||||
if (root) {
|
||||
@ -25,8 +35,14 @@ export class Transform<
|
||||
return this
|
||||
}
|
||||
|
||||
point(x: number, y: number) {
|
||||
return new Point(x, y).transform(this.screenCTM().inverse())
|
||||
/**
|
||||
* Transforms a point from screen coordinates to the elements coordinate system.
|
||||
*/
|
||||
toLocalPoint(p: Point.PointLike): Point
|
||||
toLocalPoint(x: number, y: number): Point
|
||||
toLocalPoint(x: number | Point.PointLike, y?: number) {
|
||||
const p = typeof x === 'number' ? new Point(x, y) : new Point(x)
|
||||
return p.transform(this.screenCTM().inverse())
|
||||
}
|
||||
|
||||
ctm() {
|
||||
|
141
packages/x6-vector/src/vector/vector/vector.test.ts
Normal file
141
packages/x6-vector/src/vector/vector/vector.test.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { Rect } from '../rect/rect'
|
||||
|
||||
describe('Vector', () => {
|
||||
let rect: Rect
|
||||
|
||||
beforeEach(() => {
|
||||
rect = new Rect()
|
||||
})
|
||||
|
||||
describe('x()', () => {
|
||||
it('should call attr with x', () => {
|
||||
const spy = spyOn(rect, 'attr')
|
||||
rect.x(123)
|
||||
expect(spy).toHaveBeenCalledWith('x', 123)
|
||||
})
|
||||
})
|
||||
|
||||
describe('y()', () => {
|
||||
it('should call attr with y', () => {
|
||||
const spy = spyOn(rect, 'attr')
|
||||
rect.y(123)
|
||||
expect(spy).toHaveBeenCalledWith('y', 123)
|
||||
})
|
||||
})
|
||||
|
||||
describe('move()', () => {
|
||||
it('should call `x()` and `y()` with passed parameters and returns itself', () => {
|
||||
const spyx = spyOn(rect, 'x').and.callThrough()
|
||||
const spyy = spyOn(rect, 'y').and.callThrough()
|
||||
expect(rect.move(1, 2)).toBe(rect)
|
||||
expect(spyx).toHaveBeenCalledWith(1)
|
||||
expect(spyy).toHaveBeenCalledWith(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cx()', () => {
|
||||
it('should return the elements center along the x axis', () => {
|
||||
rect.attr({ x: 10, width: 100 })
|
||||
expect(rect.cx()).toBe(60)
|
||||
})
|
||||
|
||||
it('should center the element along the x axis and returns itself', () => {
|
||||
rect.attr({ x: 10, width: 100 })
|
||||
expect(rect.cx(100)).toBe(rect)
|
||||
expect(rect.attr('x')).toBe(50)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cy()', () => {
|
||||
it('should return the elements center along the y axis', () => {
|
||||
rect.attr({ y: 10, height: 100 })
|
||||
expect(rect.cy()).toBe(60)
|
||||
})
|
||||
|
||||
it('should center the element along the y axis and returns itself', () => {
|
||||
rect.attr({ y: 10, height: 100 })
|
||||
expect(rect.cy(100)).toBe(rect)
|
||||
expect(rect.attr('y')).toBe(50)
|
||||
})
|
||||
})
|
||||
|
||||
describe('center()', () => {
|
||||
it('should call `cx()` and `cy()` with passed parameters and returns itself', () => {
|
||||
const spyCx = spyOn(rect, 'cx').and.callThrough()
|
||||
const spyCy = spyOn(rect, 'cy').and.callThrough()
|
||||
expect(rect.center(1, 2)).toBe(rect)
|
||||
expect(spyCx).toHaveBeenCalledWith(1)
|
||||
expect(spyCy).toHaveBeenCalledWith(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dx()', () => {
|
||||
it('should move the element along the x axis relatively and returns itself', () => {
|
||||
rect.attr({ x: 10, width: 100 })
|
||||
expect(rect.dx(100)).toBe(rect)
|
||||
expect(rect.attr('x')).toBe(110)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dy()', () => {
|
||||
it('should move the element along the x axis relatively and returns itself', () => {
|
||||
rect.attr({ y: 10, height: 100 })
|
||||
expect(rect.dy(100)).toBe(rect)
|
||||
expect(rect.attr('y')).toBe(110)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dmove()', () => {
|
||||
it('should call `dx()` and `dy()` with passed parameters and returns itself', () => {
|
||||
const spyDx = spyOn(rect, 'dx').and.callThrough()
|
||||
const spyDy = spyOn(rect, 'dy').and.callThrough()
|
||||
expect(rect.dmove(1, 2)).toBe(rect)
|
||||
expect(spyDx).toHaveBeenCalledWith(1)
|
||||
expect(spyDy).toHaveBeenCalledWith(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('width()', () => {
|
||||
it('should call attr with width', () => {
|
||||
const spy = spyOn(rect, 'attr')
|
||||
rect.width(123)
|
||||
expect(spy).toHaveBeenCalledWith('width', 123)
|
||||
})
|
||||
})
|
||||
|
||||
describe('height()', () => {
|
||||
it('should call attr with height', () => {
|
||||
const spy = spyOn(rect, 'attr')
|
||||
rect.height(123)
|
||||
expect(spy).toHaveBeenCalledWith('height', 123)
|
||||
})
|
||||
})
|
||||
|
||||
describe('size()', () => {
|
||||
it('should call `width()` and `height()` with passed parameters and returns itself', () => {
|
||||
const spyWidth = spyOn(rect, 'width').and.callThrough()
|
||||
const spyHeight = spyOn(rect, 'height').and.callThrough()
|
||||
expect(rect.size(1, 2)).toBe(rect)
|
||||
expect(spyWidth).toHaveBeenCalledWith(1)
|
||||
expect(spyHeight).toHaveBeenCalledWith(2)
|
||||
})
|
||||
|
||||
it('should change height proportionally if null', () => {
|
||||
const rect = Rect.create(100, 100)
|
||||
const spyWidth = spyOn(rect, 'width').and.callThrough()
|
||||
const spyHeight = spyOn(rect, 'height').and.callThrough()
|
||||
expect(rect.size(200, null)).toBe(rect)
|
||||
expect(spyWidth).toHaveBeenCalledWith(200)
|
||||
expect(spyHeight).toHaveBeenCalledWith(200)
|
||||
})
|
||||
|
||||
it('should change width proportionally if null', () => {
|
||||
const rect = Rect.create(100, 100)
|
||||
const spyWidth = spyOn(rect, 'width').and.callThrough()
|
||||
const spyHeight = spyOn(rect, 'height').and.callThrough()
|
||||
expect(rect.size(null, 200)).toBe(rect)
|
||||
expect(spyWidth).toHaveBeenCalledWith(200)
|
||||
expect(spyHeight).toHaveBeenCalledWith(200)
|
||||
})
|
||||
})
|
||||
})
|
@ -1,4 +1,3 @@
|
||||
import type { Text } from '../text/text'
|
||||
import { UnitNumber } from '../../struct/unit-number'
|
||||
import { Size } from '../common/size'
|
||||
import { Dom } from '../../dom'
|
||||
@ -80,36 +79,4 @@ export class Vector<
|
||||
dmove(x: string | number = 0, y: string | number = 0) {
|
||||
return this.dx(x).dy(y)
|
||||
}
|
||||
|
||||
// #region Font
|
||||
|
||||
font(attrs: Record<string, string | number>): this
|
||||
font(key: string, value: string | number): this
|
||||
font(a: Record<string, string | number> | string, v?: string | number) {
|
||||
if (typeof a === 'object') {
|
||||
Object.keys(a).forEach((key) => this.font(key, a[key]))
|
||||
return this
|
||||
}
|
||||
|
||||
if (a === 'leading') {
|
||||
const text = (this as any) as Text // eslint-disable-line
|
||||
if (text.leading) {
|
||||
text.leading(v)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
return a === 'anchor'
|
||||
? this.attr('text-anchor', v)
|
||||
: a === 'size' ||
|
||||
a === 'family' ||
|
||||
a === 'weight' ||
|
||||
a === 'stretch' ||
|
||||
a === 'variant' ||
|
||||
a === 'style'
|
||||
? this.attr(`font-${a}`, v)
|
||||
: this.attr(a, v)
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
|
Reference in New Issue
Block a user