1+ /*
2+ * Copyright (c) 2018 Peter Flynn
3+ *
4+ * Permission is hereby granted, free of charge, to any person obtaining a
5+ * copy of this software and associated documentation files (the "Software"),
6+ * to deal in the Software without restriction, including without limitation
7+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8+ * and/or sell copies of the Software, and to permit persons to whom the
9+ * Software is furnished to do so, subject to the following conditions:
10+ *
11+ * The above copyright notice and this permission notice shall be included in
12+ * all copies or substantial portions of the Software.
13+ *
14+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20+ * DEALINGS IN THE SOFTWARE.
21+ */
22+
23+ /*jshint esnext: true */
24+ /*globals console, require, exports */
25+
26+ var sg = require ( "scenegraph" ) ;
27+ var clipboard = require ( "clipboard" ) ;
28+
29+ function styleToWeight ( fontStyle ) {
30+ if ( fontStyle . match ( / \b B o l d \b / i) ) {
31+ return "bold" ;
32+ } else if ( fontStyle . match ( / \b B l a c k \b / i) || fontStyle . match ( / \b H e a v y \b / i) ) { // TODO: "extra bold"? (move precedence higher if so)
33+ return 900 ;
34+ } else if ( fontStyle . match ( / \b S e m i [ - ] ? b o l d \b / i) || fontStyle . match ( / \b D e m i [ - ] ? b o l d \b / i) ) {
35+ return 600 ;
36+ } else if ( fontStyle . match ( / \b M e d i u m \b / i) ) {
37+ return 500 ;
38+ } else if ( fontStyle . match ( / \b L i g h t \b / i) ) {
39+ return 300 ;
40+ } else if ( fontStyle . match ( / \b U l t r a [ - ] l i g h t \b / i) ) {
41+ return 200 ;
42+ } else {
43+ return "normal" ;
44+ }
45+ }
46+
47+ function styleIsItalic ( fontStyle ) {
48+ return ( fontStyle . match ( / \b I t a l i c \b / i) || fontStyle . match ( / \b O b l i q u e \b / i) ) ;
49+ }
50+
51+ function colorToCSS ( solidColor ) {
52+ if ( solidColor . a !== 255 ) {
53+ return `rgba(${ solidColor . r } , ${ solidColor . g } , ${ solidColor . b } , ${ num ( solidColor . a / 255 ) } )` ;
54+ } else {
55+ return solidColor . toHex ( ) ;
56+ }
57+ }
58+
59+ function num ( value ) {
60+ return Math . round ( value * 100 ) / 100 ;
61+ }
62+ // TODO: omit "px" suffix from 0s
63+
64+ function eq ( num1 , num2 ) {
65+ return ( Math . abs ( num1 - num2 ) < 0.001 ) ;
66+ }
67+
68+ function copy ( selection ) {
69+ var node = selection . items [ 0 ] ;
70+ if ( ! node ) {
71+ return ;
72+ }
73+
74+ var css = "" ;
75+
76+ // Size - for anything except point text
77+ if ( ! ( node instanceof sg . Text && ! node . areaBox ) ) {
78+ var bounds = node . localBounds ;
79+ css += `width: ${ num ( bounds . width ) } px;\n` ;
80+ css += `height: ${ num ( bounds . height ) } px;\n` ;
81+ }
82+
83+ // Corner metrics
84+ if ( node . hasRoundedCorners ) {
85+ var corners = node . effectiveCornerRadii ;
86+ var tlbr = eq ( corners . topLeft , corners . bottomRight ) ;
87+ var trbl = eq ( corners . topRight , corners . bottomLeft ) ;
88+ if ( tlbr && trbl ) {
89+ if ( eq ( corners . topLeft , corners . topRight ) ) {
90+ css += `border-radius: ${ num ( corners . topLeft ) } px;\n` ;
91+ } else {
92+ css += `border-radius: ${ num ( corners . topLeft ) } px ${ num ( corners . topRight ) } px;\n` ;
93+ }
94+ } else {
95+ css += `border-radius: ${ num ( corners . topLeft ) } px ${ num ( corners . topRight ) } px ${ num ( corners . bottomRight ) } px ${ num ( corners . bottomLeft ) } px;\n` ;
96+ }
97+ }
98+
99+ // Text styles
100+ if ( node instanceof sg . Text ) {
101+ var textStyles = node . styleRanges [ 0 ] ;
102+ if ( textStyles . fontFamily . includes ( " " ) ) {
103+ css += `font-family: "${ textStyles . fontFamily } ";\n` ;
104+ } else {
105+ css += `font-family: ${ textStyles . fontFamily } ;\n` ;
106+ }
107+ css += `font-weight: ${ styleToWeight ( textStyles . fontStyle ) } ;\n` ;
108+ if ( styleIsItalic ( textStyles . fontStyle ) ) {
109+ css += `font-style: italic;\n` ;
110+ }
111+ if ( textStyles . underline ) {
112+ css += `text-decoration: underline;\n` ;
113+ }
114+ css += `font-size: ${ num ( textStyles . fontSize ) } px;\n` ;
115+ if ( textStyles . charSpacing !== 0 ) {
116+ css += `letter-spacing: ${ num ( textStyles . charSpacing / 1000 ) } em;\n` ;
117+ }
118+ if ( node . lineSpacing !== 0 ) {
119+ css += `line-height: ${ num ( node . lineSpacing ) } px;\n` ;
120+ }
121+ css += `text-align: ${ node . textAlign } ;\n` ;
122+ }
123+
124+ // Fill
125+ var fillName = ( node instanceof sg . Text ) ? "color" : "background" ;
126+ if ( node . fill && node . fillEnabled ) {
127+ var fill = node . fill ;
128+ if ( fill instanceof sg . Color ) {
129+ css += `${ fillName } : ${ colorToCSS ( fill ) } ;\n` ;
130+ } else if ( fill . colorStops ) {
131+ var stops = fill . colorStops . map ( stop => {
132+ return colorToCSS ( stop . color ) + " " + num ( stop . stop * 100 ) + "%" ;
133+ } ) ;
134+ css += `${ fillName } : linear-gradient(${ stops . join ( ", " ) } );\n` ; // TODO: gradient direction!
135+ }
136+ // TODO: image fills
137+ } else {
138+ css += `${ fillName } : transparent;\n` ;
139+ }
140+
141+ // Stroke
142+ if ( node . stroke && node . strokeEnabled ) {
143+ var stroke = node . stroke ;
144+ css += `border: ${ num ( node . strokeWidth ) } px solid ${ colorToCSS ( stroke ) } ;\n` ;
145+ // TODO: dashed lines!
146+ }
147+
148+ // Opacity
149+ if ( node . opacity !== 1 ) {
150+ css += `opacity: ${ num ( node . opacity ) } ;\n` ;
151+ }
152+
153+ // Dropshadow
154+ if ( node . shadow && node . shadow . visible ) {
155+ var shadow = node . shadow ;
156+ var shadowSettings = `${ num ( shadow . x ) } px ${ num ( shadow . y ) } px ${ num ( shadow . blur ) } px ${ colorToCSS ( shadow . color ) } ` ;
157+ if ( node instanceof sg . Text ) {
158+ css += `text-shadow: ${ shadowSettings } ;\n` ;
159+ } else if ( node instanceof sg . Rectangle ) {
160+ css += `box-shadow: ${ shadowSettings } ;\n` ;
161+ } else {
162+ css += `filter: drop-shadow(${ shadowSettings } );\n` ;
163+ }
164+ }
165+
166+ clipboard . copyText ( css ) ;
167+ // console.log(css);
168+ }
169+
170+ exports . commands = {
171+ copy : copy
172+ } ;
0 commit comments