Spaces:
Sleeping
Sleeping
import {POS_PRECISION, SIZE_PRECISION, STROKE_PRECISION, roundNumber, sizeToStrokeWidth1, sizeToStrokeWidth2} from "./utils"; | |
import {identityHash, symbolize} from "./svgSymbols"; | |
import StaffToken from "./staffToken"; | |
import LogRecorder from "../logRecorder"; | |
import pick from "../pick"; | |
interface Position | |
{ | |
x: number; | |
y: number; | |
}; | |
interface Transform | |
{ | |
translate?: Position; | |
scale?: Position; | |
}; | |
type ElementValue = string | number | Transform | Position; | |
interface StaffFields { | |
href: string; | |
transform: Transform; | |
x: number; | |
y: number; | |
rx: number; | |
ry: number; | |
x1: number; | |
y1: number; | |
x2: number; | |
y2: number; | |
width: number; | |
height: number; | |
sw: number; | |
sw2: number; | |
scale: Position; | |
["stroke-width"]: number; | |
}; | |
export interface StaffElement extends Partial<StaffFields> { | |
type: string, | |
[key: string]: ElementValue, | |
}; | |
export type HashTable = {[key: string]: StaffElement}; | |
interface StaffElementData extends Partial<StaffFields> { | |
hash?: string; | |
identity: StaffElement; | |
}; | |
export interface StaffAttributes | |
{ | |
staffSize: number; | |
}; | |
const normalizeElement = (elem: StaffElement, attributes: StaffAttributes): StaffElementData => { | |
const data: StaffElementData = {x: null, y: null, identity: {type: elem.type}}; | |
const basicSW1 = sizeToStrokeWidth1(attributes.staffSize); | |
const basicSW2 = sizeToStrokeWidth2(attributes.staffSize); | |
switch (elem.type) { | |
case "a": | |
case "style": | |
return null; | |
case "text": | |
data.x = elem.transform.translate.x; | |
data.y = elem.transform.translate.y; | |
data.href = elem.href; | |
data.identity.text = elem.text; | |
data.identity.color = elem.color; | |
data.identity["font-size"] = elem["font-size"]; | |
data.identity["font-weight"] = elem["font-weight"]; | |
data.identity["font-style"] = elem["font-style"]; | |
data.identity["text-anchor"] = elem["text-anchor"]; | |
break; | |
case "line": | |
data.x = elem.x1 + elem.transform.translate.x; | |
data.y = elem.y1 + elem.transform.translate.y; | |
data.href = elem.href; | |
data.identity.width = roundNumber((elem.x2 - elem.x1) * elem.transform.scale.x, SIZE_PRECISION); | |
data.identity.height = roundNumber((elem.y2 - elem.y1) * elem.transform.scale.y, SIZE_PRECISION); | |
data.identity["stroke-width"] = elem["stroke-width"]; | |
data.identity["stroke-dasharray"] = elem["stroke-dasharray"]; | |
if (data.identity.width === 0 && data.identity.height === 0) | |
return null; | |
break; | |
case "rect": | |
if (!elem.transform) { | |
data.x = elem.x; | |
data.y = elem.y; | |
data.identity.width = elem.width; | |
data.identity.height = elem.height; | |
data.identity.rw = roundNumber(elem.width, SIZE_PRECISION); | |
data.identity.rh = roundNumber(elem.height, SIZE_PRECISION); | |
} | |
else { | |
data.x = elem.x + elem.transform.translate.x; | |
data.y = elem.y + elem.transform.translate.y; | |
data.identity.width = elem.width; | |
data.identity.height = elem.height; | |
data.identity.rw = roundNumber(elem.width * elem.transform.scale.x, SIZE_PRECISION); | |
data.identity.rh = roundNumber(elem.height * elem.transform.scale.y, SIZE_PRECISION); | |
} | |
break; | |
case "path": | |
data.x = elem.transform.translate.x; | |
data.y = elem.transform.translate.y; | |
data.href = elem.href; | |
data.identity.scale = elem.transform.scale; | |
data.identity.d = elem.d; | |
data.identity["stroke-width"] = elem["stroke-width"]; | |
break; | |
case "polygon": | |
data.x = elem.transform.translate.x; | |
data.y = elem.transform.translate.y; | |
data.href = elem.href; | |
data.identity.scale = elem.transform.scale; | |
//data.identity.points = elem.points.split(" ").map(x => roundNumber(Number(x), POS_PRECISION)).join(" "); | |
data.identity.points = elem.points; | |
data.identity["stroke-width"] = elem["stroke-width"]; | |
break; | |
default: | |
console.warn("unexpected element type:", elem.type, elem); | |
return null; | |
} | |
// round position | |
data.rx = roundNumber(data.x, POS_PRECISION); | |
data.ry = roundNumber(data.y, POS_PRECISION); | |
if (data.identity) { | |
if (data.identity["stroke-width"]) | |
data.sw = roundNumber(data.identity["stroke-width"] / basicSW1, STROKE_PRECISION); | |
else if (data.identity.width && data.identity.height) { | |
const strokeWidth = Math.min(data.identity.width, data.identity.height); | |
if (strokeWidth < 2) { | |
data.sw = roundNumber(strokeWidth / basicSW1, STROKE_PRECISION); | |
if (data.identity.height < data.identity.width) | |
data.sw2 = roundNumber(strokeWidth / basicSW2, STROKE_PRECISION); | |
} | |
} | |
} | |
data.hash = identityHash(data.identity); | |
return data; | |
}; | |
// TODO: consider split arc linking line into 2 parts | |
const tokenizeElements = (elements: StaffElement[], attributes: StaffAttributes, logger: LogRecorder): { | |
tokens: StaffToken[], | |
hashTable: HashTable, | |
} => { | |
const es = elements.map(e => normalizeElement(e, attributes)).filter(e => e); | |
//logger.append("tokenizeElements", {elementCount: es.length}); | |
const hashTable = {}; | |
for (const elem of es) | |
hashTable[elem.hash] = elem.identity; | |
const tokens = es.map(elem => { | |
const {x, y, rx, ry, sw, href, hash} = elem; | |
return new StaffToken({ | |
x, y, rx, ry, sw, href, hash, | |
...symbolize(elem), | |
}); | |
}); | |
const nonsymbolTokens = tokens | |
.filter(token => !token.symbol) | |
.map(token => ({ | |
...pick(token, ["x", "y", "rx", "ry", "sw", "href"]), | |
identity: hashTable[token.hash], | |
})); | |
if (nonsymbolTokens.length) | |
logger.append("tokenizeElements.nonsymbolTokens", nonsymbolTokens); | |
return {tokens, hashTable}; | |
}; | |
export default tokenizeElements; | |