1. /*
  2. * @(#)RTFReader.java 1.20 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.rtf;
  8. import java.lang.*;
  9. import java.util.*;
  10. import java.io.*;
  11. import java.awt.Font;
  12. import java.awt.Color;
  13. import javax.swing.text.*;
  14. /**
  15. * Takes a sequence of RTF tokens and text and appends the text
  16. * described by the RTF to a <code>StyledDocument</code> (the <em>target</em>).
  17. * The RTF is lexed
  18. * from the character stream by the <code>RTFParser</code> which is this class's
  19. * superclass.
  20. *
  21. * This class is an indirect subclass of OutputStream. It must be closed
  22. * in order to guarantee that all of the text has been sent to
  23. * the text acceptor.
  24. *
  25. * @see RTFParser
  26. * @see java.io.OutputStream
  27. */
  28. class RTFReader extends RTFParser
  29. {
  30. /** The object to which the parsed text is sent. */
  31. StyledDocument target;
  32. /** Miscellaneous information about the parser's state. This
  33. * dictionary is saved and restored when an RTF group begins
  34. * or ends. */
  35. Dictionary parserState; /* Current parser state */
  36. /** This is the "dst" item from parserState. rtfDestination
  37. * is the current rtf destination. It is cached in an instance
  38. * variable for speed. */
  39. Destination rtfDestination;
  40. /** This holds the current document attributes. */
  41. MutableAttributeSet documentAttributes;
  42. /** This Dictionary maps Integer font numbers to String font names. */
  43. Dictionary fontTable;
  44. /** This array maps color indices to Color objects. */
  45. Color[] colorTable;
  46. /** This array maps character style numbers to Style objects. */
  47. Style[] characterStyles;
  48. /** This array maps paragraph style numbers to Style objects. */
  49. Style[] paragraphStyles;
  50. /** This array maps section style numbers to Style objects. */
  51. Style[] sectionStyles;
  52. /** This is the RTF version number, extracted from the \rtf keyword.
  53. * The version information is currently not used. */
  54. int rtfversion;
  55. /** <code>true</code> to indicate that if the next keyword is unknown,
  56. * the containing group should be ignored. */
  57. boolean ignoreGroupIfUnknownKeyword;
  58. /** The parameter of the most recently parsed \\ucN keyword,
  59. * used for skipping alternative representations after a
  60. * Unicode character. */
  61. int skippingCharacters;
  62. static private Dictionary straightforwardAttributes;
  63. static {
  64. straightforwardAttributes = RTFAttributes.attributesByKeyword();
  65. }
  66. private MockAttributeSet mockery;
  67. /* this should be final, but there's a bug in javac... */
  68. /** textKeywords maps RTF keywords to single-character strings,
  69. * for those keywords which simply insert some text. */
  70. static Dictionary textKeywords = null;
  71. static {
  72. textKeywords = new Hashtable();
  73. textKeywords.put("\\", "\\");
  74. textKeywords.put("{", "{");
  75. textKeywords.put("}", "}");
  76. textKeywords.put(" ", "\u00A0"); /* not in the spec... */
  77. textKeywords.put("~", "\u00A0"); /* nonbreaking space */
  78. textKeywords.put("_", "\u2011"); /* nonbreaking hyphen */
  79. textKeywords.put("bullet", "\u2022");
  80. textKeywords.put("emdash", "\u2014");
  81. textKeywords.put("emspace", "\u2003");
  82. textKeywords.put("endash", "\u2013");
  83. textKeywords.put("enspace", "\u2002");
  84. textKeywords.put("ldblquote", "\u201C");
  85. textKeywords.put("lquote", "\u2018");
  86. textKeywords.put("ltrmark", "\u200E");
  87. textKeywords.put("rdblquote", "\u201D");
  88. textKeywords.put("rquote", "\u2019");
  89. textKeywords.put("rtlmark", "\u200F");
  90. textKeywords.put("tab", "\u0009");
  91. textKeywords.put("zwj", "\u200D");
  92. textKeywords.put("zwnj", "\u200C");
  93. /* There is no Unicode equivalent to an optional hyphen, as far as
  94. I can tell. */
  95. textKeywords.put("-", "\u2027"); /* TODO: optional hyphen */
  96. }
  97. /* some entries in parserState */
  98. static final String TabAlignmentKey = "tab_alignment";
  99. static final String TabLeaderKey = "tab_leader";
  100. static Dictionary characterSets;
  101. static boolean useNeXTForAnsi = false;
  102. static {
  103. characterSets = new Hashtable();
  104. }
  105. /* TODO: per-font font encodings ( \fcharset control word ) ? */
  106. /**
  107. * Creates a new RTFReader instance. Text will be sent to
  108. * the specified TextAcceptor.
  109. *
  110. * @param destination The TextAcceptor which is to receive the text.
  111. */
  112. public RTFReader(StyledDocument destination)
  113. {
  114. int i;
  115. target = destination;
  116. parserState = new Hashtable();
  117. fontTable = new Hashtable();
  118. rtfversion = -1;
  119. mockery = new MockAttributeSet();
  120. documentAttributes = new SimpleAttributeSet();
  121. }
  122. /** Called when the RTFParser encounters a bin keyword in the
  123. * RTF stream.
  124. *
  125. * @see RTFParser
  126. */
  127. public void handleBinaryBlob(byte[] data)
  128. {
  129. if (skippingCharacters > 0) {
  130. /* a blob only counts as one character for skipping purposes */
  131. skippingCharacters --;
  132. return;
  133. }
  134. /* someday, someone will want to do something with blobs */
  135. }
  136. /**
  137. * Handles any pure text (containing no control characters) in the input
  138. * stream. Called by the superclass. */
  139. public void handleText(String text)
  140. {
  141. if (skippingCharacters > 0) {
  142. if (skippingCharacters >= text.length()) {
  143. skippingCharacters -= text.length();
  144. return;
  145. } else {
  146. text = text.substring(skippingCharacters);
  147. skippingCharacters = 0;
  148. }
  149. }
  150. if (rtfDestination != null) {
  151. rtfDestination.handleText(text);
  152. return;
  153. }
  154. warning("Text with no destination. oops.");
  155. }
  156. /** The default color for text which has no specified color. */
  157. Color defaultColor()
  158. {
  159. return Color.black;
  160. }
  161. /** Called by the superclass when a new RTF group is begun.
  162. * This implementation saves the current <code>parserState</code>, and gives
  163. * the current destination a chance to save its own state.
  164. * @see RTFParser#begingroup
  165. */
  166. public void begingroup()
  167. {
  168. if (skippingCharacters > 0) {
  169. /* TODO this indicates an error in the RTF. Log it? */
  170. skippingCharacters = 0;
  171. }
  172. /* we do this little dance to avoid cloning the entire state stack and
  173. immediately throwing it away. */
  174. Object oldSaveState = parserState.get("_savedState");
  175. if (oldSaveState != null)
  176. parserState.remove("_savedState");
  177. Dictionary saveState = (Dictionary)((Hashtable)parserState).clone();
  178. if (oldSaveState != null)
  179. saveState.put("_savedState", oldSaveState);
  180. parserState.put("_savedState", saveState);
  181. if (rtfDestination != null)
  182. rtfDestination.begingroup();
  183. }
  184. /** Called by the superclass when the current RTF group is closed.
  185. * This restores the parserState saved by <code>begingroup()</code>
  186. * as well as invoking the endgroup method of the current
  187. * destination.
  188. * @see RTFParser#endgroup
  189. */
  190. public void endgroup()
  191. {
  192. if (skippingCharacters > 0) {
  193. /* NB this indicates an error in the RTF. Log it? */
  194. skippingCharacters = 0;
  195. }
  196. Dictionary restoredState = (Dictionary)parserState.get("_savedState");
  197. Destination restoredDestination = (Destination)restoredState.get("dst");
  198. if (restoredDestination != rtfDestination) {
  199. rtfDestination.close(); /* allow the destination to clean up */
  200. rtfDestination = restoredDestination;
  201. }
  202. Dictionary oldParserState = parserState;
  203. parserState = restoredState;
  204. if (rtfDestination != null)
  205. rtfDestination.endgroup(oldParserState);
  206. }
  207. protected void setRTFDestination(Destination newDestination)
  208. {
  209. /* Check that setting the destination won't close the
  210. current destination (should never happen) */
  211. Dictionary previousState = (Dictionary)parserState.get("_savedState");
  212. if (previousState != null) {
  213. if (rtfDestination != previousState.get("dst")) {
  214. warning("Warning, RTF destination overridden, invalid RTF.");
  215. rtfDestination.close();
  216. }
  217. }
  218. rtfDestination = newDestination;
  219. parserState.put("dst", rtfDestination);
  220. }
  221. /** Called by the user when there is no more input (<i>i.e.</i>,
  222. * at the end of the RTF file.)
  223. *
  224. * @see OutputStream#close
  225. */
  226. public void close()
  227. throws IOException
  228. {
  229. Enumeration docProps = documentAttributes.getAttributeNames();
  230. while(docProps.hasMoreElements()) {
  231. Object propName = docProps.nextElement();
  232. target.putProperty(propName,
  233. documentAttributes.getAttribute((String)propName));
  234. }
  235. /* RTFParser should have ensured that all our groups are closed */
  236. warning("RTF filter done.");
  237. super.close();
  238. }
  239. /**
  240. * Handles a parameterless RTF keyword. This is called by the superclass
  241. * (RTFParser) when a keyword is found in the input stream.
  242. *
  243. * @returns <code>true</code> if the keyword is recognized and handled;
  244. * <code>false</code> otherwise
  245. * @see RTFParser#handleKeyword
  246. */
  247. public boolean handleKeyword(String keyword)
  248. {
  249. Object item;
  250. boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  251. if (skippingCharacters > 0) {
  252. skippingCharacters --;
  253. return true;
  254. }
  255. ignoreGroupIfUnknownKeyword = false;
  256. if ((item = textKeywords.get(keyword)) != null) {
  257. handleText((String)item);
  258. return true;
  259. }
  260. if (keyword.equals("fonttbl")) {
  261. setRTFDestination(new FonttblDestination());
  262. return true;
  263. }
  264. if (keyword.equals("colortbl")) {
  265. setRTFDestination(new ColortblDestination());
  266. return true;
  267. }
  268. if (keyword.equals("stylesheet")) {
  269. setRTFDestination(new StylesheetDestination());
  270. return true;
  271. }
  272. if (keyword.equals("info")) {
  273. setRTFDestination(new InfoDestination());
  274. return false;
  275. }
  276. if (keyword.equals("mac")) {
  277. setCharacterSet("mac");
  278. return true;
  279. }
  280. if (keyword.equals("ansi")) {
  281. if (useNeXTForAnsi)
  282. setCharacterSet("NeXT");
  283. else
  284. setCharacterSet("ansi");
  285. return true;
  286. }
  287. if (keyword.equals("next")) {
  288. setCharacterSet("NeXT");
  289. return true;
  290. }
  291. if (keyword.equals("pc")) {
  292. setCharacterSet("cpg437"); /* IBM Code Page 437 */
  293. return true;
  294. }
  295. if (keyword.equals("pca")) {
  296. setCharacterSet("cpg850"); /* IBM Code Page 850 */
  297. return true;
  298. }
  299. if (keyword.equals("*")) {
  300. ignoreGroupIfUnknownKeyword = true;
  301. return true;
  302. }
  303. if (rtfDestination != null) {
  304. if(rtfDestination.handleKeyword(keyword))
  305. return true;
  306. }
  307. /* this point is reached only if the keyword is unrecognized */
  308. /* other destinations we don't understand and therefore ignore */
  309. if (keyword.equals("aftncn") ||
  310. keyword.equals("aftnsep") ||
  311. keyword.equals("aftnsepc") ||
  312. keyword.equals("annotation") ||
  313. keyword.equals("atnauthor") ||
  314. keyword.equals("atnicn") ||
  315. keyword.equals("atnid") ||
  316. keyword.equals("atnref") ||
  317. keyword.equals("atntime") ||
  318. keyword.equals("atrfend") ||
  319. keyword.equals("atrfstart") ||
  320. keyword.equals("bkmkend") ||
  321. keyword.equals("bkmkstart") ||
  322. keyword.equals("datafield") ||
  323. keyword.equals("do") ||
  324. keyword.equals("dptxbxtext") ||
  325. keyword.equals("falt") ||
  326. keyword.equals("field") ||
  327. keyword.equals("file") ||
  328. keyword.equals("filetbl") ||
  329. keyword.equals("fname") ||
  330. keyword.equals("fontemb") ||
  331. keyword.equals("fontfile") ||
  332. keyword.equals("footer") ||
  333. keyword.equals("footerf") ||
  334. keyword.equals("footerl") ||
  335. keyword.equals("footerr") ||
  336. keyword.equals("footnote") ||
  337. keyword.equals("ftncn") ||
  338. keyword.equals("ftnsep") ||
  339. keyword.equals("ftnsepc") ||
  340. keyword.equals("header") ||
  341. keyword.equals("headerf") ||
  342. keyword.equals("headerl") ||
  343. keyword.equals("headerr") ||
  344. keyword.equals("keycode") ||
  345. keyword.equals("nextfile") ||
  346. keyword.equals("object") ||
  347. keyword.equals("pict") ||
  348. keyword.equals("pn") ||
  349. keyword.equals("pnseclvl") ||
  350. keyword.equals("pntxtb") ||
  351. keyword.equals("pntxta") ||
  352. keyword.equals("revtbl") ||
  353. keyword.equals("rxe") ||
  354. keyword.equals("tc") ||
  355. keyword.equals("template") ||
  356. keyword.equals("txe") ||
  357. keyword.equals("xe")) {
  358. ignoreGroupIfUnknownKeywordSave = true;
  359. }
  360. if (ignoreGroupIfUnknownKeywordSave) {
  361. setRTFDestination(new DiscardingDestination());
  362. }
  363. return false;
  364. }
  365. /**
  366. * Handles an RTF keyword and its integer parameter.
  367. * This is called by the superclass
  368. * (RTFParser) when a keyword is found in the input stream.
  369. *
  370. * @returns <code>true</code> if the keyword is recognized and handled;
  371. * <code>false</code> otherwise
  372. * @see RTFParser#handleKeyword
  373. */
  374. public boolean handleKeyword(String keyword, int parameter)
  375. {
  376. boolean ignoreGroupIfUnknownKeywordSave = ignoreGroupIfUnknownKeyword;
  377. if (skippingCharacters > 0) {
  378. skippingCharacters --;
  379. return true;
  380. }
  381. ignoreGroupIfUnknownKeyword = false;
  382. if (keyword.equals("uc")) {
  383. /* count of characters to skip after a unicode character */
  384. parserState.put("UnicodeSkip", new Integer(parameter));
  385. return true;
  386. }
  387. if (keyword.equals("u")) {
  388. if (parameter < 0)
  389. parameter = parameter + 65536;
  390. handleText((char)parameter);
  391. Number skip = (Number)(parserState.get("UnicodeSkip"));
  392. if (skip != null) {
  393. skippingCharacters = skip.intValue();
  394. } else {
  395. skippingCharacters = 1;
  396. }
  397. return true;
  398. }
  399. if (keyword.equals("rtf")) {
  400. rtfversion = parameter;
  401. setRTFDestination(new DocumentDestination());
  402. return true;
  403. }
  404. if (keyword.startsWith("NeXT") ||
  405. keyword.equals("private"))
  406. ignoreGroupIfUnknownKeywordSave = true;
  407. if (rtfDestination != null) {
  408. if(rtfDestination.handleKeyword(keyword, parameter))
  409. return true;
  410. }
  411. /* this point is reached only if the keyword is unrecognized */
  412. if (ignoreGroupIfUnknownKeywordSave) {
  413. setRTFDestination(new DiscardingDestination());
  414. }
  415. return false;
  416. }
  417. private void setTargetAttribute(String name, Object value)
  418. {
  419. // target.changeAttributes(new LFDictionary(LFArray.arrayWithObject(value), LFArray.arrayWithObject(name)));
  420. }
  421. /**
  422. * setCharacterSet sets the current translation table to correspond with
  423. * the named character set. The character set is loaded if necessary.
  424. *
  425. * @see AbstractFilter
  426. */
  427. public void setCharacterSet(String name)
  428. {
  429. Object set;
  430. try {
  431. set = getCharacterSet(name);
  432. } catch (Exception e) {
  433. warning("Exception loading RTF character set \"" + name + "\": " + e);
  434. set = null;
  435. }
  436. if (set != null) {
  437. translationTable = (char[])set;
  438. } else {
  439. warning("Unknown RTF character set \"" + name + "\"");
  440. if (!name.equals("ansi")) {
  441. try {
  442. translationTable = (char[])getCharacterSet("ansi");
  443. } catch (IOException e) {
  444. throw new InternalError("RTFReader: Unable to find character set resources (" + e + ")");
  445. }
  446. }
  447. }
  448. setTargetAttribute(Constants.RTFCharacterSet, name);
  449. }
  450. /** Adds a character set to the RTFReader's list
  451. * of known character sets */
  452. public static void
  453. defineCharacterSet(String name, char[] table)
  454. {
  455. if (table.length < 256)
  456. throw new IllegalArgumentException("Translation table must have 256 entries.");
  457. characterSets.put(name, table);
  458. }
  459. /** Looks up a named character set. A character set is a 256-entry
  460. * array of characters, mapping unsigned byte values to their Unicode
  461. * equivalents. The character set is loaded if necessary.
  462. *
  463. * @returns the character set
  464. */
  465. public static Object
  466. getCharacterSet(final String name)
  467. throws IOException
  468. {
  469. char[] set;
  470. set = (char [])characterSets.get(name);
  471. if (set == null) {
  472. InputStream charsetStream;
  473. charsetStream = (InputStream)java.security.AccessController.
  474. doPrivileged(new java.security.PrivilegedAction() {
  475. public Object run() {
  476. return RTFReader.class.getResourceAsStream
  477. ("charsets/" + name + ".txt");
  478. }
  479. });
  480. set = readCharset(charsetStream);
  481. defineCharacterSet(name, set);
  482. }
  483. return set;
  484. }
  485. /** Parses a character set from an InputStream. The character set
  486. * must contain 256 decimal integers, separated by whitespace, with
  487. * no punctuation. B- and C- style comments are allowed.
  488. *
  489. * @returns the newly read character set
  490. */
  491. static char[] readCharset(InputStream strm)
  492. throws IOException
  493. {
  494. char[] values = new char[256];
  495. int i;
  496. StreamTokenizer in = new StreamTokenizer(new BufferedReader(
  497. new InputStreamReader(strm)));
  498. in.eolIsSignificant(false);
  499. in.commentChar('#');
  500. in.slashSlashComments(true);
  501. in.slashStarComments(true);
  502. i = 0;
  503. while (i < 256) {
  504. int ttype;
  505. try {
  506. ttype = in.nextToken();
  507. } catch (Exception e) {
  508. throw new IOException("Unable to read from character set file (" + e + ")");
  509. }
  510. if (ttype != in.TT_NUMBER) {
  511. // System.out.println("Bad token: type=" + ttype + " tok=" + in.sval);
  512. throw new IOException("Unexpected token in character set file");
  513. // continue;
  514. }
  515. values[i] = (char)(in.nval);
  516. i++;
  517. }
  518. return values;
  519. }
  520. static char[] readCharset(java.net.URL href)
  521. throws IOException
  522. {
  523. return readCharset(href.openStream());
  524. }
  525. /** An interface (could be an entirely abstract class) describing
  526. * a destination. The RTF reader always has a current destination
  527. * which is where text is sent.
  528. *
  529. * @see RTFReader
  530. */
  531. interface Destination {
  532. void handleBinaryBlob(byte[] data);
  533. void handleText(String text);
  534. boolean handleKeyword(String keyword);
  535. boolean handleKeyword(String keyword, int parameter);
  536. void begingroup();
  537. void endgroup(Dictionary oldState);
  538. void close();
  539. }
  540. /** This data-sink class is used to implement ignored destinations
  541. * (e.g. {\*\blegga blah blah blah} )
  542. * It accepts all keywords and text but does nothing with them. */
  543. class DiscardingDestination implements Destination
  544. {
  545. public void handleBinaryBlob(byte[] data)
  546. {
  547. /* Discard binary blobs. */
  548. }
  549. public void handleText(String text)
  550. {
  551. /* Discard text. */
  552. }
  553. public boolean handleKeyword(String text)
  554. {
  555. /* Accept and discard keywords. */
  556. return true;
  557. }
  558. public boolean handleKeyword(String text, int parameter)
  559. {
  560. /* Accept and discard parameterized keywords. */
  561. return true;
  562. }
  563. public void begingroup()
  564. {
  565. /* Ignore groups --- the RTFReader will keep track of the
  566. current group level as necessary */
  567. }
  568. public void endgroup(Dictionary oldState)
  569. {
  570. /* Ignore groups */
  571. }
  572. public void close()
  573. {
  574. /* No end-of-destination cleanup needed */
  575. }
  576. }
  577. /** Reads the fonttbl group, inserting fonts into the RTFReader's
  578. * fontTable dictionary. */
  579. class FonttblDestination implements Destination
  580. {
  581. int nextFontNumber;
  582. String nextFontFamily;
  583. public void handleBinaryBlob(byte[] data)
  584. { /* Discard binary blobs. */ }
  585. /* TODO do these routines work correctly if a write buffer divides a
  586. font name? (Probably not. Should allow for it as rare case) */
  587. public void handleText(String text)
  588. {
  589. int semicolon = text.indexOf(';');
  590. String fontName;
  591. Object fontNum; /* an Integer, but we don't care */
  592. if (semicolon > 0)
  593. fontName = text.substring(0, semicolon);
  594. else
  595. fontName = text;
  596. /* TODO: do something with the font family. */
  597. fontTable.put(new Integer(nextFontNumber), fontName);
  598. nextFontNumber = -1;
  599. nextFontFamily = null;
  600. return;
  601. }
  602. public boolean handleKeyword(String keyword)
  603. {
  604. if (keyword.charAt(0) == 'f') {
  605. nextFontFamily = keyword.substring(1);
  606. return true;
  607. }
  608. return false;
  609. }
  610. public boolean handleKeyword(String keyword, int parameter)
  611. {
  612. if (keyword.equals("f")) {
  613. nextFontNumber = parameter;
  614. return true;
  615. }
  616. return false;
  617. }
  618. /* Groups are irrelevant. */
  619. public void begingroup() {}
  620. public void endgroup(Dictionary oldState) {}
  621. /* currently, the only thing we do when the font table ends is
  622. dump its contents to the debugging log. */
  623. public void close()
  624. {
  625. Enumeration nums = fontTable.keys();
  626. warning("Done reading font table.");
  627. while(nums.hasMoreElements()) {
  628. Integer num = (Integer)nums.nextElement();
  629. warning("Number " + num + ": " + fontTable.get(num));
  630. }
  631. }
  632. }
  633. /** Reads the colortbl group. Upon end-of-group, the RTFReader's
  634. * color table is set to an array containing the read colors. */
  635. class ColortblDestination implements Destination
  636. {
  637. int red, green, blue;
  638. Vector proTemTable;
  639. public ColortblDestination()
  640. {
  641. red = 0;
  642. green = 0;
  643. blue = 0;
  644. proTemTable = new Vector();
  645. }
  646. public void handleText(String text)
  647. {
  648. int index = 0;
  649. for (index = 0; index < text.length(); index ++) {
  650. if (text.charAt(index) == ';') {
  651. Color newColor;
  652. newColor = new Color(red, green, blue);
  653. proTemTable.addElement(newColor);
  654. }
  655. }
  656. }
  657. public void close()
  658. {
  659. int count = proTemTable.size();
  660. warning("Done reading color table, " + count + " entries.");
  661. colorTable = new Color[count];
  662. proTemTable.copyInto(colorTable);
  663. }
  664. public boolean handleKeyword(String keyword, int parameter)
  665. {
  666. if (keyword.equals("red"))
  667. red = parameter;
  668. else if (keyword.equals("green"))
  669. green = parameter;
  670. else if (keyword.equals("blue"))
  671. blue = parameter;
  672. else
  673. return false;
  674. return true;
  675. }
  676. /* Colortbls don't understand any parameterless keywords */
  677. public boolean handleKeyword(String keyword) { return false; }
  678. /* Groups are irrelevant. */
  679. public void begingroup() {}
  680. public void endgroup(Dictionary oldState) {}
  681. /* Shouldn't see any binary blobs ... */
  682. public void handleBinaryBlob(byte[] data) {}
  683. }
  684. /** Handles the stylesheet keyword. Styles are read and sorted
  685. * into the three style arrays in the RTFReader. */
  686. class StylesheetDestination
  687. extends DiscardingDestination
  688. implements Destination
  689. {
  690. Dictionary definedStyles;
  691. public StylesheetDestination()
  692. {
  693. definedStyles = new Hashtable();
  694. }
  695. public void begingroup()
  696. {
  697. setRTFDestination(new StyleDefiningDestination());
  698. }
  699. public void close()
  700. {
  701. Vector chrStyles, pgfStyles, secStyles;
  702. chrStyles = new Vector();
  703. pgfStyles = new Vector();
  704. secStyles = new Vector();
  705. Enumeration styles = definedStyles.elements();
  706. while(styles.hasMoreElements()) {
  707. StyleDefiningDestination style;
  708. Style defined;
  709. style = (StyleDefiningDestination)styles.nextElement();
  710. defined = style.realize();
  711. warning("Style "+style.number+" ("+style.styleName+"): "+defined);
  712. String stype = (String)defined.getAttribute(Constants.StyleType);
  713. Vector toSet;
  714. if (stype.equals(Constants.STSection)) {
  715. toSet = secStyles;
  716. } else if (stype.equals(Constants.STCharacter)) {
  717. toSet = chrStyles;
  718. } else {
  719. toSet = pgfStyles;
  720. }
  721. if (toSet.size() <= style.number)
  722. toSet.setSize(style.number + 1);
  723. toSet.setElementAt(defined, style.number);
  724. }
  725. if (!(chrStyles.isEmpty())) {
  726. Style[] styleArray = new Style[chrStyles.size()];
  727. chrStyles.copyInto(styleArray);
  728. characterStyles = styleArray;
  729. }
  730. if (!(pgfStyles.isEmpty())) {
  731. Style[] styleArray = new Style[pgfStyles.size()];
  732. pgfStyles.copyInto(styleArray);
  733. paragraphStyles = styleArray;
  734. }
  735. if (!(secStyles.isEmpty())) {
  736. Style[] styleArray = new Style[secStyles.size()];
  737. secStyles.copyInto(styleArray);
  738. sectionStyles = styleArray;
  739. }
  740. /* (old debugging code)
  741. int i, m;
  742. if (characterStyles != null) {
  743. m = characterStyles.length;
  744. for(i=0;i<m;i++)
  745. warnings.println("chrStyle["+i+"]="+characterStyles[i]);
  746. } else warnings.println("No character styles.");
  747. if (paragraphStyles != null) {
  748. m = paragraphStyles.length;
  749. for(i=0;i<m;i++)
  750. warnings.println("pgfStyle["+i+"]="+paragraphStyles[i]);
  751. } else warnings.println("No paragraph styles.");
  752. if (sectionStyles != null) {
  753. m = characterStyles.length;
  754. for(i=0;i<m;i++)
  755. warnings.println("secStyle["+i+"]="+sectionStyles[i]);
  756. } else warnings.println("No section styles.");
  757. */
  758. }
  759. /** This subclass handles an individual style */
  760. class StyleDefiningDestination
  761. extends AttributeTrackingDestination
  762. implements Destination
  763. {
  764. final int STYLENUMBER_NONE = 222;
  765. boolean additive;
  766. boolean characterStyle;
  767. boolean sectionStyle;
  768. public String styleName;
  769. public int number;
  770. int basedOn;
  771. int nextStyle;
  772. boolean hidden;
  773. Style realizedStyle;
  774. public StyleDefiningDestination()
  775. {
  776. additive = false;
  777. characterStyle = false;
  778. sectionStyle = false;
  779. styleName = null;
  780. number = 0;
  781. basedOn = STYLENUMBER_NONE;
  782. nextStyle = STYLENUMBER_NONE;
  783. hidden = false;
  784. }
  785. public void handleText(String text)
  786. {
  787. if (styleName != null)
  788. styleName = styleName + text;
  789. else
  790. styleName = text;
  791. }
  792. public void close() {
  793. int semicolon = styleName.indexOf(';');
  794. if (semicolon > 0)
  795. styleName = styleName.substring(0, semicolon);
  796. definedStyles.put(new Integer(number), this);
  797. super.close();
  798. }
  799. public boolean handleKeyword(String keyword)
  800. {
  801. if (keyword.equals("additive")) {
  802. additive = true;
  803. return true;
  804. }
  805. if (keyword.equals("shidden")) {
  806. hidden = true;
  807. return true;
  808. }
  809. return super.handleKeyword(keyword);
  810. }
  811. public boolean handleKeyword(String keyword, int parameter)
  812. {
  813. if (keyword.equals("s")) {
  814. characterStyle = false;
  815. sectionStyle = false;
  816. number = parameter;
  817. } else if (keyword.equals("cs")) {
  818. characterStyle = true;
  819. sectionStyle = false;
  820. number = parameter;
  821. } else if (keyword.equals("ds")) {
  822. characterStyle = false;
  823. sectionStyle = true;
  824. number = parameter;
  825. } else if (keyword.equals("sbasedon")) {
  826. basedOn = parameter;
  827. } else if (keyword.equals("snext")) {
  828. nextStyle = parameter;
  829. } else {
  830. return super.handleKeyword(keyword, parameter);
  831. }
  832. return true;
  833. }
  834. public Style realize()
  835. {
  836. Style basis = null;
  837. Style next = null;
  838. if (realizedStyle != null)
  839. return realizedStyle;
  840. if (basedOn != STYLENUMBER_NONE) {
  841. StyleDefiningDestination styleDest;
  842. styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(basedOn));
  843. if (styleDest != null) {
  844. basis = styleDest.realize();
  845. }
  846. }
  847. /* NB: Swing StyleContext doesn't allow distinct styles with
  848. the same name; RTF apparently does. This may confuse the
  849. user. */
  850. realizedStyle = target.addStyle(styleName, basis);
  851. if (characterStyle) {
  852. realizedStyle.addAttributes(currentTextAttributes());
  853. realizedStyle.addAttribute(Constants.StyleType,
  854. Constants.STCharacter);
  855. } else if (sectionStyle) {
  856. realizedStyle.addAttributes(currentSectionAttributes());
  857. realizedStyle.addAttribute(Constants.StyleType,
  858. Constants.STSection);
  859. } else { /* must be a paragraph style */
  860. realizedStyle.addAttributes(currentParagraphAttributes());
  861. realizedStyle.addAttribute(Constants.StyleType,
  862. Constants.STParagraph);
  863. }
  864. if (nextStyle != STYLENUMBER_NONE) {
  865. StyleDefiningDestination styleDest;
  866. styleDest = (StyleDefiningDestination)definedStyles.get(new Integer(nextStyle));
  867. if (styleDest != null) {
  868. next = styleDest.realize();
  869. }
  870. }
  871. if (next != null)
  872. realizedStyle.addAttribute(Constants.StyleNext, next);
  873. realizedStyle.addAttribute(Constants.StyleAdditive,
  874. Boolean.valueOf(additive));
  875. realizedStyle.addAttribute(Constants.StyleHidden,
  876. Boolean.valueOf(hidden));
  877. return realizedStyle;
  878. }
  879. }
  880. }
  881. /** Handles the info group. Currently no info keywords are recognized
  882. * so this is a subclass of DiscardingDestination. */
  883. class InfoDestination
  884. extends DiscardingDestination
  885. implements Destination
  886. {
  887. }
  888. /** RTFReader.TextHandlingDestination is an abstract RTF destination
  889. * which simply tracks the attributes specified by the RTF control words
  890. * in internal form and can produce acceptable AttributeSets for the
  891. * current character, paragraph, and section attributes. It is up
  892. * to the subclasses to determine what is done with the actual text. */
  893. abstract class AttributeTrackingDestination implements Destination
  894. {
  895. /** This is the "chr" element of parserState, cached for
  896. * more efficient use */
  897. MutableAttributeSet characterAttributes;
  898. /** This is the "pgf" element of parserState, cached for
  899. * more efficient use */
  900. MutableAttributeSet paragraphAttributes;
  901. /** This is the "sec" element of parserState, cached for
  902. * more efficient use */
  903. MutableAttributeSet sectionAttributes;
  904. public AttributeTrackingDestination()
  905. {
  906. characterAttributes = rootCharacterAttributes();
  907. parserState.put("chr", characterAttributes);
  908. paragraphAttributes = rootParagraphAttributes();
  909. parserState.put("pgf", paragraphAttributes);
  910. sectionAttributes = rootSectionAttributes();
  911. parserState.put("sec", sectionAttributes);
  912. }
  913. abstract public void handleText(String text);
  914. public void handleBinaryBlob(byte[] data)
  915. {
  916. /* This should really be in TextHandlingDestination, but
  917. * since *nobody* does anything with binary blobs, this
  918. * is more convenient. */
  919. warning("Unexpected binary data in RTF file.");
  920. }
  921. public void begingroup()
  922. {
  923. AttributeSet characterParent = currentTextAttributes();
  924. AttributeSet paragraphParent = currentParagraphAttributes();
  925. AttributeSet sectionParent = currentSectionAttributes();
  926. /* It would probably be more efficient to use the
  927. * resolver property of the attributes set for
  928. * implementing rtf groups,
  929. * but that's needed for styles. */
  930. /* update the cached attribute dictionaries */
  931. characterAttributes = new SimpleAttributeSet();
  932. characterAttributes.addAttributes(characterParent);
  933. parserState.put("chr", characterAttributes);
  934. paragraphAttributes = new SimpleAttributeSet();
  935. paragraphAttributes.addAttributes(paragraphParent);
  936. parserState.put("pgf", paragraphAttributes);
  937. sectionAttributes = new SimpleAttributeSet();
  938. sectionAttributes.addAttributes(sectionParent);
  939. parserState.put("sec", sectionAttributes);
  940. }
  941. public void endgroup(Dictionary oldState)
  942. {
  943. characterAttributes = (MutableAttributeSet)parserState.get("chr");
  944. paragraphAttributes = (MutableAttributeSet)parserState.get("pgf");
  945. sectionAttributes = (MutableAttributeSet)parserState.get("sec");
  946. }
  947. public void close()
  948. {
  949. }
  950. public boolean handleKeyword(String keyword)
  951. {
  952. if (keyword.equals("ulnone")) {
  953. return handleKeyword("ul", 0);
  954. }
  955. {
  956. Object item = straightforwardAttributes.get(keyword);
  957. if (item != null) {
  958. RTFAttribute attr = (RTFAttribute)item;
  959. boolean ok;
  960. switch(attr.domain()) {
  961. case RTFAttribute.D_CHARACTER:
  962. ok = attr.set(characterAttributes);
  963. break;
  964. case RTFAttribute.D_PARAGRAPH:
  965. ok = attr.set(paragraphAttributes);
  966. break;
  967. case RTFAttribute.D_SECTION:
  968. ok = attr.set(sectionAttributes);
  969. break;
  970. case RTFAttribute.D_META:
  971. mockery.backing = parserState;
  972. ok = attr.set(mockery);
  973. mockery.backing = null;
  974. break;
  975. case RTFAttribute.D_DOCUMENT:
  976. ok = attr.set(documentAttributes);
  977. break;
  978. default:
  979. /* should never happen */
  980. ok = false;
  981. break;
  982. }
  983. if (ok)
  984. return true;
  985. }
  986. }
  987. if (keyword.equals("plain")) {
  988. resetCharacterAttributes();
  989. return true;
  990. }
  991. if (keyword.equals("pard")) {
  992. resetParagraphAttributes();
  993. return true;
  994. }
  995. if (keyword.equals("sectd")) {
  996. resetSectionAttributes();
  997. return true;
  998. }
  999. return false;
  1000. }
  1001. public boolean handleKeyword(String keyword, int parameter)
  1002. {
  1003. boolean booleanParameter = (parameter != 0);
  1004. if (keyword.equals("fc"))
  1005. keyword = "cf"; /* whatEVER, dude. */
  1006. if (keyword.equals("f")) {
  1007. parserState.put(keyword, new Integer(parameter));
  1008. return true;
  1009. }
  1010. if (keyword.equals("cf")) {
  1011. parserState.put(keyword, new Integer(parameter));
  1012. return true;
  1013. }
  1014. {
  1015. Object item = straightforwardAttributes.get(keyword);
  1016. if (item != null) {
  1017. RTFAttribute attr = (RTFAttribute)item;
  1018. boolean ok;
  1019. switch(attr.domain()) {
  1020. case RTFAttribute.D_CHARACTER:
  1021. ok = attr.set(characterAttributes, parameter);
  1022. break;
  1023. case RTFAttribute.D_PARAGRAPH:
  1024. ok = attr.set(paragraphAttributes, parameter);
  1025. break;
  1026. case RTFAttribute.D_SECTION:
  1027. ok = attr.set(sectionAttributes, parameter);
  1028. break;
  1029. case RTFAttribute.D_META:
  1030. mockery.backing = parserState;
  1031. ok = attr.set(mockery, parameter);
  1032. mockery.backing = null;
  1033. break;
  1034. case RTFAttribute.D_DOCUMENT:
  1035. ok = attr.set(documentAttributes, parameter);
  1036. break;
  1037. default:
  1038. /* should never happen */
  1039. ok = false;
  1040. break;
  1041. }
  1042. if (ok)
  1043. return true;
  1044. }
  1045. }
  1046. if (keyword.equals("fs")) {
  1047. StyleConstants.setFontSize(characterAttributes, (parameter / 2));
  1048. return true;
  1049. }
  1050. /* TODO: superscript/subscript */
  1051. if (keyword.equals("sl")) {
  1052. if (parameter == 1000) { /* magic value! */
  1053. characterAttributes.removeAttribute(StyleConstants.LineSpacing);
  1054. } else {
  1055. /* TODO: The RTF sl attribute has special meaning if it's
  1056. negative. Make sure that SwingText has the same special
  1057. meaning, or find a way to imitate that. When SwingText
  1058. handles this, also recognize the slmult keyword. */
  1059. StyleConstants.setLineSpacing(characterAttributes,
  1060. parameter / 20f);
  1061. }
  1062. return true;
  1063. }
  1064. /* TODO: Other kinds of underlining */
  1065. if (keyword.equals("tx") || keyword.equals("tb")) {
  1066. float tabPosition = parameter / 20f;
  1067. int tabAlignment, tabLeader;
  1068. Number item;
  1069. tabAlignment = TabStop.ALIGN_LEFT;
  1070. item = (Number)(parserState.get("tab_alignment"));
  1071. if (item != null)
  1072. tabAlignment = item.intValue();
  1073. tabLeader = TabStop.LEAD_NONE;
  1074. item = (Number)(parserState.get("tab_leader"));
  1075. if (item != null)
  1076. tabLeader = item.intValue();
  1077. if (keyword.equals("tb"))
  1078. tabAlignment = TabStop.ALIGN_BAR;
  1079. parserState.remove("tab_alignment");
  1080. parserState.remove("tab_leader");
  1081. TabStop newStop = new TabStop(tabPosition, tabAlignment, tabLeader);
  1082. Dictionary tabs;
  1083. Integer stopCount;
  1084. tabs = (Dictionary)parserState.get("_tabs");
  1085. if (tabs == null) {
  1086. tabs = new Hashtable();
  1087. parserState.put("_tabs", tabs);
  1088. stopCount = new Integer(1);
  1089. } else {
  1090. stopCount = (Integer)tabs.get("stop count");
  1091. stopCount = new Integer(1 + stopCount.intValue());
  1092. }
  1093. tabs.put(stopCount, newStop);
  1094. tabs.put("stop count", stopCount);
  1095. parserState.remove("_tabs_immutable");
  1096. return true;
  1097. }
  1098. if (keyword.equals("s") &&
  1099. paragraphStyles != null) {
  1100. parserState.put("paragraphStyle", paragraphStyles[parameter]);
  1101. return true;
  1102. }
  1103. if (keyword.equals("cs") &&
  1104. characterStyles != null) {
  1105. parserState.put("characterStyle", characterStyles[parameter]);
  1106. return true;
  1107. }
  1108. if (keyword.equals("ds") &&
  1109. sectionStyles != null) {
  1110. parserState.put("sectionStyle", sectionStyles[parameter]);
  1111. return true;
  1112. }
  1113. return false;
  1114. }
  1115. /** Returns a new MutableAttributeSet containing the
  1116. * default character attributes */
  1117. protected MutableAttributeSet rootCharacterAttributes()
  1118. {
  1119. MutableAttributeSet set = new SimpleAttributeSet();
  1120. /* TODO: default font */
  1121. StyleConstants.setItalic(set, false);
  1122. StyleConstants.setBold(set, false);
  1123. StyleConstants.setUnderline(set, false);
  1124. StyleConstants.setForeground(set, defaultColor());
  1125. return set;
  1126. }
  1127. /** Returns a new MutableAttributeSet containing the
  1128. * default paragraph attributes */
  1129. protected MutableAttributeSet rootParagraphAttributes()
  1130. {
  1131. MutableAttributeSet set = new SimpleAttributeSet();
  1132. StyleConstants.setLeftIndent(set, 0f);
  1133. StyleConstants.setRightIndent(set, 0f);
  1134. StyleConstants.setFirstLineIndent(set, 0f);
  1135. /* TODO: what should this be, really? */
  1136. set.setResolveParent(target.getStyle(StyleContext.DEFAULT_STYLE));
  1137. return set;
  1138. }
  1139. /** Returns a new MutableAttributeSet containing the
  1140. * default section attributes */
  1141. protected MutableAttributeSet rootSectionAttributes()
  1142. {
  1143. MutableAttributeSet set = new SimpleAttributeSet();
  1144. return set;
  1145. }
  1146. /**
  1147. * Calculates the current text (character) attributes in a form suitable
  1148. * for SwingText from the current parser state.
  1149. *
  1150. * @returns a new MutableAttributeSet containing the text attributes.
  1151. */
  1152. MutableAttributeSet currentTextAttributes()
  1153. {
  1154. MutableAttributeSet attributes =
  1155. new SimpleAttributeSet(characterAttributes);
  1156. Integer fontnum;
  1157. Integer stateItem;
  1158. /* figure out the font name */
  1159. /* TODO: catch exceptions for undefined attributes,
  1160. bad font indices, etc.? (as it stands, it is the caller's
  1161. job to clean up after corrupt RTF) */
  1162. fontnum = (Integer)parserState.get("f");
  1163. /* note setFontFamily() can not handle a null font */
  1164. String fontFamily;
  1165. if (fontnum != null)
  1166. fontFamily = (String)fontTable.get(fontnum);
  1167. else
  1168. fontFamily = null;
  1169. if (fontFamily != null)
  1170. StyleConstants.setFontFamily(attributes, fontFamily);
  1171. else
  1172. attributes.removeAttribute(StyleConstants.FontFamily);
  1173. if (colorTable != null) {
  1174. stateItem = (Integer)parserState.get("cf");
  1175. if (stateItem != null) {
  1176. Color fg = colorTable[stateItem.intValue()];
  1177. StyleConstants.setForeground(attributes, fg);
  1178. } else {
  1179. /* AttributeSet dies if you set a value to null */
  1180. attributes.removeAttribute(StyleConstants.Foreground);
  1181. }
  1182. }
  1183. if (colorTable != null) {
  1184. stateItem = (Integer)parserState.get("cb");
  1185. if (stateItem != null) {
  1186. Color bg = colorTable[stateItem.intValue()];
  1187. attributes.addAttribute(StyleConstants.Background,
  1188. bg);
  1189. } else {
  1190. /* AttributeSet dies if you set a value to null */
  1191. attributes.removeAttribute(StyleConstants.Background);
  1192. }
  1193. }
  1194. Style characterStyle = (Style)parserState.get("characterStyle");
  1195. if (characterStyle != null)
  1196. attributes.setResolveParent(characterStyle);
  1197. /* Other attributes are maintained directly in "attributes" */
  1198. return attributes;
  1199. }
  1200. /**
  1201. * Calculates the current paragraph attributes (with keys
  1202. * as given in StyleConstants) from the current parser state.
  1203. *
  1204. * @returns a newly created MutableAttributeSet.
  1205. * @see StyleConstants
  1206. */
  1207. MutableAttributeSet currentParagraphAttributes()
  1208. {
  1209. /* NB if there were a mutableCopy() method we should use it */
  1210. MutableAttributeSet bld = new SimpleAttributeSet(paragraphAttributes);
  1211. Integer stateItem;
  1212. /*** Tab stops ***/
  1213. TabStop tabs[];
  1214. tabs = (TabStop[])parserState.get("_tabs_immutable");
  1215. if (tabs == null) {
  1216. Dictionary workingTabs = (Dictionary)parserState.get("_tabs");
  1217. if (workingTabs != null) {
  1218. int count = ((Integer)workingTabs.get("stop count")).intValue();
  1219. tabs = new TabStop[count];
  1220. for (int ix = 1; ix <= count; ix ++)
  1221. tabs[ix-1] = (TabStop)workingTabs.get(new Integer(ix));
  1222. parserState.put("_tabs_immutable", tabs);
  1223. }
  1224. }
  1225. if (tabs != null)
  1226. bld.addAttribute(Constants.Tabs, tabs);
  1227. Style paragraphStyle = (Style)parserState.get("paragraphStyle");
  1228. if (paragraphStyle != null)
  1229. bld.setResolveParent(paragraphStyle);
  1230. return bld;
  1231. }
  1232. /**
  1233. * Calculates the current section attributes
  1234. * from the current parser state.
  1235. *
  1236. * @returns a newly created MutableAttributeSet.
  1237. */
  1238. public AttributeSet currentSectionAttributes()
  1239. {
  1240. MutableAttributeSet attributes = new SimpleAttributeSet(sectionAttributes);
  1241. Style sectionStyle = (Style)parserState.get("sectionStyle");
  1242. if (sectionStyle != null)
  1243. attributes.setResolveParent(sectionStyle);
  1244. return attributes;
  1245. }
  1246. /** Resets the filter's internal notion of the current character
  1247. * attributes to their default values. Invoked to handle the
  1248. * \plain keyword. */
  1249. protected void resetCharacterAttributes()
  1250. {
  1251. handleKeyword("f", 0);
  1252. handleKeyword("cf", 0);
  1253. handleKeyword("fs", 24); /* 12 pt. */
  1254. Enumeration attributes = straightforwardAttributes.elements();
  1255. while(attributes.hasMoreElements()) {
  1256. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1257. if (attr.domain() == RTFAttribute.D_CHARACTER)
  1258. attr.setDefault(characterAttributes);
  1259. }
  1260. handleKeyword("sl", 1000);
  1261. parserState.remove("characterStyle");
  1262. }
  1263. /** Resets the filter's internal notion of the current paragraph's
  1264. * attributes to their default values. Invoked to handle the
  1265. * \pard keyword. */
  1266. protected void resetParagraphAttributes()
  1267. {
  1268. parserState.remove("_tabs");
  1269. parserState.remove("_tabs_immutable");
  1270. parserState.remove("paragraphStyle");
  1271. StyleConstants.setAlignment(paragraphAttributes,
  1272. StyleConstants.ALIGN_LEFT);
  1273. Enumeration attributes = straightforwardAttributes.elements();
  1274. while(attributes.hasMoreElements()) {
  1275. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1276. if (attr.domain() == RTFAttribute.D_PARAGRAPH)
  1277. attr.setDefault(characterAttributes);
  1278. }
  1279. }
  1280. /** Resets the filter's internal notion of the current section's
  1281. * attributes to their default values. Invoked to handle the
  1282. * \sectd keyword. */
  1283. protected void resetSectionAttributes()
  1284. {
  1285. Enumeration attributes = straightforwardAttributes.elements();
  1286. while(attributes.hasMoreElements()) {
  1287. RTFAttribute attr = (RTFAttribute)attributes.nextElement();
  1288. if (attr.domain() == RTFAttribute.D_SECTION)
  1289. attr.setDefault(characterAttributes);
  1290. }
  1291. parserState.remove("sectionStyle");
  1292. }
  1293. }
  1294. /** RTFReader.TextHandlingDestination provides basic text handling
  1295. * functionality. Subclasses must implement: <dl>
  1296. * <dt>deliverText()<dd>to handle a run of text with the same
  1297. * attributes
  1298. * <dt>finishParagraph()<dd>to end the current paragraph and
  1299. * set the paragraph's attributes
  1300. * <dt>endSection()<dd>to end the current section
  1301. * </dl>
  1302. */
  1303. abstract class TextHandlingDestination
  1304. extends AttributeTrackingDestination
  1305. implements Destination
  1306. {
  1307. /** <code>true</code> if the reader has not just finished
  1308. * a paragraph; false upon startup */
  1309. boolean inParagraph;
  1310. public TextHandlingDestination()
  1311. {
  1312. super();
  1313. inParagraph = false;
  1314. }
  1315. public void handleText(String text)
  1316. {
  1317. if (! inParagraph)
  1318. beginParagraph();
  1319. deliverText(text, currentTextAttributes());
  1320. }
  1321. abstract void deliverText(String text, AttributeSet characterAttributes);
  1322. public void close()
  1323. {
  1324. if (inParagraph)
  1325. endParagraph();
  1326. super.close();
  1327. }
  1328. public boolean handleKeyword(String keyword)
  1329. {
  1330. if (keyword.equals("\r") || keyword.equals("\n")) {
  1331. keyword = "par";
  1332. }
  1333. if (keyword.equals("par")) {
  1334. // warnings.println("Ending paragraph.");
  1335. endParagraph();
  1336. return true;
  1337. }
  1338. if (keyword.equals("sect")) {
  1339. // warnings.println("Ending section.");
  1340. endSection();
  1341. return true;
  1342. }
  1343. return super.handleKeyword(keyword);
  1344. }
  1345. protected void beginParagraph()
  1346. {
  1347. inParagraph = true;
  1348. }
  1349. protected void endParagraph()
  1350. {
  1351. AttributeSet pgfAttributes = currentParagraphAttributes();
  1352. AttributeSet chrAttributes = currentTextAttributes();
  1353. finishParagraph(pgfAttributes, chrAttributes);
  1354. inParagraph = false;
  1355. }
  1356. abstract void finishParagraph(AttributeSet pgfA, AttributeSet chrA);
  1357. abstract void endSection();
  1358. }
  1359. /** RTFReader.DocumentDestination is a concrete subclass of
  1360. * TextHandlingDestination which appends the text to the
  1361. * StyledDocument given by the <code>target</code> ivar of the
  1362. * containing RTFReader.
  1363. */
  1364. class DocumentDestination
  1365. extends TextHandlingDestination
  1366. implements Destination
  1367. {
  1368. public void deliverText(String text, AttributeSet characterAttributes)
  1369. {
  1370. try {
  1371. target.insertString(target.getLength(),
  1372. text,
  1373. currentTextAttributes());
  1374. } catch (BadLocationException ble) {
  1375. /* This shouldn't be able to happen, of course */
  1376. /* TODO is InternalError the correct error to throw? */
  1377. throw new InternalError(ble.getMessage());
  1378. }
  1379. }
  1380. public void finishParagraph(AttributeSet pgfAttributes,
  1381. AttributeSet chrAttributes)
  1382. {
  1383. int pgfEndPosition = target.getLength();
  1384. try {
  1385. target.insertString(pgfEndPosition, "\n", chrAttributes);
  1386. target.setParagraphAttributes(pgfEndPosition, 1, pgfAttributes, true);
  1387. } catch (BadLocationException ble) {
  1388. /* This shouldn't be able to happen, of course */
  1389. /* TODO is InternalError the correct error to throw? */
  1390. throw new InternalError(ble.getMessage());
  1391. }
  1392. }
  1393. public void endSection()
  1394. {
  1395. /* If we implemented sections, we'd end 'em here */
  1396. }
  1397. }
  1398. }