2 * Copyright 2000-2012 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeEditor.printing;
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;
45 import java.util.List;
47 class HTMLTextPainter {
48 private static final Logger LOG = Logger.getInstance("#com.intellij.codeEditor.printing.HTMLTextPainter");
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;
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>();
66 public HTMLTextPainter(PsiFile psiFile, Project project, String dirName, boolean printLineNumbers) {
69 myPrintLineNumbers = printLineNumbers;
70 myHighlighter = HighlighterFactory.createHighlighter(project, psiFile.getVirtualFile());
72 // String fileType = FileTypeManager.getInstance().getType(psiFile.getVirtualFile().getName());
74 // FileTypeManager.TYPE_HTML.equals(fileType) ||
75 // FileTypeManager.TYPE_XML.equals(fileType) ||
76 // FileTypeManager.TYPE_JSP.equals(fileType);
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);
84 PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
85 Document document = psiDocumentManager.getDocument(psiFile);
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);
96 myMethodSeparators = methodSeparators.toArray(new LineMarkerInfo[methodSeparators.size()]);
97 myCurrentMethodSeparator = 0;
100 public void setSegment(int segmentStart, int segmentEnd, int firstLineNumber) {
101 myOffset = segmentStart;
102 mySegmentEnd = segmentEnd;
103 myFirstLineNumber = firstLineNumber;
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;
116 PsiReference ref = 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();
126 int referenceEnd = -1;
128 writeHeader(writer, new File(myFileName).getName());
129 if (myFirstLineNumber == 0) {
130 writeLineNumber(writer);
132 String closeTag = null;
134 while (myCurrentMethodSeparator < myMethodSeparators.length) {
135 LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator];
136 if (marker != null && marker.startOffset >= hIterator.getStart()) break;
137 myCurrentMethodSeparator++;
140 while(!hIterator.atEnd()) {
141 TextAttributes textAttributes = hIterator.getTextAttributes();
142 int hStart = hIterator.getStart();
143 int hEnd = hIterator.getEnd();
144 if (hEnd > mySegmentEnd) break;
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;
154 if (!haveNonWhiteSpace) {
155 // don't write separate spans for whitespace-only text fragments
156 writeString(writer, myText, hStart, hEnd - hStart, fileType);
161 if(refOffset > 0 && hStart <= refOffset && hEnd > refOffset) {
162 referenceEnd = writeReferenceTag(writer, ref);
164 // if(myForceFonts || !equals(prevAttributes, textAttributes)) {
165 if(!equals(prevAttributes, textAttributes) && referenceEnd < 0 ) {
166 if(closeTag != null) {
167 writer.write(closeTag);
169 closeTag = writeFontTag(writer, textAttributes);
170 prevAttributes = textAttributes;
173 if (myCurrentMethodSeparator < myMethodSeparators.length) {
174 LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator];
175 if (marker != null && marker.startOffset <= hEnd) {
176 writer.write("<hr>");
177 myCurrentMethodSeparator++;
181 writeString(writer, myText, hStart, hEnd - hStart, fileType);
182 // if(closeTag != null) {
183 // writer.write(closeTag);
185 if(referenceEnd > 0 && hEnd >= referenceEnd) {
186 writer.write("</a>");
188 if(refKeys.hasNext()) {
189 Integer key = (Integer)refKeys.next();
190 ref = (PsiReference)refMap.get(key);
191 refOffset = key.intValue();
196 if(closeTag != null) {
197 writer.write(closeTag);
201 catch(IOException e) {
202 LOG.error(e.getMessage(), e);
208 catch(IOException e) {
209 LOG.error(e.getMessage(), e);
214 private int writeReferenceTag(Writer writer, PsiReference ref) throws IOException {
215 PsiElement refClass = ref.resolve();
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);
221 StringBuffer fileName = new StringBuffer();
222 if (!psiPackageName.equals(refPackageName)) {
223 StringTokenizer tokens = new StringTokenizer(psiPackageName, ".");
224 while(tokens.hasMoreTokens()) {
226 fileName.append("../");
229 StringTokenizer refTokens = new StringTokenizer(refPackageName, ".");
230 while(refTokens.hasMoreTokens()) {
231 String token = refTokens.nextToken();
232 fileName.append(token);
233 fileName.append('/');
236 fileName.append(ExportToHTMLManager.getHTMLFileName(refFile));
237 //noinspection HardCodedStringLiteral
238 writer.write("<a href=\""+fileName+"\">");
239 return ref.getElement().getTextRange().getEndOffset();
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) + "\">");
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);
254 writeChar(writer, "<");
257 writeChar(writer, ">");
260 writeChar(writer, "&");
263 writeChar(writer, """);
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, " ");
273 else if (c == '\n' || c == '\r') {
274 if (c == '\r' && i+1 < start+length && charArray.charAt(i+1) == '\n') {
275 writeChar(writer, " \r");
278 else if (c == '\n') {
279 writeChar(writer, " ");
281 writeLineNumber(writer);
284 writeChar(writer, String.valueOf(c));
289 private void writeChar(Writer writer, String s) throws IOException {
294 private void writeLineNumber(@NonNls Writer writer) throws IOException {
298 if (myPrintLineNumbers) {
299 writer.write("<a name=\"l" + lineCount + "\">");
301 // String numberCloseTag = writeFontTag(writer, ourLineNumberAttributes);
303 writer.write("<span class=\"ln\">");
304 String s = Integer.toString(lineCount);
306 int extraSpaces = 4 - s.length();
309 } while (extraSpaces-- > 0);
310 writer.write("</span></a>");
312 // if (numberCloseTag != null) {
313 // writer.write(numberCloseTag);
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");
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");
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() + "); ");
351 if ((textAttributes.getFontType() & Font.BOLD) != 0) {
352 writer.write("font-weight: bold; ");
354 if ((textAttributes.getFontType() & Font.ITALIC) != 0) {
355 writer.write("font-style: italic; ");
361 writer.write("</style>\n");
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>");
370 private static boolean equals(TextAttributes attributes1, TextAttributes attributes2) {
371 if (attributes2 == null) {
372 return attributes1 == null;
374 if(attributes1 == null) {
377 if(!Comparing.equal(attributes1.getForegroundColor(), attributes2.getForegroundColor())) {
380 if(attributes1.getFontType() != attributes2.getFontType()) {
383 if(!Comparing.equal(attributes1.getBackgroundColor(), attributes2.getBackgroundColor())) {
386 if(!Comparing.equal(attributes1.getEffectColor(), attributes2.getEffectColor())) {
392 public String getHTMLFileName() {
393 return myHTMLFileName;