package com.tom_roush.pdfbox.rendering; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.Region; import android.util.Log; import com.tom_roush.pdfbox.contentstream.PDFGraphicsStreamEngine; import com.tom_roush.pdfbox.cos.COSName; import com.tom_roush.pdfbox.pdmodel.common.PDRectangle; import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType0; import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType2; import com.tom_roush.pdfbox.pdmodel.font.PDFont; import com.tom_roush.pdfbox.pdmodel.font.PDTrueTypeFont; import com.tom_roush.pdfbox.pdmodel.font.PDType0Font; import com.tom_roush.pdfbox.pdmodel.font.PDType1CFont; import com.tom_roush.pdfbox.pdmodel.font.PDType1Font; import com.tom_roush.pdfbox.pdmodel.graphics.PDLineDashPattern; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor; import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace; import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject; import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImage; import com.tom_roush.pdfbox.pdmodel.graphics.shading.PDShading; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDGraphicsState; import com.tom_roush.pdfbox.pdmodel.graphics.state.PDSoftMask; import com.tom_roush.pdfbox.pdmodel.graphics.state.RenderingMode; import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import com.tom_roush.pdfbox.util.Matrix; import com.tom_roush.pdfbox.util.Vector; import com.tom_roush.pdfbox.util.awt.AffineTransform; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * Paints a page in a PDF document to a Canvas context. May be subclassed to provide custom * rendering. * * <p>If you want to do custom graphics processing rather than Canvas rendering, then you should * subclass PDFGraphicsStreamEngine instead. Subclassing PageDrawer is only suitable for cases * where the goal is to render onto a Canvas surface. * * @author Ben Litchfield */ public class PageDrawer extends PDFGraphicsStreamEngine { // parent document renderer - note: this is needed for not-yet-implemented resource caching private final PDFRenderer renderer; // the graphics device to draw to, xform is the initial transform of the device (i.e. DPI) Paint paint; Canvas canvas; private AffineTransform xform; // the page box to draw (usually the crop box but may be another) private PDRectangle pageSize; // clipping winding rule used for the clipping path private Path.FillType clipWindingRule = null; private Path linePath = new Path(); // last clipping path private Region lastClip; // buffered clipping area for text being drawn private Region textClippingArea; // glyph cache private final Map<PDFont, Glyph2D> fontGlyph2D = new HashMap<PDFont, Glyph2D>(); /** * Constructor. * * @param parameters Parameters for page drawing. * @throws IOException If there is an error loading properties from the file. */ public PageDrawer(PageDrawerParameters parameters) throws IOException { super(parameters.getPage()); this.renderer = parameters.getRenderer(); } /** * Returns the parent renderer. */ public final PDFRenderer getRenderer() { return renderer; } /** * Returns the underlying Canvas. May be null if drawPage has not yet been called. */ protected final Canvas getCanvas() { return canvas; } /** * Returns the current line path. This is reset to empty after each fill/stroke. */ protected final Path getLinePath() { return linePath; } /** * Sets high-quality rendering hints on the current Graphics2D. */ private void setRenderingHints() { // graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // RenderingHints.VALUE_INTERPOLATION_BICUBIC); // graphics.setRenderingHint(RenderingHints.KEY_RENDERING, // RenderingHints.VALUE_RENDER_QUALITY); paint.setAntiAlias(true); } /** * Draws the page to the requested canvas. * * @param p The paint. * @param c The canvas to draw onto. * @param pageSize The size of the page to draw. * @throws IOException If there is an IO error while drawing the page. */ public void drawPage(Paint p, Canvas c, PDRectangle pageSize) throws IOException { paint = p; canvas = c; xform = new AffineTransform(canvas.getMatrix()); this.pageSize = pageSize; setRenderingHints(); canvas.translate(0, pageSize.getHeight()); canvas.scale(1, -1); paint.setStrokeCap(Paint.Cap.BUTT); paint.setStrokeJoin(Paint.Join.MITER); paint.setStrokeWidth(1.0f); // adjust for non-(0,0) crop box canvas.translate(-pageSize.getLowerLeftX(), -pageSize.getLowerLeftY()); processPage(getPage()); for (PDAnnotation annotation : getPage().getAnnotations()) { showAnnotation(annotation); } // graphics = null; } /** * Draws the pattern stream to the requested context. * * @param g The graphics context to draw onto. * @param pattern The tiling pattern to be used. * @param colorSpace color space for this tiling. * @param color color for this tiling. * @param patternMatrix the pattern matrix * @throws IOException If there is an IO error while drawing the page. */ // void drawTilingPattern(Graphics2D g, PDTilingPattern pattern, PDColorSpace colorSpace, // PDColor color, Matrix patternMatrix) throws IOException // { // Graphics2D oldGraphics = graphics; // graphics = g; // // Path oldLinePath = linePath; // linePath = new GeneralPath(); // // Area oldLastClip = lastClip; // lastClip = null; // // setRenderingHints(); // processTilingPattern(pattern, color, colorSpace, patternMatrix); // // graphics = oldGraphics; // linePath = oldLinePath; // lastClip = oldLastClip; // } TODO: PdfBox-Android /** * Returns an AWT paint for the given PDColor. */ // protected Paint getPaint(PDColor color) throws IOException // { // PDColorSpace colorSpace = color.getColorSpace(); // if (!(colorSpace instanceof PDPattern)) // { // float[] rgb = colorSpace.toRGB(color.getComponents()); // return new Color(rgb[0], rgb[1], rgb[2]); // } // else // { // PDPattern patternSpace = (PDPattern)colorSpace; // PDAbstractPattern pattern = patternSpace.getPattern(color); // if (pattern instanceof PDTilingPattern) // { // PDTilingPattern tilingPattern = (PDTilingPattern) pattern; // // if (tilingPattern.getPaintType() == PDTilingPattern.PAINT_COLORED) // { // // colored tiling pattern // return new TilingPaint(this, tilingPattern, xform); // } // else // { // // uncolored tiling pattern // return new TilingPaint(this, tilingPattern, // patternSpace.getUnderlyingColorSpace(), color, xform); // } // } // else // { // PDShadingPattern shadingPattern = (PDShadingPattern)pattern; // PDShading shading = shadingPattern.getShading(); // if (shading == null) // { // LOG.error("shadingPattern is null, will be filled with transparency"); // return new Color(0,0,0,0); // } // return shading.toPaint(Matrix.concatenate(getInitialMatrix(), // shadingPattern.getMatrix())); // } // } // } TODO: PdfBox-Android // returns an integer for color that Android understands from the PDColor // TODO: alpha? private int getColor(PDColor color) throws IOException { PDColorSpace colorSpace = color.getColorSpace(); float[] floats = colorSpace.toRGB(color.getComponents()); int r = Math.round(floats[0] * 255); int g = Math.round(floats[1] * 255); int b = Math.round(floats[2] * 255); return Color.rgb(r, g, b); } // sets the clipping path using caching for performance, we track lastClip manually because // Graphics2D#getClip() returns a new object instead of the same one passed to setClip private void setClip() { Region clippingPath = getGraphicsState().getCurrentClippingPath(); if (clippingPath != lastClip) { // graphics.setClip(clippingPath); canvas.clipRegion(clippingPath); lastClip = clippingPath; } } @Override public void beginText() throws IOException { setClip(); beginTextClip(); } @Override public void endText() throws IOException { endTextClip(); } /** * Begin buffering the text clipping path, if any. */ private void beginTextClip() { // buffer the text clip because it represents a single clipping area textClippingArea = new Region(); } /** * End buffering the text clipping path, if any. */ private void endTextClip() { PDGraphicsState state = getGraphicsState(); RenderingMode renderingMode = state.getTextState().getRenderingMode(); // apply the buffered clip as one area if (renderingMode.isClip() && !textClippingArea.isEmpty()) { state.intersectClippingPath(textClippingArea); textClippingArea = null; } } @Override protected void showFontGlyph(Matrix textRenderingMatrix, PDFont font, int code, String unicode, Vector displacement) throws IOException { AffineTransform at = textRenderingMatrix.createAffineTransform(); at.concatenate(font.getFontMatrix().createAffineTransform()); Glyph2D glyph2D = createGlyph2D(font); drawGlyph2D(glyph2D, font, code, displacement, at); } /** * Render the font using the Glyph2D interface. * * @param glyph2D the Glyph2D implementation provided a GeneralPath for each glyph * @param font the font * @param code character code * @param displacement the glyph's displacement (advance) * @param at the transformation * @throws IOException if something went wrong */ private void drawGlyph2D(Glyph2D glyph2D, PDFont font, int code, Vector displacement, AffineTransform at) throws IOException { PDGraphicsState state = getGraphicsState(); RenderingMode renderingMode = state.getTextState().getRenderingMode(); Path path = glyph2D.getPathForCharacterCode(code); if (path != null) { // stretch non-embedded glyph if it does not match the width contained in the PDF if (!font.isEmbedded()) { float fontWidth = font.getWidthFromFont(code); if (fontWidth > 0 && // ignore spaces Math.abs(fontWidth - displacement.getX() * 1000) > 0.0001) { float pdfWidth = displacement.getX() * 1000; at.scale(pdfWidth / fontWidth, 1); } } // render glyph // Shape glyph = at.createTransformedShape(path); path.transform(at.toMatrix()); if (renderingMode.isFill()) { // graphics.setComposite(state.getNonStrokingJavaComposite()); // graphics.setPaint(getNonStrokingPaint()); paint.setColor(getNonStrokingColor()); setClip(); // graphics.fill(glyph); paint.setStyle(Paint.Style.FILL); canvas.drawPath(path, paint); // canvas.clipPath(path); // canvas.drawRect(new Rect(0, 0, canvas.getWidth(), canvas.getHeight()), paint); } if (renderingMode.isStroke()) { // graphics.setComposite(state.getStrokingJavaComposite()); // graphics.setPaint(getStrokingPaint()); paint.setColor(getStrokingColor()); // graphics.setStroke(getStroke()); setClip(); // graphics.draw(glyph); paint.setStyle(Paint.Style.STROKE); canvas.drawPath(path, paint); // canvas.clipPath(path); // canvas.drawRect(new Rect(0, 0, canvas.getWidth(), canvas.getHeight()), paint); } if (renderingMode.isClip()) { // textClippingArea.add(new Area(glyph)); } } } /** * Provide a Glyph2D for the given font. * * @param font the font * @return the implementation of the Glyph2D interface for the given font * @throws IOException if something went wrong */ private Glyph2D createGlyph2D(PDFont font) throws IOException { // Is there already a Glyph2D for the given font? if (fontGlyph2D.containsKey(font)) { return fontGlyph2D.get(font); } Glyph2D glyph2D = null; if (font instanceof PDTrueTypeFont) { PDTrueTypeFont ttfFont = (PDTrueTypeFont)font; glyph2D = new TTFGlyph2D(ttfFont); // TTF is never null } else if (font instanceof PDType1Font) { PDType1Font pdType1Font = (PDType1Font)font; glyph2D = new Type1Glyph2D(pdType1Font); // T1 is never null } else if (font instanceof PDType1CFont) { PDType1CFont type1CFont = (PDType1CFont)font; glyph2D = new Type1Glyph2D(type1CFont); } else if (font instanceof PDType0Font) { PDType0Font type0Font = (PDType0Font) font; if (type0Font.getDescendantFont() instanceof PDCIDFontType2) { glyph2D = new TTFGlyph2D(type0Font); // TTF is never null } else if (type0Font.getDescendantFont() instanceof PDCIDFontType0) { // a Type0 CIDFont contains CFF font PDCIDFontType0 cidType0Font = (PDCIDFontType0)type0Font.getDescendantFont(); glyph2D = new CIDType0Glyph2D(cidType0Font); // todo: could be null (need incorporate fallback) } } else { throw new IllegalStateException("Bad font type: " + font.getClass().getSimpleName()); } // cache the Glyph2D instance if (glyph2D != null) { // fontGlyph2D.put(font, glyph2D); TODO: use caching } if (glyph2D == null) { // todo: make sure this never happens throw new UnsupportedOperationException("No font for " + font.getName()); } return glyph2D; } @Override public void appendRectangle(PointF p0, PointF p1, PointF p2, PointF p3) { // to ensure that the path is created in the right direction, we have to create // it by combining single lines instead of creating a simple rectangle linePath.moveTo(p0.x, p0.y); linePath.lineTo(p1.x, p1.y); linePath.lineTo(p2.x, p2.y); linePath.lineTo(p3.x, p3.y); // close the subpath instead of adding the last line so that a possible set line // cap style isn't taken into account at the "beginning" of the rectangle linePath.close(); } /** * Generates AWT raster for a soft mask * * @param softMask soft mask * @return AWT raster for soft mask * @throws IOException */ // private Raster createSoftMaskRaster(PDSoftMask softMask) throws IOException // { // TransparencyGroup transparencyGroup = new TransparencyGroup(softMask.getGroup(), true); // COSName subtype = softMask.getSubType(); // if (COSName.ALPHA.equals(subtype)) // { // return transparencyGroup.getAlphaRaster(); // } // else if (COSName.LUMINOSITY.equals(subtype)) // { // return transparencyGroup.getLuminosityRaster(); // } // else // { // throw new IOException("Invalid soft mask subtype."); // } // } TODO: PdfBox-Android // private Paint applySoftMaskToPaint(Paint parentPaint, PDSoftMask softMask) throws IOException // { // if (softMask != null) // { // //TODO PDFBOX-2934 // if (COSName.ALPHA.equals(softMask.getSubType())) // { // Log.i("PdfBox-Android", "alpha smask not implemented yet, is ignored"); // return parentPaint; // } // return new SoftMaskPaint(parentPaint, createSoftMaskRaster(softMask)); // } // else // { // return parentPaint; // } // } TODO: PdfBox-Android // returns the stroking AWT Paint // private Paint getStrokingPaint() throws IOException // { // return applySoftMaskToPaint( // getPaint(getGraphicsState().getStrokingColor()), // getGraphicsState().getSoftMask()); // } TODO: PdfBox-Android private int getStrokingColor() throws IOException { return getColor(getGraphicsState().getStrokingColor()); } // returns the non-stroking AWT Paint // private Paint getNonStrokingPaint() throws IOException // { // return getPaint(getGraphicsState().getNonStrokingColor()); // } TODO: PdfBox-Android private int getNonStrokingColor() throws IOException { return getColor(getGraphicsState().getNonStrokingColor()); } // set stroke based on the current CTM and the current stroke private void setStroke() { PDGraphicsState state = getGraphicsState(); // apply the CTM float lineWidth = transformWidth(state.getLineWidth()); // minimum line width as used by Adobe Reader if (lineWidth < 0.25) { lineWidth = 0.25f; } PDLineDashPattern dashPattern = state.getLineDashPattern(); int phaseStart = dashPattern.getPhase(); float[] dashArray = dashPattern.getDashArray(); if (dashArray != null) { // apply the CTM for (int i = 0; i < dashArray.length; ++i) { // minimum line dash width avoids JVM crash, see PDFBOX-2373 float w = transformWidth(dashArray[i]); if (w != 0) { dashArray[i] = Math.max(w, 0.016f); } } phaseStart = (int) transformWidth(phaseStart); // empty dash array is illegal if (dashArray.length == 0) { dashArray = null; } } paint.setStrokeWidth(lineWidth); paint.setStrokeCap(state.getLineCap()); paint.setStrokeJoin(state.getLineJoin()); paint.setPathEffect(new DashPathEffect(dashArray, phaseStart)); } @Override public void strokePath() throws IOException { // graphics.setComposite(getGraphicsState().getStrokingJavaComposite()); setStroke(); setClip(); paint.setARGB(255, 0, 0, 0); // TODO set the correct color from graphics state. paint.setStyle(Paint.Style.STROKE); paint.setColor(getStrokingColor()); setClip(); canvas.drawPath(linePath, paint); linePath.reset(); } @Override public void fillPath(Path.FillType windingRule) throws IOException { // graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); paint.setColor(getNonStrokingColor()); setClip(); linePath.setFillType(windingRule); // disable anti-aliasing for rectangular paths, this is a workaround to avoid small stripes // which occur when solid fills are used to simulate piecewise gradients, see PDFBOX-2302 // note that we ignore paths with a width/height under 1 as these are fills used as strokes, // see PDFBOX-1658 for an example // RectF bounds = new RectF((); // linePath.computeBounds(bounds, true); // boolean noAntiAlias = isRectangular(linePath) && bounds.width() > 1 && bounds.height() > 1; // if (noAntiAlias) { // graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_OFF); } // TODO: PdfBox-Android: Commit 7f5861f9559e30ad68f0bcbce5b9dd2e7c2621b5 ? paint.setStyle(Paint.Style.FILL); canvas.drawPath(linePath, paint); linePath.reset(); // if (noAntiAlias) { // JDK 1.7 has a bug where rendering hints are reset by the above call to // the setRenderingHint method, so we re-set all hints, see PDFBOX-2302 setRenderingHints(); } } /** * Returns true if the given path is rectangular. */ // private boolean isRectangular(GeneralPath path) // { // PathIterator iter = path.getPathIterator(null); // double[] coords = new double[6]; // int count = 0; // int[] xs = new int[4]; // int[] ys = new int[4]; // while (!iter.isDone()) // { // switch(iter.currentSegment(coords)) // { // case PathIterator.SEG_MOVETO: // if (count == 0) // { // xs[count] = (int)Math.floor(coords[0]); // ys[count] = (int)Math.floor(coords[1]); // } // else // { // return false; // } // count++; // break; // // case PathIterator.SEG_LINETO: // if (count < 4) // { // xs[count] = (int)Math.floor(coords[0]); // ys[count] = (int)Math.floor(coords[1]); // } // else // { // return false; // } // count++; // break; // // case PathIterator.SEG_CUBICTO: // return false; // // case PathIterator.SEG_CLOSE: // break; // } // iter.next(); // } // // if (count == 4) // { // return xs[0] == xs[1] || xs[0] == xs[2] || // ys[0] == ys[1] || ys[0] == ys[3]; // } // return false; // } TODO: PdfBox-Android /** * Fills and then strokes the path. * * @param windingRule The winding rule this path will use. * @throws IOException If there is an IO error while filling the path. */ @Override public void fillAndStrokePath(Path.FillType windingRule) throws IOException { // TODO can we avoid cloning the path? Path path = new Path(linePath); fillPath(windingRule); linePath = path; strokePath(); } @Override public void clip(Path.FillType windingRule) { // the clipping path will not be updated until the succeeding painting operator is called clipWindingRule = windingRule; } @Override public void moveTo(float x, float y) { linePath.moveTo(x, y); } @Override public void lineTo(float x, float y) { linePath.lineTo(x, y); } @Override public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) { linePath.cubicTo(x1, y1, x2, y2, x3, y3); // TODO: check if this should be relative } @Override public PointF getCurrentPoint() { Log.d("PdfBox-Android", "PageDrawer.getCurrentPoint does not return the right value"); return new PointF(); // return linePath.getCurrentPoint(); } @Override public void closePath() { linePath.close(); } @Override public void endPath() { // if (clipWindingRule != -1) // { // linePath.setWindingRule(clipWindingRule); // getGraphicsState().intersectClippingPath(linePath); // clipWindingRule = -1; // } linePath.reset(); } @Override public void drawImage(PDImage pdImage) throws IOException { com.tom_roush.pdfbox.util.Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); AffineTransform at = ctm.createAffineTransform(); if (!pdImage.getInterpolate()) { boolean isScaledUp = pdImage.getWidth() < Math.round(at.getScaleX()) || pdImage.getHeight() < Math.round(at.getScaleY()); // if the image is scaled down, we use smooth interpolation, eg PDFBOX-2364 // only when scaled up do we use nearest neighbour, eg PDFBOX-2302 / mori-cvpr01.pdf // stencils are excluded from this rule (see survey.pdf) if (isScaledUp || pdImage.isStencil()) { // graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); } } if (pdImage.isStencil()) { // fill the image with paint // Bitmap image = pdImage.getStencilImage(getNonStrokingPaint()); // draw the image // drawBufferedImage(image, at); } else { // draw the image drawBufferedImage(pdImage.getImage(), at); } if (!pdImage.getInterpolate()) { // JDK 1.7 has a bug where rendering hints are reset by the above call to // the setRenderingHint method, so we re-set all hints, see PDFBOX-2302 setRenderingHints(); } } private void drawBufferedImage(Bitmap image, AffineTransform at) throws IOException { // graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); setClip(); PDSoftMask softMask = getGraphicsState().getSoftMask(); if( softMask != null ) { AffineTransform imageTransform = new AffineTransform(at); imageTransform.scale(1, -1); imageTransform.translate(0, -1); // Paint awtPaint = new TexturePaint(image, // new Rectangle2D.Double(imageTransform.getTranslateX(), imageTransform.getTranslateY(), // imageTransform.getScaleX(), imageTransform.getScaleY())); // awtPaint = applySoftMaskToPaint(awtPaint, softMask); // graphics.setPaint(awtPaint); RectF unitRect = new RectF(0, 0, 1, 1); // graphics.fill(at.createTransformedShape(unitRect)); } else { int width = image.getWidth(); int height = image.getHeight(); AffineTransform imageTransform = new AffineTransform(at); imageTransform.scale((1.0f / width), (-1.0f / height)); imageTransform.translate(0, -height); canvas.drawBitmap(image, imageTransform.toMatrix(), paint); } } @Override public void shadingFill(COSName shadingName) throws IOException { PDShading shading = getResources().getShading(shadingName); com.tom_roush.pdfbox.util.Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); // Paint paint = shading.toPaint(ctm); // graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); // graphics.setPaint(paint); // graphics.setClip(null); // lastClip = null; // graphics.fill(getGraphicsState().getCurrentClippingPath()); } @Override public void showAnnotation(PDAnnotation annotation) throws IOException { // lastClip = null; //TODO support more annotation flags (Invisible, NoZoom, NoRotate) // int deviceType = graphics.getDeviceConfiguration().getDevice().getType(); // if (deviceType == GraphicsDevice.TYPE_PRINTER && !annotation.isPrinted()) // { // return; // } Shouldn't be needed if (/*deviceType == GraphicsDevice.TYPE_RASTER_SCREEN && */annotation.isNoView()) { return; } if (annotation.isHidden()) { return; } super.showAnnotation(annotation); } @Override public void showTransparencyGroup(PDFormXObject form) throws IOException { TransparencyGroup group = new TransparencyGroup(form, false); // graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); setClip(); // both the DPI xform and the CTM were already applied to the group, so all we do // here is draw it directly onto the Graphics2D device at the appropriate position // PDRectangle bbox = group.getBBox(); // AffineTransform prev = graphics.getTransform(); // float x = bbox.getLowerLeftX(); // float y = pageSize.getHeight() - bbox.getLowerLeftY() - bbox.getHeight(); // graphics.setTransform(AffineTransform.getTranslateInstance(x * xform.getScaleX(), // y * xform.getScaleY())); PDSoftMask softMask = getGraphicsState().getSoftMask(); if (softMask != null) { // Bitmap image = group.getImage(); // Paint awtPaint = new TexturePaint(image, // new Rectangle2D.Float(0, 0, image.getWidth(), image.getHeight())); // awtPaint = applySoftMaskToPaint(awtPaint, softMask); // todo: PDFBOX-994 problem here? // graphics.setPaint(awtPaint); // graphics.fill(new Rectangle2D.Float(0, 0, bbox.getWidth() * (float)xform.getScaleX(), // bbox.getHeight() * (float)xform.getScaleY())); } else { // graphics.drawImage(group.getImage(), null, null); } // graphics.setTransform(prev); } /** * Transparency group. **/ private final class TransparencyGroup { // private final Bitmap image; // private final PDRectangle bbox; // private final int minX; // private final int minY; // private final int width; // private final int height; TODO: PdfBox-Android /** * Creates a buffered image for a transparency group result. */ private TransparencyGroup(PDFormXObject form, boolean isSoftMask) throws IOException { // Graphics2D g2dOriginal = graphics; // Area lastClipOriginal = lastClip; // get the CTM x Form Matrix transform // Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); // Matrix transform = Matrix.concatenate(ctm, form.getMatrix()); // transform the bbox // Path transformedBox = form.getBBox().transform(transform); // clip the bbox to prevent giant bboxes from consuming all memory // Area clip = (Area)getGraphicsState().getCurrentClippingPath().clone(); // clip.intersect(new Area(transformedBox)); // Rectangle2D clipRect = clip.getBounds2D(); // this.bbox = new PDRectangle((float)clipRect.getX(), (float)clipRect.getY(), // (float)clipRect.getWidth(), (float)clipRect.getHeight()); // apply the underlying Graphics2D device's DPI transform // Shape deviceClip = xform.createTransformedShape(clip); // Rectangle2D bounds = deviceClip.getBounds2D(); // minX = (int) Math.floor(bounds.getMinX()); // minY = (int) Math.floor(bounds.getMinY()); // int maxX = (int) Math.floor(bounds.getMaxX()) + 1; // int maxY = (int) Math.floor(bounds.getMaxY()) + 1; // width = maxX - minX; // height = maxY - minY; // image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); // FIXME - color space // Graphics2D g = image.createGraphics(); // flip y-axis // g.translate(0, height); // g.scale(1, -1); // apply device transform (DPI) // g.transform(xform); // adjust the origin // g.translate(-clipRect.getX(), -clipRect.getY()); // graphics = g; try { if (isSoftMask) { // processSoftMask(form); } else { processTransparencyGroup(form); } } finally { // lastClip = lastClipOriginal; // graphics.dispose(); // graphics = g2dOriginal; } } // public Bitmap getImage() // { // return image; // } // public PDRectangle getBBox() // { // return bbox; // } // public Raster getAlphaRaster() // { // return image.getAlphaRaster(); // } // public Raster getLuminosityRaster() // { // BufferedImage gray = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); // Graphics g = gray.getGraphics(); // g.drawImage(image, 0, 0, null); // g.dispose(); // // return gray.getRaster(); // } } }