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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +40 -263
app.py CHANGED
@@ -1,274 +1,51 @@
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
- # Fontmake / UFO
14
- from fontmake.font_project import FontProject
15
- from defcon import Font, Glyph
16
- from svgpathtools import parse_path, Line, CubicBezier, QuadraticBezier
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
79
- # ------------------------
80
- def add_svg_to_glyph(glyph: Glyph, path_d: str, width=600):
81
- pen = glyph.getPen()
82
- try:
83
- svg_path = parse_path(path_d)
84
- for segment in svg_path:
85
- start = segment.start
86
- end = segment.end
87
- if isinstance(segment, Line):
88
- pen.moveTo((start.real, start.imag))
89
- pen.lineTo((end.real, end.imag))
90
- pen.closePath()
91
- elif isinstance(segment, CubicBezier):
92
- pen.moveTo((start.real, start.imag))
93
- pen.curveTo(
94
- (segment.control1.real, segment.control1.imag),
95
- (segment.control2.real, segment.control2.imag),
96
- (end.real, end.imag)
97
- )
98
- pen.closePath()
99
- elif isinstance(segment, QuadraticBezier):
100
- pen.moveTo((start.real, start.imag))
101
- pen.qCurveTo(
102
- (segment.control.real, segment.control.imag),
103
- (end.real, end.imag)
104
- )
105
- pen.closePath()
106
- except Exception as e:
107
- print(f"Erreur parsing SVG: {e}")
108
- glyph.width = width
109
 
110
  def build_ufo_from_glyphs(glyphs):
111
  font = Font()
112
  font.info.familyName = "TipTopType"
113
  font.info.styleName = "Regular"
114
-
115
  for letter, path_data in glyphs:
116
  glyph = Glyph()
117
  glyph.name = letter
118
  glyph.unicode = ord(letter)
119
- add_svg_to_glyph(glyph, path_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  font.insertGlyph(glyph)
121
-
122
  return font
123
-
124
- def save_otf_font(glyphs, font_name="TipTopType-Regular.otf"):
125
- if not glyphs:
126
- return None
127
-
128
- with tempfile.TemporaryDirectory() as tmpdir:
129
- ufo_path = os.path.join(tmpdir, "font.ufo")
130
- font = build_ufo_from_glyphs(glyphs)
131
- font.save(ufo_path)
132
-
133
- output_dir = os.path.join(tmpdir, "out")
134
- os.makedirs(output_dir, exist_ok=True)
135
-
136
- project = FontProject()
137
- project.run_from_ufos([ufo_path], output=["otf"], output_dir=output_dir)
138
-
139
- otf_files = glob.glob(os.path.join(output_dir, "**/*.otf"), recursive=True)
140
- if not otf_files:
141
- raise FileNotFoundError("Aucun fichier OTF généré par fontmake.")
142
-
143
- generated_path = otf_files[0]
144
- final_path = os.path.join(tempfile.gettempdir(), font_name)
145
- os.replace(generated_path, final_path)
146
-
147
- return final_path
148
-
149
- # ------------------------
150
- # GENERATION
151
- # ------------------------
152
- @spaces.GPU(duration=180)
153
- def respond(message: str, system_message: str, max_tokens: int, temperature: float, top_p: float):
154
- tokenizer, model = load_model()
155
- if torch.cuda.is_available():
156
- model = model.to('cuda')
157
- model_device = next(model.parameters()).device
158
-
159
- messages = [{"role": "system", "content": system_message}]
160
- messages.append({"role": "user", "content": message})
161
-
162
- inputs = tokenizer.apply_chat_template(
163
- messages,
164
- tokenize=True,
165
- add_generation_prompt=True,
166
- return_tensors="pt",
167
- ).to(model_device)
168
-
169
- streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
170
-
171
- generation_kwargs = {
172
- "input_ids": inputs,
173
- "streamer": streamer,
174
- "max_new_tokens": max_tokens,
175
- "temperature": float(temperature) if temperature > 0 else None,
176
- "top_p": float(top_p) if top_p < 1.0 else None,
177
- "do_sample": True,
178
- "use_cache": True,
179
- }
180
- if temperature <= 0.01:
181
- generation_kwargs["do_sample"] = False
182
- generation_kwargs.pop("temperature", None)
183
- generation_kwargs.pop("top_p", None)
184
-
185
- thread = Thread(target=model.generate, kwargs=generation_kwargs)
186
- thread.start()
187
-
188
- partial_response = ""
189
- for new_text in streamer:
190
- partial_response += new_text
191
- glyphs = extract_glyphs(partial_response)
192
- yield partial_response, glyphs
193
- thread.join()
194
-
195
- # ------------------------
196
- # GRADIO APP
197
- # ------------------------
198
- def create_demo():
199
- with gr.Blocks() as demo:
200
- gr.Markdown("# TypTopType")
201
- glyphs_state = gr.State([])
202
- message_history = gr.State([])
203
-
204
- with gr.Row():
205
- with gr.Column(scale=1):
206
- msg = gr.Textbox(label="input box, type here")
207
- system_message = gr.Textbox(
208
- value="Based on the following text, give me the svgpath of the glyphs from A to Z.",
209
- visible=False
210
- )
211
- max_tokens = gr.Slider(minimum=1, maximum=9048, value=9048, step=1, visible=False)
212
- temperature = gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, visible=False)
213
- top_p = gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, visible=False)
214
- cols = gr.Slider(minimum=1, maximum=10, value=5, step=1, visible=False)
215
- width = gr.Slider(minimum=50, maximum=200, value=100, step=10, visible=False)
216
- height = gr.Slider(minimum=50, maximum=200, value=100, step=10, visible=False)
217
-
218
- download_btn = gr.Button("Download svg file")
219
- download_otf_btn = gr.Button("Download OTF font")
220
-
221
- with gr.Column(scale=3):
222
- gr.Markdown("## preview")
223
- svg_preview = gr.HTML(label="SVG Preview")
224
- download_output = gr.File(label="Download ZIP")
225
- download_otf_output = gr.File(label="Download OTF")
226
-
227
- def user(user_message, history):
228
- return "", history + [[user_message, None]]
229
-
230
- def bot(history, system_message, max_tokens, temperature, top_p, cols, width, height):
231
- message = history[-1][0]
232
- response_generator = respond(message, system_message, max_tokens, temperature, top_p)
233
- full_response, glyphs_list = "", []
234
- for partial_response, glyphs in response_generator:
235
- full_response = partial_response
236
- if glyphs:
237
- glyphs_list = glyphs
238
- svg_html = generate_glyphs_html(glyphs_list, cols=cols, width=width, height=height) if glyphs_list else "No glyphs found."
239
- yield svg_html, glyphs_list
240
-
241
- def download_svg(glyphs, width, height):
242
- if not glyphs:
243
- return None
244
- svg_files = generate_svg_files(glyphs, width=width, height=height)
245
- zip_path = create_zip(svg_files)
246
- return zip_path
247
-
248
- def download_otf(glyphs):
249
- if not glyphs:
250
- return None
251
- return save_otf_font(glyphs)
252
-
253
- msg.submit(user, [msg, message_history], [msg, message_history], queue=False).then(
254
- bot, [message_history, system_message, max_tokens, temperature, top_p, cols, width, height], [svg_preview, glyphs_state]
255
- )
256
-
257
- download_btn.click(
258
- download_svg,
259
- inputs=[glyphs_state, width, height],
260
- outputs=download_output
261
- )
262
-
263
- download_otf_btn.click(
264
- download_otf,
265
- inputs=[glyphs_state],
266
- outputs=download_otf_output
267
- )
268
-
269
- return demo
270
-
271
- demo = create_demo()
272
-
273
- if __name__ == "__main__":
274
- demo.launch()
 
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