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 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.render.rtf.rtflib.rtfdoc;
  19. /*
  20. * This file is part of the RTF library of the FOP project, which was originally
  21. * created by Bertrand Delacretaz <bdelacretaz@codeconsult.ch> and by other
  22. * contributors to the jfor project (www.jfor.org), who agreed to donate jfor to
  23. * the FOP project.
  24. */
  25. import java.io.File;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.Writer;
  29. import java.net.MalformedURLException;
  30. import java.net.URL;
  31. import org.apache.commons.io.IOUtils;
  32. import org.apache.fop.render.rtf.rtflib.tools.ImageConstants;
  33. import org.apache.fop.render.rtf.rtflib.tools.ImageUtil;
  34. /**
  35. * Creates an RTF image from an external graphic file.
  36. * This class belongs to the <fo:external-graphic> tag processing. <br>
  37. *
  38. * Supports relative path like "../test.gif", too (01-08-24) <br>
  39. *
  40. * Limitations:
  41. * <li> Only the image types PNG, JPEG and EMF are supported
  42. * <li> The GIF is supported, too, but will be converted to JPG
  43. * <li> Only the attributes SRC (required), WIDTH, HEIGHT, SCALING are supported
  44. * <li> The SCALING attribute supports (uniform | non-uniform)
  45. *
  46. * Known Bugs:
  47. * <li> If the emf image has a desired size, the image will be clipped
  48. * <li> The emf, jpg & png image will not be displayed in correct size
  49. *
  50. * @author <a href="mailto:a.putz@skynamics.com">Andreas Putz</a>
  51. * @author Gianugo Rabellino gianugo@rabellino.it
  52. */
  53. public class RtfExternalGraphic extends RtfElement {
  54. /** Exception thrown when an image file/URL cannot be read */
  55. public static class ExternalGraphicException extends IOException {
  56. ExternalGraphicException(String reason) {
  57. super(reason);
  58. }
  59. }
  60. //////////////////////////////////////////////////
  61. // Supported Formats
  62. //////////////////////////////////////////////////
  63. private static class FormatBase {
  64. /**
  65. * Determines whether the image is in the according format.
  66. *
  67. * @param data Image
  68. *
  69. * @return
  70. * true If according type\n
  71. * false Other type
  72. */
  73. public static boolean isFormat(byte[] data) {
  74. return false;
  75. }
  76. /**
  77. * Convert image data if necessary - for example when format is not supported by rtf.
  78. *
  79. * @param data Image
  80. * @param type Format type
  81. */
  82. public FormatBase convert(FormatBase format, byte[] data) {
  83. return format;
  84. }
  85. /**
  86. * Determine image file format.
  87. *
  88. * @param data Image
  89. *
  90. * @return Image format class
  91. */
  92. public static FormatBase determineFormat(byte[] data) {
  93. if (FormatPNG.isFormat(data)) {
  94. return new FormatPNG();
  95. } else if (FormatJPG.isFormat(data)) {
  96. return new FormatJPG();
  97. } else if (FormatEMF.isFormat(data)) {
  98. return new FormatEMF();
  99. } else if (FormatGIF.isFormat(data)) {
  100. return new FormatGIF();
  101. } else if (FormatBMP.isFormat(data)) {
  102. return new FormatBMP();
  103. } else {
  104. return null;
  105. }
  106. }
  107. /**
  108. * Get image type.
  109. *
  110. * @return Image format class
  111. */
  112. public int getType() {
  113. return ImageConstants.I_NOT_SUPPORTED;
  114. }
  115. /**
  116. * Get rtf tag.
  117. *
  118. * @return Rtf tag for image format.
  119. */
  120. public String getRtfTag() {
  121. return "";
  122. }
  123. }
  124. private static class FormatGIF extends FormatBase {
  125. public static boolean isFormat(byte[] data) {
  126. // Indentifier "GIF8" on position 0
  127. byte [] pattern = new byte [] {(byte) 0x47, (byte) 0x49, (byte) 0x46, (byte) 0x38};
  128. return ImageUtil.compareHexValues(pattern, data, 0, true);
  129. }
  130. public int getType() {
  131. return ImageConstants.I_GIF;
  132. }
  133. }
  134. private static class FormatEMF extends FormatBase {
  135. public static boolean isFormat(byte[] data) {
  136. // No offical Indentifier known
  137. byte [] pattern = new byte [] {(byte) 0x01, (byte) 0x00, (byte) 0x00};
  138. return ImageUtil.compareHexValues(pattern, data, 0, true);
  139. }
  140. public int getType() {
  141. return ImageConstants.I_EMF;
  142. }
  143. public String getRtfTag() {
  144. return "emfblip";
  145. }
  146. }
  147. private static class FormatBMP extends FormatBase {
  148. public static boolean isFormat(byte[] data) {
  149. byte [] pattern = new byte [] {(byte) 0x42, (byte) 0x4D};
  150. return ImageUtil.compareHexValues(pattern, data, 0, true);
  151. }
  152. public int getType() {
  153. return ImageConstants.I_BMP;
  154. }
  155. }
  156. private static class FormatJPG extends FormatBase {
  157. public static boolean isFormat(byte[] data) {
  158. // Indentifier "0xFFD8" on position 0
  159. byte [] pattern = new byte [] {(byte) 0xFF, (byte) 0xD8};
  160. return ImageUtil.compareHexValues(pattern, data, 0, true);
  161. }
  162. public int getType() {
  163. return ImageConstants.I_JPG;
  164. }
  165. public String getRtfTag() {
  166. return "jpegblip";
  167. }
  168. }
  169. private static class FormatPNG extends FormatBase {
  170. public static boolean isFormat(byte[] data) {
  171. // Indentifier "PNG" on position 1
  172. byte [] pattern = new byte [] {(byte) 0x50, (byte) 0x4E, (byte) 0x47};
  173. return ImageUtil.compareHexValues(pattern, data, 1, true);
  174. }
  175. public int getType() {
  176. return ImageConstants.I_PNG;
  177. }
  178. public String getRtfTag() {
  179. return "pngblip";
  180. }
  181. }
  182. //////////////////////////////////////////////////
  183. // @@ Members
  184. //////////////////////////////////////////////////
  185. /**
  186. * The url of the image
  187. */
  188. protected URL url = null;
  189. /**
  190. * The height of the image (in pixels)
  191. */
  192. protected int height = -1;
  193. /**
  194. * The desired percent value of the height
  195. */
  196. protected int heightPercent = -1;
  197. /**
  198. * The desired height (in twips)
  199. */
  200. protected int heightDesired = -1;
  201. /**
  202. * Flag whether the desired height is a percentage
  203. */
  204. protected boolean perCentH = false;
  205. /**
  206. * The width of the image (in pixels)
  207. */
  208. protected int width = -1;
  209. /**
  210. * The desired percent value of the width
  211. */
  212. protected int widthPercent = -1;
  213. /**
  214. * The desired width (in twips)
  215. */
  216. protected int widthDesired = -1;
  217. /**
  218. * Flag whether the desired width is a percentage
  219. */
  220. protected boolean perCentW = false;
  221. /**
  222. * Flag whether the image size shall be adjusted
  223. */
  224. protected boolean scaleUniform = false;
  225. /** cropping on left/top/right/bottom edges for \piccrop*N */
  226. private int[] cropValues = new int[4];
  227. /**
  228. * Graphic compression rate
  229. */
  230. protected int graphicCompressionRate = 80;
  231. /** The image data */
  232. private byte[] imagedata = null;
  233. /** The image format */
  234. private FormatBase imageformat;
  235. //////////////////////////////////////////////////
  236. // @@ Construction
  237. //////////////////////////////////////////////////
  238. /**
  239. * Default constructor.
  240. * Create an RTF element as a child of given container.
  241. *
  242. * @param container a <code>RtfContainer</code> value
  243. * @param writer a <code>Writer</code> value
  244. * @throws IOException for I/O problems
  245. */
  246. public RtfExternalGraphic(RtfContainer container, Writer writer) throws IOException {
  247. super (container, writer);
  248. }
  249. /**
  250. * Default constructor.
  251. *
  252. * @param container a <code>RtfContainer</code> value
  253. * @param writer a <code>Writer</code> value
  254. * @param attributes a <code>RtfAttributes</code> value
  255. * @throws IOException for I/O problems
  256. */
  257. public RtfExternalGraphic(RtfContainer container, Writer writer,
  258. RtfAttributes attributes) throws IOException {
  259. super (container, writer, attributes);
  260. }
  261. //////////////////////////////////////////////////
  262. // @@ RtfElement implementation
  263. //////////////////////////////////////////////////
  264. /**
  265. * RtfElement override - catches ExternalGraphicException and writes a warning
  266. * message to the document if image cannot be read
  267. * @throws IOException for I/O problems
  268. */
  269. protected void writeRtfContent() throws IOException {
  270. try {
  271. writeRtfContentWithException();
  272. } catch (ExternalGraphicException ie) {
  273. writeExceptionInRtf(ie);
  274. }
  275. }
  276. /**
  277. * Writes the RTF content to m_writer - this one throws ExternalGraphicExceptions
  278. *
  279. * @exception IOException On error
  280. */
  281. protected void writeRtfContentWithException() throws IOException {
  282. if (writer == null) {
  283. return;
  284. }
  285. if (url == null && imagedata == null) {
  286. throw new ExternalGraphicException(
  287. "No image data is available (neither URL, nor in-memory)");
  288. }
  289. String linkToRoot = System.getProperty("jfor_link_to_root");
  290. if (url != null && linkToRoot != null) {
  291. writer.write("{\\field {\\* \\fldinst { INCLUDEPICTURE \"");
  292. writer.write(linkToRoot);
  293. File urlFile = new File(url.getFile());
  294. writer.write(urlFile.getName());
  295. writer.write("\" \\\\* MERGEFORMAT \\\\d }}}");
  296. return;
  297. }
  298. // getRtfFile ().getLog ().logInfo ("Writing image '" + url + "'.");
  299. if (imagedata == null) {
  300. try {
  301. final InputStream in = url.openStream();
  302. try {
  303. imagedata = IOUtils.toByteArray(url.openStream());
  304. } finally {
  305. IOUtils.closeQuietly(in);
  306. }
  307. } catch (Exception e) {
  308. throw new ExternalGraphicException("The attribute 'src' of "
  309. + "<fo:external-graphic> has a invalid value: '"
  310. + url + "' (" + e + ")");
  311. }
  312. }
  313. if (imagedata == null) {
  314. return;
  315. }
  316. // Determine image file format
  317. String file = (url != null ? url.getFile() : "<unknown>");
  318. imageformat = FormatBase.determineFormat(imagedata);
  319. if (imageformat != null) {
  320. imageformat = imageformat.convert(imageformat, imagedata);
  321. }
  322. if (imageformat == null
  323. || imageformat.getType() == ImageConstants.I_NOT_SUPPORTED
  324. || "".equals(imageformat.getRtfTag())) {
  325. throw new ExternalGraphicException("The tag <fo:external-graphic> "
  326. + "does not support "
  327. + file.substring(file.lastIndexOf(".") + 1)
  328. + " - image type.");
  329. }
  330. // Writes the beginning of the rtf image
  331. writeGroupMark(true);
  332. writeStarControlWord("shppict");
  333. writeGroupMark(true);
  334. writeControlWord("pict");
  335. StringBuffer buf = new StringBuffer(imagedata.length * 3);
  336. writeControlWord(imageformat.getRtfTag());
  337. computeImageSize();
  338. writeSizeInfo();
  339. writeAttributes(getRtfAttributes(), null);
  340. for (int i = 0; i < imagedata.length; i++) {
  341. int iData = imagedata [i];
  342. // Make positive byte
  343. if (iData < 0) {
  344. iData += 256;
  345. }
  346. if (iData < 16) {
  347. // Set leading zero and append
  348. buf.append('0');
  349. }
  350. buf.append(Integer.toHexString(iData));
  351. }
  352. int len = buf.length();
  353. char[] chars = new char[len];
  354. buf.getChars(0, len, chars, 0);
  355. writer.write(chars);
  356. // Writes the end of RTF image
  357. writeGroupMark(false);
  358. writeGroupMark(false);
  359. }
  360. private void computeImageSize () {
  361. if (imageformat.getType() == ImageConstants.I_PNG) {
  362. width = ImageUtil.getIntFromByteArray(imagedata, 16, 4, true);
  363. height = ImageUtil.getIntFromByteArray(imagedata, 20, 4, true);
  364. } else if (imageformat.getType() == ImageConstants.I_JPG) {
  365. int basis = -1;
  366. byte ff = (byte) 0xff;
  367. byte c0 = (byte) 0xc0;
  368. for (int i = 0; i < imagedata.length; i++) {
  369. byte b = imagedata[i];
  370. if (b != ff) {
  371. continue;
  372. }
  373. if (i == imagedata.length - 1) {
  374. continue;
  375. }
  376. b = imagedata[i + 1];
  377. if (b != c0) {
  378. continue;
  379. }
  380. basis = i + 5;
  381. break;
  382. }
  383. if (basis != -1) {
  384. width = ImageUtil.getIntFromByteArray(imagedata, basis + 2, 2, true);
  385. height = ImageUtil.getIntFromByteArray(imagedata, basis, 2, true);
  386. }
  387. } else if (imageformat.getType() == ImageConstants.I_EMF) {
  388. int i = 0;
  389. i = ImageUtil.getIntFromByteArray(imagedata, 151, 4, false);
  390. if (i != 0 ) {
  391. width = i;
  392. }
  393. i = ImageUtil.getIntFromByteArray(imagedata, 155, 4, false);
  394. if (i != 0 ) {
  395. height = i;
  396. }
  397. }
  398. }
  399. private void writeSizeInfo () throws IOException {
  400. // Set image size
  401. if (width != -1) {
  402. writeControlWord("picw" + width);
  403. }
  404. if (height != -1) {
  405. writeControlWord("pich" + height);
  406. }
  407. if (widthDesired != -1) {
  408. if (perCentW) {
  409. writeControlWord("picscalex" + widthDesired);
  410. } else {
  411. //writeControlWord("picscalex" + widthDesired * 100 / width);
  412. writeControlWord("picwgoal" + widthDesired);
  413. }
  414. } else if (scaleUniform && heightDesired != -1) {
  415. if (perCentH) {
  416. writeControlWord("picscalex" + heightDesired);
  417. } else {
  418. writeControlWord("picscalex" + heightDesired * 100 / height);
  419. }
  420. }
  421. if (heightDesired != -1) {
  422. if (perCentH) {
  423. writeControlWord("picscaley" + heightDesired);
  424. } else {
  425. //writeControlWord("picscaley" + heightDesired * 100 / height);
  426. writeControlWord("pichgoal" + heightDesired);
  427. }
  428. } else if (scaleUniform && widthDesired != -1) {
  429. if (perCentW) {
  430. writeControlWord("picscaley" + widthDesired);
  431. } else {
  432. writeControlWord("picscaley" + widthDesired * 100 / width);
  433. }
  434. }
  435. if (this.cropValues[0] != 0) {
  436. writeOneAttribute("piccropl", new Integer(this.cropValues[0]));
  437. }
  438. if (this.cropValues[1] != 0) {
  439. writeOneAttribute("piccropt", new Integer(this.cropValues[1]));
  440. }
  441. if (this.cropValues[2] != 0) {
  442. writeOneAttribute("piccropr", new Integer(this.cropValues[2]));
  443. }
  444. if (this.cropValues[3] != 0) {
  445. writeOneAttribute("piccropb", new Integer(this.cropValues[3]));
  446. }
  447. }
  448. //////////////////////////////////////////////////
  449. // @@ Member access
  450. //////////////////////////////////////////////////
  451. /**
  452. * Sets the desired height of the image.
  453. *
  454. * @param theHeight The desired image height (as a string in twips or as a percentage)
  455. */
  456. public void setHeight(String theHeight) {
  457. this.heightDesired = ImageUtil.getInt(theHeight);
  458. this.perCentH = ImageUtil.isPercent(theHeight);
  459. }
  460. /**
  461. * Sets the desired width of the image.
  462. *
  463. * @param theWidth The desired image width (as a string in twips or as a percentage)
  464. */
  465. public void setWidth(String theWidth) {
  466. this.widthDesired = ImageUtil.getInt(theWidth);
  467. this.perCentW = ImageUtil.isPercent(theWidth);
  468. }
  469. /**
  470. * Sets the desired width of the image.
  471. * @param twips The desired image width (in twips)
  472. */
  473. public void setWidthTwips(int twips) {
  474. this.widthDesired = twips;
  475. this.perCentW = false;
  476. }
  477. /**
  478. * Sets the desired height of the image.
  479. * @param twips The desired image height (in twips)
  480. */
  481. public void setHeightTwips(int twips) {
  482. this.heightDesired = twips;
  483. this.perCentH = false;
  484. }
  485. /**
  486. * Sets the flag whether the image size shall be adjusted.
  487. *
  488. * @param value
  489. * true image width or height shall be adjusted automatically\n
  490. * false no adjustment
  491. */
  492. public void setScaling(String value) {
  493. setUniformScaling("uniform".equalsIgnoreCase(value));
  494. }
  495. /**
  496. * Sets the flag whether the image size shall be adjusted.
  497. *
  498. * @param uniform
  499. * true image width or height shall be adjusted automatically\n
  500. * false no adjustment
  501. */
  502. public void setUniformScaling(boolean uniform) {
  503. this.scaleUniform = uniform;
  504. }
  505. /**
  506. * Sets cropping values for all four edges for the \piccrop*N commands.
  507. * A positive value crops toward the center of the picture;
  508. * a negative value crops away from the center, adding a space border around the picture
  509. * @param left left cropping value (in twips)
  510. * @param top top cropping value (in twips)
  511. * @param right right cropping value (in twips)
  512. * @param bottom bottom cropping value (in twips)
  513. */
  514. public void setCropping(int left, int top, int right, int bottom) {
  515. this.cropValues[0] = left;
  516. this.cropValues[1] = top;
  517. this.cropValues[2] = right;
  518. this.cropValues[3] = bottom;
  519. }
  520. /**
  521. * Sets the binary imagedata of the image.
  522. *
  523. * @param data binary imagedata as read from file.
  524. * @throws IOException On error
  525. */
  526. public void setImageData(byte[] data) throws IOException {
  527. this.imagedata = data;
  528. }
  529. /**
  530. * Sets the url of the image.
  531. *
  532. * @param urlString Image url like "file://..."
  533. * @throws IOException On error
  534. */
  535. public void setURL(String urlString) throws IOException {
  536. URL tmpUrl = null;
  537. try {
  538. tmpUrl = new URL (urlString);
  539. } catch (MalformedURLException e) {
  540. try {
  541. tmpUrl = new File (urlString).toURL ();
  542. } catch (MalformedURLException ee) {
  543. throw new ExternalGraphicException("The attribute 'src' of "
  544. + "<fo:external-graphic> has a invalid value: '"
  545. + urlString + "' (" + ee + ")");
  546. }
  547. }
  548. this.url = tmpUrl;
  549. }
  550. /**
  551. * Gets the compression rate for the image in percent.
  552. * @return Compression rate
  553. */
  554. public int getCompressionRate () {
  555. return graphicCompressionRate;
  556. }
  557. /**
  558. * Sets the compression rate for the image in percent.
  559. *
  560. * @param percent Compression rate
  561. * @return true if the compression rate is valid (0..100), false if invalid
  562. */
  563. public boolean setCompressionRate (int percent) {
  564. if (percent < 1 || percent > 100) {
  565. return false;
  566. }
  567. graphicCompressionRate = percent;
  568. return true;
  569. }
  570. //////////////////////////////////////////////////
  571. // @@ Helpers
  572. //////////////////////////////////////////////////
  573. /**
  574. * @return true if this element would generate no "useful" RTF content
  575. */
  576. public boolean isEmpty() {
  577. return url == null;
  578. }
  579. }