/* * Copyright (c) 2013 Allogy Interactive. * * Licensed 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. */ package com.hsl.txtreader; import java.lang.ref.WeakReference; import java.util.List; import java.util.Stack; import android.graphics.Bitmap; import android.graphics.Paint.Cap; import android.graphics.Paint.Join; import android.graphics.drawable.shapes.Shape; import com.sun.pdfview.BaseWatchable; import com.sun.pdfview.ImageInfo; import com.sun.pdfview.PDFCmd; import com.sun.pdfview.PDFParseException; import com.sun.pdfview.Watchable; /** * This class turns a set of PDF Commands from a PDF page into an image. It * encapsulates the state of drawing in terms of stroke, fill, transform, * etc., as well as pushing and popping these states. * * When the run method is called, this class goes through all remaining commands * in the PDF Page and draws them to its buffered image. It then updates any * ImageConsumers with the drawn data. */ public class PDFRenderer extends BaseWatchable implements Runnable { /** the page we were generate from */ private PDFPage page; /** where we are in the page's command list */ private int currentCommand; /** a weak reference to the image we render into. For the image * to remain available, some other code must retain a strong reference to it. */ private WeakReference imageRef; /** the graphics object for use within an iteration. Note this must be * set to null at the end of each iteration, or the image will not be * collected */ private Graphics2D g; /** the current graphics state */ private GraphicsState state; /** the stack of push()ed graphics states */ private Stack<GraphicsState> stack; /** the total region of this image that has been written to */ private Rectangle2D globalDirtyRegion; /** the image observers that will be updated when this image changes */ private List<ImageObserver> observers; /** the last shape we drew (to check for overlaps) */ private GeneralPath lastShape; /** the info about the image, if we need to recreate it */ private ImageInfo imageinfo; /** the next time the image should be notified about updates */ private long then = 0; /** the sum of all the individual dirty regions since the last update */ private Rectangle2D unupdatedRegion; protected StringBuffer strBuffer; /** how long (in milliseconds) to wait between image updates */ public static final long UPDATE_DURATION = 200; public static final float NOPHASE = -1000; public static final float NOWIDTH = -1000; public static final float NOLIMIT = -1000; public static final Cap NOCAP = Cap.BUTT; public static final float[] NODASH = null; public static final Join NOJOIN = Join.MITER; /** * create a new PDFGraphics state * @param page the current page * @param imageinfo the paramters of the image to render */ public PDFRenderer(PDFPage page, ImageInfo imageinfo, StringBuffer sb) { super(); this.page = page; this.imageinfo = imageinfo; this.strBuffer = sb; } /** * create a new PDFGraphics state, given a Graphics2D. This version * will <b>not</b> create an image, and you will get a NullPointerException * if you attempt to call getImage(). * @param page the current page * @param g the Graphics2D object to use for drawing * @param imgbounds the bounds of the image into which to fit the page * @param clip the portion of the page to draw, in page space, or null * if the whole page should be drawn * @param bgColor the color to draw the background of the image, or * null for no color (0 alpha value) */ public PDFRenderer(PDFPage page, Graphics2D g, Rectangle imgbounds, Rectangle2D clip, int bgColor) { super(); this.page = page; this.g = g; this.imageinfo = new ImageInfo((int) imgbounds.width, (int) imgbounds.height, clip, bgColor); g.translate(imgbounds.x, imgbounds.y); } public void appendString(StringBuffer sb) { strBuffer.append(sb); } /** * Set up the graphics transform to match the clip region * to the image size. */ private void setupRendering(Graphics2D g) { } /** * push the current graphics state onto the stack. Continue working * with the current object; calling pop() restores the state of this * object to its state when push() was called. */ public void push() { // state.cliprgn = g.getClip(); stack.push(state); state = (GraphicsState) state.clone(); } /** * restore the state of this object to what it was when the previous * push() was called. */ public void pop() { } /** * draw an outline using the current stroke and draw paint * @param s the path to stroke * @return a Rectangle2D to which the current region being * drawn will be added. May also be null, in which case no dirty * region will be recorded. */ public Rectangle2D stroke(GeneralPath s) { /* g.setComposite(state.strokeAlpha); s = new GeneralPath(autoAdjustStrokeWidth(g, state.stroke).createStrokedShape(s)); return state.strokePaint.fill(this, g, s); */ return null; } /** * auto adjust the stroke width, according to 6.5.4, which presumes that * the device characteristics (an image) require a single pixel wide * line, even if the width is set to less. We determine the scaling to * see if we would produce a line that was too small, and if so, scale * it up to produce a graphics line of 1 pixel, or so. This matches our * output with Adobe Reader. * * @param g * @param bs * @return */ private BasicStroke autoAdjustStrokeWidth(Graphics2D g, BasicStroke bs) { return null; } /** * draw an outline. * @param p the path to draw * @param bs the stroke with which to draw the path */ public void draw(GeneralPath p, BasicStroke bs) { } /** * fill an outline using the current fill paint * @param s the path to fill */ public Rectangle2D fill(GeneralPath s) { return null; } /** * draw an image. * @param image the image to draw */ public Rectangle2D drawImage(PDFImage image) { return null; } /** * add the path to the current clip. The new clip will be the intersection * of the old clip and given path. */ public void clip(GeneralPath s) { } /** * set the clip to be the given shape. The current clip is not taken * into account. */ private void setClip(Shape s) { } /** * get the current affinetransform */ public AffineTransform getTransform() { return null; } /** * concatenate the given transform with the current transform */ public void transform(AffineTransform at) { } /** * replace the current transform with the given one. */ public void setTransform(AffineTransform at) { } /** * get the initial transform from page space to Java space */ public AffineTransform getInitialTransform() { return null; } /** * Set some or all aspects of the current stroke. * @param w the width of the stroke, or NOWIDTH to leave it unchanged * @param cap the end cap style, or NOCAP to leave it unchanged * @param join the join style, or NOJOIN to leave it unchanged * @param limit the miter limit, or NOLIMIT to leave it unchanged * @param phase the phase of the dash array, or NOPHASE to leave it * unchanged * @param ary the dash array, or null to leave it unchanged. phase * and ary must both be valid, or phase must be NOPHASE while ary is null. */ public void setStrokeParts(float w, Cap cap, Join join, float limit, float[] ary, float phase) { } /** * get the current stroke as a BasicStroke */ public BasicStroke getStroke() { return null; } /** * set the current stroke as a BasicStroke */ public void setStroke(BasicStroke bs) { } /** * set the stroke color */ public void setStrokePaint(PDFPaint paint) { } /** * set the fill color */ public void setFillPaint(PDFPaint paint) { } /** * set the stroke alpha */ public void setStrokeAlpha(float alpha) { } /** * set the stroke alpha */ public void setFillAlpha(float alpha) { } /** * Add an image observer */ public void addObserver(ImageObserver observer) { } /** * Remove an image observer */ public void removeObserver(ImageObserver observer) { synchronized (observers) { observers.remove(observer); } } /** * Set the last shape drawn */ public void setLastShape(GeneralPath shape) { this.lastShape = shape; } /** * Get the last shape drawn */ public GeneralPath getLastShape() { return lastShape; } /** * Setup rendering. Called before iteration begins */ @Override public void setup() { } /** * Draws the next command in the PDFPage to the buffered image. * The image will be notified about changes no less than every * UPDATE_DURATION milliseconds. * * @return <ul><li>Watchable.RUNNING when there are commands to be processed * <li>Watchable.NEEDS_DATA when there are no commands to be * processed, but the page is not yet complete * <li>Watchable.COMPLETED when the page is done and all * the commands have been processed * <li>Watchable.STOPPED if the image we are rendering into * has gone away * </ul> */ public int iterate() throws Exception { // make sure we have a page to render if (page == null) { return Watchable.COMPLETED; } // check if this renderer is based on a weak reference to a graphics // object. If it is, and the graphics is no longer valid, then just quit Bitmap bi = null; // check if there are any commands to parse. If there aren't, // just return, but check if we'return really finished or not if (currentCommand >= page.getCommandCount()) { if (page.isFinished()) { return Watchable.COMPLETED; } else { return Watchable.NEEDS_DATA; } } // find the current command PDFCmd cmd = page.getCommand(currentCommand++); if (cmd == null) { // uh oh. Synchronization problem! throw new PDFParseException("Command not found!"); } // execute the command // Rectangle2D dirtyRegion = cmd.execute(this); // append to the global dirty region // globalDirtyRegion = addDirtyRegion(dirtyRegion, globalDirtyRegion); // unupdatedRegion = addDirtyRegion(dirtyRegion, unupdatedRegion); long now = System.currentTimeMillis(); if (now > then || rendererFinished()) { // now tell any observers, so they can repaint notifyObservers(bi, unupdatedRegion); unupdatedRegion = null; then = now + UPDATE_DURATION; } // if we need to stop, it will be caught at the start of the next // iteration. return Watchable.RUNNING; } /** * Called when iteration has stopped */ @Override public void cleanup() { page = null; state = null; stack = null; globalDirtyRegion = null; lastShape = null; // keep around the image ref and image info for use in // late addObserver() call } /** * Append a rectangle to the total dirty region of this shape */ private Rectangle2D addDirtyRegion(Rectangle2D region, Rectangle2D glob) { if (region == null) { return glob; } else if (glob == null) { return region; } else { // Rectangle2D.union(glob, region, glob); return glob; } } /** * Determine if we are finished */ private boolean rendererFinished() { if (page == null) { return true; } return (page.isFinished() && currentCommand == page.getCommandCount()); } /** * Notify the observer that a region of the image has changed */ private void notifyObservers(Bitmap bi, Rectangle2D region) { } class GraphicsState implements Cloneable { /** Clone this Graphics state. * * Note that cliprgn is not cloned. It must be set manually from * the current graphics object's clip */ @Override public Object clone() { return null; } } }