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.

AFPPainter.java 46KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  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.afp;
  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.Area;
  27. import java.awt.geom.Ellipse2D;
  28. import java.awt.geom.GeneralPath;
  29. import java.awt.geom.Rectangle2D;
  30. import java.io.IOException;
  31. import java.net.URI;
  32. import java.net.URISyntaxException;
  33. import java.nio.charset.CharacterCodingException;
  34. import java.security.MessageDigest;
  35. import java.util.Map;
  36. import org.w3c.dom.Document;
  37. import org.apache.xmlgraphics.image.loader.Image;
  38. import org.apache.xmlgraphics.image.loader.ImageException;
  39. import org.apache.xmlgraphics.image.loader.ImageInfo;
  40. import org.apache.xmlgraphics.image.loader.ImageProcessingHints;
  41. import org.apache.xmlgraphics.image.loader.ImageSessionContext;
  42. import org.apache.xmlgraphics.image.loader.ImageSize;
  43. import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
  44. import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
  45. import org.apache.fop.afp.AFPBorderPainter;
  46. import org.apache.fop.afp.AFPEventProducer;
  47. import org.apache.fop.afp.AFPObjectAreaInfo;
  48. import org.apache.fop.afp.AFPPaintingState;
  49. import org.apache.fop.afp.AFPResourceInfo;
  50. import org.apache.fop.afp.AFPUnitConverter;
  51. import org.apache.fop.afp.AbstractAFPPainter;
  52. import org.apache.fop.afp.BorderPaintingInfo;
  53. import org.apache.fop.afp.DataStream;
  54. import org.apache.fop.afp.RectanglePaintingInfo;
  55. import org.apache.fop.afp.fonts.AFPFont;
  56. import org.apache.fop.afp.fonts.AFPFontAttributes;
  57. import org.apache.fop.afp.fonts.AFPPageFonts;
  58. import org.apache.fop.afp.fonts.CharacterSet;
  59. import org.apache.fop.afp.modca.AbstractPageObject;
  60. import org.apache.fop.afp.modca.PresentationTextObject;
  61. import org.apache.fop.afp.ptoca.PtocaBuilder;
  62. import org.apache.fop.afp.ptoca.PtocaProducer;
  63. import org.apache.fop.afp.util.AFPResourceAccessor;
  64. import org.apache.fop.fonts.Font;
  65. import org.apache.fop.fonts.FontTriplet;
  66. import org.apache.fop.fonts.FontType;
  67. import org.apache.fop.fonts.Typeface;
  68. import org.apache.fop.render.ImageHandlerUtil;
  69. import org.apache.fop.render.RenderingContext;
  70. import org.apache.fop.render.intermediate.AbstractIFPainter;
  71. import org.apache.fop.render.intermediate.BorderPainter;
  72. import org.apache.fop.render.intermediate.GraphicsPainter;
  73. import org.apache.fop.render.intermediate.IFException;
  74. import org.apache.fop.render.intermediate.IFState;
  75. import org.apache.fop.render.intermediate.IFUtil;
  76. import org.apache.fop.traits.BorderProps;
  77. import org.apache.fop.traits.RuleStyle;
  78. import org.apache.fop.util.CharUtilities;
  79. /**
  80. * IFPainter implementation that produces AFP (MO:DCA).
  81. */
  82. public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> {
  83. private static final int X = 0;
  84. private static final int Y = 1;
  85. private final GraphicsPainter graphicsPainter;
  86. /** the border painter */
  87. private final AFPBorderPainterAdapter borderPainter;
  88. /** the rectangle painter */
  89. private final AbstractAFPPainter rectanglePainter;
  90. /** unit converter */
  91. private final AFPUnitConverter unitConv;
  92. private final AFPEventProducer eventProducer;
  93. private Integer bytesAvailable;
  94. /**
  95. * Default constructor.
  96. * @param documentHandler the parent document handler
  97. */
  98. public AFPPainter(AFPDocumentHandler documentHandler) {
  99. super(documentHandler);
  100. this.state = IFState.create();
  101. this.graphicsPainter = new AFPGraphicsPainter(
  102. new AFPBorderPainter(getPaintingState(), getDataStream()));
  103. this.borderPainter = new AFPBorderPainterAdapter(graphicsPainter, this, documentHandler);
  104. this.rectanglePainter = documentHandler.createRectanglePainter();
  105. this.unitConv = getPaintingState().getUnitConverter();
  106. this.eventProducer = AFPEventProducer.Provider.get(getUserAgent().getEventBroadcaster());
  107. }
  108. private AFPPaintingState getPaintingState() {
  109. return getDocumentHandler().getPaintingState();
  110. }
  111. private DataStream getDataStream() {
  112. return getDocumentHandler().getDataStream();
  113. }
  114. @Override
  115. public String getFontKey(FontTriplet triplet) throws IFException {
  116. try {
  117. return super.getFontKey(triplet);
  118. } catch (IFException e) {
  119. eventProducer.invalidConfiguration(null, e);
  120. return super.getFontKey(FontTriplet.DEFAULT_FONT_TRIPLET);
  121. }
  122. }
  123. /** {@inheritDoc} */
  124. public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect)
  125. throws IFException {
  126. //AFP doesn't support clipping, so we treat viewport like a group
  127. //this is the same code as for startGroup()
  128. try {
  129. saveGraphicsState();
  130. concatenateTransformationMatrix(transform);
  131. } catch (IOException ioe) {
  132. throw new IFException("I/O error in startViewport()", ioe);
  133. }
  134. }
  135. /** {@inheritDoc} */
  136. public void endViewport() throws IFException {
  137. try {
  138. restoreGraphicsState();
  139. } catch (IOException ioe) {
  140. throw new IFException("I/O error in endViewport()", ioe);
  141. }
  142. }
  143. private void concatenateTransformationMatrix(AffineTransform at) {
  144. if (!at.isIdentity()) {
  145. getPaintingState().concatenate(at);
  146. }
  147. }
  148. /** {@inheritDoc} */
  149. public void startGroup(AffineTransform transform, String layer) throws IFException {
  150. try {
  151. saveGraphicsState();
  152. concatenateTransformationMatrix(transform);
  153. } catch (IOException ioe) {
  154. throw new IFException("I/O error in startGroup()", ioe);
  155. }
  156. }
  157. /** {@inheritDoc} */
  158. public void endGroup() throws IFException {
  159. try {
  160. restoreGraphicsState();
  161. } catch (IOException ioe) {
  162. throw new IFException("I/O error in endGroup()", ioe);
  163. }
  164. }
  165. /** {@inheritDoc} */
  166. @Override
  167. protected Map createDefaultImageProcessingHints(ImageSessionContext sessionContext) {
  168. Map hints = super.createDefaultImageProcessingHints(sessionContext);
  169. //AFP doesn't support alpha channels
  170. hints.put(ImageProcessingHints.TRANSPARENCY_INTENT,
  171. ImageProcessingHints.TRANSPARENCY_INTENT_IGNORE);
  172. hints.put("CMYK", getDocumentHandler().getPaintingState().isCMYKImagesSupported());
  173. return hints;
  174. }
  175. /** {@inheritDoc} */
  176. @Override
  177. protected RenderingContext createRenderingContext() {
  178. AFPRenderingContext renderingContext = new AFPRenderingContext(
  179. getUserAgent(),
  180. getDocumentHandler().getResourceManager(),
  181. getPaintingState(),
  182. getFontInfo(),
  183. getContext().getForeignAttributes());
  184. return renderingContext;
  185. }
  186. /** {@inheritDoc} */
  187. public void drawImage(String uri, Rectangle rect) throws IFException {
  188. PageSegmentDescriptor pageSegment = getDocumentHandler().getPageSegmentNameFor(uri);
  189. if (pageSegment != null) {
  190. float[] srcPts = {rect.x, rect.y};
  191. int[] coords = unitConv.mpts2units(srcPts);
  192. int width = Math.round(unitConv.mpt2units(rect.width));
  193. int height = Math.round(unitConv.mpt2units(rect.height));
  194. getDataStream().createIncludePageSegment(pageSegment.getName(),
  195. coords[X], coords[Y], width, height);
  196. //Do we need to embed an external page segment?
  197. if (pageSegment.getURI() != null) {
  198. AFPResourceAccessor accessor = new AFPResourceAccessor(
  199. getDocumentHandler().getUserAgent().getResourceResolver());
  200. try {
  201. URI resourceUri = new URI(pageSegment.getURI());
  202. getDocumentHandler().getResourceManager().createIncludedResourceFromExternal(
  203. pageSegment.getName(), resourceUri, accessor);
  204. } catch (URISyntaxException urie) {
  205. throw new IFException("Could not handle resource url"
  206. + pageSegment.getURI(), urie);
  207. } catch (IOException ioe) {
  208. throw new IFException("Could not handle resource" + pageSegment.getURI(), ioe);
  209. }
  210. }
  211. } else {
  212. drawImageUsingURI(uri, rect);
  213. }
  214. }
  215. /** {@inheritDoc} */
  216. protected void drawImage(Image image, Rectangle rect,
  217. RenderingContext context, boolean convert, Map additionalHints)
  218. throws IOException, ImageException {
  219. AFPRenderingContext afpContext = (AFPRenderingContext) context;
  220. AFPResourceInfo resourceInfo = AFPImageHandler.createResourceInformation(
  221. image.getInfo().getOriginalURI(),
  222. afpContext.getForeignAttributes());
  223. //Check if the image is cached before processing it again
  224. if (afpContext.getResourceManager().isObjectCached(resourceInfo)) {
  225. AFPObjectAreaInfo areaInfo = AFPImageHandler.createObjectAreaInfo(
  226. afpContext.getPaintingState(), rect);
  227. afpContext.getResourceManager().includeCachedObject(resourceInfo, areaInfo);
  228. } else {
  229. super.drawImage(image, rect, context, convert, additionalHints);
  230. }
  231. }
  232. /** {@inheritDoc} */
  233. public void drawImage(Document doc, Rectangle rect) throws IFException {
  234. drawImageUsingDocument(doc, rect);
  235. }
  236. /** {@inheritDoc} */
  237. public void clipRect(Rectangle rect) throws IFException {
  238. //Not supported!
  239. }
  240. private float toPoint(int mpt) {
  241. return mpt / 1000f;
  242. }
  243. /** {@inheritDoc} */
  244. public void fillRect(Rectangle rect, Paint fill) throws IFException {
  245. if (fill == null) {
  246. return;
  247. }
  248. if (rect.width != 0 && rect.height != 0) {
  249. if (fill instanceof Color) {
  250. getPaintingState().setColor((Color) fill);
  251. } else {
  252. throw new UnsupportedOperationException("Non-Color paints NYI");
  253. }
  254. RectanglePaintingInfo rectanglePaintInfo = new RectanglePaintingInfo(
  255. toPoint(rect.x), toPoint(rect.y), toPoint(rect.width), toPoint(rect.height));
  256. try {
  257. rectanglePainter.paint(rectanglePaintInfo);
  258. } catch (IOException ioe) {
  259. throw new IFException("IO error while painting rectangle", ioe);
  260. }
  261. }
  262. }
  263. @Override
  264. public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom,
  265. BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException {
  266. if (top != null || bottom != null || left != null || right != null) {
  267. this.borderPainter.drawBorders(rect, top, bottom, left, right, innerBackgroundColor);
  268. }
  269. }
  270. private static final class AFPGraphicsPainter implements GraphicsPainter {
  271. private final AFPBorderPainter graphicsPainter;
  272. private AFPGraphicsPainter(AFPBorderPainter delegate) {
  273. this.graphicsPainter = delegate;
  274. }
  275. public void drawBorderLine(int x1, int y1, int x2, int y2,
  276. boolean horz, boolean startOrBefore, int style, Color color)
  277. throws IOException {
  278. BorderPaintingInfo borderPaintInfo = new BorderPaintingInfo(
  279. toPoints(x1), toPoints(y1), toPoints(x2), toPoints(y2),
  280. horz, style, color);
  281. graphicsPainter.paint(borderPaintInfo);
  282. }
  283. private float toPoints(int mpt) {
  284. return mpt / 1000f;
  285. }
  286. public void drawLine(Point start, Point end, int width,
  287. Color color, RuleStyle style) throws IOException {
  288. if (start.y != end.y) {
  289. //TODO Support arbitrary lines if necessary
  290. throw new UnsupportedOperationException("Can only deal with horizontal lines right now");
  291. }
  292. //Simply delegates to drawBorderLine() as AFP line painting is not very sophisticated.
  293. int halfWidth = width / 2;
  294. drawBorderLine(start.x, start.y - halfWidth, end.x, start.y + halfWidth,
  295. true, true, style.getEnumValue(), color);
  296. }
  297. public void moveTo(int x, int y) throws IOException {
  298. }
  299. public void lineTo(int x, int y) throws IOException {
  300. }
  301. public void arcTo(double startAngle, double endAngle, int cx, int cy,
  302. int width, int height) throws IOException {
  303. }
  304. public void rotateCoordinates(double angle) throws IOException {
  305. throw new UnsupportedOperationException("Cannot handle coordinate rotation");
  306. }
  307. public void translateCoordinates(int xTranslate, int yTranslate) throws IOException {
  308. throw new UnsupportedOperationException("Cannot handle coordinate translation");
  309. }
  310. public void scaleCoordinates(float xScale, float yScale) throws IOException {
  311. throw new UnsupportedOperationException("Cannot handle coordinate scaling");
  312. }
  313. public void closePath() throws IOException {
  314. }
  315. public void clip() throws IOException {
  316. }
  317. public void saveGraphicsState() throws IOException {
  318. }
  319. public void restoreGraphicsState() throws IOException {
  320. }
  321. }
  322. //TODO Try to resolve the name-clash between the AFPBorderPainter in the afp package
  323. //and this one. Not done for now to avoid a lot of re-implementation and code duplication.
  324. private static class AFPBorderPainterAdapter extends BorderPainter {
  325. private final class BorderImagePainter implements Graphics2DImagePainter {
  326. private final double cornerCorrectionFactor;
  327. private final Rectangle borderRect;
  328. private final BorderProps bpsStart;
  329. private final BorderProps bpsEnd;
  330. private final BorderProps bpsBefore;
  331. private final BorderProps bpsAfter;
  332. private final boolean[] roundCorner;
  333. private final Color innerBackgroundColor;
  334. /* TODO represent border related parameters in a class */
  335. private BorderImagePainter(double cornerCorrectionFactor, Rectangle borderRect,
  336. BorderProps bpsStart, BorderProps bpsEnd,
  337. BorderProps bpsBefore, BorderProps bpsAfter,
  338. boolean[] roundCorner, Color innerBackgroundColor) {
  339. this.cornerCorrectionFactor = cornerCorrectionFactor;
  340. this.borderRect = borderRect;
  341. this.bpsStart = bpsStart;
  342. this.bpsBefore = bpsBefore;
  343. this.roundCorner = roundCorner;
  344. this.bpsEnd = bpsEnd;
  345. this.bpsAfter = bpsAfter;
  346. this.innerBackgroundColor = innerBackgroundColor;
  347. }
  348. public void paint(Graphics2D g2d, Rectangle2D area) {
  349. //background
  350. Area background = new Area(area);
  351. Area cornerRegion = new Area();
  352. Area[] cornerBorder = new Area[]{new Area(), new Area(), new Area(), new Area()};
  353. Area[] clip = new Area[4];
  354. if (roundCorner[TOP_LEFT]) {
  355. AffineTransform transform = new AffineTransform();
  356. int beforeRadius = (int)(cornerCorrectionFactor * bpsBefore.getRadiusStart());
  357. int startRadius = (int)(cornerCorrectionFactor * bpsStart.getRadiusStart());
  358. int beforeWidth = bpsBefore.width;
  359. int startWidth = bpsStart.width;
  360. int corner = TOP_LEFT;
  361. background.subtract(makeCornerClip(beforeRadius, startRadius,
  362. transform));
  363. clip[TOP_LEFT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius));
  364. clip[TOP_LEFT].transform(transform);
  365. cornerRegion.add(clip[TOP_LEFT]);
  366. cornerBorder[TOP].add(makeCornerBorderBPD(beforeRadius,
  367. startRadius, beforeWidth, startWidth, transform));
  368. cornerBorder[LEFT].add(makeCornerBorderIPD(beforeRadius,
  369. startRadius, beforeWidth, startWidth, transform));
  370. }
  371. if (roundCorner[TOP_RIGHT]) {
  372. AffineTransform transform
  373. = new AffineTransform(-1, 0, 0, 1, borderRect.width, 0);
  374. int beforeRadius = (int)(cornerCorrectionFactor * bpsBefore.getRadiusEnd());
  375. int startRadius = (int)(cornerCorrectionFactor * bpsEnd.getRadiusStart());
  376. int beforeWidth = bpsBefore.width;
  377. int startWidth = bpsEnd.width;
  378. int corner = TOP_RIGHT;
  379. background.subtract(makeCornerClip(beforeRadius, startRadius,
  380. transform));
  381. clip[TOP_RIGHT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius));
  382. clip[TOP_RIGHT].transform(transform);
  383. cornerRegion.add(clip[TOP_RIGHT]);
  384. cornerBorder[TOP].add(makeCornerBorderBPD(beforeRadius,
  385. startRadius, beforeWidth, startWidth, transform));
  386. cornerBorder[RIGHT].add(makeCornerBorderIPD(beforeRadius,
  387. startRadius, beforeWidth, startWidth, transform));
  388. }
  389. if (roundCorner[BOTTOM_RIGHT]) {
  390. AffineTransform transform = new AffineTransform(-1, 0, 0, -1,
  391. borderRect.width, borderRect.height);
  392. int beforeRadius = (int)(cornerCorrectionFactor * bpsAfter.getRadiusEnd());
  393. int startRadius = (int)(cornerCorrectionFactor * bpsEnd.getRadiusEnd());
  394. int beforeWidth = bpsAfter.width;
  395. int startWidth = bpsEnd.width;
  396. int corner = BOTTOM_RIGHT;
  397. background.subtract(makeCornerClip(beforeRadius, startRadius,
  398. transform));
  399. clip[BOTTOM_RIGHT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius));
  400. clip[BOTTOM_RIGHT].transform(transform);
  401. cornerRegion.add(clip[BOTTOM_RIGHT]);
  402. cornerBorder[BOTTOM].add(makeCornerBorderBPD(beforeRadius,
  403. startRadius, beforeWidth, startWidth, transform));
  404. cornerBorder[RIGHT].add(makeCornerBorderIPD(beforeRadius,
  405. startRadius, beforeWidth, startWidth, transform));
  406. }
  407. if (roundCorner[BOTTOM_LEFT]) {
  408. AffineTransform transform
  409. = new AffineTransform(1, 0, 0, -1, 0, borderRect.height);
  410. int beforeRadius = (int)(cornerCorrectionFactor * bpsAfter.getRadiusStart());
  411. int startRadius = (int)(cornerCorrectionFactor * bpsStart.getRadiusEnd());
  412. int beforeWidth = bpsAfter.width;
  413. int startWidth = bpsStart.width;
  414. int corner = BOTTOM_LEFT;
  415. background.subtract(makeCornerClip(beforeRadius, startRadius,
  416. transform));
  417. clip[BOTTOM_LEFT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius));
  418. clip[BOTTOM_LEFT].transform(transform);
  419. cornerRegion.add(clip[BOTTOM_LEFT]);
  420. cornerBorder[BOTTOM].add(makeCornerBorderBPD(beforeRadius,
  421. startRadius, beforeWidth, startWidth, transform));
  422. cornerBorder[LEFT].add(makeCornerBorderIPD(beforeRadius,
  423. startRadius, beforeWidth, startWidth, transform));
  424. }
  425. g2d.setColor(innerBackgroundColor);
  426. g2d.fill(background);
  427. //paint the borders
  428. //TODO refactor to repeating code into method
  429. if (bpsBefore != null && bpsBefore.width > 0) {
  430. GeneralPath borderPath = new GeneralPath();
  431. borderPath.moveTo(0, 0);
  432. borderPath.lineTo(borderRect.width, 0);
  433. borderPath.lineTo(
  434. borderRect.width - (bpsEnd == null ? 0 : bpsEnd.width),
  435. bpsBefore.width);
  436. borderPath.lineTo(bpsStart == null ? 0 : bpsStart.width, bpsBefore.width);
  437. Area border = new Area(borderPath);
  438. if (clip[TOP_LEFT] != null) {
  439. border.subtract(clip[TOP_LEFT]);
  440. }
  441. if (clip[TOP_RIGHT] != null) {
  442. border.subtract(clip[TOP_RIGHT]);
  443. }
  444. g2d.setColor(bpsBefore.color);
  445. g2d.fill(border);
  446. g2d.fill(cornerBorder[TOP]);
  447. }
  448. if (bpsEnd != null && bpsEnd.width > 0) {
  449. GeneralPath borderPath = new GeneralPath();
  450. borderPath.moveTo(borderRect.width, 0);
  451. borderPath.lineTo(borderRect.width, borderRect.height);
  452. borderPath.lineTo(
  453. borderRect.width - bpsEnd.width,
  454. borderRect.height - (bpsAfter == null ? 0 : bpsAfter.width));
  455. borderPath.lineTo(
  456. borderRect.width - bpsEnd.width,
  457. bpsBefore == null ? 0 : bpsBefore.width);
  458. Area border = new Area(borderPath);
  459. if (clip[BOTTOM_RIGHT] != null) {
  460. border.subtract(clip[BOTTOM_RIGHT]);
  461. }
  462. if (clip[TOP_RIGHT] != null) {
  463. border.subtract(clip[TOP_RIGHT]);
  464. }
  465. g2d.setColor(bpsEnd.color);
  466. g2d.fill(border);
  467. g2d.fill(cornerBorder[RIGHT]);
  468. }
  469. if (bpsAfter != null && bpsAfter.width > 0) {
  470. GeneralPath borderPath = new GeneralPath();
  471. borderPath.moveTo(0, borderRect.height);
  472. borderPath.lineTo(borderRect.width, borderRect.height);
  473. borderPath.lineTo(
  474. borderRect.width - (bpsEnd == null ? 0 : bpsEnd.width),
  475. borderRect.height - bpsAfter.width);
  476. borderPath.lineTo(bpsStart == null ? 0 : bpsStart.width,
  477. borderRect.height - bpsAfter.width);
  478. Area border = new Area(borderPath);
  479. if (clip[BOTTOM_LEFT] != null) {
  480. border.subtract(clip[BOTTOM_LEFT]);
  481. }
  482. if (clip[BOTTOM_RIGHT] != null) {
  483. border.subtract(clip[BOTTOM_RIGHT]);
  484. }
  485. g2d.setColor(bpsAfter.color);
  486. g2d.fill(border);
  487. g2d.fill(cornerBorder[BOTTOM]);
  488. }
  489. if (bpsStart != null && bpsStart.width > 0) {
  490. GeneralPath borderPath = new GeneralPath();
  491. borderPath.moveTo(bpsStart.width,
  492. bpsBefore == null ? 0 : bpsBefore.width);
  493. borderPath.lineTo(bpsStart.width,
  494. borderRect.height - (bpsAfter == null ? 0 : bpsAfter.width));
  495. borderPath.lineTo(0, borderRect.height);
  496. borderPath.lineTo(0, 0);
  497. Area border = new Area(borderPath);
  498. if (clip[BOTTOM_LEFT] != null) {
  499. border.subtract(clip[BOTTOM_LEFT]);
  500. }
  501. if (clip[TOP_LEFT] != null) {
  502. border.subtract(clip[TOP_LEFT]);
  503. }
  504. g2d.setColor(bpsStart.color);
  505. g2d.fill(border);
  506. g2d.fill(cornerBorder[LEFT]);
  507. }
  508. }
  509. public Dimension getImageSize() {
  510. return borderRect.getSize();
  511. }
  512. }
  513. private final AFPPainter painter;
  514. private final AFPDocumentHandler documentHandler;
  515. public AFPBorderPainterAdapter(GraphicsPainter graphicsPainter, AFPPainter painter,
  516. AFPDocumentHandler documentHandler) {
  517. super(graphicsPainter);
  518. this.painter = painter;
  519. this.documentHandler = documentHandler;
  520. }
  521. public void drawBorders(final Rectangle borderRect,
  522. final BorderProps bpsBefore, final BorderProps bpsAfter,
  523. final BorderProps bpsStart, final BorderProps bpsEnd, Color innerBackgroundColor)
  524. throws IFException {
  525. drawRoundedCorners(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd, innerBackgroundColor);
  526. }
  527. private boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter,
  528. BorderProps bpsStart, BorderProps bpsEnd) {
  529. return !hasRoundedCorners(bpsBefore, bpsAfter, bpsStart, bpsEnd);
  530. }
  531. private boolean hasRoundedCorners(final BorderProps bpsBefore, final BorderProps bpsAfter,
  532. final BorderProps bpsStart, final BorderProps bpsEnd) {
  533. return ((bpsStart == null ? false : bpsStart.getRadiusStart() > 0)
  534. && (bpsBefore == null ? false : bpsBefore.getRadiusStart() > 0))
  535. || ((bpsBefore == null ? false : bpsBefore.getRadiusEnd() > 0)
  536. && (bpsEnd == null ? false : bpsEnd.getRadiusStart() > 0))
  537. || ((bpsEnd == null ? false : bpsEnd.getRadiusEnd() > 0)
  538. && (bpsAfter == null ? false : bpsAfter.getRadiusEnd() > 0))
  539. || ((bpsAfter == null ? false : bpsAfter.getRadiusStart() > 0)
  540. && (bpsStart == null ? false : bpsStart.getRadiusEnd() > 0));
  541. }
  542. private void drawRoundedCorners(final Rectangle borderRect,
  543. final BorderProps bpsBefore, final BorderProps bpsAfter,
  544. final BorderProps bpsStart, final BorderProps bpsEnd,
  545. final Color innerBackgroundColor) throws IFException {
  546. final double cornerCorrectionFactor = calculateCornerCorrectionFactor(borderRect.width,
  547. borderRect.height, bpsBefore, bpsAfter, bpsStart, bpsEnd);
  548. final boolean[] roundCorner = new boolean[]{
  549. bpsBefore != null && bpsStart != null
  550. && bpsBefore.getRadiusStart() > 0
  551. && bpsStart.getRadiusStart() > 0
  552. && isNotCollapseOuter(bpsBefore)
  553. && isNotCollapseOuter(bpsStart),
  554. bpsEnd != null && bpsBefore != null
  555. && bpsEnd.getRadiusStart() > 0
  556. && bpsBefore.getRadiusEnd() > 0
  557. && isNotCollapseOuter(bpsEnd)
  558. && isNotCollapseOuter(bpsBefore),
  559. bpsEnd != null && bpsAfter != null
  560. && bpsEnd.getRadiusEnd() > 0
  561. && bpsAfter.getRadiusEnd() > 0
  562. && isNotCollapseOuter(bpsEnd)
  563. && isNotCollapseOuter(bpsAfter),
  564. bpsStart != null && bpsAfter != null
  565. && bpsStart.getRadiusEnd() > 0
  566. && bpsAfter.getRadiusStart() > 0
  567. && isNotCollapseOuter(bpsStart)
  568. && isNotCollapseOuter(bpsAfter)
  569. };
  570. if (!roundCorner[TOP_LEFT] && !roundCorner[TOP_RIGHT]
  571. && !roundCorner[BOTTOM_RIGHT] && !roundCorner[BOTTOM_LEFT]) {
  572. try {
  573. drawRectangularBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd);
  574. } catch (IOException ioe) {
  575. throw new IFException("IO error drawing borders", ioe);
  576. }
  577. return;
  578. }
  579. String areaKey = makeKey(borderRect,
  580. bpsBefore, bpsEnd, bpsAfter,
  581. bpsStart, innerBackgroundColor);
  582. Graphics2DImagePainter painter = null;
  583. String name = documentHandler.getCachedRoundedCorner(areaKey);
  584. if (name == null) {
  585. name = documentHandler.cacheRoundedCorner(areaKey);
  586. painter = new BorderImagePainter(cornerCorrectionFactor, borderRect,
  587. bpsStart, bpsEnd, bpsBefore, bpsAfter,
  588. roundCorner, innerBackgroundColor);
  589. }
  590. paintCornersAsBitmap(painter, borderRect, name);
  591. }
  592. private boolean isNotCollapseOuter(BorderProps bp) {
  593. return !bp.isCollapseOuter();
  594. }
  595. private Area makeCornerClip(final int beforeRadius, final int startRadius,
  596. final AffineTransform transform) {
  597. Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius);
  598. Area clip = new Area(clipR);
  599. Ellipse2D.Double e = new Ellipse2D.Double();
  600. e.x = 0;
  601. e.y = 0;
  602. e.width = 2 * startRadius;
  603. e.height = 2 * beforeRadius;
  604. clip.subtract(new Area(e));
  605. clip.transform(transform);
  606. return clip;
  607. }
  608. private Area makeCornerBorderBPD(final int beforeRadius, final int startRadius,
  609. final int beforeWidth, final int startWidth, final AffineTransform transform) {
  610. Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius);
  611. Ellipse2D.Double e = new Ellipse2D.Double();
  612. e.x = 0;
  613. e.y = 0;
  614. e.width = 2 * startRadius;
  615. e.height = 2 * beforeRadius;
  616. Ellipse2D.Double i = new Ellipse2D.Double();
  617. i.x = startWidth;
  618. i.y = beforeWidth;
  619. i.width = 2 * (startRadius - startWidth);
  620. i.height = 2 * (beforeRadius - beforeWidth);
  621. Area clip = new Area(e);
  622. clip.subtract(new Area(i));
  623. clip.intersect(new Area(clipR));
  624. GeneralPath cut = new GeneralPath();
  625. cut.moveTo(0, 0);
  626. cut.lineTo(startRadius, ((float) startRadius * beforeWidth) / startWidth);
  627. cut.lineTo(startRadius, 0);
  628. clip.intersect(new Area(cut));
  629. clip.transform(transform);
  630. return clip;
  631. }
  632. private Area makeCornerBorderIPD(final int beforeRadius, final int startRadius,
  633. final int beforeWidth, final int startWidth, final AffineTransform transform) {
  634. Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius);
  635. Ellipse2D.Double e = new Ellipse2D.Double();
  636. e.x = 0;
  637. e.y = 0;
  638. e.width = 2 * startRadius;
  639. e.height = 2 * beforeRadius;
  640. Ellipse2D.Double i = new Ellipse2D.Double();
  641. i.x = startWidth;
  642. i.y = beforeWidth;
  643. i.width = 2 * (startRadius - startWidth);
  644. i.height = 2 * (beforeRadius - beforeWidth);
  645. Area clip = new Area(e);
  646. clip.subtract(new Area(i));
  647. clip.intersect(new Area(clipR));
  648. GeneralPath cut = new GeneralPath();
  649. cut.moveTo(0, 0);
  650. cut.lineTo(startRadius, ((float) startRadius * beforeWidth) / startWidth);
  651. cut.lineTo(startRadius, 0);
  652. clip.subtract(new Area(cut));
  653. clip.transform(transform);
  654. return clip;
  655. }
  656. private String makeKey(Rectangle area, BorderProps beforeProps,
  657. BorderProps endProps, BorderProps afterProps, BorderProps startProps,
  658. Color innerBackgroundColor) {
  659. return hash(new StringBuffer()
  660. .append(area.width)
  661. .append(":")
  662. .append(area.height)
  663. .append(":")
  664. .append(beforeProps)
  665. .append(":")
  666. .append(endProps)
  667. .append(":")
  668. .append(afterProps)
  669. .append(":")
  670. .append(startProps)
  671. .append(":")
  672. .append(innerBackgroundColor)
  673. .toString());
  674. }
  675. private String hash(String text) {
  676. MessageDigest md;
  677. try {
  678. md = MessageDigest.getInstance("MD5");
  679. } catch (Exception e) {
  680. throw new RuntimeException("Internal error", e);
  681. }
  682. byte[] result = md.digest(text.getBytes());
  683. StringBuffer sb = new StringBuffer();
  684. char[] digits = {'0', '1', '2', '3', '4', '5', '6',
  685. '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  686. for (int idx = 0; idx < 6; ++idx) {
  687. byte b = result[idx];
  688. sb.append(digits[(b & 0xf0) >> 4]);
  689. sb.append(digits[b & 0x0f]);
  690. }
  691. return sb.toString();
  692. }
  693. private void paintCornersAsBitmap(Graphics2DImagePainter painter,
  694. Rectangle boundingBox, String name) throws IFException {
  695. //TODO parameters ok?
  696. ImageInfo info = new ImageInfo(name, null);
  697. ImageSize size = new ImageSize();
  698. size.setSizeInMillipoints(boundingBox.width, boundingBox.height);
  699. //Use the foreign attributes map to set image handling hints
  700. Map map = new java.util.HashMap(2);
  701. map.put(AFPForeignAttributeReader.RESOURCE_NAME, name);
  702. map.put(AFPForeignAttributeReader.RESOURCE_LEVEL, "print-file");
  703. AFPRenderingContext context = (AFPRenderingContext)
  704. this.painter.createRenderingContext(/*map*/);
  705. size.setResolution(context.getPaintingState().getResolution());
  706. size.calcPixelsFromSize();
  707. info.setSize(size);
  708. ImageGraphics2D img = new ImageGraphics2D(info, painter);
  709. Map hints = new java.util.HashMap();
  710. hints.put(ImageHandlerUtil.CONVERSION_MODE, ImageHandlerUtil.CONVERSION_MODE_BITMAP);
  711. hints.put("TARGET_RESOLUTION",
  712. context.getPaintingState().getResolution());
  713. try {
  714. this.painter.drawImage(img, boundingBox, context, true, hints);
  715. } catch (IOException ioe) {
  716. throw new IFException(
  717. "I/O error while painting corner using a bitmap", ioe);
  718. } catch (ImageException ie) {
  719. throw new IFException(
  720. "Image error while painting corner using a bitmap", ie);
  721. }
  722. }
  723. protected void arcTo(double startAngle, double endAngle, int cx, int cy, int width,
  724. int height) throws IOException {
  725. throw new UnsupportedOperationException("Can only deal with horizontal lines right now");
  726. }
  727. }
  728. /** {@inheritDoc} */
  729. @Override
  730. public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
  731. throws IFException {
  732. try {
  733. this.graphicsPainter.drawLine(start, end, width, color, style);
  734. } catch (IOException ioe) {
  735. throw new IFException("I/O error in drawLine()", ioe);
  736. }
  737. }
  738. /** {@inheritDoc} */
  739. public void drawText(int x, int y, final int letterSpacing, final int wordSpacing,
  740. final int[][] dp, final String text) throws IFException {
  741. final int fontSize = this.state.getFontSize();
  742. getPaintingState().setFontSize(fontSize);
  743. FontTriplet triplet = new FontTriplet(
  744. state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
  745. //TODO Ignored: state.getFontVariant()
  746. String fontKey = getFontKey(triplet);
  747. // register font as necessary
  748. Map<String, Typeface> fontMetricMap = getFontInfo().getFonts();
  749. final AFPFont afpFont = (AFPFont) fontMetricMap.get(fontKey);
  750. final Font font = getFontInfo().getFontInstance(triplet, fontSize);
  751. AFPPageFonts pageFonts = getPaintingState().getPageFonts();
  752. AFPFontAttributes fontAttributes = pageFonts.registerFont(fontKey, afpFont, fontSize);
  753. final int fontReference = fontAttributes.getFontReference();
  754. final int[] coords = unitConv.mpts2units(new float[] {x, y});
  755. final CharacterSet charSet = afpFont.getCharacterSet(fontSize);
  756. if (afpFont.isEmbeddable()) {
  757. try {
  758. getDocumentHandler().getResourceManager().embedFont(afpFont, charSet);
  759. } catch (IOException ioe) {
  760. throw new IFException("Error while embedding font resources", ioe);
  761. }
  762. }
  763. AbstractPageObject page = getDataStream().getCurrentPage();
  764. try {
  765. int size = charSet.encodeChars(text).getLength();
  766. if (bytesAvailable != null && bytesAvailable < size) {
  767. page.endPresentationObject();
  768. }
  769. } catch (CharacterCodingException e) {
  770. throw new IFException(e.getMessage(), e);
  771. }
  772. final PresentationTextObject pto = page.getPresentationTextObject();
  773. try {
  774. pto.createControlSequences(new PtocaProducer() {
  775. public void produce(PtocaBuilder builder) throws IOException {
  776. Point p = getPaintingState().getPoint(coords[X], coords[Y]);
  777. builder.setTextOrientation(getPaintingState().getRotation());
  778. builder.absoluteMoveBaseline(p.y);
  779. builder.absoluteMoveInline(p.x);
  780. builder.setExtendedTextColor(state.getTextColor());
  781. builder.setCodedFont((byte) fontReference);
  782. int l = text.length();
  783. int[] dx = IFUtil.convertDPToDX(dp);
  784. int dxl = (dx != null ? dx.length : 0);
  785. StringBuffer sb = new StringBuffer();
  786. if (dxl > 0 && dx[0] != 0) {
  787. int dxu = Math.round(unitConv.mpt2units(dx[0]));
  788. builder.relativeMoveInline(-dxu);
  789. }
  790. //Following are two variants for glyph placement.
  791. //SVI does not seem to be implemented in the same way everywhere, so
  792. //a fallback alternative is preserved here.
  793. final boolean usePTOCAWordSpacing = true;
  794. if (usePTOCAWordSpacing) {
  795. int interCharacterAdjustment = 0;
  796. if (letterSpacing != 0) {
  797. interCharacterAdjustment = Math.round(unitConv.mpt2units(
  798. letterSpacing));
  799. }
  800. builder.setInterCharacterAdjustment(interCharacterAdjustment);
  801. int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
  802. int fixedSpaceCharacterIncrement = Math.round(unitConv.mpt2units(
  803. spaceWidth + letterSpacing));
  804. int varSpaceCharacterIncrement = fixedSpaceCharacterIncrement;
  805. if (wordSpacing != 0) {
  806. varSpaceCharacterIncrement = Math.round(unitConv.mpt2units(
  807. spaceWidth + wordSpacing + letterSpacing));
  808. }
  809. builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement);
  810. boolean fixedSpaceMode = false;
  811. int ttPos = p.x;
  812. for (int i = 0; i < l; i++) {
  813. char orgChar = text.charAt(i);
  814. float glyphAdjust = 0;
  815. if (afpFont.getFontType() == FontType.TRUETYPE) {
  816. flushText(builder, sb, charSet);
  817. fixedSpaceMode = true;
  818. int charWidth = font.getCharWidth(orgChar);
  819. sb.append(orgChar);
  820. glyphAdjust += charWidth;
  821. } else if (CharUtilities.isFixedWidthSpace(orgChar)) {
  822. flushText(builder, sb, charSet);
  823. builder.setVariableSpaceCharacterIncrement(
  824. fixedSpaceCharacterIncrement);
  825. fixedSpaceMode = true;
  826. sb.append(CharUtilities.SPACE);
  827. int charWidth = font.getCharWidth(orgChar);
  828. glyphAdjust += (charWidth - spaceWidth);
  829. } else {
  830. if (fixedSpaceMode) {
  831. flushText(builder, sb, charSet);
  832. builder.setVariableSpaceCharacterIncrement(
  833. varSpaceCharacterIncrement);
  834. fixedSpaceMode = false;
  835. }
  836. char ch;
  837. if (orgChar == CharUtilities.NBSPACE) {
  838. ch = ' '; //converted to normal space to allow word spacing
  839. } else {
  840. ch = orgChar;
  841. }
  842. sb.append(ch);
  843. }
  844. if (i < dxl - 1) {
  845. glyphAdjust += dx[i + 1];
  846. }
  847. if (afpFont.getFontType() == FontType.TRUETYPE) {
  848. flushText(builder, sb, charSet);
  849. ttPos += Math.round(unitConv.mpt2units(glyphAdjust));
  850. builder.absoluteMoveInline(ttPos);
  851. } else if (glyphAdjust != 0) {
  852. flushText(builder, sb, charSet);
  853. int increment = Math.round(unitConv.mpt2units(glyphAdjust));
  854. builder.relativeMoveInline(increment);
  855. }
  856. }
  857. } else {
  858. for (int i = 0; i < l; i++) {
  859. char orgChar = text.charAt(i);
  860. float glyphAdjust = 0;
  861. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  862. sb.append(CharUtilities.SPACE);
  863. int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
  864. int charWidth = font.getCharWidth(orgChar);
  865. glyphAdjust += (charWidth - spaceWidth);
  866. } else {
  867. sb.append(orgChar);
  868. }
  869. if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
  870. glyphAdjust += wordSpacing;
  871. }
  872. glyphAdjust += letterSpacing;
  873. if (i < dxl - 1) {
  874. glyphAdjust += dx[i + 1];
  875. }
  876. if (glyphAdjust != 0) {
  877. flushText(builder, sb, charSet);
  878. int increment = Math.round(unitConv.mpt2units(glyphAdjust));
  879. builder.relativeMoveInline(increment);
  880. }
  881. }
  882. }
  883. flushText(builder, sb, charSet);
  884. bytesAvailable = pto.getBytesAvailable();
  885. }
  886. private void flushText(PtocaBuilder builder, StringBuffer sb,
  887. final CharacterSet charSet) throws IOException {
  888. if (sb.length() > 0) {
  889. builder.addTransparentData(charSet.encodeChars(sb));
  890. sb.setLength(0);
  891. }
  892. }
  893. });
  894. } catch (IOException ioe) {
  895. throw new IFException("I/O error in drawText()", ioe);
  896. }
  897. }
  898. /**
  899. * Saves the graphics state of the rendering engine.
  900. * @throws IOException if an I/O error occurs
  901. */
  902. protected void saveGraphicsState() throws IOException {
  903. getPaintingState().save();
  904. }
  905. /**
  906. * Restores the last graphics state of the rendering engine.
  907. * @throws IOException if an I/O error occurs
  908. */
  909. protected void restoreGraphicsState() throws IOException {
  910. getPaintingState().restore();
  911. }
  912. /** {@inheritDoc} */
  913. public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter,
  914. BorderProps bpsStart, BorderProps bpsEnd) throws IFException {
  915. }
  916. /** {@inheritDoc} */
  917. public boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter,
  918. BorderProps bpsStart, BorderProps bpsEnd) {
  919. return borderPainter.isBackgroundRequired(bpsBefore, bpsAfter, bpsStart, bpsEnd);
  920. }
  921. /** {@inheritDoc} */
  922. public void fillBackground(Rectangle rect, Paint fill, BorderProps bpsBefore,
  923. BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) throws IFException {
  924. // not supported in AFP
  925. }
  926. }