UNPKG

12.3 kBJavaScriptView Raw
1const { cagColor, cssStyle, css2cag } = require('./helpers')
2const { pxPmm } = require('./constants')
3
4const svgCore = function (obj, element) {
5 if ('ID' in element) { obj.id = element.ID }
6 if ('position' in element) { obj.position = element.position }
7}
8
9const svgPresentation = function (obj, element) {
10 // presentation attributes for all
11 if ('DISPLAY' in element) { obj.visible = element.DISPLAY }
12 // presentation attributes for solids
13 if ('COLOR' in element) { obj.fill = cagColor(element.COLOR); obj.stroke = obj.fill }
14 if ('OPACITY' in element) { obj.opacity = element.OPACITY }
15 if ('FILL' in element) {
16 obj.fill = cagColor(element.FILL)
17 } else {
18 const s = cssStyle(element, 'fill')
19 if (s) {
20 obj.fill = cagColor(s)
21 }
22 }
23 if ('FILL-OPACITY' in element) { obj.opacity = element['FILL-OPACITY'] }
24 // presentation attributes for lines
25 if ('STROKE-WIDTH' in element) {
26 obj.strokeWidth = element['STROKE-WIDTH']
27 } else {
28 const sw = cssStyle(element, 'stroke-width')
29 if (sw) {
30 obj.strokeWidth = sw
31 }
32 }
33 if ('STROKE' in element) {
34 obj.stroke = cagColor(element.STROKE)
35 } else {
36 const s = cssStyle(element, 'stroke')
37 if (s) {
38 obj.stroke = cagColor(s)
39 }
40 }
41 if ('STROKE-OPACITY' in element) { obj.strokeOpacity = element['STROKE-OPACITY'] }
42}
43
44const svgTransforms = function (cag, element) {
45 let list = null
46 if ('TRANSFORM' in element) {
47 list = element.TRANSFORM
48 } else {
49 const s = cssStyle(element, 'transform')
50 if (s) { list = s }
51 }
52 if (list !== null) {
53 cag.transforms = []
54 const exp = new RegExp('\\w+\\(.+\\)', 'i')
55 let v = exp.exec(list)
56 while (v !== null) {
57 const s = exp.lastIndex
58 const e = list.indexOf(')') + 1
59 let t = list.slice(s, e) // the transform
60 t = t.trim()
61 // add the transform to the CAG
62 // which are applied in the order provided
63 const n = t.slice(0, t.indexOf('('))
64 let a = t.slice(t.indexOf('(') + 1, t.indexOf(')')).trim()
65 if (a.indexOf(',') > 0) { a = a.split(',') } else { a = a.split(' ') }
66 let o
67 switch (n) {
68 case 'translate':
69 if (a.length === 1) a.push(0) // as per SVG
70 o = { translate: [a[0], a[1]] }
71 cag.transforms.push(o)
72 break
73 case 'scale':
74 if (a.length === 1) a.push(a[0]) // as per SVG
75 o = { scale: [a[0], a[1]] }
76 cag.transforms.push(o)
77 break
78 case 'rotate':
79 o = { rotate: a }
80 cag.transforms.push(o)
81 break
82 // case 'matrix':
83 // case 'skewX':
84 // case 'skewY':
85 default:
86 break
87 }
88 // shorten the list and continue
89 list = list.slice(e, list.length)
90 v = exp.exec(list)
91 }
92 }
93}
94
95const svgSvg = function (element, { customPxPmm }) {
96 // default SVG with no viewport
97 const obj = { type: 'svg', x: 0, y: 0, width: '100%', height: '100%', strokeWidth: '1' }
98
99 // default units per mm
100 obj.unitsPmm = [pxPmm, pxPmm]
101
102 if ('PXPMM' in element) {
103 // WOW! a supplied value for pixels per milimeter!!!
104 obj.pxPmm = element.PXPMM
105 obj.unitsPmm = [obj.pxPmm, obj.pxPmm]
106 }
107 if ('WIDTH' in element) { obj.width = element.WIDTH }
108 if ('HEIGHT' in element) { obj.height = element.HEIGHT }
109 if ('VIEWBOX' in element) {
110 const list = element.VIEWBOX.trim()
111 const exp = new RegExp('([\\d\\.\\-]+)[\\s,]+([\\d\\.\\-]+)[\\s,]+([\\d\\.\\-]+)[\\s,]+([\\d\\.\\-]+)', 'i')
112 const v = exp.exec(list)
113 if (v !== null) {
114 obj.viewX = parseFloat(v[1])
115 obj.viewY = parseFloat(v[2])
116 obj.viewW = parseFloat(v[3])
117 obj.viewH = parseFloat(v[4])
118 }
119 // apply the viewbox
120 if (obj.width.indexOf('%') < 0) {
121 // calculate a scaling from width and viewW
122 let s = css2cag(obj.width, customPxPmm) // width in millimeters
123 s = obj.viewW / s
124 // scale the default units
125 // obj.unitsPmm[0] = obj.unitsPmm[0] * s;
126 obj.unitsPmm[0] = s
127 } else {
128 // scale the default units by the width (%)
129 const u = obj.unitsPmm[0] * (parseFloat(obj.width) / 100.0)
130 obj.unitsPmm[0] = u
131 }
132 if (obj.height.indexOf('%') < 0) {
133 // calculate a scaling from height and viewH
134 let s = css2cag(obj.height, pxPmm) // height in millimeters
135 s = obj.viewH / s
136 // scale the default units
137 // obj.unitsPmm[1] = obj.unitsPmm[1] * s;
138 obj.unitsPmm[1] = s
139 } else {
140 // scale the default units by the width (%)
141 const u = obj.unitsPmm[1] * (parseFloat(obj.height) / 100.0)
142 obj.unitsPmm[1] = u
143 }
144 } else {
145 obj.viewX = 0
146 obj.viewY = 0
147 obj.viewW = 1920 / obj.unitsPmm[0] // average screen size / pixels per unit
148 obj.viewH = 1080 / obj.unitsPmm[1] // average screen size / pixels per unit
149 }
150 obj.viewP = Math.sqrt((obj.viewW * obj.viewW) + (obj.viewH * obj.viewH)) / Math.SQRT2
151
152 // core attributes
153 svgCore(obj, element)
154 // presentation attributes
155 svgPresentation(obj, element)
156
157 obj.objects = []
158 return obj
159}
160
161const svgEllipse = function (element) {
162 const obj = { type: 'ellipse', cx: '0', cy: '0', rx: '0', ry: '0' }
163 if ('CX' in element) { obj.cx = element.CX }
164 if ('CY' in element) { obj.cy = element.CY }
165 if ('RX' in element) { obj.rx = element.RX }
166 if ('RY' in element) { obj.ry = element.RY }
167 // transforms
168 svgTransforms(obj, element)
169 // core attributes
170 svgCore(obj, element)
171 // presentation attributes
172 svgPresentation(obj, element)
173 return obj
174}
175
176const svgLine = function (element) {
177 const obj = { type: 'line', x1: '0', y1: '0', x2: '0', y2: '0' }
178 if ('X1' in element) { obj.x1 = element.X1 }
179 if ('Y1' in element) { obj.y1 = element.Y1 }
180 if ('X2' in element) { obj.x2 = element.X2 }
181 if ('Y2' in element) { obj.y2 = element.Y2 }
182 // transforms
183 svgTransforms(obj, element)
184 // core attributes
185 svgCore(obj, element)
186 // presentation attributes
187 svgPresentation(obj, element)
188 return obj
189}
190
191const svgListOfPoints = function (list) {
192 const points = []
193 const exp = new RegExp('([\\d\\-\\+\\.]+)[\\s,]+([\\d\\-\\+\\.]+)[\\s,]*', 'i')
194 list = list.trim()
195 let v = exp.exec(list)
196 while (v !== null) {
197 let point = v[0]
198 const next = exp.lastIndex + point.length
199 point = { x: v[1], y: v[2] }
200 points.push(point)
201 list = list.slice(next, list.length)
202 v = exp.exec(list)
203 }
204 return points
205}
206
207const svgPolyline = function (element) {
208 const obj = { type: 'polyline' }
209 // transforms
210 svgTransforms(obj, element)
211 // core attributes
212 svgCore(obj, element)
213 // presentation attributes
214 svgPresentation(obj, element)
215
216 if ('POINTS' in element) {
217 obj.points = svgListOfPoints(element.POINTS)
218 }
219 return obj
220}
221
222const svgPolygon = function (element) {
223 const obj = { type: 'polygon' }
224 // transforms
225 svgTransforms(obj, element)
226 // core attributes
227 svgCore(obj, element)
228 // presentation attributes
229 svgPresentation(obj, element)
230
231 if ('POINTS' in element) {
232 obj.points = svgListOfPoints(element.POINTS)
233 }
234 return obj
235}
236
237const svgRect = function (element) {
238 const obj = { type: 'rect', x: '0', y: '0', rx: '0', ry: '0', width: '0', height: '0' }
239
240 if ('X' in element) { obj.x = element.X }
241 if ('Y' in element) { obj.y = element.Y }
242 if ('RX' in element) {
243 obj.rx = element.RX
244 if (!('RY' in element)) { obj.ry = obj.rx } // by SVG specification
245 }
246 if ('RY' in element) {
247 obj.ry = element.RY
248 if (!('RX' in element)) { obj.rx = obj.ry } // by SVG specification
249 }
250 if (obj.rx !== obj.ry) {
251 console.log('Warning: Unsupported RECT with RX and RY radius')
252 }
253 if ('WIDTH' in element) { obj.width = element.WIDTH }
254 if ('HEIGHT' in element) { obj.height = element.HEIGHT }
255 // transforms
256 svgTransforms(obj, element)
257 // core attributes
258 svgCore(obj, element)
259 // presentation attributes
260 svgPresentation(obj, element)
261 return obj
262}
263
264const svgCircle = function (element) {
265 const obj = { type: 'circle', x: '0', y: '0', radius: '0' }
266
267 if ('CX' in element) { obj.x = element.CX }
268 if ('CY' in element) { obj.y = element.CY }
269 if ('R' in element) { obj.radius = element.R }
270 // transforms
271 svgTransforms(obj, element)
272 // core attributes
273 svgCore(obj, element)
274 // presentation attributes
275 svgPresentation(obj, element)
276 return obj
277}
278
279const svgGroup = function (element) {
280 const obj = { type: 'group' }
281 // transforms
282 svgTransforms(obj, element)
283 // core attributes
284 svgCore(obj, element)
285 // presentation attributes
286 svgPresentation(obj, element)
287
288 if ('X' in element || 'Y' in element) {
289 let x = '0'
290 let y = '0'
291 if ('X' in element) x = element.X
292 if ('Y' in element) y = element.Y
293 if (!('transforms' in obj)) obj.transforms = []
294 const o = { translate: [x, y] }
295 obj.transforms.push(o)
296 }
297
298 obj.objects = []
299 return obj
300}
301
302//
303// Convert the PATH element into object representation
304//
305const svgPath = function (element) {
306 const obj = { type: 'path' }
307 // transforms
308 svgTransforms(obj, element)
309 // core attributes
310 svgCore(obj, element)
311 // presentation attributes
312 svgPresentation(obj, element)
313
314 obj.commands = []
315 if ('D' in element) {
316 let co = null // current command
317 let bf = ''
318
319 let i = 0
320 const l = element.D.length
321 while (i < l) {
322 const c = element.D[i]
323 switch (c) {
324 // numbers
325 // FIXME support E notation numbers
326 case '-':
327 if (bf.length > 0) {
328 co.p.push(bf)
329 bf = ''
330 }
331 bf += c
332 break
333 case '.':
334 if (bf.length > 0) {
335 if (bf.indexOf('.') >= 0) {
336 co.p.push(bf)
337 bf = ''
338 }
339 }
340 bf += c
341 break
342 case '0':
343 case '1':
344 case '2':
345 case '3':
346 case '4':
347 case '5':
348 case '6':
349 case '7':
350 case '8':
351 case '9':
352 bf += c
353 break
354 // commands
355 case 'a':
356 case 'A':
357 case 'c':
358 case 'C':
359 case 'h':
360 case 'H':
361 case 'l':
362 case 'L':
363 case 'v':
364 case 'V':
365 case 'm':
366 case 'M':
367 case 'q':
368 case 'Q':
369 case 's':
370 case 'S':
371 case 't':
372 case 'T':
373 case 'z':
374 case 'Z':
375 if (co !== null) {
376 if (bf.length > 0) {
377 co.p.push(bf)
378 bf = ''
379 }
380 obj.commands.push(co)
381 }
382 co = { c: c, p: [] }
383 break
384 // white space
385 case ',':
386 case ' ':
387 case '\n':
388 if (co !== null) {
389 if (bf.length > 0) {
390 co.p.push(bf)
391 bf = ''
392 }
393 }
394 break
395 default:
396 break
397 }
398 i++
399 }
400 if (i === l && co !== null) {
401 if (bf.length > 0) {
402 co.p.push(bf)
403 }
404 obj.commands.push(co)
405 }
406 }
407 return obj
408}
409
410// generate GROUP with attributes from USE element
411// - expect X,Y,HEIGHT,WIDTH,XLINK:HREF
412// - append translate(x,y) if X,Y available
413// deep clone the referenced OBJECT and add to group
414// - clone using JSON.parse(JSON.stringify(obj))
415const svgUse = function (element, { svgObjects }) {
416 const obj = { type: 'group' }
417 // transforms
418 svgTransforms(obj, element)
419 // core attributes
420 svgCore(obj, element)
421 // presentation attributes
422 svgPresentation(obj, element)
423
424 if ('X' in element || 'Y' in element) {
425 let x = '0'
426 let y = '0'
427 if ('X' in element) x = element.X
428 if ('Y' in element) y = element.Y
429 if (!('transforms' in obj)) obj.transforms = []
430 const o = { translate: [x, y] }
431 obj.transforms.push(o)
432 }
433
434 obj.objects = []
435 if ('XLINK:HREF' in element) {
436 // lookup the named object
437 let ref = element['XLINK:HREF']
438 if (ref[0] === '#') { ref = ref.slice(1, ref.length) }
439 if (svgObjects[ref] !== undefined) {
440 ref = svgObjects[ref]
441 ref = JSON.parse(JSON.stringify(ref))
442 obj.objects.push(ref)
443 }
444 }
445 return obj
446}
447
448module.exports = {
449 svgCore,
450 svgPresentation,
451 svgSvg,
452 svgRect,
453 svgCircle,
454 svgEllipse,
455 svgLine,
456 svgPolyline,
457 svgPolygon,
458 svgGroup,
459 svgPath,
460 svgUse
461}