22e365cc5a4f95e60b545a264344525183e5f94f
[idea/community.git] / platform / lang-impl / src / com / intellij / codeEditor / printing / HTMLTextPainter.java
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeEditor.printing;
18
19 import com.intellij.codeInsight.daemon.LineMarkerInfo;
20 import com.intellij.ide.highlighter.HighlighterFactory;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.editor.colors.EditorColorsManager;
24 import com.intellij.openapi.editor.colors.EditorColorsScheme;
25 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
26 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
27 import com.intellij.openapi.editor.markup.TextAttributes;
28 import com.intellij.openapi.fileTypes.FileType;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.Comparing;
31 import com.intellij.openapi.vfs.CharsetToolkit;
32 import com.intellij.psi.PsiDocumentManager;
33 import com.intellij.psi.PsiElement;
34 import com.intellij.psi.PsiFile;
35 import com.intellij.psi.PsiReference;
36 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
37 import com.intellij.psi.impl.file.PsiDirectoryFactory;
38 import com.intellij.psi.util.PsiUtilBase;
39 import com.intellij.ui.JBColor;
40 import org.jetbrains.annotations.NonNls;
41
42 import java.awt.*;
43 import java.io.*;
44 import java.util.*;
45 import java.util.List;
46
47 class HTMLTextPainter {
48   private static final Logger LOG = Logger.getInstance("#com.intellij.codeEditor.printing.HTMLTextPainter");
49
50   private int myOffset = 0;
51   private final EditorHighlighter myHighlighter;
52   private final String myText;
53   private final String myFileName;
54   private final String myHTMLFileName;
55   private int mySegmentEnd;
56   private final PsiFile myPsiFile;
57   private int lineCount;
58   private int myFirstLineNumber;
59   private final boolean myPrintLineNumbers;
60   private int myColumn;
61   private final LineMarkerInfo[] myMethodSeparators;
62   private int myCurrentMethodSeparator;
63   private final Project myProject;
64   private final Map<TextAttributes, String> myStyleMap = new HashMap<TextAttributes, String>();
65
66   public HTMLTextPainter(PsiFile psiFile, Project project, String dirName, boolean printLineNumbers) {
67     myProject = project;
68     myPsiFile = psiFile;
69     myPrintLineNumbers = printLineNumbers;
70     myHighlighter = HighlighterFactory.createHighlighter(project, psiFile.getVirtualFile());
71
72 //    String fileType = FileTypeManager.getInstance().getType(psiFile.getVirtualFile().getName());
73 //    myForceFonts =
74 //      FileTypeManager.TYPE_HTML.equals(fileType) ||
75 //      FileTypeManager.TYPE_XML.equals(fileType) ||
76 //      FileTypeManager.TYPE_JSP.equals(fileType);
77
78     myText = psiFile.getText();
79     myHighlighter.setText(myText);
80     mySegmentEnd = myText.length();
81     myFileName = psiFile.getVirtualFile().getPresentableUrl();
82     myHTMLFileName = dirName + File.separator + ExportToHTMLManager.getHTMLFileName(psiFile);
83
84     PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
85     Document document = psiDocumentManager.getDocument(psiFile);
86
87     ArrayList<LineMarkerInfo> methodSeparators = new ArrayList<LineMarkerInfo>();
88     if (document != null) {
89       final List<LineMarkerInfo> separators = FileSeparatorProvider.getInstance().getFileSeparators(psiFile, document,
90                                                                                                     PsiUtilBase.findEditor(psiFile));
91       if (separators != null) {
92         methodSeparators.addAll(separators);
93       }
94     }
95
96     myMethodSeparators = methodSeparators.toArray(new LineMarkerInfo[methodSeparators.size()]);
97     myCurrentMethodSeparator = 0;
98   }
99
100   public void setSegment(int segmentStart, int segmentEnd, int firstLineNumber) {
101     myOffset = segmentStart;
102     mySegmentEnd = segmentEnd;
103     myFirstLineNumber = firstLineNumber;
104   }
105
106   @SuppressWarnings({"HardCodedStringLiteral"})
107   public void paint(TreeMap refMap, FileType fileType) throws FileNotFoundException {
108     HighlighterIterator hIterator = myHighlighter.createIterator(myOffset);
109     if(hIterator.atEnd()) return;
110     OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(myHTMLFileName), CharsetToolkit.UTF8_CHARSET);
111     lineCount = myFirstLineNumber;
112     TextAttributes prevAttributes = null;
113     Iterator refKeys = null;
114
115     int refOffset = -1;
116     PsiReference ref = null;
117     if(refMap != null) {
118       refKeys = refMap.keySet().iterator();
119       if(refKeys.hasNext()) {
120         Integer key = (Integer)refKeys.next();
121         ref = (PsiReference)refMap.get(key);
122         refOffset = key.intValue();
123       }
124     }
125
126     int referenceEnd = -1;
127     try {
128       writeHeader(writer, new File(myFileName).getName());
129       if (myFirstLineNumber == 0) {
130         writeLineNumber(writer);
131       }
132       String closeTag = null;
133
134       while (myCurrentMethodSeparator < myMethodSeparators.length) {
135         LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator];
136         if (marker != null && marker.startOffset >= hIterator.getStart()) break;
137         myCurrentMethodSeparator++;
138       }
139
140       while(!hIterator.atEnd()) {
141         TextAttributes textAttributes = hIterator.getTextAttributes();
142         int hStart = hIterator.getStart();
143         int hEnd = hIterator.getEnd();
144         if (hEnd > mySegmentEnd) break;
145
146         boolean haveNonWhiteSpace = false;
147         for(int offset = hStart; offset < hEnd; offset++) {
148           char c = myText.charAt(offset);
149           if (c != ' ' && c != '\t') {
150             haveNonWhiteSpace = true;
151             break;
152           }
153         }
154         if (!haveNonWhiteSpace) {
155           // don't write separate spans for whitespace-only text fragments
156           writeString(writer, myText, hStart, hEnd - hStart, fileType);
157           hIterator.advance();
158           continue;
159         }
160
161         if(refOffset > 0 && hStart <= refOffset && hEnd > refOffset) {
162           referenceEnd = writeReferenceTag(writer, ref);
163         }
164 //        if(myForceFonts || !equals(prevAttributes, textAttributes)) {
165         if(!equals(prevAttributes, textAttributes) && referenceEnd < 0 ) {
166           if(closeTag != null) {
167             writer.write(closeTag);
168           }
169           closeTag = writeFontTag(writer, textAttributes);
170           prevAttributes = textAttributes;
171         }
172
173         if (myCurrentMethodSeparator < myMethodSeparators.length) {
174           LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator];
175           if (marker != null && marker.startOffset <= hEnd) {
176             writer.write("<hr>");
177             myCurrentMethodSeparator++;
178           }
179         }
180
181         writeString(writer, myText, hStart, hEnd - hStart, fileType);
182 //        if(closeTag != null) {
183 //          writer.write(closeTag);
184 //        }
185         if(referenceEnd > 0 && hEnd >= referenceEnd) {
186           writer.write("</a>");
187           referenceEnd = -1;
188           if(refKeys.hasNext()) {
189             Integer key = (Integer)refKeys.next();
190             ref = (PsiReference)refMap.get(key);
191             refOffset = key.intValue();
192           }
193         }
194         hIterator.advance();
195       }
196       if(closeTag != null) {
197         writer.write(closeTag);
198       }
199       writeFooter(writer);
200     }
201     catch(IOException e) {
202       LOG.error(e.getMessage(), e);
203     }
204     finally {
205       try {
206         writer.close();
207       }
208       catch(IOException e) {
209         LOG.error(e.getMessage(), e);
210       }
211     }
212   }
213
214   private int writeReferenceTag(Writer writer, PsiReference ref) throws IOException {
215     PsiElement refClass = ref.resolve();
216
217     PsiFile refFile = refClass.getContainingFile();
218     String refPackageName = PsiDirectoryFactory.getInstance(myProject).getQualifiedName(refFile.getContainingDirectory(), false);
219     String psiPackageName = PsiDirectoryFactory.getInstance(myProject).getQualifiedName(myPsiFile.getContainingDirectory(), false);
220
221     StringBuffer fileName = new StringBuffer();
222     if (!psiPackageName.equals(refPackageName)) {
223       StringTokenizer tokens = new StringTokenizer(psiPackageName, ".");
224       while(tokens.hasMoreTokens()) {
225         tokens.nextToken();
226         fileName.append("../");
227       }
228
229       StringTokenizer refTokens = new StringTokenizer(refPackageName, ".");
230       while(refTokens.hasMoreTokens()) {
231         String token = refTokens.nextToken();
232         fileName.append(token);
233         fileName.append('/');
234       }
235     }
236     fileName.append(ExportToHTMLManager.getHTMLFileName(refFile));
237     //noinspection HardCodedStringLiteral
238     writer.write("<a href=\""+fileName+"\">");
239     return ref.getElement().getTextRange().getEndOffset();
240   }
241
242   @SuppressWarnings({"HardCodedStringLiteral"})
243   private String writeFontTag(Writer writer, TextAttributes textAttributes) throws IOException {
244 //    "<FONT COLOR=\"#000000\">"
245     writer.write("<span class=\"" + myStyleMap.get(textAttributes) + "\">");
246     return "</span>";
247   }
248
249   @SuppressWarnings({"HardCodedStringLiteral"})
250   private void writeString(Writer writer, CharSequence charArray, int start, int length, FileType fileType) throws IOException {
251     for(int i=start; i<start+length; i++) {
252       char c = charArray.charAt(i);
253       if(c=='<') {
254         writeChar(writer, "&lt;");
255       }
256       else if(c=='>') {
257         writeChar(writer, "&gt;");
258       }
259       else if (c=='&') {
260         writeChar(writer, "&amp;");
261       }
262       else if (c=='\"') {
263         writeChar(writer, "&quot;");
264       }
265       else if (c == '\t') {
266         int tabSize = CodeStyleSettingsManager.getSettings(myProject).getTabSize(fileType);
267         if (tabSize <= 0) tabSize = 1;
268         int nSpaces = tabSize - myColumn % tabSize;
269         for (int j = 0; j < nSpaces; j++) {
270           writeChar(writer, " ");
271         }
272       }
273       else if (c == '\n' || c == '\r') {
274         if (c == '\r' && i+1 < start+length && charArray.charAt(i+1) == '\n') {
275           writeChar(writer, " \r");
276           i++;
277         }
278         else if (c == '\n') {
279           writeChar(writer, " ");
280         }
281         writeLineNumber(writer);
282       }
283       else {
284         writeChar(writer, String.valueOf(c));
285       }
286     }
287   }
288
289   private void writeChar(Writer writer, String s) throws IOException {
290     writer.write(s);
291     myColumn++;
292   }
293
294   private void writeLineNumber(@NonNls Writer writer) throws IOException {
295     writer.write('\n');
296     myColumn = 0;
297     lineCount++;
298     if (myPrintLineNumbers) {
299       writer.write("<a name=\"l" + lineCount + "\">");
300
301 //      String numberCloseTag = writeFontTag(writer, ourLineNumberAttributes);
302
303       writer.write("<span class=\"ln\">");
304       String s = Integer.toString(lineCount);
305       writer.write(s);
306       int extraSpaces = 4 - s.length();
307       do {
308         writer.write(' ');
309       } while (extraSpaces-- > 0);
310       writer.write("</span></a>");
311
312 //      if (numberCloseTag != null) {
313 //        writer.write(numberCloseTag);
314 //      }
315     }
316   }
317
318   private void writeHeader(@NonNls Writer writer, String title) throws IOException {
319     EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
320     writer.write("<html>\r\n");
321     writer.write("<head>\r\n");
322     writer.write("<title>" + title + "</title>\r\n");
323     writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n");
324     writeStyles(writer);
325     writer.write("</head>\r\n");
326     Color color = scheme.getDefaultBackground();
327     if (color==null) color = JBColor.GRAY;
328     writer.write("<BODY BGCOLOR=\"#" + Integer.toString(color.getRGB() & 0xFFFFFF, 16) + "\">\r\n");
329     writer.write("<TABLE CELLSPACING=0 CELLPADDING=5 COLS=1 WIDTH=\"100%\" BGCOLOR=\"#C0C0C0\" >\r\n");
330     writer.write("<TR><TD><CENTER>\r\n");
331     writer.write("<FONT FACE=\"Arial, Helvetica\" COLOR=\"#000000\">\r\n");
332     writer.write(title + "</FONT>\r\n");
333     writer.write("</center></TD></TR></TABLE>\r\n");
334     writer.write("<pre>\r\n");
335   }
336
337   private void writeStyles(@NonNls final Writer writer) throws IOException {
338     writer.write("<style type=\"text/css\">\n");
339     writer.write(".ln { color: rgb(0,0,0); font-weight: normal; font-style: normal; }\n");
340     HighlighterIterator hIterator = myHighlighter.createIterator(myOffset);
341     while(!hIterator.atEnd()) {
342       TextAttributes textAttributes = hIterator.getTextAttributes();
343       if (!myStyleMap.containsKey(textAttributes)) {
344         @NonNls String styleName = "s" + myStyleMap.size();
345         myStyleMap.put(textAttributes, styleName);
346         writer.write("." + styleName + " { ");
347         final Color foreColor = textAttributes.getForegroundColor();
348         if (foreColor != null) {
349           writer.write("color: rgb(" + foreColor.getRed() + "," + foreColor.getGreen() + "," + foreColor.getBlue() + "); ");
350         }
351         if ((textAttributes.getFontType() & Font.BOLD) != 0) {
352           writer.write("font-weight: bold; ");
353         }
354         if ((textAttributes.getFontType() & Font.ITALIC) != 0) {
355           writer.write("font-style: italic; ");
356         }
357         writer.write("}\n");
358       }
359       hIterator.advance();
360     }
361     writer.write("</style>\n");
362   }
363
364   private static void writeFooter(@NonNls Writer writer) throws IOException {
365     writer.write("</pre>\r\n");
366     writer.write("</body>\r\n");
367     writer.write("</html>");
368   }
369
370   private static boolean equals(TextAttributes attributes1, TextAttributes attributes2) {
371     if (attributes2 == null) {
372       return attributes1 == null;
373     }
374     if(attributes1 == null) {
375       return false;
376     }
377     if(!Comparing.equal(attributes1.getForegroundColor(), attributes2.getForegroundColor())) {
378       return false;
379     }
380     if(attributes1.getFontType() != attributes2.getFontType()) {
381       return false;
382     }
383     if(!Comparing.equal(attributes1.getBackgroundColor(), attributes2.getBackgroundColor())) {
384       return false;
385     }
386     if(!Comparing.equal(attributes1.getEffectColor(), attributes2.getEffectColor())) {
387       return false;
388     }
389     return true;
390   }
391
392   public String getHTMLFileName() {
393     return myHTMLFileName;
394   }
395 }