ChevalierJoseph commited on
Commit
3e4e2e9
·
verified ·
1 Parent(s): d7059e6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -51
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import spaces
2
  from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
3
  import gradio as gr
@@ -9,10 +10,10 @@ import zipfile
9
  import tempfile
10
  import os
11
  from fontTools.ttLib import TTFont
12
- from fontTools.ttLib.tables import ttProgram
13
- from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
14
- from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
15
- from fontTools.pens.ttGlyphPen import TTGlyphPen
16
 
17
  def load_model():
18
  tokenizer = AutoTokenizer.from_pretrained("ChevalierJoseph/typtop")
@@ -60,53 +61,190 @@ def generate_svg_files(glyphs, width=100, height=100):
60
  svg_files[f"{lettre}.svg"] = svg_content
61
  return svg_files
62
 
63
- def create_otf_font(glyphs, output_path="font.otf"):
64
- font = TTFont()
65
-
66
- # Ajouter les tables nécessaires
67
- font['head'] = TTFont.Table('head')
68
- font['hhea'] = TTFont.Table('hhea')
69
- font['maxp'] = TTFont.Table('maxp')
70
- font['name'] = TTFont.Table('name')
71
- font['OS/2'] = TTFont.Table('OS/2')
72
- font['post'] = TTFont.Table('post')
73
- font['hmtx'] = TTFont.Table('hmtx')
74
- font['cmap'] = TTFont.Table('cmap')
75
- font['glyf'] = TTFont.Table('glyf')
76
-
77
- # Configurer les champs obligatoires pour chaque table
78
- font['head'].fontRevision = 1.0
79
- font['hhea'].ascent = 800
80
- font['hhea'].descent = -200
81
- font['hhea'].lineGap = 0
82
- font['maxp'].numGlyphs = len(glyphs)
83
-
84
- # Créer une table cmap pour mapper les caractères Unicode aux glyphe
85
- cmap_table = CmapSubtable.newSubtable(3, 1, 0) # Windows platform, Unicode BMP encoding
86
- cmap_table.cmap = {}
87
- font['cmap'].tables = [cmap_table]
88
-
89
- # Ajouter chaque glyphe à la police
90
- glyf_table = font['glyf']
91
- hmtx_table = font['hmtx']
92
-
93
- for lettre, path in glyphs:
94
- unicode_val = ord(lettre)
95
- glyph_name = f"glyph{unicode_val}"
96
-
97
- # Créer un glyphe vide (pour un exemple simple)
98
- glyph = glyf_table.glyphClass()
99
- glyph.name = glyph_name
100
- glyf_table[glyph_name] = glyph
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
- # Mapper le code Unicode au nom du glyphe
103
- cmap_table.cmap[unicode_val] = glyph_name
104
- # Définir les métriques pour chaque glyphe
105
- hmtx_table[glyph_name] = (1000, 0) # (advanceWidth, leftSideBearing)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- # Sauvegarder la police
108
- font.save(output_path)
109
- return output_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  def create_zip(svg_files):
112
  with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp_file:
@@ -226,8 +364,13 @@ def create_demo():
226
  def export_to_otf(glyphs):
227
  if not glyphs:
228
  return None
229
- otf_path = create_otf_font(glyphs)
230
- return otf_path
 
 
 
 
 
231
 
232
  msg.submit(
233
  user, [msg, message_history], [msg, message_history], queue=False
@@ -255,3 +398,4 @@ demo = create_demo()
255
 
256
  if __name__ == "__main__":
257
  demo.launch()
 
 
1
+ ```python
2
  import spaces
3
  from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
4
  import gradio as gr
 
10
  import tempfile
11
  import os
12
  from fontTools.ttLib import TTFont
13
+ from fontTools.fontBuilder import FontBuilder
14
+ from fontTools.pens.t2CharStringPen import T2CharStringPen
15
+ from fontTools.cffLib import PrivateDict
16
+ import traceback
17
 
18
  def load_model():
19
  tokenizer = AutoTokenizer.from_pretrained("ChevalierJoseph/typtop")
 
61
  svg_files[f"{lettre}.svg"] = svg_content
62
  return svg_files
63
 
64
+ def draw_svg_path(pen, path_string):
65
+ """
66
+ Parse SVG path string and draw it using the given pen.
67
+ Args:
68
+ pen: The drawing pen
69
+ path_string (str): SVG path string
70
+ """
71
+ # Simple SVG path parser - handles basic commands
72
+ # This is a simplified version - you might need to extend it for complex paths
73
+ commands = re.findall(r'[MLHVCSQTAZmlhvcsqtaz][^MLHVCSQTAZmlhvcsqtaz]*', path_string)
74
+ current_x, current_y = 0, 0
75
+ for command in commands:
76
+ cmd = command[0]
77
+ coords = re.findall(r'-?\d+(?:\.\d+)?', command[1:])
78
+ coords = [float(c) for c in coords]
79
+ if cmd == 'M': # Move to (absolute)
80
+ if len(coords) >= 2:
81
+ current_x, current_y = coords[0], -coords[1] # Inverser l'axe Y
82
+ pen.moveTo((current_x, current_y))
83
+ elif cmd == 'm': # Move to (relative)
84
+ if len(coords) >= 2:
85
+ current_x += coords[0]
86
+ current_y -= coords[1] # Inverser l'axe Y
87
+ pen.moveTo((current_x, current_y))
88
+ elif cmd == 'L': # Line to (absolute)
89
+ for i in range(0, len(coords), 2):
90
+ if i + 1 < len(coords):
91
+ current_x, current_y = coords[i], -coords[i + 1] # Inverser l'axe Y
92
+ pen.lineTo((current_x, current_y))
93
+ elif cmd == 'l': # Line to (relative)
94
+ for i in range(0, len(coords), 2):
95
+ if i + 1 < len(coords):
96
+ current_x += coords[i]
97
+ current_y -= coords[i + 1] # Inverser l'axe Y
98
+ pen.lineTo((current_x, current_y))
99
+ elif cmd == 'H': # Horizontal line to (absolute)
100
+ for x in coords:
101
+ current_x = x
102
+ pen.lineTo((current_x, current_y))
103
+ elif cmd == 'h': # Horizontal line to (relative)
104
+ for dx in coords:
105
+ current_x += dx
106
+ pen.lineTo((current_x, current_y))
107
+ elif cmd == 'V': # Vertical line to (absolute)
108
+ for y in coords:
109
+ current_y = -y # Inverser l'axe Y
110
+ pen.lineTo((current_x, current_y))
111
+ elif cmd == 'v': # Vertical line to (relative)
112
+ for dy in coords:
113
+ current_y -= dy # Inverser l'axe Y
114
+ pen.lineTo((current_x, current_y))
115
+ elif cmd == 'C': # Cubic bezier curve (absolute)
116
+ for i in range(0, len(coords), 6):
117
+ if i + 5 < len(coords):
118
+ x1, y1 = coords[i], -coords[i + 1] # Inverser l'axe Y
119
+ x2, y2 = coords[i + 2], -coords[i + 3] # Inverser l'axe Y
120
+ current_x, current_y = coords[i + 4], -coords[i + 5] # Inverser l'axe Y
121
+ pen.curveTo((x1, y1), (x2, y2), (current_x, current_y))
122
+ elif cmd == 'c': # Cubic bezier curve (relative)
123
+ for i in range(0, len(coords), 6):
124
+ if i + 5 < len(coords):
125
+ x1 = current_x + coords[i]
126
+ y1 = current_y - coords[i + 1] # Inverser l'axe Y
127
+ x2 = current_x + coords[i + 2]
128
+ y2 = current_y - coords[i + 3] # Inverser l'axe Y
129
+ current_x += coords[i + 4]
130
+ current_y -= coords[i + 5] # Inverser l'axe Y
131
+ pen.curveTo((x1, y1), (x2, y2), (current_x, current_y))
132
+ elif cmd in ['Z', 'z']: # Close path
133
+ pen.closePath()
134
+ pen.endPath()
135
 
136
+ def create_otf_font(glyphs, output_path="font.otf"):
137
+ glyph_paths = {lettre: path for lettre, path in glyphs}
138
+
139
+ # Add default .notdef if not present (simple rectangle)
140
+ default_notdef_path = "M50 700H450V0H50Z M100 600L400 100 M400 600L100 100" # Box with X, Y down for SVG
141
+ if '.notdef' not in glyph_paths:
142
+ glyph_paths['.notdef'] = default_notdef_path
143
+
144
+ # Prepare glyph order
145
+ glyph_names = list(glyph_paths.keys())
146
+ if glyph_names[0] != '.notdef':
147
+ glyph_names.insert(0, '.notdef') # Ensure .notdef is first
148
+
149
+ # Prepare Unicode mapping
150
+ unicode_map = {}
151
+ for glyph_name in glyph_paths:
152
+ if len(glyph_name) == 1 and glyph_name.isalpha():
153
+ unicode_value = ord(glyph_name.upper())
154
+ unicode_map[unicode_value] = glyph_name
155
+
156
+ # Prepare PrivateDict with defaults
157
+ private_dict = PrivateDict()
158
+ private_dict.nominalWidthX = 600
159
+ private_dict.defaultWidthX = 600
160
+ private_dict.blueValues = []
161
+ private_dict.otherBlues = []
162
+ private_dict.familyBlues = []
163
+ private_dict.familyOtherBlues = []
164
+ private_dict.BlueScale = 0.039625
165
+ private_dict.BlueShift = 7
166
+ private_dict.BlueFuzz = 1
167
+ private_dict.StdHW = [100]
168
+ private_dict.StdVW = [100]
169
+
170
+ # Create charstrings
171
+ char_strings = {}
172
+ for glyph_name, svg_path in glyph_paths.items():
173
+ try:
174
+ pen = T2CharStringPen(600, None) # Width 600, no glyphSet yet
175
+ draw_svg_path(pen, svg_path)
176
+ charstring = pen.getCharString(private=private_dict)
177
+ char_strings[glyph_name] = charstring
178
+ except Exception as e:
179
+ print(f"Error converting glyph {glyph_name}: {e}")
180
+ traceback.print_exc()
181
+
182
+ # Prepare font names
183
+ new_font_name = "Custom Glyph Font"
184
+ family_name = new_font_name
185
+ style_name = "Regular"
186
+ full_name = f"{family_name} {style_name}"
187
+ ps_name = f"{family_name.replace(' ', '')}-{style_name}"
188
+ name_strings = {
189
+ "familyName": family_name,
190
+ "styleName": style_name,
191
+ "fullName": full_name,
192
+ "psName": ps_name,
193
+ # Add more localized if needed
194
+ }
195
 
196
+ # Setup FontBuilder
197
+ try:
198
+ fb = FontBuilder(1000, isTTF=False) # unitsPerEm=1000, OTF (CFF)
199
+ fb.setupGlyphOrder(glyph_names)
200
+ fb.setupCharacterMap(unicode_map)
201
+
202
+ # Setup CFF table with privateDicts
203
+ top_dict_data = {"FullName": full_name}
204
+ fb.setupCFF(ps_name, top_dict_data, char_strings, {ps_name: private_dict})
205
+
206
+ # Calculate metrics with debug
207
+ advance_widths = {gn: 600 for gn in glyph_names}
208
+ metrics = {}
209
+ max_ascent = 0
210
+ min_descent = 0
211
+ for gn, cs in char_strings.items():
212
+ try:
213
+ bounds = cs.calcBounds(None)
214
+ lsb = int(round(bounds[0])) if bounds else 0
215
+ if bounds:
216
+ max_ascent = max(max_ascent, int(round(bounds[3])))
217
+ min_descent = min(min_descent, int(round(bounds[1])))
218
+ metrics[gn] = (advance_widths[gn], lsb)
219
+ except Exception as e:
220
+ print(f"Error calculating bounds for {gn}: {e}")
221
+ traceback.print_exc()
222
+ metrics[gn] = (advance_widths[gn], 0) # Fallback
223
+
224
+ fb.setupHorizontalMetrics(metrics)
225
+ # Adjust ascent/descent based on glyphs if possible
226
+ ascent = max(800, max_ascent)
227
+ descent = min(-200, min_descent)
228
+ fb.setupHorizontalHeader(ascent=ascent, descent=descent)
229
+ fb.setupNameTable(name_strings)
230
+ fb.setupOS2()
231
+ fb.setupPost()
232
+ except Exception as e:
233
+ print(f"Error building font: {e}")
234
+ traceback.print_exc()
235
+ return None
236
+
237
+ # Save the font
238
+ try:
239
+ fb.save(output_path)
240
+ # Basic validation
241
+ test_font = TTFont(output_path)
242
+ test_font.close()
243
+ return output_path
244
+ except Exception as e:
245
+ print(f"Error saving font: {e}")
246
+ traceback.print_exc()
247
+ return None
248
 
249
  def create_zip(svg_files):
250
  with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp_file:
 
364
  def export_to_otf(glyphs):
365
  if not glyphs:
366
  return None
367
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".otf") as tmp_file:
368
+ otf_path = tmp_file.name
369
+ created_path = create_otf_font(glyphs, otf_path)
370
+ if created_path:
371
+ return created_path
372
+ else:
373
+ return None
374
 
375
  msg.submit(
376
  user, [msg, message_history], [msg, message_history], queue=False
 
398
 
399
  if __name__ == "__main__":
400
  demo.launch()
401
+ ```