Source code: org/jfor/jfor/rtflib/rtfdoc/RtfExternalGraphic.java
1 package org.jfor.jfor.rtflib.rtfdoc;
2
3 import org.jfor.jfor.rtflib.rtfdoc.RtfElement;
4 import org.jfor.jfor.rtflib.rtfdoc.RtfContainer;
5 import org.jfor.jfor.rtflib.rtfdoc.RtfAttributes;
6
7 import org.jfor.jfor.tools.ImageConstants;
8 import org.jfor.jfor.tools.ImageUtil;
9 import org.jfor.jfor.tools.jpeg.JpegEncoderFactory;
10 import org.jfor.jfor.tools.jpeg.JPEGException;
11 import org.jfor.jfor.tools.jpeg.IJpegEncoder;
12
13 import java.io.BufferedInputStream;
14 import java.io.BufferedOutputStream;
15 import java.io.InputStreamReader;
16
17 import java.io.ByteArrayOutputStream;
18 import java.io.InputStream;
19 import java.io.IOException;
20 import java.io.Writer;
21
22 import java.io.File;
23 import java.net.URL;
24 import java.net.MalformedURLException;
25 import java.util.Hashtable;
26
27 /*-----------------------------------------------------------------------------
28 * jfor - Open-Source XSL-FO to RTF converter - see www.jfor.org
29 *
30 * ====================================================================
31 * jfor Apache-Style Software License.
32 * Copyright (c) 2002 by the jfor project. All rights reserved.
33 *
34 * Redistribution and use in source and binary forms, with or without
35 * modification, are permitted provided that the following conditions
36 * are met:
37 *
38 * 1. Redistributions of source code must retain the above copyright
39 * notice, this list of conditions and the following disclaimer.
40 *
41 * 2. Redistributions in binary form must reproduce the above copyright
42 * notice, this list of conditions and the following disclaimer in
43 * the documentation and/or other materials provided with the
44 * distribution.
45 *
46 * 3. The end-user documentation included with the redistribution,
47 * if any, must include the following acknowledgment:
48 * "This product includes software developed
49 * by the jfor project (http://www.jfor.org)."
50 * Alternately, this acknowledgment may appear in the software itself,
51 * if and wherever such third-party acknowledgments normally appear.
52 *
53 * 4. The name "jfor" must not be used to endorse
54 * or promote products derived from this software without prior written
55 * permission. For written permission, please contact info@jfor.org.
56 *
57 * 5. Products derived from this software may not be called "jfor",
58 * nor may "jfor" appear in their name, without prior written
59 * permission of info@jfor.org.
60 *
61 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
62 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
63 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
64 * DISCLAIMED. IN NO EVENT SHALL THE JFOR PROJECT OR ITS CONTRIBUTORS BE
65 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
66 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
67 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
68 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
69 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
70 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
71 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
72 * ====================================================================
73 * Contributor(s):
74 * @author Gianugo Rabellino gianugo@rabellino.it
75 -----------------------------------------------------------------------------*/
76
77 /**
78 * Creates an RTF image from an external graphic file.
79 * This class belongs to the <fo:external-graphic> tag processing. <br>
80 *
81 * Supports relative path like "../test.gif", too (01-08-24) <br>
82 *
83 * Limitations:
84 * <li> Only the image types PNG, JPEG and EMF are supported
85 * <li> The GIF is supported, too, but will be converted to JPG
86 * <li> Only the attributes SRC (required), WIDTH, HEIGHT, SCALING are supported
87 * <li> The SCALING attribute supports (uniform | non-uniform)
88 *
89 * Known Bugs:
90 * <li> If the emf image has a desired size, the image will be clipped
91 * <li> The emf, jpg & png image will not be displayed in correct size
92 *
93 * @author <a href="mailto:a.putz@skynamics.com">Andreas Putz</a>
94 */
95
96 public class RtfExternalGraphic extends RtfElement
97 {
98 /** Exception thrown when an image file/URL cannot be read */
99 public static class ExternalGraphicException extends IOException
100 {
101 ExternalGraphicException(String reason)
102 {
103 super(reason);
104 }
105 }
106
107 //////////////////////////////////////////////////
108 // @@ Members
109 //////////////////////////////////////////////////
110
111
112 /**
113 * The url of the image
114 */
115 protected URL url = null;
116
117 /**
118 * The height of the image
119 */
120 protected int height = -1;
121
122 /**
123 * The desired percent value of the height
124 */
125 protected int heightPercent = -1;
126
127 /**
128 * The desired height
129 */
130 protected int heightDesired = -1;
131
132 /**
133 * Flag whether the desired height is a percentage
134 */
135 protected boolean perCentH = false;
136
137 /**
138 * The width of the image
139 */
140 protected int width = -1;
141
142 /**
143 * The desired percent value of the width
144 */
145 protected int widthPercent = -1;
146
147 /**
148 * The desired width
149 */
150 protected int widthDesired = -1;
151
152 /**
153 * Flag whether the desired width is a percentage
154 */
155 protected boolean perCentW = false;
156
157 /**
158 * Flag whether the image size shall be adjusted
159 */
160 protected boolean scaleUniform = false;
161
162 /**
163 * Graphic compression rate
164 */
165 protected int graphicCompressionRate = 80;
166
167
168 //////////////////////////////////////////////////
169 // @@ Construction
170 //////////////////////////////////////////////////
171
172
173 /**
174 * Default constructor.
175 * Create an RTF element as a child of given container.
176 *
177 * @param container a <code>RtfContainer</code> value
178 * @param writer a <code>Writer</code> value
179 */
180 public RtfExternalGraphic(RtfContainer container, Writer writer) throws IOException {
181 super (container, writer);
182 }
183
184 /**
185 * Default constructor.
186 *
187 * @param container a <code>RtfContainer</code> value
188 * @param writer a <code>Writer</code> value
189 * @param attributes a <code>RtfAttributes</code> value
190 */
191 public RtfExternalGraphic(RtfContainer container, Writer writer,
192 RtfAttributes attributes) throws IOException {
193 super (container, writer, attributes);
194 }
195
196
197 //////////////////////////////////////////////////
198 // @@ RtfElement implementation
199 //////////////////////////////////////////////////
200
201 /** RtfElement override - catches ExternalGraphicException and writes a warning
202 * message to the document if image cannot be read
203 */
204 protected void writeRtfContent() throws IOException {
205 try {
206 writeRtfContentWithException();
207 } catch(ExternalGraphicException ie) {
208 writeExceptionInRtf(ie);
209 }
210 }
211
212 /**
213 * Writes the RTF content to m_writer - this one throws ExternalGraphicExceptions
214 *
215 * @exception IOException On error
216 */
217 protected void writeRtfContentWithException() throws IOException {
218
219 if (m_writer == null) {
220 return;
221 }
222
223
224 if (url == null) {
225 throw new ExternalGraphicException("The attribute 'url' of <fo:external-graphic> is null.");
226 }
227
228 String linkToRoot = System.getProperty( "jfor_link_to_root" );
229 if (linkToRoot != null)
230 {
231 m_writer.write( "{\\field {\\* \\fldinst { INCLUDEPICTURE \"" );
232 m_writer.write( linkToRoot );
233 File urlFile = new File( url.getFile() );
234 m_writer.write( urlFile.getName() );
235 m_writer.write( "\" \\\\* MERGEFORMAT \\\\d }}}" );
236 return;
237 }
238
239 getRtfFile ().getLog ().logInfo ("Writing image '" + url + "'.");
240
241
242 byte[] data = null;
243 try
244 {
245 // image reading patch provided by Michael Krause <michakurt@web.de>
246 final BufferedInputStream bin = new BufferedInputStream(url.openStream());
247 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
248 while (true) {
249 final int datum = bin.read();
250 if (datum == -1) break;
251 bout.write(datum);
252 }
253 bout.flush();
254 data = bout.toByteArray();
255 }
256 catch (Exception e) {
257 throw new ExternalGraphicException("The attribute 'src' of <fo:external-graphic> has a invalid value: '" + url + "' (" + e + ")");
258 }
259
260 if (data == null) {
261 return;
262 }
263
264 // Determine image file format
265 String file = url.getFile ();
266 int type = determineImageType(data, file.substring(file.lastIndexOf(".") + 1));
267
268 if (type >= ImageConstants.I_TO_CONVERT_BASIS) {
269 // convert
270 int to = ImageConstants.CONVERT_TO [type - ImageConstants.I_TO_CONVERT_BASIS];
271
272 if (to == ImageConstants.I_JPG) {
273 try {
274 //convert to jpeg
275 final IJpegEncoder jpgEncoder = new JpegEncoderFactory().getEncoder();
276 data = jpgEncoder.encodeJPEG(graphicCompressionRate,data);
277 type = to;
278 }
279 catch (JPEGException e) {
280 throw new IOException("JPEG conversion error, src = '" + url + "' (" + e + ")");
281 }
282 }
283 else {
284 type = ImageConstants.I_NOT_SUPPORTED;
285 }
286 }
287
288
289 if (type == ImageConstants.I_NOT_SUPPORTED) {
290 throw new ExternalGraphicException("The tag <fo:external-graphic> does not support " + file.substring(file.lastIndexOf(".") + 1) + " - image type.");
291 }
292
293 String rtfImageCode = ImageConstants.RTF_TAGS [type];
294
295 // Writes the beginning of the rtf image
296
297 writeGroupMark(true);
298 writeStarControlWord("shppict");
299 writeGroupMark(true);
300 writeControlWord("pict");
301
302 StringBuffer buf = new StringBuffer(data.length * 3);
303
304 writeControlWord(rtfImageCode);
305
306 if (type == ImageConstants.I_PNG) {
307 width = ImageUtil.getIntFromByteArray(data, 16, 4, true);
308 height = ImageUtil.getIntFromByteArray(data, 20, 4, true);
309 }
310 else if (type == ImageConstants.I_JPG) {
311 int basis = -1;
312 byte ff = (byte) 0xff;
313 byte c0 = (byte) 0xc0;
314 for (int i = 0; i < data.length; i++) {
315 byte b = data[i];
316 if (b != ff)
317 continue;
318 if (i == data.length - 1)
319 continue;
320 b = data[i + 1];
321 if (b != c0)
322 continue;
323 basis = i + 5;
324 break;
325 }
326
327 if (basis != -1) {
328 width = ImageUtil.getIntFromByteArray(data, basis + 2, 2, true);
329 height = ImageUtil.getIntFromByteArray(data, basis, 2, true);
330 }
331 }
332 else if (type == ImageConstants.I_EMF) {
333 width = ImageUtil.getIntFromByteArray(data, 151, 4, false);
334 height = ImageUtil.getIntFromByteArray(data, 155, 4, false);
335 }
336
337 // Set image size
338 if (width != -1) {
339 writeControlWord("picw" + width);
340 }
341 if (height != -1) {
342 writeControlWord("pich" + height);
343 }
344
345 if (widthDesired != -1) {
346 if (perCentW) {
347 writeControlWord("picscalex" + widthDesired);
348 }
349 else {
350 writeControlWord("picscalex" + widthDesired * 100 / width);
351 }
352
353 writeControlWord("picwgoal" + widthDesired);
354 }
355 else if (scaleUniform && heightDesired != -1) {
356 if (perCentH) {
357 writeControlWord("picscalex" + heightDesired);
358 }
359 else {
360 writeControlWord("picscalex" + heightDesired * 100 / height);
361 }
362 }
363
364 if (heightDesired != -1) {
365 if (perCentH) {
366 writeControlWord("picscaley" + heightDesired);
367 }
368 else {
369 writeControlWord("picscaley" + heightDesired * 100 / height);
370 }
371
372 writeControlWord("pichgoal" + heightDesired);
373 }
374 else if (scaleUniform && widthDesired != -1) {
375 if (perCentW) {
376 writeControlWord("picscaley" + widthDesired);
377 }
378 else {
379 writeControlWord("picscaley" + widthDesired * 100 / width);
380 }
381 }
382
383 for (int i = 0; i < data.length; i++) {
384 int iData = data [i];
385
386 // Make positive byte
387 if (iData < 0)
388 iData += 256;
389
390 if (iData < 16) {
391 // Set leading zero and append
392 buf.append('0');
393 }
394
395 buf.append(Integer.toHexString(iData));
396 }
397
398 int len = buf.length();
399 char[] chars = new char[len];
400
401 buf.getChars(0, len, chars, 0);
402 m_writer.write(chars);
403
404 // Writes the end of RTF image
405
406 writeGroupMark(false);
407 writeGroupMark(false);
408 }
409
410
411 //////////////////////////////////////////////////
412 // @@ Member access
413 //////////////////////////////////////////////////
414
415 /**
416 * Sets the desired height of the image.
417 *
418 * @param theHeight The desired image height
419 */
420 public void setHeight(String theHeight) {
421 this.heightDesired = ImageUtil.getInt(theHeight);
422 this.perCentH = ImageUtil.isPercent(theHeight);
423 }
424
425 /**
426 * Sets the desired width of the image.
427 *
428 * @param theWidth The desired image width
429 */
430 public void setWidth(String theWidth) {
431 this.widthDesired = ImageUtil.getInt(theWidth);
432 this.perCentW = ImageUtil.isPercent(theWidth);
433 }
434
435 /**
436 * Sets the flag whether the image size shall be adjusted.
437 *
438 * @param value
439 * true image width or height shall be adjusted automatically\n
440 * false no adjustment
441 */
442 public void setScaling(String value) {
443 if (value.equalsIgnoreCase("uniform")) {
444 this.scaleUniform = true;
445 }
446 }
447
448 /**
449 * Sets the url of the image.
450 *
451 * @param urlString Image url like "file://..."
452 * @throws IOException On error
453 */
454 public void setURL(String urlString) throws IOException
455 {
456 URL tmpUrl = null;
457 try
458 {
459 tmpUrl = new URL (urlString);
460 }
461 catch (MalformedURLException e)
462 {
463 try
464 {
465 tmpUrl = new File (urlString).toURL ();
466 }
467 catch (MalformedURLException ee)
468 {
469 throw new ExternalGraphicException("The attribute 'src' of <fo:external-graphic> has a invalid value: '" + urlString + "' (" + ee + ")");
470 }
471 }
472 this.url = tmpUrl;
473 }
474
475 /**
476 * Gets the compression rate for the image in percent.
477 * @return Compression rate
478 */
479 public int getCompressionRate ()
480 {
481 return graphicCompressionRate;
482 }
483
484 /**
485 * Sets the compression rate for the image in percent.
486 *
487 * @param percent Compression rate
488 * @return
489 * true: The compression rate is valid (0..100)\n
490 * false: The compression rate is invalid
491 */
492 public boolean setCompressionRate (int percent)
493 {
494 if (percent < 1 || percent > 100)
495 return false;
496
497 graphicCompressionRate = percent;
498 return true;
499 }
500
501
502 //////////////////////////////////////////////////
503 // @@ Helpers
504 //////////////////////////////////////////////////
505
506
507 /**
508 * Determines wheter the image is a jpeg.
509 *
510 * @param data Image
511 *
512 * @return
513 * true If JPEG type\n
514 * false Other type
515 */
516 private boolean isJPEG(byte[] data) {
517 // Indentifier "0xFFD8" on position 0
518 byte [] pattern = new byte [] {(byte) 0xFF, (byte) 0xD8};
519
520 return ImageUtil.compareHexValues(pattern, data, 0, true);
521 }
522
523 /**
524 * Determines wheter the image is a png.
525 *
526 * @param data Image
527 *
528 * @return
529 * true If PNG type\n
530 * false Other type
531 */
532 private boolean isPNG(byte[] data) {
533 // Indentifier "PNG" on position 1
534 byte [] pattern = new byte [] {(byte) 0x50, (byte) 0x4E, (byte) 0x47};
535
536 return ImageUtil.compareHexValues(pattern, data, 1, true);
537 }
538
539 /**
540 * Determines wheter the image is a emf.
541 *
542 * @param data Image
543 *
544 * @return
545 * true If EMF type\n
546 * false Other type
547 */
548 private boolean isEMF(byte[] data) {
549 // No offical Indentifier known
550 byte [] pattern = new byte [] {(byte) 0x01, (byte) 0x00, (byte) 0x00};
551
552 return ImageUtil.compareHexValues(pattern, data, 0, true);
553 }
554
555 /**
556 * Determines wheter the image is a gif.
557 *
558 * @param data Image
559 *
560 * @return
561 * true If GIF type\n
562 * false Other type
563 */
564 private boolean isGIF(byte[] data) {
565 // Indentifier "GIF8" on position 0
566 byte [] pattern = new byte [] {(byte) 0x47, (byte) 0x49, (byte) 0x46, (byte) 0x38};
567
568 return ImageUtil.compareHexValues(pattern, data, 0, true);
569 }
570
571 /**
572 * Determines wheter the image is a gif.
573 *
574 * @param data Image
575 *
576 * @return
577 * true If BMP type\n
578 * false Other type
579 */
580 private boolean isBMP(byte[] data) {
581 // Indentifier "BM" on position 0
582 byte [] pattern = new byte [] {(byte) 0x42, (byte) 0x4D};
583
584 return ImageUtil.compareHexValues(pattern, data, 0, true);
585 }
586
587 /**
588 * Determine image file format.
589 *
590 * @param data Image
591 * @param ext Image extension
592 *
593 * @return Image type by ImageConstants.java
594 */
595 private int determineImageType(byte [] data, String ext) {
596 int type = ImageConstants.I_NOT_SUPPORTED;
597
598 if (isPNG(data)) {
599 type = ImageConstants.I_PNG;
600 }
601 else if (isJPEG(data)) {
602 type = ImageConstants.I_JPG_C;
603 }
604 else if (isEMF(data)) {
605 type = ImageConstants.I_EMF;
606 }
607 else if (isGIF(data)) {
608 type = ImageConstants.I_GIF;
609 }
610 else {
611 Object tmp = ImageConstants.SUPPORTED_IMAGE_TYPES.get(ext.toLowerCase());
612 if (tmp != null) {
613 type = ((Integer) tmp).intValue();
614 }
615 }
616
617 return type;
618 }
619
620 /** true if this element would generate no "useful" RTF content */
621 public boolean isEmpty()
622 {
623 return url==null;
624 }
625 }