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.

PDFGraphicsPainter.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  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.pdf;
  19. import java.awt.Color;
  20. import java.awt.Point;
  21. import java.awt.Rectangle;
  22. import java.io.IOException;
  23. import org.apache.fop.fo.Constants;
  24. import org.apache.fop.render.intermediate.ArcToBezierCurveTransformer;
  25. import org.apache.fop.render.intermediate.BezierCurvePainter;
  26. import org.apache.fop.render.intermediate.BorderPainter;
  27. import org.apache.fop.render.intermediate.GraphicsPainter;
  28. import org.apache.fop.traits.RuleStyle;
  29. import org.apache.fop.util.ColorUtil;
  30. /**
  31. * PDF-specific implementation of the {@link GraphicsPainter}.
  32. */
  33. public class PDFGraphicsPainter implements GraphicsPainter, BezierCurvePainter {
  34. private final PDFContentGeneratorHelper generator;
  35. /** Used for drawing arcs since PS does not natively support drawing elliptic curves */
  36. private final ArcToBezierCurveTransformer arcToBezierCurveTransformer;
  37. public PDFGraphicsPainter(PDFContentGenerator generator) {
  38. this.generator = new PDFContentGeneratorHelper(generator);
  39. this.arcToBezierCurveTransformer = new ArcToBezierCurveTransformer(this);
  40. }
  41. /** {@inheritDoc} */
  42. public void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz,
  43. boolean startOrBefore, int style, Color col) {
  44. //TODO lose scale?
  45. drawBorderLine2(x1 / 1000f, y1 / 1000f, x2 / 1000f, y2 / 1000f,
  46. horz, startOrBefore, style, col);
  47. }
  48. /** {@inheritDoc} */
  49. private void drawBorderLine2(float x1, float y1, float x2, float y2, boolean horz,
  50. boolean startOrBefore, int style, Color col) {
  51. float w = x2 - x1;
  52. float h = y2 - y1;
  53. float colFactor;
  54. switch (style) {
  55. case Constants.EN_DASHED:
  56. generator.setColor(col);
  57. if (horz) {
  58. float dashedWidth = BorderPainter.dashWidthCalculator(w, h);
  59. if (dashedWidth != 0) {
  60. float ym = y1 + (h / 2);
  61. generator.setDashLine(dashedWidth, dashedWidth * BorderPainter.DASHED_BORDER_SPACE_RATIO)
  62. .setLineWidth(h)
  63. .strokeLine(x1, ym, x2, ym);
  64. }
  65. } else {
  66. float dashedWidth = BorderPainter.dashWidthCalculator(h, w);
  67. if (dashedWidth != 0) {
  68. float xm = x1 + (w / 2);
  69. generator.setDashLine(dashedWidth, dashedWidth * BorderPainter.DASHED_BORDER_SPACE_RATIO)
  70. .setLineWidth(w)
  71. .strokeLine(xm, y1, xm, y2);
  72. }
  73. }
  74. break;
  75. case Constants.EN_DOTTED:
  76. generator.setColor(col).setRoundCap();
  77. if (horz) {
  78. float unit = Math.abs(2 * h);
  79. int rep = (int) (w / unit);
  80. if (rep % 2 == 0) {
  81. rep++;
  82. }
  83. unit = w / rep;
  84. float ym = y1 + (h / 2);
  85. generator.setDashLine(0, unit)
  86. .setLineWidth(h)
  87. .strokeLine(x1, ym, x2, ym);
  88. } else {
  89. float unit = Math.abs(2 * w);
  90. int rep = (int) (h / unit);
  91. if (rep % 2 == 0) {
  92. rep++;
  93. }
  94. unit = h / rep;
  95. float xm = x1 + (w / 2);
  96. generator.setDashLine(0, unit)
  97. .setLineWidth(w)
  98. .strokeLine(xm, y1, xm, y2);
  99. }
  100. break;
  101. case Constants.EN_DOUBLE:
  102. generator.setColor(col)
  103. .setSolidLine();
  104. if (horz) {
  105. float h3 = h / 3;
  106. float ym1 = y1 + (h3 / 2);
  107. float ym2 = ym1 + h3 + h3;
  108. generator.setLineWidth(h3)
  109. .strokeLine(x1, ym1, x2, ym1)
  110. .strokeLine(x1, ym2, x2, ym2);
  111. } else {
  112. float w3 = w / 3;
  113. float xm1 = x1 + (w3 / 2);
  114. float xm2 = xm1 + w3 + w3;
  115. generator.setLineWidth(w3)
  116. .strokeLine(xm1, y1, xm1, y2)
  117. .strokeLine(xm2, y1, xm2, y2);
  118. }
  119. break;
  120. case Constants.EN_GROOVE:
  121. case Constants.EN_RIDGE:
  122. colFactor = (style == Constants.EN_GROOVE ? 0.4f : -0.4f);
  123. generator.setSolidLine();
  124. if (horz) {
  125. Color uppercol = ColorUtil.lightenColor(col, -colFactor);
  126. Color lowercol = ColorUtil.lightenColor(col, colFactor);
  127. float h3 = h / 3;
  128. float ym1 = y1 + (h3 / 2);
  129. generator.setLineWidth(h3)
  130. .setColor(uppercol)
  131. .strokeLine(x1, ym1, x2, ym1)
  132. .setColor(col)
  133. .strokeLine(x1, ym1 + h3, x2, ym1 + h3)
  134. .setColor(lowercol)
  135. .strokeLine(x1, ym1 + h3 + h3, x2, ym1 + h3 + h3);
  136. } else {
  137. Color leftcol = ColorUtil.lightenColor(col, -colFactor);
  138. Color rightcol = ColorUtil.lightenColor(col, colFactor);
  139. float w3 = w / 3;
  140. float xm1 = x1 + (w3 / 2);
  141. generator.setLineWidth(w3)
  142. .setColor(leftcol)
  143. .strokeLine(xm1, y1, xm1, y2)
  144. .setColor(col)
  145. .strokeLine(xm1 + w3, y1, xm1 + w3, y2)
  146. .setColor(rightcol)
  147. .strokeLine(xm1 + w3 + w3, y1, xm1 + w3 + w3, y2);
  148. }
  149. break;
  150. case Constants.EN_INSET:
  151. case Constants.EN_OUTSET:
  152. colFactor = (style == Constants.EN_OUTSET ? 0.4f : -0.4f);
  153. generator.setSolidLine();
  154. Color c = col;
  155. if (horz) {
  156. c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
  157. float ym1 = y1 + (h / 2);
  158. generator.setLineWidth(h)
  159. .setColor(c)
  160. .strokeLine(x1, ym1, x2, ym1);
  161. } else {
  162. c = ColorUtil.lightenColor(c, (startOrBefore ? 1 : -1) * colFactor);
  163. float xm1 = x1 + (w / 2);
  164. generator.setLineWidth(w)
  165. .setColor(c)
  166. .strokeLine(xm1, y1, xm1, y2);
  167. }
  168. break;
  169. case Constants.EN_HIDDEN:
  170. break;
  171. default:
  172. generator.setColor(col).setSolidLine();
  173. if (horz) {
  174. float ym = y1 + (h / 2);
  175. generator.setLineWidth(h)
  176. .strokeLine(x1, ym, x2, ym);
  177. } else {
  178. float xm = x1 + (w / 2);
  179. generator.setLineWidth(w)
  180. .strokeLine(xm, y1, xm, y2);
  181. }
  182. }
  183. }
  184. /** {@inheritDoc} */
  185. public void drawLine(Point start, Point end,
  186. int width, Color color, RuleStyle style) {
  187. if (start.y != end.y) {
  188. //TODO Support arbitrary lines if necessary
  189. throw new UnsupportedOperationException(
  190. "Can only deal with horizontal lines right now");
  191. }
  192. saveGraphicsState();
  193. int half = width / 2;
  194. int starty = start.y - half;
  195. Rectangle boundingRect = new Rectangle(start.x, start.y - half, end.x - start.x, width);
  196. switch (style.getEnumValue()) {
  197. case Constants.EN_SOLID:
  198. case Constants.EN_DASHED:
  199. case Constants.EN_DOUBLE:
  200. drawBorderLine(start.x, start.y - half, end.x, end.y + half,
  201. true, true, style.getEnumValue(), color);
  202. break;
  203. case Constants.EN_DOTTED:
  204. generator.clipRect(boundingRect)
  205. //This displaces the dots to the right by half a dot's width
  206. //TODO There's room for improvement here
  207. .transformCoordinatesLine(1, 0, 0 , 1, half, 0);
  208. drawBorderLine(start.x, start.y - half, end.x, end.y + half, true, true, style.getEnumValue(),
  209. color);
  210. break;
  211. case Constants.EN_GROOVE:
  212. case Constants.EN_RIDGE:
  213. generator.setFillColor(ColorUtil.lightenColor(color, 0.6f))
  214. .fillRect(start.x, start.y, end.x, starty + 2 * half)
  215. .setFillColor(color)
  216. .fillRidge(style, start.x, start.y, end.x, end.y, half);
  217. break;
  218. default:
  219. throw new UnsupportedOperationException("rule style not supported");
  220. }
  221. restoreGraphicsState();
  222. }
  223. private static String format(int coordinate) {
  224. //TODO lose scale?
  225. return format(coordinate / 1000f);
  226. }
  227. private static String format(float coordinate) {
  228. return PDFContentGenerator.format(coordinate);
  229. }
  230. /** {@inheritDoc} */
  231. public void moveTo(int x, int y) {
  232. generator.moveTo(x, y);
  233. }
  234. /** {@inheritDoc} */
  235. public void lineTo(int x, int y) {
  236. generator.lineTo(x, y);
  237. }
  238. /** {@inheritDoc} */
  239. public void arcTo(final double startAngle, final double endAngle, final int cx, final int cy,
  240. final int width, final int height) throws IOException {
  241. arcToBezierCurveTransformer.arcTo(startAngle, endAngle, cx, cy, width, height);
  242. }
  243. /** {@inheritDoc} */
  244. public void closePath() {
  245. generator.closePath();
  246. }
  247. /** {@inheritDoc} */
  248. public void clip() {
  249. generator.clip();
  250. }
  251. /** {@inheritDoc} */
  252. public void saveGraphicsState() {
  253. generator.saveGraphicsState();
  254. }
  255. /** {@inheritDoc} */
  256. public void restoreGraphicsState() {
  257. generator.restoreGraphicsState();
  258. }
  259. /** {@inheritDoc} */
  260. public void rotateCoordinates(double angle) throws IOException {
  261. float s = (float) Math.sin(angle);
  262. float c = (float) Math.cos(angle);
  263. generator.transformFloatCoordinates(c, s, -s, c, 0, 0);
  264. }
  265. /** {@inheritDoc} */
  266. public void translateCoordinates(int xTranslate, int yTranslate) throws IOException {
  267. generator.transformCoordinates(1000, 0, 0, 1000, xTranslate, yTranslate);
  268. }
  269. /** {@inheritDoc} */
  270. public void scaleCoordinates(float xScale, float yScale) throws IOException {
  271. generator.transformFloatCoordinates(xScale, 0, 0, yScale, 0, 0);
  272. }
  273. /** {@inheritDoc} */
  274. public void cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) {
  275. generator.cubicBezierTo(p1x, p1y, p2x, p2y, p3x, p3y);
  276. }
  277. // TODO consider enriching PDFContentGenerator with part of this API
  278. private static class PDFContentGeneratorHelper {
  279. private final PDFContentGenerator generator;
  280. public PDFContentGeneratorHelper(PDFContentGenerator generator) {
  281. this.generator = generator;
  282. }
  283. public PDFContentGeneratorHelper moveTo(int x, int y) {
  284. return add("m", format(x), format(y));
  285. }
  286. public PDFContentGeneratorHelper lineTo(int x, int y) {
  287. return add("l", format(x), format(y));
  288. }
  289. /** {@inheritDoc} */
  290. public PDFContentGeneratorHelper cubicBezierTo(int p1x, int p1y, int p2x, int p2y, int p3x, int p3y) {
  291. return add("c", format(p1x), format(p1y), format(p2x), format(p2y), format(p3x), format(p3y));
  292. }
  293. public PDFContentGeneratorHelper closePath() {
  294. return add("h");
  295. }
  296. public PDFContentGeneratorHelper clip() {
  297. return addLine("W\nn");
  298. }
  299. public PDFContentGeneratorHelper clipRect(Rectangle rectangle) {
  300. generator.clipRect(rectangle);
  301. return this;
  302. }
  303. public PDFContentGeneratorHelper saveGraphicsState() {
  304. return addLine("q");
  305. }
  306. public PDFContentGeneratorHelper restoreGraphicsState() {
  307. return addLine("Q");
  308. }
  309. public PDFContentGeneratorHelper setSolidLine() {
  310. generator.add("[] 0 d ");
  311. return this;
  312. }
  313. public PDFContentGeneratorHelper setRoundCap() {
  314. return add("J", "1");
  315. }
  316. public PDFContentGeneratorHelper strokeLine(float xStart, float yStart, float xEnd, float yEnd) {
  317. add("m", xStart, yStart);
  318. return addLine("l S", xEnd, yEnd);
  319. }
  320. public PDFContentGeneratorHelper fillRect(int xStart, int yStart, int xEnd, int yEnd) {
  321. String xS = format(xStart);
  322. String xE = format(xEnd);
  323. String yS = format(yStart);
  324. String yE = format(yEnd);
  325. return addLine("m", xS, yS)
  326. .addLine("l", xE, yS)
  327. .addLine("l", xE, yE)
  328. .addLine("l", xS, yE)
  329. .addLine("h")
  330. .addLine("f");
  331. }
  332. public PDFContentGeneratorHelper fillRidge(RuleStyle style, int xStart, int yStart, int xEnd,
  333. int yEnd, int half) {
  334. String xS = format(xStart);
  335. String xE = format(xEnd);
  336. String yS = format(yStart);
  337. if (style == RuleStyle.GROOVE) {
  338. addLine("m", xS, yS)
  339. .addLine("l", xE, yS)
  340. .addLine("l", xE, format(yStart + half))
  341. .addLine("l", format(xStart + half), format(yStart + half))
  342. .addLine("l", xS, format(yStart + 2 * half));
  343. } else {
  344. addLine("m", xE, yS)
  345. .addLine("l", xE, format(yStart + 2 * half))
  346. .addLine("l", xS, format(yStart + 2 * half))
  347. .addLine("l", xS, format(yStart + half))
  348. .addLine("l", format(xEnd - half), format(yStart + half));
  349. }
  350. return addLine("h").addLine("f");
  351. }
  352. public PDFContentGeneratorHelper setLineWidth(float width) {
  353. return addLine("w", width);
  354. }
  355. public PDFContentGeneratorHelper setDashLine(float first, float... rest) {
  356. StringBuilder sb = new StringBuilder();
  357. sb.append("[").append(format(first));
  358. for (float unit : rest) {
  359. sb.append(" ").append(format(unit));
  360. }
  361. sb.append("] 0 d ");
  362. generator.add(sb.toString());
  363. return this;
  364. }
  365. public PDFContentGeneratorHelper setColor(Color col) {
  366. generator.setColor(col, false);
  367. return this;
  368. }
  369. public PDFContentGeneratorHelper setFillColor(Color col) {
  370. generator.setColor(col, true);
  371. return this;
  372. }
  373. public PDFContentGeneratorHelper transformFloatCoordinates(float a, float b, float c, float d,
  374. float e, float f) {
  375. return add("cm", a, b, c, d, e, f);
  376. }
  377. public PDFContentGeneratorHelper transformCoordinates(int a, int b, int c, int d, int e, int f) {
  378. return add("cm", format(a), format(b), format(c), format(d), format(e), format(f));
  379. }
  380. public PDFContentGeneratorHelper transformCoordinatesLine(int a, int b, int c, int d, int e, int f) {
  381. return addLine("cm", format(a), format(b), format(c), format(d), format(e), format(f));
  382. }
  383. public PDFContentGeneratorHelper add(String op) {
  384. assert op.equals(op.trim());
  385. generator.add(op + " ");
  386. return this;
  387. }
  388. private PDFContentGeneratorHelper add(String op, String... args) {
  389. add(createArgs(args), op);
  390. return this;
  391. }
  392. public PDFContentGeneratorHelper addLine(String op) {
  393. assert op.equals(op.trim());
  394. generator.add(op + "\n");
  395. return this;
  396. }
  397. public PDFContentGeneratorHelper addLine(String op, String... args) {
  398. addLine(createArgs(args), op);
  399. return this;
  400. }
  401. private PDFContentGeneratorHelper add(String op, float... args) {
  402. add(createArgs(args), op);
  403. return this;
  404. }
  405. public PDFContentGeneratorHelper addLine(String op, float... args) {
  406. addLine(createArgs(args), op);
  407. return this;
  408. }
  409. private StringBuilder createArgs(float... args) {
  410. StringBuilder sb = new StringBuilder();
  411. for (float arg : args) {
  412. sb.append(format(arg)).append(" ");
  413. }
  414. return sb;
  415. }
  416. private StringBuilder createArgs(String... args) {
  417. StringBuilder sb = new StringBuilder();
  418. for (String arg : args) {
  419. sb.append(arg).append(" ");
  420. }
  421. return sb;
  422. }
  423. private void add(StringBuilder args, String op) {
  424. assert op.equals(op.trim());
  425. generator.add(args.append(op).append(" ").toString());
  426. }
  427. private void addLine(StringBuilder args, String op) {
  428. assert op.equals(op.trim());
  429. generator.add(args.append(op).append("\n").toString());
  430. }
  431. }
  432. }