author | xdono |
Wed, 02 Jul 2008 12:55:45 -0700 | |
changeset 715 | f16baef3a20e |
parent 438 | 2ae294e4518c |
child 1299 | 027d966d5658 |
permissions | -rw-r--r-- |
2 | 1 |
/* |
715 | 2 |
* Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved. |
2 | 3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 |
* |
|
5 |
* This code is free software; you can redistribute it and/or modify it |
|
6 |
* under the terms of the GNU General Public License version 2 only, as |
|
7 |
* published by the Free Software Foundation. Sun designates this |
|
8 |
* particular file as subject to the "Classpath" exception as provided |
|
9 |
* by Sun in the LICENSE file that accompanied this code. |
|
10 |
* |
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that |
|
15 |
* accompanied this code). |
|
16 |
* |
|
17 |
* You should have received a copy of the GNU General Public License version |
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation, |
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 |
* |
|
21 |
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 |
* CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 |
* have any questions. |
|
24 |
*/ |
|
25 |
package javax.swing.text.rtf; |
|
26 |
||
27 |
import java.lang.*; |
|
28 |
import java.util.*; |
|
29 |
import java.awt.Color; |
|
30 |
import java.awt.Font; |
|
31 |
import java.io.OutputStream; |
|
32 |
import java.io.IOException; |
|
33 |
||
34 |
import javax.swing.text.*; |
|
35 |
||
36 |
/** |
|
37 |
* Generates an RTF output stream (java.io.OutputStream) from rich text |
|
38 |
* (handed off through a series of LTTextAcceptor calls). Can be used to |
|
39 |
* generate RTF from any object which knows how to write to a text acceptor |
|
40 |
* (e.g., LTAttributedText and LTRTFFilter). |
|
41 |
* |
|
42 |
* <p>Note that this is a lossy conversion since RTF's model of |
|
43 |
* text does not exactly correspond with LightText's. |
|
44 |
* |
|
45 |
* @see LTAttributedText |
|
46 |
* @see LTRTFFilter |
|
47 |
* @see LTTextAcceptor |
|
48 |
* @see java.io.OutputStream |
|
49 |
*/ |
|
50 |
||
51 |
class RTFGenerator extends Object |
|
52 |
{ |
|
53 |
/* These dictionaries map Colors, font names, or Style objects |
|
54 |
to Integers */ |
|
55 |
Dictionary colorTable; |
|
56 |
int colorCount; |
|
57 |
Dictionary fontTable; |
|
58 |
int fontCount; |
|
59 |
Dictionary styleTable; |
|
60 |
int styleCount; |
|
61 |
||
62 |
/* where all the text is going */ |
|
63 |
OutputStream outputStream; |
|
64 |
||
65 |
boolean afterKeyword; |
|
66 |
||
67 |
MutableAttributeSet outputAttributes; |
|
68 |
||
69 |
/* the value of the last \\ucN keyword emitted */ |
|
70 |
int unicodeCount; |
|
71 |
||
72 |
/* for efficiency's sake (ha) */ |
|
73 |
private Segment workingSegment; |
|
74 |
||
75 |
int[] outputConversion; |
|
76 |
||
77 |
/** The default color, used for text without an explicit color |
|
78 |
* attribute. */ |
|
79 |
static public final Color defaultRTFColor = Color.black; |
|
80 |
||
81 |
static public final float defaultFontSize = 12f; |
|
82 |
||
83 |
static public final String defaultFontFamily = "Helvetica"; |
|
84 |
||
85 |
/* constants so we can avoid allocating objects in inner loops */ |
|
438
2ae294e4518c
6613529: Avoid duplicate object creation within JDK packages
dav
parents:
2
diff
changeset
|
86 |
final static private Object MagicToken; |
2 | 87 |
|
88 |
/* An array of character-keyword pairs. This could be done |
|
89 |
as a dictionary (and lookup would be quicker), but that |
|
90 |
would require allocating an object for every character |
|
91 |
written (slow!). */ |
|
92 |
static class CharacterKeywordPair |
|
93 |
{ public char character; public String keyword; }; |
|
94 |
static protected CharacterKeywordPair[] textKeywords; |
|
95 |
||
96 |
static { |
|
97 |
MagicToken = new Object(); |
|
98 |
||
99 |
Dictionary textKeywordDictionary = RTFReader.textKeywords; |
|
100 |
Enumeration keys = textKeywordDictionary.keys(); |
|
101 |
Vector tempPairs = new Vector(); |
|
102 |
while(keys.hasMoreElements()) { |
|
103 |
CharacterKeywordPair pair = new CharacterKeywordPair(); |
|
104 |
pair.keyword = (String)keys.nextElement(); |
|
105 |
pair.character = ((String)textKeywordDictionary.get(pair.keyword)).charAt(0); |
|
106 |
tempPairs.addElement(pair); |
|
107 |
} |
|
108 |
textKeywords = new CharacterKeywordPair[tempPairs.size()]; |
|
109 |
tempPairs.copyInto(textKeywords); |
|
110 |
} |
|
111 |
||
112 |
static final char[] hexdigits = { '0', '1', '2', '3', '4', '5', '6', '7', |
|
113 |
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
|
114 |
||
115 |
static public void writeDocument(Document d, OutputStream to) |
|
116 |
throws IOException |
|
117 |
{ |
|
118 |
RTFGenerator gen = new RTFGenerator(to); |
|
119 |
Element root = d.getDefaultRootElement(); |
|
120 |
||
121 |
gen.examineElement(root); |
|
122 |
gen.writeRTFHeader(); |
|
123 |
gen.writeDocumentProperties(d); |
|
124 |
||
125 |
/* TODO this assumes a particular element structure; is there |
|
126 |
a way to iterate more generically ? */ |
|
127 |
int max = root.getElementCount(); |
|
128 |
for(int idx = 0; idx < max; idx++) |
|
129 |
gen.writeParagraphElement(root.getElement(idx)); |
|
130 |
||
131 |
gen.writeRTFTrailer(); |
|
132 |
} |
|
133 |
||
134 |
public RTFGenerator(OutputStream to) |
|
135 |
{ |
|
136 |
colorTable = new Hashtable(); |
|
438
2ae294e4518c
6613529: Avoid duplicate object creation within JDK packages
dav
parents:
2
diff
changeset
|
137 |
colorTable.put(defaultRTFColor, Integer.valueOf(0)); |
2 | 138 |
colorCount = 1; |
139 |
||
140 |
fontTable = new Hashtable(); |
|
141 |
fontCount = 0; |
|
142 |
||
143 |
styleTable = new Hashtable(); |
|
144 |
/* TODO: put default style in style table */ |
|
145 |
styleCount = 0; |
|
146 |
||
147 |
workingSegment = new Segment(); |
|
148 |
||
149 |
outputStream = to; |
|
150 |
||
151 |
unicodeCount = 1; |
|
152 |
} |
|
153 |
||
154 |
public void examineElement(Element el) |
|
155 |
{ |
|
156 |
AttributeSet a = el.getAttributes(); |
|
157 |
String fontName; |
|
158 |
Object foregroundColor, backgroundColor; |
|
159 |
||
160 |
tallyStyles(a); |
|
161 |
||
162 |
if (a != null) { |
|
163 |
/* TODO: default color must be color 0! */ |
|
164 |
||
165 |
foregroundColor = StyleConstants.getForeground(a); |
|
166 |
if (foregroundColor != null && |
|
167 |
colorTable.get(foregroundColor) == null) { |
|
168 |
colorTable.put(foregroundColor, new Integer(colorCount)); |
|
169 |
colorCount ++; |
|
170 |
} |
|
171 |
||
172 |
backgroundColor = a.getAttribute(StyleConstants.Background); |
|
173 |
if (backgroundColor != null && |
|
174 |
colorTable.get(backgroundColor) == null) { |
|
175 |
colorTable.put(backgroundColor, new Integer(colorCount)); |
|
176 |
colorCount ++; |
|
177 |
} |
|
178 |
||
179 |
fontName = StyleConstants.getFontFamily(a); |
|
180 |
||
181 |
if (fontName == null) |
|
182 |
fontName = defaultFontFamily; |
|
183 |
||
184 |
if (fontName != null && |
|
185 |
fontTable.get(fontName) == null) { |
|
186 |
fontTable.put(fontName, new Integer(fontCount)); |
|
187 |
fontCount ++; |
|
188 |
} |
|
189 |
} |
|
190 |
||
191 |
int el_count = el.getElementCount(); |
|
192 |
for(int el_idx = 0; el_idx < el_count; el_idx ++) { |
|
193 |
examineElement(el.getElement(el_idx)); |
|
194 |
} |
|
195 |
} |
|
196 |
||
197 |
private void tallyStyles(AttributeSet a) { |
|
198 |
while (a != null) { |
|
199 |
if (a instanceof Style) { |
|
200 |
Integer aNum = (Integer)styleTable.get(a); |
|
201 |
if (aNum == null) { |
|
202 |
styleCount = styleCount + 1; |
|
203 |
aNum = new Integer(styleCount); |
|
204 |
styleTable.put(a, aNum); |
|
205 |
} |
|
206 |
} |
|
207 |
a = a.getResolveParent(); |
|
208 |
} |
|
209 |
} |
|
210 |
||
211 |
private Style findStyle(AttributeSet a) |
|
212 |
{ |
|
213 |
while(a != null) { |
|
214 |
if (a instanceof Style) { |
|
215 |
Object aNum = styleTable.get(a); |
|
216 |
if (aNum != null) |
|
217 |
return (Style)a; |
|
218 |
} |
|
219 |
a = a.getResolveParent(); |
|
220 |
} |
|
221 |
return null; |
|
222 |
} |
|
223 |
||
224 |
private Integer findStyleNumber(AttributeSet a, String domain) |
|
225 |
{ |
|
226 |
while(a != null) { |
|
227 |
if (a instanceof Style) { |
|
228 |
Integer aNum = (Integer)styleTable.get(a); |
|
229 |
if (aNum != null) { |
|
230 |
if (domain == null || |
|
231 |
domain.equals(a.getAttribute(Constants.StyleType))) |
|
232 |
return aNum; |
|
233 |
} |
|
234 |
||
235 |
} |
|
236 |
a = a.getResolveParent(); |
|
237 |
} |
|
238 |
return null; |
|
239 |
} |
|
240 |
||
241 |
static private Object attrDiff(MutableAttributeSet oldAttrs, |
|
242 |
AttributeSet newAttrs, |
|
243 |
Object key, |
|
244 |
Object dfl) |
|
245 |
{ |
|
246 |
Object oldValue, newValue; |
|
247 |
||
248 |
oldValue = oldAttrs.getAttribute(key); |
|
249 |
newValue = newAttrs.getAttribute(key); |
|
250 |
||
251 |
if (newValue == oldValue) |
|
252 |
return null; |
|
253 |
if (newValue == null) { |
|
254 |
oldAttrs.removeAttribute(key); |
|
255 |
if (dfl != null && !dfl.equals(oldValue)) |
|
256 |
return dfl; |
|
257 |
else |
|
258 |
return null; |
|
259 |
} |
|
260 |
if (oldValue == null || |
|
261 |
!equalArraysOK(oldValue, newValue)) { |
|
262 |
oldAttrs.addAttribute(key, newValue); |
|
263 |
return newValue; |
|
264 |
} |
|
265 |
return null; |
|
266 |
} |
|
267 |
||
268 |
static private boolean equalArraysOK(Object a, Object b) |
|
269 |
{ |
|
270 |
Object[] aa, bb; |
|
271 |
if (a == b) |
|
272 |
return true; |
|
273 |
if (a == null || b == null) |
|
274 |
return false; |
|
275 |
if (a.equals(b)) |
|
276 |
return true; |
|
277 |
if (!(a.getClass().isArray() && b.getClass().isArray())) |
|
278 |
return false; |
|
279 |
aa = (Object[])a; |
|
280 |
bb = (Object[])b; |
|
281 |
if (aa.length != bb.length) |
|
282 |
return false; |
|
283 |
||
284 |
int i; |
|
285 |
int l = aa.length; |
|
286 |
for(i = 0; i < l; i++) { |
|
287 |
if (!equalArraysOK(aa[i], bb[i])) |
|
288 |
return false; |
|
289 |
} |
|
290 |
||
291 |
return true; |
|
292 |
} |
|
293 |
||
294 |
/* Writes a line break to the output file, for ease in debugging */ |
|
295 |
public void writeLineBreak() |
|
296 |
throws IOException |
|
297 |
{ |
|
298 |
writeRawString("\n"); |
|
299 |
afterKeyword = false; |
|
300 |
} |
|
301 |
||
302 |
||
303 |
public void writeRTFHeader() |
|
304 |
throws IOException |
|
305 |
{ |
|
306 |
int index; |
|
307 |
||
308 |
/* TODO: Should the writer attempt to examine the text it's writing |
|
309 |
and pick a character set which will most compactly represent the |
|
310 |
document? (currently the writer always uses the ansi character |
|
311 |
set, which is roughly ISO-8859 Latin-1, and uses Unicode escapes |
|
312 |
for all other characters. However Unicode is a relatively |
|
313 |
recent addition to RTF, and not all readers will understand it.) */ |
|
314 |
writeBegingroup(); |
|
315 |
writeControlWord("rtf", 1); |
|
316 |
writeControlWord("ansi"); |
|
317 |
outputConversion = outputConversionForName("ansi"); |
|
318 |
writeLineBreak(); |
|
319 |
||
320 |
/* write font table */ |
|
321 |
String[] sortedFontTable = new String[fontCount]; |
|
322 |
Enumeration fonts = fontTable.keys(); |
|
323 |
String font; |
|
324 |
while(fonts.hasMoreElements()) { |
|
325 |
font = (String)fonts.nextElement(); |
|
326 |
Integer num = (Integer)(fontTable.get(font)); |
|
327 |
sortedFontTable[num.intValue()] = font; |
|
328 |
} |
|
329 |
writeBegingroup(); |
|
330 |
writeControlWord("fonttbl"); |
|
331 |
for(index = 0; index < fontCount; index ++) { |
|
332 |
writeControlWord("f", index); |
|
333 |
writeControlWord("fnil"); /* TODO: supply correct font style */ |
|
334 |
writeText(sortedFontTable[index]); |
|
335 |
writeText(";"); |
|
336 |
} |
|
337 |
writeEndgroup(); |
|
338 |
writeLineBreak(); |
|
339 |
||
340 |
/* write color table */ |
|
341 |
if (colorCount > 1) { |
|
342 |
Color[] sortedColorTable = new Color[colorCount]; |
|
343 |
Enumeration colors = colorTable.keys(); |
|
344 |
Color color; |
|
345 |
while(colors.hasMoreElements()) { |
|
346 |
color = (Color)colors.nextElement(); |
|
347 |
Integer num = (Integer)(colorTable.get(color)); |
|
348 |
sortedColorTable[num.intValue()] = color; |
|
349 |
} |
|
350 |
writeBegingroup(); |
|
351 |
writeControlWord("colortbl"); |
|
352 |
for(index = 0; index < colorCount; index ++) { |
|
353 |
color = sortedColorTable[index]; |
|
354 |
if (color != null) { |
|
355 |
writeControlWord("red", color.getRed()); |
|
356 |
writeControlWord("green", color.getGreen()); |
|
357 |
writeControlWord("blue", color.getBlue()); |
|
358 |
} |
|
359 |
writeRawString(";"); |
|
360 |
} |
|
361 |
writeEndgroup(); |
|
362 |
writeLineBreak(); |
|
363 |
} |
|
364 |
||
365 |
/* write the style sheet */ |
|
366 |
if (styleCount > 1) { |
|
367 |
writeBegingroup(); |
|
368 |
writeControlWord("stylesheet"); |
|
369 |
Enumeration styles = styleTable.keys(); |
|
370 |
while(styles.hasMoreElements()) { |
|
371 |
Style style = (Style)styles.nextElement(); |
|
372 |
int styleNumber = ((Integer)styleTable.get(style)).intValue(); |
|
373 |
writeBegingroup(); |
|
374 |
String styleType = (String)style.getAttribute(Constants.StyleType); |
|
375 |
if (styleType == null) |
|
376 |
styleType = Constants.STParagraph; |
|
377 |
if (styleType.equals(Constants.STCharacter)) { |
|
378 |
writeControlWord("*"); |
|
379 |
writeControlWord("cs", styleNumber); |
|
380 |
} else if(styleType.equals(Constants.STSection)) { |
|
381 |
writeControlWord("*"); |
|
382 |
writeControlWord("ds", styleNumber); |
|
383 |
} else { |
|
384 |
writeControlWord("s", styleNumber); |
|
385 |
} |
|
386 |
||
387 |
AttributeSet basis = style.getResolveParent(); |
|
388 |
MutableAttributeSet goat; |
|
389 |
if (basis == null) { |
|
390 |
goat = new SimpleAttributeSet(); |
|
391 |
} else { |
|
392 |
goat = new SimpleAttributeSet(basis); |
|
393 |
} |
|
394 |
||
395 |
updateSectionAttributes(goat, style, false); |
|
396 |
updateParagraphAttributes(goat, style, false); |
|
397 |
updateCharacterAttributes(goat, style, false); |
|
398 |
||
399 |
basis = style.getResolveParent(); |
|
400 |
if (basis != null && basis instanceof Style) { |
|
401 |
Integer basedOn = (Integer)styleTable.get(basis); |
|
402 |
if (basedOn != null) { |
|
403 |
writeControlWord("sbasedon", basedOn.intValue()); |
|
404 |
} |
|
405 |
} |
|
406 |
||
407 |
Style nextStyle = (Style)style.getAttribute(Constants.StyleNext); |
|
408 |
if (nextStyle != null) { |
|
409 |
Integer nextNum = (Integer)styleTable.get(nextStyle); |
|
410 |
if (nextNum != null) { |
|
411 |
writeControlWord("snext", nextNum.intValue()); |
|
412 |
} |
|
413 |
} |
|
414 |
||
415 |
Boolean hidden = (Boolean)style.getAttribute(Constants.StyleHidden); |
|
416 |
if (hidden != null && hidden.booleanValue()) |
|
417 |
writeControlWord("shidden"); |
|
418 |
||
419 |
Boolean additive = (Boolean)style.getAttribute(Constants.StyleAdditive); |
|
420 |
if (additive != null && additive.booleanValue()) |
|
421 |
writeControlWord("additive"); |
|
422 |
||
423 |
||
424 |
writeText(style.getName()); |
|
425 |
writeText(";"); |
|
426 |
writeEndgroup(); |
|
427 |
} |
|
428 |
writeEndgroup(); |
|
429 |
writeLineBreak(); |
|
430 |
} |
|
431 |
||
432 |
outputAttributes = new SimpleAttributeSet(); |
|
433 |
} |
|
434 |
||
435 |
void writeDocumentProperties(Document doc) |
|
436 |
throws IOException |
|
437 |
{ |
|
438 |
/* Write the document properties */ |
|
439 |
int i; |
|
440 |
boolean wroteSomething = false; |
|
441 |
||
442 |
for(i = 0; i < RTFAttributes.attributes.length; i++) { |
|
443 |
RTFAttribute attr = RTFAttributes.attributes[i]; |
|
444 |
if (attr.domain() != RTFAttribute.D_DOCUMENT) |
|
445 |
continue; |
|
446 |
Object prop = doc.getProperty(attr.swingName()); |
|
447 |
boolean ok = attr.writeValue(prop, this, false); |
|
448 |
if (ok) |
|
449 |
wroteSomething = true; |
|
450 |
} |
|
451 |
||
452 |
if (wroteSomething) |
|
453 |
writeLineBreak(); |
|
454 |
} |
|
455 |
||
456 |
public void writeRTFTrailer() |
|
457 |
throws IOException |
|
458 |
{ |
|
459 |
writeEndgroup(); |
|
460 |
writeLineBreak(); |
|
461 |
} |
|
462 |
||
463 |
protected void checkNumericControlWord(MutableAttributeSet currentAttributes, |
|
464 |
AttributeSet newAttributes, |
|
465 |
Object attrName, |
|
466 |
String controlWord, |
|
467 |
float dflt, float scale) |
|
468 |
throws IOException |
|
469 |
{ |
|
470 |
Object parm; |
|
471 |
||
472 |
if ((parm = attrDiff(currentAttributes, newAttributes, |
|
473 |
attrName, MagicToken)) != null) { |
|
474 |
float targ; |
|
475 |
if (parm == MagicToken) |
|
476 |
targ = dflt; |
|
477 |
else |
|
478 |
targ = ((Number)parm).floatValue(); |
|
479 |
writeControlWord(controlWord, Math.round(targ * scale)); |
|
480 |
} |
|
481 |
} |
|
482 |
||
483 |
protected void checkControlWord(MutableAttributeSet currentAttributes, |
|
484 |
AttributeSet newAttributes, |
|
485 |
RTFAttribute word) |
|
486 |
throws IOException |
|
487 |
{ |
|
488 |
Object parm; |
|
489 |
||
490 |
if ((parm = attrDiff(currentAttributes, newAttributes, |
|
491 |
word.swingName(), MagicToken)) != null) { |
|
492 |
if (parm == MagicToken) |
|
493 |
parm = null; |
|
494 |
word.writeValue(parm, this, true); |
|
495 |
} |
|
496 |
} |
|
497 |
||
498 |
protected void checkControlWords(MutableAttributeSet currentAttributes, |
|
499 |
AttributeSet newAttributes, |
|
500 |
RTFAttribute words[], |
|
501 |
int domain) |
|
502 |
throws IOException |
|
503 |
{ |
|
504 |
int wordIndex; |
|
505 |
int wordCount = words.length; |
|
506 |
for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { |
|
507 |
RTFAttribute attr = words[wordIndex]; |
|
508 |
if (attr.domain() == domain) |
|
509 |
checkControlWord(currentAttributes, newAttributes, attr); |
|
510 |
} |
|
511 |
} |
|
512 |
||
513 |
void updateSectionAttributes(MutableAttributeSet current, |
|
514 |
AttributeSet newAttributes, |
|
515 |
boolean emitStyleChanges) |
|
516 |
throws IOException |
|
517 |
{ |
|
518 |
if (emitStyleChanges) { |
|
519 |
Object oldStyle = current.getAttribute("sectionStyle"); |
|
520 |
Object newStyle = findStyleNumber(newAttributes, Constants.STSection); |
|
521 |
if (oldStyle != newStyle) { |
|
522 |
if (oldStyle != null) { |
|
523 |
resetSectionAttributes(current); |
|
524 |
} |
|
525 |
if (newStyle != null) { |
|
526 |
writeControlWord("ds", ((Integer)newStyle).intValue()); |
|
527 |
current.addAttribute("sectionStyle", newStyle); |
|
528 |
} else { |
|
529 |
current.removeAttribute("sectionStyle"); |
|
530 |
} |
|
531 |
} |
|
532 |
} |
|
533 |
||
534 |
checkControlWords(current, newAttributes, |
|
535 |
RTFAttributes.attributes, RTFAttribute.D_SECTION); |
|
536 |
} |
|
537 |
||
538 |
protected void resetSectionAttributes(MutableAttributeSet currentAttributes) |
|
539 |
throws IOException |
|
540 |
{ |
|
541 |
writeControlWord("sectd"); |
|
542 |
||
543 |
int wordIndex; |
|
544 |
int wordCount = RTFAttributes.attributes.length; |
|
545 |
for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { |
|
546 |
RTFAttribute attr = RTFAttributes.attributes[wordIndex]; |
|
547 |
if (attr.domain() == RTFAttribute.D_SECTION) |
|
548 |
attr.setDefault(currentAttributes); |
|
549 |
} |
|
550 |
||
551 |
currentAttributes.removeAttribute("sectionStyle"); |
|
552 |
} |
|
553 |
||
554 |
void updateParagraphAttributes(MutableAttributeSet current, |
|
555 |
AttributeSet newAttributes, |
|
556 |
boolean emitStyleChanges) |
|
557 |
throws IOException |
|
558 |
{ |
|
559 |
Object parm; |
|
560 |
Object oldStyle, newStyle; |
|
561 |
||
562 |
/* The only way to get rid of tabs or styles is with the \pard keyword, |
|
563 |
emitted by resetParagraphAttributes(). Ideally we should avoid |
|
564 |
emitting \pard if the new paragraph's tabs are a superset of the old |
|
565 |
paragraph's tabs. */ |
|
566 |
||
567 |
if (emitStyleChanges) { |
|
568 |
oldStyle = current.getAttribute("paragraphStyle"); |
|
569 |
newStyle = findStyleNumber(newAttributes, Constants.STParagraph); |
|
570 |
if (oldStyle != newStyle) { |
|
571 |
if (oldStyle != null) { |
|
572 |
resetParagraphAttributes(current); |
|
573 |
oldStyle = null; |
|
574 |
} |
|
575 |
} |
|
576 |
} else { |
|
577 |
oldStyle = null; |
|
578 |
newStyle = null; |
|
579 |
} |
|
580 |
||
581 |
Object oldTabs = current.getAttribute(Constants.Tabs); |
|
582 |
Object newTabs = newAttributes.getAttribute(Constants.Tabs); |
|
583 |
if (oldTabs != newTabs) { |
|
584 |
if (oldTabs != null) { |
|
585 |
resetParagraphAttributes(current); |
|
586 |
oldTabs = null; |
|
587 |
oldStyle = null; |
|
588 |
} |
|
589 |
} |
|
590 |
||
591 |
if (oldStyle != newStyle && newStyle != null) { |
|
592 |
writeControlWord("s", ((Integer)newStyle).intValue()); |
|
593 |
current.addAttribute("paragraphStyle", newStyle); |
|
594 |
} |
|
595 |
||
596 |
checkControlWords(current, newAttributes, |
|
597 |
RTFAttributes.attributes, RTFAttribute.D_PARAGRAPH); |
|
598 |
||
599 |
if (oldTabs != newTabs && newTabs != null) { |
|
600 |
TabStop tabs[] = (TabStop[])newTabs; |
|
601 |
int index; |
|
602 |
for(index = 0; index < tabs.length; index ++) { |
|
603 |
TabStop tab = tabs[index]; |
|
604 |
switch (tab.getAlignment()) { |
|
605 |
case TabStop.ALIGN_LEFT: |
|
606 |
case TabStop.ALIGN_BAR: |
|
607 |
break; |
|
608 |
case TabStop.ALIGN_RIGHT: |
|
609 |
writeControlWord("tqr"); |
|
610 |
break; |
|
611 |
case TabStop.ALIGN_CENTER: |
|
612 |
writeControlWord("tqc"); |
|
613 |
break; |
|
614 |
case TabStop.ALIGN_DECIMAL: |
|
615 |
writeControlWord("tqdec"); |
|
616 |
break; |
|
617 |
} |
|
618 |
switch (tab.getLeader()) { |
|
619 |
case TabStop.LEAD_NONE: |
|
620 |
break; |
|
621 |
case TabStop.LEAD_DOTS: |
|
622 |
writeControlWord("tldot"); |
|
623 |
break; |
|
624 |
case TabStop.LEAD_HYPHENS: |
|
625 |
writeControlWord("tlhyph"); |
|
626 |
break; |
|
627 |
case TabStop.LEAD_UNDERLINE: |
|
628 |
writeControlWord("tlul"); |
|
629 |
break; |
|
630 |
case TabStop.LEAD_THICKLINE: |
|
631 |
writeControlWord("tlth"); |
|
632 |
break; |
|
633 |
case TabStop.LEAD_EQUALS: |
|
634 |
writeControlWord("tleq"); |
|
635 |
break; |
|
636 |
} |
|
637 |
int twips = Math.round(20f * tab.getPosition()); |
|
638 |
if (tab.getAlignment() == TabStop.ALIGN_BAR) { |
|
639 |
writeControlWord("tb", twips); |
|
640 |
} else { |
|
641 |
writeControlWord("tx", twips); |
|
642 |
} |
|
643 |
} |
|
644 |
current.addAttribute(Constants.Tabs, tabs); |
|
645 |
} |
|
646 |
} |
|
647 |
||
648 |
public void writeParagraphElement(Element el) |
|
649 |
throws IOException |
|
650 |
{ |
|
651 |
updateParagraphAttributes(outputAttributes, el.getAttributes(), true); |
|
652 |
||
653 |
int sub_count = el.getElementCount(); |
|
654 |
for(int idx = 0; idx < sub_count; idx ++) { |
|
655 |
writeTextElement(el.getElement(idx)); |
|
656 |
} |
|
657 |
||
658 |
writeControlWord("par"); |
|
659 |
writeLineBreak(); /* makes the raw file more readable */ |
|
660 |
} |
|
661 |
||
662 |
/* debugging. TODO: remove. |
|
663 |
private static String tabdump(Object tso) |
|
664 |
{ |
|
665 |
String buf; |
|
666 |
int i; |
|
667 |
||
668 |
if (tso == null) |
|
669 |
return "[none]"; |
|
670 |
||
671 |
TabStop[] ts = (TabStop[])tso; |
|
672 |
||
673 |
buf = "["; |
|
674 |
for(i = 0; i < ts.length; i++) { |
|
675 |
buf = buf + ts[i].toString(); |
|
676 |
if ((i+1) < ts.length) |
|
677 |
buf = buf + ","; |
|
678 |
} |
|
679 |
return buf + "]"; |
|
680 |
} |
|
681 |
*/ |
|
682 |
||
683 |
protected void resetParagraphAttributes(MutableAttributeSet currentAttributes) |
|
684 |
throws IOException |
|
685 |
{ |
|
686 |
writeControlWord("pard"); |
|
687 |
||
438
2ae294e4518c
6613529: Avoid duplicate object creation within JDK packages
dav
parents:
2
diff
changeset
|
688 |
currentAttributes.addAttribute(StyleConstants.Alignment, Integer.valueOf(0)); |
2 | 689 |
|
690 |
int wordIndex; |
|
691 |
int wordCount = RTFAttributes.attributes.length; |
|
692 |
for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { |
|
693 |
RTFAttribute attr = RTFAttributes.attributes[wordIndex]; |
|
694 |
if (attr.domain() == RTFAttribute.D_PARAGRAPH) |
|
695 |
attr.setDefault(currentAttributes); |
|
696 |
} |
|
697 |
||
698 |
currentAttributes.removeAttribute("paragraphStyle"); |
|
699 |
currentAttributes.removeAttribute(Constants.Tabs); |
|
700 |
} |
|
701 |
||
702 |
void updateCharacterAttributes(MutableAttributeSet current, |
|
703 |
AttributeSet newAttributes, |
|
704 |
boolean updateStyleChanges) |
|
705 |
throws IOException |
|
706 |
{ |
|
707 |
Object parm; |
|
708 |
||
709 |
if (updateStyleChanges) { |
|
710 |
Object oldStyle = current.getAttribute("characterStyle"); |
|
711 |
Object newStyle = findStyleNumber(newAttributes, |
|
712 |
Constants.STCharacter); |
|
713 |
if (oldStyle != newStyle) { |
|
714 |
if (oldStyle != null) { |
|
715 |
resetCharacterAttributes(current); |
|
716 |
} |
|
717 |
if (newStyle != null) { |
|
718 |
writeControlWord("cs", ((Integer)newStyle).intValue()); |
|
719 |
current.addAttribute("characterStyle", newStyle); |
|
720 |
} else { |
|
721 |
current.removeAttribute("characterStyle"); |
|
722 |
} |
|
723 |
} |
|
724 |
} |
|
725 |
||
726 |
if ((parm = attrDiff(current, newAttributes, |
|
727 |
StyleConstants.FontFamily, null)) != null) { |
|
728 |
Number fontNum = (Number)fontTable.get(parm); |
|
729 |
writeControlWord("f", fontNum.intValue()); |
|
730 |
} |
|
731 |
||
732 |
checkNumericControlWord(current, newAttributes, |
|
733 |
StyleConstants.FontSize, "fs", |
|
734 |
defaultFontSize, 2f); |
|
735 |
||
736 |
checkControlWords(current, newAttributes, |
|
737 |
RTFAttributes.attributes, RTFAttribute.D_CHARACTER); |
|
738 |
||
739 |
checkNumericControlWord(current, newAttributes, |
|
740 |
StyleConstants.LineSpacing, "sl", |
|
741 |
0, 20f); /* TODO: sl wackiness */ |
|
742 |
||
743 |
if ((parm = attrDiff(current, newAttributes, |
|
744 |
StyleConstants.Background, MagicToken)) != null) { |
|
745 |
int colorNum; |
|
746 |
if (parm == MagicToken) |
|
747 |
colorNum = 0; |
|
748 |
else |
|
749 |
colorNum = ((Number)colorTable.get(parm)).intValue(); |
|
750 |
writeControlWord("cb", colorNum); |
|
751 |
} |
|
752 |
||
753 |
if ((parm = attrDiff(current, newAttributes, |
|
754 |
StyleConstants.Foreground, null)) != null) { |
|
755 |
int colorNum; |
|
756 |
if (parm == MagicToken) |
|
757 |
colorNum = 0; |
|
758 |
else |
|
759 |
colorNum = ((Number)colorTable.get(parm)).intValue(); |
|
760 |
writeControlWord("cf", colorNum); |
|
761 |
} |
|
762 |
} |
|
763 |
||
764 |
protected void resetCharacterAttributes(MutableAttributeSet currentAttributes) |
|
765 |
throws IOException |
|
766 |
{ |
|
767 |
writeControlWord("plain"); |
|
768 |
||
769 |
int wordIndex; |
|
770 |
int wordCount = RTFAttributes.attributes.length; |
|
771 |
for(wordIndex = 0; wordIndex < wordCount; wordIndex++) { |
|
772 |
RTFAttribute attr = RTFAttributes.attributes[wordIndex]; |
|
773 |
if (attr.domain() == RTFAttribute.D_CHARACTER) |
|
774 |
attr.setDefault(currentAttributes); |
|
775 |
} |
|
776 |
||
777 |
StyleConstants.setFontFamily(currentAttributes, defaultFontFamily); |
|
778 |
currentAttributes.removeAttribute(StyleConstants.FontSize); /* =default */ |
|
779 |
currentAttributes.removeAttribute(StyleConstants.Background); |
|
780 |
currentAttributes.removeAttribute(StyleConstants.Foreground); |
|
781 |
currentAttributes.removeAttribute(StyleConstants.LineSpacing); |
|
782 |
currentAttributes.removeAttribute("characterStyle"); |
|
783 |
} |
|
784 |
||
785 |
public void writeTextElement(Element el) |
|
786 |
throws IOException |
|
787 |
{ |
|
788 |
updateCharacterAttributes(outputAttributes, el.getAttributes(), true); |
|
789 |
||
790 |
if (el.isLeaf()) { |
|
791 |
try { |
|
792 |
el.getDocument().getText(el.getStartOffset(), |
|
793 |
el.getEndOffset() - el.getStartOffset(), |
|
794 |
this.workingSegment); |
|
795 |
} catch (BadLocationException ble) { |
|
796 |
/* TODO is this the correct error to raise? */ |
|
797 |
ble.printStackTrace(); |
|
798 |
throw new InternalError(ble.getMessage()); |
|
799 |
} |
|
800 |
writeText(this.workingSegment); |
|
801 |
} else { |
|
802 |
int sub_count = el.getElementCount(); |
|
803 |
for(int idx = 0; idx < sub_count; idx ++) |
|
804 |
writeTextElement(el.getElement(idx)); |
|
805 |
} |
|
806 |
} |
|
807 |
||
808 |
public void writeText(Segment s) |
|
809 |
throws IOException |
|
810 |
{ |
|
811 |
int pos, end; |
|
812 |
char[] array; |
|
813 |
||
814 |
pos = s.offset; |
|
815 |
end = pos + s.count; |
|
816 |
array = s.array; |
|
817 |
for( ; pos < end; pos ++) |
|
818 |
writeCharacter(array[pos]); |
|
819 |
} |
|
820 |
||
821 |
public void writeText(String s) |
|
822 |
throws IOException |
|
823 |
{ |
|
824 |
int pos, end; |
|
825 |
||
826 |
pos = 0; |
|
827 |
end = s.length(); |
|
828 |
for( ; pos < end; pos ++) |
|
829 |
writeCharacter(s.charAt(pos)); |
|
830 |
} |
|
831 |
||
832 |
public void writeRawString(String str) |
|
833 |
throws IOException |
|
834 |
{ |
|
835 |
int strlen = str.length(); |
|
836 |
for (int offset = 0; offset < strlen; offset ++) |
|
837 |
outputStream.write((int)str.charAt(offset)); |
|
838 |
} |
|
839 |
||
840 |
public void writeControlWord(String keyword) |
|
841 |
throws IOException |
|
842 |
{ |
|
843 |
outputStream.write('\\'); |
|
844 |
writeRawString(keyword); |
|
845 |
afterKeyword = true; |
|
846 |
} |
|
847 |
||
848 |
public void writeControlWord(String keyword, int arg) |
|
849 |
throws IOException |
|
850 |
{ |
|
851 |
outputStream.write('\\'); |
|
852 |
writeRawString(keyword); |
|
853 |
writeRawString(String.valueOf(arg)); /* TODO: correct in all cases? */ |
|
854 |
afterKeyword = true; |
|
855 |
} |
|
856 |
||
857 |
public void writeBegingroup() |
|
858 |
throws IOException |
|
859 |
{ |
|
860 |
outputStream.write('{'); |
|
861 |
afterKeyword = false; |
|
862 |
} |
|
863 |
||
864 |
public void writeEndgroup() |
|
865 |
throws IOException |
|
866 |
{ |
|
867 |
outputStream.write('}'); |
|
868 |
afterKeyword = false; |
|
869 |
} |
|
870 |
||
871 |
public void writeCharacter(char ch) |
|
872 |
throws IOException |
|
873 |
{ |
|
874 |
/* Nonbreaking space is in most RTF encodings, but the keyword is |
|
875 |
preferable; same goes for tabs */ |
|
876 |
if (ch == 0xA0) { /* nonbreaking space */ |
|
877 |
outputStream.write(0x5C); /* backslash */ |
|
878 |
outputStream.write(0x7E); /* tilde */ |
|
879 |
afterKeyword = false; /* non-alpha keywords are self-terminating */ |
|
880 |
return; |
|
881 |
} |
|
882 |
||
883 |
if (ch == 0x09) { /* horizontal tab */ |
|
884 |
writeControlWord("tab"); |
|
885 |
return; |
|
886 |
} |
|
887 |
||
888 |
if (ch == 10 || ch == 13) { /* newline / paragraph */ |
|
889 |
/* ignore CRs, we'll write a paragraph element soon enough */ |
|
890 |
return; |
|
891 |
} |
|
892 |
||
893 |
int b = convertCharacter(outputConversion, ch); |
|
894 |
if (b == 0) { |
|
895 |
/* Unicode characters which have corresponding RTF keywords */ |
|
896 |
int i; |
|
897 |
for(i = 0; i < textKeywords.length; i++) { |
|
898 |
if (textKeywords[i].character == ch) { |
|
899 |
writeControlWord(textKeywords[i].keyword); |
|
900 |
return; |
|
901 |
} |
|
902 |
} |
|
903 |
/* In some cases it would be reasonable to check to see if the |
|
904 |
glyph being written out is in the Symbol encoding, and if so, |
|
905 |
to switch to the Symbol font for this character. TODO. */ |
|
906 |
/* Currently all unrepresentable characters are written as |
|
907 |
Unicode escapes. */ |
|
908 |
String approximation = approximationForUnicode(ch); |
|
909 |
if (approximation.length() != unicodeCount) { |
|
910 |
unicodeCount = approximation.length(); |
|
911 |
writeControlWord("uc", unicodeCount); |
|
912 |
} |
|
913 |
writeControlWord("u", (int)ch); |
|
914 |
writeRawString(" "); |
|
915 |
writeRawString(approximation); |
|
916 |
afterKeyword = false; |
|
917 |
return; |
|
918 |
} |
|
919 |
||
920 |
if (b > 127) { |
|
921 |
int nybble; |
|
922 |
outputStream.write('\\'); |
|
923 |
outputStream.write('\''); |
|
924 |
nybble = ( b & 0xF0 ) >>> 4; |
|
925 |
outputStream.write(hexdigits[nybble]); |
|
926 |
nybble = ( b & 0x0F ); |
|
927 |
outputStream.write(hexdigits[nybble]); |
|
928 |
afterKeyword = false; |
|
929 |
return; |
|
930 |
} |
|
931 |
||
932 |
switch (b) { |
|
933 |
case '}': |
|
934 |
case '{': |
|
935 |
case '\\': |
|
936 |
outputStream.write(0x5C); /* backslash */ |
|
937 |
afterKeyword = false; /* in a keyword, actually ... */ |
|
938 |
/* fall through */ |
|
939 |
default: |
|
940 |
if (afterKeyword) { |
|
941 |
outputStream.write(0x20); /* space */ |
|
942 |
afterKeyword = false; |
|
943 |
} |
|
944 |
outputStream.write(b); |
|
945 |
break; |
|
946 |
} |
|
947 |
} |
|
948 |
||
949 |
String approximationForUnicode(char ch) |
|
950 |
{ |
|
951 |
/* TODO: Find reasonable approximations for all Unicode characters |
|
952 |
in all RTF code pages... heh, heh... */ |
|
953 |
return "?"; |
|
954 |
} |
|
955 |
||
956 |
/** Takes a translation table (a 256-element array of characters) |
|
957 |
* and creates an output conversion table for use by |
|
958 |
* convertCharacter(). */ |
|
959 |
/* Not very efficient at all. Could be changed to sort the table |
|
960 |
for binary search. TODO. (Even though this is inefficient however, |
|
961 |
writing RTF is still much faster than reading it.) */ |
|
962 |
static int[] outputConversionFromTranslationTable(char[] table) |
|
963 |
{ |
|
964 |
int[] conversion = new int[2 * table.length]; |
|
965 |
||
966 |
int index; |
|
967 |
||
968 |
for(index = 0; index < table.length; index ++) { |
|
969 |
conversion[index * 2] = table[index]; |
|
970 |
conversion[(index * 2) + 1] = index; |
|
971 |
} |
|
972 |
||
973 |
return conversion; |
|
974 |
} |
|
975 |
||
976 |
static int[] outputConversionForName(String name) |
|
977 |
throws IOException |
|
978 |
{ |
|
979 |
char[] table = (char[])RTFReader.getCharacterSet(name); |
|
980 |
return outputConversionFromTranslationTable(table); |
|
981 |
} |
|
982 |
||
983 |
/** Takes a char and a conversion table (an int[] in the current |
|
984 |
* implementation, but conversion tables should be treated as an opaque |
|
985 |
* type) and returns the |
|
986 |
* corresponding byte value (as an int, since bytes are signed). |
|
987 |
*/ |
|
988 |
/* Not very efficient. TODO. */ |
|
989 |
static protected int convertCharacter(int[] conversion, char ch) |
|
990 |
{ |
|
991 |
int index; |
|
992 |
||
993 |
for(index = 0; index < conversion.length; index += 2) { |
|
994 |
if(conversion[index] == ch) |
|
995 |
return conversion[index + 1]; |
|
996 |
} |
|
997 |
||
998 |
return 0; /* 0 indicates an unrepresentable character */ |
|
999 |
} |
|
1000 |
||
1001 |
} |