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

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