diff mupdf-source/thirdparty/tesseract/java/com/google/scrollview/ui/SVWindow.java @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/tesseract/java/com/google/scrollview/ui/SVWindow.java	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,648 @@
+// Copyright 2007 Google Inc. All Rights Reserved.
+//
+// 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.google.scrollview.ui;
+
+import com.google.scrollview.ScrollView;
+import com.google.scrollview.events.SVEvent;
+import com.google.scrollview.events.SVEventHandler;
+import com.google.scrollview.events.SVEventType;
+import com.google.scrollview.ui.SVMenuBar;
+import com.google.scrollview.ui.SVPopupMenu;
+
+import org.piccolo2d.PCamera;
+import org.piccolo2d.PCanvas;
+import org.piccolo2d.PLayer;
+import org.piccolo2d.extras.swing.PScrollPane;
+import org.piccolo2d.nodes.PImage;
+import org.piccolo2d.nodes.PPath;
+import org.piccolo2d.nodes.PText;
+import org.piccolo2d.util.PPaintContext;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GraphicsEnvironment;
+import java.awt.Rectangle;
+import java.awt.TextArea;
+import java.awt.geom.IllegalPathStateException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
+
+/**
+ * The SVWindow is the top-level ui class. It should get instantiated whenever
+ * the user intends to create a new window. It contains helper functions to draw
+ * on the canvas, add new menu items, show modal dialogs etc.
+ *
+ * @author wanke@google.com
+ */
+public class SVWindow extends JFrame {
+  /**
+   * Constants defining the maximum initial size of the window.
+   */
+  private static final int MAX_WINDOW_X = 1000;
+  private static final int MAX_WINDOW_Y = 800;
+
+  /* Constant defining the (approx) height of the default message box*/
+  private static final int DEF_MESSAGEBOX_HEIGHT = 200;
+
+  /** Constant defining the "speed" at which to zoom in and out. */
+  public static final double SCALING_FACTOR = 2;
+
+  /** The top level layer we add our PNodes to (root node). */
+  PLayer layer;
+
+  /** The current color of the pen. It is used to draw edges, text, etc. */
+  Color currentPenColor;
+
+  /**
+   * The current color of the brush. It is used to draw the interior of
+   * primitives.
+   */
+  Color currentBrushColor;
+
+  /** The system name of the current font we are using (e.g.
+   *  "Times New Roman"). */
+  Font currentFont;
+
+  /** The stroke width to be used. */
+  // This really needs to be a fixed width stroke as the basic stroke is
+  // anti-aliased and gets too faint, but the piccolo fixed width stroke
+  // is too buggy and generates missing initial moveto in path definition
+  // errors with an IllegalPathStateException that cannot be caught because
+  // it is in the automatic repaint function. If we can fix the exceptions
+  // in piccolo, then we can use the following instead of BasicStroke:
+  //   import edu.umd.cs.piccolox.util.PFixedWidthStroke;
+  //   PFixedWidthStroke stroke = new PFixedWidthStroke(0.5f);
+  // Instead we use the BasicStroke and turn off anti-aliasing.
+  BasicStroke stroke = new BasicStroke(0.5f);
+
+  /**
+   * A unique representation for the window, also known by the client. It is
+   * used when sending messages from server to client to identify him.
+   */
+  public int hash;
+
+  /**
+   * The total number of created Windows. If this ever reaches 0 (apart from the
+   * beginning), quit the server.
+   */
+  public static int nrWindows = 0;
+
+  /**
+   * The Canvas, MessageBox, EventHandler, Menubar and Popupmenu associated with
+   * this window.
+   */
+  private SVEventHandler svEventHandler = null;
+  private SVMenuBar svMenuBar = null;
+  private TextArea ta = null;
+  public SVPopupMenu svPuMenu = null;
+  public PCanvas canvas;
+  private int winSizeX;
+  private int winSizeY;
+
+  /** Set the brush to an RGB color */
+  public void brush(int red, int green, int blue) {
+    brush(red, green, blue, 255);
+  }
+
+  /** Set the brush to an RGBA color */
+  public void brush(int red, int green, int blue, int alpha) {
+    // If alpha is zero, use a null brush to save rendering time.
+    if (alpha == 0) {
+      currentBrushColor = null;
+    } else {
+      currentBrushColor = new Color(red, green, blue, alpha);
+    }
+  }
+
+  /** Erase all content from the window, but do not destroy it. */
+  public void clear() {
+    // Manipulation of Piccolo's scene graph should be done from Swings
+    // event dispatch thread since Piccolo is not thread safe. This code calls
+    // removeAllChildren() from that thread and releases the latch.
+    final java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(1);
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        layer.removeAllChildren();
+        repaint();
+        latch.countDown();
+      }
+    });
+    try {
+      latch.await();
+    } catch (InterruptedException e) {
+    }
+  }
+
+  /**
+   * Start setting up a new polyline. The server will now expect
+   * polyline data until the polyline is complete.
+   *
+   * @param length number of coordinate pairs
+   */
+  public void createPolyline(int length) {
+    ScrollView.polylineXCoords = new float[length];
+    ScrollView.polylineYCoords = new float[length];
+    ScrollView.polylineSize = length;
+    ScrollView.polylineScanned = 0;
+  }
+
+  /**
+   * Draw the now complete polyline.
+   */
+  public void drawPolyline() {
+    int numCoords = ScrollView.polylineXCoords.length;
+    if (numCoords < 2) {
+      return;
+    }
+    PPath pn = PPath.createLine(ScrollView.polylineXCoords[0],
+                                ScrollView.polylineYCoords[0],
+                                ScrollView.polylineXCoords[1],
+                                ScrollView.polylineYCoords[1]);
+    pn.reset();
+    pn.moveTo(ScrollView.polylineXCoords[0], ScrollView.polylineYCoords[0]);
+    for (int p = 1; p < numCoords; ++p) {
+      pn.lineTo(ScrollView.polylineXCoords[p], ScrollView.polylineYCoords[p]);
+    }
+    pn.closePath();
+    ScrollView.polylineSize = 0;
+    pn.setStrokePaint(currentPenColor);
+    pn.setPaint(null);  // Don't fill the polygon - this is just a polyline.
+    pn.setStroke(stroke);
+    layer.addChild(pn);
+  }
+
+  /**
+   * Construct a new SVWindow and set it visible.
+   *
+   * @param name Title of the window.
+   * @param hash Unique internal representation. This has to be the same as
+   *        defined by the client, as they use this to refer to the windows.
+   * @param posX X position of where to draw the window (upper left).
+   * @param posY Y position of where to draw the window (upper left).
+   * @param sizeX The width of the window.
+   * @param sizeY The height of the window.
+   * @param canvasSizeX The canvas width of the window.
+   * @param canvasSizeY The canvas height of the window.
+   */
+  public SVWindow(String name, int hash, int posX, int posY, int sizeX,
+                  int sizeY, int canvasSizeX, int canvasSizeY) {
+    super(name);
+
+    // Provide defaults for sizes.
+    if (sizeX <= 0) sizeX = canvasSizeX;
+    if (sizeY <= 0) sizeY = canvasSizeY;
+    if (canvasSizeX <= 0) canvasSizeX = sizeX;
+    if (canvasSizeY <= 0) canvasSizeY = sizeY;
+
+    // Avoid later division by zero.
+    if (sizeX <= 0) {
+      sizeX = 1;
+      canvasSizeX = sizeX;
+    }
+    if (sizeY <= 0) {
+      sizeY = 1;
+      canvasSizeY = sizeY;
+    }
+
+    // Initialize variables
+    nrWindows++;
+    this.hash = hash;
+    this.svEventHandler = new SVEventHandler(this);
+    this.currentPenColor = Color.BLACK;
+    this.currentBrushColor = Color.BLACK;
+    this.currentFont = new Font("Times New Roman", Font.PLAIN, 12);
+
+    // Determine the initial size and zoom factor of the window.
+    // If the window is too big, rescale it and zoom out.
+    int shrinkfactor = 1;
+
+    if (sizeX > MAX_WINDOW_X) {
+      shrinkfactor = (sizeX + MAX_WINDOW_X - 1) / MAX_WINDOW_X;
+    }
+    if (sizeY / shrinkfactor > MAX_WINDOW_Y) {
+      shrinkfactor = (sizeY + MAX_WINDOW_Y - 1) / MAX_WINDOW_Y;
+    }
+    winSizeX = sizeX / shrinkfactor;
+    winSizeY = sizeY / shrinkfactor;
+    double initialScalingfactor = 1.0 / shrinkfactor;
+    if (winSizeX > canvasSizeX || winSizeY > canvasSizeY) {
+      initialScalingfactor = Math.min(1.0 * winSizeX / canvasSizeX,
+                                      1.0 * winSizeY / canvasSizeY);
+    }
+
+    // Setup the actual window (its size, camera, title, etc.)
+    if (canvas == null) {
+      canvas = new PCanvas();
+      getContentPane().add(canvas, BorderLayout.CENTER);
+    }
+
+    layer = canvas.getLayer();
+    canvas.setBackground(Color.BLACK);
+
+    // Disable antialiasing to make the lines more visible.
+    canvas.setDefaultRenderQuality(PPaintContext.LOW_QUALITY_RENDERING);
+
+    setLayout(new BorderLayout());
+
+    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+    validate();
+    canvas.requestFocus();
+
+    // Manipulation of Piccolo's scene graph should be done from Swings
+    // event dispatch thread since Piccolo is not thread safe. This code calls
+    // initialize() from that thread once the PFrame is initialized, so you are
+    // safe to start working with Piccolo in the initialize() method.
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        repaint();
+      }
+    });
+
+    setSize(winSizeX, winSizeY);
+    setLocation(posX, posY);
+    setTitle(name);
+
+    // Add a Scrollpane to be able to scroll within the canvas
+    PScrollPane scrollPane = new PScrollPane(canvas);
+    getContentPane().add(scrollPane);
+    scrollPane.setWheelScrollingEnabled(false);
+    PCamera lc = canvas.getCamera();
+    lc.scaleViewAboutPoint(initialScalingfactor, 0, 0);
+
+    // Disable the default event handlers and add our own.
+    addWindowListener(svEventHandler);
+    canvas.removeInputEventListener(canvas.getPanEventHandler());
+    canvas.removeInputEventListener(canvas.getZoomEventHandler());
+    canvas.addInputEventListener(svEventHandler);
+    canvas.addKeyListener(svEventHandler);
+
+    // Make the window visible.
+    validate();
+    setVisible(true);
+
+  }
+
+  /**
+   * Convenience function to add a message box to the window which can be used
+   * to output debug information.
+   */
+  public void addMessageBox() {
+    if (ta == null) {
+      ta = new TextArea();
+      ta.setEditable(false);
+      getContentPane().add(ta, BorderLayout.SOUTH);
+    }
+    // We need to make the window bigger to accommodate the message box.
+    winSizeY += DEF_MESSAGEBOX_HEIGHT;
+    setSize(winSizeX, winSizeY);
+  }
+
+  /**
+   * Allows you to specify the thickness with which to draw lines, recantgles
+   * and ellipses.
+   * @param width The new thickness.
+   */
+  public void setStrokeWidth(float width) {
+    // If this worked we wouldn't need the antialiased rendering off.
+    // stroke = new PFixedWidthStroke(width);
+    stroke = new BasicStroke(width);
+  }
+
+  /**
+   * Draw an ellipse at (x,y) with given width and height, using the
+   * current stroke, the current brush color to fill it and the
+   * current pen color for the outline.
+   */
+  public void drawEllipse(int x, int y, int width, int height) {
+    PPath pn = PPath.createEllipse(x, y, width, height);
+    pn.setStrokePaint(currentPenColor);
+    pn.setStroke(stroke);
+    pn.setPaint(currentBrushColor);
+    layer.addChild(pn);
+  }
+
+  /**
+   * Draw the image with the given name at (x,y). Any image loaded stays in
+   * memory, so if you intend to redraw an image, you do not have to use
+   * createImage again.
+   */
+  public void drawImage(PImage img, int xPos, int yPos) {
+    img.setX(xPos);
+    img.setY(yPos);
+    layer.addChild(img);
+  }
+
+  /**
+   * Draw a line from (x1,y1) to (x2,y2) using the current pen color and stroke.
+   */
+  public void drawLine(int x1, int y1, int x2, int y2) {
+    PPath pn = PPath.createLine(x1, y1, x2, y2);
+    pn.setStrokePaint(currentPenColor);
+    pn.setPaint(null);  // Null paint may render faster than the default.
+    pn.setStroke(stroke);
+    pn.moveTo(x1, y1);
+    pn.lineTo(x2, y2);
+    layer.addChild(pn);
+  }
+
+  /**
+   * Draw a rectangle given the two points (x1,y1) and (x2,y2) using the current
+   * stroke, pen color for the border and the brush to fill the
+   * interior.
+   */
+  public void drawRectangle(int x1, int y1, int x2, int y2) {
+
+    if (x1 > x2) {
+      int t = x1;
+      x1 = x2;
+      x2 = t;
+    }
+    if (y1 > y2) {
+      int t = y1;
+      y1 = y2;
+      y2 = t;
+    }
+
+    PPath pn = PPath.createRectangle(x1, y1, x2 - x1, y2 - y1);
+    pn.setStrokePaint(currentPenColor);
+    pn.setStroke(stroke);
+    pn.setPaint(currentBrushColor);
+    layer.addChild(pn);
+  }
+
+  /**
+   * Draw some text at (x,y) using the current pen color and text attributes. If
+   * the current font does NOT support at least one character, it tries to find
+   * a font which is capable of displaying it and use that to render the text.
+   * Note: If the font says it can render a glyph, but in reality it turns out
+   * to be crap, there is nothing we can do about it.
+   */
+  public void drawText(int x, int y, String text) {
+    int unreadableCharAt = -1;
+    char[] chars = text.toCharArray();
+    PText pt = new PText(text);
+    pt.setTextPaint(currentPenColor);
+    pt.setFont(currentFont);
+
+    // Check to see if every character can be displayed by the current font.
+    for (int i = 0; i < chars.length; i++) {
+      if (!currentFont.canDisplay(chars[i])) {
+        // Set to the first not displayable character.
+        unreadableCharAt = i;
+        break;
+      }
+    }
+
+    // Have to find some working font and use it for this text entry.
+    if (unreadableCharAt != -1) {
+      Font[] allfonts =
+          GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
+      for (int j = 0; j < allfonts.length; j++) {
+        if (allfonts[j].canDisplay(chars[unreadableCharAt])) {
+          Font tempFont =
+              new Font(allfonts[j].getFontName(), currentFont.getStyle(),
+                  currentFont.getSize());
+          pt.setFont(tempFont);
+          break;
+        }
+      }
+    }
+
+    pt.setX(x);
+    pt.setY(y);
+    layer.addChild(pt);
+  }
+
+  /** Set the pen color to an RGB value */
+  public void pen(int red, int green, int blue) {
+    pen(red, green, blue, 255);
+  }
+
+  /** Set the pen color to an RGBA value */
+  public void pen(int red, int green, int blue, int alpha) {
+    currentPenColor = new Color(red, green, blue, alpha);
+  }
+
+  /**
+   * Define how to display text. Note: underlined is not currently not supported
+   */
+  public void textAttributes(String font, int pixelSize, boolean bold,
+      boolean italic, boolean underlined) {
+
+    // For legacy reasons convert "Times" to "Times New Roman"
+    if (font.equals("Times")) {
+      font = "Times New Roman";
+    }
+
+    int style = Font.PLAIN;
+    if (bold) {
+      style += Font.BOLD;
+    }
+    if (italic) {
+      style += Font.ITALIC;
+    }
+    currentFont = new Font(font, style, pixelSize);
+  }
+
+  /**
+   * Zoom the window to the rectangle given the two points (x1,y1)
+   * and (x2,y2), which must be greater than (x1,y1).
+   */
+  public void zoomRectangle(int x1, int y1, int x2, int y2) {
+    if (x2 > x1 && y2 > y1) {
+      winSizeX = getWidth();
+      winSizeY = getHeight();
+      int width = x2 - x1;
+      int height = y2 - y1;
+      // Since piccolo doesn't do this well either, pad with a margin
+      // all the way around.
+      int wmargin = width / 2;
+      int hmargin = height / 2;
+      double scalefactor = Math.min(winSizeX / (2.0 * wmargin + width),
+                                    winSizeY / (2.0 * hmargin + height));
+      PCamera lc = canvas.getCamera();
+      lc.scaleView(scalefactor / lc.getViewScale());
+      lc.animateViewToPanToBounds(new Rectangle(x1 - hmargin, y1 - hmargin,
+                                                2 * wmargin + width,
+                                                2 * hmargin + height), 0);
+    }
+  }
+
+  /**
+   * Flush buffers and update display.
+   *
+   * Only actually reacts if there are no more messages in the stack, to prevent
+   * the canvas from flickering.
+   */
+  public void update() {
+    // TODO(rays) fix bugs in piccolo or use something else.
+    // The repaint function generates many
+    // exceptions for no good reason. We catch and ignore as many as we
+    // can here, but most of them are generated by the system repaints
+    // caused by resizing/exposing parts of the window etc, and they
+    // generate unwanted stack traces that have to be piped to /dev/null
+    // (on linux).
+    try {
+      repaint();
+    } catch (NullPointerException e) {
+      // Do nothing so the output isn't full of stack traces.
+    } catch (IllegalPathStateException e) {
+      // Do nothing so the output isn't full of stack traces.
+    }
+  }
+
+  /** Adds a checkbox entry to the menubar, c.f. SVMenubar.add(...) */
+  public void addMenuBarItem(String parent, String name, int id,
+                             boolean checked) {
+    svMenuBar.add(parent, name, id, checked);
+  }
+
+  /** Adds a submenu to the menubar, c.f. SVMenubar.add(...) */
+  public void addMenuBarItem(String parent, String name) {
+    addMenuBarItem(parent, name, -1);
+  }
+
+  /** Adds a new entry to the menubar, c.f. SVMenubar.add(...) */
+  public void addMenuBarItem(String parent, String name, int id) {
+    if (svMenuBar == null) {
+      svMenuBar = new SVMenuBar(this);
+
+    }
+    svMenuBar.add(parent, name, id);
+  }
+
+  /** Add a message to the message box. */
+  public void addMessage(String message) {
+    if (ta != null) {
+      ta.append(message + "\n");
+    } else {
+      System.out.println(message + "\n");
+    }
+  }
+
+  /**
+   * This method converts a string which might contain hexadecimal values to a
+   * string which contains the respective unicode counterparts.
+   *
+   * For example, Hall0x0094chen returns Hall<o umlaut>chen
+   * encoded as utf8.
+   *
+   * @param input The original string, containing 0x values
+   * @return The converted string which has the replaced unicode symbols
+   */
+  private static String convertIntegerStringToUnicodeString(String input) {
+    StringBuffer sb = new StringBuffer(input);
+    Pattern numbers = Pattern.compile("0x[0-9a-fA-F]{4}");
+    Matcher matcher = numbers.matcher(sb);
+
+    while (matcher.find()) {
+      // Find the next match which resembles a hexadecimal value and convert it
+      // to
+      // its char value
+      char a = (char) (Integer.decode(matcher.group()).intValue());
+
+      // Replace the original with the new character
+      sb.replace(matcher.start(), matcher.end(), String.valueOf(a));
+
+      // Start again, since our positions have switched
+      matcher.reset();
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Show a modal input dialog. The answer by the dialog is then send to the
+   * client, together with the associated menu id, as SVET_POPUP
+   *
+   * @param msg The text that is displayed in the dialog.
+   * @param def The default value of the dialog.
+   * @param id The associated commandId
+   * @param evtype The event this is associated with (usually SVET_MENU
+   * or SVET_POPUP)
+   */
+  public void showInputDialog(String msg, String def, int id,
+                              SVEventType evtype) {
+    svEventHandler.timer.stop();
+    String tmp =
+        (String) JOptionPane.showInputDialog(this, msg, "",
+            JOptionPane.QUESTION_MESSAGE, null, null, def);
+
+    if (tmp != null) {
+      tmp = convertIntegerStringToUnicodeString(tmp);
+      SVEvent res = new SVEvent(evtype, this, id, tmp);
+      ScrollView.addMessage(res);
+    }
+    svEventHandler.timer.restart();
+  }
+
+
+  /**
+   * Shows a modal input dialog to the user. The return value is automatically
+   * sent to the client as SVET_INPUT event (with command id -1).
+   *
+   * @param msg The text of the dialog.
+   */
+  public void showInputDialog(String msg) {
+    showInputDialog(msg, null, -1, SVEventType.SVET_INPUT);
+  }
+
+  /**
+   * Shows a dialog presenting "Yes" and "No" as answers and returns either a
+   * "y" or "n" to the client.
+   *
+   * Closing the dialog without answering is handled like "No".
+   *
+   * @param msg The text that is displayed in the dialog.
+   */
+  public void showYesNoDialog(String msg) {
+    // res returns 0 on yes, 1 on no. Seems to be a bit counterintuitive
+    int res =
+        JOptionPane.showOptionDialog(this, msg, "", JOptionPane.YES_NO_OPTION,
+            JOptionPane.QUESTION_MESSAGE, null, null, null);
+
+    SVEvent e = new SVEvent(SVEventType.SVET_INPUT, this, 0, 0, 0, 0,
+                            res == 0 ? "y" : "n");
+    ScrollView.addMessage(e);
+  }
+
+  /** Adds a submenu to the popup menu, c.f. SVPopupMenu.add(...) */
+  public void addPopupMenuItem(String parent, String name) {
+    if (svPuMenu == null) {
+      svPuMenu = new SVPopupMenu(this);
+    }
+    svPuMenu.add(parent, name, -1);
+  }
+
+  /** Adds a new menu entry to the popup menu, c.f. SVPopupMenu.add(...) */
+  public void addPopupMenuItem(String parent, String name, int cmdEvent,
+      String value, String desc) {
+    if (svPuMenu == null) {
+      svPuMenu = new SVPopupMenu(this);
+    }
+    svPuMenu.add(parent, name, cmdEvent, value, desc);
+  }
+
+  /** Destroys a window. */
+  public void destroy() {
+    ScrollView.addMessage(new SVEvent(SVEventType.SVET_DESTROY, this, 0,
+        "SVET_DESTROY"));
+    setVisible(false);
+    // dispose();
+  }
+}