Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

PCLPainter.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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.pcl;
  19. import java.awt.Color;
  20. import java.awt.Dimension;
  21. import java.awt.Graphics2D;
  22. import java.awt.Paint;
  23. import java.awt.Point;
  24. import java.awt.Rectangle;
  25. import java.awt.geom.AffineTransform;
  26. import java.awt.geom.Point2D;
  27. import java.awt.geom.Rectangle2D;
  28. import java.io.IOException;
  29. import java.util.Map;
  30. import java.util.Stack;
  31. import org.w3c.dom.Document;
  32. import org.apache.xmlgraphics.image.loader.ImageException;
  33. import org.apache.xmlgraphics.image.loader.ImageInfo;
  34. import org.apache.xmlgraphics.image.loader.ImageProcessingHints;
  35. import org.apache.xmlgraphics.image.loader.ImageSize;
  36. import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
  37. import org.apache.xmlgraphics.java2d.GraphicContext;
  38. import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
  39. import org.apache.fop.fonts.Font;
  40. import org.apache.fop.fonts.FontTriplet;
  41. import org.apache.fop.render.ImageHandlerUtil;
  42. import org.apache.fop.render.RenderingContext;
  43. import org.apache.fop.render.intermediate.AbstractIFPainter;
  44. import org.apache.fop.render.intermediate.IFException;
  45. import org.apache.fop.render.intermediate.IFState;
  46. import org.apache.fop.render.intermediate.IFUtil;
  47. import org.apache.fop.render.java2d.FontMetricsMapper;
  48. import org.apache.fop.render.java2d.Java2DPainter;
  49. import org.apache.fop.traits.BorderProps;
  50. import org.apache.fop.traits.RuleStyle;
  51. import org.apache.fop.util.CharUtilities;
  52. /**
  53. * {@link org.apache.fop.render.intermediate.IFPainter} implementation that produces PCL 5.
  54. */
  55. public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements PCLConstants {
  56. private static final boolean DEBUG = false;
  57. /** The PCL generator */
  58. private PCLGenerator gen;
  59. private PCLPageDefinition currentPageDefinition;
  60. private int currentPrintDirection = 0;
  61. //private GeneralPath currentPath = null;
  62. private Stack<GraphicContext> graphicContextStack = new Stack<GraphicContext>();
  63. private GraphicContext graphicContext = new GraphicContext();
  64. /**
  65. * Main constructor.
  66. * @param parent the parent document handler
  67. * @param pageDefinition the page definition describing the page to be rendered
  68. */
  69. public PCLPainter(PCLDocumentHandler parent, PCLPageDefinition pageDefinition) {
  70. super(parent);
  71. this.gen = parent.getPCLGenerator();
  72. this.state = IFState.create();
  73. this.currentPageDefinition = pageDefinition;
  74. }
  75. PCLRenderingUtil getPCLUtil() {
  76. return getDocumentHandler().getPCLUtil();
  77. }
  78. /** @return the target resolution */
  79. protected int getResolution() {
  80. int resolution = Math.round(getUserAgent().getTargetResolution());
  81. if (resolution <= 300) {
  82. return 300;
  83. } else {
  84. return 600;
  85. }
  86. }
  87. private boolean isSpeedOptimized() {
  88. return getPCLUtil().getRenderingMode() == PCLRenderingMode.SPEED;
  89. }
  90. //----------------------------------------------------------------------------------------------
  91. /** {@inheritDoc} */
  92. public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
  93. throws IFException {
  94. saveGraphicsState();
  95. try {
  96. concatenateTransformationMatrix(transform);
  97. /* PCL cannot clip!
  98. if (clipRect != null) {
  99. clipRect(clipRect);
  100. }*/
  101. } catch (IOException ioe) {
  102. throw new IFException("I/O error in startViewport()", ioe);
  103. }
  104. }
  105. /** {@inheritDoc} */
  106. public void endViewport() throws IFException {
  107. restoreGraphicsState();
  108. }
  109. /** {@inheritDoc} */
  110. public void startGroup(AffineTransform transform) throws IFException {
  111. saveGraphicsState();
  112. try {
  113. concatenateTransformationMatrix(transform);
  114. } catch (IOException ioe) {
  115. throw new IFException("I/O error in startGroup()", ioe);
  116. }
  117. }
  118. /** {@inheritDoc} */
  119. public void endGroup() throws IFException {
  120. restoreGraphicsState();
  121. }
  122. /** {@inheritDoc} */
  123. public void drawImage(String uri, Rectangle rect) throws IFException {
  124. drawImageUsingURI(uri, rect);
  125. }
  126. /** {@inheritDoc} */
  127. protected RenderingContext createRenderingContext() {
  128. PCLRenderingContext pdfContext = new PCLRenderingContext(
  129. getUserAgent(), this.gen, getPCLUtil()) {
  130. public Point2D transformedPoint(int x, int y) {
  131. return PCLPainter.this.transformedPoint(x, y);
  132. }
  133. public GraphicContext getGraphicContext() {
  134. return PCLPainter.this.graphicContext;
  135. }
  136. };
  137. return pdfContext;
  138. }
  139. /** {@inheritDoc} */
  140. public void drawImage(Document doc, Rectangle rect) throws IFException {
  141. drawImageUsingDocument(doc, rect);
  142. }
  143. /** {@inheritDoc} */
  144. public void clipRect(Rectangle rect) throws IFException {
  145. //PCL cannot clip (only HP GL/2 can)
  146. //If you need clipping support, switch to RenderingMode.BITMAP.
  147. }
  148. /** {@inheritDoc} */
  149. public void fillRect(Rectangle rect, Paint fill) throws IFException {
  150. if (fill == null) {
  151. return;
  152. }
  153. if (rect.width != 0 && rect.height != 0) {
  154. Color fillColor = null;
  155. if (fill != null) {
  156. if (fill instanceof Color) {
  157. fillColor = (Color)fill;
  158. } else {
  159. throw new UnsupportedOperationException("Non-Color paints NYI");
  160. }
  161. try {
  162. setCursorPos(rect.x, rect.y);
  163. gen.fillRect(rect.width, rect.height, fillColor);
  164. } catch (IOException ioe) {
  165. throw new IFException("I/O error in fillRect()", ioe);
  166. }
  167. }
  168. }
  169. }
  170. /** {@inheritDoc} */
  171. public void drawBorderRect(final Rectangle rect,
  172. final BorderProps top, final BorderProps bottom,
  173. final BorderProps left, final BorderProps right) throws IFException {
  174. if (isSpeedOptimized()) {
  175. super.drawBorderRect(rect, top, bottom, left, right);
  176. return;
  177. }
  178. if (top != null || bottom != null || left != null || right != null) {
  179. final Rectangle boundingBox = rect;
  180. final Dimension dim = boundingBox.getSize();
  181. Graphics2DImagePainter painter = new Graphics2DImagePainter() {
  182. public void paint(Graphics2D g2d, Rectangle2D area) {
  183. g2d.translate(-rect.x, -rect.y);
  184. Java2DPainter painter = new Java2DPainter(g2d,
  185. getContext(), getFontInfo(), state);
  186. try {
  187. painter.drawBorderRect(rect, top, bottom, left, right);
  188. } catch (IFException e) {
  189. //This should never happen with the Java2DPainter
  190. throw new RuntimeException("Unexpected error while painting borders", e);
  191. }
  192. }
  193. public Dimension getImageSize() {
  194. return dim.getSize();
  195. }
  196. };
  197. paintMarksAsBitmap(painter, boundingBox);
  198. }
  199. }
  200. /** {@inheritDoc} */
  201. public void drawLine(final Point start, final Point end,
  202. final int width, final Color color, final RuleStyle style)
  203. throws IFException {
  204. if (isSpeedOptimized()) {
  205. super.drawLine(start, end, width, color, style);
  206. return;
  207. }
  208. final Rectangle boundingBox = getLineBoundingBox(start, end, width);
  209. final Dimension dim = boundingBox.getSize();
  210. Graphics2DImagePainter painter = new Graphics2DImagePainter() {
  211. public void paint(Graphics2D g2d, Rectangle2D area) {
  212. g2d.translate(-boundingBox.x, -boundingBox.y);
  213. Java2DPainter painter = new Java2DPainter(g2d,
  214. getContext(), getFontInfo(), state);
  215. try {
  216. painter.drawLine(start, end, width, color, style);
  217. } catch (IFException e) {
  218. //This should never happen with the Java2DPainter
  219. throw new RuntimeException("Unexpected error while painting a line", e);
  220. }
  221. }
  222. public Dimension getImageSize() {
  223. return dim.getSize();
  224. }
  225. };
  226. paintMarksAsBitmap(painter, boundingBox);
  227. }
  228. private void paintMarksAsBitmap(Graphics2DImagePainter painter, Rectangle boundingBox)
  229. throws IFException {
  230. ImageInfo info = new ImageInfo(null, null);
  231. ImageSize size = new ImageSize();
  232. size.setSizeInMillipoints(boundingBox.width, boundingBox.height);
  233. info.setSize(size);
  234. ImageGraphics2D img = new ImageGraphics2D(info, painter);
  235. Map hints = new java.util.HashMap();
  236. if (isSpeedOptimized()) {
  237. //Gray text may not be painted in this case! We don't get dithering in Sun JREs.
  238. //But this approach is about twice as fast as the grayscale image.
  239. hints.put(ImageProcessingHints.BITMAP_TYPE_INTENT,
  240. ImageProcessingHints.BITMAP_TYPE_INTENT_MONO);
  241. } else {
  242. hints.put(ImageProcessingHints.BITMAP_TYPE_INTENT,
  243. ImageProcessingHints.BITMAP_TYPE_INTENT_GRAY);
  244. }
  245. hints.put(ImageHandlerUtil.CONVERSION_MODE, ImageHandlerUtil.CONVERSION_MODE_BITMAP);
  246. PCLRenderingContext context = (PCLRenderingContext)createRenderingContext();
  247. context.setSourceTransparencyEnabled(true);
  248. try {
  249. drawImage(img, boundingBox, context, true, hints);
  250. } catch (IOException ioe) {
  251. throw new IFException(
  252. "I/O error while painting marks using a bitmap", ioe);
  253. } catch (ImageException ie) {
  254. throw new IFException(
  255. "Error while painting marks using a bitmap", ie);
  256. }
  257. }
  258. /** {@inheritDoc} */
  259. public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp, String text)
  260. throws IFException {
  261. try {
  262. FontTriplet triplet = new FontTriplet(
  263. state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
  264. //TODO Ignored: state.getFontVariant()
  265. //TODO Opportunity for font caching if font state is more heavily used
  266. String fontKey = getFontKey(triplet);
  267. boolean pclFont = getPCLUtil().isAllTextAsBitmaps() ? false
  268. : HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
  269. if (pclFont) {
  270. drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
  271. } else {
  272. drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
  273. if (DEBUG) {
  274. state.setTextColor(Color.GRAY);
  275. HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
  276. drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
  277. }
  278. }
  279. } catch (IOException ioe) {
  280. throw new IFException("I/O error in drawText()", ioe);
  281. }
  282. }
  283. private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
  284. String text, FontTriplet triplet) throws IOException {
  285. Color textColor = state.getTextColor();
  286. if (textColor != null) {
  287. gen.setTransparencyMode(true, false);
  288. gen.selectGrayscale(textColor);
  289. }
  290. gen.setTransparencyMode(true, true);
  291. setCursorPos(x, y);
  292. float fontSize = state.getFontSize() / 1000f;
  293. Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());
  294. int l = text.length();
  295. int[] dx = IFUtil.convertDPToDX ( dp );
  296. int dxl = (dx != null ? dx.length : 0);
  297. StringBuffer sb = new StringBuffer(Math.max(16, l));
  298. if (dx != null && dxl > 0 && dx[0] != 0) {
  299. sb.append("\u001B&a+").append(gen.formatDouble2(dx[0] / 100.0)).append('H');
  300. }
  301. for (int i = 0; i < l; i++) {
  302. char orgChar = text.charAt(i);
  303. char ch;
  304. float glyphAdjust = 0;
  305. if (font.hasChar(orgChar)) {
  306. ch = font.mapChar(orgChar);
  307. } else {
  308. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  309. //Fixed width space are rendered as spaces so copy/paste works in a reader
  310. ch = font.mapChar(CharUtilities.SPACE);
  311. int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
  312. glyphAdjust = -(10 * spaceDiff / fontSize);
  313. } else {
  314. ch = font.mapChar(orgChar);
  315. }
  316. }
  317. sb.append(ch);
  318. if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
  319. glyphAdjust += wordSpacing;
  320. }
  321. glyphAdjust += letterSpacing;
  322. if (dx != null && i < dxl - 1) {
  323. glyphAdjust += dx[i + 1];
  324. }
  325. if (glyphAdjust != 0) {
  326. sb.append("\u001B&a+").append(gen.formatDouble2(glyphAdjust / 100.0)).append('H');
  327. }
  328. }
  329. gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
  330. }
  331. private static final double SAFETY_MARGIN_FACTOR = 0.05;
  332. private Rectangle getTextBoundingBox( // CSOK: ParameterNumber
  333. int x, int y,
  334. int letterSpacing, int wordSpacing, int[][] dp,
  335. String text,
  336. Font font, FontMetricsMapper metrics) {
  337. int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000;
  338. int descent = metrics.getDescender(font.getFontSize()) / 1000; //is negative
  339. int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize());
  340. Rectangle boundingRect = new Rectangle(
  341. x, y - maxAscent - safetyMargin,
  342. 0, maxAscent - descent + 2 * safetyMargin);
  343. int l = text.length();
  344. int[] dx = IFUtil.convertDPToDX ( dp );
  345. int dxl = (dx != null ? dx.length : 0);
  346. if (dx != null && dxl > 0 && dx[0] != 0) {
  347. boundingRect.setLocation(boundingRect.x - (int)Math.ceil(dx[0] / 10f), boundingRect.y);
  348. }
  349. float width = 0.0f;
  350. for (int i = 0; i < l; i++) {
  351. char orgChar = text.charAt(i);
  352. float glyphAdjust = 0;
  353. int cw = font.getCharWidth(orgChar);
  354. if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
  355. glyphAdjust += wordSpacing;
  356. }
  357. glyphAdjust += letterSpacing;
  358. if (dx != null && i < dxl - 1) {
  359. glyphAdjust += dx[i + 1];
  360. }
  361. width += cw + glyphAdjust;
  362. }
  363. int extraWidth = font.getFontSize() / 3;
  364. boundingRect.setSize(
  365. (int)Math.ceil(width) + extraWidth,
  366. boundingRect.height);
  367. return boundingRect;
  368. }
  369. private void drawTextAsBitmap(final int x, final int y,
  370. final int letterSpacing, final int wordSpacing, final int[][] dp,
  371. final String text, FontTriplet triplet) throws IFException {
  372. //Use Java2D to paint different fonts via bitmap
  373. final Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());
  374. //for cursive fonts, so the text isn't clipped
  375. final FontMetricsMapper mapper = (FontMetricsMapper) getFontInfo().getMetricsFor(
  376. font.getFontName());
  377. final int maxAscent = mapper.getMaxAscent(font.getFontSize()) / 1000;
  378. final int ascent = mapper.getAscender(font.getFontSize()) / 1000;
  379. final int descent = mapper.getDescender(font.getFontSize()) / 1000;
  380. int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize());
  381. final int baselineOffset = maxAscent + safetyMargin;
  382. final Rectangle boundingBox = getTextBoundingBox(x, y,
  383. letterSpacing, wordSpacing, dp, text, font, mapper);
  384. final Dimension dim = boundingBox.getSize();
  385. Graphics2DImagePainter painter = new Graphics2DImagePainter() {
  386. public void paint(Graphics2D g2d, Rectangle2D area) {
  387. if (DEBUG) {
  388. g2d.setBackground(Color.LIGHT_GRAY);
  389. g2d.clearRect(0, 0, (int)area.getWidth(), (int)area.getHeight());
  390. }
  391. g2d.translate(-x, -y + baselineOffset);
  392. if (DEBUG) {
  393. Rectangle rect = new Rectangle(x, y - maxAscent, 3000, maxAscent);
  394. g2d.draw(rect);
  395. rect = new Rectangle(x, y - ascent, 2000, ascent);
  396. g2d.draw(rect);
  397. rect = new Rectangle(x, y, 1000, -descent);
  398. g2d.draw(rect);
  399. }
  400. Java2DPainter painter = new Java2DPainter(g2d, getContext(), getFontInfo(), state);
  401. try {
  402. painter.drawText(x, y, letterSpacing, wordSpacing, dp, text);
  403. } catch (IFException e) {
  404. //This should never happen with the Java2DPainter
  405. throw new RuntimeException("Unexpected error while painting text", e);
  406. }
  407. }
  408. public Dimension getImageSize() {
  409. return dim.getSize();
  410. }
  411. };
  412. paintMarksAsBitmap(painter, boundingBox);
  413. }
  414. /** Saves the current graphics state on the stack. */
  415. private void saveGraphicsState() {
  416. graphicContextStack.push(graphicContext);
  417. graphicContext = (GraphicContext)graphicContext.clone();
  418. }
  419. /** Restores the last graphics state from the stack. */
  420. private void restoreGraphicsState() {
  421. graphicContext = graphicContextStack.pop();
  422. }
  423. private void concatenateTransformationMatrix(AffineTransform transform) throws IOException {
  424. if (!transform.isIdentity()) {
  425. graphicContext.transform(transform);
  426. changePrintDirection();
  427. }
  428. }
  429. private Point2D transformedPoint(int x, int y) {
  430. return PCLRenderingUtil.transformedPoint(x, y, graphicContext.getTransform(),
  431. currentPageDefinition, currentPrintDirection);
  432. }
  433. private void changePrintDirection() throws IOException {
  434. AffineTransform at = graphicContext.getTransform();
  435. int newDir;
  436. newDir = PCLRenderingUtil.determinePrintDirection(at);
  437. if (newDir != this.currentPrintDirection) {
  438. this.currentPrintDirection = newDir;
  439. gen.changePrintDirection(this.currentPrintDirection);
  440. }
  441. }
  442. /**
  443. * Sets the current cursor position. The coordinates are transformed to the absolute position
  444. * on the logical PCL page and then passed on to the PCLGenerator.
  445. * @param x the x coordinate (in millipoints)
  446. * @param y the y coordinate (in millipoints)
  447. */
  448. void setCursorPos(int x, int y) throws IOException {
  449. Point2D transPoint = transformedPoint(x, y);
  450. gen.setCursorPos(transPoint.getX(), transPoint.getY());
  451. }
  452. }