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

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