Mercurial > hgrepos > Python2 > PyMuPDF
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(); + } +}
