/** @module editorElements.Transform */
/**
* Helper class used by {@link Transform}.
*
* Represents one single property of the transform argument, for example `translate(360 150)`
* that may be a part of longer transform argument like `transform="translate(360 150) rotate(90 30 20)"`
*/
class Property {
/**
* Initialize the Property object
* @param {string} [string] string in the property format `propertyname(list of space separated values)`
*/
constructor(string) {
if (string !== undefined) {
this.name = string.replace(/^[ ]*([^(]+).*/, '$1');
this.args = string.replace(/^[^(]+\((.*)\)/, '$1').split(' ');
}
}
/**
* set or replace the name of this property
* @param {string} name new name for this property
*/
setName(name) {
this.name = name;
}
/**
* set arguments of this property
* @param {array} args array of arguments
*/
setArguments(args) {
this.args = args;
}
/**
* get string representation of the property
* @return {string} property in the property format `name(arg1 arg2)`
*/
get() {
return this.name + '(' + this.args.join(' ') + ')';
}
}
/**
* API for manipulating the transform argument used in SVG
*/
export default class Transform {
/**
* Initialize the Transform object
* @param {string} [string] string in the format of the `transform` argument in SVG, for example `translate(360 150) rotate(90 30 20)`
*/
constructor(string) {
/**
* array of {@link Property} instances
* @type {Array}
*/
this.items = [];
if (string !== undefined) {
for (const item of string.split(')')) {
if (item) {
// if not empty
this.items.push(new Property(item + ')'));
}
}
}
}
/**
* convert distances from SVG pixels to grid pixels
* @param {App} appInstance instance of [App](./module-App.html)
*/
toGridPixels(appInstance) {
this.pixelConversion(val => appInstance.SVGToGrid(val));
}
/**
* convert distances from grid pixels to SVG pixels
* @param {App} appInstance instance of [App](./module-App.html)
*/
toSVGPixels(appInstance) {
this.pixelConversion(val => appInstance.gridToSVG(val));
}
/**
* Convert distances using a specified convertor. Used by toGridPixels and toSVGPixels
* @param {Function} convertor function that converts int to int
*/
pixelConversion(convertor) {
const propertyMap = {
translate: item => {
item.args = item.args.map(arg => convertor(arg));
return item;
},
rotate: item => {
item.args = [item.args[0], convertor(item.args[1]), convertor(item.args[2])];
return item;
}
};
this.items = this.items.map(item => {
return propertyMap[item.name] ? propertyMap[item.name](item) : item;
});
}
/**
* find a transform property by name and get its index in the [items](#items) array
* @param {string} name name of the property
* @return {number} index of the property in the array of properties or `-1` if not found
*/
getIndex(name) {
for (let i = 0; i < this.items.length; i++) {
if (name === this.items[i].name) {
return i;
}
}
return -1;
}
/**
* get the translate property
* @return {Object} object containing parameters of the translate attribute
*/
getTranslate() {
let args = this.getArguments(this.getIndex('translate'));
return {
x: Number(args[0]),
y: Number(args[1])
};
}
/**
* get the rotate property
* @return {Object} object containing parameters of the rotate attribute
*/
getRotate() {
let args = this.getArguments(this.getIndex('rotate'));
return {
deg: Number(args[0]),
centerX: Number(args[1]),
centerY: Number(args[2])
};
}
/**
* set translate to the specified values
* @param {number} x horizontal translation
* @param {number} y vertical translation
*/
setTranslate(x, y) {
this.setParameter('translate', [x, y]);
}
/**
* set rotate to the specified values
* @param {number} deg angle of the rotation in degrees
* @param {number} centerX horizontal position of the center of the rotation
* @param {number} centerY vertical position of the center of the rotation
*/
setRotate(deg, centerX, centerY) {
this.setParameter('rotate', [deg, centerX, centerY]);
}
/**
* rotate by 90 degrees to the right or left, depending on the parameter `right`
* @param {number} centerX horizontal position of the center of the rotation
* @param {number} centerY vertical position of the center of the rotation
* @param {boolean} right rotate to the right if `true`, to the left if `false`
*/
rotateRightAngle(centerX, centerY, right) {
const amount = right ? 90 : 270;
if (this.getIndex('rotate') === -1) {
this.setRotate(amount, centerX, centerY);
} else {
let newRotation = (parseInt(this.getRotate().deg) + amount) % 360;
if (newRotation === 180) {
// swap center coordinates
// because rotate(c, x, y) is defined like transform(-x, -y) rotate(c) transform(x, y)
let a = centerX;
centerX = centerY;
centerY = a;
}
this.setRotate(newRotation, centerX, centerY);
}
}
/**
* rotate by 90 degrees to the right
* @param {number} centerX horizontal position of the center of the rotation
* @param {number} centerY vertical position of the center of the rotation
*/
rotateRight(centerX, centerY) {
this.rotateRightAngle(centerX, centerY, true);
}
/**
* rotate by 90 degrees to the left
* @param {number} centerX horizontal position of the center of the rotation
* @param {number} centerY vertical position of the center of the rotation
*/
rotateLeft(centerX, centerY) {
this.rotateRightAngle(centerX, centerY, false);
}
/**
* get the transform values in a string
* @return {string} string that can be used as a value for the transform property of a SVG element
*/
get() {
let retVal;
for (const item of this.items) {
if (retVal) {
retVal += ' ' + item.get();
} else {
retVal = item.get();
}
}
return retVal;
}
/**
* get arguments of a property specified by index
* @param {number} index index of the property
* @return {array} array of arguments of the specified property
*/
getArguments(index) {
return this.items[index].args;
}
/**
* set argumets of a property specified by name
* @param {string} name name of the property
* @param {array} args array of arguments of the specified property
*/
setParameter(name, args) {
// determine index of the parameter (if set), else index == -1
let index = this.getIndex(name);
// if the property has been already set, change it (rewrite the array in the right index)
// else create a new one (set index to the length of an array --> ad an item to the end)
if (index === -1) {
index = this.items.length;
this.items[index] = new Property();
this.items[index].setName(name);
}
// save args under the right index
this.items[index].setArguments(args);
}
}