pyflutter-app 0.3.14__tar.gz → 0.4__tar.gz

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.
Files changed (94) hide show
  1. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/.gitignore +1 -0
  2. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/PKG-INFO +5 -5
  3. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/home_page/page_sheets.py +8 -6
  4. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/home_page/view.py +11 -7
  5. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_circuit/view_circuit.py +27 -9
  6. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/renderer.py +80 -60
  7. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/view_image.py +10 -4
  8. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_ocr/view_ocr.py +7 -3
  9. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/run_app_convert.py +8 -8
  10. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/new-export_app.sh +1 -7
  11. pyflutter_app-0.4/pyflutter_ui/__init__.py +86 -0
  12. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/app.py +5 -5
  13. pyflutter_app-0.4/pyflutter_ui/config_app.py +163 -0
  14. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/navigator.py +19 -18
  15. pyflutter_app-0.4/pyflutter_ui/table_widget.py +160 -0
  16. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/__init__.py +1 -1
  17. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/core.py +161 -108
  18. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/widgets.py +202 -371
  19. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyproject.toml +6 -9
  20. pyflutter_app-0.4/teste.py +16 -0
  21. pyflutter_app-0.4/uv.lock +1100 -0
  22. pyflutter_app-0.3.14/.idea/.gitignore +0 -3
  23. pyflutter_app-0.3.14/.idea/inspectionProfiles/Project_Default.xml +0 -30
  24. pyflutter_app-0.3.14/.idea/inspectionProfiles/profiles_settings.xml +0 -6
  25. pyflutter_app-0.3.14/.idea/misc.xml +0 -7
  26. pyflutter_app-0.3.14/.idea/modules.xml +0 -8
  27. pyflutter_app-0.3.14/.idea/pyflutter_app.iml +0 -14
  28. pyflutter_app-0.3.14/.idea/vcs.xml +0 -6
  29. pyflutter_app-0.3.14/cartas/__init__.py +0 -2316
  30. pyflutter_app-0.3.14/convert_stream/__init__.py +0 -1502
  31. pyflutter_app-0.3.14/converter/page_forms/Diagnorstico.txt +0 -108
  32. pyflutter_app-0.3.14/converter/page_forms/__init__.py +0 -5
  33. pyflutter_app-0.3.14/converter/page_forms/common.py +0 -192
  34. pyflutter_app-0.3.14/converter/page_forms/config.py +0 -68
  35. pyflutter_app-0.3.14/converter/page_forms/controller_forms.py +0 -457
  36. pyflutter_app-0.3.14/converter/page_forms/diagnose_forms.py +0 -64
  37. pyflutter_app-0.3.14/converter/page_forms/master_forms.py +0 -113
  38. pyflutter_app-0.3.14/converter/page_forms/model_forms.py +0 -344
  39. pyflutter_app-0.3.14/converter/page_forms/service.py +0 -347
  40. pyflutter_app-0.3.14/converter/page_forms/view_forms.py +0 -166
  41. pyflutter_app-0.3.14/converter/page_whatsapp/PATCH_whatsapp_files.md +0 -245
  42. pyflutter_app-0.3.14/converter/page_whatsapp/__init__.py +0 -4
  43. pyflutter_app-0.3.14/converter/page_whatsapp/common.py +0 -41
  44. pyflutter_app-0.3.14/converter/page_whatsapp/controller_whatsapp.py +0 -391
  45. pyflutter_app-0.3.14/converter/page_whatsapp/model_whatsapp.py +0 -395
  46. pyflutter_app-0.3.14/converter/page_whatsapp/view_whatsapp.py +0 -271
  47. pyflutter_app-0.3.14/ocr_stream/__init__.py +0 -1835
  48. pyflutter_app-0.3.14/pyflutter_ui/__init__.py +0 -30
  49. pyflutter_app-0.3.14/teste.py +0 -27
  50. pyflutter_app-0.3.14/uv.lock +0 -1035
  51. pyflutter_app-0.3.14/whatsapp_files/__init__.py +0 -1295
  52. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/README.md +0 -0
  53. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/__init__.py +0 -0
  54. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/__main__.py +0 -0
  55. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/assets/forms_config.json +0 -0
  56. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/config_app.py +0 -0
  57. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/home_page/__init__.py +0 -0
  58. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/home_page/_page_edit.py +0 -0
  59. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/home_page/controller.py +0 -0
  60. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/home_page/model.py +0 -0
  61. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/lib_toi.py +0 -0
  62. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_circuit/__init__.py +0 -0
  63. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_circuit/common.py +0 -0
  64. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_circuit/controller_circuit.py +0 -0
  65. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_circuit/model_circuit.py +0 -0
  66. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_circuit/service.py +0 -0
  67. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/__init__.py +0 -0
  68. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/common.py +0 -0
  69. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/controller_image.py +0 -0
  70. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/model_image.py +0 -0
  71. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/progress.py +0 -0
  72. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_image/widgets.py +0 -0
  73. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_ocr/__init__.py +0 -0
  74. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_ocr/common.py +0 -0
  75. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_ocr/controller_ocr.py +0 -0
  76. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/page_ocr/model_ocr.py +0 -0
  77. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/table_widget.py +0 -0
  78. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/converter/util.py +0 -0
  79. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/local_build/linux.sh +0 -0
  80. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/local_build/send.sh +0 -0
  81. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/main.py +0 -0
  82. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/mvc/controller.py +0 -0
  83. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/mvc/model.py +0 -0
  84. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/mvc/view.py +0 -0
  85. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/file_picker.py +0 -0
  86. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/scheduler.py +0 -0
  87. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/theme.py +0 -0
  88. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/buttons.py +0 -0
  89. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/container.py +0 -0
  90. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/list_builder.py +0 -0
  91. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/progress.py +0 -0
  92. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/pyflutter_ui/ui/tables.py +0 -0
  93. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/table_stream/__init__.py +0 -0
  94. {pyflutter_app-0.3.14 → pyflutter_app-0.4}/update.sh +0 -0
@@ -8,3 +8,4 @@ wheels/
8
8
 
9
9
  # Virtual environments
10
10
  .venv
11
+ .idea
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyflutter_app
3
- Version: 0.3.14
3
+ Version: 0.4
4
4
  Summary: Bibliotecas Python para conversão de documentos, interface e processamento de tabelas.
5
5
  Requires-Python: >=3.12
6
- Requires-Dist: opencv-python>=5.0.0.93
6
+ Requires-Dist: convert-stream>=2.6.2
7
+ Requires-Dist: numpy>=2.4.6
8
+ Requires-Dist: ocr-stream>=2.6.3
7
9
  Requires-Dist: openpyxl>=3.1.5
8
- Requires-Dist: pandas-stubs~=3.0.3
9
10
  Requires-Dist: pandas>=3.0.0
10
- Requires-Dist: playwright>=1.63.0
11
- Requires-Dist: pymupdf>=1.28.2
12
11
  Requires-Dist: pytesseract>=0.3.13
12
+ Requires-Dist: responds-forms>=0.4.3
13
13
  Description-Content-Type: text/markdown
14
14
 
15
15
  # pyflutter
@@ -18,11 +18,13 @@ from pyflutter_ui import (
18
18
  FilePickerResult,
19
19
  FilePickerType,
20
20
  PlatformFile,
21
+ PageApp,
21
22
  )
22
23
 
23
24
  from tkinter import messagebox
24
25
  import pandas as pd
25
26
 
27
+ from pyflutter_ui.ui.core import GlobalKey
26
28
  from table_stream import (
27
29
  ArrayList,
28
30
  HashMapDict,
@@ -38,11 +40,13 @@ from converter.home_page._page_edit import PageEditExcel
38
40
  from converter.config_app import AppConfigNotifier
39
41
 
40
42
 
41
- class PageSheets(StatelessWidget):
43
+ class PageSheets(PageApp):
42
44
  """Importar/carregar planilhas. De lá se acessa a edição de dados."""
43
45
 
44
- def __init__(self, key: str | None = None) -> None:
45
- super().__init__(key)
46
+ def __init__(
47
+ self, key: GlobalKey | None = None, *, name: str | None = "Planilhas"
48
+ ) -> None:
49
+ super().__init__(key, name=name)
46
50
 
47
51
  def build(self, context: BuildContext) -> FlutterWidget:
48
52
  return _PageSheetsState()
@@ -286,9 +290,7 @@ class _PageSheetsState(StatelessWidget):
286
290
  Expanded(Consumer(ControllerHomePage, self._build_table)),
287
291
  Row(
288
292
  [
289
- ElevatedButton(
290
- "<- Voltar", lambda: context.navigator.pop()
291
- ),
293
+ ElevatedButton("<- Voltar", lambda: context.navigator.pop()),
292
294
  Text("Ir para: "),
293
295
  ElevatedButton(
294
296
  "Edição de dados",
@@ -11,23 +11,27 @@ from pyflutter_ui import (
11
11
  MainAxisAlignment,
12
12
  Scaffold,
13
13
  Text,
14
+ PageApp,
14
15
  )
15
16
 
16
17
  # Páginas para navegação.
17
18
  from converter.home_page.page_sheets import PageSheets
18
- from converter.page_forms import PageRespondsForms
19
19
  from converter.page_image import PageImage
20
20
  from converter.page_circuit import PageCircuit
21
- from converter.page_whatsapp import PageWhatsApp
22
21
  from converter.page_ocr import PageOcr
23
22
  from pyflutter_ui.ui.core import GlobalKey
24
23
 
24
+ from app.page_forms import PageRespondsForms
25
+ from app.page_whatsapp import PageWhatsApp
25
26
 
26
- class HomePage(StatelessWidget):
27
+
28
+ class HomePage(PageApp):
27
29
  """Home: somente botões para navegar entre as páginas."""
28
30
 
29
- def __init__(self, key: GlobalKey | None = None) -> None:
30
- super().__init__(key)
31
+ def __init__(
32
+ self, key: GlobalKey | None = None, *, name: str = "Home Page"
33
+ ) -> None:
34
+ super().__init__(key, name=name)
31
35
 
32
36
  def build(self, context: BuildContext) -> FlutterWidget:
33
37
  return _HomePageState()
@@ -36,7 +40,7 @@ class HomePage(StatelessWidget):
36
40
  class _HomePageState(StatelessWidget):
37
41
 
38
42
  # (rótulo do botão, descrição, página)
39
- PAGES: list[tuple[str, str, Callable[[], FlutterWidget]]] = [
43
+ PAGES: list[tuple[str, str, Callable[[], PageApp]]] = [
40
44
  ("Planilhas", "Importar/carregar planilhas e editar dados", PageSheets),
41
45
  ("WhatsApp", "Importar chats (.zip) para o cache do WhatsApp", PageWhatsApp),
42
46
  ("Responds Forms", "Lançar cartas TOI no Microsoft Forms", PageRespondsForms),
@@ -53,7 +57,7 @@ class _HomePageState(StatelessWidget):
53
57
  context: BuildContext,
54
58
  label: str,
55
59
  description: str,
56
- page: Callable[[], FlutterWidget],
60
+ page: Callable[[], PageApp],
57
61
  ) -> FlutterWidget:
58
62
  return Row(
59
63
  [
@@ -21,7 +21,9 @@ from pyflutter_ui import (
21
21
  StatelessWidget,
22
22
  Text,
23
23
  TkMaterialApp,
24
+ PageApp,
24
25
  )
26
+ from pyflutter_ui.ui.core import GlobalKey
25
27
  from table_stream import Directory, File
26
28
 
27
29
  from converter.table_widget import TableOrProgress
@@ -33,14 +35,16 @@ from converter.page_circuit.controller_circuit import (
33
35
 
34
36
 
35
37
  def _shorten(text: str, limit: int = 60) -> str:
36
- return text if len(text) <= limit else "..." + text[-(limit - 3):]
38
+ return text if len(text) <= limit else "..." + text[-(limit - 3) :]
37
39
 
38
40
 
39
- class PageCircuit(StatelessWidget):
41
+ class PageCircuit(PageApp):
40
42
  """Divide a base exportada (Excel/CSV) em um arquivo por valor de coluna, para o QGIS."""
41
43
 
42
- def __init__(self, key=None) -> None:
43
- super().__init__(key)
44
+ def __init__(
45
+ self, key: GlobalKey | None = None, *, name: str | None = None
46
+ ) -> None:
47
+ super().__init__(key, name=name)
44
48
 
45
49
  def build(self, context: BuildContext) -> FlutterWidget:
46
50
  return _PageCircuitState(self.key)
@@ -133,13 +137,17 @@ class _PageCircuitState(StatelessWidget):
133
137
  ctrl.set_output_dir(Directory(path))
134
138
 
135
139
  # ------------------------------------------------------------------ widgets
136
- def build_body(self, context: BuildContext, ctrl: ControllerCircuit) -> FlutterWidget:
140
+ def build_body(
141
+ self, context: BuildContext, ctrl: ControllerCircuit
142
+ ) -> FlutterWidget:
137
143
  return Column(
138
144
  [
139
145
  Row(
140
146
  [
141
147
  ElevatedButton("<- Voltar", lambda: self.action_back(context)),
142
- ElevatedButton("Importar base", lambda: self.action_import(ctrl)),
148
+ ElevatedButton(
149
+ "Importar base", lambda: self.action_import(ctrl)
150
+ ),
143
151
  Text(_shorten(ctrl.file_label)),
144
152
  ]
145
153
  ),
@@ -167,15 +175,25 @@ class _PageCircuitState(StatelessWidget):
167
175
  ),
168
176
  Row(
169
177
  [
170
- ElevatedButton("Pasta de saída", lambda: self.action_pick_output(ctrl)),
178
+ ElevatedButton(
179
+ "Pasta de saída", lambda: self.action_pick_output(ctrl)
180
+ ),
171
181
  ElevatedButton("Abrir pasta", ctrl.open_output_dir),
172
182
  Text(_shorten(ctrl.output_dir.absolute())),
173
183
  ]
174
184
  ),
175
185
  Row(
176
186
  [
177
- Checkbox(value=ctrl.export_csv, text="CSV", on_changed=ctrl.set_export_csv),
178
- Checkbox(value=ctrl.export_excel, text="Excel", on_changed=ctrl.set_export_excel),
187
+ Checkbox(
188
+ value=ctrl.export_csv,
189
+ text="CSV",
190
+ on_changed=ctrl.set_export_csv,
191
+ ),
192
+ Checkbox(
193
+ value=ctrl.export_excel,
194
+ text="Excel",
195
+ on_changed=ctrl.set_export_excel,
196
+ ),
179
197
  Checkbox(
180
198
  value=ctrl.use_period_dirs,
181
199
  text="Pastas ano/mês (numano/nummes)",
@@ -1,15 +1,22 @@
1
1
  from __future__ import annotations
2
- import os
2
+ import base64
3
3
  import tkinter as tk
4
4
  from abc import ABC, abstractmethod
5
5
  from dataclasses import dataclass
6
+ from io import BytesIO
7
+ from pathlib import Path
6
8
  from typing import Callable
7
9
 
8
- import cv2
9
- import numpy as np
10
-
11
10
  from table_stream import File
12
- from convert_stream import DocumentPdf, convert_page_pdf, load_image_from_file
11
+ from convert_stream import (
12
+ DocumentPdf,
13
+ ImageElement,
14
+ LibPDF,
15
+ PageDocumentPdf,
16
+ RotationAngle,
17
+ convert_page_pdf,
18
+ )
19
+ from convert_stream.safety_pdf import PdfSafetyGuard, PdfSafetyPolicy
13
20
 
14
21
  from converter.page_image.common import IMAGE_EXTENSIONS
15
22
 
@@ -31,8 +38,8 @@ class PageSource(ABC):
31
38
  def page_count(self) -> int: ...
32
39
 
33
40
  @abstractmethod
34
- def render_page(self, index: int) -> np.ndarray:
35
- """Página em BGR (ndarray do OpenCV). Levanta exceção se falhar."""
41
+ def render_page(self, index: int) -> ImageElement:
42
+ """Página como ImageElement. Levanta exceção se falhar."""
36
43
  ...
37
44
 
38
45
  def close(self) -> None:
@@ -40,39 +47,48 @@ class PageSource(ABC):
40
47
 
41
48
 
42
49
  class ImageFileSource(PageSource):
43
- """Adapter de arquivo de imagem (convert_stream.load_image_from_file)."""
50
+ """Adapter de arquivo de imagem (convert_stream.ImageElement)."""
44
51
 
45
52
  def __init__(self, f: File) -> None:
46
- self._file = f
53
+ self._file: File = f
47
54
 
48
55
  def page_count(self) -> int:
49
56
  return 1
50
57
 
51
- def render_page(self, index: int) -> np.ndarray:
52
- return load_image_from_file(self._file)
58
+ def render_page(self, index: int) -> ImageElement:
59
+ return ImageElement.create_from_file(self._file)
53
60
 
54
61
 
55
62
  class PdfFileSource(PageSource):
56
- """Adapter de PDF (convert_stream.DocumentPdf + convert_page_pdf)."""
63
+ """
64
+ Adapter de PDF (convert_stream.DocumentPdf, engine PDFium).
65
+ Toda entrada passa pelo PdfSafetyGuard antes de chegar ao engine.
66
+ """
57
67
 
58
68
  DPI: int = 200
59
69
 
60
70
  def __init__(self, f: File) -> None:
61
- self._doc: DocumentPdf = DocumentPdf.create_from_file(f)
71
+ self._guard: PdfSafetyGuard = PdfSafetyGuard(PdfSafetyPolicy())
72
+ data: bytes = self._guard.read_pdf_bytes(Path(f.absolute()))
73
+ self._doc: DocumentPdf = DocumentPdf.create_from_bytes(
74
+ BytesIO(data), lib_pdf=LibPDF.PDFIUM
75
+ )
76
+ self._guard.check_document(self._doc)
62
77
  self._count: int = self._doc.lenght()
63
78
 
64
79
  def page_count(self) -> int:
65
80
  return self._count
66
81
 
67
- def render_page(self, index: int) -> np.ndarray:
68
- page = self._doc.get_page(index)
82
+ def render_page(self, index: int) -> ImageElement:
83
+ page: PageDocumentPdf | None = self._doc.get_page(index)
69
84
  if page is None:
70
85
  raise IndexError(f"Página {index + 1} inexistente (total {self._count}).")
71
86
 
72
- img = convert_page_pdf(page, dpi=self.DPI)
87
+ dpi: int = self._guard.fit_dpi(page, self.DPI)
88
+ img: ImageElement | None = convert_page_pdf(page, dpi=dpi)
73
89
  if img is None:
74
90
  raise ValueError(f"Falha ao renderizar a página {index + 1} do PDF.")
75
- return img.to_opencv()
91
+ return img
76
92
 
77
93
  def close(self) -> None:
78
94
  try:
@@ -97,8 +113,8 @@ class PageSourceFactory:
97
113
 
98
114
  @classmethod
99
115
  def create(cls, f: File) -> PageSource:
100
- ext = (f.extension() or "").lower()
101
- builder = cls._registry.get(ext)
116
+ ext: str = (f.extension() or "").lower()
117
+ builder: Callable[[File], PageSource] | None = cls._registry.get(ext)
102
118
  if builder is None:
103
119
  raise ValueError(f"Tipo de arquivo não suportado: {ext or '-'}")
104
120
  if not f.exists():
@@ -117,19 +133,25 @@ PageSourceFactory.register(".pdf", PdfFileSource)
117
133
  class DocumentViewer:
118
134
  """
119
135
  Abre imagem ou PDF, informa o número de páginas e gera o tk.PhotoImage
120
- (formato PPM, sem PIL). Mantém o arquivo e a página atuais em memória
121
- para zoom/rotação serem rápidos. release() fecha o arquivo (necessário
122
- antes de renomear/mover, principalmente no Windows).
136
+ (PNG gerado pelo convert_stream; o Tk 8.6 decodifica PNG nativamente).
137
+ Mantém a página base em memória para zoom/rotação serem rápidos.
138
+ release() fecha o arquivo (necessário antes de renomear/mover).
123
139
  """
124
140
 
125
141
  MAX_SOURCE_SIDE: int = 4000 # reduz páginas gigantes ao carregar
126
142
  MAX_OUTPUT_SIDE: int = 6000 # teto do zoom
127
143
  MARGIN: int = 4
128
144
 
145
+ _ROTATIONS: dict[int, RotationAngle] = {
146
+ 1: RotationAngle.ROTATION_90,
147
+ 2: RotationAngle.ROTATION_180,
148
+ 3: RotationAngle.ROTATION_270,
149
+ }
150
+
129
151
  def __init__(self) -> None:
130
152
  self._source: PageSource | None = None
131
153
  self._path: str | None = None
132
- self._base: np.ndarray | None = None
154
+ self._base: ImageElement | None = None
133
155
  self._base_key: tuple[str, int] | None = None
134
156
  self.error: str | None = None
135
157
 
@@ -143,7 +165,7 @@ class DocumentViewer:
143
165
  self._base_key = None
144
166
 
145
167
  def _ensure(self, f: File) -> bool:
146
- path = f.absolute()
168
+ path: str = f.absolute()
147
169
  if self._path == path and self._source is not None:
148
170
  return True
149
171
 
@@ -162,34 +184,33 @@ class DocumentViewer:
162
184
  def page_count(self, f: File) -> int:
163
185
  if not self._ensure(f):
164
186
  return 0
165
- return self._source.page_count() # type: ignore[union-attr]
187
+ assert self._source is not None
188
+ return self._source.page_count()
166
189
 
167
- def _load_base(self, f: File, page: int) -> np.ndarray | None:
190
+ def _load_base(self, f: File, page: int) -> ImageElement | None:
168
191
  if not self._ensure(f):
169
192
  return None
170
193
 
171
- src = self._source
194
+ src: PageSource | None = self._source
172
195
  assert src is not None
173
196
  page = min(max(page, 0), max(src.page_count() - 1, 0))
174
- key = (self._path or "", page)
197
+ key: tuple[str, int] = (self._path or "", page)
175
198
  if key == self._base_key and self._base is not None:
176
199
  return self._base
177
200
 
178
201
  try:
179
- img = src.render_page(page)
202
+ img: ImageElement = src.render_page(page)
180
203
  except Exception as e:
181
204
  self._base = None
182
205
  self._base_key = None
183
206
  self.error = str(e)
184
207
  return None
185
208
 
186
- h, w = img.shape[:2]
187
- biggest = max(h, w)
209
+ w, h = img.get_dimensions()
210
+ biggest: int = max(w, h)
188
211
  if biggest > self.MAX_SOURCE_SIDE:
189
- k = self.MAX_SOURCE_SIDE / biggest
190
- img = cv2.resize(
191
- img, (int(w * k), int(h * k)), interpolation=cv2.INTER_AREA
192
- )
212
+ k: float = self.MAX_SOURCE_SIDE / biggest
213
+ img.resize(max(1, int(w * k)), max(1, int(h * k)))
193
214
 
194
215
  self._base = img
195
216
  self._base_key = key
@@ -210,31 +231,30 @@ class DocumentViewer:
210
231
  rotation: quartos de volta no sentido horário (0..3).
211
232
  None = falha (motivo em self.error).
212
233
  """
213
- img = self._load_base(f, page)
214
- if img is None:
234
+ base: ImageElement | None = self._load_base(f, page)
235
+ if base is None:
236
+ return None
237
+
238
+ try:
239
+ img: ImageElement = base.copy() # a base nunca é alterada
240
+ angle: RotationAngle | None = self._ROTATIONS.get(rotation % 4)
241
+ if angle is not None:
242
+ img.set_rotation(angle)
243
+
244
+ w, h = img.get_dimensions()
245
+ bw: int = max(box[0] - self.MARGIN, 1)
246
+ bh: int = max(box[1] - self.MARGIN, 1)
247
+
248
+ scale: float = min(bw / w, bh / h) * zoom
249
+ scale = min(scale, self.MAX_OUTPUT_SIDE / max(w, h))
250
+ nw: int = max(1, int(w * scale))
251
+ nh: int = max(1, int(h * scale))
252
+ img.resize(nw, nh)
253
+
254
+ png: bytes = img.to_bytes().getvalue()
255
+ photo = tk.PhotoImage(data=base64.b64encode(png), format="PNG")
256
+ except Exception as e:
257
+ self.error = str(e)
215
258
  return None
216
259
 
217
- turns = rotation % 4
218
- if turns == 1:
219
- img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
220
- elif turns == 2:
221
- img = cv2.rotate(img, cv2.ROTATE_180)
222
- elif turns == 3:
223
- img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
224
-
225
- h, w = img.shape[:2]
226
- bw = max(box[0] - self.MARGIN, 1)
227
- bh = max(box[1] - self.MARGIN, 1)
228
-
229
- scale = min(bw / w, bh / h) * zoom
230
- scale = min(scale, self.MAX_OUTPUT_SIDE / max(w, h))
231
- nw = max(1, int(w * scale))
232
- nh = max(1, int(h * scale))
233
-
234
- interp = cv2.INTER_AREA if scale < 1 else cv2.INTER_LINEAR
235
- out = cv2.resize(img, (nw, nh), interpolation=interp)
236
- rgb = cv2.cvtColor(out, cv2.COLOR_BGR2RGB)
237
-
238
- header = f"P6 {nw} {nh} 255\n".encode("ascii")
239
- photo = tk.PhotoImage(data=header + rgb.tobytes(), format="PPM")
240
260
  return RenderedImage(photo=photo, width=nw, height=nh)
@@ -20,7 +20,9 @@ from pyflutter_ui import (
20
20
  Text,
21
21
  TkMaterialApp,
22
22
  ValueListenableBuilder,
23
+ PageApp,
23
24
  )
25
+ from pyflutter_ui.ui.core import GlobalKey
24
26
  from table_stream import ArrayList, Directory, File
25
27
 
26
28
  from converter.page_image.common import DOCUMENT_EXTENSIONS
@@ -31,11 +33,13 @@ from converter.page_image.widgets import ImageReaderPanel, ProgressBar, TextInpu
31
33
  _PICKER_EXTENSIONS: list[str] = sorted(e.lstrip(".") for e in DOCUMENT_EXTENSIONS)
32
34
 
33
35
 
34
- class PageImage(StatelessWidget):
36
+ class PageImage(PageApp):
35
37
  """Leitor de imagens e PDFs para renomear cartas manualmente (UC / TOI / data)."""
36
38
 
37
- def __init__(self, key=None) -> None:
38
- super().__init__(key)
39
+ def __init__(
40
+ self, key: GlobalKey | None = None, *, name: str | None = None
41
+ ) -> None:
42
+ super().__init__(key, name=name)
39
43
 
40
44
  def build(self, context: BuildContext) -> FlutterWidget:
41
45
  return _PageImageState(self.key)
@@ -157,7 +161,9 @@ class _PageImageState(StatelessWidget):
157
161
  [
158
162
  ElevatedButton("<- Voltar", lambda: self.action_back(context)),
159
163
  ElevatedButton("Importar pasta", self.action_import_folder),
160
- ElevatedButton("Importar imagens/PDF", self.action_import_files),
164
+ ElevatedButton(
165
+ "Importar imagens/PDF", self.action_import_files
166
+ ),
161
167
  ElevatedButton("Limpar lista", ctrl.clear_list),
162
168
  ElevatedButton("Remover da lista", ctrl.remove_current),
163
169
  ElevatedButton(
@@ -19,7 +19,9 @@ from pyflutter_ui import (
19
19
  StatelessWidget,
20
20
  Text,
21
21
  TkMaterialApp,
22
+ PageApp,
22
23
  )
24
+ from pyflutter_ui.ui.core import GlobalKey
23
25
  from table_stream import Directory
24
26
 
25
27
  from converter.table_widget import TableOrProgress
@@ -31,11 +33,13 @@ def _shorten(text: str, limit: int = 80) -> str:
31
33
  return text if len(text) <= limit else "..." + text[-(limit - 3) :]
32
34
 
33
35
 
34
- class PageOcr(StatelessWidget):
36
+ class PageOcr(PageApp):
35
37
  """Aplica OCR em todos os PDFs e/ou imagens de uma pasta."""
36
38
 
37
- def __init__(self, key=None) -> None:
38
- super().__init__(key)
39
+ def __init__(
40
+ self, key: GlobalKey | None = None, *, name: str | None = None
41
+ ) -> None:
42
+ super().__init__(key, name=name)
39
43
 
40
44
  def build(self, context: BuildContext) -> FlutterWidget:
41
45
  return _PageOcrState(self.key)
@@ -3,11 +3,13 @@ from converter.home_page.view import HomePage
3
3
  from converter.home_page.controller import ControllerHomePage
4
4
  from converter.home_page.model import ExcelModel
5
5
 
6
- from converter.page_forms import ControllerForms, ModelForms
7
- from converter.config_app import AppConfigNotifier, FactoryMapConfig, MapConfig
6
+ from app.page_forms import ControllerForms, ModelForms
7
+ from app.page_whatsapp import ControllerWhatsAppPage, ModelWhatsApp
8
+
9
+ from converter.config_app import AppConfigNotifier
8
10
  from converter.page_image import ControllerImage, ModelImage
9
11
  from converter.page_circuit import ControllerCircuit, ModelCircuit
10
- from converter.page_whatsapp import ControllerWhatsAppPage, ModelWhatsApp
12
+
11
13
  from converter.page_ocr import ControllerOcrPage, ModelOcr
12
14
 
13
15
 
@@ -15,23 +17,21 @@ def main() -> None:
15
17
  Provider.add_state(AppConfigNotifier.create()) # ValueNotifier/Configuração
16
18
 
17
19
  controller_excel = ControllerHomePage(ExcelModel())
18
- controller_forms = ControllerForms(ModelForms())
19
- controller_image = ControllerImage(ModelImage())
20
20
 
21
21
  app = TkMaterialApp(home=HomePage())
22
22
  # app.register_notifier(controller)
23
23
 
24
24
  Provider.add_state(controller_excel)
25
25
  Provider.add_state(controller_excel.data_columns)
26
- Provider.add_state(controller_forms)
27
- Provider.add_state(controller_image)
26
+ Provider.add_state(ControllerForms(ModelForms()))
27
+ Provider.add_state(ControllerImage(ModelImage()))
28
28
  Provider.add_state(ControllerCircuit(ModelCircuit()))
29
29
  Provider.add_state(ControllerWhatsAppPage(ModelWhatsApp()))
30
30
  Provider.add_state(ControllerOcrPage(ModelOcr()))
31
31
 
32
32
  app.set_width(760)
33
33
  app.set_height(480)
34
- app.title = "App Convert"
34
+
35
35
  run_app(app)
36
36
 
37
37
 
@@ -3,7 +3,7 @@
3
3
  THIS=$0
4
4
  THIS_DIR=$(dirname $(readlink -f "$THIS"))
5
5
  APPNAME='pyflutter'
6
- PREFIX_OUTDIR="Documentos/PROMPTS"
6
+ PREFIX_OUTDIR="Documentos/PROMPTS/${APPNAME}"
7
7
 
8
8
  OUTPUT_DIR="${HOME}/${PREFIX_OUTDIR}"
9
9
 
@@ -15,14 +15,8 @@ IMAGE_PAGE_DIR="${CONVERTER_DIR}/page_image"
15
15
  # 1. Declaração do Mapa: [Diretório de Origem]="Arquivo de Saída"
16
16
  declare -A MODULOS=(
17
17
  ["${THIS_DIR}/pyflutter_ui"]="${OUTPUT_DIR}/pyflutter.py"
18
- ["${HOME_PAGE_DIR}"]="${OUTPUT_DIR}/home_page.py"
19
- ["${IMAGE_PAGE_DIR}"]="${OUTPUT_DIR}/image_page.py"
20
18
  ["${CONVERTER_DIR}"]="${OUTPUT_DIR}/converter.py"
21
19
  ["${THIS_DIR}/table_stream"]="${OUTPUT_DIR}/table_stream.py"
22
- ["${THIS_DIR}/ocr_stream"]="${OUTPUT_DIR}/ocr_stream.py"
23
- ["${THIS_DIR}/convert_stream"]="${OUTPUT_DIR}/convert_stream.py"
24
- ["${THIS_DIR}/whatsapp_files"]="${OUTPUT_DIR}/whatsapp.py"
25
- ["${THIS_DIR}/cartas"]="${OUTPUT_DIR}/cartas.py"
26
20
  )
27
21
 
28
22
  function start(){
@@ -0,0 +1,86 @@
1
+ from pyflutter_ui.ui import (
2
+ BaseMaterialApp,
3
+ BuildContext,
4
+ PageApp,
5
+ ChangeNotifier,
6
+ CommandNotifier,
7
+ Consumer,
8
+ Element,
9
+ FlutterWidget,
10
+ ConsumerState,
11
+ LayoutData,
12
+ Navigator,
13
+ Provider,
14
+ StatefulWidget,
15
+ StatelessWidget,
16
+ State,
17
+ ValueNotifier,
18
+ ValueListenableBuilder,
19
+ Provider,
20
+ AlignElement,
21
+ Align,
22
+ AppBar,
23
+ BottomNavigationBar,
24
+ Center,
25
+ Card,
26
+ Checkbox,
27
+ CircularProgress,
28
+ Container,
29
+ Divider,
30
+ Drawer,
31
+ Column,
32
+ Row,
33
+ DropdownButton,
34
+ ElevatedButton,
35
+ Flexible,
36
+ Expanded,
37
+ FloatingActionButton,
38
+ Icon,
39
+ IconButton,
40
+ LinearProgress,
41
+ Provider,
42
+ Padding,
43
+ Image,
44
+ ListBuilder,
45
+ MainAxisAlignment,
46
+ CrossAxisAlignment,
47
+ TableView,
48
+ TkMaterialApp,
49
+ Scaffold,
50
+ Text,
51
+ SizedBox,
52
+ Alignment,
53
+ ScrollView,
54
+ )
55
+
56
+ from .theme import (
57
+ ColorScheme,
58
+ CardTheme,
59
+ InputDecorationTheme,
60
+ TextStyle,
61
+ Theme,
62
+ ThemeData,
63
+ ThemeMode,
64
+ ThemeProvider,
65
+ TextTheme,
66
+ make_themed_app,
67
+ )
68
+
69
+ from .file_picker import (
70
+ FilePickerController,
71
+ FilePickerResult,
72
+ FilePickerType,
73
+ PlatformFile,
74
+ )
75
+
76
+ from pyflutter_ui.ui.list_builder import (
77
+ ScrollableColumn,
78
+ ScrollableColumnElement,
79
+ ScrollableColumnOrProgress,
80
+ )
81
+
82
+ from .navigator import NavigatorApp, PageRoute, Route
83
+ from .app import run_app, SnackBar, SnackBarType
84
+
85
+ from .table_widget import TableOrProgress
86
+ from .config_app import AppConfigNotifier, FactoryMapConfig, MapConfig