/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.render.afp; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.security.MessageDigest; import java.util.Map; import org.w3c.dom.Document; import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageProcessingHints; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.image.loader.ImageSize; import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; import org.apache.fop.afp.AFPBorderPainter; import org.apache.fop.afp.AFPEventProducer; import org.apache.fop.afp.AFPObjectAreaInfo; import org.apache.fop.afp.AFPPaintingState; import org.apache.fop.afp.AFPResourceInfo; import org.apache.fop.afp.AFPUnitConverter; import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.BorderPaintingInfo; import org.apache.fop.afp.DataStream; import org.apache.fop.afp.RectanglePaintingInfo; import org.apache.fop.afp.fonts.AFPFont; import org.apache.fop.afp.fonts.AFPFontAttributes; import org.apache.fop.afp.fonts.AFPPageFonts; import org.apache.fop.afp.fonts.CharacterSet; import org.apache.fop.afp.modca.AbstractPageObject; import org.apache.fop.afp.modca.PresentationTextObject; import org.apache.fop.afp.ptoca.PtocaBuilder; import org.apache.fop.afp.ptoca.PtocaProducer; import org.apache.fop.afp.util.AFPResourceAccessor; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.FontType; import org.apache.fop.fonts.Typeface; import org.apache.fop.render.ImageHandlerUtil; import org.apache.fop.render.RenderingContext; import org.apache.fop.render.intermediate.AbstractIFPainter; import org.apache.fop.render.intermediate.BorderPainter; import org.apache.fop.render.intermediate.GraphicsPainter; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFState; import org.apache.fop.render.intermediate.IFUtil; import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.CharUtilities; /** * IFPainter implementation that produces AFP (MO:DCA). */ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> { private static final int X = 0; private static final int Y = 1; private final GraphicsPainter graphicsPainter; /** the border painter */ private final AFPBorderPainterAdapter borderPainter; /** the rectangle painter */ private final AbstractAFPPainter rectanglePainter; /** unit converter */ private final AFPUnitConverter unitConv; private final AFPEventProducer eventProducer; private Integer bytesAvailable; /** * Default constructor. * @param documentHandler the parent document handler */ public AFPPainter(AFPDocumentHandler documentHandler) { super(documentHandler); this.state = IFState.create(); this.graphicsPainter = new AFPGraphicsPainter( new AFPBorderPainter(getPaintingState(), getDataStream())); this.borderPainter = new AFPBorderPainterAdapter(graphicsPainter, this, documentHandler); this.rectanglePainter = documentHandler.createRectanglePainter(); this.unitConv = getPaintingState().getUnitConverter(); this.eventProducer = AFPEventProducer.Provider.get(getUserAgent().getEventBroadcaster()); } private AFPPaintingState getPaintingState() { return getDocumentHandler().getPaintingState(); } private DataStream getDataStream() { return getDocumentHandler().getDataStream(); } @Override public String getFontKey(FontTriplet triplet) throws IFException { try { return super.getFontKey(triplet); } catch (IFException e) { eventProducer.invalidConfiguration(null, e); return super.getFontKey(FontTriplet.DEFAULT_FONT_TRIPLET); } } /** {@inheritDoc} */ public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) throws IFException { //AFP doesn't support clipping, so we treat viewport like a group //this is the same code as for startGroup() try { saveGraphicsState(); concatenateTransformationMatrix(transform); } catch (IOException ioe) { throw new IFException("I/O error in startViewport()", ioe); } } /** {@inheritDoc} */ public void endViewport() throws IFException { try { restoreGraphicsState(); } catch (IOException ioe) { throw new IFException("I/O error in endViewport()", ioe); } } private void concatenateTransformationMatrix(AffineTransform at) { if (!at.isIdentity()) { getPaintingState().concatenate(at); } } /** {@inheritDoc} */ public void startGroup(AffineTransform transform, String layer) throws IFException { try { saveGraphicsState(); concatenateTransformationMatrix(transform); } catch (IOException ioe) { throw new IFException("I/O error in startGroup()", ioe); } } /** {@inheritDoc} */ public void endGroup() throws IFException { try { restoreGraphicsState(); } catch (IOException ioe) { throw new IFException("I/O error in endGroup()", ioe); } } /** {@inheritDoc} */ @Override protected Map createDefaultImageProcessingHints(ImageSessionContext sessionContext) { Map hints = super.createDefaultImageProcessingHints(sessionContext); //AFP doesn't support alpha channels hints.put(ImageProcessingHints.TRANSPARENCY_INTENT, ImageProcessingHints.TRANSPARENCY_INTENT_IGNORE); hints.put("CMYK", getDocumentHandler().getPaintingState().isCMYKImagesSupported()); return hints; } /** {@inheritDoc} */ @Override protected RenderingContext createRenderingContext() { AFPRenderingContext renderingContext = new AFPRenderingContext( getUserAgent(), getDocumentHandler().getResourceManager(), getPaintingState(), getFontInfo(), getContext().getForeignAttributes()); return renderingContext; } /** {@inheritDoc} */ public void drawImage(String uri, Rectangle rect) throws IFException { PageSegmentDescriptor pageSegment = getDocumentHandler().getPageSegmentNameFor(uri); if (pageSegment != null) { float[] srcPts = {rect.x, rect.y}; int[] coords = unitConv.mpts2units(srcPts); int width = Math.round(unitConv.mpt2units(rect.width)); int height = Math.round(unitConv.mpt2units(rect.height)); getDataStream().createIncludePageSegment(pageSegment.getName(), coords[X], coords[Y], width, height); //Do we need to embed an external page segment? if (pageSegment.getURI() != null) { AFPResourceAccessor accessor = new AFPResourceAccessor( getDocumentHandler().getUserAgent().getResourceResolver()); try { URI resourceUri = new URI(pageSegment.getURI()); getDocumentHandler().getResourceManager().createIncludedResourceFromExternal( pageSegment.getName(), resourceUri, accessor); } catch (URISyntaxException urie) { throw new IFException("Could not handle resource url" + pageSegment.getURI(), urie); } catch (IOException ioe) { throw new IFException("Could not handle resource" + pageSegment.getURI(), ioe); } } } else { drawImageUsingURI(uri, rect); } } /** {@inheritDoc} */ protected void drawImage(Image image, Rectangle rect, RenderingContext context, boolean convert, Map additionalHints) throws IOException, ImageException { AFPRenderingContext afpContext = (AFPRenderingContext) context; AFPResourceInfo resourceInfo = AFPImageHandler.createResourceInformation( image.getInfo().getOriginalURI(), afpContext.getForeignAttributes()); //Check if the image is cached before processing it again if (afpContext.getResourceManager().isObjectCached(resourceInfo)) { AFPObjectAreaInfo areaInfo = AFPImageHandler.createObjectAreaInfo( afpContext.getPaintingState(), rect); afpContext.getResourceManager().includeCachedObject(resourceInfo, areaInfo); } else { super.drawImage(image, rect, context, convert, additionalHints); } } /** {@inheritDoc} */ public void drawImage(Document doc, Rectangle rect) throws IFException { drawImageUsingDocument(doc, rect); } /** {@inheritDoc} */ public void clipRect(Rectangle rect) throws IFException { //Not supported! } private float toPoint(int mpt) { return mpt / 1000f; } /** {@inheritDoc} */ public void fillRect(Rectangle rect, Paint fill) throws IFException { if (fill == null) { return; } if (rect.width != 0 && rect.height != 0) { if (fill instanceof Color) { getPaintingState().setColor((Color) fill); } else { throw new UnsupportedOperationException("Non-Color paints NYI"); } RectanglePaintingInfo rectanglePaintInfo = new RectanglePaintingInfo( toPoint(rect.x), toPoint(rect.y), toPoint(rect.width), toPoint(rect.height)); try { rectanglePainter.paint(rectanglePaintInfo); } catch (IOException ioe) { throw new IFException("IO error while painting rectangle", ioe); } } } @Override public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException { if (top != null || bottom != null || left != null || right != null) { this.borderPainter.drawBorders(rect, top, bottom, left, right, innerBackgroundColor); } } private static final class AFPGraphicsPainter implements GraphicsPainter { private final AFPBorderPainter graphicsPainter; private AFPGraphicsPainter(AFPBorderPainter delegate) { this.graphicsPainter = delegate; } public void drawBorderLine(int x1, int y1, int x2, int y2, boolean horz, boolean startOrBefore, int style, Color color) throws IOException { BorderPaintingInfo borderPaintInfo = new BorderPaintingInfo( toPoints(x1), toPoints(y1), toPoints(x2), toPoints(y2), horz, style, color); graphicsPainter.paint(borderPaintInfo); } private float toPoints(int mpt) { return mpt / 1000f; } public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IOException { if (start.y != end.y) { //TODO Support arbitrary lines if necessary throw new UnsupportedOperationException("Can only deal with horizontal lines right now"); } //Simply delegates to drawBorderLine() as AFP line painting is not very sophisticated. int halfWidth = width / 2; drawBorderLine(start.x, start.y - halfWidth, end.x, start.y + halfWidth, true, true, style.getEnumValue(), color); } public void moveTo(int x, int y) throws IOException { } public void lineTo(int x, int y) throws IOException { } public void arcTo(double startAngle, double endAngle, int cx, int cy, int width, int height) throws IOException { } public void rotateCoordinates(double angle) throws IOException { throw new UnsupportedOperationException("Cannot handle coordinate rotation"); } public void translateCoordinates(int xTranslate, int yTranslate) throws IOException { throw new UnsupportedOperationException("Cannot handle coordinate translation"); } public void scaleCoordinates(float xScale, float yScale) throws IOException { throw new UnsupportedOperationException("Cannot handle coordinate scaling"); } public void closePath() throws IOException { } public void clip() throws IOException { } public void saveGraphicsState() throws IOException { } public void restoreGraphicsState() throws IOException { } } //TODO Try to resolve the name-clash between the AFPBorderPainter in the afp package //and this one. Not done for now to avoid a lot of re-implementation and code duplication. private static class AFPBorderPainterAdapter extends BorderPainter { private final class BorderImagePainter implements Graphics2DImagePainter { private final double cornerCorrectionFactor; private final Rectangle borderRect; private final BorderProps bpsStart; private final BorderProps bpsEnd; private final BorderProps bpsBefore; private final BorderProps bpsAfter; private final boolean[] roundCorner; private final Color innerBackgroundColor; /* TODO represent border related parameters in a class */ private BorderImagePainter(double cornerCorrectionFactor, Rectangle borderRect, BorderProps bpsStart, BorderProps bpsEnd, BorderProps bpsBefore, BorderProps bpsAfter, boolean[] roundCorner, Color innerBackgroundColor) { this.cornerCorrectionFactor = cornerCorrectionFactor; this.borderRect = borderRect; this.bpsStart = bpsStart; this.bpsBefore = bpsBefore; this.roundCorner = roundCorner; this.bpsEnd = bpsEnd; this.bpsAfter = bpsAfter; this.innerBackgroundColor = innerBackgroundColor; } public void paint(Graphics2D g2d, Rectangle2D area) { //background Area background = new Area(area); Area cornerRegion = new Area(); Area[] cornerBorder = new Area[]{new Area(), new Area(), new Area(), new Area()}; Area[] clip = new Area[4]; if (roundCorner[TOP_LEFT]) { AffineTransform transform = new AffineTransform(); int beforeRadius = (int)(cornerCorrectionFactor * bpsBefore.getRadiusStart()); int startRadius = (int)(cornerCorrectionFactor * bpsStart.getRadiusStart()); int beforeWidth = bpsBefore.width; int startWidth = bpsStart.width; int corner = TOP_LEFT; background.subtract(makeCornerClip(beforeRadius, startRadius, transform)); clip[TOP_LEFT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); clip[TOP_LEFT].transform(transform); cornerRegion.add(clip[TOP_LEFT]); cornerBorder[TOP].add(makeCornerBorderBPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); cornerBorder[LEFT].add(makeCornerBorderIPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); } if (roundCorner[TOP_RIGHT]) { AffineTransform transform = new AffineTransform(-1, 0, 0, 1, borderRect.width, 0); int beforeRadius = (int)(cornerCorrectionFactor * bpsBefore.getRadiusEnd()); int startRadius = (int)(cornerCorrectionFactor * bpsEnd.getRadiusStart()); int beforeWidth = bpsBefore.width; int startWidth = bpsEnd.width; int corner = TOP_RIGHT; background.subtract(makeCornerClip(beforeRadius, startRadius, transform)); clip[TOP_RIGHT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); clip[TOP_RIGHT].transform(transform); cornerRegion.add(clip[TOP_RIGHT]); cornerBorder[TOP].add(makeCornerBorderBPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); cornerBorder[RIGHT].add(makeCornerBorderIPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); } if (roundCorner[BOTTOM_RIGHT]) { AffineTransform transform = new AffineTransform(-1, 0, 0, -1, borderRect.width, borderRect.height); int beforeRadius = (int)(cornerCorrectionFactor * bpsAfter.getRadiusEnd()); int startRadius = (int)(cornerCorrectionFactor * bpsEnd.getRadiusEnd()); int beforeWidth = bpsAfter.width; int startWidth = bpsEnd.width; int corner = BOTTOM_RIGHT; background.subtract(makeCornerClip(beforeRadius, startRadius, transform)); clip[BOTTOM_RIGHT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); clip[BOTTOM_RIGHT].transform(transform); cornerRegion.add(clip[BOTTOM_RIGHT]); cornerBorder[BOTTOM].add(makeCornerBorderBPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); cornerBorder[RIGHT].add(makeCornerBorderIPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); } if (roundCorner[BOTTOM_LEFT]) { AffineTransform transform = new AffineTransform(1, 0, 0, -1, 0, borderRect.height); int beforeRadius = (int)(cornerCorrectionFactor * bpsAfter.getRadiusStart()); int startRadius = (int)(cornerCorrectionFactor * bpsStart.getRadiusEnd()); int beforeWidth = bpsAfter.width; int startWidth = bpsStart.width; int corner = BOTTOM_LEFT; background.subtract(makeCornerClip(beforeRadius, startRadius, transform)); clip[BOTTOM_LEFT] = new Area(new Rectangle(0, 0, startRadius, beforeRadius)); clip[BOTTOM_LEFT].transform(transform); cornerRegion.add(clip[BOTTOM_LEFT]); cornerBorder[BOTTOM].add(makeCornerBorderBPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); cornerBorder[LEFT].add(makeCornerBorderIPD(beforeRadius, startRadius, beforeWidth, startWidth, transform)); } g2d.setColor(innerBackgroundColor); g2d.fill(background); //paint the borders //TODO refactor to repeating code into method if (bpsBefore != null && bpsBefore.width > 0) { GeneralPath borderPath = new GeneralPath(); borderPath.moveTo(0, 0); borderPath.lineTo(borderRect.width, 0); borderPath.lineTo( borderRect.width - (bpsEnd == null ? 0 : bpsEnd.width), bpsBefore.width); borderPath.lineTo(bpsStart == null ? 0 : bpsStart.width, bpsBefore.width); Area border = new Area(borderPath); if (clip[TOP_LEFT] != null) { border.subtract(clip[TOP_LEFT]); } if (clip[TOP_RIGHT] != null) { border.subtract(clip[TOP_RIGHT]); } g2d.setColor(bpsBefore.color); g2d.fill(border); g2d.fill(cornerBorder[TOP]); } if (bpsEnd != null && bpsEnd.width > 0) { GeneralPath borderPath = new GeneralPath(); borderPath.moveTo(borderRect.width, 0); borderPath.lineTo(borderRect.width, borderRect.height); borderPath.lineTo( borderRect.width - bpsEnd.width, borderRect.height - (bpsAfter == null ? 0 : bpsAfter.width)); borderPath.lineTo( borderRect.width - bpsEnd.width, bpsBefore == null ? 0 : bpsBefore.width); Area border = new Area(borderPath); if (clip[BOTTOM_RIGHT] != null) { border.subtract(clip[BOTTOM_RIGHT]); } if (clip[TOP_RIGHT] != null) { border.subtract(clip[TOP_RIGHT]); } g2d.setColor(bpsEnd.color); g2d.fill(border); g2d.fill(cornerBorder[RIGHT]); } if (bpsAfter != null && bpsAfter.width > 0) { GeneralPath borderPath = new GeneralPath(); borderPath.moveTo(0, borderRect.height); borderPath.lineTo(borderRect.width, borderRect.height); borderPath.lineTo( borderRect.width - (bpsEnd == null ? 0 : bpsEnd.width), borderRect.height - bpsAfter.width); borderPath.lineTo(bpsStart == null ? 0 : bpsStart.width, borderRect.height - bpsAfter.width); Area border = new Area(borderPath); if (clip[BOTTOM_LEFT] != null) { border.subtract(clip[BOTTOM_LEFT]); } if (clip[BOTTOM_RIGHT] != null) { border.subtract(clip[BOTTOM_RIGHT]); } g2d.setColor(bpsAfter.color); g2d.fill(border); g2d.fill(cornerBorder[BOTTOM]); } if (bpsStart != null && bpsStart.width > 0) { GeneralPath borderPath = new GeneralPath(); borderPath.moveTo(bpsStart.width, bpsBefore == null ? 0 : bpsBefore.width); borderPath.lineTo(bpsStart.width, borderRect.height - (bpsAfter == null ? 0 : bpsAfter.width)); borderPath.lineTo(0, borderRect.height); borderPath.lineTo(0, 0); Area border = new Area(borderPath); if (clip[BOTTOM_LEFT] != null) { border.subtract(clip[BOTTOM_LEFT]); } if (clip[TOP_LEFT] != null) { border.subtract(clip[TOP_LEFT]); } g2d.setColor(bpsStart.color); g2d.fill(border); g2d.fill(cornerBorder[LEFT]); } } public Dimension getImageSize() { return borderRect.getSize(); } } private final AFPPainter painter; private final AFPDocumentHandler documentHandler; public AFPBorderPainterAdapter(GraphicsPainter graphicsPainter, AFPPainter painter, AFPDocumentHandler documentHandler) { super(graphicsPainter); this.painter = painter; this.documentHandler = documentHandler; } public void drawBorders(final Rectangle borderRect, final BorderProps bpsBefore, final BorderProps bpsAfter, final BorderProps bpsStart, final BorderProps bpsEnd, Color innerBackgroundColor) throws IFException { drawRoundedCorners(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd, innerBackgroundColor); } private boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) { return !hasRoundedCorners(bpsBefore, bpsAfter, bpsStart, bpsEnd); } private boolean hasRoundedCorners(final BorderProps bpsBefore, final BorderProps bpsAfter, final BorderProps bpsStart, final BorderProps bpsEnd) { return ((bpsStart == null ? false : bpsStart.getRadiusStart() > 0) && (bpsBefore == null ? false : bpsBefore.getRadiusStart() > 0)) || ((bpsBefore == null ? false : bpsBefore.getRadiusEnd() > 0) && (bpsEnd == null ? false : bpsEnd.getRadiusStart() > 0)) || ((bpsEnd == null ? false : bpsEnd.getRadiusEnd() > 0) && (bpsAfter == null ? false : bpsAfter.getRadiusEnd() > 0)) || ((bpsAfter == null ? false : bpsAfter.getRadiusStart() > 0) && (bpsStart == null ? false : bpsStart.getRadiusEnd() > 0)); } private void drawRoundedCorners(final Rectangle borderRect, final BorderProps bpsBefore, final BorderProps bpsAfter, final BorderProps bpsStart, final BorderProps bpsEnd, final Color innerBackgroundColor) throws IFException { final double cornerCorrectionFactor = calculateCornerCorrectionFactor(borderRect.width, borderRect.height, bpsBefore, bpsAfter, bpsStart, bpsEnd); final boolean[] roundCorner = new boolean[]{ bpsBefore != null && bpsStart != null && bpsBefore.getRadiusStart() > 0 && bpsStart.getRadiusStart() > 0 && isNotCollapseOuter(bpsBefore) && isNotCollapseOuter(bpsStart), bpsEnd != null && bpsBefore != null && bpsEnd.getRadiusStart() > 0 && bpsBefore.getRadiusEnd() > 0 && isNotCollapseOuter(bpsEnd) && isNotCollapseOuter(bpsBefore), bpsEnd != null && bpsAfter != null && bpsEnd.getRadiusEnd() > 0 && bpsAfter.getRadiusEnd() > 0 && isNotCollapseOuter(bpsEnd) && isNotCollapseOuter(bpsAfter), bpsStart != null && bpsAfter != null && bpsStart.getRadiusEnd() > 0 && bpsAfter.getRadiusStart() > 0 && isNotCollapseOuter(bpsStart) && isNotCollapseOuter(bpsAfter) }; if (!roundCorner[TOP_LEFT] && !roundCorner[TOP_RIGHT] && !roundCorner[BOTTOM_RIGHT] && !roundCorner[BOTTOM_LEFT]) { try { drawRectangularBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); } catch (IOException ioe) { throw new IFException("IO error drawing borders", ioe); } return; } String areaKey = makeKey(borderRect, bpsBefore, bpsEnd, bpsAfter, bpsStart, innerBackgroundColor); Graphics2DImagePainter painter = null; String name = documentHandler.getCachedRoundedCorner(areaKey); if (name == null) { name = documentHandler.cacheRoundedCorner(areaKey); painter = new BorderImagePainter(cornerCorrectionFactor, borderRect, bpsStart, bpsEnd, bpsBefore, bpsAfter, roundCorner, innerBackgroundColor); } paintCornersAsBitmap(painter, borderRect, name); } private boolean isNotCollapseOuter(BorderProps bp) { return !bp.isCollapseOuter(); } private Area makeCornerClip(final int beforeRadius, final int startRadius, final AffineTransform transform) { Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius); Area clip = new Area(clipR); Ellipse2D.Double e = new Ellipse2D.Double(); e.x = 0; e.y = 0; e.width = 2 * startRadius; e.height = 2 * beforeRadius; clip.subtract(new Area(e)); clip.transform(transform); return clip; } private Area makeCornerBorderBPD(final int beforeRadius, final int startRadius, final int beforeWidth, final int startWidth, final AffineTransform transform) { Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius); Ellipse2D.Double e = new Ellipse2D.Double(); e.x = 0; e.y = 0; e.width = 2 * startRadius; e.height = 2 * beforeRadius; Ellipse2D.Double i = new Ellipse2D.Double(); i.x = startWidth; i.y = beforeWidth; i.width = 2 * (startRadius - startWidth); i.height = 2 * (beforeRadius - beforeWidth); Area clip = new Area(e); clip.subtract(new Area(i)); clip.intersect(new Area(clipR)); GeneralPath cut = new GeneralPath(); cut.moveTo(0, 0); cut.lineTo(startRadius, ((float) startRadius * beforeWidth) / startWidth); cut.lineTo(startRadius, 0); clip.intersect(new Area(cut)); clip.transform(transform); return clip; } private Area makeCornerBorderIPD(final int beforeRadius, final int startRadius, final int beforeWidth, final int startWidth, final AffineTransform transform) { Rectangle clipR = new Rectangle(0, 0, startRadius, beforeRadius); Ellipse2D.Double e = new Ellipse2D.Double(); e.x = 0; e.y = 0; e.width = 2 * startRadius; e.height = 2 * beforeRadius; Ellipse2D.Double i = new Ellipse2D.Double(); i.x = startWidth; i.y = beforeWidth; i.width = 2 * (startRadius - startWidth); i.height = 2 * (beforeRadius - beforeWidth); Area clip = new Area(e); clip.subtract(new Area(i)); clip.intersect(new Area(clipR)); GeneralPath cut = new GeneralPath(); cut.moveTo(0, 0); cut.lineTo(startRadius, ((float) startRadius * beforeWidth) / startWidth); cut.lineTo(startRadius, 0); clip.subtract(new Area(cut)); clip.transform(transform); return clip; } private String makeKey(Rectangle area, BorderProps beforeProps, BorderProps endProps, BorderProps afterProps, BorderProps startProps, Color innerBackgroundColor) { return hash(new StringBuffer() .append(area.width) .append(":") .append(area.height) .append(":") .append(beforeProps) .append(":") .append(endProps) .append(":") .append(afterProps) .append(":") .append(startProps) .append(":") .append(innerBackgroundColor) .toString()); } private String hash(String text) { MessageDigest md; try { md = MessageDigest.getInstance("MD5"); } catch (Exception e) { throw new RuntimeException("Internal error", e); } byte[] result = md.digest(text.getBytes()); StringBuffer sb = new StringBuffer(); char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; for (int idx = 0; idx < 6; ++idx) { byte b = result[idx]; sb.append(digits[(b & 0xf0) >> 4]); sb.append(digits[b & 0x0f]); } return sb.toString(); } private void paintCornersAsBitmap(Graphics2DImagePainter painter, Rectangle boundingBox, String name) throws IFException { //TODO parameters ok? ImageInfo info = new ImageInfo(name, null); ImageSize size = new ImageSize(); size.setSizeInMillipoints(boundingBox.width, boundingBox.height); //Use the foreign attributes map to set image handling hints Map map = new java.util.HashMap(2); map.put(AFPForeignAttributeReader.RESOURCE_NAME, name); map.put(AFPForeignAttributeReader.RESOURCE_LEVEL, "print-file"); AFPRenderingContext context = (AFPRenderingContext) this.painter.createRenderingContext(/*map*/); size.setResolution(context.getPaintingState().getResolution()); size.calcPixelsFromSize(); info.setSize(size); ImageGraphics2D img = new ImageGraphics2D(info, painter); Map hints = new java.util.HashMap(); hints.put(ImageHandlerUtil.CONVERSION_MODE, ImageHandlerUtil.CONVERSION_MODE_BITMAP); hints.put("TARGET_RESOLUTION", context.getPaintingState().getResolution()); try { this.painter.drawImage(img, boundingBox, context, true, hints); } catch (IOException ioe) { throw new IFException( "I/O error while painting corner using a bitmap", ioe); } catch (ImageException ie) { throw new IFException( "Image error while painting corner using a bitmap", ie); } } protected void arcTo(double startAngle, double endAngle, int cx, int cy, int width, int height) throws IOException { throw new UnsupportedOperationException("Can only deal with horizontal lines right now"); } } /** {@inheritDoc} */ @Override public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IFException { try { this.graphicsPainter.drawLine(start, end, width, color, style); } catch (IOException ioe) { throw new IFException("I/O error in drawLine()", ioe); } } /** {@inheritDoc} */ public void drawText(int x, int y, final int letterSpacing, final int wordSpacing, final int[][] dp, final String text) throws IFException { new DefaultPtocaProducer(x, y, letterSpacing, wordSpacing, dp, text); } private final class DefaultPtocaProducer implements PtocaProducer { final int[] coords; final int fontReference; final String text; final int[][] dp; final int letterSpacing; final int wordSpacing; final Font font; final AFPFont afpFont; final CharacterSet charSet; final PresentationTextObject pto; private DefaultPtocaProducer(int x, int y, final int letterSpacing, final int wordSpacing, final int[][] dp, final String text) throws IFException { this.letterSpacing = letterSpacing; this.wordSpacing = wordSpacing; this.text = text; this.dp = dp; final int fontSize = state.getFontSize(); getPaintingState().setFontSize(fontSize); FontTriplet triplet = new FontTriplet( state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); //TODO Ignored: state.getFontVariant() String fontKey = getFontKey(triplet); // register font as necessary Map<String, Typeface> fontMetricMap = getFontInfo().getFonts(); afpFont = (AFPFont) fontMetricMap.get(fontKey); font = getFontInfo().getFontInstance(triplet, fontSize); AFPPageFonts pageFonts = getPaintingState().getPageFonts(); AFPFontAttributes fontAttributes = pageFonts.registerFont(fontKey, afpFont, fontSize); fontReference = fontAttributes.getFontReference(); coords = unitConv.mpts2units(new float[] {x, y}); charSet = afpFont.getCharacterSet(fontSize); if (afpFont.isEmbeddable()) { try { getDocumentHandler().getResourceManager().embedFont(afpFont, charSet); } catch (IOException ioe) { throw new IFException("Error while embedding font resources", ioe); } } AbstractPageObject page = getDataStream().getCurrentPage(); try { if (bytesAvailable != null && bytesAvailable < getSize()) { page.endPresentationObject(); } pto = page.getPresentationTextObject(); pto.createControlSequences(this); } catch (IOException ioe) { throw new IFException("I/O error in drawText()", ioe); } } private int getSize() throws IOException { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); PtocaBuilder pb = new PtocaBuilder() { protected OutputStream getOutputStreamForControlSequence(int length) { return bos; } }; produce(pb); return bos.size(); } public void produce(PtocaBuilder builder) throws IOException { Point p = getPaintingState().getPoint(coords[X], coords[Y]); builder.setTextOrientation(getPaintingState().getRotation()); builder.absoluteMoveBaseline(p.y); builder.absoluteMoveInline(p.x); builder.setExtendedTextColor(state.getTextColor()); builder.setCodedFont((byte) fontReference); int l = text.length(); int[] dx = IFUtil.convertDPToDX(dp); int dxl = (dx != null ? dx.length : 0); StringBuffer sb = new StringBuffer(); if (dxl > 0 && dx[0] != 0) { int dxu = Math.round(unitConv.mpt2units(dx[0])); builder.relativeMoveInline(-dxu); } //Following are two variants for glyph placement. //SVI does not seem to be implemented in the same way everywhere, so //a fallback alternative is preserved here. final boolean usePTOCAWordSpacing = true; if (usePTOCAWordSpacing) { int interCharacterAdjustment = 0; if (letterSpacing != 0) { interCharacterAdjustment = Math.round(unitConv.mpt2units( letterSpacing)); } builder.setInterCharacterAdjustment(interCharacterAdjustment); int spaceWidth = font.getCharWidth(CharUtilities.SPACE); int fixedSpaceCharacterIncrement = Math.round(unitConv.mpt2units( spaceWidth + letterSpacing)); int varSpaceCharacterIncrement = fixedSpaceCharacterIncrement; if (wordSpacing != 0) { varSpaceCharacterIncrement = Math.round(unitConv.mpt2units( spaceWidth + wordSpacing + letterSpacing)); } builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement); boolean fixedSpaceMode = false; int ttPos = p.x; for (int i = 0; i < l; i++) { char orgChar = text.charAt(i); float glyphAdjust = 0; if (afpFont.getFontType() == FontType.TRUETYPE) { flushText(builder, sb, charSet); fixedSpaceMode = true; int charWidth = font.getCharWidth(orgChar); sb.append(orgChar); glyphAdjust += charWidth; } else if (CharUtilities.isFixedWidthSpace(orgChar)) { flushText(builder, sb, charSet); builder.setVariableSpaceCharacterIncrement( fixedSpaceCharacterIncrement); fixedSpaceMode = true; sb.append(CharUtilities.SPACE); int charWidth = font.getCharWidth(orgChar); glyphAdjust += (charWidth - spaceWidth); } else { if (fixedSpaceMode) { flushText(builder, sb, charSet); builder.setVariableSpaceCharacterIncrement( varSpaceCharacterIncrement); fixedSpaceMode = false; } char ch; if (orgChar == CharUtilities.NBSPACE) { ch = ' '; //converted to normal space to allow word spacing } else { ch = orgChar; } sb.append(ch); } if (i < dxl - 1) { glyphAdjust += dx[i + 1]; } if (afpFont.getFontType() == FontType.TRUETYPE) { flushText(builder, sb, charSet); ttPos += Math.round(unitConv.mpt2units(glyphAdjust)); builder.absoluteMoveInline(ttPos); } else if (glyphAdjust != 0) { flushText(builder, sb, charSet); int increment = Math.round(unitConv.mpt2units(glyphAdjust)); builder.relativeMoveInline(increment); } } } else { for (int i = 0; i < l; i++) { char orgChar = text.charAt(i); float glyphAdjust = 0; if (CharUtilities.isFixedWidthSpace(orgChar)) { sb.append(CharUtilities.SPACE); int spaceWidth = font.getCharWidth(CharUtilities.SPACE); int charWidth = font.getCharWidth(orgChar); glyphAdjust += (charWidth - spaceWidth); } else { sb.append(orgChar); } if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) { glyphAdjust += wordSpacing; } glyphAdjust += letterSpacing; if (i < dxl - 1) { glyphAdjust += dx[i + 1]; } if (glyphAdjust != 0) { flushText(builder, sb, charSet); int increment = Math.round(unitConv.mpt2units(glyphAdjust)); builder.relativeMoveInline(increment); } } } flushText(builder, sb, charSet); if (pto != null) { bytesAvailable = pto.getBytesAvailable(); } } private void flushText(PtocaBuilder builder, StringBuffer sb, final CharacterSet charSet) throws IOException { if (sb.length() > 0) { builder.addTransparentData(charSet.encodeChars(sb)); sb.setLength(0); } } } /** * Saves the graphics state of the rendering engine. * @throws IOException if an I/O error occurs */ protected void saveGraphicsState() throws IOException { getPaintingState().save(); } /** * Restores the last graphics state of the rendering engine. * @throws IOException if an I/O error occurs */ protected void restoreGraphicsState() throws IOException { getPaintingState().restore(); } /** {@inheritDoc} */ public void clipBackground(Rectangle rect, BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) throws IFException { } /** {@inheritDoc} */ public boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) { return borderPainter.isBackgroundRequired(bpsBefore, bpsAfter, bpsStart, bpsEnd); } /** {@inheritDoc} */ public void fillBackground(Rectangle rect, Paint fill, BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) throws IFException { // not supported in AFP } }