spiceditor 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of spiceditor might be problematic. Click here for more details.

Files changed (51) hide show
  1. pyspice/__init__.py +0 -0
  2. pyspice/dialogs.py +103 -0
  3. pyspice/editor_widget.py +196 -0
  4. pyspice/file_browser.py +143 -0
  5. pyspice/highlighter.py +74 -0
  6. pyspice/line_number_text_edit.py +54 -0
  7. pyspice/magic_scrollbar.py +11 -0
  8. pyspice/main.py +435 -0
  9. pyspice/resources.py +1120 -0
  10. pyspice/spice_console.py +282 -0
  11. pyspice/spice_magic_editor.py +389 -0
  12. pyspice/splitter.py +118 -0
  13. pyspice/term.py +63 -0
  14. pyspice/textract.py +597 -0
  15. pyspice/utils.py +33 -0
  16. spiceditor/__init__.py +0 -0
  17. spiceditor/dialogs.py +103 -0
  18. spiceditor/editor_widget.py +196 -0
  19. spiceditor/file_browser.py +143 -0
  20. spiceditor/highlighter.py +74 -0
  21. spiceditor/line_number_text_edit.py +54 -0
  22. spiceditor/magic_scrollbar.py +11 -0
  23. spiceditor/main.py +435 -0
  24. spiceditor/resources.py +1120 -0
  25. spiceditor/spice_console.py +282 -0
  26. spiceditor/spice_magic_editor.py +389 -0
  27. spiceditor/splitter.py +118 -0
  28. spiceditor/term.py +63 -0
  29. spiceditor/textract.py +597 -0
  30. spiceditor/utils.py +33 -0
  31. spiceditor-0.0.4.dist-info/LICENSE +674 -0
  32. spiceditor-0.0.4.dist-info/METADATA +31 -0
  33. spiceditor-0.0.4.dist-info/RECORD +51 -0
  34. spiceditor-0.0.4.dist-info/WHEEL +5 -0
  35. spiceditor-0.0.4.dist-info/entry_points.txt +2 -0
  36. spiceditor-0.0.4.dist-info/top_level.txt +1 -0
  37. spyce/__init__.py +0 -0
  38. spyce/dialogs.py +103 -0
  39. spyce/editor_widget.py +196 -0
  40. spyce/file_browser.py +143 -0
  41. spyce/highlighter.py +74 -0
  42. spyce/line_number_text_edit.py +54 -0
  43. spyce/magic_scrollbar.py +11 -0
  44. spyce/main.py +435 -0
  45. spyce/resources.py +1120 -0
  46. spyce/spice_console.py +282 -0
  47. spyce/spice_magic_editor.py +389 -0
  48. spyce/splitter.py +118 -0
  49. spyce/term.py +63 -0
  50. spyce/textract.py +597 -0
  51. spyce/utils.py +33 -0
spiceditor/textract.py ADDED
@@ -0,0 +1,597 @@
1
+ import os
2
+ import platform
3
+ import subprocess
4
+
5
+ import fitz # PyMuPDF
6
+ import sys
7
+
8
+ import fitz # PyMuPDF
9
+ from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QLine, QLineF, QRectF, QPointF
10
+ from PyQt5.QtGui import QPixmap, QImage, QFont, QPainter, QColor, QCursor, QIcon, QTransform, QPen
11
+ from PyQt5.QtWidgets import QMainWindow, QLabel, QSizePolicy, QApplication, QVBoxLayout, QWidget, QPushButton, \
12
+ QShortcut, QInputDialog, QTabBar, QToolBar, QHBoxLayout, QComboBox, QGraphicsView, QGraphicsScene, \
13
+ QGraphicsPixmapItem, QGraphicsItem, QGraphicsEllipseItem, \
14
+ QGraphicsRectItem, QGraphicsProxyWidget, QGraphicsLineItem
15
+ from pymupdf import Rect
16
+ from scipy.signal import savgol_filter
17
+
18
+ from spiceditor.utils import create_cursor_image
19
+
20
+
21
+ class Eraser(QGraphicsEllipseItem):
22
+ pass
23
+
24
+
25
+ def smooth_with_savgol(points, window_size=10, poly_order=2):
26
+ if len(points) < window_size:
27
+ return points
28
+ x = [p[0] for p in points]
29
+ y = [p[1] for p in points]
30
+ smoothed_x = savgol_filter(x, window_size, poly_order)
31
+ smoothed_y = savgol_filter(y, window_size, poly_order)
32
+ return list(zip(smoothed_x, smoothed_y))
33
+
34
+
35
+ class GraphicsScene(QGraphicsScene):
36
+ NONE = 0
37
+ POINTER = 1
38
+ WRITING = 2
39
+ ERASING = 3
40
+ RECTANGLES = 4
41
+ ELLIPSES = 5
42
+
43
+ navigate = pyqtSignal(int)
44
+
45
+ def keyPressEvent(self, event):
46
+ super().keyPressEvent(event)
47
+ if event.key() == Qt.Key_E:
48
+ self.status = GraphicsScene.ERASING
49
+ elif event.key() == Qt.Key_W:
50
+ self.status = GraphicsScene.WRITING
51
+ elif event.key() in [Qt.Key_Right, Qt.Key_Down]:
52
+ self.navigate.emit(1)
53
+ elif event.key() in [Qt.Key_Left, Qt.Key_Up]:
54
+ self.navigate.emit(-1)
55
+
56
+ def __init__(self):
57
+ super().__init__()
58
+ self.handwriting = []
59
+ self.pixmap = QGraphicsPixmapItem()
60
+ self.pixmap.setTransformationMode(Qt.SmoothTransformation) # Enable smooth transformation for the pixmap item
61
+
62
+ self.addItem(self.pixmap)
63
+ self.image = None
64
+ self.start = None
65
+ self.page = 0
66
+ self.status = GraphicsScene.NONE
67
+
68
+ self.gum = Eraser(0, 0, 100, 100, self.pixmap)
69
+ self.gum.setBrush(QColor(255, 255, 255))
70
+ self.gum.setPen(QPen(QColor(0, 0, 0), 2))
71
+ self.gum.setVisible(False)
72
+ self.drawings = {}
73
+ self.rectangle = None
74
+ self.color = QPen(Qt.black, 2)
75
+ # make the ellipse movable
76
+ # self.gum.setFlag(QGraphicsItem.ItemIsMovable)
77
+
78
+ def set_image(self, image, page):
79
+ current = self.drawings.get(self.page, [])
80
+ for item in current:
81
+ self.removeItem(item)
82
+
83
+ self.image = image
84
+ self.page = page
85
+ # self.resize_image(size)
86
+ self.pixmap.setPixmap(self.image)
87
+
88
+ for item in self.drawings.get(self.page, []):
89
+ self.addItem(item)
90
+
91
+ def mousePressEvent(self, event):
92
+ event.accept()
93
+ super().mousePressEvent(event)
94
+ if event.button() == Qt.RightButton:
95
+ return
96
+
97
+ self.start = event.scenePos()
98
+
99
+ # get objects at the click position
100
+ items = self.items(self.start)
101
+ for item in items:
102
+ if type(item) not in [QGraphicsPixmapItem, Eraser]:
103
+ self.start = None
104
+ return
105
+
106
+ if self.status == GraphicsScene.WRITING:
107
+ self.handwriting.clear()
108
+
109
+ elif self.status == GraphicsScene.RECTANGLES:
110
+ self.rectangle = self.addRect(QRectF(self.start, self.start), self.color)
111
+
112
+ elif self.status == GraphicsScene.ELLIPSES:
113
+ self.rectangle = self.addEllipse(QRectF(self.start, self.start), self.color)
114
+
115
+ if self.rectangle is not None:
116
+ self.rectangle.setFlag(QGraphicsItem.ItemIsMovable)
117
+ if self.drawings.get(self.page, None) is None:
118
+ self.drawings[self.page] = []
119
+ self.drawings[self.page].append(self.rectangle)
120
+
121
+ def mouseMoveEvent(self, event):
122
+ super().mouseMoveEvent(event)
123
+ if self.status == GraphicsScene.WRITING:
124
+ if self.start is None:
125
+ return
126
+
127
+ line = self.addLine(QLineF(self.start, event.scenePos()), self.color)
128
+ self.handwriting.append(line)
129
+ self.start = event.scenePos()
130
+
131
+ elif self.status == GraphicsScene.ERASING:
132
+ if self.start is not None:
133
+ self.gum.setPos(event.scenePos() - QLine(50, 50, 50, 50).p2())
134
+ colliding = self.gum.collidingItems()
135
+ for item in colliding:
136
+ if type(item) in [QGraphicsPixmapItem, QGraphicsProxyWidget]:
137
+ continue
138
+ if item in self.drawings.get(self.page, []):
139
+ self.drawings[self.page].remove(item)
140
+ self.removeItem(item)
141
+
142
+ elif self.status in [GraphicsScene.RECTANGLES, GraphicsScene.ELLIPSES]:
143
+ if self.start is None:
144
+ return
145
+ self.rectangle.setRect(QRectF(self.start, event.scenePos()))
146
+
147
+ def mouseReleaseEvent(self, event):
148
+ super().mouseReleaseEvent(event)
149
+ self.start = None
150
+ self.rectangle = None
151
+
152
+ if self.status == GraphicsScene.WRITING:
153
+ self.smooth_handwriting()
154
+
155
+ def smooth_handwriting(self):
156
+ points = []
157
+ for p in self.handwriting: # type: QGraphicsLineItem
158
+ points.append((p.line().x1(), p.line().y1()))
159
+
160
+ for line in self.handwriting:
161
+ self.removeItem(line)
162
+
163
+ smoothed = smooth_with_savgol(points, 20, 4)
164
+
165
+ if len(smoothed) > 0:
166
+ p1 = smoothed[0]
167
+ for p2 in smoothed[1:]:
168
+ line = self.addLine(QLineF(QPointF(*p1), QPointF(*p2)), self.color)
169
+ if self.drawings.get(self.page) is None:
170
+ self.drawings[self.page] = []
171
+ self.drawings[self.page].append(line)
172
+ p1 = p2
173
+
174
+ def erase_all(self):
175
+ for item in self.drawings.get(self.page, []):
176
+ self.removeItem(item)
177
+ self.drawings[self.page] = []
178
+
179
+
180
+ class GraphicsView(QGraphicsView):
181
+ resized = pyqtSignal()
182
+
183
+ def __init__(self, a):
184
+ super().__init__(a)
185
+ self.setRenderHint(QPainter.SmoothPixmapTransform)
186
+ self.setRenderHint(QPainter.Antialiasing)
187
+ self.setRenderHint(QPainter.TextAntialiasing)
188
+ self.setRenderHint(QPainter.HighQualityAntialiasing)
189
+ # self.scence = None
190
+
191
+ def resizeEvent(self, event):
192
+ super().resizeEvent(event)
193
+ transf = QTransform()
194
+ ratio1 = self.viewport().size().width() / self.scene().image.width()
195
+ ratio2 = self.viewport().size().height() / self.scene().image.height()
196
+ ratio = min(ratio1, ratio2)
197
+ transf.scale(ratio, ratio)
198
+ self.setTransform(transf)
199
+ self.scene().setSceneRect(0, 0, self.scene().image.width(), self.scene().image.height())
200
+ self.resized.emit()
201
+
202
+ # if self.scence is not None:
203
+ # self.scene().removeItem(self.scence)
204
+ # self.scence = self.scene().addRect(self.sceneRect(), QColor(255, 0, 0))
205
+
206
+ def set_image(self, image, page):
207
+ self.scene().set_image(image, page)
208
+
209
+
210
+ class Slides(QWidget):
211
+ play_code = pyqtSignal(str)
212
+
213
+ def set_writing_mode(self, mode):
214
+ for i, elem in enumerate(self.group):
215
+ elem.blockSignals(True)
216
+ elem.setChecked(i == mode)
217
+ elem.blockSignals(False)
218
+
219
+ self.scene.status = mode
220
+ self.scene.gum.setVisible(mode == GraphicsScene.ERASING)
221
+
222
+ if mode == GraphicsScene.POINTER:
223
+ self.view.viewport().setCursor(QCursor(create_cursor_image()))
224
+ else:
225
+ self.view.viewport().setCursor(Qt.ArrowCursor)
226
+
227
+ def set_color(self, color):
228
+ for i, elem in enumerate(self.color_group):
229
+ elem.blockSignals(True)
230
+ elem.setChecked(i == color)
231
+ elem.blockSignals(False)
232
+
233
+ colors = [Qt.black, Qt.red, Qt.green, Qt.blue, Qt.yellow, Qt.magenta, Qt.cyan, Qt.gray]
234
+ self.scene.color = QPen(colors[color], self.scene.color.width())
235
+ if self.scene.status in [GraphicsScene.NONE, GraphicsScene.ERASING]:
236
+ self.set_writing_mode(GraphicsScene.WRITING)
237
+
238
+ def erase_all(self):
239
+ self.scene.erase_all()
240
+
241
+ def create_toolbar(self):
242
+ toolbar = QToolBar()
243
+ # toolbar.setMaximumHeight(35)
244
+ toolbar.show()
245
+
246
+ none = toolbar.addAction("", lambda: self.set_writing_mode(0))
247
+ none.setIcon(QIcon(":/icons/cursor.svg"))
248
+ none.setCheckable(True)
249
+ none.setChecked(True)
250
+
251
+ pointer = toolbar.addAction("Pointer", lambda: self.set_writing_mode(1))
252
+ pointer.setIcon(QIcon(":/icons/origin.svg"))
253
+ pointer.setCheckable(True)
254
+
255
+ write = toolbar.addAction("", lambda: self.set_writing_mode(2))
256
+ write.setIcon(QIcon(":/icons/edit.svg"))
257
+ write.setCheckable(True)
258
+
259
+ rectangle = toolbar.addAction(QIcon(":/icons/rectangle.svg"), "", lambda: self.set_writing_mode(4))
260
+ rectangle.setCheckable(True)
261
+ circle = toolbar.addAction(QIcon(":/icons/circle.svg"), "", lambda: self.set_writing_mode(5))
262
+ circle.setCheckable(True)
263
+
264
+ toolbar.addSeparator()
265
+ erase = toolbar.addAction("", lambda: self.set_writing_mode(3))
266
+ erase.setIcon(QIcon(":/icons/cancel.svg"))
267
+ erase.setCheckable(True)
268
+ toolbar.addSeparator()
269
+
270
+ self.group = [none, pointer, write, erase, rectangle, circle]
271
+
272
+ erase_all = toolbar.addAction("", lambda: self.erase_all())
273
+ erase_all.setIcon(QIcon(":/icons/bin.svg"))
274
+
275
+ toolbar.addSeparator()
276
+ t1 = toolbar.addAction(QIcon(":/icons/minus.svg"), "", lambda: self.set_thickness(0))
277
+ t1.setCheckable(True)
278
+ t1.setChecked(True)
279
+ t2 = toolbar.addAction(QIcon(":/icons/minus_med.svg"), "", lambda: self.set_thickness(1))
280
+ t2.setCheckable(True)
281
+ t3 = toolbar.addAction(QIcon(":/icons/minus_big.svg"), "", lambda: self.set_thickness(2))
282
+ t3.setCheckable(True)
283
+
284
+ self.thickness_group = [t1, t2, t3]
285
+
286
+ toolbar.addSeparator()
287
+ black = toolbar.addAction("", lambda: self.set_color(0))
288
+ black.setIcon(QIcon(":/icons/black.svg"))
289
+ red = toolbar.addAction("", lambda: self.set_color(1))
290
+ red.setIcon(QIcon(":/icons/red.svg"))
291
+ green = toolbar.addAction("", lambda: self.set_color(2))
292
+ green.setIcon(QIcon(":/icons/green.svg"))
293
+ blue = toolbar.addAction("", lambda: self.set_color(3))
294
+ blue.setIcon(QIcon(":/icons/blue.svg"))
295
+
296
+ self.color_group = [black, red, green, blue]
297
+ for elem in self.color_group:
298
+ elem.setCheckable(True)
299
+ black.setChecked(True)
300
+
301
+ toolbar.addSeparator()
302
+
303
+ prev = toolbar.addAction("✕", lambda: self.move_to(False))
304
+ prev.setIcon(QIcon(":/icons/arrow-left.svg"))
305
+
306
+ self.action_touchable = toolbar.addAction("Hold")
307
+ self.action_touchable.setCheckable(True)
308
+ self.action_touchable.setIcon(QIcon(":/icons/pan.svg"))
309
+
310
+ toolbar.addAction(QIcon(":/icons/plus.svg"), "", self.add_empty_page)
311
+
312
+ next1 = toolbar.addAction("⬇", lambda: self.move_to(True))
313
+ next1.setIcon(QIcon(":/icons/arrow-right.svg"))
314
+ return toolbar
315
+
316
+ def add_empty_page(self):
317
+ self.page += 1
318
+ self.pages_number.insert(self.page, None)
319
+ self.update_image()
320
+
321
+ def set_thickness(self, thickness):
322
+ for i, elem in enumerate(self.thickness_group):
323
+ elem.blockSignals(True)
324
+ elem.setChecked(i == thickness)
325
+ elem.blockSignals(False)
326
+
327
+ self.scene.color = QPen(self.scene.color.color(), 2 + thickness * 4)
328
+ if self.scene.status in [GraphicsScene.NONE, GraphicsScene.ERASING]:
329
+ self.set_writing_mode(GraphicsScene.WRITING)
330
+
331
+ def __init__(self, config, pdf_path, page):
332
+ super().__init__()
333
+ self.config = config
334
+ self.thickness = 2
335
+ self.action_touchable = None
336
+ self.group = []
337
+ self.color_group = []
338
+ self.touchable = True
339
+ self.program = ""
340
+ self.code_buttons = []
341
+ self.code_line_height = 0
342
+ self.resized_pixmap = None
343
+ self.doc = fitz.open(pdf_path)
344
+ self.pages_number = [i for i in range(len(self.doc))]
345
+
346
+ self.filename = pdf_path
347
+ self.page = page
348
+ self.base = None
349
+
350
+ # Create a QLabel to display the image
351
+ self.scene = GraphicsScene()
352
+ self.scene.navigate.connect(self.navigate)
353
+ self.view = GraphicsView(self.scene)
354
+ self.view.resized.connect(self.resized)
355
+ self.view.setAlignment(Qt.AlignCenter)
356
+ self.view.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
357
+
358
+ self.base = QGraphicsRectItem(0, 0, 35, 15, self.scene.pixmap)
359
+ self.base.setBrush(QColor(220, 220, 220))
360
+ self.base.setPen(Qt.transparent)
361
+ self.proxy = QGraphicsProxyWidget(self.base)
362
+
363
+ # add shortcut ctrl+n to number of page
364
+ def get_number_of_page():
365
+ a, b = QInputDialog.getInt(self, "Number of page", "Enter the number of page", self.page + 1, 1,
366
+ len(self.doc), 1, Qt.WindowFlags())
367
+ self.page = a - 1
368
+ self.update_image()
369
+
370
+ q = QShortcut("Ctrl+N", self)
371
+ q.activated.connect(get_number_of_page)
372
+
373
+ # Set up a layout
374
+ layout = QHBoxLayout()
375
+ layout.setSpacing(0)
376
+ layout.addWidget(self.view)
377
+
378
+ self.setLayout(layout)
379
+ self.update_image()
380
+
381
+ self.toolbar_float = None
382
+ self.toolbar = self.create_toolbar()
383
+ self.set_toolbar_float(True)
384
+
385
+ QTimer.singleShot(0, self.resize_image)
386
+
387
+ QApplication.instance().setStyleSheet("""
388
+ QToolTip {
389
+ font-family: 'Courier New', monospace;
390
+ font-size: 12pt;
391
+ color: #ffffff;
392
+ background-color: #333333;
393
+ border: 1px solid #ffffff;
394
+ }
395
+ """)
396
+
397
+ def is_toolbar_float(self):
398
+ return self.toolbar_float
399
+
400
+ def set_toolbar_float(self, value, parent=None):
401
+
402
+ if value:
403
+ self.toolbar.setParent(None)
404
+ self.toolbar.setOrientation(Qt.Horizontal)
405
+ self.proxy.setWidget(self.toolbar)
406
+ self.proxy.setPos(-2, 10)
407
+ self.proxy.setZValue(1)
408
+ self.base.setFlags(QGraphicsItem.ItemIgnoresTransformations | QGraphicsItem.ItemIsMovable)
409
+ self.proxy.show()
410
+ self.base.show()
411
+ self.toolbar.show()
412
+
413
+ else:
414
+ self.proxy.setWidget(None)
415
+ self.base.hide()
416
+ self.toolbar.setParent(parent)
417
+ self.toolbar.setOrientation(Qt.Horizontal)
418
+ self.toolbar.show()
419
+
420
+ self.toolbar_float = value
421
+
422
+ def resized(self):
423
+ if self.base is not None:
424
+ self.base.setPos(0, 0)
425
+
426
+ def get_toolbar(self):
427
+ return self.toolbar
428
+
429
+ def navigate(self, delta):
430
+ self.page = (self.page + delta) % len(self.doc)
431
+ self.update_image()
432
+
433
+ def play_program(self, program):
434
+ self.setFocus()
435
+ self.play_code.emit(program)
436
+ return
437
+
438
+ # Command to open a new terminal and execute the Python code
439
+ if platform.system() == "Linux":
440
+ subprocess.run([
441
+ "gnome-terminal",
442
+ "--",
443
+ "bash",
444
+ "-c",
445
+ f"python3 -c \"{self.program}\"; exec bash"
446
+ ])
447
+ else:
448
+ # Command to open a new PowerShell window and execute Python code
449
+ subprocess.run([
450
+ "powershell",
451
+ "-NoExit",
452
+ "-Command",
453
+ f"python -c \"{self.program}\""
454
+ ])
455
+
456
+ def mousePressEvent(self, a0):
457
+ super().mousePressEvent(a0)
458
+ if a0.button() == Qt.RightButton:
459
+ return
460
+
461
+ if self.scene.status != GraphicsScene.NONE:
462
+ return
463
+
464
+ if not self.action_touchable.isChecked():
465
+ right_side = a0.x() > self.view.width() // 2
466
+ self.move_to(right_side)
467
+
468
+ def move_to(self, right_side):
469
+ if right_side:
470
+ self.page = (self.page + 1) % len(self.doc)
471
+ else:
472
+ self.page = (self.page - 1) % len(self.doc)
473
+
474
+ self.update_image()
475
+
476
+ def toggle_cursor(self):
477
+ if self.view.cursor() == Qt.ArrowCursor:
478
+ self.set_custom_cursor(self.view)
479
+ else:
480
+ self.view.setCursor(Qt.ArrowCursor)
481
+ self.view.update()
482
+
483
+ def update_image(self):
484
+ # Clear buttons
485
+ for button, _ in self.code_buttons:
486
+ self.scene.removeItem(button)
487
+ self.code_buttons.clear()
488
+
489
+ # Get number of page
490
+ pdf_page = self.pages_number[self.page]
491
+ if pdf_page is None:
492
+ image = QPixmap(1920, 1080)
493
+ image.fill(Qt.white)
494
+ else:
495
+ page = self.doc[pdf_page]
496
+ pix = page.get_pixmap(matrix=fitz.Matrix(2, 2), alpha=False, annots=True)
497
+ image = QImage(pix.samples, pix.width, pix.height, pix.stride, QImage.Format_RGB888)
498
+ for d in page.get_drawings():
499
+ fill = d.get("fill")
500
+ type = d.get("type")
501
+ color = d.get("color")
502
+
503
+ if type != 'f' and color == (1.0, 0, 1.0):
504
+ rect = d.get("rect")
505
+ self.extract_text_and_fonts(rect)
506
+
507
+ self.update_button_pos()
508
+
509
+ self.pixmap = QPixmap(image)
510
+ self.resize_image()
511
+ self.view.set_image(self.pixmap, self.page)
512
+
513
+ def resizeEvent(self, event):
514
+ super().resizeEvent(event)
515
+ self.resize_image()
516
+
517
+ def resize_image(self):
518
+ # Resize the pixmap to fit the QLabel while maintaining aspect ratio
519
+ if not self.pixmap is None and not self.pixmap.isNull():
520
+ self.resized_pixmap = self.pixmap.scaled(
521
+ self.view.size(),
522
+ Qt.KeepAspectRatio,
523
+ Qt.SmoothTransformation
524
+ )
525
+
526
+ def extract_text_and_fonts(self, rect=None):
527
+ lines = []
528
+ try:
529
+
530
+ page_without_empty = 0
531
+ for i in range(self.page+1):
532
+ page_without_empty += 1 if self.pages_number[i] is not None else 0
533
+
534
+ page = self.doc[page_without_empty - 1]
535
+
536
+ # Extract blocks of text
537
+ blocks = page.get_text("dict", clip=rect)['blocks']
538
+ for block in blocks:
539
+ if 'lines' in block: # Ensure the block contains text
540
+
541
+ block_bbox = block['bbox'] # Get block position
542
+ for line in block['lines']:
543
+ line_text = ""
544
+ line_box = line["bbox"]
545
+ for span in line['spans']:
546
+ text = span['text']
547
+ font = span['font']
548
+ position = span['bbox'] # Get position of the span
549
+ line_text += text
550
+
551
+ lines.append((line_text, line_box))
552
+
553
+ except Exception as e:
554
+ print(f"An error occurred: {e}")
555
+
556
+ if len(lines) != 0:
557
+ rect: Rect
558
+
559
+ xs = set()
560
+ for text, pos in lines:
561
+ x1, y1, x2, y2 = pos
562
+ x1 = int(x1)
563
+ xs.add(x1)
564
+ xs = list(xs)
565
+ xs.sort()
566
+
567
+ program = str()
568
+ for text, pos in lines:
569
+ x1, y1, x2, y2 = pos
570
+ x1 = int(x1)
571
+ i = xs.index(x1) * 4
572
+ program += " " * i + text + "\n"
573
+
574
+ code_pos = (rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0)
575
+
576
+ play_button = QPushButton()
577
+ play_button.setFixedSize(45, 45)
578
+ play_button.setIcon(self.style().standardIcon(QApplication.style().SP_MediaPlay))
579
+ play_button.clicked.connect(lambda x=program, y=program: self.play_program(y))
580
+
581
+ proxy = QGraphicsProxyWidget(self.scene.pixmap)
582
+ proxy.setWidget(play_button)
583
+ # proxy.setFlags(QGraphicsItem.ItemIgnoresTransformations)
584
+
585
+ # Add the proxy widget to the scene
586
+ # self.scene.addItem(proxy)
587
+
588
+ self.code_buttons.append((proxy, code_pos))
589
+ play_button.setToolTip(self.program)
590
+
591
+ def update_button_pos(self):
592
+
593
+ for button, code_pos in self.code_buttons:
594
+ code_x, code_y, code_w, code_h = code_pos
595
+ button: QGraphicsRectItem
596
+ button.setPos((code_x + code_w) * 2 - button.sceneBoundingRect().width(),
597
+ (code_y + code_h) * 2 - button.sceneBoundingRect().height())
spiceditor/utils.py ADDED
@@ -0,0 +1,33 @@
1
+ from PyQt5.QtCore import Qt
2
+ from PyQt5.QtGui import QPixmap, QColor, QPainter
3
+
4
+
5
+ def create_cursor_image(size=30):
6
+ size = 30 # Size of the cursor
7
+ cursor_image = QPixmap(size, size)
8
+ cursor_image.fill(Qt.transparent) # Fill the pixmap with transparency
9
+
10
+ # Create a QPainter to draw the red dot
11
+ painter = QPainter(cursor_image)
12
+ painter.setBrush(QColor(255, 0, 0, 128)) # Red color
13
+ painter.setPen(Qt.NoPen)
14
+ painter.drawEllipse(0, 0, size, size)
15
+ painter.end()
16
+
17
+ return cursor_image
18
+
19
+ def color(icon_path, color):
20
+ # Load the pixmap from the icon path
21
+ pixmap = QPixmap(icon_path)
22
+
23
+ # Create an empty QPixmap with the same size
24
+ colored_pixmap = QPixmap(pixmap.size())
25
+ colored_pixmap.fill(Qt.transparent)
26
+
27
+ # Paint the new color onto the QPixmap
28
+ painter = QPainter(colored_pixmap)
29
+ painter.drawPixmap(0, 0, pixmap)
30
+ painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
31
+ painter.fillRect(colored_pixmap.rect(), QColor(color))
32
+ painter.end()
33
+ return colored_pixmap