public SinglePageLayout(Djvu_html5 app) {
    this.app = app;
    this.tileCache = app.getTileCache();
    this.pageCache = app.getPageCache();

    pageCache.addFullDecodeListener(this);
    tileCache.addTileCacheListener(this);

    this.canvas = app.getCanvas();

    this.background = app.getBackground();
    this.pageMargin = app.getPageMargin();

    new PanController();

    boolean pageParam = false;
    try {
      page = Integer.parseInt(Window.Location.getParameter("p")) - 1;
      pageParam = true;
    } catch (Exception e) {
      page = 0;
    }
    locationUpdateEnabled = pageParam || app.getLocationUpdateEnabled();
    pageCache.fetchPage(page);
  }
  public void redraw() {
    Context2d graphics2d = canvas.getContext2d();
    int w = canvas.getCoordinateSpaceWidth(), h = canvas.getCoordinateSpaceHeight();
    graphics2d.setFillStyle(background);
    graphics2d.fillRect(0, 0, w, h);
    if (pageInfo == null) return;

    int subsample = toSubsample(zoom);
    double scale = zoom / toZoom(subsample);
    graphics2d.save();
    int startX = w / 2 - centerX, startY = h / 2 - centerY;
    graphics2d.translate(startX, startY);
    graphics2d.scale(scale, scale);
    graphics2d.translate(-startX, -startY);
    graphics2d.scale(1, -1); // DjVu images have y-axis inverted

    int tileSize = tileCache.tileSize;
    int pw = (int) (pageInfo.width * zoom), ph = (int) (pageInfo.height * zoom);
    range.xmin = (int) (Math.max(0, centerX - w * 0.5) / tileSize / scale);
    range.xmax = (int) Math.ceil(Math.min(pw, centerX + w * 0.5) / tileSize / scale);
    range.ymin = (int) (Math.max(0, centerY - h * 0.5) / tileSize / scale);
    range.ymax = (int) Math.ceil(Math.min(ph, centerY + h * 0.5) / tileSize / scale);
    imagesArray = tileCache.getTileImages(page, subsample, range, imagesArray);
    for (int y = range.ymin; y <= range.ymax; y++)
      for (int x = range.xmin; x <= range.xmax; x++) {
        CanvasElement canvasElement = imagesArray[y - range.ymin][x - range.xmin];
        graphics2d.drawImage(
            canvasElement,
            startX + x * tileSize,
            -startY - y * tileSize - canvasElement.getHeight());
      }
    graphics2d.restore();
    // missing tile graphics may exceed the page boundary
    graphics2d.fillRect(startX + pw, 0, w, h);
    graphics2d.fillRect(0, startY + ph, w, h);
  }