Added basic selection rendering (incl. testing/demo code)

This commit is contained in:
LunarEclipse 2024-05-14 20:44:03 +02:00
parent ba660af9eb
commit e124669185
2 changed files with 79 additions and 18 deletions

View File

@ -3,6 +3,8 @@
<interface> <interface>
<requires lib="gtk+" version="3.24"/> <requires lib="gtk+" version="3.24"/>
<template class="main_window" parent="GtkApplicationWindow"> <template class="main_window" parent="GtkApplicationWindow">
<property name="width-request">800</property>
<property name="height-request">500</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="title" translatable="yes">PDF Table Extractor</property> <property name="title" translatable="yes">PDF Table Extractor</property>
<property name="icon-name">document-page-setup</property> <property name="icon-name">document-page-setup</property>

View File

@ -19,42 +19,88 @@ Coords: TypeAlias = Tuple[float, float]
class Selection: class Selection:
def __init__(self, bounds: Tuple[Coords, Coords], columns: Optional[Sequence[float]] = None): def __init__(self, bounds: Tuple[Coords, Coords], columns: Optional[Sequence[float]] = None):
self.bounds = bounds self.bounds = bounds
self.columns = columns self.columns: list[float] = list(columns or [])
class Page:
def __init__(self, index: int, raw: fitz.Page):
self.index = index
self.raw = raw
self.selections: list[Selection] = []
class Document: class Document:
def __init__(self, filename: str): def __init__(self, filename: str):
self.filename = filename self.filename = filename
self.document = fitz.Document(filename) self.raw = fitz.Document(filename)
self.selections: Dict[int, List[Selection]] = {} self.pages = []
for i, p in enumerate(self.raw.pages()): # type: ignore
page = Page(index=i, raw=p)
self.pages.append(page)
class PdfPage(Gtk.DrawingArea): class PdfPage(Gtk.DrawingArea):
def __init__(self, page, *args, **kwargs): def __init__(self, page, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.page: fitz.Page = page self.page: Page = page
pix = self.page.get_pixmap(dpi=96) # type: ignore pix: fitz.Pixmap = self.page.raw.get_pixmap(dpi=96) # type: ignore
self.set_size_request(pix.width, pix.height) self.set_size_request(pix.width, pix.height)
self.set_app_paintable(True) # type: ignore
self.connect("draw", self.on_draw, {}) self.connect("draw", self.on_draw, {})
if self.page.index == 4:
self.page.selections.append(Selection(
((0.1, 0.18), (0.9, 0.72)),
columns=[0.08, 0.24, 0.34, 0.42, 0.51, 0.59, 0.67, 0.73, 0.91]
))
def on_draw(self, widget: Gtk.DrawingArea, cr: cairo.Context, data: GObject.GPointer): def on_draw(self, widget: Gtk.DrawingArea, cr: cairo.Context, data: GObject.GPointer):
# app: Application = widget.get_window().get_application() # type: ignore
width = widget.get_allocated_width() width = widget.get_allocated_width()
height = widget.get_allocated_height() height = widget.get_allocated_height()
sctx = widget.get_style_context() sctx = widget.get_style_context()
Gtk.render_background(sctx, cr, 0, 0, width, height) Gtk.render_background(sctx, cr, 0, 0, width, height)
pix = self.page.get_pixmap(dpi=96) # type: ignore pix: fitz.Pixmap = self.page.raw.get_pixmap(dpi=96) # type: ignore
img = PIL.Image.frombytes("RGBA" if pix.alpha else "RGB", [pix.width, pix.height], pix.samples) img = PIL.Image.frombytes("RGBA" if pix.alpha else "RGB", [pix.width, pix.height], pix.samples)
img.putalpha(1) img.putalpha(1)
img = PIL.Image.merge("RGBA", (lambda r, g, b, a: (b, g, r, a))(*img.split())) # type: ignore
mv: memoryview = memoryview(bytearray(img.tobytes())) mv: memoryview = memoryview(bytearray(img.tobytes()))
ims = cairo.ImageSurface.create_for_data(mv, cairo.Format.RGB24, pix.width, pix.height) ims = cairo.ImageSurface.create_for_data(mv, cairo.Format.RGB24, pix.width, pix.height)
cr.set_source_surface(ims, 0, 0) cr.set_source_surface(ims, 0, 0)
cr.paint() cr.paint()
for sel in self.page.selections:
sel_x1 = sel.bounds[0][0]*pix.width
sel_y1 = sel.bounds[0][1]*pix.height
sel_x2 = sel.bounds[1][0]*pix.width
sel_y2 = sel.bounds[1][1]*pix.height
# Base settings
cr.set_line_cap(cairo.LINE_CAP_BUTT)
cr.set_line_width(2)
# Columns (draw first - below selection)
cr.set_dash([5])
cr.set_source_rgba(1, 0, 0)
for col in sel.columns:
col_x = sel_x1 + (sel_x2 - sel_x1) * col
cr.move_to(col_x, sel_y1)
cr.line_to(col_x, sel_y2)
cr.stroke()
# Selection
cr.rectangle(sel_x1, sel_y1, sel_x2 - sel_x1, sel_y2 - sel_y1)
# White part of the pattern
cr.set_source_rgba(1, 1, 1)
cr.set_dash([5], 5)
cr.stroke_preserve() # important preserve - reuse rectangle
# Black part of the pattern
cr.set_source_rgba(0, 0, 0)
cr.set_dash([5])
cr.stroke()
@Gtk.Template.from_file("MainWindow.glade") @Gtk.Template.from_file("MainWindow.glade")
class MainWindow(Gtk.ApplicationWindow): class MainWindow(Gtk.ApplicationWindow):
@ -76,8 +122,6 @@ class MainWindow(Gtk.ApplicationWindow):
self.app.connect("notify::document", self.on_document_updated) self.app.connect("notify::document", self.on_document_updated)
# self.pdf_list_box.add(PdfPage())
# @Gtk.Template.Callback() # @Gtk.Template.Callback()
# def example_button_released_cb(self, widget: Gtk.Button, **kwargs): # def example_button_released_cb(self, widget: Gtk.Button, **kwargs):
# assert self.example_button == widget # assert self.example_button == widget
@ -119,20 +163,35 @@ class MainWindow(Gtk.ApplicationWindow):
# editing an existing one won't trigger it # editing an existing one won't trigger it
def on_document_updated(self, recvobj, gparamstring): def on_document_updated(self, recvobj, gparamstring):
document: Document = self.app.get_property("document") document: Document = self.app.get_property("document")
self.header_bar.set_title(document.filename.split("/")[-1]) # type: ignore self.header_bar.set_title(document.filename.split("/")[-1])
self.header_bar.set_subtitle(document.filename) # type: ignore self.header_bar.set_subtitle(document.filename)
for i in document.document.pages(): # type: ignore for child in self.pdf_list_box.get_children():
row = Gtk.ListBoxRow() if type(child) is Gtk.ListBoxRow:
page = PdfPage(i) child.destroy()
row.add(page)
self.pdf_list_box.add(row) # type: ignore for i in document.pages:
self.pdf_list_box.add(PdfPage(i))
self.pdf_list_box.show_all() self.pdf_list_box.show_all()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def on_open_button_small_clicked(self, widget, **kwargs): def on_open_button_small_clicked(self, widget, **kwargs):
pass TEST_FILENAME = "/home/luna/Documents/Resources/Praca Licencjacka/sources/2018_Torres-Benitez_Metabolomic analysis Parmotrema.pdf"
try:
self.app.set_property("document", Document(TEST_FILENAME))
except Exception as e:
message_dialog = Gtk.MessageDialog(
title="An error has occured.",
transient_for=self,
modal=True,
message_type=Gtk.MessageType.ERROR,
text=repr(e),
secondary_text=traceback.format_exc(),
buttons=Gtk.ButtonsType.OK,
)
message_dialog.run() # type: ignore
message_dialog.destroy()
class Application(Gtk.Application): class Application(Gtk.Application):