1 /*
2 * $Id: RtfImage.java 3433 2008-05-24 19:32:11Z xlv $
3 *
4 * Copyright 2001, 2002, 2003, 2004 by Mark Hall
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * (the "License"); you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the License.
13 *
14 * The Original Code is 'iText, a free JAVA-PDF library'.
15 *
16 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18 * All Rights Reserved.
19 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21 *
22 * Contributor(s): all the names of the contributors are added in the source code
23 * where applicable.
24 *
25 * Alternatively, the contents of this file may be used under the terms of the
26 * LGPL license (the ?GNU LIBRARY GENERAL PUBLIC LICENSE?), in which case the
27 * provisions of LGPL are applicable instead of those above. If you wish to
28 * allow use of your version of this file only under the terms of the LGPL
29 * License and not to allow others to use your version of this file under
30 * the MPL, indicate your decision by deleting the provisions above and
31 * replace them with the notice and other provisions required by the LGPL.
32 * If you do not delete the provisions above, a recipient may use your version
33 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34 *
35 * This library is free software; you can redistribute it and/or modify it
36 * under the terms of the MPL as stated above or under the terms of the GNU
37 * Library General Public License as published by the Free Software Foundation;
38 * either version 2 of the License, or any later version.
39 *
40 * This library is distributed in the hope that it will be useful, but WITHOUT
41 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43 * details.
44 *
45 * If you didn't download this code from the following link, you should check if
46 * you aren't using an obsolete version:
47 * http://www.lowagie.com/iText/
48 */
49
50 package com.lowagie.text.rtf.graphic;
51
52 import java.io.EOFException;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.OutputStream;
56
57 import com.lowagie.text.DocumentException;
58 import com.lowagie.text.Element;
59 import com.lowagie.text.Image;
60 import com.lowagie.text.pdf.codec.wmf.MetaDo;
61 import com.lowagie.text.rtf.RtfElement;
62 import com.lowagie.text.rtf.document.RtfDocument;
63 import com.lowagie.text.rtf.document.output.RtfByteArrayBuffer;
64 import com.lowagie.text.rtf.style.RtfParagraphStyle;
65 import com.lowagie.text.rtf.text.RtfParagraph;
66
67 /**
68 * The RtfImage contains one image. Supported image types are jpeg, png, wmf, bmp.
69 *
70 * @version $Id: RtfImage.java 3433 2008-05-24 19:32:11Z xlv $
71 * @author Mark Hall (Mark.Hall@mail.room3b.eu)
72 * @author Paulo Soares
73 * @author Thomas Bickel (tmb99@inode.at)
74 */
75 public class RtfImage extends RtfElement {
76
77 /**
78 * Constant for the shape/picture group
79 */
80 private static final byte[] PICTURE_GROUP = "\\*\\shppict".getBytes();
81 /**
82 * Constant for a picture
83 */
84 private static final byte[] PICTURE = "\\pict".getBytes();
85 /**
86 * Constant for a jpeg image
87 */
88 private static final byte[] PICTURE_JPEG = "\\jpegblip".getBytes();
89 /**
90 * Constant for a png image
91 */
92 private static final byte[] PICTURE_PNG = "\\pngblip".getBytes();
93 /**
94 * Constant for a wmf image
95 */
96 private static final byte[] PICTURE_WMF = "\\wmetafile8".getBytes();
97 /**
98 * Constant for the picture width
99 */
100 private static final byte[] PICTURE_WIDTH = "\\picw".getBytes();
101 /**
102 * Constant for the picture height
103 */
104 private static final byte[] PICTURE_HEIGHT = "\\pich".getBytes();
105 /**
106 * Constant for the picture width scale
107 */
108 private static final byte[] PICTURE_SCALED_WIDTH = "\\picwgoal".getBytes();
109 /**
110 * Constant for the picture height scale
111 */
112 private static final byte[] PICTURE_SCALED_HEIGHT = "\\pichgoal".getBytes();
113 /**
114 * Constant for horizontal picture scaling
115 */
116 private static final byte[] PICTURE_SCALE_X = "\\picscalex".getBytes();
117 /**
118 * Constant for vertical picture scaling
119 */
120 private static final byte[] PICTURE_SCALE_Y = "\\picscaley".getBytes();
121 /**
122 * "\bin" constant
123 */
124 private static final byte[] PICTURE_BINARY_DATA = "\\bin".getBytes();
125 /**
126 * Constant for converting pixels to twips
127 */
128 private static final int PIXEL_TWIPS_FACTOR = 15;
129
130 /**
131 * The type of image this is.
132 */
133 private final int imageType;
134 /**
135 * Binary image data.
136 */
137 private final byte[][] imageData;
138 /**
139 * The alignment of this picture
140 */
141 private int alignment = Element.ALIGN_LEFT;
142 /**
143 * The width of this picture
144 */
145 private float width = 0;
146 /**
147 * The height of this picture
148 */
149 private float height = 0;
150 /**
151 * The intended display width of this picture
152 */
153 private float plainWidth = 0;
154 /**
155 * The intended display height of this picture
156 */
157 private float plainHeight = 0;
158 /**
159 * Whether this RtfImage is a top level element and should
160 * be an extra paragraph.
161 */
162 private boolean topLevelElement = false;
163
164 /**
165 * Constructs a RtfImage for an Image.
166 *
167 * @param doc The RtfDocument this RtfImage belongs to
168 * @param image The Image that this RtfImage wraps
169 * @throws DocumentException If an error occurred accessing the image content
170 */
171 public RtfImage(RtfDocument doc, Image image) throws DocumentException
172 {
173 super(doc);
174 imageType = image.getOriginalType();
175 if (!(imageType == Image.ORIGINAL_JPEG || imageType == Image.ORIGINAL_BMP
176 || imageType == Image.ORIGINAL_PNG || imageType == Image.ORIGINAL_WMF || imageType == Image.ORIGINAL_GIF)) {
177 throw new DocumentException("Only BMP, PNG, WMF, GIF and JPEG images are supported by the RTF Writer");
178 }
179 alignment = image.getAlignment();
180 width = image.getWidth();
181 height = image.getHeight();
182 plainWidth = image.getPlainWidth();
183 plainHeight = image.getPlainHeight();
184 this.imageData = getImageData(image);
185 }
186
187 /**
188 * Extracts the image data from the Image.
189 *
190 * @param image The image for which to extract the content
191 * @return The raw image data, not formated
192 * @throws DocumentException If an error occurs accessing the image content
193 */
194 private byte[][] getImageData(Image image) throws DocumentException
195 {
196 final int WMF_PLACEABLE_HEADER_SIZE = 22;
197 final RtfByteArrayBuffer bab = new RtfByteArrayBuffer();
198
199 try {
200 if(imageType == Image.ORIGINAL_BMP) {
201 bab.append(MetaDo.wrapBMP(image));
202 } else {
203 final byte[] iod = image.getOriginalData();
204 if(iod == null) {
205
206 final InputStream imageIn = image.getUrl().openStream();
207 if(imageType == Image.ORIGINAL_WMF) { //remove the placeable header first
208 for(int k = 0; k < WMF_PLACEABLE_HEADER_SIZE; k++) {
209 if(imageIn.read() < 0) throw new EOFException("while removing wmf placeable header");
210 }
211 }
212 bab.write(imageIn);
213 imageIn.close();
214
215 } else {
216
217 if(imageType == Image.ORIGINAL_WMF) {
218 //remove the placeable header
219 bab.write(iod, WMF_PLACEABLE_HEADER_SIZE, iod.length - WMF_PLACEABLE_HEADER_SIZE);
220 } else {
221 bab.append(iod);
222 }
223
224 }
225 }
226
227 return bab.toByteArrayArray();
228
229 } catch(IOException ioe) {
230 throw new DocumentException(ioe.getMessage());
231 }
232 }
233
234
235 /**
236 * lookup table used for converting bytes to hex chars.
237 * TODO Should probably be refactored into a helper class
238 */
239 public final static byte[] byte2charLUT = new byte[512]; //'0001020304050607 ... fafbfcfdfeff'
240 static {
241 char c = '0';
242 for(int k = 0; k < 16; k++) {
243 for(int x = 0; x < 16; x++) {
244 byte2charLUT[((k*16)+x)*2] = byte2charLUT[(((x*16)+k)*2)+1] = (byte)c;
245 }
246 if(++c == ':') c = 'a';
247 }
248 }
249
250 /**
251 * Writes the image data to the given buffer as hex encoded text.
252 *
253 * @param bab
254 * @throws IOException
255 */
256 private void writeImageDataHexEncoded(final OutputStream bab) throws IOException
257 {
258 int cnt = 0;
259 for(int k = 0; k < imageData.length; k++) {
260 final byte[] chunk = imageData[k];
261 for(int x = 0; x < chunk.length; x++) {
262 bab.write(byte2charLUT, (chunk[x]&0xff)*2, 2);
263 if(++cnt == 64) {
264 bab.write('\n');
265 cnt = 0;
266 }
267 }
268 }
269 if(cnt > 0) bab.write('\n');
270 }
271
272 /**
273 * Returns the image raw data size in bytes.
274 *
275 * @return the size in bytes
276 */
277 private int imageDataSize()
278 {
279 int size = 0;
280 for(int k = 0; k < imageData.length; k++) {
281 size += imageData[k].length;
282 }
283 return size;
284 }
285
286 /**
287 * Writes the RtfImage content
288 */
289 public void writeContent(final OutputStream result) throws IOException
290 {
291
292 if(this.topLevelElement) {
293 result.write(RtfParagraph.PARAGRAPH_DEFAULTS);
294 switch(alignment) {
295 case Element.ALIGN_LEFT:
296 result.write(RtfParagraphStyle.ALIGN_LEFT);
297 break;
298 case Element.ALIGN_RIGHT:
299 result.write(RtfParagraphStyle.ALIGN_RIGHT);
300 break;
301 case Element.ALIGN_CENTER:
302 result.write(RtfParagraphStyle.ALIGN_CENTER);
303 break;
304 case Element.ALIGN_JUSTIFIED:
305 result.write(RtfParagraphStyle.ALIGN_JUSTIFY);
306 break;
307 }
308 }
309 result.write(OPEN_GROUP);
310 result.write(PICTURE_GROUP);
311 result.write(OPEN_GROUP);
312 result.write(PICTURE);
313 switch(imageType) {
314 case Image.ORIGINAL_JPEG:
315 result.write(PICTURE_JPEG);
316 break;
317 case Image.ORIGINAL_PNG:
318 case Image.ORIGINAL_GIF:
319 result.write(PICTURE_PNG);
320 break;
321 case Image.ORIGINAL_WMF:
322 case Image.ORIGINAL_BMP:
323 result.write(PICTURE_WMF);
324 break;
325 }
326 result.write(PICTURE_WIDTH);
327 result.write(intToByteArray((int) width));
328 result.write(PICTURE_HEIGHT);
329 result.write(intToByteArray((int) height));
330 if(this.document.getDocumentSettings().isWriteImageScalingInformation()) {
331 result.write(PICTURE_SCALE_X);
332 result.write(intToByteArray((int)(100 * plainWidth / width)));
333 result.write(PICTURE_SCALE_Y);
334 result.write(intToByteArray((int)(100 * plainHeight / height)));
335 }
336 if(this.document.getDocumentSettings().isImagePDFConformance()) {
337 result.write(PICTURE_SCALED_WIDTH);
338 result.write(intToByteArray((int) (plainWidth * RtfElement.TWIPS_FACTOR)));
339 result.write(PICTURE_SCALED_HEIGHT);
340 result.write(intToByteArray((int) (plainHeight * RtfElement.TWIPS_FACTOR)));
341 } else {
342 if(this.width != this.plainWidth || this.imageType == Image.ORIGINAL_BMP) {
343 result.write(PICTURE_SCALED_WIDTH);
344 result.write(intToByteArray((int) (plainWidth * PIXEL_TWIPS_FACTOR)));
345 }
346 if(this.height != this.plainHeight || this.imageType == Image.ORIGINAL_BMP) {
347 result.write(PICTURE_SCALED_HEIGHT);
348 result.write(intToByteArray((int) (plainHeight * PIXEL_TWIPS_FACTOR)));
349 }
350 }
351
352 if(this.document.getDocumentSettings().isImageWrittenAsBinary()) {
353 //binary
354 result.write('\n');
355 result.write(PICTURE_BINARY_DATA);
356 result.write(intToByteArray(imageDataSize()));
357 result.write(DELIMITER);
358 if(result instanceof RtfByteArrayBuffer) {
359 ((RtfByteArrayBuffer)result).append(imageData);
360 } else {
361 for(int k = 0; k < imageData.length; k++) {
362 result.write(imageData[k]);
363 }
364 }
365 } else {
366 //hex encoded
367 result.write(DELIMITER);
368 result.write('\n');
369 writeImageDataHexEncoded(result);
370 }
371
372 result.write(CLOSE_GROUP);
373 result.write(CLOSE_GROUP);
374 if(this.topLevelElement) {
375 result.write(RtfParagraph.PARAGRAPH);
376 result.write(RtfParagraph.PARAGRAPH);
377 }
378 result.write('\n');
379 }
380
381 /**
382 * Sets the alignment of this RtfImage. Uses the alignments from com.lowagie.text.Element.
383 *
384 * @param alignment The alignment to use.
385 */
386 public void setAlignment(int alignment) {
387 this.alignment = alignment;
388 }
389
390 /**
391 * Set whether this RtfImage should behave like a top level element
392 * and enclose itself in a paragraph.
393 *
394 * @param topLevelElement Whether to behave like a top level element.
395 */
396 public void setTopLevelElement(boolean topLevelElement) {
397 this.topLevelElement = topLevelElement;
398 }
399
400
401 }