pyflutter-app 0.3.13__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.
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/.gitignore +1 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/PKG-INFO +5 -5
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/home_page/page_sheets.py +8 -6
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/home_page/view.py +11 -7
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_circuit/view_circuit.py +27 -9
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/renderer.py +80 -60
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/view_image.py +10 -4
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_ocr/view_ocr.py +7 -3
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/run_app_convert.py +8 -8
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/new-export_app.sh +1 -7
- pyflutter_app-0.4/pyflutter_ui/__init__.py +86 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/app.py +5 -5
- pyflutter_app-0.4/pyflutter_ui/config_app.py +163 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/navigator.py +19 -18
- pyflutter_app-0.4/pyflutter_ui/table_widget.py +160 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/__init__.py +1 -1
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/core.py +161 -108
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/widgets.py +202 -371
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyproject.toml +6 -9
- pyflutter_app-0.4/teste.py +16 -0
- pyflutter_app-0.4/uv.lock +1100 -0
- pyflutter_app-0.3.13/.idea/.gitignore +0 -3
- pyflutter_app-0.3.13/.idea/inspectionProfiles/Project_Default.xml +0 -30
- pyflutter_app-0.3.13/.idea/inspectionProfiles/profiles_settings.xml +0 -6
- pyflutter_app-0.3.13/.idea/misc.xml +0 -7
- pyflutter_app-0.3.13/.idea/modules.xml +0 -8
- pyflutter_app-0.3.13/.idea/pyflutter_app.iml +0 -14
- pyflutter_app-0.3.13/.idea/vcs.xml +0 -6
- pyflutter_app-0.3.13/cartas/__init__.py +0 -2085
- pyflutter_app-0.3.13/convert_stream/__init__.py +0 -1502
- pyflutter_app-0.3.13/converter/page_forms/Diagnorstico.txt +0 -108
- pyflutter_app-0.3.13/converter/page_forms/__init__.py +0 -5
- pyflutter_app-0.3.13/converter/page_forms/common.py +0 -192
- pyflutter_app-0.3.13/converter/page_forms/config.py +0 -68
- pyflutter_app-0.3.13/converter/page_forms/controller_forms.py +0 -457
- pyflutter_app-0.3.13/converter/page_forms/diagnose_forms.py +0 -64
- pyflutter_app-0.3.13/converter/page_forms/master_forms.py +0 -113
- pyflutter_app-0.3.13/converter/page_forms/model_forms.py +0 -344
- pyflutter_app-0.3.13/converter/page_forms/service.py +0 -347
- pyflutter_app-0.3.13/converter/page_forms/view_forms.py +0 -166
- pyflutter_app-0.3.13/converter/page_whatsapp/PATCH_whatsapp_files.md +0 -245
- pyflutter_app-0.3.13/converter/page_whatsapp/__init__.py +0 -4
- pyflutter_app-0.3.13/converter/page_whatsapp/common.py +0 -41
- pyflutter_app-0.3.13/converter/page_whatsapp/controller_whatsapp.py +0 -391
- pyflutter_app-0.3.13/converter/page_whatsapp/model_whatsapp.py +0 -395
- pyflutter_app-0.3.13/converter/page_whatsapp/view_whatsapp.py +0 -271
- pyflutter_app-0.3.13/ocr_stream/__init__.py +0 -1835
- pyflutter_app-0.3.13/pyflutter_ui/__init__.py +0 -30
- pyflutter_app-0.3.13/teste.py +0 -27
- pyflutter_app-0.3.13/uv.lock +0 -1035
- pyflutter_app-0.3.13/whatsapp_files/__init__.py +0 -1193
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/README.md +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/__init__.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/__main__.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/assets/forms_config.json +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/config_app.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/home_page/__init__.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/home_page/_page_edit.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/home_page/controller.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/home_page/model.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/lib_toi.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_circuit/__init__.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_circuit/common.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_circuit/controller_circuit.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_circuit/model_circuit.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_circuit/service.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/__init__.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/common.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/controller_image.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/model_image.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/progress.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_image/widgets.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_ocr/__init__.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_ocr/common.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_ocr/controller_ocr.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/page_ocr/model_ocr.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/table_widget.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/converter/util.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/local_build/linux.sh +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/local_build/send.sh +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/main.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/mvc/controller.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/mvc/model.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/mvc/view.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/file_picker.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/scheduler.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/theme.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/buttons.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/container.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/list_builder.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/progress.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/pyflutter_ui/ui/tables.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/table_stream/__init__.py +0 -0
- {pyflutter_app-0.3.13 → pyflutter_app-0.4}/update.sh +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyflutter_app
|
|
3
|
-
Version: 0.
|
|
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:
|
|
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(
|
|
43
|
+
class PageSheets(PageApp):
|
|
42
44
|
"""Importar/carregar planilhas. De lá se acessa a edição de dados."""
|
|
43
45
|
|
|
44
|
-
def __init__(
|
|
45
|
-
|
|
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
|
-
|
|
27
|
+
|
|
28
|
+
class HomePage(PageApp):
|
|
27
29
|
"""Home: somente botões para navegar entre as páginas."""
|
|
28
30
|
|
|
29
|
-
def __init__(
|
|
30
|
-
|
|
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[[],
|
|
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[[],
|
|
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(
|
|
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__(
|
|
43
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
178
|
-
|
|
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
|
|
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
|
|
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) ->
|
|
35
|
-
"""Página
|
|
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.
|
|
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) ->
|
|
52
|
-
return
|
|
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
|
-
"""
|
|
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.
|
|
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) ->
|
|
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
|
-
|
|
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
|
|
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
|
-
(
|
|
121
|
-
para zoom/rotação serem rápidos.
|
|
122
|
-
antes de renomear/mover
|
|
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:
|
|
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
|
-
|
|
187
|
+
assert self._source is not None
|
|
188
|
+
return self._source.page_count()
|
|
166
189
|
|
|
167
|
-
def _load_base(self, f: File, page: int) ->
|
|
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
|
-
|
|
187
|
-
biggest = max(
|
|
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
|
|
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
|
-
|
|
214
|
-
if
|
|
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(
|
|
36
|
+
class PageImage(PageApp):
|
|
35
37
|
"""Leitor de imagens e PDFs para renomear cartas manualmente (UC / TOI / data)."""
|
|
36
38
|
|
|
37
|
-
def __init__(
|
|
38
|
-
|
|
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(
|
|
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(
|
|
36
|
+
class PageOcr(PageApp):
|
|
35
37
|
"""Aplica OCR em todos os PDFs e/ou imagens de uma pasta."""
|
|
36
38
|
|
|
37
|
-
def __init__(
|
|
38
|
-
|
|
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
|
|
7
|
-
from
|
|
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
|
-
|
|
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(
|
|
27
|
-
Provider.add_state(
|
|
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
|
-
|
|
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
|