update
parent
eb4a0119e0
commit
59608c7409
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* {}
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2024/02/05 16:19:45
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DOMParser } from '@xmldom/xmldom'
|
||||||
|
import svgpath from 'svgpath'
|
||||||
|
|
||||||
|
import { PATH_DECIMAL } from '../config.js'
|
||||||
|
import { normalizePath, pathify } from './path.js'
|
||||||
|
import { generateAmendTrans } from './view_box.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻转svgpath
|
||||||
|
* @param {string} path path序列
|
||||||
|
* @return {string} 反转后的path序列
|
||||||
|
*/
|
||||||
|
export function reversal(path) {
|
||||||
|
return svgpath(path).scale(1, -1).round(PATH_DECIMAL).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换svg为一个path,并且按照目标高度,偏移进行拉伸
|
||||||
|
* @param {string} svg 原始svg
|
||||||
|
* @param {object} options.targetHeight 目标高度
|
||||||
|
* @return {object} path序列和viewbox
|
||||||
|
*/
|
||||||
|
export function normalizeSvg(svg, options) {
|
||||||
|
let svgDocNode = new DOMParser().parseFromString(
|
||||||
|
pathify(svg),
|
||||||
|
'application/xml'
|
||||||
|
)
|
||||||
|
let svgNode = svgDocNode.getElementsByTagName('svg')[0]
|
||||||
|
|
||||||
|
//解决所有的变换,生成一个path
|
||||||
|
let path = normalizePath(svgNode)
|
||||||
|
|
||||||
|
let trans = svgpath(path)
|
||||||
|
|
||||||
|
//根据目标viewbox进行变换
|
||||||
|
let targetHeight = options.targetHeight
|
||||||
|
|
||||||
|
if (targetHeight) {
|
||||||
|
let viewObj = generateAmendTrans(svgNode, targetHeight)
|
||||||
|
viewObj.transforms.forEach(function (viewTrans) {
|
||||||
|
trans[viewTrans[0]].apply(trans, viewTrans[1])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
viewbox: viewObj.targetViewbox,
|
||||||
|
path: trans.round(PATH_DECIMAL).toString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
/**
|
||||||
|
* {}
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2024/02/05 16:12:14
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { DOMParser, XMLSerializer } from '@xmldom/xmldom'
|
||||||
|
import svgpath from 'svgpath'
|
||||||
|
|
||||||
|
const shape = {
|
||||||
|
$translatePath(convertObj, attributes) {
|
||||||
|
let data = {}
|
||||||
|
attributes.forEach(function (attribute) {
|
||||||
|
data[attribute.name] = attribute.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return convertObj.template(data)
|
||||||
|
},
|
||||||
|
generatePathNode(doc, node) {
|
||||||
|
let convertObj, attributes, path, tagName
|
||||||
|
|
||||||
|
tagName = String(node.tagName).toLowerCase()
|
||||||
|
//polyline与polygon是同一个东西
|
||||||
|
if (tagName === 'polyline') {
|
||||||
|
tagName = 'polygon'
|
||||||
|
}
|
||||||
|
|
||||||
|
convertObj = shape[tagName]
|
||||||
|
if (!convertObj) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path = shape.$translatePath(convertObj, node.attributes)
|
||||||
|
if (!path) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newPathNode = doc.createElement('path')
|
||||||
|
//赋值d属性
|
||||||
|
newPathNode.setAttribute('d', path)
|
||||||
|
//保留其他属性
|
||||||
|
node.attributes.forEach(function (attribute) {
|
||||||
|
if (!convertObj.attrs.includes(attribute.name)) {
|
||||||
|
newPathNode.setAttribute(attribute.name, attribute.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return newPathNode
|
||||||
|
},
|
||||||
|
rect: {
|
||||||
|
attrs: ['x', 'y', 'width', 'height', 'rx', 'ry'],
|
||||||
|
template(data) {
|
||||||
|
// 需要做一个保护,如果没有写属性值那么默认为0
|
||||||
|
if (!data.x) {
|
||||||
|
data.x = '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.y) {
|
||||||
|
data.y = '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.rx && data.ry) {
|
||||||
|
data.rx = data.ry
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.ry && data.rx) {
|
||||||
|
data.ry = data.rx
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.ry && !data.rx) {
|
||||||
|
data.ry = data.rx = '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `M${data.x},${data.y}m${data.rx},0l${data.width - 2 * data.rx},0q${
|
||||||
|
data.rx
|
||||||
|
},0 ${data.rx},${data.ry}l0,${data.height - 2 * data.ry}q0,${
|
||||||
|
data.ry
|
||||||
|
} ${-data.rx},${data.ry}l${
|
||||||
|
-data.width + 2 * data.rx
|
||||||
|
},0q${-data.rx},0 ${-data.rx},${-data.ry}l0,${
|
||||||
|
-data.height + 2 * data.ry
|
||||||
|
}q0,${-data.ry},${data.rx},${-data.ry}Z`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
circle: {
|
||||||
|
attrs: ['cx', 'cy', 'r'],
|
||||||
|
template(data) {
|
||||||
|
return `M${data.cx},${data.cy}m${-data.r},0a${data.r},${data.r},0,1,0,${
|
||||||
|
data.r * 2
|
||||||
|
},0a${data.r},${data.r},0,1,0,-${data.r * 2},0Z`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
attrs: ['x1', 'y1', 'x2', 'y2'],
|
||||||
|
template(data) {
|
||||||
|
return `M${data.x1},${data.y1}L${data.x2},${data.y2}L${data.x1},${data.y1}Z`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ellipse: {
|
||||||
|
attrs: ['cx', 'cy', 'rx', 'ry'],
|
||||||
|
template(data) {
|
||||||
|
return `M${data.cx - data.rx},${data.cy}a${data.rx},${data.ry},0,1,0,${
|
||||||
|
data.rx * 2
|
||||||
|
},0a${data.rx},${data.ry},0,1,0,-${data.rx * 2},0Z`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
polygon: {
|
||||||
|
attrs: ['points'],
|
||||||
|
template(data) {
|
||||||
|
data.points = data.points.replace(/(^\s*)|(\s*$)/g, '')
|
||||||
|
let points = data.points.split(' ')
|
||||||
|
let p = ''
|
||||||
|
points.forEach(function (point, index) {
|
||||||
|
if (index === 0) {
|
||||||
|
p += 'M' + point
|
||||||
|
} else {
|
||||||
|
p += 'L' + point
|
||||||
|
}
|
||||||
|
})
|
||||||
|
p += 'Z'
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePath(path, transforms) {
|
||||||
|
let t = svgpath(path).abs()
|
||||||
|
transforms.forEach(it => t.transform(it))
|
||||||
|
return t.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePath(node, transforms) {
|
||||||
|
let path = ''
|
||||||
|
let newTransForms = transforms ? [...transforms] : []
|
||||||
|
|
||||||
|
if (node.getAttribute && node.getAttribute('transform')) {
|
||||||
|
newTransForms.push(node.getAttribute('transform'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.hasChildNodes() && node.tagName === 'path') {
|
||||||
|
path = generatePath(node.getAttribute('d'), newTransForms)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.hasChildNodes()) {
|
||||||
|
node.childNodes.forEach(function (childNode) {
|
||||||
|
path += normalizePath(childNode, newTransForms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
function transNode(doc, node) {
|
||||||
|
let convertObj, newPathNode
|
||||||
|
|
||||||
|
//基本的图形都是没有子结点的
|
||||||
|
if (!node.hasChildNodes() && node.nodeName !== 'path') {
|
||||||
|
newPathNode = shape.generatePathNode(doc, node)
|
||||||
|
newPathNode && node.parentNode.replaceChild(newPathNode, node)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
node.childNodes.forEach(function (child) {
|
||||||
|
transNode(doc, child)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pathify(svgString) {
|
||||||
|
let doc = new DOMParser().parseFromString(svgString, 'application/xml')
|
||||||
|
|
||||||
|
transNode(doc, doc)
|
||||||
|
|
||||||
|
return new XMLSerializer().serializeToString(doc)
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
/**
|
||||||
|
* {}
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2024/02/05 16:04:06
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getViewBox(svgNode) {
|
||||||
|
if (!svgNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let width, height
|
||||||
|
let viewbox = svgNode.getAttribute('viewBox').replace(',', ' ').split(' ')
|
||||||
|
|
||||||
|
if (viewbox && viewbox.length === 4) {
|
||||||
|
return viewbox.map(v => +v)
|
||||||
|
}
|
||||||
|
|
||||||
|
width = +svgNode.getAttribute('width')
|
||||||
|
height = +svgNode.getAttribute('height')
|
||||||
|
if (width != 0 && height != 0) {
|
||||||
|
return [0, 0, width, height]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getViewPort(svgNode) {
|
||||||
|
let width, height, viewbox
|
||||||
|
width = +svgNode.getAttribute('width')
|
||||||
|
height = +svgNode.getAttribute('height')
|
||||||
|
|
||||||
|
viewbox = svgNode.getAttribute('viewBox').replace(',', ' ').split(' ')
|
||||||
|
if (!width) {
|
||||||
|
width = +viewbox[2]
|
||||||
|
}
|
||||||
|
if (!height) {
|
||||||
|
height = +viewbox[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [width, height]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 targetHeight 缩放 width
|
||||||
|
// 如: normalizeViewport([1024, 2048], 1024)
|
||||||
|
// #=> [0,0,512, 1024]
|
||||||
|
function getTargetViewbox(viewport, targetHeight) {
|
||||||
|
let width = viewport[0]
|
||||||
|
let height = viewport[1]
|
||||||
|
|
||||||
|
if (height != targetHeight) {
|
||||||
|
width = ~~((targetHeight / height) * width)
|
||||||
|
height = targetHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
return [0, 0, width, height]
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeXY(viewbox, targetViewbox) {
|
||||||
|
let x, y, targetX, targetY
|
||||||
|
|
||||||
|
x = viewbox[0]
|
||||||
|
y = viewbox[1]
|
||||||
|
targetX = targetViewbox[0]
|
||||||
|
targetY = targetViewbox[1]
|
||||||
|
//可能会有x,y偏移的情况,所以这边需要做出相应的转换
|
||||||
|
if (x != targetX || y != targetY) {
|
||||||
|
return [['translate', [targetX - x, targetY - y]]]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeWidthHeight(viewbox, targetViewbox) {
|
||||||
|
let width, height, targetWidth, targetHeight, widthScale, heightScale
|
||||||
|
|
||||||
|
width = viewbox[2]
|
||||||
|
height = viewbox[3]
|
||||||
|
targetWidth = targetViewbox[2]
|
||||||
|
targetHeight = targetViewbox[3]
|
||||||
|
|
||||||
|
widthScale = width / targetWidth
|
||||||
|
heightScale = height / targetHeight
|
||||||
|
//比较正常的等比缩放
|
||||||
|
if (widthScale == heightScale) {
|
||||||
|
return [['scale', [1 / widthScale, 1 / widthScale]]]
|
||||||
|
}
|
||||||
|
|
||||||
|
//下面是不等比缩放
|
||||||
|
//不等比缩放后
|
||||||
|
let maxScale = Math.max(widthScale, heightScale)
|
||||||
|
let transforms = [],
|
||||||
|
newWidth,
|
||||||
|
newHeight
|
||||||
|
|
||||||
|
if (widthScale < heightScale) {
|
||||||
|
newWidth = targetWidth * maxScale
|
||||||
|
transforms.push(['translate', [(newWidth - width) / 2, 0]])
|
||||||
|
} else if (widthScale > heightScale) {
|
||||||
|
newHeight = newHeight * maxScale
|
||||||
|
transforms.push(['translate', [0, (newHeight - height) / 2]])
|
||||||
|
}
|
||||||
|
|
||||||
|
transforms.push(['scale', [1 / maxScale, 1 / maxScale]])
|
||||||
|
|
||||||
|
return transforms
|
||||||
|
}
|
||||||
|
|
||||||
|
//返回需要进行的转换数组
|
||||||
|
function normalizeViewBox(viewbox, targetViewbox) {
|
||||||
|
return normalizeXY(viewbox, targetViewbox).concat(
|
||||||
|
normalizeWidthHeight(viewbox, targetViewbox)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateAmendTrans(svgNode, targetHeight) {
|
||||||
|
let viewport = getViewPort(svgNode)
|
||||||
|
let viewbox = getViewBox(svgNode)
|
||||||
|
let targetViewbox = getTargetViewbox(viewport, targetHeight)
|
||||||
|
|
||||||
|
return {
|
||||||
|
transforms: normalizeViewBox(viewbox, targetViewbox),
|
||||||
|
targetViewbox: targetViewbox
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue