Repository navigation
Expand file tree
/
Copy pathmain.py
More file actions
212 lines (178 loc) · 7.55 KB
/
Copy pathmain.py
File metadata and controls
212 lines (178 loc) · 7.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
"""
Scripta CLI
Usage examples:
python main.py --input "Hello, this is Scripta." --output output/test.png
python main.py --input doc.pdf --style a01 --page ruled --ink blue --output output/doc.pdf
python main.py --input essay.docx --page parchment --ink black --output output/essay.pdf
python main.py --backend neural --style a01 --input "Hello world" --output output/neural.png
python main.py --list-writers
python main.py --list-pages
"""
import argparse
import sys
from pathlib import Path
import config
from scripta.input_handler import from_text, from_file
from scripta.page_compositor import PAGE_STYLES
def save_output(pages, out_path: Path) -> None:
suffix = out_path.suffix.lower()
if suffix == ".pdf":
try:
import img2pdf
pdf_bytes = img2pdf.convert([p.tobytes() for p in pages])
# img2pdf needs raw bytes with size info — use PIL save approach instead
except Exception:
pass
# Reliable PDF output via Pillow
if len(pages) == 1:
pages[0].save(str(out_path), "PDF", resolution=config.PAGE_DPI)
else:
pages[0].save(
str(out_path), "PDF", resolution=config.PAGE_DPI,
save_all=True, append_images=pages[1:],
)
else:
# PNG output — save each page
if len(pages) == 1:
pages[0].save(str(out_path))
else:
for i, page in enumerate(pages):
p = out_path.with_stem(f"{out_path.stem}_p{i+1}")
page.save(str(p))
print(f"Saved {len(pages)} pages.")
def main():
config.ensure_runtime_dirs()
parser = argparse.ArgumentParser(
prog="scripta",
description="Convert text to humanized handwriting.",
)
parser.add_argument("--input", "-i", type=str,
help="Input text string, or path to .txt / .pdf / .docx")
parser.add_argument("--output", "-o", type=str, default="output/out.png",
help="Output path (.png or .pdf). Default: output/out.png")
parser.add_argument("--style", "-s", type=str, default=None,
help="Writer ID from IAM dataset (e.g. a01). Default: random.")
parser.add_argument("--page", "-p", type=str, default="ruled",
choices=list(PAGE_STYLES.keys()),
help="Page style. Default: ruled")
parser.add_argument("--ink", type=str, default=config.DEFAULT_INK,
choices=list(config.INK_COLORS.keys()),
help="Ink color. Default: blue")
parser.add_argument("--no-artifacts", action="store_true",
help="Skip artifact simulation (faster, cleaner)")
parser.add_argument("--seed", type=int, default=None,
help="Random seed for reproducible output")
parser.add_argument("--backend", type=str, default="font",
choices=["font", "neural"],
help="Rendering backend: 'font' (fast) or 'neural' (VATr++, best quality). Default: font")
parser.add_argument("--list-writers", action="store_true",
help="List all available writer IDs and exit")
parser.add_argument("--list-pages", action="store_true",
help="List all page styles and exit")
args = parser.parse_args()
if args.list_pages:
print("Available page styles:")
for name in PAGE_STYLES:
print(f" {name}")
return
# ------------------------------------------------------------------
# Neural backend: list writers from IAM style samples
# ------------------------------------------------------------------
if args.backend == "neural":
from scripta.neural_renderer import NeuralRenderer, STYLE_DIR
from scripta.neural_page_compositor import NeuralPageCompositor
if args.list_writers:
renderer = NeuralRenderer()
styles = renderer.available_styles()
print(f"Available neural writer styles ({len(styles)}):")
for s in styles:
print(f" {s}")
return
if not args.input:
parser.print_help()
sys.exit(1)
input_path = Path(args.input)
paragraphs = from_file(input_path) if input_path.exists() else from_text(args.input)
total_words = sum(len(p) for p in paragraphs)
print(f"Input: {len(paragraphs)} paragraph(s), {total_words} word(s)")
print("Loading VATr++ neural renderer...")
renderer = NeuralRenderer()
try:
renderer.load(verbose=True)
except Exception as exc:
print(f"ERROR: {exc}")
sys.exit(1)
# Pick style: user choice or random from available
available = renderer.available_styles()
if not available:
print(
"ERROR: No neural style samples found. "
"Run `python scripts/prep_style_samples.py` after setting up VATr-pp and the IAM dataset."
)
sys.exit(1)
style_id = args.style if args.style in available else available[0]
print(f"Using neural style: {style_id}")
renderer.set_style(style_id)
out_path = Path(args.output)
out_path.parent.mkdir(parents=True, exist_ok=True)
compositor = NeuralPageCompositor(
neural_renderer=renderer,
writer_id=style_id,
ink_color=args.ink,
page_style=args.page,
apply_artifacts=not args.no_artifacts,
seed=args.seed,
)
print(f"Rendering (neural) on '{args.page}' page in {args.ink} ink...")
pages = compositor.render(paragraphs)
print(f"Rendered {len(pages)} page(s)")
save_output(pages, out_path)
print(f"Saved: {out_path.resolve()}")
return
# ------------------------------------------------------------------
# Font backend (default)
# ------------------------------------------------------------------
from scripta.glyph_store import GlyphStore
from scripta.page_compositor import PageCompositor
print("Loading handwriting dataset...")
store = GlyphStore()
try:
store.load(verbose=True)
except Exception as exc:
print(f"ERROR: {exc}")
sys.exit(1)
if args.list_writers:
print(f"\nAvailable writers ({len(store.writers)}):")
for w in sorted(store.writers):
vocab_size = len(store.writer_vocabulary(w))
print(f" {w} ({vocab_size} words in vocabulary)")
return
if not args.input:
parser.print_help()
sys.exit(1)
input_path = Path(args.input)
if input_path.exists():
print(f"Reading file: {input_path}")
paragraphs = from_file(input_path)
else:
paragraphs = from_text(args.input)
total_words = sum(len(p) for p in paragraphs)
print(f"Input: {len(paragraphs)} paragraph(s), {total_words} word(s)")
out_path = Path(args.output)
out_path.parent.mkdir(parents=True, exist_ok=True)
compositor = PageCompositor(
glyph_store=store,
writer_id=args.style,
ink_color=args.ink,
page_style=args.page,
apply_artifacts=not args.no_artifacts,
seed=args.seed,
)
print(f"Rendering with writer '{compositor.writer_state.writer_id}' "
f"on '{args.page}' page in {args.ink} ink...")
pages = compositor.render(paragraphs)
print(f"Rendered {len(pages)} page(s)")
save_output(pages, out_path)
print(f"Saved: {out_path.resolve()}")
if __name__ == "__main__":
main()