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