Helptools: refactor the HTML code generation
[scilab.git] / scilab / modules / helptools / src / java / org / scilab / modules / helptools / image / ImageConverter.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - Calixte DENIZET
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.helptools.image;
14
15 import java.awt.Graphics2D;
16 import java.awt.image.BufferedImage;
17 import java.io.BufferedReader;
18 import java.io.BufferedWriter;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileOutputStream;
22 import java.io.FileReader;
23 import java.io.FileWriter;
24 import java.io.IOException;
25 import java.nio.channels.FileChannel;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.TreeMap;
29
30 import javax.activation.MimetypesFileTypeMap;
31 import javax.imageio.ImageIO;
32 import javax.swing.Icon;
33
34 import org.scilab.modules.commons.ScilabCommonsUtils;
35 import org.scilab.modules.helptools.Converter;
36 import org.scilab.modules.helptools.DocbookTagConverter;
37
38 /**
39  * Class to handle the image conversion
40  * @author Calixte DENIZET
41  */
42 public final class ImageConverter {
43
44     private static final String MD5_FILE = "images_md5.txt";
45
46     private DocbookTagConverter conv;
47     private Map<String, ExternalImageConverter> externalConverters = new HashMap<String, ExternalImageConverter>();
48     private MimetypesFileTypeMap mimeMap = new MimetypesFileTypeMap();
49     private Map<String, String> md5s = null;
50
51     public ImageConverter() {
52         mimeMap.addMimeTypes("type=image/latex exts=tex,latex");
53         mimeMap.addMimeTypes("type=image/mathml exts=mml,mathml");
54         mimeMap.addMimeTypes("type=image/svg exts=svg");
55         mimeMap.addMimeTypes("type=image/scilab exts=sce");
56         mimeMap.addMimeTypes("type=image/scilab-xcos exts=xcos,zcos");
57     }
58
59     public void setDocbookTagConverter(DocbookTagConverter conv) {
60         this.conv = conv;
61     }
62
63     /**
64      * Register a new ExternalImageConverter
65      * @param c the converter to register
66      */
67     public void registerExternalImageConverter(ExternalImageConverter c) {
68         if (c != null) {
69             externalConverters.put(c.getMimeType(), c);
70         }
71     }
72
73     /**
74      * Get the current Scilab image converter
75      * @return a Scilab image converter
76      */
77     public ScilabImageConverter getScilabImageConverter() {
78         return (ScilabImageConverter) externalConverters.get("image/scilab");
79     }
80
81     /**
82      * Load file containing md5s
83      * @param dir the directory where to find the file
84      */
85     public void loadMD5s(String dir) {
86         md5s = getMD5s(dir);
87     }
88
89     /**
90      * Save file containing md5s
91      * @param dir the directory where to find the file
92      */
93     public void saveMD5s(String dir) {
94         writeMD5s(dir, md5s);
95     }
96
97     /**
98      * Compare md5
99      * @param code the code to compare
100      * @param file the file name of the future image
101      */
102     public boolean compareMD5(String code, String file) {
103         if (md5s != null && code != null && file != null && !file.isEmpty()) {
104             code = code.trim().replaceAll("[ \t]*[\n]+[ \t]*", "");
105             String md5 = ScilabCommonsUtils.getMD5(code);
106             String oldmd5 = md5s.get(file);
107             if (oldmd5 != null && md5.equals(oldmd5)) {
108                 return true;
109             }
110             md5s.put(file, md5);
111         }
112
113         return false;
114     }
115
116     /**
117      * Get the generated md5s
118      * @param dir the dir where to find the files
119      * @return a map filename -&gt; md5 of the code
120      */
121     private static Map<String, String> getMD5s(String dir) {
122         File f = new File(dir + File.separator + MD5_FILE);
123         Map<String, String> map = new HashMap<String, String>();
124         if (f.exists() && f.canRead()) {
125             BufferedReader reader = null;
126             try {
127                 reader = new BufferedReader(new FileReader(f));
128                 String line = reader.readLine();
129                 while (line != null) {
130                     String[] arr = line.split("=");
131                     map.put(arr[0], arr[1]);
132                     line = reader.readLine();
133                 }
134             } catch (IOException e) {
135                 System.err.println(e);
136             } finally {
137                 if (reader != null) {
138                     try {
139                         reader.close();
140                     } catch (IOException e) {
141
142                     }
143                 }
144             }
145         }
146
147         return map;
148     }
149
150     /**
151      * Write the generated md5s
152      * @param dir the dir where to find the files
153      * @param a map filename -&gt; md5 of the code
154      */
155     private static void writeMD5s(String dir, Map<String, String> map) {
156         File f = new File(dir + File.separator + MD5_FILE);
157         if (!f.exists()) {
158             try {
159                 f.createNewFile();
160             } catch (IOException e) {
161                 System.err.println(e);
162                 return;
163             }
164         }
165
166         if (f.canWrite()) {
167             BufferedWriter writer = null;
168             try {
169                 writer = new BufferedWriter(new FileWriter(f));
170                 Map<String, String> tree = new TreeMap<String, String>(map);
171                 for (Map.Entry<String, String> entry : tree.entrySet()) {
172                     String s = entry.getKey() + "=" + entry.getValue();
173                     writer.write(s, 0, s.length());
174                     writer.newLine();
175                 }
176                 writer.flush();
177             } catch (IOException e) {
178                 System.err.println(e);
179             } finally {
180                 if (writer != null) {
181                     try {
182                         writer.close();
183                     } catch (IOException e) {
184
185                     }
186                 }
187             }
188         }
189     }
190
191     /**
192      * @param attrs the attribute of the image
193      * @param path the current XML file which is parsed
194      * @param image the filename
195      * @param destDir the destination directory
196      * @return true if the code has been rendered into {@code imageFile}
197      */
198     public String getImageByFile(Map<String, String> attrs, String path, String image, String outputDir, String destDir, String baseImagePath) {
199         File f = new File(image);
200         if (!f.isAbsolute()) {
201             f = new File(path + File.separator + image);
202         }
203
204         String destFile = outputDir + File.separator + destDir + File.separator + f.getName();
205
206         ExternalImageConverter imgConv = externalConverters.get(mimeMap.getContentType(f));
207         if (imgConv != null) {
208             destFile += ".png";
209         }
210         File imageFile = new File(destFile);
211         String imageName = destDir + "/" + imageFile.getName();
212
213         if (f.lastModified() > imageFile.lastModified()) {
214             if (imgConv != null) {
215                 return imgConv.convertToImage(f, attrs, imageFile, imageName);
216             }
217             copyImageFile(f, outputDir + File.separator + destDir);
218         }
219
220         return conv.generateImageCode(conv.getBaseImagePath() + imageName, attrs);
221     }
222
223     /**
224      * @param code the code to translate
225      * @param attrs the attribute of the image
226      * @param mime type
227      * @param imageFile the filename
228      * @return true if the code has been rendered into {@code imageFile}
229      */
230     public String getImageByCode(String currentFile, String code, Map<String, String> attrs, String mime, File imageFile, String imageName, String baseImagePath, int lineNumber, String language, boolean isLocalized) {
231         ExternalImageConverter imgConv = externalConverters.get(mime);
232         if (imgConv == null) {
233             System.err.println("In file " + currentFile + " at line " + lineNumber + ": invalid code:\n" + code);
234             return null;
235         }
236
237         if (!imageFile.exists() && md5s != null) {
238             md5s.remove(imageFile.getName());
239         }
240
241         File current = new File(currentFile);
242         if (!compareMD5(code, imageFile.getName())) {
243             if (isLocalized || language.equals("en_US")) {
244                 System.err.println("Info: Create image " + imageFile.getName() + " from line " + lineNumber + " in " + current.getName());
245             } else if (!language.equals("en_US") && imageFile.exists()) {
246                 System.err.println("Warning: Overwrite image " + imageFile.getName() + " from line " + lineNumber + " in " + current.getName() + ". Check the code or use scilab:localized=\"true\" attribute.");
247             }
248
249             return imgConv.convertToImage(currentFile, code, attrs, imageFile, imageName);
250         }
251
252         return conv.generateImageCode(conv.getBaseImagePath() + imageName, attrs);
253     }
254
255     /**
256      * Test if an image file exists.
257      * @param path of the parsed file
258      * @param image the image name
259      * @return null if the image exists, the expected file path otherwise.
260      */
261     public static File imageExists(String path, String image) {
262         File f = new File(image);
263         if (!f.isAbsolute()) {
264             f = new File(path + File.separator + image);
265         }
266
267         if (f.exists()) {
268             return null;
269         } else {
270             return f;
271         }
272     }
273
274     /**
275      * @param f the file to copy
276      * @param destDir the destination directory
277      */
278     public static void copyImageFile(File f, String destDir) {
279         FileChannel src = null;
280         FileChannel dest = null;
281         try {
282             File destFile = new File(destDir + File.separator + f.getName());
283             if (!destFile.exists()) {
284                 destFile.createNewFile();
285             } else if (f.lastModified() <= destFile.lastModified()) {
286                 return;
287             }
288
289             src = new FileInputStream(f).getChannel();
290             dest = new FileOutputStream(destFile).getChannel();
291             dest.transferFrom(src, 0, src.size());
292         } catch (IOException e) {
293             System.err.println(e);
294         } finally {
295             try {
296                 if (src != null) {
297                     src.close();
298                 }
299                 if (dest != null) {
300                     dest.close();
301                 }
302             } catch (IOException e) {
303                 System.err.println(e);
304             }
305         }
306     }
307
308     /**
309      * @param icon the icon to convert into PNG
310      * @param imageFile the destination file
311      * @return true if all is ok
312      */
313     public static boolean convertIconToPNG(Icon icon, File imageFile) {
314         BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
315         Graphics2D g2d = image.createGraphics();
316         icon.paintIcon(null, g2d, 0, 0);
317
318         try {
319             ImageIO.write(image, "png", imageFile.getAbsoluteFile());
320         } catch (IOException ex) {
321             return false;
322         }
323
324         g2d.dispose();
325
326         return true;
327     }
328 }