master
yutent 2024-02-05 17:10:41 +08:00
parent eb4a0119e0
commit 59608c7409
3 changed files with 352 additions and 0 deletions

55
src/lib/svg/index.js Normal file
View File

@ -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()
}
}

175
src/lib/svg/path.js Normal file
View File

@ -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)
}

122
src/lib/svg/view_box.js Normal file
View File

@ -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
}
}