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
        BufferedImage bi = null;
        if (imageRef != null) {
            bi = (BufferedImage) imageRef.get();
            if (bi == null) {
                System.out.println("Image went away.  Stopping");
                return Watchable.STOPPED;
            }

            g = (Graphics2D) bi.createGraphics();
        }

        // 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 are based on a reference to a graphics, don't hold on to it
        // since that will prevent the image from being collected.
        if (imageRef != null) {
            g = null;
        }

        // if we need to stop, it will be caught at the start of the next
        // iteration.
        return Watchable.RUNNING;
    }