1+ package edu .rpi .legup .utility ;
2+
3+ import java .awt .BasicStroke ;
4+ import java .awt .Color ;
5+ import java .awt .Component ;
6+ import java .awt .Graphics ;
7+ import java .awt .Graphics2D ;
8+ import static java .awt .RenderingHints .KEY_ANTIALIASING ;
9+ import static java .awt .RenderingHints .VALUE_ANTIALIAS_ON ;
10+ import javax .xml .parsers .DocumentBuilderFactory ;
11+ import javax .xml .parsers .DocumentBuilder ;
12+ import java .awt .Shape ;
13+ import java .awt .Stroke ;
14+ import java .awt .geom .AffineTransform ;
15+ import java .awt .geom .Area ;
16+ import java .awt .geom .Path2D ;
17+ import java .awt .geom .RoundRectangle2D ;
18+ import java .io .IOException ;
19+ import java .net .URL ;
20+ import java .util .Map ;
21+ import java .util .Optional ;
22+ import java .util .Scanner ;
23+ import javax .swing .Icon ;
24+ import javax .xml .parsers .ParserConfigurationException ;
25+ import org .w3c .dom .Document ;
26+ import org .w3c .dom .Element ;
27+ import org .w3c .dom .NodeList ;
28+ import org .xml .sax .SAXException ;
29+
30+ public class SVGImage {
31+ Drawable [] items ;
32+ float x ;
33+ float y ;
34+ float width ;
35+ float height ;
36+ interface Processor {
37+ Shape process (Element node );
38+ }
39+ static Map <String , Color > colors = Map .of (
40+ "white" , Color .WHITE ,
41+ "black" , Color .BLACK ,
42+ "red" , Color .RED ,
43+ "green" , Color .GREEN ,
44+ "blue" , Color .BLUE ,
45+ "cyan" , Color .CYAN ,
46+ "magenta" , Color .MAGENTA ,
47+ "yellow" , Color .YELLOW
48+ );
49+ record ColoredStroke (Stroke stroke , Color color ) {}
50+ record Drawable (Optional <Color > fill , Optional <ColoredStroke > stroke , Area shape ) {}
51+ record ElementProcessor (String element , Processor processor ) implements Map .Entry <String , ElementProcessor > {
52+ Drawable process (Element node ) {
53+ Color strokeColor = null ;
54+ if (node .hasAttribute ("stroke" )) {
55+ strokeColor = colors .get (node .getAttribute ("stroke" ).toLowerCase ());
56+ }
57+ Float strokeWidth = null ;
58+ if (node .hasAttribute ("stroke-width" )) {
59+ strokeWidth = Float .valueOf (node .getAttribute ("stroke-width" ));
60+ }
61+ ColoredStroke stroke = null ;
62+ if (strokeWidth != null && strokeColor != null ) {
63+ stroke = new ColoredStroke (new BasicStroke (strokeWidth ), strokeColor );
64+ }
65+ Color fill = null ;
66+ if (node .hasAttribute ("fill" )) {
67+ fill = colors .get (node .getAttribute ("fill" ).toLowerCase ());
68+ }
69+ Area area = new Area (processor .process (node ));
70+ return new Drawable (Optional .ofNullable (fill ), Optional .ofNullable (stroke ), area );
71+ }
72+ @ Override
73+ public String getKey () {
74+ return element ;
75+ }
76+ @ Override
77+ public ElementProcessor getValue () {
78+ return this ;
79+ }
80+ @ Override
81+ public ElementProcessor setValue (ElementProcessor value ) {
82+ return this ;
83+ }
84+ }
85+ static Map <String , ElementProcessor > processors = Map .ofEntries (
86+ new ElementProcessor ("rect" , (Element node ) -> {
87+ float radiusX = 0 ;
88+ if (node .hasAttribute ("rx" )) {
89+ radiusX = Float .parseFloat (node .getAttribute ("rx" ));
90+ }
91+ float radiusY = 0 ;
92+ if (node .hasAttribute ("ry" )) {
93+ radiusX = Float .parseFloat (node .getAttribute ("ry" ));
94+ }
95+ float x = Float .parseFloat (node .getAttribute ("x" ));
96+ float y = Float .parseFloat (node .getAttribute ("y" ));
97+ float width = Float .parseFloat (node .getAttribute ("width" ));
98+ float height = Float .parseFloat (node .getAttribute ("height" ));
99+ return new RoundRectangle2D .Float (x , y , width , height , radiusX , radiusY );
100+ }),
101+ new ElementProcessor ("path" , (Element node ) -> {
102+ Path2D path = new Path2D .Float ();
103+ Scanner scanner = new Scanner (node .getAttribute ("d" ));
104+ while (scanner .hasNext ()) {
105+ switch (scanner .next ()) {
106+ case "M" -> {
107+ path .moveTo (scanner .nextFloat (), scanner .nextFloat ());
108+ }
109+ case "Q" -> {
110+ path .quadTo (scanner .nextFloat (), scanner .nextFloat (),
111+ scanner .nextFloat (), scanner .nextFloat ());
112+ }
113+ case "L" -> {
114+ path .lineTo (scanner .nextFloat (), scanner .nextFloat ());
115+ }
116+ case "V" -> {
117+ path .lineTo (path .getCurrentPoint ().getX (), scanner .nextFloat ());
118+ }
119+ case "H" -> {
120+ path .lineTo (scanner .nextFloat (), path .getCurrentPoint ().getY ());
121+ }
122+ case "Z" -> {
123+ path .closePath ();
124+ }
125+ }
126+ }
127+ return path ;
128+ })
129+ );
130+ public SVGImage (URL url ) {
131+ try {
132+ DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance ();
133+ DocumentBuilder builder = factory .newDocumentBuilder ();
134+ Document doc = builder .parse (url .toString ());
135+ String viewBox = doc .getDocumentElement ().getAttribute ("viewBox" );
136+ Scanner scanner = new Scanner (viewBox );
137+ x = scanner .nextFloat ();
138+ y = scanner .nextFloat ();
139+ width = scanner .nextFloat ();
140+ height = scanner .nextFloat ();
141+ NodeList nodes = doc .getElementsByTagName ("*" );
142+ items = new Drawable [nodes .getLength ()];
143+ for (int i = 0 ; i < nodes .getLength (); i ++) {
144+ Element node = (Element ) nodes .item (i );
145+ ElementProcessor processor = processors .get (node .getNodeName ());
146+ if (processor != null ) {
147+ items [i ] = processor .process (node );
148+ }
149+ }
150+ } catch (ParserConfigurationException | SAXException | IOException e ) {
151+ System .out .println (e );
152+ }
153+ }
154+ public Icon getIcon (int width , int height ) {
155+ return new Icon () {
156+ @ Override
157+ public void paintIcon (Component c , Graphics graphics , int x , int y ) {
158+ Graphics2D g = (Graphics2D ) graphics ;
159+ g .setRenderingHint (KEY_ANTIALIASING , VALUE_ANTIALIAS_ON );
160+ for (Drawable item : items ) {
161+ if (item == null ) {
162+ continue ;
163+ }
164+ Area shape = (Area ) item .shape ().clone ();
165+ float scaleX = width / SVGImage .this .width ;
166+ float scaleY = height / SVGImage .this .height ;
167+ AffineTransform transform = AffineTransform .getScaleInstance (scaleX , scaleY );
168+ float shiftX = x - SVGImage .this .x ;
169+ float shiftY = y - SVGImage .this .y ;
170+ transform .translate (shiftX , shiftY );
171+ shape .transform (transform );
172+ item .fill .ifPresent ((Color fill ) -> {
173+ g .setColor (fill );
174+ g .fill (shape );
175+ });
176+ item .stroke .ifPresent ((ColoredStroke stroke ) -> {
177+ g .setColor (stroke .color ());
178+ g .setStroke (stroke .stroke ());
179+ g .draw (shape );
180+ });
181+ }
182+ }
183+ @ Override
184+ public int getIconWidth () {
185+ return width ;
186+ }
187+ @ Override
188+ public int getIconHeight () {
189+ return height ;
190+ }
191+ };
192+ }
193+ }
0 commit comments