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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  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. float borderWidthRatio = ((float) beforeWidth) / startWidth;
  602. if (beforeWidth * startRadius > startWidth * beforeRadius) {
  603. cut.lineTo(startRadius, borderWidthRatio * startRadius);
  604. cut.lineTo(startRadius, 0);
  605. } else {
  606. cut.lineTo(startRadius, borderWidthRatio * startRadius);
  607. cut.lineTo(startRadius, 0);
  608. }
  609. clip.intersect(new Area(cut));
  610. clip.transform(transform);
  611. return clip;
  612. }
  613. private Area makeCornerBorderIPD(final int beforeRadius, final int startRadius,
  614. final int beforeWidth, final int startWidth, final AffineTransform transform) {
  615. Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius);
  616. Ellipse2D.Double e = new Ellipse2D.Double();
  617. e.x = 0;
  618. e.y = 0;
  619. e.width = 2 * startRadius;
  620. e.height = 2 * beforeRadius;
  621. Ellipse2D.Double i = new Ellipse2D.Double();
  622. i.x = startWidth;
  623. i.y = beforeWidth;
  624. i.width = 2 * (startRadius - startWidth);
  625. i.height = 2 * (beforeRadius - beforeWidth);
  626. Area clip = new Area(e);
  627. clip.subtract(new Area(i));
  628. clip.intersect(new Area(clipR));
  629. GeneralPath cut = new GeneralPath();
  630. cut.moveTo(0, 0);
  631. float borderWidthRatio = ((float) beforeWidth) / startWidth;
  632. if (beforeWidth * startRadius > startWidth * beforeRadius) {
  633. cut.lineTo(startRadius, borderWidthRatio * startRadius);
  634. cut.lineTo(startRadius, 0);
  635. } else {
  636. cut.lineTo(startRadius, borderWidthRatio * startRadius);
  637. cut.lineTo(startRadius, 0);
  638. }
  639. clip.subtract(new Area(cut));
  640. clip.transform(transform);
  641. return clip;
  642. }
  643. private String makeKey(Rectangle area, BorderProps beforeProps,
  644. BorderProps endProps, BorderProps afterProps, BorderProps startProps,
  645. Color innerBackgroundColor) {
  646. return hash(new StringBuffer()
  647. .append(area.width)
  648. .append(":")
  649. .append(area.height)
  650. .append(":")
  651. .append(beforeProps)
  652. .append(":")
  653. .append(endProps)
  654. .append(":")
  655. .append(afterProps)
  656. .append(":")
  657. .append(startProps)
  658. .append(":")
  659. .append(innerBackgroundColor)
  660. .toString());
  661. }
  662. private String hash(String text) {
  663. MessageDigest md;
  664. try {
  665. md = MessageDigest.getInstance("MD5");
  666. } catch (Exception e) {
  667. throw new RuntimeException("Internal error", e);
  668. }
  669. byte[] result = md.digest(text.getBytes());
  670. StringBuffer sb = new StringBuffer();
  671. char[] digits = {'0', '1', '2', '3', '4', '5', '6',
  672. '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  673. for (int idx = 0; idx < 6; ++idx) {
  674. byte b = result[idx];
  675. sb.append(digits[(b & 0xf0) >> 4]);
  676. sb.append(digits[b & 0x0f]);
  677. }
  678. return sb.toString();
  679. }
  680. private void paintCornersAsBitmap(Graphics2DImagePainter painter,
  681. Rectangle boundingBox, String name) throws IFException {
  682. //TODO parameters ok?
  683. ImageInfo info = new ImageInfo(name, null);
  684. ImageSize size = new ImageSize();
  685. size.setSizeInMillipoints(boundingBox.width, boundingBox.height);
  686. //Use the foreign attributes map to set image handling hints
  687. Map map = new java.util.HashMap(2);
  688. map.put(AFPForeignAttributeReader.RESOURCE_NAME, name);
  689. map.put(AFPForeignAttributeReader.RESOURCE_LEVEL, "print-file");
  690. AFPRenderingContext context = (AFPRenderingContext)
  691. this.painter.createRenderingContext(/*map*/);
  692. size.setResolution(context.getPaintingState().getResolution());
  693. size.calcPixelsFromSize();
  694. info.setSize(size);
  695. ImageGraphics2D img = new ImageGraphics2D(info, painter);
  696. Map hints = new java.util.HashMap();
  697. hints.put(ImageHandlerUtil.CONVERSION_MODE, ImageHandlerUtil.CONVERSION_MODE_BITMAP);
  698. hints.put("TARGET_RESOLUTION",
  699. new Integer(context.getPaintingState().getResolution()));
  700. try {
  701. this.painter.drawImage(img, boundingBox, context, true, hints);
  702. } catch (IOException ioe) {
  703. throw new IFException(
  704. "I/O error while painting corner using a bitmap", ioe);
  705. } catch (ImageException ie) {
  706. throw new IFException(
  707. "Image error while painting corner using a bitmap", ie);
  708. }
  709. }
  710. protected void arcTo(double startAngle, double endAngle, int cx, int cy, int width,
  711. int height) throws IOException {
  712. throw new UnsupportedOperationException("Can only deal with horizontal lines right now");
  713. }
  714. }
  715. /** {@inheritDoc} */
  716. @Override
  717. public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
  718. throws IFException {
  719. try {
  720. this.graphicsPainter.drawLine(start, end, width, color, style);
  721. } catch (IOException ioe) {
  722. throw new IFException("I/O error in drawLine()", ioe);
  723. }
  724. }
  725. /** {@inheritDoc} */
  726. public void drawText( // CSOK: MethodLength
  727. int x, int y, final int letterSpacing, final int wordSpacing,
  728. final int[][] dp, final String text) throws IFException {
  729. final int fontSize = this.state.getFontSize();
  730. getPaintingState().setFontSize(fontSize);
  731. FontTriplet triplet = new FontTriplet(
  732. state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
  733. //TODO Ignored: state.getFontVariant()
  734. String fontKey = getFontKey(triplet);
  735. // register font as necessary
  736. Map<String, Typeface> fontMetricMap = getFontInfo().getFonts();
  737. final AFPFont afpFont = (AFPFont) fontMetricMap.get(fontKey);
  738. final Font font = getFontInfo().getFontInstance(triplet, fontSize);
  739. AFPPageFonts pageFonts = getPaintingState().getPageFonts();
  740. AFPFontAttributes fontAttributes = pageFonts.registerFont(fontKey, afpFont, fontSize);
  741. final int fontReference = fontAttributes.getFontReference();
  742. final int[] coords = unitConv.mpts2units(new float[] {x, y});
  743. final CharacterSet charSet = afpFont.getCharacterSet(fontSize);
  744. if (afpFont.isEmbeddable()) {
  745. try {
  746. getDocumentHandler().getResourceManager().embedFont(afpFont, charSet);
  747. } catch (IOException ioe) {
  748. throw new IFException("Error while embedding font resources", ioe);
  749. }
  750. }
  751. AbstractPageObject page = getDataStream().getCurrentPage();
  752. PresentationTextObject pto = page.getPresentationTextObject();
  753. try {
  754. pto.createControlSequences(new PtocaProducer() {
  755. public void produce(PtocaBuilder builder) throws IOException {
  756. Point p = getPaintingState().getPoint(coords[X], coords[Y]);
  757. builder.setTextOrientation(getPaintingState().getRotation());
  758. builder.absoluteMoveBaseline(p.y);
  759. builder.absoluteMoveInline(p.x);
  760. builder.setExtendedTextColor(state.getTextColor());
  761. builder.setCodedFont((byte) fontReference);
  762. int l = text.length();
  763. int[] dx = IFUtil.convertDPToDX ( dp );
  764. int dxl = (dx != null ? dx.length : 0);
  765. StringBuffer sb = new StringBuffer();
  766. if (dxl > 0 && dx[0] != 0) {
  767. int dxu = Math.round(unitConv.mpt2units(dx[0]));
  768. builder.relativeMoveInline(-dxu);
  769. }
  770. //Following are two variants for glyph placement.
  771. //SVI does not seem to be implemented in the same way everywhere, so
  772. //a fallback alternative is preserved here.
  773. final boolean usePTOCAWordSpacing = true;
  774. if (usePTOCAWordSpacing) {
  775. int interCharacterAdjustment = 0;
  776. if (letterSpacing != 0) {
  777. interCharacterAdjustment = Math.round(unitConv.mpt2units(
  778. letterSpacing));
  779. }
  780. builder.setInterCharacterAdjustment(interCharacterAdjustment);
  781. int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
  782. int fixedSpaceCharacterIncrement = Math.round(unitConv.mpt2units(
  783. spaceWidth + letterSpacing));
  784. int varSpaceCharacterIncrement = fixedSpaceCharacterIncrement;
  785. if (wordSpacing != 0) {
  786. varSpaceCharacterIncrement = Math.round(unitConv.mpt2units(
  787. spaceWidth + wordSpacing + letterSpacing));
  788. }
  789. builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement);
  790. boolean fixedSpaceMode = false;
  791. for (int i = 0; i < l; i++) {
  792. char orgChar = text.charAt(i);
  793. float glyphAdjust = 0;
  794. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  795. flushText(builder, sb, charSet);
  796. builder.setVariableSpaceCharacterIncrement(
  797. fixedSpaceCharacterIncrement);
  798. fixedSpaceMode = true;
  799. sb.append(CharUtilities.SPACE);
  800. int charWidth = font.getCharWidth(orgChar);
  801. glyphAdjust += (charWidth - spaceWidth);
  802. } else {
  803. if (fixedSpaceMode) {
  804. flushText(builder, sb, charSet);
  805. builder.setVariableSpaceCharacterIncrement(
  806. varSpaceCharacterIncrement);
  807. fixedSpaceMode = false;
  808. }
  809. char ch;
  810. if (orgChar == CharUtilities.NBSPACE) {
  811. ch = ' '; //converted to normal space to allow word spacing
  812. } else {
  813. ch = orgChar;
  814. }
  815. sb.append(ch);
  816. }
  817. if (i < dxl - 1) {
  818. glyphAdjust += dx[i + 1];
  819. }
  820. if (glyphAdjust != 0) {
  821. flushText(builder, sb, charSet);
  822. int increment = Math.round(unitConv.mpt2units(glyphAdjust));
  823. builder.relativeMoveInline(increment);
  824. }
  825. }
  826. } else {
  827. for (int i = 0; i < l; i++) {
  828. char orgChar = text.charAt(i);
  829. float glyphAdjust = 0;
  830. if (CharUtilities.isFixedWidthSpace(orgChar)) {
  831. sb.append(CharUtilities.SPACE);
  832. int spaceWidth = font.getCharWidth(CharUtilities.SPACE);
  833. int charWidth = font.getCharWidth(orgChar);
  834. glyphAdjust += (charWidth - spaceWidth);
  835. } else {
  836. sb.append(orgChar);
  837. }
  838. if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
  839. glyphAdjust += wordSpacing;
  840. }
  841. glyphAdjust += letterSpacing;
  842. if (i < dxl - 1) {
  843. glyphAdjust += dx[i + 1];
  844. }
  845. if (glyphAdjust != 0) {
  846. flushText(builder, sb, charSet);
  847. int increment = Math.round(unitConv.mpt2units(glyphAdjust));
  848. builder.relativeMoveInline(increment);
  849. }
  850. }
  851. }
  852. flushText(builder, sb, charSet);
  853. }
  854. private void flushText(PtocaBuilder builder, StringBuffer sb,
  855. final CharacterSet charSet) throws IOException {
  856. if (sb.length() > 0) {
  857. builder.addTransparentData(charSet.encodeChars(sb));
  858. sb.setLength(0);
  859. }
  860. }
  861. });
  862. } catch (IOException ioe) {
  863. throw new IFException("I/O error in drawText()", ioe);
  864. }
  865. }
  866. /**
  867. * Saves the graphics state of the rendering engine.
  868. * @throws IOException if an I/O error occurs
  869. */
  870. protected void saveGraphicsState() throws IOException {
  871. getPaintingState().save();
  872. }
  873. /**
  874. * Restores the last graphics state of the rendering engine.
  875. * @throws IOException if an I/O error occurs
  876. */
  877. protected void restoreGraphicsState() throws IOException {
  878. getPaintingState().restore();
  879. }
  880. /** {@inheritDoc} */
  881. public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter,
  882. BorderProps bpsStart, BorderProps bpsEnd) throws IFException {
  883. }
  884. /** {@inheritDoc} */
  885. public boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter,
  886. BorderProps bpsStart, BorderProps bpsEnd) {
  887. return borderPainter.isBackgroundRequired(bpsBefore, bpsAfter, bpsStart, bpsEnd);
  888. }
  889. /** {@inheritDoc} */
  890. public void fillBackground(Rectangle rect, Paint fill, BorderProps bpsBefore,
  891. BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) throws IFException {
  892. // not supported in AFP
  893. }
  894. }