source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rsyntaxtextarea/RtfGenerator.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 15.7 KB
Line 
1/*
2 * 07/28/2008
3 *
4 * RtfGenerator.java - Generates RTF via a simple Java API.
5 *
6 * This library is distributed under a modified BSD license. See the included
7 * RSyntaxTextArea.License.txt file for details.
8 */
9package org.fife.ui.rsyntaxtextarea;
10
11import java.awt.Color;
12import java.awt.Font;
13import java.awt.GraphicsEnvironment;
14import java.awt.Toolkit;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.List;
18
19
20/**
21 * Generates RTF text via a simple Java API.<p>
22 *
23 * The following RTF features are supported:
24 * <ul>
25 * <li>Fonts
26 * <li>Font sizes
27 * <li>Foreground and background colors
28 * <li>Bold, italic, and underline
29 * </ul>
30 *
31 * The RTF generated isn't really "optimized," but it will do, especially for
32 * small amounts of text, such as what's common when copy-and-pasting. It
33 * tries to be sufficient for the use case of copying syntax highlighted
34 * code:
35 * <ul>
36 * <li>It assumes that tokens changing foreground color often is fairly
37 * common.
38 * <li>It assumes that background highlighting is fairly uncommon.
39 * </ul>
40 *
41 * @author Robert Futrell
42 * @version 1.0
43 */
44public class RtfGenerator {
45
46 private List fontList;
47 private List colorList;
48 private StringBuffer document;
49 private boolean lastWasControlWord;
50 private int lastFontIndex;
51 private int lastFGIndex;
52 private boolean lastBold;
53 private boolean lastItalic;
54 private int lastFontSize;
55 private String monospacedFontName;
56
57 /**
58 * Java2D assumes a 72 dpi screen resolution, but on Windows the screen
59 * resolution is either 96 dpi or 120 dpi, depending on your font display
60 * settings. This is an attempt to make the RTF generated match the
61 * size of what's displayed in the RSyntaxTextArea.
62 */
63 private int screenRes;
64
65 /**
66 * The default font size for RTF. This is point size, in half
67 * points.
68 */
69 private static final int DEFAULT_FONT_SIZE = 12;//24;
70
71
72 /**
73 * Constructor.
74 */
75 public RtfGenerator() {
76 fontList = new ArrayList(1); // Usually only 1.
77 colorList = new ArrayList(1); // Usually only 1.
78 document = new StringBuffer();
79 reset();
80 }
81
82
83 /**
84 * Adds a newline to the RTF document.
85 *
86 * @see #appendToDoc(String, Font, Color, Color)
87 */
88 public void appendNewline() {
89 document.append("\\par");
90 document.append('\n'); // Just for ease of reading RTF.
91 lastWasControlWord = false;
92 }
93
94
95 /**
96 * Appends styled text to the RTF document being generated.
97 *
98 * @param text The text to append.
99 * @param f The font of the text. If this is <code>null</code>, the
100 * default font is used.
101 * @param fg The foreground of the text. If this is <code>null</code>,
102 * the default foreground color is used.
103 * @param bg The background color of the text. If this is
104 * <code>null</code>, the default background color is used.
105 * @see #appendNewline()
106 */
107 public void appendToDoc(String text, Font f, Color fg, Color bg) {
108 appendToDoc(text, f, fg, bg, false);
109 }
110
111
112 /**
113 * Appends styled text to the RTF document being generated.
114 *
115 * @param text The text to append.
116 * @param f The font of the text. If this is <code>null</code>, the
117 * default font is used.
118 * @param bg The background color of the text. If this is
119 * <code>null</code>, the default background color is used.
120 * @param underline Whether the text should be underlined.
121 * @see #appendNewline()
122 */
123 public void appendToDocNoFG(String text, Font f, Color bg,
124 boolean underline) {
125 appendToDoc(text, f, null, bg, underline, false);
126 }
127
128
129 /**
130 * Appends styled text to the RTF document being generated.
131 *
132 * @param text The text to append.
133 * @param f The font of the text. If this is <code>null</code>, the
134 * default font is used.
135 * @param fg The foreground of the text. If this is <code>null</code>,
136 * the default foreground color is used.
137 * @param bg The background color of the text. If this is
138 * <code>null</code>, the default background color is used.
139 * @param underline Whether the text should be underlined.
140 * @see #appendNewline()
141 */
142 public void appendToDoc(String text, Font f, Color fg, Color bg,
143 boolean underline) {
144 appendToDoc(text, f, fg, bg, underline, true);
145 }
146
147
148 /**
149 * Appends styled text to the RTF document being generated.
150 *
151 * @param text The text to append.
152 * @param f The font of the text. If this is <code>null</code>, the
153 * default font is used.
154 * @param fg The foreground of the text. If this is <code>null</code>,
155 * the default foreground color is used.
156 * @param bg The background color of the text. If this is
157 * <code>null</code>, the default background color is used.
158 * @param underline Whether the text should be underlined.
159 * @param setFG Whether the foreground specified by <code>fg</code> should
160 * be honored (if it is non-<code>null</code>).
161 * @see #appendNewline()
162 */
163 public void appendToDoc(String text, Font f, Color fg, Color bg,
164 boolean underline, boolean setFG) {
165
166 if (text!=null) {
167
168 // Set font to use, if different from last addition.
169 int fontIndex = f==null ? 0 : (getFontIndex(fontList, f)+1);
170 if (fontIndex!=lastFontIndex) {
171 document.append("\\f").append(fontIndex);
172 lastFontIndex = fontIndex;
173 lastWasControlWord = true;
174 }
175
176 // Set styles to use.
177 if (f!=null) {
178 int fontSize = fixFontSize(f.getSize2D()); // Half points
179 if (fontSize!=lastFontSize) {
180 document.append("\\fs").append(fontSize);
181 lastFontSize = fontSize;
182 lastWasControlWord = true;
183 }
184 if (f.isBold()!=lastBold) {
185 document.append(lastBold ? "\\b0" : "\\b");
186 lastBold = !lastBold;
187 lastWasControlWord = true;
188 }
189 if (f.isItalic()!=lastItalic) {
190 document.append(lastItalic ? "\\i0" : "\\i");
191 lastItalic = !lastItalic;
192 lastWasControlWord = true;
193 }
194 }
195 else { // No font specified - assume neither bold nor italic.
196 if (lastFontSize!=DEFAULT_FONT_SIZE) {
197 document.append("\\fs").append(DEFAULT_FONT_SIZE);
198 lastFontSize = DEFAULT_FONT_SIZE;
199 lastWasControlWord = true;
200 }
201 if (lastBold) {
202 document.append("\\b0");
203 lastBold = false;
204 lastWasControlWord = true;
205 }
206 if (lastItalic) {
207 document.append("\\i0");
208 lastItalic = false;
209 lastWasControlWord = true;
210 }
211 }
212 if (underline) {
213 document.append("\\ul");
214 lastWasControlWord = true;
215 }
216
217 // Set the foreground color.
218 if (setFG) {
219 int fgIndex = 0;
220 if (fg!=null) { // null => fg color index 0
221 fgIndex = getIndex(colorList, fg)+1;
222 }
223 if (fgIndex!=lastFGIndex) {
224 document.append("\\cf").append(fgIndex);
225 lastFGIndex = fgIndex;
226 lastWasControlWord = true;
227 }
228 }
229
230 // Set the background color.
231 if (bg!=null) {
232 int pos = getIndex(colorList, bg);
233 document.append("\\highlight").append(pos+1);
234 lastWasControlWord = true;
235 }
236
237 if (lastWasControlWord) {
238 document.append(' '); // Delimiter
239 lastWasControlWord = false;
240 }
241 escapeAndAdd(document, text);
242
243 // Reset everything that was set for this text fragment.
244 if (bg!=null) {
245 document.append("\\highlight0");
246 lastWasControlWord = true;
247 }
248 if (underline) {
249 document.append("\\ul0");
250 lastWasControlWord = true;
251 }
252
253 }
254
255 }
256
257
258 /**
259 * Appends some text to a buffer, with special care taken for special
260 * characters as defined by the RTF spec:
261 *
262 * <ul>
263 * <li>All tab characters are replaced with the string
264 * "<code>\tab</code>"
265 * <li>'\', '{' and '}' are changed to "\\", "\{" and "\}"
266 * </ul>
267 *
268 * @param text The text to append (with tab chars substituted).
269 * @param sb The buffer to append to.
270 */
271 private final void escapeAndAdd(StringBuffer sb, String text) {
272 // TODO: On the move to 1.5 use StringBuffer append() overloads that
273 // can take a CharSequence and a range of that CharSequence to speed
274 // things up.
275 //int last = 0;
276 int count = text.length();
277 for (int i=0; i<count; i++) {
278 char ch = text.charAt(i);
279 switch (ch) {
280 case '\t':
281 // Micro-optimization: for syntax highlighting with
282 // tab indentation, there are often multiple tabs
283 // back-to-back at the start of lines, so don't put
284 // spaces between each "\tab".
285 sb.append("\\tab");
286 while ((++i<count) && text.charAt(i)=='\t') {
287 sb.append("\\tab");
288 }
289 sb.append(' ');
290 i--; // We read one too far.
291 break;
292 case '\\':
293 case '{':
294 case '}':
295 sb.append('\\').append(ch);
296 break;
297 default:
298 sb.append(ch);
299 break;
300 }
301 }
302 }
303
304
305 /**
306 * Returns a font point size adjusted for the current screen resolution.
307 * Java2D assumes 72 dpi. On systems with larger dpi (Windows, GTK, etc.),
308 * font rendering will appear to small if we simply return a Java "Font"
309 * object's getSize() value. We need to adjust it for the screen
310 * resolution.
311 *
312 * @param pointSize A Java Font's point size, as returned from
313 * <code>getSize2D()</code>.
314 * @return The font point size, adjusted for the current screen resolution.
315 * This will allow other applications to render fonts the same
316 * size as they appear in the Java application.
317 */
318 private int fixFontSize(float pointSize) {
319 if (screenRes!=72) { // Java2D assumes 72 dpi
320 pointSize = (int)Math.round(pointSize*screenRes/72.0);
321 }
322 return (int)pointSize;
323 }
324
325
326 private String getColorTableRtf() {
327
328 // Example:
329 // "{\\colortbl ;\\red255\\green0\\blue0;\\red0\\green0\\blue255; }"
330
331 StringBuffer sb = new StringBuffer();
332
333 sb.append("{\\colortbl ;");
334 for (int i=0; i<colorList.size(); i++) {
335 Color c = (Color)colorList.get(i);
336 sb.append("\\red").append(c.getRed());
337 sb.append("\\green").append(c.getGreen());
338 sb.append("\\blue").append(c.getBlue());
339 sb.append(';');
340 }
341 sb.append("}");
342
343 return sb.toString();
344
345 }
346
347
348 /**
349 * Returns the index of the specified font in a list of fonts. This
350 * method only checks for a font by its family name; its attributes such
351 * as bold and italic are ignored.<p>
352 *
353 * If the font is not in the list, it is added, and its new index is
354 * returned.
355 *
356 * @param list The list (possibly) containing the font.
357 * @param font The font to get the index of.
358 * @return The index of the font.
359 */
360 private static int getFontIndex(List list, Font font) {
361 String fontName = font.getFamily();
362 for (int i=0; i<list.size(); i++) {
363 Font font2 = (Font)list.get(i);
364 if (font2.getFamily().equals(fontName)) {
365 return i;
366 }
367 }
368 list.add(font);
369 return list.size()-1;
370 }
371
372
373 private String getFontTableRtf() {
374
375 // Example:
376 // "{\\fonttbl{\\f0\\fmodern\\fcharset0 Courier;}}"
377
378 StringBuffer sb = new StringBuffer();
379
380 // Workaround for text areas using the Java logical font "Monospaced"
381 // by default. There's no way to know what it's mapped to, so we
382 // just search for a monospaced font on the system.
383 String monoFamilyName = getMonospacedFontName();
384
385 sb.append("{\\fonttbl{\\f0\\fnil\\fcharset0 " + monoFamilyName + ";}");
386 for (int i=0; i<fontList.size(); i++) {
387 Font f = (Font)fontList.get(i);
388 String familyName = f.getFamily();
389 if (familyName.equals("Monospaced")) {
390 familyName = monoFamilyName;
391 }
392 sb.append("{\\f").append(i+1).append("\\fnil\\fcharset0 ");
393 sb.append(familyName).append(";}");
394 }
395 sb.append('}');
396
397 return sb.toString();
398
399 }
400
401
402 /**
403 * Returns the index of the specified item in a list. If the item
404 * is not in the list, it is added, and its new index is returned.
405 *
406 * @param list The list (possibly) containing the item.
407 * @param item The item to get the index of.
408 * @return The index of the item.
409 */
410 private static int getIndex(List list, Object item) {
411 int pos = list.indexOf(item);
412 if (pos==-1) {
413 list.add(item);
414 pos = list.size()-1;
415 }
416 return pos;
417 }
418
419
420 /**
421 * Try to pick a monospaced font installed on this system. We try
422 * to check for monospaced fonts that are commonly installed on
423 * different OS's. This information was gleaned from
424 * http://www.codestyle.org/css/font-family/sampler-Monospace.shtml.
425 *
426 * @return The name of a monospaced font.
427 */
428 private String getMonospacedFontName() {
429
430 if (monospacedFontName==null) {
431
432 GraphicsEnvironment ge = GraphicsEnvironment.
433 getLocalGraphicsEnvironment();
434 String[] familyNames = ge.getAvailableFontFamilyNames();
435 Arrays.sort(familyNames);
436 boolean windows = System.getProperty("os.name").toLowerCase().
437 indexOf("windows")>=0;
438
439 // "Monaco" is the "standard" monospaced font on OS X. We'll
440 // check for it first so on Macs we don't get stuck with the
441 // uglier Courier New. It'll look funny on Windows though, so
442 // don't pick it if we're on Windows.
443 // It's found on Windows 1.76% of the time, OS X 96.73%
444 // of the time, and UNIX 00.00% (?) of the time.
445 if (!windows && Arrays.binarySearch(familyNames, "Monaco")>=0) {
446 monospacedFontName = "Monaco";
447 }
448
449 // "Courier New" is found on Windows 96.48% of the time,
450 // OS X 92.38% of the time, and UNIX 61.95% of the time.
451 else if (Arrays.binarySearch(familyNames, "Courier New")>=0) {
452 monospacedFontName = "Courier New";
453 }
454
455 // "Courier" is found on Windows ??.??% of the time,
456 // OS X 96.27% of the time, and UNIX 74.04% of the time.
457 else if (Arrays.binarySearch(familyNames, "Courier")>=0) {
458 monospacedFontName = "Courier";
459 }
460
461 // "Nimbus Mono L" is on Windows 00.00% (?) of the time,
462 // OS X 00.00% (?) of the time, but on UNIX 88.79% of the time.
463 else if (Arrays.binarySearch(familyNames, "Nimbus Mono L")>=0) {
464 monospacedFontName = "Nimbus Mono L";
465 }
466
467 // "Lucida Sans Typewriter" is on Windows 49.37% of the time,
468 // OS X 90.43% of the time, and UNIX 00.00% (?) of the time.
469 else if (Arrays.binarySearch(familyNames, "Lucida Sans Typewriter")>=0) {
470 monospacedFontName = "Lucida Sans Typewriter";
471 }
472
473 // "Bitstream Vera Sans Mono" is on Windows 29.81% of the time,
474 // OS X 25.53% of the time, and UNIX 80.71% of the time.
475 else if (Arrays.binarySearch(familyNames, "Bitstream Vera Sans Mono")>=0) {
476 monospacedFontName = "Bitstream Vera Sans Mono";
477 }
478
479 // Windows: 34.16% of the time, OS X: 00.00% (?) of the time,
480 // UNIX: 33.92% of the time.
481 if (monospacedFontName==null) {
482 monospacedFontName = "Terminal";
483 }
484
485 }
486
487 return monospacedFontName;
488
489 }
490
491
492 /**
493 * Returns the RTF document created by this generator.
494 *
495 * @return The RTF document, as a <code>String</code>.
496 */
497 public String getRtf() {
498
499 StringBuffer sb = new StringBuffer();
500 sb.append("{");
501
502 // Header
503 sb.append("\\rtf1\\ansi\\ansicpg1252");
504 sb.append("\\deff0"); // First font in font table is the default
505 sb.append("\\deflang1033");
506 sb.append("\\viewkind4"); // "Normal" view
507 sb.append("\\uc\\pard\\f0");
508 sb.append("\\fs20"); // Font size in half-points (default 24)
509 sb.append(getFontTableRtf()).append('\n');
510 sb.append(getColorTableRtf()).append('\n');
511
512 // Content
513 sb.append(document);
514
515 sb.append("}");
516
517 //System.err.println("*** " + sb.length());
518 return sb.toString();
519
520 }
521
522
523 /**
524 * Resets this generator. All document information and content is
525 * cleared.
526 */
527 public void reset() {
528 fontList.clear();
529 colorList.clear();
530 document.setLength(0);
531 lastWasControlWord = false;
532 lastFontIndex = 0;
533 lastFGIndex = 0;
534 lastBold = false;
535 lastItalic = false;
536 lastFontSize = DEFAULT_FONT_SIZE;
537 screenRes = Toolkit.getDefaultToolkit().getScreenResolution();
538 }
539
540
541}
Note: See TracBrowser for help on using the repository browser.