Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

PSImageHandlerSVG.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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.ps;
  19. import java.awt.Color;
  20. import java.awt.Dimension;
  21. import java.awt.Rectangle;
  22. import java.awt.geom.AffineTransform;
  23. import java.awt.geom.Rectangle2D;
  24. import java.awt.image.BufferedImage;
  25. import java.awt.image.ColorModel;
  26. import java.io.ByteArrayInputStream;
  27. import java.io.ByteArrayOutputStream;
  28. import java.io.IOException;
  29. import java.io.InputStream;
  30. import java.util.ArrayList;
  31. import java.util.HashMap;
  32. import java.util.List;
  33. import javax.imageio.ImageIO;
  34. import org.w3c.dom.Document;
  35. import org.w3c.dom.Node;
  36. import org.w3c.dom.NodeList;
  37. import org.apache.batik.bridge.BridgeContext;
  38. import org.apache.batik.bridge.GVTBuilder;
  39. import org.apache.batik.gvt.GraphicsNode;
  40. import org.apache.batik.transcoder.SVGAbstractTranscoder;
  41. import org.apache.batik.transcoder.TranscoderException;
  42. import org.apache.batik.transcoder.TranscoderInput;
  43. import org.apache.batik.transcoder.TranscoderOutput;
  44. import org.apache.batik.transcoder.image.PNGTranscoder;
  45. import org.apache.xmlgraphics.image.loader.Image;
  46. import org.apache.xmlgraphics.image.loader.ImageFlavor;
  47. import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM;
  48. import org.apache.xmlgraphics.ps.ImageEncoder;
  49. import org.apache.xmlgraphics.ps.ImageEncodingHelper;
  50. import org.apache.xmlgraphics.ps.PSGenerator;
  51. import org.apache.fop.image.loader.batik.BatikImageFlavors;
  52. import org.apache.fop.image.loader.batik.BatikUtil;
  53. import org.apache.fop.render.ImageHandler;
  54. import org.apache.fop.render.RenderingContext;
  55. import org.apache.fop.render.ps.svg.PSSVGGraphics2D;
  56. import org.apache.fop.svg.SVGEventProducer;
  57. import org.apache.fop.svg.SVGUserAgent;
  58. import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
  59. /**
  60. * Image handler implementation which handles SVG images for PostScript output.
  61. */
  62. public class PSImageHandlerSVG implements ImageHandler {
  63. private static final Color FALLBACK_COLOR = new Color(255, 33, 117);
  64. private HashMap<String, String> gradientsFound = new HashMap<String, String>();
  65. private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {
  66. BatikImageFlavors.SVG_DOM
  67. };
  68. /** {@inheritDoc} */
  69. public void handleImage(RenderingContext context, Image image, Rectangle pos)
  70. throws IOException {
  71. PSRenderingContext psContext = (PSRenderingContext)context;
  72. PSGenerator gen = psContext.getGenerator();
  73. ImageXMLDOM imageSVG = (ImageXMLDOM)image;
  74. if (shouldRaster(imageSVG)) {
  75. InputStream is = renderSVGToInputStream(context, imageSVG);
  76. float x = (float) pos.getX() / 1000f;
  77. float y = (float) pos.getY() / 1000f;
  78. float w = (float) pos.getWidth() / 1000f;
  79. float h = (float) pos.getHeight() / 1000f;
  80. Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h);
  81. MaskedImage mi = convertToRGB(ImageIO.read(is));
  82. BufferedImage ri = mi.getImage();
  83. ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri);
  84. Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight());
  85. String imgDescription = ri.getClass().getName();
  86. ImageEncodingHelper helper = new ImageEncodingHelper(ri);
  87. ColorModel cm = helper.getEncodedColorModel();
  88. PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen, ri, mi.getMaskColor());
  89. } else {
  90. //Controls whether text painted by Batik is generated using text or path operations
  91. boolean strokeText = shouldStrokeText(imageSVG.getDocument().getChildNodes());
  92. //TODO Configure text stroking
  93. SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(),
  94. new FOPFontFamilyResolverImpl(psContext.getFontInfo()), new AffineTransform());
  95. PSSVGGraphics2D graphics = new PSSVGGraphics2D(strokeText, gen);
  96. graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
  97. BridgeContext ctx = new PSBridgeContext(ua,
  98. (strokeText ? null : psContext.getFontInfo()),
  99. context.getUserAgent().getImageManager(),
  100. context.getUserAgent().getImageSessionContext());
  101. //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine)
  102. //to it.
  103. Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument());
  104. GraphicsNode root;
  105. try {
  106. GVTBuilder builder = new GVTBuilder();
  107. root = builder.build(ctx, clonedDoc);
  108. } catch (Exception e) {
  109. SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
  110. context.getUserAgent().getEventBroadcaster());
  111. eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI());
  112. return;
  113. }
  114. // get the 'width' and 'height' attributes of the SVG document
  115. float w = (float)ctx.getDocumentSize().getWidth() * 1000f;
  116. float h = (float)ctx.getDocumentSize().getHeight() * 1000f;
  117. float sx = pos.width / w;
  118. float sy = pos.height / h;
  119. gen.commentln("%FOPBeginSVG");
  120. gen.saveGraphicsState();
  121. final boolean clip = false;
  122. if (clip) {
  123. /*
  124. * Clip to the svg area.
  125. * Note: To have the svg overlay (under) a text area then use
  126. * an fo:block-container
  127. */
  128. gen.writeln("newpath");
  129. gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f,
  130. pos.width / 1000f, pos.height / 1000f);
  131. gen.writeln("clip");
  132. }
  133. // transform so that the coordinates (0,0) is from the top left
  134. // and positive is down and to the right. (0,0) is where the
  135. // viewBox puts it.
  136. gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f);
  137. AffineTransform transform = new AffineTransform();
  138. // scale to viewbox
  139. transform.translate(pos.getMinX(), pos.getMinY());
  140. gen.getCurrentState().concatMatrix(transform);
  141. try {
  142. root.paint(graphics);
  143. } catch (Exception e) {
  144. SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
  145. context.getUserAgent().getEventBroadcaster());
  146. eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI());
  147. }
  148. gen.restoreGraphicsState();
  149. gen.commentln("%FOPEndSVG");
  150. }
  151. }
  152. private InputStream renderSVGToInputStream(RenderingContext context, ImageXMLDOM imageSVG) throws IOException {
  153. PNGTranscoder png = new PNGTranscoder();
  154. Float width = getDimension(imageSVG.getDocument(), "width") * 8;
  155. png.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, width);
  156. Float height = getDimension(imageSVG.getDocument(), "height") * 8;
  157. png.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, height);
  158. TranscoderInput input = new TranscoderInput(imageSVG.getDocument());
  159. ByteArrayOutputStream os = new ByteArrayOutputStream();
  160. TranscoderOutput output = new TranscoderOutput(os);
  161. try {
  162. png.transcode(input, output);
  163. } catch (TranscoderException ex) {
  164. SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
  165. context.getUserAgent().getEventBroadcaster());
  166. eventProducer.svgRenderingError(this, ex, imageSVG.getInfo().getOriginalURI());
  167. } finally {
  168. os.flush();
  169. os.close();
  170. }
  171. return new ByteArrayInputStream(os.toByteArray());
  172. }
  173. private MaskedImage convertToRGB(BufferedImage alphaImage) {
  174. int[] red = new int[256];
  175. int[] green = new int[256];
  176. int[] blue = new int[256];
  177. BufferedImage rgbImage = new BufferedImage(alphaImage.getWidth(),
  178. alphaImage.getHeight(), BufferedImage.TYPE_INT_RGB);
  179. //Count occurances of each colour in image
  180. for (int cx = 0; cx < alphaImage.getWidth(); cx++) {
  181. for (int cy = 0; cy < alphaImage.getHeight(); cy++) {
  182. int pixelValue = alphaImage.getRGB(cx, cy);
  183. Color pixelColor = new Color(pixelValue);
  184. red[pixelColor.getRed()]++;
  185. green[pixelColor.getGreen()]++;
  186. blue[pixelColor.getBlue()]++;
  187. }
  188. }
  189. //Find colour not in image
  190. Color alphaSwap = null;
  191. for (int i = 0; i < 256; i++) {
  192. if (red[i] == 0) {
  193. alphaSwap = new Color(i, 0, 0);
  194. break;
  195. } else if (green[i] == 0) {
  196. alphaSwap = new Color(0, i, 0);
  197. break;
  198. } else if (blue[i] == 0) {
  199. alphaSwap = new Color(0, 0, i);
  200. break;
  201. }
  202. }
  203. //Check if all variations are used in all three colours
  204. if (alphaSwap == null) {
  205. //Fallback colour is no unique colour channel can be found
  206. alphaSwap = FALLBACK_COLOR;
  207. }
  208. //Replace alpha channel with the new mask colour
  209. for (int cx = 0; cx < alphaImage.getWidth(); cx++) {
  210. for (int cy = 0; cy < alphaImage.getHeight(); cy++) {
  211. int pixelValue = alphaImage.getRGB(cx, cy);
  212. if (pixelValue == 0) {
  213. rgbImage.setRGB(cx, cy, alphaSwap.getRGB());
  214. } else {
  215. rgbImage.setRGB(cx, cy, alphaImage.getRGB(cx, cy));
  216. }
  217. }
  218. }
  219. return new MaskedImage(rgbImage, alphaSwap);
  220. }
  221. private static class MaskedImage {
  222. private Color maskColor = new Color(0, 0, 0);
  223. private BufferedImage image;
  224. public MaskedImage(BufferedImage image, Color maskColor) {
  225. this.image = image;
  226. this.maskColor = maskColor;
  227. }
  228. public Color getMaskColor() {
  229. return maskColor;
  230. }
  231. public BufferedImage getImage() {
  232. return image;
  233. }
  234. }
  235. private Float getDimension(Document document, String dimension) {
  236. if (document.getFirstChild().getAttributes().getNamedItem(dimension) != null) {
  237. String width = document.getFirstChild().getAttributes().getNamedItem(dimension).getNodeValue();
  238. width = width.replaceAll("[^\\d.]", "");
  239. return Float.parseFloat(width);
  240. }
  241. return null;
  242. }
  243. private boolean shouldRaster(ImageXMLDOM image) {
  244. //A list of objects on which to check opacity
  245. try {
  246. List<String> gradMatches = new ArrayList<String>();
  247. gradMatches.add("radialGradient");
  248. gradMatches.add("linearGradient");
  249. return recurseSVGElements(image.getDocument().getChildNodes(), gradMatches, false);
  250. } finally {
  251. gradientsFound.clear();
  252. }
  253. }
  254. private boolean recurseSVGElements(NodeList childNodes, List<String> gradMatches, boolean isMatched) {
  255. boolean opacityFound = false;
  256. for (int i = 0; i < childNodes.getLength(); i++) {
  257. Node curNode = childNodes.item(i);
  258. if (isMatched && curNode.getLocalName() != null && curNode.getLocalName().equals("stop")) {
  259. if (curNode.getAttributes().getNamedItem("style") != null) {
  260. String[] stylePairs = curNode.getAttributes().getNamedItem("style").getNodeValue()
  261. .split(";");
  262. for (int styleAtt = 0; styleAtt < stylePairs.length; styleAtt++) {
  263. String[] style = stylePairs[styleAtt].split(":");
  264. if (style[0].equalsIgnoreCase("stop-opacity")) {
  265. if (Double.parseDouble(style[1]) < 1) {
  266. return true;
  267. }
  268. }
  269. }
  270. }
  271. if (curNode.getAttributes().getNamedItem("stop-opacity") != null) {
  272. String opacityValue = curNode.getAttributes().getNamedItem("stop-opacity").getNodeValue();
  273. if (Double.parseDouble(opacityValue) < 1) {
  274. return true;
  275. }
  276. }
  277. }
  278. String nodeName = curNode.getLocalName();
  279. boolean inMatch = false;
  280. if (!isMatched) {
  281. inMatch = nodeName != null && gradMatches.contains(nodeName);
  282. if (inMatch) {
  283. gradientsFound.put(curNode.getAttributes().getNamedItem("id").getNodeValue(), nodeName);
  284. }
  285. } else {
  286. inMatch = true;
  287. }
  288. opacityFound = recurseSVGElements(curNode.getChildNodes(), gradMatches, inMatch);
  289. if (opacityFound) {
  290. return true;
  291. }
  292. }
  293. return opacityFound;
  294. }
  295. public static boolean shouldStrokeText(NodeList childNodes) {
  296. for (int i = 0; i < childNodes.getLength(); i++) {
  297. Node curNode = childNodes.item(i);
  298. if (shouldStrokeText(curNode.getChildNodes())) {
  299. return true;
  300. }
  301. if ("text".equals(curNode.getLocalName())) {
  302. return curNode.getAttributes().getNamedItem("filter") != null;
  303. }
  304. }
  305. return false;
  306. }
  307. /** {@inheritDoc} */
  308. public int getPriority() {
  309. return 400;
  310. }
  311. /** {@inheritDoc} */
  312. public Class getSupportedImageClass() {
  313. return ImageXMLDOM.class;
  314. }
  315. /** {@inheritDoc} */
  316. public ImageFlavor[] getSupportedImageFlavors() {
  317. return FLAVORS;
  318. }
  319. /** {@inheritDoc} */
  320. public boolean isCompatible(RenderingContext targetContext, Image image) {
  321. if (targetContext instanceof PSRenderingContext) {
  322. PSRenderingContext psContext = (PSRenderingContext)targetContext;
  323. return !psContext.isCreateForms()
  324. && (image == null || (image instanceof ImageXMLDOM
  325. && image.getFlavor().isCompatible(BatikImageFlavors.SVG_DOM)));
  326. }
  327. return false;
  328. }
  329. }