mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-31 10:54:33 +01:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			dependabot
			...
			fix-svg-cr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d308451584 | ||
|   | f444383b46 | 
| @@ -4557,9 +4557,9 @@ http-parser-js@>=0.5.1: | ||||
|   integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== | ||||
|  | ||||
| http-proxy-middleware@^2.0.3: | ||||
|   version "2.0.7" | ||||
|   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" | ||||
|   integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== | ||||
|   version "2.0.6" | ||||
|   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" | ||||
|   integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== | ||||
|   dependencies: | ||||
|     "@types/http-proxy" "^1.17.8" | ||||
|     http-proxy "^1.18.1" | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import { | ||||
|   SVG_NS, | ||||
| } from "../constants"; | ||||
| import { normalizeLink, toValidURL } from "../data/url"; | ||||
| import { getElementAbsoluteCoords } from "../element"; | ||||
| import { getElementAbsoluteCoords, hashString } from "../element"; | ||||
| import { | ||||
|   createPlaceholderEmbeddableLabel, | ||||
|   getEmbedLink, | ||||
| @@ -411,7 +411,26 @@ const renderElementToSvg = ( | ||||
|       const fileData = | ||||
|         isInitializedImageElement(element) && files[element.fileId]; | ||||
|       if (fileData) { | ||||
|         const symbolId = `image-${fileData.id}`; | ||||
|         // TODO set to `false` before merging | ||||
|         const { reuseImages = true } = renderConfig; | ||||
|  | ||||
|         let symbolId = `image-${fileData.id}`; | ||||
|  | ||||
|         let uncroppedWidth = element.width; | ||||
|         let uncroppedHeight = element.height; | ||||
|         if (element.crop) { | ||||
|           ({ width: uncroppedWidth, height: uncroppedHeight } = | ||||
|             getUncroppedWidthAndHeight(element)); | ||||
|  | ||||
|           symbolId = `image-crop-${fileData.id}-${hashString( | ||||
|             `${uncroppedWidth}x${uncroppedHeight}`, | ||||
|           )}`; | ||||
|         } | ||||
|  | ||||
|         if (!reuseImages) { | ||||
|           symbolId = `image-${element.id}`; | ||||
|         } | ||||
|  | ||||
|         let symbol = svgRoot.querySelector(`#${symbolId}`); | ||||
|         if (!symbol) { | ||||
|           symbol = svgRoot.ownerDocument!.createElementNS(SVG_NS, "symbol"); | ||||
| @@ -421,18 +440,7 @@ const renderElementToSvg = ( | ||||
|           image.setAttribute("href", fileData.dataURL); | ||||
|           image.setAttribute("preserveAspectRatio", "none"); | ||||
|  | ||||
|           if (element.crop) { | ||||
|             const { width: uncroppedWidth, height: uncroppedHeight } = | ||||
|               getUncroppedWidthAndHeight(element); | ||||
|  | ||||
|             symbol.setAttribute( | ||||
|               "viewBox", | ||||
|               `${ | ||||
|                 element.crop.x / (element.crop.naturalWidth / uncroppedWidth) | ||||
|               } ${ | ||||
|                 element.crop.y / (element.crop.naturalHeight / uncroppedHeight) | ||||
|               } ${width} ${height}`, | ||||
|             ); | ||||
|           if (element.crop || !reuseImages) { | ||||
|             image.setAttribute("width", `${uncroppedWidth}`); | ||||
|             image.setAttribute("height", `${uncroppedHeight}`); | ||||
|           } else { | ||||
| @@ -456,8 +464,23 @@ const renderElementToSvg = ( | ||||
|           use.setAttribute("filter", IMAGE_INVERT_FILTER); | ||||
|         } | ||||
|  | ||||
|         use.setAttribute("width", `${width}`); | ||||
|         use.setAttribute("height", `${height}`); | ||||
|         let normalizedCropX = 0; | ||||
|         let normalizedCropY = 0; | ||||
|  | ||||
|         if (element.crop) { | ||||
|           const { width: uncroppedWidth, height: uncroppedHeight } = | ||||
|             getUncroppedWidthAndHeight(element); | ||||
|           normalizedCropX = | ||||
|             element.crop.x / (element.crop.naturalWidth / uncroppedWidth); | ||||
|           normalizedCropY = | ||||
|             element.crop.y / (element.crop.naturalHeight / uncroppedHeight); | ||||
|         } | ||||
|  | ||||
|         const adjustedCenterX = cx + normalizedCropX; | ||||
|         const adjustedCenterY = cy + normalizedCropY; | ||||
|  | ||||
|         use.setAttribute("width", `${width + normalizedCropX}`); | ||||
|         use.setAttribute("height", `${height + normalizedCropY}`); | ||||
|         use.setAttribute("opacity", `${opacity}`); | ||||
|  | ||||
|         // We first apply `scale` transforms (horizontal/vertical mirroring) | ||||
| @@ -467,21 +490,43 @@ const renderElementToSvg = ( | ||||
|         // the transformations correctly (the transform-origin was not being | ||||
|         // applied correctly). | ||||
|         if (element.scale[0] !== 1 || element.scale[1] !== 1) { | ||||
|           const translateX = element.scale[0] !== 1 ? -width : 0; | ||||
|           const translateY = element.scale[1] !== 1 ? -height : 0; | ||||
|           use.setAttribute( | ||||
|             "transform", | ||||
|             `scale(${element.scale[0]}, ${element.scale[1]}) translate(${translateX} ${translateY})`, | ||||
|             `translate(${adjustedCenterX} ${adjustedCenterY}) scale(${ | ||||
|               element.scale[0] | ||||
|             } ${ | ||||
|               element.scale[1] | ||||
|             }) translate(${-adjustedCenterX} ${-adjustedCenterY})`, | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         const g = svgRoot.ownerDocument!.createElementNS(SVG_NS, "g"); | ||||
|  | ||||
|         if (element.crop) { | ||||
|           const mask = svgRoot.ownerDocument!.createElementNS(SVG_NS, "mask"); | ||||
|           mask.setAttribute("id", `mask-image-crop-${element.id}`); | ||||
|           mask.setAttribute("fill", "#fff"); | ||||
|           const maskRect = svgRoot.ownerDocument!.createElementNS( | ||||
|             SVG_NS, | ||||
|             "rect", | ||||
|           ); | ||||
|  | ||||
|           maskRect.setAttribute("x", `${normalizedCropX}`); | ||||
|           maskRect.setAttribute("y", `${normalizedCropY}`); | ||||
|           maskRect.setAttribute("width", `${width}`); | ||||
|           maskRect.setAttribute("height", `${height}`); | ||||
|  | ||||
|           mask.appendChild(maskRect); | ||||
|           root.appendChild(mask); | ||||
|           g.setAttribute("mask", `url(#${mask.id})`); | ||||
|         } | ||||
|  | ||||
|         g.appendChild(use); | ||||
|         g.setAttribute( | ||||
|           "transform", | ||||
|           `translate(${offsetX || 0} ${ | ||||
|             offsetY || 0 | ||||
|           }) rotate(${degree} ${cx} ${cy})`, | ||||
|           `translate(${offsetX - normalizedCropX} ${ | ||||
|             offsetY - normalizedCropY | ||||
|           }) rotate(${degree} ${adjustedCenterX} ${adjustedCenterY})`, | ||||
|         ); | ||||
|  | ||||
|         if (element.roundness) { | ||||
|   | ||||
| @@ -46,6 +46,13 @@ export type SVGRenderConfig = { | ||||
|   frameRendering: AppState["frameRendering"]; | ||||
|   canvasBackgroundColor: AppState["viewBackgroundColor"]; | ||||
|   embedsValidationStatus: EmbedsValidationStatus; | ||||
|   /** | ||||
|    * whether to attempt to reuse images as much as possible through symbols | ||||
|    * (reduces SVG size, but may be incompoatible with some SVG renderers) | ||||
|    * | ||||
|    * @default true | ||||
|    */ | ||||
|   reuseImages?: boolean; | ||||
| }; | ||||
|  | ||||
| export type InteractiveCanvasRenderConfig = { | ||||
|   | ||||
| @@ -10,5 +10,5 @@ exports[`export > exporting svg containing transformed images > svg export outpu | ||||
|     </style> | ||||
|      | ||||
|   </defs> | ||||
|   <clipPath id="image-clipPath-id1" data-id="id1"><rect width="100" height="100" rx="25" ry="25"></rect></clipPath><g transform="translate(30.710678118654755 30.710678118654755) rotate(315 50 50)" clip-path="url(#image-clipPath-id1)" data-id="id1"><use href="#image-file_A" width="100" height="100" opacity="1"></use></g><clipPath id="image-clipPath-id2" data-id="id2"><rect width="50" height="50" rx="12.5" ry="12.5"></rect></clipPath><g transform="translate(130.71067811865476 30.710678118654755) rotate(45 25 25)" clip-path="url(#image-clipPath-id2)" data-id="id2"><use href="#image-file_A" width="50" height="50" opacity="1" transform="scale(-1, 1) translate(-50 0)"></use></g><clipPath id="image-clipPath-id3" data-id="id3"><rect width="100" height="100" rx="25" ry="25"></rect></clipPath><g transform="translate(30.710678118654755 130.71067811865476) rotate(45 50 50)" clip-path="url(#image-clipPath-id3)" data-id="id3"><use href="#image-file_A" width="100" height="100" opacity="1" transform="scale(1, -1) translate(0 -100)"></use></g><clipPath id="image-clipPath-id4" data-id="id4"><rect width="50" height="50" rx="12.5" ry="12.5"></rect></clipPath><g transform="translate(130.71067811865476 130.71067811865476) rotate(315 25 25)" clip-path="url(#image-clipPath-id4)" data-id="id4"><use href="#image-file_A" width="50" height="50" opacity="1" transform="scale(-1, -1) translate(-50 -50)"></use></g></svg>" | ||||
|   <clipPath id="image-clipPath-id1" data-id="id1"><rect width="100" height="100" rx="25" ry="25"></rect></clipPath><g transform="translate(30.710678118654755 30.710678118654755) rotate(315 50 50)" clip-path="url(#image-clipPath-id1)" data-id="id1"><use href="#image-file_A" width="100" height="100" opacity="1"></use></g><clipPath id="image-clipPath-id2" data-id="id2"><rect width="50" height="50" rx="12.5" ry="12.5"></rect></clipPath><g transform="translate(130.71067811865476 30.710678118654755) rotate(45 25 25)" clip-path="url(#image-clipPath-id2)" data-id="id2"><use href="#image-file_A" width="50" height="50" opacity="1" transform="translate(25 25) scale(-1 1) translate(-25 -25)"></use></g><clipPath id="image-clipPath-id3" data-id="id3"><rect width="100" height="100" rx="25" ry="25"></rect></clipPath><g transform="translate(30.710678118654755 130.71067811865476) rotate(45 50 50)" clip-path="url(#image-clipPath-id3)" data-id="id3"><use href="#image-file_A" width="100" height="100" opacity="1" transform="translate(50 50) scale(1 -1) translate(-50 -50)"></use></g><clipPath id="image-clipPath-id4" data-id="id4"><rect width="50" height="50" rx="12.5" ry="12.5"></rect></clipPath><g transform="translate(130.71067811865476 130.71067811865476) rotate(315 25 25)" clip-path="url(#image-clipPath-id4)" data-id="id4"><use href="#image-file_A" width="50" height="50" opacity="1" transform="translate(25 25) scale(-1 -1) translate(-25 -25)"></use></g></svg>" | ||||
| `; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user