From d22f43cbf005a49a813696f8e48afb4e6a78b67f Mon Sep 17 00:00:00 2001 From: Andrey Chalkin Date: Wed, 14 Nov 2018 01:19:23 +0700 Subject: [PATCH] Gradients support (#41) * fix: gradients may start from incorrect place * Initial stable release --- CHANGELOG.md | 11 +++++-- package.json | 2 +- src/core/core.ts | 2 +- src/core/resources-parser.ts | 13 ++++---- src/core/styles/fill.ts | 38 ++++++++++++++++++--- src/core/styles/filters.ts | 6 ++-- src/core/styles/models/fill.ts | 13 ++++++++ src/core/utils/gradients-list.ts | 4 +++ src/core/utils/index.ts | 4 +++ src/utils/index.ts | 1 + test/core/resources-parser.spec.ts | 11 +------ test/core/styles/fill-stroke.spec.ts | 49 +++++++++++++++++++--------- 12 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 src/core/utils/gradients-list.ts create mode 100644 src/core/utils/index.ts create mode 100644 src/utils/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc245b..7aba3e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [1.0.0] +### Fix +- gradients may start from incorrect place + +## [1.0.0-4] ### Changed: - Multiple output format as default - Remove empty style rules while converting @@ -306,8 +311,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add Artboard parser -[Unreleased]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0-3...HEAD -[1.0.0-3]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0-1...v1.0.0-3 +[Unreleased]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0-4...v1.0.0 +[1.0.0-4]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0-3...v1.0.0-4 +[1.0.0-3]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0-2...v1.0.0-3 [1.0.0-2]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0-1...v1.0.0-2 [1.0.0-1]: https://github.com/L2jLiga/xd2svg/compare/v1.0.0-0...v1.0.0-1 [1.0.0-0]: https://github.com/L2jLiga/xd2svg/compare/v0.8.1...v1.0.0-0 diff --git a/package.json b/package.json index 85d5518..efd440d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xd2svg", - "version": "1.0.0-4", + "version": "1.0.0", "description": "Utility for converting Adobe XD files (*.xd) to SVG", "keywords": [ "svg", diff --git a/src/core/core.ts b/src/core/core.ts index 97aa290..0df1492 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -12,7 +12,7 @@ import { manifestParser } from './manifest-parser import { Artboard, ArtboardInfo, Dictionary, Directory } from './models'; import { resourcesParser } from './resources-parser'; import { svgo } from './svgo'; -import { defs } from './utils/defs-list'; +import { defs } from './utils'; interface InjectableSvgData { defs: string; diff --git a/src/core/resources-parser.ts b/src/core/resources-parser.ts index c8a478d..f542d28 100644 --- a/src/core/resources-parser.ts +++ b/src/core/resources-parser.ts @@ -7,11 +7,11 @@ */ import { readFileSync } from 'fs'; +import * as builder from 'xmlbuilder'; import { createElem } from './artboard-converter'; import { ArtboardInfo, Dictionary, Directory } from './models'; import { Color } from './styles/models'; -import { colorTransformer } from './utils/color-transformer'; -import { defs } from './utils/defs-list'; +import { colorTransformer, defs, gradients } from './utils'; export function resourcesParser(directory: Directory): Dictionary { const json = readFileSync(`${directory.name}/resources/graphics/graphicContent.agc`, 'utf-8'); @@ -41,14 +41,13 @@ function buildArtboardsInfo(artboards: { [id: string]: any }): Dictionary buildElement(gradients[gradientId], gradientId)); +function buildGradients(list: Dictionary<{ type, stops }>): void { + Object.entries(list).forEach(([gradientId, gradient]) => buildElement(gradient, gradientId)); } function buildElement({type, stops}, gradientId: string): void { - const gradient = defs.element(type === 'linear' ? 'linearGradient' : 'radialGradient', {id: gradientId}); + const gradient = builder.begin().element(type === 'linear' ? 'linearGradient' : 'radialGradient', {id: gradientId}); + gradients[gradientId] = gradient; stops.forEach((stop: { offset: string, color: Color }) => { gradient.element('stop', { diff --git a/src/core/styles/fill.ts b/src/core/styles/fill.ts index db20506..5d7c2b8 100644 --- a/src/core/styles/fill.ts +++ b/src/core/styles/fill.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://github.com/L2jLiga/xd2svg/LICENSE */ -import { XMLElementOrXMLNode } from 'xmlbuilder'; -import { manifestInfo } from '../manifest-parser'; -import { colorTransformer } from '../utils/color-transformer'; -import { Fill, Parser, Pattern } from './models'; +import { XMLElementOrXMLNode } from 'xmlbuilder'; +import { manifestInfo } from '../manifest-parser'; +import { colorTransformer, gradients } from '../utils'; +import { Fill, Parser, Pattern } from './models'; export const fill: Parser = { name: 'fill', @@ -17,11 +17,14 @@ export const fill: Parser = { }; export function fillParser(src: Fill, defs: XMLElementOrXMLNode): string { + switch (src.type) { case 'color': return colorTransformer(src.fill.color); case 'gradient': - return `url(#${src.gradient.ref})`; + const gradientId: string = makeGradient(src.gradient, defs); + + return `url(#${gradientId})`; case 'pattern': createPattern(src.pattern, defs); @@ -33,6 +36,31 @@ export function fillParser(src: Fill, defs: XMLElementOrXMLNode): string { } } +function makeGradient(gradientInfo: Fill['gradient'], defs: XMLElementOrXMLNode) { + const gradient: XMLElementOrXMLNode = gradients[gradientInfo.ref].clone(); + const gradientId = 'gradient-' + Object.values(gradientInfo).join('-'); + + gradient.attribute('id', gradientId); + applyIfPossible(gradient, 'gradientUnits', gradientInfo.units); + applyIfPossible(gradient, 'x1', gradientInfo.x1); + applyIfPossible(gradient, 'x2', gradientInfo.x2); + applyIfPossible(gradient, 'y1', gradientInfo.y1); + applyIfPossible(gradient, 'y2', gradientInfo.y2); + applyIfPossible(gradient, 'cx', gradientInfo.cx); + applyIfPossible(gradient, 'cy', gradientInfo.cy); + applyIfPossible(gradient, 'r', gradientInfo.r); + + defs.importDocument(gradient); + + return gradientId; +} + +function applyIfPossible(ele: XMLElementOrXMLNode, name: string, value: any): void { + if (value) { + ele.att(name, value); + } +} + function createPattern(patternObject: Pattern, defs: XMLElementOrXMLNode): void { const resources = manifestInfo.resources; const pattern = defs.element('pattern', { diff --git a/src/core/styles/filters.ts b/src/core/styles/filters.ts index ee6557e..b424094 100644 --- a/src/core/styles/filters.ts +++ b/src/core/styles/filters.ts @@ -7,9 +7,9 @@ */ import { XMLElementOrXMLNode } from 'xmlbuilder'; -import { camelToDash } from '../utils/camel-to-dash'; -import { colorTransformer } from '../utils/color-transformer'; -import { Parser } from './models'; +import { camelToDash } from '../utils/camel-to-dash'; +import { colorTransformer } from '../utils/color-transformer'; +import { Parser } from './models'; export const filters: Parser = { name: 'filter', diff --git a/src/core/styles/models/fill.ts b/src/core/styles/models/fill.ts index e182729..157bb3f 100644 --- a/src/core/styles/models/fill.ts +++ b/src/core/styles/models/fill.ts @@ -15,7 +15,20 @@ export interface Fill { color: Color; }; gradient?: { + // Common gradients + units: 'objectBoundingBox' | 'userSpaceOnUse'; ref: string; + + // Linear gradients + x1?: number; + y1?: number; + x2?: number; + y2?: number; + + // Radial gradients + cx?: number; + cy?: number; + r?: number; }; pattern?: Pattern; color?: Color; diff --git a/src/core/utils/gradients-list.ts b/src/core/utils/gradients-list.ts new file mode 100644 index 0000000..7b483c1 --- /dev/null +++ b/src/core/utils/gradients-list.ts @@ -0,0 +1,4 @@ +import { XMLElementOrXMLNode } from 'xmlbuilder'; +import { Dictionary } from '../models'; + +export const gradients: Dictionary = {}; diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts new file mode 100644 index 0000000..75654c4 --- /dev/null +++ b/src/core/utils/index.ts @@ -0,0 +1,4 @@ +export { camelToDash } from './camel-to-dash'; +export { colorTransformer } from './color-transformer'; +export { defs } from './defs-list'; +export { gradients } from './gradients-list'; diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..b881061 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export { error, bold, red, blue } from './logger'; diff --git a/test/core/resources-parser.spec.ts b/test/core/resources-parser.spec.ts index e120642..8021ea6 100644 --- a/test/core/resources-parser.spec.ts +++ b/test/core/resources-parser.spec.ts @@ -107,15 +107,6 @@ describe('Core > Resources parser', () => { resourcesParser({name: ''} as any); - assert.equal( - defs.end(), - '' + - '' + - '' + - '' + - '' + - '' + - '', - ); + assert.equal(defs.end(), ''); }); }); diff --git a/test/core/styles/fill-stroke.spec.ts b/test/core/styles/fill-stroke.spec.ts index e2b6ef1..923d010 100644 --- a/test/core/styles/fill-stroke.spec.ts +++ b/test/core/styles/fill-stroke.spec.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://github.com/L2jLiga/xd2svg/LICENSE */ -import * as assert from 'assert'; -import * as builder from 'xmlbuilder'; -import { fill } from '../../../src/core/styles/fill'; -import { Color, Pattern } from '../../../src/core/styles/models'; -import { stroke } from '../../../src/core/styles/stroke'; +import * as assert from 'assert'; +import * as builder from 'xmlbuilder'; +import { fill } from '../../../src/core/styles/fill'; +import { Color, Fill, Pattern } from '../../../src/core/styles/models'; +import { stroke } from '../../../src/core/styles/stroke'; +import { gradients } from '../../../src/core/utils'; describe('Core > Styles parsers', () => { const pattern: Pattern = { @@ -64,11 +65,20 @@ describe('Core > Styles parsers', () => { }); it('should return ref to gradient when type is gradient', () => { - const gradientRef = 'test-ref'; - - const result = fill.parse({type: 'gradient', gradient: {ref: gradientRef}}); - - assert.equal(result, `url(#${gradientRef})`); + const defs = builder.begin(); + const gradientInfo: Fill['gradient'] = { + ref: 'ref', + units: 'objectBoundingBox', + x1: 1, + x2: 3, + y1: 2, + y2: 4, + }; + gradients[gradientInfo.ref] = builder.begin().element('gradient'); + + const result = fill.parse({type: 'gradient', gradient: gradientInfo}, defs); + + assert.equal(result, `url(#gradient-${Object.values(gradientInfo).join('-')})`); }); it('should append pattern element to defs and return ref to this pattern if type is pattern', () => { @@ -143,11 +153,20 @@ describe('Core > Styles parsers', () => { }); it('should return ref to gradient when type is gradient', () => { - const gradientRef = 'test-ref'; - - const result = stroke.parse({type: 'gradient', gradient: {ref: gradientRef}}); - - assert.equal(result, `url(#${gradientRef})`); + const defs = builder.begin(); + const gradientInfo: Fill['gradient'] = { + ref: 'ref', + units: 'objectBoundingBox', + x1: 1, + x2: 3, + y1: 2, + y2: 4, + }; + gradients[gradientInfo.ref] = builder.begin().element('gradient'); + + const result = stroke.parse({type: 'gradient', gradient: gradientInfo}, defs); + + assert.equal(result, `url(#gradient-${Object.values(gradientInfo).join('-')})`); }); it('should append pattern element to defs and return ref to this pattern if type is pattern', () => {