Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/tesseract/src/viewer/scrollview.cpp @ 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/src/viewer/scrollview.cpp Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,833 @@ +/////////////////////////////////////////////////////////////////////// +// File: scrollview.cpp +// Description: ScrollView +// Author: Joern Wanke +// +// (C) Copyright 2007, Google Inc. +// 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. +// +/////////////////////////////////////////////////////////////////////// +// + +// Include automatically generated configuration file if running autoconf. +#ifdef HAVE_CONFIG_H +# include "config_auto.h" +#endif + +#include "scrollview.h" + +#include "svutil.h" // for SVNetwork + +#include <allheaders.h> + +#include <algorithm> +#include <climits> +#include <cstdarg> +#include <cstring> +#include <map> +#include <memory> // for std::unique_ptr +#include <mutex> // for std::mutex +#include <string> +#include <thread> // for std::thread +#include <utility> +#include <vector> + +namespace tesseract { + +const int kSvPort = 8461; +const int kMaxMsgSize = 4096; +const int kMaxIntPairSize = 45; // Holds %d,%d, for up to 64 bit. + +struct SVPolyLineBuffer { + bool empty; // Independent indicator to allow SendMsg to call SendPolygon. + std::vector<int> xcoords; + std::vector<int> ycoords; +}; + +// A map between the window IDs and their corresponding pointers. +static std::map<int, ScrollView *> svmap; +static std::mutex *svmap_mu; +// A map of all semaphores waiting for a specific event on a specific window. +static std::map<std::pair<ScrollView *, SVEventType>, + std::pair<SVSemaphore *, std::unique_ptr<SVEvent>>> waiting_for_events; +static std::mutex *waiting_for_events_mu; + +std::unique_ptr<SVEvent> SVEvent::copy() const { + auto any = std::unique_ptr<SVEvent>(new SVEvent); + any->command_id = command_id; + any->counter = counter; + any->parameter = new char[strlen(parameter) + 1]; + strcpy(any->parameter, parameter); + any->type = type; + any->x = x; + any->y = y; + any->x_size = x_size; + any->y_size = y_size; + any->window = window; + return any; +} + +// Destructor. +// It is defined here, so the compiler can create a single vtable +// instead of weak vtables in every compilation unit. +SVEventHandler::~SVEventHandler() = default; + +#ifndef GRAPHICS_DISABLED +/// This is the main loop which handles the ScrollView-logic from the server +/// to the client. It basically loops through messages, parses them to events +/// and distributes it to the waiting handlers. +/// It is run from a different thread and synchronizes via SVSync. +void ScrollView::MessageReceiver() { + int counter_event_id = 0; // ongoing counter + char *message = nullptr; + // Wait until a new message appears in the input stream_. + do { + message = ScrollView::GetStream()->Receive(); + } while (message == nullptr); + + // This is the main loop which iterates until the server is dead (strlen = + // -1). It basically parses for 3 different messagetypes and then distributes + // the events accordingly. + while (true) { + // The new event we create. + std::unique_ptr<SVEvent> cur(new SVEvent); + // The ID of the corresponding window. + int window_id; + + int ev_type; + + int n; + // Fill the new SVEvent properly. + sscanf(message, "%d,%d,%d,%d,%d,%d,%d,%n", &window_id, &ev_type, &cur->x, &cur->y, &cur->x_size, + &cur->y_size, &cur->command_id, &n); + char *p = (message + n); + + svmap_mu->lock(); + cur->window = svmap[window_id]; + + if (cur->window != nullptr) { + auto length = strlen(p); + cur->parameter = new char[length + 1]; + strcpy(cur->parameter, p); + if (length > 0) { // remove the last \n + cur->parameter[length - 1] = '\0'; + } + cur->type = static_cast<SVEventType>(ev_type); + // Correct selection coordinates so x,y is the min pt and size is +ve. + if (cur->x_size > 0) { + cur->x -= cur->x_size; + } else { + cur->x_size = -cur->x_size; + } + if (cur->y_size > 0) { + cur->y -= cur->y_size; + } else { + cur->y_size = -cur->y_size; + } + // Returned y will be the bottom-left if y is reversed. + if (cur->window->y_axis_is_reversed_) { + cur->y = cur->window->TranslateYCoordinate(cur->y + cur->y_size); + } + cur->counter = counter_event_id; + // Increase by 2 since we will also create an SVET_ANY event from cur, + // which will have a counter_id of cur + 1 (and thus gets processed + // after cur). + counter_event_id += 2; + + // In case of an SVET_EXIT event, quit the whole application. + if (ev_type == SVET_EXIT) { + SendRawMessage("svmain:exit()"); + break; + } + + // Place two copies of it in the table for the window. + cur->window->SetEvent(cur.get()); + + // Check if any of the threads currently waiting want it. + std::pair<ScrollView *, SVEventType> awaiting_list(cur->window, cur->type); + std::pair<ScrollView *, SVEventType> awaiting_list_any(cur->window, SVET_ANY); + std::pair<ScrollView *, SVEventType> awaiting_list_any_window((ScrollView *)nullptr, + SVET_ANY); + waiting_for_events_mu->lock(); + if (waiting_for_events.count(awaiting_list) > 0) { + waiting_for_events[awaiting_list].second = std::move(cur); + waiting_for_events[awaiting_list].first->Signal(); + } else if (waiting_for_events.count(awaiting_list_any) > 0) { + waiting_for_events[awaiting_list_any].second = std::move(cur); + waiting_for_events[awaiting_list_any].first->Signal(); + } else if (waiting_for_events.count(awaiting_list_any_window) > 0) { + waiting_for_events[awaiting_list_any_window].second = std::move(cur); + waiting_for_events[awaiting_list_any_window].first->Signal(); + } + waiting_for_events_mu->unlock(); + // Signal the corresponding semaphore twice (for both copies). + ScrollView *sv = svmap[window_id]; + if (sv != nullptr) { + sv->Signal(); + sv->Signal(); + } + } + svmap_mu->unlock(); + + // Wait until a new message appears in the input stream_. + do { + message = ScrollView::GetStream()->Receive(); + } while (message == nullptr); + } +} + +// Table to implement the color index values in the old system. +static const uint8_t table_colors[ScrollView::GREEN_YELLOW + 1][4] = { + {0, 0, 0, 0}, // NONE (transparent) + {0, 0, 0, 255}, // BLACK. + {255, 255, 255, 255}, // WHITE. + {255, 0, 0, 255}, // RED. + {255, 255, 0, 255}, // YELLOW. + {0, 255, 0, 255}, // GREEN. + {0, 255, 255, 255}, // CYAN. + {0, 0, 255, 255}, // BLUE. + {255, 0, 255, 255}, // MAGENTA. + {0, 128, 255, 255}, // AQUAMARINE. + {0, 0, 64, 255}, // DARK_SLATE_BLUE. + {128, 128, 255, 255}, // LIGHT_BLUE. + {64, 64, 255, 255}, // MEDIUM_BLUE. + {0, 0, 32, 255}, // MIDNIGHT_BLUE. + {0, 0, 128, 255}, // NAVY_BLUE. + {192, 192, 255, 255}, // SKY_BLUE. + {64, 64, 128, 255}, // SLATE_BLUE. + {32, 32, 64, 255}, // STEEL_BLUE. + {255, 128, 128, 255}, // CORAL. + {128, 64, 0, 255}, // BROWN. + {128, 128, 0, 255}, // SANDY_BROWN. + {192, 192, 0, 255}, // GOLD. + {192, 192, 128, 255}, // GOLDENROD. + {0, 64, 0, 255}, // DARK_GREEN. + {32, 64, 0, 255}, // DARK_OLIVE_GREEN. + {64, 128, 0, 255}, // FOREST_GREEN. + {128, 255, 0, 255}, // LIME_GREEN. + {192, 255, 192, 255}, // PALE_GREEN. + {192, 255, 0, 255}, // YELLOW_GREEN. + {192, 192, 192, 255}, // LIGHT_GREY. + {64, 64, 128, 255}, // DARK_SLATE_GREY. + {64, 64, 64, 255}, // DIM_GREY. + {128, 128, 128, 255}, // GREY. + {64, 192, 0, 255}, // KHAKI. + {255, 0, 192, 255}, // MAROON. + {255, 128, 0, 255}, // ORANGE. + {255, 128, 64, 255}, // ORCHID. + {255, 192, 192, 255}, // PINK. + {128, 0, 128, 255}, // PLUM. + {255, 0, 64, 255}, // INDIAN_RED. + {255, 64, 0, 255}, // ORANGE_RED. + {255, 0, 192, 255}, // VIOLET_RED. + {255, 192, 128, 255}, // SALMON. + {128, 128, 0, 255}, // TAN. + {0, 255, 255, 255}, // TURQUOISE. + {0, 128, 128, 255}, // DARK_TURQUOISE. + {192, 0, 255, 255}, // VIOLET. + {128, 128, 0, 255}, // WHEAT. + {128, 255, 0, 255} // GREEN_YELLOW +}; + +/******************************************************************************* + * Scrollview implementation. + *******************************************************************************/ + +SVNetwork *ScrollView::stream_ = nullptr; +int ScrollView::nr_created_windows_ = 0; +int ScrollView::image_index_ = 0; + +/// Calls Initialize with all arguments given. +ScrollView::ScrollView(const char *name, int x_pos, int y_pos, int x_size, int y_size, + int x_canvas_size, int y_canvas_size, bool y_axis_reversed, + const char *server_name) { + Initialize(name, x_pos, y_pos, x_size, y_size, x_canvas_size, y_canvas_size, y_axis_reversed, + server_name); +} + +/// Calls Initialize with default argument for server_name_. +ScrollView::ScrollView(const char *name, int x_pos, int y_pos, int x_size, int y_size, + int x_canvas_size, int y_canvas_size, bool y_axis_reversed) { + Initialize(name, x_pos, y_pos, x_size, y_size, x_canvas_size, y_canvas_size, y_axis_reversed, + "localhost"); +} + +/// Calls Initialize with default argument for server_name_ & y_axis_reversed. +ScrollView::ScrollView(const char *name, int x_pos, int y_pos, int x_size, int y_size, + int x_canvas_size, int y_canvas_size) { + Initialize(name, x_pos, y_pos, x_size, y_size, x_canvas_size, y_canvas_size, false, "localhost"); +} + +/// Sets up a ScrollView window, depending on the constructor variables. +void ScrollView::Initialize(const char *name, int x_pos, int y_pos, int x_size, int y_size, + int x_canvas_size, int y_canvas_size, bool y_axis_reversed, + const char *server_name) { + // If this is the first ScrollView Window which gets created, there is no + // network connection yet and we have to set it up in a different thread. + if (stream_ == nullptr) { + nr_created_windows_ = 0; + stream_ = new SVNetwork(server_name, kSvPort); + waiting_for_events_mu = new std::mutex(); + svmap_mu = new std::mutex(); + SendRawMessage("svmain = luajava.bindClass('com.google.scrollview.ScrollView')\n"); + std::thread t(&ScrollView::MessageReceiver); + t.detach(); + } + + // Set up the variables on the clientside. + nr_created_windows_++; + event_handler_ = nullptr; + event_handler_ended_ = false; + y_axis_is_reversed_ = y_axis_reversed; + y_size_ = y_canvas_size; + window_name_ = name; + window_id_ = nr_created_windows_; + // Set up polygon buffering. + points_ = new SVPolyLineBuffer; + points_->empty = true; + + svmap_mu->lock(); + svmap[window_id_] = this; + svmap_mu->unlock(); + + for (auto &i : event_table_) { + i = nullptr; + } + + semaphore_ = new SVSemaphore(); + + // Set up an actual Window on the client side. + char message[kMaxMsgSize]; + snprintf(message, sizeof(message), + "w%d = luajava.newInstance('com.google.scrollview.ui" + ".SVWindow','%s',%u,%u,%u,%u,%u,%u,%u)\n", + window_id_, window_name_, window_id_, x_pos, y_pos, x_size, y_size, x_canvas_size, + y_canvas_size); + SendRawMessage(message); + + std::thread t(&ScrollView::StartEventHandler, this); + t.detach(); +} + +/// Sits and waits for events on this window. +void ScrollView::StartEventHandler() { + for (;;) { + stream_->Flush(); + semaphore_->Wait(); + int serial = -1; + int k = -1; + mutex_.lock(); + // Check every table entry if it is valid and not already processed. + + for (int i = 0; i < SVET_COUNT; i++) { + if (event_table_[i] != nullptr && (serial < 0 || event_table_[i]->counter < serial)) { + serial = event_table_[i]->counter; + k = i; + } + } + // If we didn't find anything we had an old alarm and just sleep again. + if (k != -1) { + auto new_event = std::move(event_table_[k]); + mutex_.unlock(); + if (event_handler_ != nullptr) { + event_handler_->Notify(new_event.get()); + } + if (new_event->type == SVET_DESTROY) { + // Signal the destructor that it is safe to terminate. + event_handler_ended_ = true; + return; + } + } else { + mutex_.unlock(); + } + // The thread should run as long as its associated window is alive. + } +} +#endif // !GRAPHICS_DISABLED + +ScrollView::~ScrollView() { +#ifndef GRAPHICS_DISABLED + svmap_mu->lock(); + if (svmap[window_id_] != nullptr) { + svmap_mu->unlock(); + // So the event handling thread can quit. + SendMsg("destroy()"); + + AwaitEvent(SVET_DESTROY); + svmap_mu->lock(); + svmap[window_id_] = nullptr; + svmap_mu->unlock(); + // The event handler thread for this window *must* receive the + // destroy event and set its pointer to this to nullptr before we allow + // the destructor to exit. + while (!event_handler_ended_) { + Update(); + } + } else { + svmap_mu->unlock(); + } + delete semaphore_; + delete points_; +#endif // !GRAPHICS_DISABLED +} + +#ifndef GRAPHICS_DISABLED +/// Send a message to the server, attaching the window id. +void ScrollView::SendMsg(const char *format, ...) { + if (!points_->empty) { + SendPolygon(); + } + va_list args; + char message[kMaxMsgSize - 4]; + + va_start(args, format); // variable list + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + char form[kMaxMsgSize]; + snprintf(form, sizeof(form), "w%d:%s\n", window_id_, message); + + stream_->Send(form); +} + +/// Send a message to the server without a +/// window id. Used for global events like exit(). +void ScrollView::SendRawMessage(const char *msg) { + stream_->Send(msg); +} + +/// Add an Event Listener to this ScrollView Window +void ScrollView::AddEventHandler(SVEventHandler *listener) { + event_handler_ = listener; +} + +void ScrollView::Signal() { + semaphore_->Signal(); +} + +void ScrollView::SetEvent(const SVEvent *svevent) { + // Copy event + auto any = svevent->copy(); + auto specific = svevent->copy(); + any->counter = specific->counter + 1; + + // Place both events into the queue. + std::lock_guard<std::mutex> guard(mutex_); + + event_table_[specific->type] = std::move(specific); + event_table_[SVET_ANY] = std::move(any); +} + +/// Block until an event of the given type is received. +/// Note: The calling function is responsible for deleting the returned +/// SVEvent afterwards! +std::unique_ptr<SVEvent> ScrollView::AwaitEvent(SVEventType type) { + // Initialize the waiting semaphore. + auto *sem = new SVSemaphore(); + std::pair<ScrollView *, SVEventType> ea(this, type); + waiting_for_events_mu->lock(); + waiting_for_events[ea] = {sem, nullptr}; + waiting_for_events_mu->unlock(); + // Wait on it, but first flush. + stream_->Flush(); + sem->Wait(); + // Process the event we got woken up for (its in waiting_for_events pair). + waiting_for_events_mu->lock(); + auto ret = std::move(waiting_for_events[ea].second); + waiting_for_events.erase(ea); + delete sem; + waiting_for_events_mu->unlock(); + return ret; +} + +// Send the current buffered polygon (if any) and clear it. +void ScrollView::SendPolygon() { + if (!points_->empty) { + points_->empty = true; // Allows us to use SendMsg. + int length = points_->xcoords.size(); + // length == 1 corresponds to 2 SetCursors in a row and only the + // last setCursor has any effect. + if (length == 2) { + // An isolated line! + SendMsg("drawLine(%d,%d,%d,%d)", points_->xcoords[0], points_->ycoords[0], + points_->xcoords[1], points_->ycoords[1]); + } else if (length > 2) { + // A polyline. + SendMsg("createPolyline(%d)", length); + char coordpair[kMaxIntPairSize]; + std::string decimal_coords; + for (int i = 0; i < length; ++i) { + snprintf(coordpair, kMaxIntPairSize, "%d,%d,", points_->xcoords[i], points_->ycoords[i]); + decimal_coords += coordpair; + } + decimal_coords += '\n'; + SendRawMessage(decimal_coords.c_str()); + SendMsg("drawPolyline()"); + } + points_->xcoords.clear(); + points_->ycoords.clear(); + } +} + +/******************************************************************************* + * LUA "API" functions. + *******************************************************************************/ + +// Sets the position from which to draw to (x,y). +void ScrollView::SetCursor(int x, int y) { + SendPolygon(); + DrawTo(x, y); +} + +// Draws from the current position to (x,y) and sets the new position to it. +void ScrollView::DrawTo(int x, int y) { + points_->xcoords.push_back(x); + points_->ycoords.push_back(TranslateYCoordinate(y)); + points_->empty = false; +} + +// Draw a line using the current pen color. +void ScrollView::Line(int x1, int y1, int x2, int y2) { + if (!points_->xcoords.empty() && x1 == points_->xcoords.back() && + TranslateYCoordinate(y1) == points_->ycoords.back()) { + // We are already at x1, y1, so just draw to x2, y2. + DrawTo(x2, y2); + } else if (!points_->xcoords.empty() && x2 == points_->xcoords.back() && + TranslateYCoordinate(y2) == points_->ycoords.back()) { + // We are already at x2, y2, so just draw to x1, y1. + DrawTo(x1, y1); + } else { + // This is a new line. + SetCursor(x1, y1); + DrawTo(x2, y2); + } +} + +// Set the visibility of the window. +void ScrollView::SetVisible(bool visible) { + if (visible) { + SendMsg("setVisible(true)"); + } else { + SendMsg("setVisible(false)"); + } +} + +// Set the alwaysOnTop flag. +void ScrollView::AlwaysOnTop(bool b) { + if (b) { + SendMsg("setAlwaysOnTop(true)"); + } else { + SendMsg("setAlwaysOnTop(false)"); + } +} + +// Adds a message entry to the message box. +void ScrollView::AddMessage(const char *message) { + char form[kMaxMsgSize]; + snprintf(form, sizeof(form), "w%d:%s", window_id_, message); + + char *esc = AddEscapeChars(form); + SendMsg("addMessage(\"%s\")", esc); + delete[] esc; +} + +void ScrollView::AddMessageF(const char *format, ...) { + va_list args; + char message[kMaxMsgSize - 4]; + + va_start(args, format); // variable list + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + AddMessage(message); +} + +// Set a messagebox. +void ScrollView::AddMessageBox() { + SendMsg("addMessageBox()"); +} + +// Exit the client completely (and notify the server of it). +void ScrollView::Exit() { + SendRawMessage("svmain:exit()"); + exit(0); +} + +// Clear the canvas. +void ScrollView::Clear() { + SendMsg("clear()"); +} + +// Set the stroke width. +void ScrollView::Stroke(float width) { + SendMsg("setStrokeWidth(%f)", width); +} + +// Draw a rectangle using the current pen color. +// The rectangle is filled with the current brush color. +void ScrollView::Rectangle(int x1, int y1, int x2, int y2) { + if (x1 == x2 && y1 == y2) { + return; // Scrollviewer locks up. + } + SendMsg("drawRectangle(%d,%d,%d,%d)", x1, TranslateYCoordinate(y1), x2, TranslateYCoordinate(y2)); +} + +// Draw an ellipse using the current pen color. +// The ellipse is filled with the current brush color. +void ScrollView::Ellipse(int x1, int y1, int width, int height) { + SendMsg("drawEllipse(%d,%d,%u,%u)", x1, TranslateYCoordinate(y1), width, height); +} + +// Set the pen color to the given RGB values. +void ScrollView::Pen(int red, int green, int blue) { + SendMsg("pen(%d,%d,%d)", red, green, blue); +} + +// Set the pen color to the given RGB values. +void ScrollView::Pen(int red, int green, int blue, int alpha) { + SendMsg("pen(%d,%d,%d,%d)", red, green, blue, alpha); +} + +// Set the brush color to the given RGB values. +void ScrollView::Brush(int red, int green, int blue) { + SendMsg("brush(%d,%d,%d)", red, green, blue); +} + +// Set the brush color to the given RGB values. +void ScrollView::Brush(int red, int green, int blue, int alpha) { + SendMsg("brush(%d,%d,%d,%d)", red, green, blue, alpha); +} + +// Set the attributes for future Text(..) calls. +void ScrollView::TextAttributes(const char *font, int pixel_size, bool bold, bool italic, + bool underlined) { + const char *b; + const char *i; + const char *u; + + if (bold) { + b = "true"; + } else { + b = "false"; + } + if (italic) { + i = "true"; + } else { + i = "false"; + } + if (underlined) { + u = "true"; + } else { + u = "false"; + } + SendMsg("textAttributes('%s',%u,%s,%s,%s)", font, pixel_size, b, i, u); +} + +// Draw text at the given coordinates. +void ScrollView::Text(int x, int y, const char *mystring) { + SendMsg("drawText(%d,%d,'%s')", x, TranslateYCoordinate(y), mystring); +} + +// Open and draw an image given a name at (x,y). +void ScrollView::Draw(const char *image, int x_pos, int y_pos) { + SendMsg("openImage('%s')", image); + SendMsg("drawImage('%s',%d,%d)", image, x_pos, TranslateYCoordinate(y_pos)); +} + +// Add new checkboxmenuentry to menubar. +void ScrollView::MenuItem(const char *parent, const char *name, int cmdEvent, bool flag) { + if (parent == nullptr) { + parent = ""; + } + if (flag) { + SendMsg("addMenuBarItem('%s','%s',%d,true)", parent, name, cmdEvent); + } else { + SendMsg("addMenuBarItem('%s','%s',%d,false)", parent, name, cmdEvent); + } +} + +// Add new menuentry to menubar. +void ScrollView::MenuItem(const char *parent, const char *name, int cmdEvent) { + if (parent == nullptr) { + parent = ""; + } + SendMsg("addMenuBarItem('%s','%s',%d)", parent, name, cmdEvent); +} + +// Add new submenu to menubar. +void ScrollView::MenuItem(const char *parent, const char *name) { + if (parent == nullptr) { + parent = ""; + } + SendMsg("addMenuBarItem('%s','%s')", parent, name); +} + +// Add new submenu to popupmenu. +void ScrollView::PopupItem(const char *parent, const char *name) { + if (parent == nullptr) { + parent = ""; + } + SendMsg("addPopupMenuItem('%s','%s')", parent, name); +} + +// Add new submenuentry to popupmenu. +void ScrollView::PopupItem(const char *parent, const char *name, int cmdEvent, const char *value, + const char *desc) { + if (parent == nullptr) { + parent = ""; + } + char *esc = AddEscapeChars(value); + char *esc2 = AddEscapeChars(desc); + SendMsg("addPopupMenuItem('%s','%s',%d,'%s','%s')", parent, name, cmdEvent, esc, esc2); + delete[] esc; + delete[] esc2; +} + +// Send an update message for a single window. +void ScrollView::UpdateWindow() { + SendMsg("update()"); +} + +// Note: this is an update to all windows +void ScrollView::Update() { + std::lock_guard<std::mutex> guard(*svmap_mu); + for (auto &iter : svmap) { + if (iter.second != nullptr) { + iter.second->UpdateWindow(); + } + } +} + +// Set the pen color, using an enum value (e.g. ScrollView::ORANGE) +void ScrollView::Pen(Color color) { + Pen(table_colors[color][0], table_colors[color][1], table_colors[color][2], + table_colors[color][3]); +} + +// Set the brush color, using an enum value (e.g. ScrollView::ORANGE) +void ScrollView::Brush(Color color) { + Brush(table_colors[color][0], table_colors[color][1], table_colors[color][2], + table_colors[color][3]); +} + +// Shows a modal Input Dialog which can return any kind of String +char *ScrollView::ShowInputDialog(const char *msg) { + SendMsg("showInputDialog(\"%s\")", msg); + // wait till an input event (all others are thrown away) + auto ev = AwaitEvent(SVET_INPUT); + char *p = new char[strlen(ev->parameter) + 1]; + strcpy(p, ev->parameter); + return p; +} + +// Shows a modal Yes/No Dialog which will return 'y' or 'n' +int ScrollView::ShowYesNoDialog(const char *msg) { + SendMsg("showYesNoDialog(\"%s\")", msg); + // Wait till an input event (all others are thrown away) + auto ev = AwaitEvent(SVET_INPUT); + int a = ev->parameter[0]; + return a; +} + +// Zoom the window to the rectangle given upper left corner and +// lower right corner. +void ScrollView::ZoomToRectangle(int x1, int y1, int x2, int y2) { + y1 = TranslateYCoordinate(y1); + y2 = TranslateYCoordinate(y2); + SendMsg("zoomRectangle(%d,%d,%d,%d)", std::min(x1, x2), std::min(y1, y2), std::max(x1, x2), + std::max(y1, y2)); +} + +// Send an image of type Pix. +void ScrollView::Draw(Image image, int x_pos, int y_pos) { + l_uint8 *data; + size_t size; + pixWriteMem(&data, &size, image, IFF_PNG); + int base64_len = (size + 2) / 3 * 4; + y_pos = TranslateYCoordinate(y_pos); + SendMsg("readImage(%d,%d,%d)", x_pos, y_pos, base64_len); + // Base64 encode the data. + const char kBase64Table[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + char *base64 = new char[base64_len + 1]; + memset(base64, '=', base64_len); + base64[base64_len] = '\0'; + int remainder = 0; + int bits_left = 0; + int code_len = 0; + for (size_t i = 0; i < size; ++i) { + int code = (data[i] >> (bits_left + 2)) | remainder; + base64[code_len++] = kBase64Table[code & 63]; + bits_left += 2; + remainder = data[i] << (6 - bits_left); + if (bits_left == 6) { + base64[code_len++] = kBase64Table[remainder & 63]; + bits_left = 0; + remainder = 0; + } + } + if (bits_left > 0) { + base64[code_len++] = kBase64Table[remainder & 63]; + } + SendRawMessage(base64); + delete[] base64; + lept_free(data); +} + +// Escapes the ' character with a \, so it can be processed by LUA. +// Note: The caller will have to make sure it deletes the newly allocated item. +char *ScrollView::AddEscapeChars(const char *input) { + const char *nextptr = strchr(input, '\''); + const char *lastptr = input; + char *message = new char[kMaxMsgSize]; + int pos = 0; + while (nextptr != nullptr) { + strncpy(message + pos, lastptr, nextptr - lastptr); + pos += nextptr - lastptr; + message[pos] = '\\'; + pos += 1; + lastptr = nextptr; + nextptr = strchr(nextptr + 1, '\''); + } + strcpy(message + pos, lastptr); + return message; +} + +// Inverse the Y axis if the coordinates are actually inversed. +int ScrollView::TranslateYCoordinate(int y) { + if (!y_axis_is_reversed_) { + return y; + } else { + return y_size_ - y; + } +} + +char ScrollView::Wait() { + // Wait till an input or click event (all others are thrown away) + char ret = '\0'; + SVEventType ev_type = SVET_ANY; + do { + std::unique_ptr<SVEvent> ev(AwaitEvent(SVET_ANY)); + ev_type = ev->type; + if (ev_type == SVET_INPUT) { + ret = ev->parameter[0]; + } + } while (ev_type != SVET_INPUT && ev_type != SVET_CLICK); + return ret; +} + +#endif // !GRAPHICS_DISABLED + +} // namespace tesseract
