Example #1
0
/**
 * Área de dibujo del SVG.
 *
 * @author jpresa
 */
public class SVGViewer extends Canvas implements ImageConsumer, ImageLoader, Runnable, EventTarget {

  private static Logger logger = (Logger) Logger.getInstance(SVGViewer.class);

  protected int width, height;

  /** Imagen de fondo */
  protected Image backImage;

  /** Imagen para dibujar el SVG */
  private Image svgImage;

  /** Imagen para dibujar todo en segundo plano */
  private Image offscreenImage;

  /** The SVG renderer */
  protected SVGRaster raster;

  private ImageProducer imageProducer;

  /** Thread procesador de eventos */
  private Thread thread;

  /** Cola de eventos */
  protected SVGEventQueue eventQueue;

  /** Listeners de eventos */
  private TinyVector listeners;

  /** Manejador de eventos del raton */
  private MouseHandler mouseHandler;

  /** Cache de imagenes */
  private Hashtable imageCash;
  /** URL base de la imagen */
  protected URL baseURL;

  /** Documento SVG pendiente de cargar hasta que no se inicialice el componente */
  private URL pendingSVG;

  /** Documento SVG cargado actualmente */
  private URL currentSVG;

  private Vector errorListeners;
  private Vector linkListeners;
  private Vector rasterTransformListeners;
  private Vector rasterUpdateListeners;
  private Vector pointInsertedListeners;
  private Vector loadListeners;
  private Vector drawListeners;

  /** Indica si hay svg cargado */
  private boolean loaded;

  /** Zona actual dibujada en coordenadas SVG */
  protected TinyRect currentZone;

  /** Dibujado sincrono o asincrono */
  private boolean synchPaint;

  /** Flag de antialiasing activo */
  private boolean antialiased;

  ControlListener adapter;

  public SVGViewer(Composite parent, int style, boolean synchPaint) {
    super(parent, style);

    this.synchPaint = synchPaint;

    this.addControlListener(
        new org.eclipse.swt.events.ControlAdapter() {
          public void controlResized(org.eclipse.swt.events.ControlEvent e) {
            viewerResized();
          }
        });

    this.addPaintListener(
        new PaintListener() {
          public void paintControl(PaintEvent e) {
            if (svgImage == null) {
              // raster.setCamera();
              // raster.update();
              // raster.sendPixels();
              drawSVG();
            }

            GC gc = new GC(offscreenImage);

            gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
            gc.fillRectangle(0, 0, width, height);

            drawBackground(gc);

            // Dibujo de la imagen con el SVG
            if (svgImage != null && !svgImage.isDisposed()) {
              gc.drawImage(svgImage, 0, 0);
            }

            drawForeground(gc);
            gc.dispose();

            e.gc.drawImage(offscreenImage, 0, 0);
          }
        });

    // Establece la implementación de ImageLoader para cargar imagenes
    SVGImageElem.setImageLoader(this);

    // Colas de eventos
    eventQueue = new SVGEventQueue();
    listeners = new TinyVector(4);
    // Cache de imagenes
    imageCash = new Hashtable();

    // Listeners eventos sobre el SVG
    errorListeners = new Vector();
    linkListeners = new Vector();
    rasterTransformListeners = new Vector();
    rasterUpdateListeners = new Vector();
    pointInsertedListeners = new Vector();
    loadListeners = new Vector();
    drawListeners = new Vector();

    currentZone = new TinyRect();

    // Manejador de eventos
    mouseHandler = new MouseHandler(this);
    mouseHandler.type = MouseHandler.LINK_MOUSE;
    // NUEVO
    // mouseHandler.setPanType(MouseHandler.PAN_LINE);
    mouseHandler.setPanType(MouseHandler.PAN_MOVE);
    // FIN NUEVO
    addMouseListener(mouseHandler);
    addMouseMoveListener(mouseHandler);

    antialiased = true;

    //		PlayerListener defaultListener = new PlayerListener(this);
    //		this.addEventListener("default", defaultListener, false);
    //		this.start();
  }

  public SVGViewer(Composite parent, int style) {
    this(parent, style, false);
  }

  protected void drawBackground(GC gc) {
    if (backImage != null && !backImage.isDisposed()) gc.drawImage(backImage, 0, 0);
  }

  protected void drawForeground(GC gc) {}

  private void viewerResized() {
    width = getSize().x;
    height = getSize().y;
    logger.debug("Tamaño del viewer: " + width + "x" + height);

    if (svgImage != null && !svgImage.isDisposed()) svgImage.dispose();

    if (offscreenImage != null && !offscreenImage.isDisposed()) {
      offscreenImage.dispose();
    }
    offscreenImage = new Image(getDisplay(), width, height);

    // Crea el raster SVG
    TinyPixbuf buffer = new TinyPixbuf(width, height);
    raster = new SVGRaster(buffer);
    imageProducer = new ImageProducer(raster);
    imageProducer.setConsumer(this);
    raster.setSVGImageProducer(imageProducer);

    // Antialiasing
    raster.setAntialiased(antialiased);

    // Poner fondo transparente
    raster.setBackground(0x00FFFFFF);

    // Avisar al manejador de eventos de que el tamaño ha cambiado
    if (mouseHandler != null) {
      mouseHandler.viewerResized();
    }

    // Cargar el documento pendiente, si existe
    if (pendingSVG != null) {
      loadSVGUrl(pendingSVG);
      pendingSVG = null;
    } else {
      if (currentSVG != null) {
        loadSVGUrl(currentSVG);
      }
    }
  }

  public MouseHandler getMouseHandler() {
    return mouseHandler;
  }

  public boolean isSynchPaint() {
    return synchPaint;
  }

  public void setSynchPaint(boolean synchPaint) {
    this.synchPaint = synchPaint;
  }

  public SVGDocument getSVGDocument() {
    if (raster != null) return raster.document;
    else return null;
  }

  /**
   * Realiza la carga desde una url de una imagen en formato SVGTiny, en este caso no se espera que
   * tenga información basada en capas o layers.
   *
   * @param url
   */
  public void loadSVGUrl(URL url) {
    //		SVGEvent event = new SVGEvent(SVGEvent.EVENT_LOAD, url);
    //		postEvent(event);

    // Si el componente no esta inicializado, el documento se guarda como pendiente de cargar
    if (raster == null) {
      pendingSVG = url;
      return;
    }

    currentSVG = url;

    // 1. Reset the event queue
    eventQueue.reset();
    // 2. Load the document
    SVGDocument document = loadSVG(url);
    if (document == null) return;
    Enumeration e = loadListeners.elements();
    while (e.hasMoreElements()) {
      ((SVGViewerLoadListener) e.nextElement()).documentLoaded(document);
    }
    // 3. Set the loaded document to be current document
    // canvas.currentURL = new String(url);
    raster.setSVGDocument(document);
    // 4. Set the original view
    raster.view.x = raster.origview.x;
    raster.view.y = raster.origview.y;
    raster.view.width = raster.origview.width;
    raster.view.height = raster.origview.height;
    // 5. Set the camera transform
    // raster.setCamera();
    // 6. Draw
    drawSVG();
  }

  /**
   * Loads an SVGT document from the given URL.
   *
   * @param urlStr The SVGT document URL or path.
   * @return An SVGT document.
   */
  protected SVGDocument loadSVG(URL url) {
    InputStream is = null;
    try {
      String completeUrl = url.toExternalForm();
      int p = completeUrl.lastIndexOf('/');
      if (p != -1) {
        baseURL = new URL(completeUrl.substring(0, p + 1));
      }
      is = url.openStream();
      if (url.toString().endsWith("svgz")) {
        is = new GZIPInputStream(is);
      }
    } catch (Exception ex) {
      logger.error(ex);
      // ex.printStackTrace();
      Enumeration en = errorListeners.elements();
      while (en.hasMoreElements()) {
        SVGViewerErrorListener l = (SVGViewerErrorListener) en.nextElement();
        l.error(new SVGViewerErrorEvent(ex));
      }
    }

    return loadSVG(is);
  }

  /**
   * Loads an SVGT document from the given InputStream.
   *
   * @param is The InputStream.
   * @return An SVGT document.
   */
  private SVGDocument loadSVG(InputStream is) {
    loaded = false;
    String errorMessage = null;
    Throwable throwable = null;
    SVGDocument doc = raster.createSVGDocument();
    try {
      // Read and parse the SVGT stream
      TinyPixbuf pixbuf = raster.getPixelBuffer();
      // Create the SVGT attributes parser
      SVGAttr attrParser = new SVGAttr(pixbuf.width, pixbuf.height);
      // Create the SVGT stream parser
      SVGParser parser = new SVGParser(attrParser);
      // Parse the input SVGT stream parser into the document
      int errorCode = parser.load(doc, is);
      errorCode = errorCode >> 10;
      if (errorCode != 0) {
        logger.error("Error al parsear SVG. Código:" + errorCode);
        errorMessage = "Error de sintaxis XML";
      } else {
        if (doc.root.children == null || doc.root.children.data[0] instanceof SVGUnknownElem) {
          errorMessage = "El fichero no es un SVG válido";
        } else {
          // Fichero correcto
          logger.debug("Fichero cargado correctamente");
          loaded = true;
        }
      }
    } catch (OutOfMemoryError memerror) {
      doc = null;
      Runtime.getRuntime().gc();
      logger.error("Not enought memory", memerror);
      errorMessage = "Memoria insuficiente";
      throwable = memerror;
      // memerror.printStackTrace();
    } catch (SecurityException secex) {
      doc = null;
      logger.error("Security violation", secex);
      errorMessage = "Violación de seguridad";
      throwable = secex;
      // secex.printStackTrace();
    } catch (Exception ex) {
      doc = null;
      logger.error("Not in SVGT format", ex);
      errorMessage = "El fichero no está en formato SVG Tiny";
      throwable = ex;
      // ex.printStackTrace();
    } catch (Throwable t) {
      doc = null;
      logger.error("Not in SVGT format", t);
      errorMessage = "El fichero no está en formato SVG Tiny";
      throwable = t;
      // thr.printStackTrace();
    } finally {
      if (errorMessage != null) {
        Enumeration en = errorListeners.elements();
        while (en.hasMoreElements()) {
          SVGViewerErrorListener l = (SVGViewerErrorListener) en.nextElement();
          l.error(new SVGViewerErrorEvent(errorMessage, throwable));
        }
      }
      try {
        if (is != null) is.close();
      } catch (IOException ioe) {
        logger.error(ioe);
        // ioe.printStackTrace();
      }
    }
    return doc;
  }

  /** Carga una imagen en la posición X,Y dada. */
  public void loadImage(int x, int y, int width, int height, String path) {
    //		SVGEvent event = new SVGEvent(SVGEvent.EVENT_LOAD_IMAGE, new SVGLoadImageEventData(x, y,
    // width, height, path));
    //		postEvent(event);

    SVGDocument document = raster.document;
    SVGSVGElem rootElem = (SVGSVGElem) document.root;

    SVGNode selNode = null;
    // Parece que la siguiente linea no es necesaria. La matriz de transformacion es la misma que la
    // del root
    // selNode = rootElem.nodeHitAt(canvas.raster, new TinyPoint(evData.getX(), evData.getY()));
    if (selNode == null) selNode = raster.root;

    // Calculo de la posicion de la imagen en el SVG
    TinyMatrix inverse = selNode.getGlobalTransform().inverse();
    TinyPoint p = new TinyPoint(x << TinyUtil.FIX_BITS, y << TinyUtil.FIX_BITS);
    inverse.transform(p);
    TinyPoint p2 =
        new TinyPoint((x + width) << TinyUtil.FIX_BITS, (y + height) << TinyUtil.FIX_BITS);
    inverse.transform(p2);
    // Nuevo elemento imagen
    SVGImageElem imageElem = (SVGImageElem) document.createElement(SVG.ELEM_IMAGE);
    imageElem.x = p.x;
    imageElem.y = p.y;
    imageElem.width = p2.x - p.x;
    imageElem.height = p2.y - p.y;
    imageElem.xlink_href = new TinyString(path.toCharArray());

    // Insertar la imagen en el root
    rootElem.addChildAndRecordEvent(imageElem, rootElem.children.count);

    drawSVG();
  }

  /**
   * Se encarga de devolver los identificadores de cada uno de los layers cargados.
   *
   * @return
   */
  public String[] getAllIDLayers() {
    SVGDocument document = raster.document;
    int numLayers = document.root.children.count;
    String[] idLayers = new String[numLayers];
    for (int i = 0; i < numLayers; i++) {
      SVGNode layer = (SVGNode) document.root.children.data[i];
      if (layer.id != null) {
        idLayers[i] = new String(layer.id.data);
      }
    }
    return idLayers;
  }

  /**
   * Mueve un nodo a la posición dada entre el conjunto de nodos que están a su mismo nivel.
   *
   * @param parent Nodo padre
   * @param node Nodo hijo a mover
   * @param newPos Nueva posición
   */
  public void moveNode(SVGNode parent, SVGNode node, int newPos) {
    TinyVector children = parent.children;
    if (children != null) {
      if (newPos >= 0 && newPos < children.count) {
        logger.debug("Moviendo nodo...");
        int curPos = children.indexOf(node, 0); // Posicion actual del nodo a mover
        logger.debug("Posicion actual: " + curPos + ", posicion nueva: " + newPos);
        if (curPos == -1) return;
        if (curPos < newPos) {
          // Desplazamiento hacia delante de los nodos hermanos
          for (int i = curPos; i < newPos; i++) {
            children.data[i] = children.data[i + 1];
          }
        } else if (curPos > newPos) {
          // Desplazamiento hacia atras de los nodos hermanos
          for (int i = curPos; i > newPos; i--) {
            children.data[i] = children.data[i - 1];
          }
        }
        // Insercion del nodo en su nueva posicion
        children.data[newPos] = node;
      }
    }
  }

  /** Activa el modo Zoom In de visualización de la imagen. */
  public void setModeZoomIn() {
    mouseHandler.type = MouseHandler.ZOOM_IN_MOUSE;
    logger.debug("Modo zoom in");
  }

  /** Activa el modo Zoom Out de visualización de la imagen. */
  public void setModeZoomOut() {
    mouseHandler.type = MouseHandler.ZOOM_OUT_MOUSE;
    logger.debug("Modeo zoom out");
  }

  /** Hace zoom out directamente, sin necesidad de pulsar sobre el canvas. */
  public void zoomOut() {
    mouseHandler.zoomOut();
  }

  public void zoomIn(int x0, int y0, int x1, int y1, boolean force) {
    mouseHandler.zoomIn(x0, y0, x1, y1, force);
  }

  public void zoomIn(int x0, int y0, int x1, int y1) {
    mouseHandler.zoomIn(x0, y0, x1, y1, false);
  }

  /** Activa el modo de visualización de movimiento de la imagen vectorial. */
  public void setModePan() {
    mouseHandler.type = MouseHandler.PAN_MOUSE;
    logger.debug("Modo pan");
  }

  /**
   * Desplaza la imagen
   *
   * @param dx Desplazamiento horizontal en pixeles
   * @param dy Desplazamiento vertical en pixeles
   */
  public void pan(int dx, int dy) {
    mouseHandler.pan(dx, dy);
  }

  //	/**
  //	 * Activa el modo de seleccion.
  //	 */
  //	public void setModeLink(String selectableAncestorId) {
  //		if(selectableAncestorId!=null)
  //			mouseHandler.setSelectableAncestorId(selectableAncestorId);
  //		mouseHandler.setAllAncestorsIds(getAllIDLayers());
  //		mouseHandler.type = MouseHandler.LINK_MOUSE;
  //		logger.debug("Modo seleccion. Id nodo ancestro: " + selectableAncestorId);
  //	}
  //
  //	/**
  //	 * Activa el modo de seleccion.
  //	 */
  //	public void setModeLink() {
  //		setModeLink(null);
  //	}

  public void setModeLink(String selectableAncestorId) {
    mouseHandler.setSelectableAncestorId(selectableAncestorId);
    mouseHandler.type = MouseHandler.LINK_MOUSE;
    logger.debug("Modo seleccion. Id nodo ancestro: " + selectableAncestorId);
  }

  /** Activa el modo de seleccion. */
  public void setModeLink() {
    mouseHandler.type = MouseHandler.LINK_MOUSE;
    logger.debug("Modo seleccion");
  }

  /**
   * Dibuja un nodo que representa una linea.
   *
   * @param parentNodeId Id del nodo padre
   */
  public void setModeDrawLine(String parentNodeId) {
    mouseHandler.initDraw(parentNodeId);
    mouseHandler.type = MouseHandler.POLYLINE_MOUSE;
    logger.debug("Modo dibujar linea. Id nodo padre: " + parentNodeId);
  }

  /**
   * Dibuja un nodo que representa un poligono.
   *
   * @param parentNodeId Id del nodo padre
   */
  public void setModeDrawPolygon(String parentNodeId) {
    mouseHandler.initDraw(parentNodeId);
    mouseHandler.type = MouseHandler.POLYGON_MOUSE;
    logger.debug("Modo dibujar poligono. Id nodo padre: " + parentNodeId);
  }

  /**
   * Dibuja un nodo que representa un punto.
   *
   * @param parentNodeId Id del nodo padre
   */
  public void setModeDrawPoint(String parentNodeId) {
    mouseHandler.initDraw(parentNodeId);
    mouseHandler.type = MouseHandler.POINT_MOUSE;
    logger.debug("Modo dibujar punto. Id nodo padre: " + parentNodeId);
  }

  /**
   * Confirma la inserción de un elemento de dibujo en el SVG, cuando éste puede tener un número
   * variable de vértices.
   *
   * @return El nodo dibujado
   */
  public SVGNode endDraw() {
    return mouseHandler.endDraw();
  }

  /** Cancela la inserción del último elemento dibujado. */
  public void cancelDraw() {
    mouseHandler.cancelDraw();
  }

  /** Obtiene el ultimo nodo dibujado e insertado. */
  public SVGNode getLastInsertedNode() {
    return mouseHandler.getLastInsertedNode();
  }

  /** Obtiene el nodo sobre el que se pueden seleccionar elementos */
  public String getSelectableAncestorId() {
    return mouseHandler.getSelectableAncestorId();
  }

  /** Establece el nodo sobre el que se pueden seleccionar elementos */
  public void setSelectableAncestorId(String selectableAncestorId) {
    mouseHandler.setSelectableAncestorId(selectableAncestorId);
  }

  /**
   * Escribe en el stream de salida que se le pasa por parámetro el contenido del árbol DOM en xml.
   */
  public void serializeSVG2XML(OutputStream outputStream) throws IOException {
    logger.debug("Serializando SVG...");
    SVGDocument document = raster.getSVGDocument();
    document.serializeSVG2XML(outputStream);
    logger.debug("SVG serializado");
  }

  /**
   * Escribe en el stream de salida que se le pasa por parámetro el contenido del árbol DOM en xml,
   * solamente para los nodos del SVG que han sido modificados. Devuelve un Vector con los nodos que
   * contienen imagenes asociadas.
   */
  public Vector serializeModifiedNodes2XML(
      OutputStream outputStream,
      boolean generateIds,
      boolean generateHeader,
      String idValueNotSerialized)
      throws IOException {
    logger.debug("Serializando Cambios del SVG...");
    SVGDocument document = raster.getSVGDocument();
    return document.serializeModifiedNodes2XML(
        outputStream, generateIds, generateHeader, idValueNotSerialized);
  }

  /**
   * Añade un evento a la cola.
   *
   * @param theEvent instancia de la clase Event o de una subclase.
   */
  public synchronized void postEvent(SVGEvent theEvent) {
    // IMPORTANT
    theEvent.eventTarget = this;
    eventQueue.postEvent(theEvent);
  }

  //////////////////////////////////////////////////////////////////////////////////////////
  // Metodos de la interfaz ImageLoader
  //////////////////////////////////////////////////////////////////////////////////////////
  /** Crea un TinyBitmap para la URL o path dado. */
  public TinyBitmap createTinyBitmap(TinyString uri) {
    String imgRef = new String(uri.data);
    logger.debug("Creando bitmap de URI: " + imgRef);
    SVGBitmap bitmap = null;
    try {
      URL url = null;
      try {
        url = new URL(imgRef);
      } catch (MalformedURLException e) {
        logger.debug(imgRef + " no es una URL completa");
        url = new URL(baseURL, imgRef);
        logger.debug("URL compuesta: " + url.toExternalForm());
      }
      // check in the cash
      bitmap = (SVGBitmap) imageCash.get(url);
      // not found
      if (bitmap == null) {
        bitmap = new SVGBitmap(url);
        imageCash.put(url, bitmap);
      }
    } catch (Exception ex) {
      // ex.printStackTrace();
      logger.error("Error al crear bitmap", ex);
    }
    return bitmap;
  }

  /**
   * Crea un TinyBitmap que decodifica la imagen almacenada en el array de bytes especificado, con
   * el offset y la longitud indicadas.
   */
  public TinyBitmap createTinyBitmap(byte[] imageData, int imageOffset, int imageLength) {
    return new SVGBitmap(imageData, imageOffset, imageLength);
  }

  //////////////////////////////////////////////////////////////////////////////////////////
  // Metodos de la interfaz ImageConsumer
  //////////////////////////////////////////////////////////////////////////////////////////

  public void setPixels(
      final int xmin,
      final int ymin,
      final int width,
      final int height,
      final int[] pixels32,
      final int pixeloffset,
      final int pixelscan) {
    logger.debug("Actualizando imagen de pantalla");
    // Zona actual e imagen de fondo
    if (loaded) {
      SVGNode root = raster.document.root;
      // Zona actual
      TinyMatrix inverse = root.getGlobalTransform().inverse();
      TinyPoint p1 = new TinyPoint(0, 0);
      inverse.transform(p1);
      TinyPoint p2 = new TinyPoint(width << TinyUtil.FIX_BITS, height << TinyUtil.FIX_BITS);
      inverse.transform(p2);

      if (currentZone.isEmpty()
          || currentZone.xmin != p1.x
          || currentZone.ymin != p1.y
          || currentZone.xmax != p2.x
          || currentZone.ymax != p2.y) {
        currentZone.xmin = p1.x;
        currentZone.ymin = p1.y;
        currentZone.xmax = p2.x;
        currentZone.ymax = p2.y;
        // Nueva imagen de fondo
        if (backImage != null && !backImage.isDisposed()) {
          backImage.dispose();
          backImage = null;
        }
        backImage = getBackImage();
      }
    }

    // Imagen del SVG
    int totalHeight = 0;
    try {
      totalHeight = pixels32.length / pixelscan;

      // Establecer los pixeles y los alphas
      ImageData imData =
          new ImageData(pixelscan, totalHeight, 32, new PaletteData(0xFF0000, 0xFF00, 0xFF));

      byte[] alphaData = new byte[pixels32.length];
      for (int i = 0; i < pixels32.length; i++) {
        alphaData[i] = (byte) (pixels32[i] >> 24);
      }
      imData.setPixels(0, 0, pixels32.length, pixels32, 0);
      imData.setAlphas(0, 0, pixels32.length, alphaData, 0);
      if (svgImage != null && !svgImage.isDisposed()) svgImage.dispose();
      // logger.debug("Antes de generar la imagen1:");
      // logger.debug("Antes de generar la imagen2:"+imData.height);
      svgImage = new Image(Display.getDefault(), imData);
      logger.debug("Imagen generada");
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      logger.debug("ERROR");
    }

    logger.debug("Verificando clipping");
    // Clipping
    if (width < pixelscan || height < totalHeight) {
      logger.debug("Hay clipping");
      GC gc = new GC(svgImage);
      int rasterBackground = raster.getBackground();
      Color rasterBackgroundColor =
          new Color(
              Display.getDefault(),
              (rasterBackground & 0x00FF0000) >> 16,
              (rasterBackground & 0x0000FF00) >> 8,
              (rasterBackground & 0x000000FF));
      gc.setBackground(rasterBackgroundColor);
      if (xmin > 0) {
        gc.fillRectangle(0, 0, xmin, totalHeight);
      }
      if (xmin + width < pixelscan) {
        gc.fillRectangle(xmin + width, 0, pixelscan - xmin - width, totalHeight);
      }
      if (ymin > 0) {
        gc.fillRectangle(0, 0, pixelscan, ymin);
      }
      if (ymin + height < totalHeight) {
        gc.fillRectangle(0, ymin + height, pixelscan, totalHeight - ymin - height);
      }
      rasterBackgroundColor.dispose();
      gc.dispose();
    }

    // logger.debug("Arrancando thread de display");
    if (synchPaint) {
      getDisplay().syncExec(new redrawThread());
    } else {
      getDisplay().asyncExec(new redrawThread());
    }
    logger.debug("Imagen de pantalla actualizada");
  }

  /** Thread para hacer el dibujado */
  private class redrawThread implements Runnable {
    public void run() {
      if (!isDisposed()) {
        redraw();
        // update();
      }
    }
  }

  protected Image getBackImage() {
    return null;
  }

  public void reloadCurrentZone() {
    currentZone = new TinyRect();
  }

  public void drawSVG() {
    Enumeration en = drawListeners.elements();
    while (en.hasMoreElements()) {
      ((SVGViewerDrawListener) en.nextElement()).beginDraw();
    }

    try {
      raster.setCamera();

      en = rasterTransformListeners.elements();
      while (en.hasMoreElements()) {
        SVGViewerRasterTransformListener l = (SVGViewerRasterTransformListener) en.nextElement();
        l.rasterTransformed();
      }
      raster.update();
      en = rasterUpdateListeners.elements();
      while (en.hasMoreElements()) {
        SVGViewerRasterUpdateListener l = (SVGViewerRasterUpdateListener) en.nextElement();
        l.rasterUpdated();
      }
      raster.sendPixels();
      logger.debug("Draw svg finalizado");

    } catch (Exception e) {
      e.printStackTrace();
      logger.error(e);
    } finally {
      en = drawListeners.elements();
      while (en.hasMoreElements()) {
        ((SVGViewerDrawListener) en.nextElement()).endDraw();
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////
  // Metodos de la interfaz Runnable
  //////////////////////////////////////////////////////////////////////////////////////////

  /** Inicia el thread procesador de eventos */
  public synchronized void start() {
    thread = new Thread(this);
    thread.setPriority(Thread.MIN_PRIORITY);
    thread.start();
  }

  /** Detiene el thread procesador de eventos */
  public synchronized void stop() {
    thread = null;
    SVGEvent event = new SVGEvent(SVGEvent.EVENT_UNLOAD, null);
    postEvent(event);
  }

  /** run() del thread procesador de eventos */
  public void run() {

    Thread currentThread = Thread.currentThread();
    try {
      while (currentThread == thread) {
        eventQueue.handleEvent(eventQueue.getNextEvent());
      }
    } catch (InterruptedException e) {
      return;
    } catch (Throwable thr) {
      // thr.printStackTrace();
      logger.error(thr);
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////
  // Metodos de la interfaz EventTarget
  //////////////////////////////////////////////////////////////////////////////////////////
  /**
   * <b>uDOM:</b> This method allows the registration of event listeners on the event target.
   *
   * @param type The event type for which the user is registering
   * @param listener The listener parameter takes an interface implemented by the user which
   *     contains the methods to be called when the event occurs.
   * @param useCapture If true, useCapture indicates that the user wishes to initiate capture. After
   *     initiating capture, all events of the specified type will be dispatched to the registered
   *     EventListener before being dispatched to any EventTargets beneath them in the tree. Events
   *     which are bubbling upward through the tree will not trigger an EventListener designated to
   *     use capture.
   */
  public void addEventListener(
      java.lang.String type, org.w3c.dom.events.EventListener listener, boolean useCapture) {
    listeners.addElement(listener);
  }

  /**
   * <b>uDOM:</b> This method allows the removal of event listeners from the event target.
   *
   * @param type Specifies the event type of the EventListener being removed.
   * @param listener The listener parameter indicates the EventListener to be removed.
   * @param useCapture Specifies whether the EventListener being removed was registered as a
   *     capturing listener or not.
   */
  public void removeEventListener(
      java.lang.String type, org.w3c.dom.events.EventListener listener, boolean useCapture) {

    int i = listeners.indexOf(listener, 0);
    if (i > 0) {
      listeners.removeElementAt(i);
    }
  }
  /**
   * <b>uDOM:</b> This method allows the dispatch of events into the implementations event model.
   * Events dispatched in this manner will have the same behavior as events dispatched directly by
   * the implementation.
   *
   * @param evt Specifies the event type, behavior, and contextual information to be used in
   *     processing the event.
   * @return The return value of dispatchEvent indicates whether any of the listeners which handled
   *     the event called preventDefault. If preventDefault was called the value is false, else the
   *     value is true.
   */
  public boolean dispatchEvent(org.w3c.dom.events.Event evt) {
    org.w3c.dom.events.EventListener h;
    for (int i = 0; i < listeners.count; i++) {
      h = (org.w3c.dom.events.EventListener) listeners.data[i];
      if (h != null) h.handleEvent(evt);
    }
    return true;
  }

  public boolean isAntialiased() {
    return antialiased;
  }

  public void setAntialiased(boolean antialiased) {
    if (this.antialiased != antialiased) {
      this.antialiased = antialiased;
      raster.setAntialiased(antialiased);
    }
  }

  public void addSVGViewerErrorListener(SVGViewerErrorListener listener) {
    errorListeners.addElement(listener);
  }

  public void removeSVGViewerErrorListener(SVGViewerErrorListener listener) {
    errorListeners.removeElement(listener);
  }

  public Vector getSVGViewerErrorListeners() {
    return errorListeners;
  }

  public void addSVGViewerRasterTransformListener(SVGViewerRasterTransformListener listener) {
    rasterTransformListeners.addElement(listener);
  }

  public void removeSVGViewerRasterTransformListener(SVGViewerRasterTransformListener listener) {
    rasterTransformListeners.removeElement(listener);
  }

  public Vector getSVGViewerRasterTransformListeners() {
    return rasterTransformListeners;
  }

  public void addSVGViewerRasterUpdateListener(SVGViewerRasterUpdateListener listener) {
    rasterUpdateListeners.addElement(listener);
  }

  public void removeSVGViewerRasterUpdateListener(SVGViewerRasterUpdateListener listener) {
    rasterUpdateListeners.removeElement(listener);
  }

  public Vector getSVGViewerRasterUpdateListeners() {
    return rasterUpdateListeners;
  }

  public void addSVGViewerLinkListener(SVGViewerLinkListener listener) {
    linkListeners.addElement(listener);
  }

  public void removeSVGViewerLinkListener(SVGViewerLinkListener listener) {
    linkListeners.removeElement(listener);
  }

  public Vector getSVGViewerLinkListeners() {
    return linkListeners;
  }

  public void addSVGViewerPointInsertedListener(SVGViewerPointInsertedListener listener) {
    pointInsertedListeners.addElement(listener);
  }

  public void removeSVGViewerPointInsertedListener(SVGViewerPointInsertedListener listener) {
    pointInsertedListeners.removeElement(listener);
  }

  public Vector getSVGViewerPointInsertedListeners() {
    return pointInsertedListeners;
  }

  public void addSVGViewerLoadListener(SVGViewerLoadListener listener) {
    loadListeners.addElement(listener);
  }

  public void removeSVGViewerLoadListener(SVGViewerLoadListener listener) {
    loadListeners.removeElement(listener);
  }

  public Vector getSVGViewerLoadListeners() {
    return loadListeners;
  }

  public void addSVGViewerDrawListener(SVGViewerDrawListener listener) {
    drawListeners.addElement(listener);
  }

  public void removeSVGViewerDrawListener(SVGViewerDrawListener listener) {
    drawListeners.removeElement(listener);
  }

  public Vector getSVGViewerDrawListeners() {
    return drawListeners;
  }

  /** Borramos la informacion y los handles asociados. */
  public void dispose() {

    if (offscreenImage != null && !offscreenImage.isDisposed()) {
      offscreenImage.dispose();
    }
    if (svgImage != null) svgImage.dispose();

    if (adapter != null) this.removeControlListener(adapter);
  }
}