001: /*
002: * $Id: RtfDocument.java 2808 2007-05-30 10:58:16Z psoares33 $
003: * $Name: $
004: *
005: * Copyright 2003, 2004, 2005 by Mark Hall
006: *
007: * The contents of this file are subject to the Mozilla Public License Version 1.1
008: * (the "License"); you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the License.
014: *
015: * The Original Code is 'iText, a free JAVA-PDF library'.
016: *
017: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
018: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
019: * All Rights Reserved.
020: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
021: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
022: *
023: * Contributor(s): all the names of the contributors are added in the source code
024: * where applicable.
025: *
026: * Alternatively, the contents of this file may be used under the terms of the
027: * LGPL license (the ?GNU LIBRARY GENERAL PUBLIC LICENSE?), in which case the
028: * provisions of LGPL are applicable instead of those above. If you wish to
029: * allow use of your version of this file only under the terms of the LGPL
030: * License and not to allow others to use your version of this file under
031: * the MPL, indicate your decision by deleting the provisions above and
032: * replace them with the notice and other provisions required by the LGPL.
033: * If you do not delete the provisions above, a recipient may use your version
034: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
035: *
036: * This library is free software; you can redistribute it and/or modify it
037: * under the terms of the MPL as stated above or under the terms of the GNU
038: * Library General Public License as published by the Free Software Foundation;
039: * either version 2 of the License, or any later version.
040: *
041: * This library is distributed in the hope that it will be useful, but WITHOUT
042: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
043: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
044: * details.
045: *
046: * If you didn't download this code from the following link, you should check if
047: * you aren't using an obsolete version:
048: * http://www.lowagie.com/iText/
049: */
050:
051: package com.lowagie.text.rtf.document;
052:
053: import java.io.IOException;
054: import java.io.OutputStream;
055: import java.util.ArrayList;
056:
057: import com.lowagie.text.rtf.RtfBasicElement;
058: import com.lowagie.text.rtf.RtfElement;
059: import com.lowagie.text.rtf.RtfMapper;
060: import com.lowagie.text.rtf.document.output.*;
061: import com.lowagie.text.rtf.graphic.RtfImage;
062:
063: /**
064: * The RtfDocument stores all document related data and also the main data stream.
065: * INTERNAL CLASS - NOT TO BE USED DIRECTLY
066: *
067: * @version $Id: RtfDocument.java 2808 2007-05-30 10:58:16Z psoares33 $
068: * @author Mark Hall (mhall@edu.uni-klu.ac.at)
069: * @author Todd Bush [Tab support]
070: * @author Thomas Bickel (tmb99@inode.at)
071: */
072: public class RtfDocument extends RtfElement {
073: /**
074: * Stores the actual document data
075: */
076: private RtfDataCache data = null;
077: /**
078: * The RtfMapper to use in this RtfDocument
079: */
080: private RtfMapper mapper = null;
081: /**
082: * The RtfDocumentHeader that handles all document header methods
083: */
084: private RtfDocumentHeader documentHeader = null;
085: /**
086: * Stores integers that have been generated as unique random numbers
087: */
088: private ArrayList previousRandomInts = null;
089: /**
090: * Whether to automatically generate TOC entries for Chapters and Sections. Defaults to false
091: */
092: private boolean autogenerateTOCEntries = false;
093: /**
094: * The RtfDocumentSettings for this RtfDocument.
095: */
096: private RtfDocumentSettings documentSettings = null;
097: /**
098: * The last RtfBasicElement that was added directly to the RtfDocument.
099: */
100: private RtfBasicElement lastElementWritten = null;
101:
102: /**
103: * Constant for the Rtf document start
104: */
105: private static final byte[] RTF_DOCUMENT = "\\rtf1".getBytes();
106:
107: private final static byte[] FSC_LINE = "\\line ".getBytes();
108: private final static byte[] FSC_PAR = "\\par ".getBytes();
109: private final static byte[] FSC_TAB = "\\tab ".getBytes();
110: private final static byte[] FSC_PAGE_PAR = "\\page\\par "
111: .getBytes();
112: private final static byte[] FSC_NEWPAGE = "$newpage$".getBytes();
113: private final static byte[] FSC_BACKSLASH = "\\".getBytes();
114: private final static byte[] FSC_HEX_PREFIX = "\\\'".getBytes();
115: private final static byte[] FSC_UNI_PREFIX = "\\u".getBytes();
116:
117: /**
118: * The default constructor for a RtfDocument
119: */
120: public RtfDocument() {
121: super (null);
122: this .data = new RtfMemoryCache();
123: this .mapper = new RtfMapper(this );
124: this .documentHeader = new RtfDocumentHeader(this );
125: this .documentHeader.init();
126: this .previousRandomInts = new ArrayList();
127: this .documentSettings = new RtfDocumentSettings(this );
128: }
129:
130: /**
131: * unused
132: * @deprecated replaced by {@link #writeContent(OutputStream)}
133: */
134: public byte[] write() {
135: return (new byte[0]);
136: }
137:
138: /**
139: * unused
140: */
141: public void writeContent(final OutputStream out) throws IOException {
142: }
143:
144: /**
145: * Writes the document
146: *
147: * @param out The <code>OutputStream</code> to write the RTF document to.
148: */
149: public void writeDocument(OutputStream out) {
150: try {
151: out.write(OPEN_GROUP);
152: out.write(RtfDocument.RTF_DOCUMENT);
153: //out.write(documentHeader.write());
154: this .documentHeader.writeContent(out);
155: this .data.writeTo(out);
156: out.write(CLOSE_GROUP);
157: } catch (IOException ioe) {
158: ioe.printStackTrace();
159: }
160: }
161:
162: /**
163: * Opens the RtfDocument and initialises the data cache. If the data cache is
164: * set to CACHE_DISK, but the cache cannot be initialised then the memory cache
165: * is used.
166: */
167: public void open() {
168: try {
169: switch (this .documentSettings.getDataCacheStyle()) {
170: case RtfDataCache.CACHE_MEMORY_EFFICIENT:
171: this .data = new RtfEfficientMemoryCache();
172: break;
173: case RtfDataCache.CACHE_MEMORY:
174: this .data = new RtfMemoryCache();
175: break;
176: case RtfDataCache.CACHE_DISK:
177: this .data = new RtfDiskCache();
178: break;
179: default:
180: throw (new RuntimeException("unknown"));
181: }
182:
183: } catch (IOException ioe) {
184: System.err
185: .println("Could not initialise disk cache. Using memory cache.");
186: ioe.printStackTrace();
187: this .data = new RtfMemoryCache();
188: }
189: }
190:
191: /**
192: * Adds an element to the rtf document
193: *
194: * @param element The element to add
195: */
196: public void add(RtfBasicElement element) {
197: try {
198: if (element instanceof RtfInfoElement) {
199: this .documentHeader
200: .addInfoElement((RtfInfoElement) element);
201: } else {
202: if (element instanceof RtfImage) {
203: ((RtfImage) element).setTopLevelElement(true);
204: }
205: element.writeContent(this .data.getOutputStream());
206: this .lastElementWritten = element;
207: }
208: } catch (IOException ioe) {
209: ioe.printStackTrace();
210: }
211: }
212:
213: /**
214: * Gets the RtfMapper object of this RtfDocument
215: *
216: * @return The RtfMapper
217: */
218: public RtfMapper getMapper() {
219: return this .mapper;
220: }
221:
222: /**
223: * Generates a random integer that is unique with respect to the document.
224: *
225: * @return A random int
226: */
227: public int getRandomInt() {
228: Integer newInt = null;
229: do {
230: newInt = new Integer(
231: (int) (Math.random() * Integer.MAX_VALUE));
232: } while (this .previousRandomInts.contains(newInt));
233: this .previousRandomInts.add(newInt);
234: return newInt.intValue();
235: }
236:
237: /**
238: * Gets the RtfDocumentHeader of this RtfDocument
239: *
240: * @return The RtfDocumentHeader of this RtfDocument
241: */
242: public RtfDocumentHeader getDocumentHeader() {
243: return this .documentHeader;
244: }
245:
246: /**
247: * Replaces special characters with their unicode values
248: * @param str The original <code>String</code>
249: * @param useHex indicated if the hexadecimal value has to be used
250: * @param softLineBreaks whether to use soft line breaks instead of default hard ones.
251: *
252: * @deprecated replaced by {@link #filterSpecialChar(OutputStream, String, boolean, boolean)}
253: * @return The converted String
254: */
255: public String filterSpecialChar(String str, boolean useHex,
256: boolean softLineBreaks) {
257: if (str == null) {
258: return "";
259: }
260: int length = str.length();
261: int z = 'z';
262: StringBuffer ret = new StringBuffer(length);
263: for (int i = 0; i < length; i++) {
264: char ch = str.charAt(i);
265:
266: if (ch == '\\') {
267: ret.append("\\\\");
268: } else if (ch == '\n') {
269: if (softLineBreaks) {
270: ret.append("\\line ");
271: } else {
272: ret.append("\\par ");
273: }
274: } else if (ch == '\t') {
275: ret.append("\\tab ");
276: } else if ((ch) > z
277: && this .documentSettings.isAlwaysUseUnicode()) {
278: if (useHex) {
279: ret.append("\\\'").append(Long.toHexString(ch));
280: } else {
281: ret.append("\\u").append((long) ch).append('?');
282: }
283: } else {
284: ret.append(ch);
285: }
286: }
287: String s = ret.toString();
288: if (s.indexOf("$newpage$") >= 0) {
289: String before = s.substring(0, s.indexOf("$newpage$"));
290: String after = s.substring(s.indexOf("$newpage$") + 9);
291: ret = new StringBuffer(before);
292: ret.append("\\page\\par ");
293: ret.append(after);
294: return ret.toString();
295: }
296: return s;
297: }
298:
299: /**
300: * Writes the given string to the given {@link OutputStream} encoding the string characters.
301: *
302: * @param out destination OutputStream
303: * @param str string to write
304: * @param useHex if <code>true</code> hex encoding characters is preferred to unicode encoding if possible
305: * @param softLineBreaks if <code>true</code> return characters are written as soft line breaks
306: *
307: * @throws IOException
308: */
309: public void filterSpecialChar(final OutputStream out,
310: final String str, final boolean useHex,
311: final boolean softLineBreaks) throws IOException {
312: if (out == null) {
313: throw (new NullPointerException("null OutpuStream"));
314: }
315:
316: final boolean alwaysUseUniCode = this .documentSettings
317: .isAlwaysUseUnicode();
318: if (str == null) {
319: return;
320: }
321: final int len = str.length();
322: if (len == 0) {
323: return;
324: }
325:
326: for (int k = 0; k < len; k++) {
327: final char c = str.charAt(k);
328: if (c < 0x20) {
329: //allow return and tab only
330: if (c == '\n') {
331: out.write(softLineBreaks ? FSC_LINE : FSC_PAR);
332: } else if (c == '\t') {
333: out.write(FSC_TAB);
334: } else {
335: out.write('?');
336: }
337: } else if ((c == '\\') || (c == '{') || (c == '}')) {
338: //escape
339: out.write(FSC_BACKSLASH);
340: out.write(c);
341: } else if ((c == '$') && (len - k >= FSC_NEWPAGE.length)
342: && subMatch(str, k, FSC_NEWPAGE)) {
343: //"$newpage$" -> "\\page\\par "
344: out.write(FSC_PAGE_PAR);
345: k += FSC_NEWPAGE.length - 1;
346: } else {
347: if ((c > 0xff) || ((c > 'z') && alwaysUseUniCode)) {
348: if (useHex && (c <= 0xff)) {
349: //encode as 2 char hex string
350: out.write(FSC_HEX_PREFIX);
351: out.write(RtfImage.byte2charLUT, c * 2, 2);
352: } else {
353: //encode as decimal, signed short value
354: out.write(FSC_UNI_PREFIX);
355: String s = Short.toString((short) c);
356: for (int x = 0; x < s.length(); x++) {
357: out.write(s.charAt(x));
358: }
359: out.write('?');
360: }
361: } else {
362: out.write(c);
363: }
364: }
365: }
366: }
367:
368: /**
369: * Returns <code>true</code> if <tt>m.length</tt> characters in <tt>str</tt>, starting at offset <tt>soff</tt>
370: * match the bytes in the given array <tt>m</tt>.
371: *
372: * @param str the string to search for a match
373: * @param soff the starting offset in str
374: * @param m the array to match
375: * @return <code>true</code> if there is match
376: */
377: private static boolean subMatch(final String str, int soff,
378: final byte[] m) {
379: for (int k = 0; k < m.length; k++) {
380: if (str.charAt(soff++) != m[k]) {
381: return (false);
382: }
383: }
384: return (true);
385: }
386:
387: /**
388: * Whether to automagically generate table of contents entries when
389: * adding Chapters or Sections.
390: *
391: * @param autogenerate Whether to automatically generate TOC entries
392: */
393: public void setAutogenerateTOCEntries(boolean autogenerate) {
394: this .autogenerateTOCEntries = autogenerate;
395: }
396:
397: /**
398: * Get whether to autmatically generate table of contents entries
399: *
400: * @return Wheter to automatically generate TOC entries
401: */
402: public boolean getAutogenerateTOCEntries() {
403: return this .autogenerateTOCEntries;
404: }
405:
406: /**
407: * Gets the RtfDocumentSettings that specify how the rtf document is generated.
408: *
409: * @return The current RtfDocumentSettings.
410: */
411: public RtfDocumentSettings getDocumentSettings() {
412: return this .documentSettings;
413: }
414:
415: /**
416: * Gets the last RtfBasicElement that was directly added to the RtfDocument.
417: *
418: * @return The last RtfBasicElement that was directly added to the RtfDocument.
419: */
420: public RtfBasicElement getLastElementWritten() {
421: return this.lastElementWritten;
422: }
423: }
|