Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve svg rendering #974

Merged
merged 7 commits into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jsdoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"packages/io/amf-deserializer/src",
"packages/io/amf-serializer",
"packages/io/svg-deserializer/src",
"packages/io/svg-serializer",
"packages/io/x3d-deserializer/src"
],
"includePattern": ".+\\.js(doc|x)?$",
Expand Down
6 changes: 4 additions & 2 deletions packages/io/svg-serializer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ This serializer outputs a 'blobable' array of data from one or more JSCAD geomet
The array of data can either be used to create a Blob (`new Blob(blobable)`), or converted to a Node.js buffer.

The serialization of the following geometries are possible.
- serialization of 2D geometry (geom2) to continous SVG paths
- serialization of 2D paths (path2) to individual SVG paths
- serialization of 2D geometry (geom2) to continous SVG paths, where the color (initial fill) is 'black' if not provided
- serialization of 2D paths (path2) to individual SVG paths, where the color (initial stroke) is 'none' if not provided

In addition, geometries can have special attributes (id, class) which will be passed on to the SVG paths.

## Table of Contents

Expand Down
54 changes: 33 additions & 21 deletions packages/io/svg-serializer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,23 @@ All code released under MIT license
Notes:
1) geom2 conversion to:
SVG GROUP containing a SVG PATH for each outline of the geometry
SVG GROUP containing a continous SVG PATH that contains the outlines of the geometry
2) geom3 conversion to:
none
3) path2 conversion to:
SVG GROUP containing a SVG PATH for each path
*/

/**
* Serializer of JSCAD geometries to SVG elements.
* Serializer of JSCAD geometries to SVG source (XML).
*
* The serialization of the following geometries are possible.
* - serialization of 2D geometry (geom2) to SVG path (a continous path containing the outlines of the geometry)
* - serialization of 2D geometry (path2) to SVG path
*
* Colors are added to SVG shapes when found on the geometry.
* Special attributes (id and class) are added to SVG shapes when found on the geometry.
*
* @module io/svg-serializer
* @example
* const { serializer, mimeType } = require('@jscad/svg-serializer')
Expand All @@ -32,12 +40,13 @@ const version = require('./package.json').version
const mimeType = 'image/svg+xml'

/**
* Serialize the give objects to SVG format.
* Serialize the give objects to SVG code (XML).
* @see https://www.w3.org/TR/SVG/Overview.html
* @param {Object} options - options for serialization, REQUIRED
* @param {String} [options.unit='mm'] - unit of design; em, ex, px, in, cm, mm, pt, pc
* @param {Function} [options.statusCallback] - call back function for progress ({ progress: 0-100 })
* @param {Object|Array} objects - objects to serialize as SVG
* @returns {Array} serialized contents with one SVG structure (string)
* @returns {Array} serialized contents, SVG code (XML string)
* @alias module:io/svg-serializer.serialize
* @example
* const geometry = primitives.square()
Expand Down Expand Up @@ -77,6 +86,9 @@ const serialize = (options, ...objects) => {
width: width + options.unit,
height: height + options.unit,
viewBox: ('0 0 ' + width + ' ' + height),
fill: "none",
'fill-rule': "evenodd",
'stroke-width': "0.1px",
version: '1.1',
baseProfile: 'tiny',
xmlns: 'http://www.w3.org/2000/svg',
Expand Down Expand Up @@ -143,32 +155,32 @@ const reflect = (x, y, px, py) => {
const convertGeom2 = (object, offsets, options) => {
const outlines = geometries.geom2.toOutlines(object)
const paths = outlines.map((outline) => geometries.path2.fromPoints({ closed: true }, outline))
if (object.color) {
paths.forEach((path) => {
path.fill = object.color
})
}

options.color = "black" // SVG initial color
if (object.color) options.color = convertColor(object.color)
options.id = null
if (object.id) options.id = object.id
options.class = null
if (object.class) options.class = object.class

return convertToContinousPath(paths, offsets, options)
}

const convertToContinousPath = (paths, offsets, options) => {
let instructions = ''
paths.forEach((path) => (instructions += convertPath(path, offsets, options)))
let continouspath = ['path', { d: instructions }]
if (paths.length > 0) {
const path0 = paths[0]
if (path0.fill) {
continouspath = ['path', { 'fill-rule': 'evenodd', fill: convertColor(path0.fill), d: instructions }]
}
}
return ['g', continouspath]
const d = { fill: options.color, d: instructions }
if (options.id) d.id = options.id
if (options.class) d.class = options.class
return ['g', ['path', d]]
}

const convertPaths = (paths, offsets, options) => paths.reduce((res, path, i) => {
if (path.color) {
return res.concat([['path', { stroke: convertColor(path.color), 'stroke-width': 1, d: convertPath(path, offsets, options) }]])
}
return res.concat([['path', { d: convertPath(path, offsets, options) }]])
d = { d: convertPath(path, offsets, options) }
if (path.color) d.stroke = convertColor(path.color)
if (path.id) d.id = path.id
if (path.class) d.class = path.class
return res.concat([['path', d]])
}, ['g'])

const convertPath = (path, offsets, options) => {
Expand Down
24 changes: 13 additions & 11 deletions packages/io/svg-serializer/tests/geom2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ test('serialize 2D geometries (simple) to svg', (t) => {
test('serialize 2D geometries (color) to svg', (t) => {
let cag2 = primitives.rectangle({ size: [10, 20] })
cag2 = colors.colorize([0.5, 0.5, 0.5, 0.5], cag2)
cag2.id = 'r2'
cag2.class = 'gray-rect'

const observed2 = serializer.serialize({}, cag2)
t.deepEqual([expected4], observed2)
Expand Down Expand Up @@ -62,52 +64,52 @@ test('serialize 2D geometries (complex) to svg', (t) => {
const expected1 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="0mm" height="0mm" viewBox="0 0 0 0" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="0mm" height="0mm" viewBox="0 0 0 0" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d=""/>
<path fill="black" d=""/>
</g>
</svg>
`

const expected2 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="10mm" height="20mm" viewBox="0 0 10 20" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="10mm" height="20mm" viewBox="0 0 10 20" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d="M0 20L10 20L10 0L0 0L0 20"/>
<path fill="black" d="M0 20L10 20L10 0L0 0L0 20"/>
</g>
</svg>
`

const expected3 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="70mm" height="80mm" viewBox="0 0 70 80" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="70mm" height="80mm" viewBox="0 0 70 80" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d="M0 80L10 80L10 60L0 60L0 80"/>
<path fill="black" d="M0 80L10 80L10 60L0 60L0 80"/>
</g>
<g>
<path d="M60 20L70 20L70 0L60 0L60 20"/>
<path fill="black" d="M60 20L70 20L70 0L60 0L60 20"/>
</g>
</svg>
`

const expected4 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="10mm" height="20mm" viewBox="0 0 10 20" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="10mm" height="20mm" viewBox="0 0 10 20" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path fill-rule="evenodd" fill="rgb(127.5,127.5,127.5,127.5)" d="M0 20L10 20L10 0L0 0L0 20"/>
<path fill="rgb(127.5,127.5,127.5,127.5)" d="M0 20L10 20L10 0L0 0L0 20" id="r2" class="gray-rect"/>
</g>
</svg>
`

const expected5 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="150mm" height="150mm" viewBox="0 0 150 150" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="150mm" height="150mm" viewBox="0 0 150 150" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path fill-rule="evenodd" fill="rgb(127.5,127.5,127.5,127.5)" d="M0 150L150 150L150 0L115 0L115 75L35 75L35 0L0 0L0 150M90 115L83 115L83 100L67 100L67 115L60 115L60 85L90 85L90 115M73 94L77 94L77 90L73 90L73 94"/>
<path fill="rgb(127.5,127.5,127.5,127.5)" d="M0 150L150 150L150 0L115 0L115 75L35 75L35 0L0 0L0 150M90 115L83 115L83 100L67 100L67 115L60 115L60 85L90 85L90 115M73 94L77 94L77 90L73 90L73 94"/>
</g>
</svg>
`
10 changes: 6 additions & 4 deletions packages/io/svg-serializer/tests/path2.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ test('serialize 2D path (color) objects to svg', (t) => {
// simple open path
let object1 = primitives.line([[0, 0], [1, 1], [-3, 3]])
object1 = colors.colorize([0.5, 0.5, 0.5, 0.5], object1)
object1.id = 'l1'
object1.class = 'gray-line'
const observed = serializer.serialize({}, object1)
t.deepEqual(observed, [expected4])
})
Expand All @@ -37,7 +39,7 @@ test('serialize 2D path (color) objects to svg', (t) => {
const expected1 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="4mm" height="3mm" viewBox="0 0 4 3" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="4mm" height="3mm" viewBox="0 0 4 3" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d="M3 3L4 2L0 0"/>
</g>
Expand All @@ -47,7 +49,7 @@ const expected1 = `<?xml version="1.0" encoding="UTF-8"?>
const expected3 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="42.3333mm" height="56.4444mm" viewBox="0 0 42.3333 56.4444" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="42.3333mm" height="56.4444mm" viewBox="0 0 42.3333 56.4444" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d="M21.1667 0L0 56.4444L42.3333 56.4444L21.1667 0"/>
</g>
Expand All @@ -57,9 +59,9 @@ const expected3 = `<?xml version="1.0" encoding="UTF-8"?>
const expected4 = `<?xml version="1.0" encoding="UTF-8"?>
<!-- Created by JSCAD SVG Serializer -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg width="4mm" height="3mm" viewBox="0 0 4 3" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="4mm" height="3mm" viewBox="0 0 4 3" fill="none" fill-rule="evenodd" stroke-width="0.1px" version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path stroke="rgb(127.5,127.5,127.5,127.5)" stroke-width="1" d="M3 3L4 2L0 0"/>
<path d="M3 3L4 2L0 0" stroke="rgb(127.5,127.5,127.5,127.5)" id="l1" class="gray-line"/>
</g>
</svg>
`