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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  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. * Only the image types PNG, JPEG and EMF are supported
  42. * The GIF is supported, too, but will be converted to JPG
  43. * Only the attributes SRC (required), WIDTH, HEIGHT, SCALING are supported
  44. * The SCALING attribute supports (uniform | non-uniform)
  45. *
  46. * Known Bugs:
  47. * If the emf image has a desired size, the image will be clipped
  48. * The emf, jpg, png image will not be displayed in correct size
  49. *
  50. * This work was originally authored by <a href="mailto:a.putz@skynamics.com">Andreas Putz</a>
  51. * This work was originally authored by 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 format Format type
  80. * @param data Image
  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;
  189. /**
  190. * The height of the image (in pixels)
  191. */
  192. protected int height = -1;
  193. /**
  194. * The desired height (in twips)
  195. */
  196. protected int heightDesired = -1;
  197. /**
  198. * Flag whether the desired height is a percentage
  199. */
  200. protected boolean perCentH;
  201. /**
  202. * The width of the image (in pixels)
  203. */
  204. protected int width = -1;
  205. /**
  206. * The desired width (in twips)
  207. */
  208. protected int widthDesired = -1;
  209. /**
  210. * Flag whether the desired width is a percentage
  211. */
  212. protected boolean perCentW;
  213. /**
  214. * Flag whether the image size shall be adjusted
  215. */
  216. protected boolean scaleUniform;
  217. /** cropping on left/top/right/bottom edges for \piccrop*N */
  218. private int[] cropValues = new int[4];
  219. /**
  220. * Graphic compression rate
  221. */
  222. protected int graphicCompressionRate = 80;
  223. /** The image data */
  224. private byte[] imagedata;
  225. /** The image format */
  226. private FormatBase imageformat;
  227. //////////////////////////////////////////////////
  228. // @@ Construction
  229. //////////////////////////////////////////////////
  230. /**
  231. * Default constructor.
  232. * Create an RTF element as a child of given container.
  233. *
  234. * @param container a <code>RtfContainer</code> value
  235. * @param writer a <code>Writer</code> value
  236. * @throws IOException for I/O problems
  237. */
  238. public RtfExternalGraphic(RtfContainer container, Writer writer) throws IOException {
  239. super(container, writer);
  240. }
  241. /**
  242. * Default constructor.
  243. *
  244. * @param container a <code>RtfContainer</code> value
  245. * @param writer a <code>Writer</code> value
  246. * @param attributes a <code>RtfAttributes</code> value
  247. * @throws IOException for I/O problems
  248. */
  249. public RtfExternalGraphic(RtfContainer container, Writer writer,
  250. RtfAttributes attributes) throws IOException {
  251. super(container, writer, attributes);
  252. }
  253. //////////////////////////////////////////////////
  254. // @@ RtfElement implementation
  255. //////////////////////////////////////////////////
  256. /**
  257. * RtfElement override - catches ExternalGraphicException and writes a warning
  258. * message to the document if image cannot be read
  259. * @throws IOException for I/O problems
  260. */
  261. protected void writeRtfContent() throws IOException {
  262. try {
  263. writeRtfContentWithException();
  264. } catch (ExternalGraphicException ie) {
  265. writeExceptionInRtf(ie);
  266. }
  267. }
  268. /**
  269. * Writes the RTF content to m_writer - this one throws ExternalGraphicExceptions
  270. *
  271. * @exception IOException On error
  272. */
  273. protected void writeRtfContentWithException() throws IOException {
  274. if (writer == null) {
  275. return;
  276. }
  277. if (url == null && imagedata == null) {
  278. throw new ExternalGraphicException(
  279. "No image data is available (neither URL, nor in-memory)");
  280. }
  281. String linkToRoot = System.getProperty("jfor_link_to_root");
  282. if (url != null && linkToRoot != null) {
  283. writer.write("{\\field {\\* \\fldinst { INCLUDEPICTURE \"");
  284. writer.write(linkToRoot);
  285. File urlFile = new File(url.getFile());
  286. writer.write(urlFile.getName());
  287. writer.write("\" \\\\* MERGEFORMAT \\\\d }}}");
  288. return;
  289. }
  290. // getRtfFile ().getLog ().logInfo ("Writing image '" + url + "'.");
  291. if (imagedata == null) {
  292. try {
  293. final InputStream in = url.openStream();
  294. try {
  295. imagedata = IOUtils.toByteArray(url.openStream());
  296. } finally {
  297. IOUtils.closeQuietly(in);
  298. }
  299. } catch (Exception e) {
  300. throw new ExternalGraphicException("The attribute 'src' of "
  301. + "<fo:external-graphic> has a invalid value: '"
  302. + url + "' (" + e + ")");
  303. }
  304. }
  305. if (imagedata == null) {
  306. return;
  307. }
  308. // Determine image file format
  309. String file = (url != null ? url.getFile() : "<unknown>");
  310. imageformat = FormatBase.determineFormat(imagedata);
  311. if (imageformat != null) {
  312. imageformat = imageformat.convert(imageformat, imagedata);
  313. }
  314. if (imageformat == null
  315. || imageformat.getType() == ImageConstants.I_NOT_SUPPORTED
  316. || "".equals(imageformat.getRtfTag())) {
  317. throw new ExternalGraphicException("The tag <fo:external-graphic> "
  318. + "does not support "
  319. + file.substring(file.lastIndexOf(".") + 1)
  320. + " - image type.");
  321. }
  322. // Writes the beginning of the rtf image
  323. writeGroupMark(true);
  324. writeStarControlWord("shppict");
  325. writeGroupMark(true);
  326. writeControlWord("pict");
  327. StringBuffer buf = new StringBuffer(imagedata.length * 3);
  328. writeControlWord(imageformat.getRtfTag());
  329. computeImageSize();
  330. writeSizeInfo();
  331. writeAttributes(getRtfAttributes(), null);
  332. for (byte anImagedata : imagedata) {
  333. int iData = anImagedata;
  334. // Make positive byte
  335. if (iData < 0) {
  336. iData += 256;
  337. }
  338. if (iData < 16) {
  339. // Set leading zero and append
  340. buf.append('0');
  341. }
  342. buf.append(Integer.toHexString(iData));
  343. }
  344. int len = buf.length();
  345. char[] chars = new char[len];
  346. buf.getChars(0, len, chars, 0);
  347. writer.write(chars);
  348. // Writes the end of RTF image
  349. writeGroupMark(false);
  350. writeGroupMark(false);
  351. }
  352. private void computeImageSize() {
  353. if (imageformat.getType() == ImageConstants.I_PNG) {
  354. width = ImageUtil.getIntFromByteArray(imagedata, 16, 4, true);
  355. height = ImageUtil.getIntFromByteArray(imagedata, 20, 4, true);
  356. } else if (imageformat.getType() == ImageConstants.I_JPG) {
  357. int basis = -1;
  358. byte ff = (byte) 0xff;
  359. byte c0 = (byte) 0xc0;
  360. for (int i = 0; i < imagedata.length; i++) {
  361. byte b = imagedata[i];
  362. if (b != ff) {
  363. continue;
  364. }
  365. if (i == imagedata.length - 1) {
  366. continue;
  367. }
  368. b = imagedata[i + 1];
  369. if (b != c0) {
  370. continue;
  371. }
  372. basis = i + 5;
  373. break;
  374. }
  375. if (basis != -1) {
  376. width = ImageUtil.getIntFromByteArray(imagedata, basis + 2, 2, true);
  377. height = ImageUtil.getIntFromByteArray(imagedata, basis, 2, true);
  378. }
  379. } else if (imageformat.getType() == ImageConstants.I_EMF) {
  380. int i = 0;
  381. i = ImageUtil.getIntFromByteArray(imagedata, 151, 4, false);
  382. if (i != 0) {
  383. width = i;
  384. }
  385. i = ImageUtil.getIntFromByteArray(imagedata, 155, 4, false);
  386. if (i != 0) {
  387. height = i;
  388. }
  389. }
  390. }
  391. private void writeSizeInfo() throws IOException {
  392. // Set image size
  393. if (width != -1) {
  394. writeControlWord("picw" + width);
  395. }
  396. if (height != -1) {
  397. writeControlWord("pich" + height);
  398. }
  399. if (widthDesired != -1) {
  400. if (perCentW) {
  401. writeControlWord("picscalex" + widthDesired);
  402. } else {
  403. //writeControlWord("picscalex" + widthDesired * 100 / width);
  404. writeControlWord("picwgoal" + widthDesired);
  405. }
  406. } else if (scaleUniform && heightDesired != -1) {
  407. if (perCentH) {
  408. writeControlWord("picscalex" + heightDesired);
  409. } else {
  410. writeControlWord("picscalex" + heightDesired * 100 / height);
  411. }
  412. }
  413. if (heightDesired != -1) {
  414. if (perCentH) {
  415. writeControlWord("picscaley" + heightDesired);
  416. } else {
  417. //writeControlWord("picscaley" + heightDesired * 100 / height);
  418. writeControlWord("pichgoal" + heightDesired);
  419. }
  420. } else if (scaleUniform && widthDesired != -1) {
  421. if (perCentW) {
  422. writeControlWord("picscaley" + widthDesired);
  423. } else {
  424. writeControlWord("picscaley" + widthDesired * 100 / width);
  425. }
  426. }
  427. if (this.cropValues[0] != 0) {
  428. writeOneAttribute("piccropl", this.cropValues[0]);
  429. }
  430. if (this.cropValues[1] != 0) {
  431. writeOneAttribute("piccropt", this.cropValues[1]);
  432. }
  433. if (this.cropValues[2] != 0) {
  434. writeOneAttribute("piccropr", this.cropValues[2]);
  435. }
  436. if (this.cropValues[3] != 0) {
  437. writeOneAttribute("piccropb", this.cropValues[3]);
  438. }
  439. }
  440. //////////////////////////////////////////////////
  441. // @@ Member access
  442. //////////////////////////////////////////////////
  443. /**
  444. * Sets the desired height of the image.
  445. *
  446. * @param theHeight The desired image height (as a string in twips or as a percentage)
  447. */
  448. public void setHeight(String theHeight) {
  449. this.heightDesired = ImageUtil.getInt(theHeight);
  450. this.perCentH = ImageUtil.isPercent(theHeight);
  451. }
  452. /**
  453. * Sets the desired width of the image.
  454. *
  455. * @param theWidth The desired image width (as a string in twips or as a percentage)
  456. */
  457. public void setWidth(String theWidth) {
  458. this.widthDesired = ImageUtil.getInt(theWidth);
  459. this.perCentW = ImageUtil.isPercent(theWidth);
  460. }
  461. /**
  462. * Sets the desired width of the image.
  463. * @param twips The desired image width (in twips)
  464. */
  465. public void setWidthTwips(int twips) {
  466. this.widthDesired = twips;
  467. this.perCentW = false;
  468. }
  469. /**
  470. * Sets the desired height of the image.
  471. * @param twips The desired image height (in twips)
  472. */
  473. public void setHeightTwips(int twips) {
  474. this.heightDesired = twips;
  475. this.perCentH = false;
  476. }
  477. /**
  478. * Sets the flag whether the image size shall be adjusted.
  479. *
  480. * @param value
  481. * true image width or height shall be adjusted automatically\n
  482. * false no adjustment
  483. */
  484. public void setScaling(String value) {
  485. setUniformScaling("uniform".equalsIgnoreCase(value));
  486. }
  487. /**
  488. * Sets the flag whether the image size shall be adjusted.
  489. *
  490. * @param uniform
  491. * true image width or height shall be adjusted automatically\n
  492. * false no adjustment
  493. */
  494. public void setUniformScaling(boolean uniform) {
  495. this.scaleUniform = uniform;
  496. }
  497. /**
  498. * Sets cropping values for all four edges for the \piccrop*N commands.
  499. * A positive value crops toward the center of the picture;
  500. * a negative value crops away from the center, adding a space border around the picture
  501. * @param left left cropping value (in twips)
  502. * @param top top cropping value (in twips)
  503. * @param right right cropping value (in twips)
  504. * @param bottom bottom cropping value (in twips)
  505. */
  506. public void setCropping(int left, int top, int right, int bottom) {
  507. this.cropValues[0] = left;
  508. this.cropValues[1] = top;
  509. this.cropValues[2] = right;
  510. this.cropValues[3] = bottom;
  511. }
  512. /**
  513. * Sets the binary imagedata of the image.
  514. *
  515. * @param data binary imagedata as read from file.
  516. * @throws IOException On error
  517. */
  518. public void setImageData(byte[] data) throws IOException {
  519. this.imagedata = data;
  520. }
  521. /**
  522. * Sets the url of the image.
  523. *
  524. * @param urlString Image url like "file://..."
  525. * @throws IOException On error
  526. */
  527. public void setURL(String urlString) throws IOException {
  528. URL tmpUrl = null;
  529. try {
  530. tmpUrl = new URL(urlString);
  531. } catch (MalformedURLException e) {
  532. try {
  533. tmpUrl = new File(urlString).toURI().toURL();
  534. } catch (MalformedURLException ee) {
  535. throw new ExternalGraphicException("The attribute 'src' of "
  536. + "<fo:external-graphic> has a invalid value: '"
  537. + urlString + "' (" + ee + ")");
  538. }
  539. }
  540. this.url = tmpUrl;
  541. }
  542. /**
  543. * Gets the compression rate for the image in percent.
  544. * @return Compression rate
  545. */
  546. public int getCompressionRate() {
  547. return graphicCompressionRate;
  548. }
  549. /**
  550. * Sets the compression rate for the image in percent.
  551. *
  552. * @param percent Compression rate
  553. * @return true if the compression rate is valid (0..100), false if invalid
  554. */
  555. public boolean setCompressionRate(int percent) {
  556. if (percent < 1 || percent > 100) {
  557. return false;
  558. }
  559. graphicCompressionRate = percent;
  560. return true;
  561. }
  562. //////////////////////////////////////////////////
  563. // @@ Helpers
  564. //////////////////////////////////////////////////
  565. /**
  566. * @return true if this element would generate no "useful" RTF content
  567. */
  568. public boolean isEmpty() {
  569. return url == null;
  570. }
  571. }