001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: RtfExternalGraphic.java 426576 2006-07-28 15:44:37Z jeremias $ */
019:
020: package org.apache.fop.render.rtf.rtflib.rtfdoc;
021:
022: /*
023: * This file is part of the RTF library of the FOP project, which was originally
024: * created by Bertrand Delacretaz <bdelacretaz@codeconsult.ch> and by other
025: * contributors to the jfor project (www.jfor.org), who agreed to donate jfor to
026: * the FOP project.
027: */
028:
029: import org.apache.commons.io.IOUtils;
030: import org.apache.fop.render.rtf.rtflib.tools.ImageConstants;
031: import org.apache.fop.render.rtf.rtflib.tools.ImageUtil; //import org.apache.fop.render.rtf.rtflib.tools.jpeg.Encoder;
032: //import org.apache.fop.render.rtf.rtflib.tools.jpeg.JPEGException;
033:
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.io.Writer;
037:
038: import java.io.File;
039: import java.net.URL;
040: import java.net.MalformedURLException;
041:
042: /**
043: * Creates an RTF image from an external graphic file.
044: * This class belongs to the <fo:external-graphic> tag processing. <br>
045: *
046: * Supports relative path like "../test.gif", too (01-08-24) <br>
047: *
048: * Limitations:
049: * <li> Only the image types PNG, JPEG and EMF are supported
050: * <li> The GIF is supported, too, but will be converted to JPG
051: * <li> Only the attributes SRC (required), WIDTH, HEIGHT, SCALING are supported
052: * <li> The SCALING attribute supports (uniform | non-uniform)
053: *
054: * Known Bugs:
055: * <li> If the emf image has a desired size, the image will be clipped
056: * <li> The emf, jpg & png image will not be displayed in correct size
057: *
058: * @author <a href="mailto:a.putz@skynamics.com">Andreas Putz</a>
059: * @author Gianugo Rabellino gianugo@rabellino.it
060: */
061:
062: public class RtfExternalGraphic extends RtfElement {
063: /** Exception thrown when an image file/URL cannot be read */
064: public static class ExternalGraphicException extends IOException {
065: ExternalGraphicException(String reason) {
066: super (reason);
067: }
068: }
069:
070: //////////////////////////////////////////////////
071: // Supported Formats
072: //////////////////////////////////////////////////
073: private static class FormatBase {
074:
075: /**
076: * Determines whether the image is in the according format.
077: *
078: * @param data Image
079: *
080: * @return
081: * true If according type\n
082: * false Other type
083: */
084: public static boolean isFormat(byte[] data) {
085: return false;
086: }
087:
088: /**
089: * Convert image data if necessary - for example when format is not supported by rtf.
090: *
091: * @param data Image
092: * @param type Format type
093: */
094: public FormatBase convert(FormatBase format, byte[] data) {
095: return format;
096: }
097:
098: /**
099: * Determine image file format.
100: *
101: * @param data Image
102: *
103: * @return Image format class
104: */
105:
106: public static FormatBase determineFormat(byte[] data) {
107:
108: if (FormatPNG.isFormat(data)) {
109: return new FormatPNG();
110: } else if (FormatJPG.isFormat(data)) {
111: return new FormatJPG();
112: } else if (FormatEMF.isFormat(data)) {
113: return new FormatEMF();
114: } else if (FormatGIF.isFormat(data)) {
115: return new FormatGIF();
116: } else if (FormatBMP.isFormat(data)) {
117: return new FormatBMP();
118: } else {
119: return null;
120: }
121: }
122:
123: /**
124: * Get image type.
125: *
126: * @return Image format class
127: */
128: public int getType() {
129: return ImageConstants.I_NOT_SUPPORTED;
130: }
131:
132: /**
133: * Get rtf tag.
134: *
135: * @return Rtf tag for image format.
136: */
137: public String getRtfTag() {
138: return "";
139: }
140: }
141:
142: private static class FormatGIF extends FormatBase {
143: public static boolean isFormat(byte[] data) {
144: // Indentifier "GIF8" on position 0
145: byte[] pattern = new byte[] { (byte) 0x47, (byte) 0x49,
146: (byte) 0x46, (byte) 0x38 };
147:
148: return ImageUtil.compareHexValues(pattern, data, 0, true);
149: }
150:
151: public int getType() {
152: return ImageConstants.I_GIF;
153: }
154: }
155:
156: private static class FormatEMF extends FormatBase {
157: public static boolean isFormat(byte[] data) {
158: // No offical Indentifier known
159: byte[] pattern = new byte[] { (byte) 0x01, (byte) 0x00,
160: (byte) 0x00 };
161:
162: return ImageUtil.compareHexValues(pattern, data, 0, true);
163: }
164:
165: public int getType() {
166: return ImageConstants.I_EMF;
167: }
168:
169: public String getRtfTag() {
170: return "emfblip";
171: }
172: }
173:
174: private static class FormatBMP extends FormatBase {
175: public static boolean isFormat(byte[] data) {
176: byte[] pattern = new byte[] { (byte) 0x42, (byte) 0x4D };
177:
178: return ImageUtil.compareHexValues(pattern, data, 0, true);
179: }
180:
181: public int getType() {
182: return ImageConstants.I_BMP;
183: }
184: }
185:
186: private static class FormatJPG extends FormatBase {
187: public static boolean isFormat(byte[] data) {
188: // Indentifier "0xFFD8" on position 0
189: byte[] pattern = new byte[] { (byte) 0xFF, (byte) 0xD8 };
190:
191: return ImageUtil.compareHexValues(pattern, data, 0, true);
192: }
193:
194: public int getType() {
195: return ImageConstants.I_JPG;
196: }
197:
198: public String getRtfTag() {
199: return "jpegblip";
200: }
201: }
202:
203: private static class FormatPNG extends FormatBase {
204: public static boolean isFormat(byte[] data) {
205: // Indentifier "PNG" on position 1
206: byte[] pattern = new byte[] { (byte) 0x50, (byte) 0x4E,
207: (byte) 0x47 };
208:
209: return ImageUtil.compareHexValues(pattern, data, 1, true);
210: }
211:
212: public int getType() {
213: return ImageConstants.I_PNG;
214: }
215:
216: public String getRtfTag() {
217: return "pngblip";
218: }
219: }
220:
221: //////////////////////////////////////////////////
222: // @@ Members
223: //////////////////////////////////////////////////
224:
225: /**
226: * The url of the image
227: */
228: protected URL url = null;
229:
230: /**
231: * The height of the image (in pixels)
232: */
233: protected int height = -1;
234:
235: /**
236: * The desired percent value of the height
237: */
238: protected int heightPercent = -1;
239:
240: /**
241: * The desired height (in twips)
242: */
243: protected int heightDesired = -1;
244:
245: /**
246: * Flag whether the desired height is a percentage
247: */
248: protected boolean perCentH = false;
249:
250: /**
251: * The width of the image (in pixels)
252: */
253: protected int width = -1;
254:
255: /**
256: * The desired percent value of the width
257: */
258: protected int widthPercent = -1;
259:
260: /**
261: * The desired width (in twips)
262: */
263: protected int widthDesired = -1;
264:
265: /**
266: * Flag whether the desired width is a percentage
267: */
268: protected boolean perCentW = false;
269:
270: /**
271: * Flag whether the image size shall be adjusted
272: */
273: protected boolean scaleUniform = false;
274:
275: /**
276: * Graphic compression rate
277: */
278: protected int graphicCompressionRate = 80;
279:
280: /** The image data */
281: private byte[] imagedata = null;
282:
283: /** The image format */
284: private FormatBase imageformat;
285:
286: //////////////////////////////////////////////////
287: // @@ Construction
288: //////////////////////////////////////////////////
289:
290: /**
291: * Default constructor.
292: * Create an RTF element as a child of given container.
293: *
294: * @param container a <code>RtfContainer</code> value
295: * @param writer a <code>Writer</code> value
296: * @throws IOException for I/O problems
297: */
298: public RtfExternalGraphic(RtfContainer container, Writer writer)
299: throws IOException {
300: super (container, writer);
301: }
302:
303: /**
304: * Default constructor.
305: *
306: * @param container a <code>RtfContainer</code> value
307: * @param writer a <code>Writer</code> value
308: * @param attributes a <code>RtfAttributes</code> value
309: * @throws IOException for I/O problems
310: */
311: public RtfExternalGraphic(RtfContainer container, Writer writer,
312: RtfAttributes attributes) throws IOException {
313: super (container, writer, attributes);
314: }
315:
316: //////////////////////////////////////////////////
317: // @@ RtfElement implementation
318: //////////////////////////////////////////////////
319:
320: /**
321: * RtfElement override - catches ExternalGraphicException and writes a warning
322: * message to the document if image cannot be read
323: * @throws IOException for I/O problems
324: */
325: protected void writeRtfContent() throws IOException {
326: try {
327: writeRtfContentWithException();
328: } catch (ExternalGraphicException ie) {
329: writeExceptionInRtf(ie);
330: }
331: }
332:
333: /**
334: * Writes the RTF content to m_writer - this one throws ExternalGraphicExceptions
335: *
336: * @exception IOException On error
337: */
338: protected void writeRtfContentWithException() throws IOException {
339:
340: if (writer == null) {
341: return;
342: }
343:
344: if (url == null) {
345: throw new ExternalGraphicException(
346: "The attribute 'url' of "
347: + "<fo:external-graphic> is null.");
348: }
349:
350: String linkToRoot = System.getProperty("jfor_link_to_root");
351: if (linkToRoot != null) {
352: writer.write("{\\field {\\* \\fldinst { INCLUDEPICTURE \"");
353: writer.write(linkToRoot);
354: File urlFile = new File(url.getFile());
355: writer.write(urlFile.getName());
356: writer.write("\" \\\\* MERGEFORMAT \\\\d }}}");
357: return;
358: }
359:
360: // getRtfFile ().getLog ().logInfo ("Writing image '" + url + "'.");
361:
362: if (imagedata == null) {
363: try {
364: final InputStream in = url.openStream();
365: try {
366: imagedata = IOUtils.toByteArray(url.openStream());
367: } finally {
368: IOUtils.closeQuietly(in);
369: }
370: } catch (Exception e) {
371: throw new ExternalGraphicException(
372: "The attribute 'src' of "
373: + "<fo:external-graphic> has a invalid value: '"
374: + url + "' (" + e + ")");
375: }
376: }
377:
378: if (imagedata == null) {
379: return;
380: }
381:
382: // Determine image file format
383: String file = url.getFile();
384: imageformat = FormatBase.determineFormat(imagedata);
385: if (imageformat != null) {
386: imageformat = imageformat.convert(imageformat, imagedata);
387: }
388:
389: if (imageformat == null
390: || imageformat.getType() == ImageConstants.I_NOT_SUPPORTED
391: || "".equals(imageformat.getRtfTag())) {
392: throw new ExternalGraphicException(
393: "The tag <fo:external-graphic> "
394: + "does not support "
395: + file.substring(file.lastIndexOf(".") + 1)
396: + " - image type.");
397: }
398:
399: // Writes the beginning of the rtf image
400:
401: writeGroupMark(true);
402: writeStarControlWord("shppict");
403: writeGroupMark(true);
404: writeControlWord("pict");
405:
406: StringBuffer buf = new StringBuffer(imagedata.length * 3);
407:
408: writeControlWord(imageformat.getRtfTag());
409:
410: computeImageSize();
411: writeSizeInfo();
412:
413: for (int i = 0; i < imagedata.length; i++) {
414: int iData = imagedata[i];
415:
416: // Make positive byte
417: if (iData < 0) {
418: iData += 256;
419: }
420:
421: if (iData < 16) {
422: // Set leading zero and append
423: buf.append('0');
424: }
425:
426: buf.append(Integer.toHexString(iData));
427: }
428:
429: int len = buf.length();
430: char[] chars = new char[len];
431:
432: buf.getChars(0, len, chars, 0);
433: writer.write(chars);
434:
435: // Writes the end of RTF image
436:
437: writeGroupMark(false);
438: writeGroupMark(false);
439: }
440:
441: private void computeImageSize() {
442: if (imageformat.getType() == ImageConstants.I_PNG) {
443: width = ImageUtil.getIntFromByteArray(imagedata, 16, 4,
444: true);
445: height = ImageUtil.getIntFromByteArray(imagedata, 20, 4,
446: true);
447: } else if (imageformat.getType() == ImageConstants.I_JPG) {
448: int basis = -1;
449: byte ff = (byte) 0xff;
450: byte c0 = (byte) 0xc0;
451: for (int i = 0; i < imagedata.length; i++) {
452: byte b = imagedata[i];
453: if (b != ff) {
454: continue;
455: }
456: if (i == imagedata.length - 1) {
457: continue;
458: }
459: b = imagedata[i + 1];
460: if (b != c0) {
461: continue;
462: }
463: basis = i + 5;
464: break;
465: }
466:
467: if (basis != -1) {
468: width = ImageUtil.getIntFromByteArray(imagedata,
469: basis + 2, 2, true);
470: height = ImageUtil.getIntFromByteArray(imagedata,
471: basis, 2, true);
472: }
473: } else if (imageformat.getType() == ImageConstants.I_EMF) {
474: int i = 0;
475:
476: i = ImageUtil.getIntFromByteArray(imagedata, 151, 4, false);
477: if (i != 0) {
478: width = i;
479: }
480:
481: i = ImageUtil.getIntFromByteArray(imagedata, 155, 4, false);
482: if (i != 0) {
483: height = i;
484: }
485:
486: }
487: }
488:
489: private void writeSizeInfo() throws IOException {
490: // Set image size
491: if (width != -1) {
492: writeControlWord("picw" + width);
493: }
494: if (height != -1) {
495: writeControlWord("pich" + height);
496: }
497:
498: if (widthDesired != -1) {
499: if (perCentW) {
500: writeControlWord("picscalex" + widthDesired);
501: } else {
502: //writeControlWord("picscalex" + widthDesired * 100 / width);
503: writeControlWord("picwgoal" + widthDesired);
504: }
505:
506: } else if (scaleUniform && heightDesired != -1) {
507: if (perCentH) {
508: writeControlWord("picscalex" + heightDesired);
509: } else {
510: writeControlWord("picscalex" + heightDesired * 100
511: / height);
512: }
513: }
514:
515: if (heightDesired != -1) {
516: if (perCentH) {
517: writeControlWord("picscaley" + heightDesired);
518: } else {
519: //writeControlWord("picscaley" + heightDesired * 100 / height);
520: writeControlWord("pichgoal" + heightDesired);
521: }
522:
523: } else if (scaleUniform && widthDesired != -1) {
524: if (perCentW) {
525: writeControlWord("picscaley" + widthDesired);
526: } else {
527: writeControlWord("picscaley" + widthDesired * 100
528: / width);
529: }
530: }
531: }
532:
533: //////////////////////////////////////////////////
534: // @@ Member access
535: //////////////////////////////////////////////////
536:
537: /**
538: * Sets the desired height of the image.
539: *
540: * @param theHeight The desired image height (as a string in twips or as a percentage)
541: */
542: public void setHeight(String theHeight) {
543: this .heightDesired = ImageUtil.getInt(theHeight);
544: this .perCentH = ImageUtil.isPercent(theHeight);
545: }
546:
547: /**
548: * Sets the desired width of the image.
549: *
550: * @param theWidth The desired image width (as a string in twips or as a percentage)
551: */
552: public void setWidth(String theWidth) {
553: this .widthDesired = ImageUtil.getInt(theWidth);
554: this .perCentW = ImageUtil.isPercent(theWidth);
555: }
556:
557: /**
558: * Sets the flag whether the image size shall be adjusted.
559: *
560: * @param value
561: * true image width or height shall be adjusted automatically\n
562: * false no adjustment
563: */
564: public void setScaling(String value) {
565: if (value.equalsIgnoreCase("uniform")) {
566: this .scaleUniform = true;
567: }
568: }
569:
570: /**
571: * Sets the binary imagedata of the image.
572: *
573: * @param imagedata Binary imagedata as read from file.
574: * @throws IOException On error
575: */
576: public void setImageData(byte[] data) throws IOException {
577: this .imagedata = data;
578: }
579:
580: /**
581: * Sets the url of the image.
582: *
583: * @param urlString Image url like "file://..."
584: * @throws IOException On error
585: */
586: public void setURL(String urlString) throws IOException {
587: URL tmpUrl = null;
588: try {
589: tmpUrl = new URL(urlString);
590: } catch (MalformedURLException e) {
591: try {
592: tmpUrl = new File(urlString).toURL();
593: } catch (MalformedURLException ee) {
594: throw new ExternalGraphicException(
595: "The attribute 'src' of "
596: + "<fo:external-graphic> has a invalid value: '"
597: + urlString + "' (" + ee + ")");
598: }
599: }
600: this .url = tmpUrl;
601: }
602:
603: /**
604: * Gets the compression rate for the image in percent.
605: * @return Compression rate
606: */
607: public int getCompressionRate() {
608: return graphicCompressionRate;
609: }
610:
611: /**
612: * Sets the compression rate for the image in percent.
613: *
614: * @param percent Compression rate
615: * @return true if the compression rate is valid (0..100), false if invalid
616: */
617: public boolean setCompressionRate(int percent) {
618: if (percent < 1 || percent > 100) {
619: return false;
620: }
621:
622: graphicCompressionRate = percent;
623: return true;
624: }
625:
626: //////////////////////////////////////////////////
627: // @@ Helpers
628: //////////////////////////////////////////////////
629:
630: /**
631: * @return true if this element would generate no "useful" RTF content
632: */
633: public boolean isEmpty() {
634: return url == null;
635: }
636: }
|