comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright 2007 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); You may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
6 // applicable law or agreed to in writing, software distributed under the
7 // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
8 // OF ANY KIND, either express or implied. See the License for the specific
9 // language governing permissions and limitations under the License.
10
11 package com.google.scrollview.ui;
12
13 import com.google.scrollview.ScrollView;
14 import com.google.scrollview.events.SVEvent;
15 import com.google.scrollview.events.SVEventHandler;
16 import com.google.scrollview.events.SVEventType;
17 import com.google.scrollview.ui.SVMenuBar;
18 import com.google.scrollview.ui.SVPopupMenu;
19
20 import org.piccolo2d.PCamera;
21 import org.piccolo2d.PCanvas;
22 import org.piccolo2d.PLayer;
23 import org.piccolo2d.extras.swing.PScrollPane;
24 import org.piccolo2d.nodes.PImage;
25 import org.piccolo2d.nodes.PPath;
26 import org.piccolo2d.nodes.PText;
27 import org.piccolo2d.util.PPaintContext;
28
29 import java.awt.BasicStroke;
30 import java.awt.BorderLayout;
31 import java.awt.Color;
32 import java.awt.Font;
33 import java.awt.GraphicsEnvironment;
34 import java.awt.Rectangle;
35 import java.awt.TextArea;
36 import java.awt.geom.IllegalPathStateException;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40 import javax.swing.JFrame;
41 import javax.swing.JOptionPane;
42 import javax.swing.SwingUtilities;
43 import javax.swing.WindowConstants;
44
45 /**
46 * The SVWindow is the top-level ui class. It should get instantiated whenever
47 * the user intends to create a new window. It contains helper functions to draw
48 * on the canvas, add new menu items, show modal dialogs etc.
49 *
50 * @author wanke@google.com
51 */
52 public class SVWindow extends JFrame {
53 /**
54 * Constants defining the maximum initial size of the window.
55 */
56 private static final int MAX_WINDOW_X = 1000;
57 private static final int MAX_WINDOW_Y = 800;
58
59 /* Constant defining the (approx) height of the default message box*/
60 private static final int DEF_MESSAGEBOX_HEIGHT = 200;
61
62 /** Constant defining the "speed" at which to zoom in and out. */
63 public static final double SCALING_FACTOR = 2;
64
65 /** The top level layer we add our PNodes to (root node). */
66 PLayer layer;
67
68 /** The current color of the pen. It is used to draw edges, text, etc. */
69 Color currentPenColor;
70
71 /**
72 * The current color of the brush. It is used to draw the interior of
73 * primitives.
74 */
75 Color currentBrushColor;
76
77 /** The system name of the current font we are using (e.g.
78 * "Times New Roman"). */
79 Font currentFont;
80
81 /** The stroke width to be used. */
82 // This really needs to be a fixed width stroke as the basic stroke is
83 // anti-aliased and gets too faint, but the piccolo fixed width stroke
84 // is too buggy and generates missing initial moveto in path definition
85 // errors with an IllegalPathStateException that cannot be caught because
86 // it is in the automatic repaint function. If we can fix the exceptions
87 // in piccolo, then we can use the following instead of BasicStroke:
88 // import edu.umd.cs.piccolox.util.PFixedWidthStroke;
89 // PFixedWidthStroke stroke = new PFixedWidthStroke(0.5f);
90 // Instead we use the BasicStroke and turn off anti-aliasing.
91 BasicStroke stroke = new BasicStroke(0.5f);
92
93 /**
94 * A unique representation for the window, also known by the client. It is
95 * used when sending messages from server to client to identify him.
96 */
97 public int hash;
98
99 /**
100 * The total number of created Windows. If this ever reaches 0 (apart from the
101 * beginning), quit the server.
102 */
103 public static int nrWindows = 0;
104
105 /**
106 * The Canvas, MessageBox, EventHandler, Menubar and Popupmenu associated with
107 * this window.
108 */
109 private SVEventHandler svEventHandler = null;
110 private SVMenuBar svMenuBar = null;
111 private TextArea ta = null;
112 public SVPopupMenu svPuMenu = null;
113 public PCanvas canvas;
114 private int winSizeX;
115 private int winSizeY;
116
117 /** Set the brush to an RGB color */
118 public void brush(int red, int green, int blue) {
119 brush(red, green, blue, 255);
120 }
121
122 /** Set the brush to an RGBA color */
123 public void brush(int red, int green, int blue, int alpha) {
124 // If alpha is zero, use a null brush to save rendering time.
125 if (alpha == 0) {
126 currentBrushColor = null;
127 } else {
128 currentBrushColor = new Color(red, green, blue, alpha);
129 }
130 }
131
132 /** Erase all content from the window, but do not destroy it. */
133 public void clear() {
134 // Manipulation of Piccolo's scene graph should be done from Swings
135 // event dispatch thread since Piccolo is not thread safe. This code calls
136 // removeAllChildren() from that thread and releases the latch.
137 final java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(1);
138 SwingUtilities.invokeLater(new Runnable() {
139 public void run() {
140 layer.removeAllChildren();
141 repaint();
142 latch.countDown();
143 }
144 });
145 try {
146 latch.await();
147 } catch (InterruptedException e) {
148 }
149 }
150
151 /**
152 * Start setting up a new polyline. The server will now expect
153 * polyline data until the polyline is complete.
154 *
155 * @param length number of coordinate pairs
156 */
157 public void createPolyline(int length) {
158 ScrollView.polylineXCoords = new float[length];
159 ScrollView.polylineYCoords = new float[length];
160 ScrollView.polylineSize = length;
161 ScrollView.polylineScanned = 0;
162 }
163
164 /**
165 * Draw the now complete polyline.
166 */
167 public void drawPolyline() {
168 int numCoords = ScrollView.polylineXCoords.length;
169 if (numCoords < 2) {
170 return;
171 }
172 PPath pn = PPath.createLine(ScrollView.polylineXCoords[0],
173 ScrollView.polylineYCoords[0],
174 ScrollView.polylineXCoords[1],
175 ScrollView.polylineYCoords[1]);
176 pn.reset();
177 pn.moveTo(ScrollView.polylineXCoords[0], ScrollView.polylineYCoords[0]);
178 for (int p = 1; p < numCoords; ++p) {
179 pn.lineTo(ScrollView.polylineXCoords[p], ScrollView.polylineYCoords[p]);
180 }
181 pn.closePath();
182 ScrollView.polylineSize = 0;
183 pn.setStrokePaint(currentPenColor);
184 pn.setPaint(null); // Don't fill the polygon - this is just a polyline.
185 pn.setStroke(stroke);
186 layer.addChild(pn);
187 }
188
189 /**
190 * Construct a new SVWindow and set it visible.
191 *
192 * @param name Title of the window.
193 * @param hash Unique internal representation. This has to be the same as
194 * defined by the client, as they use this to refer to the windows.
195 * @param posX X position of where to draw the window (upper left).
196 * @param posY Y position of where to draw the window (upper left).
197 * @param sizeX The width of the window.
198 * @param sizeY The height of the window.
199 * @param canvasSizeX The canvas width of the window.
200 * @param canvasSizeY The canvas height of the window.
201 */
202 public SVWindow(String name, int hash, int posX, int posY, int sizeX,
203 int sizeY, int canvasSizeX, int canvasSizeY) {
204 super(name);
205
206 // Provide defaults for sizes.
207 if (sizeX <= 0) sizeX = canvasSizeX;
208 if (sizeY <= 0) sizeY = canvasSizeY;
209 if (canvasSizeX <= 0) canvasSizeX = sizeX;
210 if (canvasSizeY <= 0) canvasSizeY = sizeY;
211
212 // Avoid later division by zero.
213 if (sizeX <= 0) {
214 sizeX = 1;
215 canvasSizeX = sizeX;
216 }
217 if (sizeY <= 0) {
218 sizeY = 1;
219 canvasSizeY = sizeY;
220 }
221
222 // Initialize variables
223 nrWindows++;
224 this.hash = hash;
225 this.svEventHandler = new SVEventHandler(this);
226 this.currentPenColor = Color.BLACK;
227 this.currentBrushColor = Color.BLACK;
228 this.currentFont = new Font("Times New Roman", Font.PLAIN, 12);
229
230 // Determine the initial size and zoom factor of the window.
231 // If the window is too big, rescale it and zoom out.
232 int shrinkfactor = 1;
233
234 if (sizeX > MAX_WINDOW_X) {
235 shrinkfactor = (sizeX + MAX_WINDOW_X - 1) / MAX_WINDOW_X;
236 }
237 if (sizeY / shrinkfactor > MAX_WINDOW_Y) {
238 shrinkfactor = (sizeY + MAX_WINDOW_Y - 1) / MAX_WINDOW_Y;
239 }
240 winSizeX = sizeX / shrinkfactor;
241 winSizeY = sizeY / shrinkfactor;
242 double initialScalingfactor = 1.0 / shrinkfactor;
243 if (winSizeX > canvasSizeX || winSizeY > canvasSizeY) {
244 initialScalingfactor = Math.min(1.0 * winSizeX / canvasSizeX,
245 1.0 * winSizeY / canvasSizeY);
246 }
247
248 // Setup the actual window (its size, camera, title, etc.)
249 if (canvas == null) {
250 canvas = new PCanvas();
251 getContentPane().add(canvas, BorderLayout.CENTER);
252 }
253
254 layer = canvas.getLayer();
255 canvas.setBackground(Color.BLACK);
256
257 // Disable antialiasing to make the lines more visible.
258 canvas.setDefaultRenderQuality(PPaintContext.LOW_QUALITY_RENDERING);
259
260 setLayout(new BorderLayout());
261
262 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
263
264 validate();
265 canvas.requestFocus();
266
267 // Manipulation of Piccolo's scene graph should be done from Swings
268 // event dispatch thread since Piccolo is not thread safe. This code calls
269 // initialize() from that thread once the PFrame is initialized, so you are
270 // safe to start working with Piccolo in the initialize() method.
271 SwingUtilities.invokeLater(new Runnable() {
272 public void run() {
273 repaint();
274 }
275 });
276
277 setSize(winSizeX, winSizeY);
278 setLocation(posX, posY);
279 setTitle(name);
280
281 // Add a Scrollpane to be able to scroll within the canvas
282 PScrollPane scrollPane = new PScrollPane(canvas);
283 getContentPane().add(scrollPane);
284 scrollPane.setWheelScrollingEnabled(false);
285 PCamera lc = canvas.getCamera();
286 lc.scaleViewAboutPoint(initialScalingfactor, 0, 0);
287
288 // Disable the default event handlers and add our own.
289 addWindowListener(svEventHandler);
290 canvas.removeInputEventListener(canvas.getPanEventHandler());
291 canvas.removeInputEventListener(canvas.getZoomEventHandler());
292 canvas.addInputEventListener(svEventHandler);
293 canvas.addKeyListener(svEventHandler);
294
295 // Make the window visible.
296 validate();
297 setVisible(true);
298
299 }
300
301 /**
302 * Convenience function to add a message box to the window which can be used
303 * to output debug information.
304 */
305 public void addMessageBox() {
306 if (ta == null) {
307 ta = new TextArea();
308 ta.setEditable(false);
309 getContentPane().add(ta, BorderLayout.SOUTH);
310 }
311 // We need to make the window bigger to accommodate the message box.
312 winSizeY += DEF_MESSAGEBOX_HEIGHT;
313 setSize(winSizeX, winSizeY);
314 }
315
316 /**
317 * Allows you to specify the thickness with which to draw lines, recantgles
318 * and ellipses.
319 * @param width The new thickness.
320 */
321 public void setStrokeWidth(float width) {
322 // If this worked we wouldn't need the antialiased rendering off.
323 // stroke = new PFixedWidthStroke(width);
324 stroke = new BasicStroke(width);
325 }
326
327 /**
328 * Draw an ellipse at (x,y) with given width and height, using the
329 * current stroke, the current brush color to fill it and the
330 * current pen color for the outline.
331 */
332 public void drawEllipse(int x, int y, int width, int height) {
333 PPath pn = PPath.createEllipse(x, y, width, height);
334 pn.setStrokePaint(currentPenColor);
335 pn.setStroke(stroke);
336 pn.setPaint(currentBrushColor);
337 layer.addChild(pn);
338 }
339
340 /**
341 * Draw the image with the given name at (x,y). Any image loaded stays in
342 * memory, so if you intend to redraw an image, you do not have to use
343 * createImage again.
344 */
345 public void drawImage(PImage img, int xPos, int yPos) {
346 img.setX(xPos);
347 img.setY(yPos);
348 layer.addChild(img);
349 }
350
351 /**
352 * Draw a line from (x1,y1) to (x2,y2) using the current pen color and stroke.
353 */
354 public void drawLine(int x1, int y1, int x2, int y2) {
355 PPath pn = PPath.createLine(x1, y1, x2, y2);
356 pn.setStrokePaint(currentPenColor);
357 pn.setPaint(null); // Null paint may render faster than the default.
358 pn.setStroke(stroke);
359 pn.moveTo(x1, y1);
360 pn.lineTo(x2, y2);
361 layer.addChild(pn);
362 }
363
364 /**
365 * Draw a rectangle given the two points (x1,y1) and (x2,y2) using the current
366 * stroke, pen color for the border and the brush to fill the
367 * interior.
368 */
369 public void drawRectangle(int x1, int y1, int x2, int y2) {
370
371 if (x1 > x2) {
372 int t = x1;
373 x1 = x2;
374 x2 = t;
375 }
376 if (y1 > y2) {
377 int t = y1;
378 y1 = y2;
379 y2 = t;
380 }
381
382 PPath pn = PPath.createRectangle(x1, y1, x2 - x1, y2 - y1);
383 pn.setStrokePaint(currentPenColor);
384 pn.setStroke(stroke);
385 pn.setPaint(currentBrushColor);
386 layer.addChild(pn);
387 }
388
389 /**
390 * Draw some text at (x,y) using the current pen color and text attributes. If
391 * the current font does NOT support at least one character, it tries to find
392 * a font which is capable of displaying it and use that to render the text.
393 * Note: If the font says it can render a glyph, but in reality it turns out
394 * to be crap, there is nothing we can do about it.
395 */
396 public void drawText(int x, int y, String text) {
397 int unreadableCharAt = -1;
398 char[] chars = text.toCharArray();
399 PText pt = new PText(text);
400 pt.setTextPaint(currentPenColor);
401 pt.setFont(currentFont);
402
403 // Check to see if every character can be displayed by the current font.
404 for (int i = 0; i < chars.length; i++) {
405 if (!currentFont.canDisplay(chars[i])) {
406 // Set to the first not displayable character.
407 unreadableCharAt = i;
408 break;
409 }
410 }
411
412 // Have to find some working font and use it for this text entry.
413 if (unreadableCharAt != -1) {
414 Font[] allfonts =
415 GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
416 for (int j = 0; j < allfonts.length; j++) {
417 if (allfonts[j].canDisplay(chars[unreadableCharAt])) {
418 Font tempFont =
419 new Font(allfonts[j].getFontName(), currentFont.getStyle(),
420 currentFont.getSize());
421 pt.setFont(tempFont);
422 break;
423 }
424 }
425 }
426
427 pt.setX(x);
428 pt.setY(y);
429 layer.addChild(pt);
430 }
431
432 /** Set the pen color to an RGB value */
433 public void pen(int red, int green, int blue) {
434 pen(red, green, blue, 255);
435 }
436
437 /** Set the pen color to an RGBA value */
438 public void pen(int red, int green, int blue, int alpha) {
439 currentPenColor = new Color(red, green, blue, alpha);
440 }
441
442 /**
443 * Define how to display text. Note: underlined is not currently not supported
444 */
445 public void textAttributes(String font, int pixelSize, boolean bold,
446 boolean italic, boolean underlined) {
447
448 // For legacy reasons convert "Times" to "Times New Roman"
449 if (font.equals("Times")) {
450 font = "Times New Roman";
451 }
452
453 int style = Font.PLAIN;
454 if (bold) {
455 style += Font.BOLD;
456 }
457 if (italic) {
458 style += Font.ITALIC;
459 }
460 currentFont = new Font(font, style, pixelSize);
461 }
462
463 /**
464 * Zoom the window to the rectangle given the two points (x1,y1)
465 * and (x2,y2), which must be greater than (x1,y1).
466 */
467 public void zoomRectangle(int x1, int y1, int x2, int y2) {
468 if (x2 > x1 && y2 > y1) {
469 winSizeX = getWidth();
470 winSizeY = getHeight();
471 int width = x2 - x1;
472 int height = y2 - y1;
473 // Since piccolo doesn't do this well either, pad with a margin
474 // all the way around.
475 int wmargin = width / 2;
476 int hmargin = height / 2;
477 double scalefactor = Math.min(winSizeX / (2.0 * wmargin + width),
478 winSizeY / (2.0 * hmargin + height));
479 PCamera lc = canvas.getCamera();
480 lc.scaleView(scalefactor / lc.getViewScale());
481 lc.animateViewToPanToBounds(new Rectangle(x1 - hmargin, y1 - hmargin,
482 2 * wmargin + width,
483 2 * hmargin + height), 0);
484 }
485 }
486
487 /**
488 * Flush buffers and update display.
489 *
490 * Only actually reacts if there are no more messages in the stack, to prevent
491 * the canvas from flickering.
492 */
493 public void update() {
494 // TODO(rays) fix bugs in piccolo or use something else.
495 // The repaint function generates many
496 // exceptions for no good reason. We catch and ignore as many as we
497 // can here, but most of them are generated by the system repaints
498 // caused by resizing/exposing parts of the window etc, and they
499 // generate unwanted stack traces that have to be piped to /dev/null
500 // (on linux).
501 try {
502 repaint();
503 } catch (NullPointerException e) {
504 // Do nothing so the output isn't full of stack traces.
505 } catch (IllegalPathStateException e) {
506 // Do nothing so the output isn't full of stack traces.
507 }
508 }
509
510 /** Adds a checkbox entry to the menubar, c.f. SVMenubar.add(...) */
511 public void addMenuBarItem(String parent, String name, int id,
512 boolean checked) {
513 svMenuBar.add(parent, name, id, checked);
514 }
515
516 /** Adds a submenu to the menubar, c.f. SVMenubar.add(...) */
517 public void addMenuBarItem(String parent, String name) {
518 addMenuBarItem(parent, name, -1);
519 }
520
521 /** Adds a new entry to the menubar, c.f. SVMenubar.add(...) */
522 public void addMenuBarItem(String parent, String name, int id) {
523 if (svMenuBar == null) {
524 svMenuBar = new SVMenuBar(this);
525
526 }
527 svMenuBar.add(parent, name, id);
528 }
529
530 /** Add a message to the message box. */
531 public void addMessage(String message) {
532 if (ta != null) {
533 ta.append(message + "\n");
534 } else {
535 System.out.println(message + "\n");
536 }
537 }
538
539 /**
540 * This method converts a string which might contain hexadecimal values to a
541 * string which contains the respective unicode counterparts.
542 *
543 * For example, Hall0x0094chen returns Hall<o umlaut>chen
544 * encoded as utf8.
545 *
546 * @param input The original string, containing 0x values
547 * @return The converted string which has the replaced unicode symbols
548 */
549 private static String convertIntegerStringToUnicodeString(String input) {
550 StringBuffer sb = new StringBuffer(input);
551 Pattern numbers = Pattern.compile("0x[0-9a-fA-F]{4}");
552 Matcher matcher = numbers.matcher(sb);
553
554 while (matcher.find()) {
555 // Find the next match which resembles a hexadecimal value and convert it
556 // to
557 // its char value
558 char a = (char) (Integer.decode(matcher.group()).intValue());
559
560 // Replace the original with the new character
561 sb.replace(matcher.start(), matcher.end(), String.valueOf(a));
562
563 // Start again, since our positions have switched
564 matcher.reset();
565 }
566 return sb.toString();
567 }
568
569 /**
570 * Show a modal input dialog. The answer by the dialog is then send to the
571 * client, together with the associated menu id, as SVET_POPUP
572 *
573 * @param msg The text that is displayed in the dialog.
574 * @param def The default value of the dialog.
575 * @param id The associated commandId
576 * @param evtype The event this is associated with (usually SVET_MENU
577 * or SVET_POPUP)
578 */
579 public void showInputDialog(String msg, String def, int id,
580 SVEventType evtype) {
581 svEventHandler.timer.stop();
582 String tmp =
583 (String) JOptionPane.showInputDialog(this, msg, "",
584 JOptionPane.QUESTION_MESSAGE, null, null, def);
585
586 if (tmp != null) {
587 tmp = convertIntegerStringToUnicodeString(tmp);
588 SVEvent res = new SVEvent(evtype, this, id, tmp);
589 ScrollView.addMessage(res);
590 }
591 svEventHandler.timer.restart();
592 }
593
594
595 /**
596 * Shows a modal input dialog to the user. The return value is automatically
597 * sent to the client as SVET_INPUT event (with command id -1).
598 *
599 * @param msg The text of the dialog.
600 */
601 public void showInputDialog(String msg) {
602 showInputDialog(msg, null, -1, SVEventType.SVET_INPUT);
603 }
604
605 /**
606 * Shows a dialog presenting "Yes" and "No" as answers and returns either a
607 * "y" or "n" to the client.
608 *
609 * Closing the dialog without answering is handled like "No".
610 *
611 * @param msg The text that is displayed in the dialog.
612 */
613 public void showYesNoDialog(String msg) {
614 // res returns 0 on yes, 1 on no. Seems to be a bit counterintuitive
615 int res =
616 JOptionPane.showOptionDialog(this, msg, "", JOptionPane.YES_NO_OPTION,
617 JOptionPane.QUESTION_MESSAGE, null, null, null);
618
619 SVEvent e = new SVEvent(SVEventType.SVET_INPUT, this, 0, 0, 0, 0,
620 res == 0 ? "y" : "n");
621 ScrollView.addMessage(e);
622 }
623
624 /** Adds a submenu to the popup menu, c.f. SVPopupMenu.add(...) */
625 public void addPopupMenuItem(String parent, String name) {
626 if (svPuMenu == null) {
627 svPuMenu = new SVPopupMenu(this);
628 }
629 svPuMenu.add(parent, name, -1);
630 }
631
632 /** Adds a new menu entry to the popup menu, c.f. SVPopupMenu.add(...) */
633 public void addPopupMenuItem(String parent, String name, int cmdEvent,
634 String value, String desc) {
635 if (svPuMenu == null) {
636 svPuMenu = new SVPopupMenu(this);
637 }
638 svPuMenu.add(parent, name, cmdEvent, value, desc);
639 }
640
641 /** Destroys a window. */
642 public void destroy() {
643 ScrollView.addMessage(new SVEvent(SVEventType.SVET_DESTROY, this, 0,
644 "SVET_DESTROY"));
645 setVisible(false);
646 // dispose();
647 }
648 }