You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RtfExternalGraphic.java 19KB

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