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 44KB

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