ChevalierJoseph commited on
Commit
77eb792
·
verified ·
1 Parent(s): 69b111e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +251 -47
app.py CHANGED
@@ -1,51 +1,255 @@
1
- from fontTools.pens.recordingPen import RecordingPen
2
- from fontTools.svgLib.path import parsePath
3
-
4
- def parse_svg_path(path_data):
5
- """ Parse SVG path data and return a sequence of drawing commands."""
6
- path = parsePath(path_data)
7
- pen = RecordingPen()
8
- current_point = (0, 0)
9
- for segment in path:
10
- if segment.command == "M":
11
- current_point = (segment.endPoint.real, segment.endPoint.imag)
12
- pen.moveTo(current_point)
13
- elif segment.command == "L":
14
- current_point = (segment.endPoint.real, segment.endPoint.imag)
15
- pen.lineTo(current_point)
16
- elif segment.command == "C":
17
- current_point = (segment.endPoint.real, segment.endPoint.imag)
18
- pen.curveTo(
19
- ((segment.ctrl1.real, segment.ctrl1.imag),
20
- (segment.ctrl2.real, segment.ctrl2.imag),
21
- current_point)
22
- )
23
- elif segment.command == "Z":
24
- pen.closePath()
25
- return pen.value
26
-
27
- def build_ufo_from_glyphs(glyphs):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  font = Font()
29
  font.info.familyName = "TipTopType"
30
  font.info.styleName = "Regular"
31
- for letter, path_data in glyphs:
32
- glyph = Glyph()
33
- glyph.name = letter
34
- glyph.unicode = ord(letter)
35
- pen = glyph.getPen()
36
- try:
37
- commands = parse_svg_path(path_data.strip())
38
- for cmd in commands:
39
- if cmd[0] == "moveTo":
40
- pen.moveTo(cmd[1])
41
- elif cmd[0] == "lineTo":
42
- pen.lineTo(cmd[1])
43
- elif cmd[0] == "curveTo":
44
- pen.curveTo(cmd[1][0], cmd[1][1], cmd[1][2])
45
- elif cmd[0] == "closePath":
46
- pen.closePath()
47
- glyph.width = 600 # largeur du glyphe
48
- except Exception as e:
49
- print(f"Erreur injection SVG pour {letter}: {e}")
50
- font.insertGlyph(glyph)
51
  return font
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spaces
2
+ from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
3
+ import gradio as gr
4
+ import torch
5
+ from threading import Thread
6
+ import re
7
+ import io
8
+ import zipfile
9
+ import tempfile
10
+ import os
11
+ import glob
12
+
13
+ # UFO / FONTMAKE
14
+ from defcon import Font, Glyph
15
+ from fontmake.font_project import FontProject
16
+ from ufo2ft.util import _readSVG
17
+
18
+ # ------------------------
19
+ # MODELE
20
+ # ------------------------
21
+ def load_model():
22
+ tokenizer = AutoTokenizer.from_pretrained("ChevalierJoseph/typtop")
23
+ model = AutoModelForCausalLM.from_pretrained("ChevalierJoseph/typtop")
24
+ return tokenizer, model
25
+
26
+ # ------------------------
27
+ # SVG + GLYPH EXTRACTION
28
+ # ------------------------
29
+ def generate_svg(path_data, width=50, height=50):
30
+ svg_template = f"""
31
+ <svg width="{width}" height="{height}" viewBox="0 0 {width} {height}" xmlns="http://www.w3.org/2000/svg">
32
+ <path d="{path_data}" fill="black"/>
33
+ </svg>
34
+ """
35
+ return svg_template
36
+
37
+ def extract_glyphs(text):
38
+ pattern = r"Glyph\s+([A-Z])\s+([MmZzLlHhVvCcSsQqTtAa0-9,\s\.\-]+?)(?=\s*Glyph\s+[A-Z]|\s*$)"
39
+ glyphs = re.findall(pattern, text)
40
+ return glyphs
41
+
42
+ def generate_glyphs_html(glyphs, cols=5, width=100, height=100):
43
+ html_parts = []
44
+ for lettre, path in glyphs:
45
+ svg_content = f"""
46
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-100 -800 900 900" width="{width}" height="{height}">
47
+ <g transform="translate(0, 0)">
48
+ <path d="{path.strip()}" fill="black"/>
49
+ </g>
50
+ </svg>
51
+ """
52
+ html_parts.append(f"<div style='display: inline-block; margin: 10px; text-align: center;'><h3>{lettre}</h3>{svg_content}</div>")
53
+ grid_style = f"display: grid; grid-template-columns: repeat({cols}, 1fr); gap: 20px;"
54
+ return f'<div style="{grid_style}">{"".join(html_parts)}</div>'
55
+
56
+ def generate_svg_files(glyphs, width=100, height=100):
57
+ svg_files = {}
58
+ for lettre, path in glyphs:
59
+ svg_content = f"""
60
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-100 -800 900 900" width="{width}" height="{height}">
61
+ <g transform="translate(0, 0)">
62
+ <path d="{path.strip()}" fill="black"/>
63
+ </g>
64
+ </svg>
65
+ """
66
+ svg_files[f"{lettre}.svg"] = svg_content
67
+ return svg_files
68
+
69
+ def create_zip(svg_files):
70
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp_file:
71
+ zip_path = tmp_file.name
72
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
73
+ for filename, content in svg_files.items():
74
+ zip_file.writestr(filename, content)
75
+ return zip_path
76
+
77
+ # ------------------------
78
+ # UFO / FONTMAKE (copie exacte des SVG)
79
+ # ------------------------
80
+ def build_ufo_from_glyphs_svg_direct(glyphs):
81
  font = Font()
82
  font.info.familyName = "TipTopType"
83
  font.info.styleName = "Regular"
84
+
85
+ with tempfile.TemporaryDirectory() as tmpdir:
86
+ for letter, path_data in glyphs:
87
+ # Crée un fichier SVG temporaire pour ce glyphe
88
+ svg_path = os.path.join(tmpdir, f"{letter}.svg")
89
+ with open(svg_path, "w") as f:
90
+ f.write(f'<svg xmlns="http://www.w3.org/2000/svg"><path d="{path_data.strip()}" /></svg>')
91
+
92
+ glyph = Glyph()
93
+ glyph.name = letter
94
+ glyph.unicode = ord(letter)
95
+
96
+ # Lit le SVG et ajoute les contours directement dans le glyphe
97
+ contours, _ = _readSVG(svg_path)
98
+ for contour in contours:
99
+ glyph.appendContour(contour)
100
+
101
+ font.insertGlyph(glyph)
102
+
 
103
  return font
104
+
105
+ def save_otf_font(glyphs, font_name="TipTopType-Regular.otf"):
106
+ if not glyphs:
107
+ return None
108
+
109
+ with tempfile.TemporaryDirectory() as tmpdir:
110
+ ufo_path = os.path.join(tmpdir, "font.ufo")
111
+ font = build_ufo_from_glyphs_svg_direct(glyphs)
112
+ font.save(ufo_path)
113
+
114
+ output_dir = os.path.join(tmpdir, "out")
115
+ os.makedirs(output_dir, exist_ok=True)
116
+
117
+ project = FontProject()
118
+ project.run_from_ufos([ufo_path], output=["otf"], output_dir=output_dir)
119
+
120
+ otf_files = glob.glob(os.path.join(output_dir, "**/*.otf"), recursive=True)
121
+ if not otf_files:
122
+ raise FileNotFoundError("Aucun fichier OTF généré par fontmake.")
123
+
124
+ generated_path = otf_files[0]
125
+ final_path = os.path.join(tempfile.gettempdir(), font_name)
126
+ os.replace(generated_path, final_path)
127
+
128
+ return final_path
129
+
130
+ # ------------------------
131
+ # GENERATION
132
+ # ------------------------
133
+ @spaces.GPU(duration=180)
134
+ def respond(message: str, system_message: str, max_tokens: int, temperature: float, top_p: float):
135
+ tokenizer, model = load_model()
136
+ if torch.cuda.is_available():
137
+ model = model.to('cuda')
138
+ model_device = next(model.parameters()).device
139
+
140
+ messages = [{"role": "system", "content": system_message}]
141
+ messages.append({"role": "user", "content": message})
142
+
143
+ inputs = tokenizer.apply_chat_template(
144
+ messages,
145
+ tokenize=True,
146
+ add_generation_prompt=True,
147
+ return_tensors="pt",
148
+ ).to(model_device)
149
+
150
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
151
+
152
+ generation_kwargs = {
153
+ "input_ids": inputs,
154
+ "streamer": streamer,
155
+ "max_new_tokens": max_tokens,
156
+ "temperature": float(temperature) if temperature > 0 else None,
157
+ "top_p": float(top_p) if top_p < 1.0 else None,
158
+ "do_sample": True,
159
+ "use_cache": True,
160
+ }
161
+ if temperature <= 0.01:
162
+ generation_kwargs["do_sample"] = False
163
+ generation_kwargs.pop("temperature", None)
164
+ generation_kwargs.pop("top_p", None)
165
+
166
+ thread = Thread(target=model.generate, kwargs=generation_kwargs)
167
+ thread.start()
168
+
169
+ partial_response = ""
170
+ for new_text in streamer:
171
+ partial_response += new_text
172
+ glyphs = extract_glyphs(partial_response)
173
+ yield partial_response, glyphs
174
+ thread.join()
175
+
176
+ # ------------------------
177
+ # GRADIO APP
178
+ # ------------------------
179
+ def create_demo():
180
+ with gr.Blocks() as demo:
181
+ gr.Markdown("# TypTopType")
182
+ glyphs_state = gr.State([])
183
+ message_history = gr.State([])
184
+
185
+ with gr.Row():
186
+ with gr.Column(scale=1):
187
+ msg = gr.Textbox(label="input box, type here")
188
+ system_message = gr.Textbox(
189
+ value="Based on the following text, give me the svgpath of the glyphs from A to Z.",
190
+ visible=False
191
+ )
192
+ max_tokens = gr.Slider(minimum=1, maximum=9048, value=9048, step=1, visible=False)
193
+ temperature = gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, visible=False)
194
+ top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, visible=False)
195
+ cols = gr.Slider(minimum=1, maximum=10, value=5, step=1, visible=False)
196
+ width = gr.Slider(minimum=50, maximum=200, value=100, step=10, visible=False)
197
+ height = gr.Slider(minimum=50, maximum=200, value=100, step=10, visible=False)
198
+
199
+ download_btn = gr.Button("Download svg file")
200
+ download_otf_btn = gr.Button("Download OTF font")
201
+
202
+ with gr.Column(scale=3):
203
+ gr.Markdown("## preview")
204
+ svg_preview = gr.HTML(label="SVG Preview")
205
+ download_output = gr.File(label="Download ZIP")
206
+ download_otf_output = gr.File(label="Download OTF")
207
+
208
+ def user(user_message, history):
209
+ return "", history + [[user_message, None]]
210
+
211
+ def bot(history, system_message, max_tokens, temperature, top_p, cols, width, height):
212
+ message = history[-1][0]
213
+ response_generator = respond(message, system_message, max_tokens, temperature, top_p)
214
+ full_response, glyphs_list = "", []
215
+ for partial_response, glyphs in response_generator:
216
+ full_response = partial_response
217
+ if glyphs:
218
+ glyphs_list = glyphs
219
+ svg_html = generate_glyphs_html(glyphs_list, cols=cols, width=width, height=height) if glyphs_list else "No glyphs found."
220
+ yield svg_html, glyphs_list
221
+
222
+ def download_svg(glyphs, width, height):
223
+ if not glyphs:
224
+ return None
225
+ svg_files = generate_svg_files(glyphs, width=width, height=height)
226
+ zip_path = create_zip(svg_files)
227
+ return zip_path
228
+
229
+ def download_otf(glyphs):
230
+ if not glyphs:
231
+ return None
232
+ return save_otf_font(glyphs)
233
+
234
+ msg.submit(user, [msg, message_history], [msg, message_history], queue=False).then(
235
+ bot, [message_history, system_message, max_tokens, temperature, top_p, cols, width, height], [svg_preview, glyphs_state]
236
+ )
237
+
238
+ download_btn.click(
239
+ download_svg,
240
+ inputs=[glyphs_state, width, height],
241
+ outputs=download_output
242
+ )
243
+
244
+ download_otf_btn.click(
245
+ download_otf,
246
+ inputs=[glyphs_state],
247
+ outputs=download_otf_output
248
+ )
249
+
250
+ return demo
251
+
252
+ demo = create_demo()
253
+
254
+ if __name__ == "__main__":
255
+ demo.launch()