| /* |
| * 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 |
| } |
| } |