Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
| 1 |
-
import re
|
| 2 |
-
import os
|
| 3 |
-
import tempfile
|
| 4 |
-
import glob
|
| 5 |
-
from fontTools.pens.svgPathPen import SVGPathPen
|
| 6 |
-
from fontTools.svgLib.path import parsePath
|
| 7 |
-
from defcon import Font, Glyph
|
| 8 |
-
from fontmake.font_project import FontProject
|
| 9 |
import spaces
|
| 10 |
from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
|
| 11 |
import gradio as gr
|
| 12 |
import torch
|
| 13 |
from threading import Thread
|
| 14 |
-
import
|
| 15 |
import io
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
# ------------------------
|
| 18 |
# MODELE
|
|
@@ -76,59 +77,73 @@ def create_zip(svg_files):
|
|
| 76 |
# ------------------------
|
| 77 |
# UFO / FONTMAKE
|
| 78 |
# ------------------------
|
| 79 |
-
def
|
| 80 |
-
""" Parse the SVG path and draw it into the glyph using the correct pen methods."""
|
| 81 |
pen = glyph.getPen()
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
pen.lineTo((
|
| 90 |
-
|
|
|
|
|
|
|
| 91 |
pen.curveTo(
|
| 92 |
-
(
|
| 93 |
-
|
| 94 |
-
|
| 95 |
)
|
| 96 |
-
elif segment.command == "Z":
|
| 97 |
pen.closePath()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
def build_ufo_from_glyphs(glyphs):
|
| 100 |
font = Font()
|
| 101 |
font.info.familyName = "TipTopType"
|
| 102 |
font.info.styleName = "Regular"
|
|
|
|
| 103 |
for letter, path_data in glyphs:
|
| 104 |
glyph = Glyph()
|
| 105 |
glyph.name = letter
|
| 106 |
glyph.unicode = ord(letter)
|
| 107 |
-
|
| 108 |
-
parse_svg_path(path_data.strip(), glyph)
|
| 109 |
-
glyph.width = 600 # largeur du glyphe
|
| 110 |
-
except Exception as e:
|
| 111 |
-
print(f"Erreur injection SVG pour {letter}: {e}")
|
| 112 |
font.insertGlyph(glyph)
|
|
|
|
| 113 |
return font
|
| 114 |
|
| 115 |
def save_otf_font(glyphs, font_name="TipTopType-Regular.otf"):
|
| 116 |
if not glyphs:
|
| 117 |
return None
|
|
|
|
| 118 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 119 |
ufo_path = os.path.join(tmpdir, "font.ufo")
|
| 120 |
font = build_ufo_from_glyphs(glyphs)
|
| 121 |
font.save(ufo_path)
|
|
|
|
| 122 |
output_dir = os.path.join(tmpdir, "out")
|
| 123 |
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
| 124 |
project = FontProject()
|
| 125 |
project.run_from_ufos([ufo_path], output=["otf"], output_dir=output_dir)
|
|
|
|
| 126 |
otf_files = glob.glob(os.path.join(output_dir, "**/*.otf"), recursive=True)
|
| 127 |
if not otf_files:
|
| 128 |
raise FileNotFoundError("Aucun fichier OTF généré par fontmake.")
|
|
|
|
| 129 |
generated_path = otf_files[0]
|
| 130 |
final_path = os.path.join(tempfile.gettempdir(), font_name)
|
| 131 |
os.replace(generated_path, final_path)
|
|
|
|
| 132 |
return final_path
|
| 133 |
|
| 134 |
# ------------------------
|
|
@@ -140,15 +155,19 @@ def respond(message: str, system_message: str, max_tokens: int, temperature: flo
|
|
| 140 |
if torch.cuda.is_available():
|
| 141 |
model = model.to('cuda')
|
| 142 |
model_device = next(model.parameters()).device
|
|
|
|
| 143 |
messages = [{"role": "system", "content": system_message}]
|
| 144 |
messages.append({"role": "user", "content": message})
|
|
|
|
| 145 |
inputs = tokenizer.apply_chat_template(
|
| 146 |
messages,
|
| 147 |
tokenize=True,
|
| 148 |
add_generation_prompt=True,
|
| 149 |
return_tensors="pt",
|
| 150 |
).to(model_device)
|
|
|
|
| 151 |
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
|
|
|
|
| 152 |
generation_kwargs = {
|
| 153 |
"input_ids": inputs,
|
| 154 |
"streamer": streamer,
|
|
@@ -162,8 +181,10 @@ def respond(message: str, system_message: str, max_tokens: int, temperature: flo
|
|
| 162 |
generation_kwargs["do_sample"] = False
|
| 163 |
generation_kwargs.pop("temperature", None)
|
| 164 |
generation_kwargs.pop("top_p", None)
|
|
|
|
| 165 |
thread = Thread(target=model.generate, kwargs=generation_kwargs)
|
| 166 |
thread.start()
|
|
|
|
| 167 |
partial_response = ""
|
| 168 |
for new_text in streamer:
|
| 169 |
partial_response += new_text
|
|
@@ -179,6 +200,7 @@ def create_demo():
|
|
| 179 |
gr.Markdown("# TypTopType")
|
| 180 |
glyphs_state = gr.State([])
|
| 181 |
message_history = gr.State([])
|
|
|
|
| 182 |
with gr.Row():
|
| 183 |
with gr.Column(scale=1):
|
| 184 |
msg = gr.Textbox(label="input box, type here")
|
|
@@ -192,15 +214,19 @@ def create_demo():
|
|
| 192 |
cols = gr.Slider(minimum=1, maximum=10, value=5, step=1, visible=False)
|
| 193 |
width = gr.Slider(minimum=50, maximum=200, value=100, step=10, visible=False)
|
| 194 |
height = gr.Slider(minimum=50, maximum=200, value=100, step=10, visible=False)
|
|
|
|
| 195 |
download_btn = gr.Button("Download svg file")
|
| 196 |
download_otf_btn = gr.Button("Download OTF font")
|
|
|
|
| 197 |
with gr.Column(scale=3):
|
| 198 |
gr.Markdown("## preview")
|
| 199 |
svg_preview = gr.HTML(label="SVG Preview")
|
| 200 |
download_output = gr.File(label="Download ZIP")
|
| 201 |
download_otf_output = gr.File(label="Download OTF")
|
|
|
|
| 202 |
def user(user_message, history):
|
| 203 |
return "", history + [[user_message, None]]
|
|
|
|
| 204 |
def bot(history, system_message, max_tokens, temperature, top_p, cols, width, height):
|
| 205 |
message = history[-1][0]
|
| 206 |
response_generator = respond(message, system_message, max_tokens, temperature, top_p)
|
|
@@ -211,31 +237,38 @@ def create_demo():
|
|
| 211 |
glyphs_list = glyphs
|
| 212 |
svg_html = generate_glyphs_html(glyphs_list, cols=cols, width=width, height=height) if glyphs_list else "No glyphs found."
|
| 213 |
yield svg_html, glyphs_list
|
|
|
|
| 214 |
def download_svg(glyphs, width, height):
|
| 215 |
if not glyphs:
|
| 216 |
return None
|
| 217 |
svg_files = generate_svg_files(glyphs, width=width, height=height)
|
| 218 |
zip_path = create_zip(svg_files)
|
| 219 |
return zip_path
|
|
|
|
| 220 |
def download_otf(glyphs):
|
| 221 |
if not glyphs:
|
| 222 |
return None
|
| 223 |
return save_otf_font(glyphs)
|
|
|
|
| 224 |
msg.submit(user, [msg, message_history], [msg, message_history], queue=False).then(
|
| 225 |
bot, [message_history, system_message, max_tokens, temperature, top_p, cols, width, height], [svg_preview, glyphs_state]
|
| 226 |
)
|
|
|
|
| 227 |
download_btn.click(
|
| 228 |
download_svg,
|
| 229 |
inputs=[glyphs_state, width, height],
|
| 230 |
outputs=download_output
|
| 231 |
)
|
|
|
|
| 232 |
download_otf_btn.click(
|
| 233 |
download_otf,
|
| 234 |
inputs=[glyphs_state],
|
| 235 |
outputs=download_otf_output
|
| 236 |
)
|
|
|
|
| 237 |
return demo
|
| 238 |
|
| 239 |
demo = create_demo()
|
|
|
|
| 240 |
if __name__ == "__main__":
|
| 241 |
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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 |
# ------------------------
|
|
|
|
| 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,
|
|
|
|
| 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
|
|
|
|
| 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")
|
|
|
|
| 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)
|
|
|
|
| 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()
|