first commit
This commit is contained in:
commit
67eb7fa315
13 changed files with 943 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Python
|
||||||
|
*/venv
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.DS_Store
|
||||||
20
ascii_writer/README.md
Normal file
20
ascii_writer/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# ASCII Writer
|
||||||
|
|
||||||
|
Une bibliothèque python pour dessiner avec du texte brut dans le style ASCII (mais avec Unicode).
|
||||||
|
Par Manetta Berends
|
||||||
|
|
||||||
|
## Système
|
||||||
|
- Linux
|
||||||
|
- OSX
|
||||||
|
|
||||||
|
## Dépendance
|
||||||
|
Ce site peut aider à l'installation: https://avoidsoftware.sarahgarcin.com/dependances.html
|
||||||
|
|
||||||
|
- Python 3
|
||||||
|
- Asciiwriter ```pip3 install asciiwriter```
|
||||||
|
|
||||||
|
## Marche à suivre
|
||||||
|
- Télécharger les fichiers .py
|
||||||
|
- Ouvrir le terminal et lancer le script python souhaité: `python3 line.py`
|
||||||
|
- Le résultat s'affiche dans le terminal
|
||||||
|
-Vous pouvez ensuite tester les autres exemples présents dans le dossier et modifier les variables et le texte dans les fichiers Python.
|
||||||
28
ascii_writer/line.py
Normal file
28
ascii_writer/line.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Draws a single, vertical line
|
||||||
|
"""
|
||||||
|
|
||||||
|
from asciiWriter.patterns import vertical
|
||||||
|
from asciiWriter.utils import make_lines, visit, print_lines
|
||||||
|
from asciiWriter.marks import sentence, space
|
||||||
|
|
||||||
|
# Set the canvas
|
||||||
|
width = 75
|
||||||
|
height = 75
|
||||||
|
|
||||||
|
# Define the line, most importantly it's position
|
||||||
|
pattern = vertical(20)
|
||||||
|
# We're going to fill the line with a text
|
||||||
|
mark = sentence('AVOID SOFTWARE ')
|
||||||
|
# Set the character for the 'blank' space
|
||||||
|
blank = space()
|
||||||
|
|
||||||
|
# Make a canvas
|
||||||
|
lines = make_lines(width, height)
|
||||||
|
# Draw the result
|
||||||
|
result = visit(lines, pattern, mark, blank)
|
||||||
|
|
||||||
|
# Print the result
|
||||||
|
print_lines(result)
|
||||||
30
ascii_writer/line_random.py
Normal file
30
ascii_writer/line_random.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Draws a single just like line.py but does so
|
||||||
|
using a 'random' character
|
||||||
|
"""
|
||||||
|
|
||||||
|
from asciiWriter.patterns import vertical
|
||||||
|
from asciiWriter.utils import make_lines, visit, print_lines
|
||||||
|
from asciiWriter.marks import random, space
|
||||||
|
|
||||||
|
# Set the canvas
|
||||||
|
width = 75
|
||||||
|
height = 75
|
||||||
|
|
||||||
|
# Define the line, most importantly it's position
|
||||||
|
image_pattern = vertical(20)
|
||||||
|
# We're going to fill the line with random selections
|
||||||
|
# from the '%$&@!*' chars
|
||||||
|
mark = random('%$&@!*')
|
||||||
|
# Set the character for the 'blank' space
|
||||||
|
blank = space()
|
||||||
|
|
||||||
|
# Make a canvas
|
||||||
|
lines = make_lines(width, height)
|
||||||
|
# Draw the result
|
||||||
|
result = visit(lines, image_pattern, mark, blank)
|
||||||
|
|
||||||
|
# Print the result
|
||||||
|
print_lines(result)
|
||||||
39
ascii_writer/repeated_lines.py
Normal file
39
ascii_writer/repeated_lines.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Draws lines like line.py, but draws more than one
|
||||||
|
"""
|
||||||
|
|
||||||
|
from asciiWriter.patterns import vertical
|
||||||
|
from asciiWriter.utils import make_lines, visit, print_lines, merge
|
||||||
|
from asciiWriter.marks import sentence, space
|
||||||
|
|
||||||
|
# Set the canvas
|
||||||
|
width = 75
|
||||||
|
height = 75
|
||||||
|
|
||||||
|
# We are going to draw multiple lines and collect them
|
||||||
|
# in a list named 'layers'
|
||||||
|
layers = []
|
||||||
|
|
||||||
|
# Set the position of the line, do this in a loop
|
||||||
|
# from 10 to 75 in steps of then
|
||||||
|
for x in range(10, 75, 10):
|
||||||
|
# Define the line, x will start at 10 and grow in steps of 10
|
||||||
|
image_pattern = vertical(x)
|
||||||
|
# Fill the line with the sentence 'OPEN DESIGN COURSE '
|
||||||
|
mark = sentence('AVOID SOFTWARE ')
|
||||||
|
# Set the blank space
|
||||||
|
blank = space()
|
||||||
|
|
||||||
|
# Make a canvas
|
||||||
|
lines = make_lines(width, height)
|
||||||
|
# Make a layer with the line
|
||||||
|
layer = visit(lines, image_pattern, mark, blank)
|
||||||
|
# Add the layer to the list of layers
|
||||||
|
layers.append(layer)
|
||||||
|
|
||||||
|
# Merge the list of layers into a single layer
|
||||||
|
result = merge(width, height, blank(), layers)
|
||||||
|
# Print the result
|
||||||
|
print_lines(result)
|
||||||
36
ascii_writer/repeated_sinus.py
Normal file
36
ascii_writer/repeated_sinus.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from asciiWriter.patterns import sinus_vertical
|
||||||
|
from asciiWriter.utils import make_lines, visit, print_lines, merge
|
||||||
|
from asciiWriter.marks import sentence, space
|
||||||
|
|
||||||
|
# Define width and height of the output
|
||||||
|
width = 80
|
||||||
|
height = 70
|
||||||
|
|
||||||
|
# As we draw multiple sinoids we will collect
|
||||||
|
# them in a list of layers
|
||||||
|
layers = []
|
||||||
|
|
||||||
|
# Loop through an offset from -40 to 40 in steps of 10
|
||||||
|
for x in range(-40, 40, 10):
|
||||||
|
# Set the pattern with the changing offset
|
||||||
|
pattern = sinus_vertical(period=40, amplitude=40, offset=x)
|
||||||
|
# We use a sentence to draw the text
|
||||||
|
mark = sentence('convivialité ')
|
||||||
|
# Define a blank character
|
||||||
|
blank = space()
|
||||||
|
|
||||||
|
# Make the canvas
|
||||||
|
lines = make_lines(width, height)
|
||||||
|
|
||||||
|
# Draw the sinoid, but add it to the list
|
||||||
|
result = visit(lines, pattern, mark, blank)
|
||||||
|
# Add it the result to the list of layers
|
||||||
|
layers.append(result)
|
||||||
|
|
||||||
|
# Merge the layers into one layer again
|
||||||
|
merged = merge(width, height, blank(), layers)
|
||||||
|
|
||||||
|
# Print the result
|
||||||
|
print_lines(merged)
|
||||||
37
ascii_writer/repeated_sinus_amplitude_variation.py
Normal file
37
ascii_writer/repeated_sinus_amplitude_variation.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from asciiWriter.patterns import sinus_vertical
|
||||||
|
from asciiWriter.utils import make_lines, visit, print_lines, merge
|
||||||
|
from asciiWriter.marks import sentence, space
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Define width and height of the output
|
||||||
|
width = 75
|
||||||
|
height = 75
|
||||||
|
|
||||||
|
# As we draw multiple sinoids we will collect
|
||||||
|
# them in a list of layers
|
||||||
|
layers = []
|
||||||
|
|
||||||
|
# Loop through an amplitude of -50 to 50 in steps of 10
|
||||||
|
for amplitude in range(-50, 50, 10):
|
||||||
|
# Set the pattern with the changing amplitude
|
||||||
|
pattern = sinus_vertical(period=40, amplitude=amplitude)
|
||||||
|
# We use a sentence to draw the text
|
||||||
|
mark = sentence('convivialité ')
|
||||||
|
# Define a blank character
|
||||||
|
blank = space()
|
||||||
|
|
||||||
|
# Make the canvas
|
||||||
|
lines = make_lines(width, height)
|
||||||
|
# Draw the sinoid, but add it to the list
|
||||||
|
result = visit(lines, pattern, mark, blank)
|
||||||
|
# Add it the result to the list of layers
|
||||||
|
layers.append(result)
|
||||||
|
|
||||||
|
# Merge the layers into one layer again
|
||||||
|
merged = merge(width, height, blank(), layers)
|
||||||
|
|
||||||
|
# Print the result
|
||||||
|
print_lines(merged)
|
||||||
27
ascii_writer/sinus-text.py
Normal file
27
ascii_writer/sinus-text.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from asciiWriter.patterns import sinus_vertical
|
||||||
|
from asciiWriter.utils import make_lines, visit, print_lines
|
||||||
|
from asciiWriter.marks import sentence, space, single
|
||||||
|
|
||||||
|
# Define width and height of the output
|
||||||
|
width = 75
|
||||||
|
height = 75
|
||||||
|
|
||||||
|
# Set the pattern we use to draw, in this case a
|
||||||
|
# sinoid, with period of 40 lines, and an amplitude
|
||||||
|
# of 30 characters. Slightly less than half our canvas width
|
||||||
|
pattern = sinus_vertical(period=40, amplitude=30)
|
||||||
|
# We use a sentence to draw the text
|
||||||
|
mark = sentence('tools shape pratices shape tools shape pratices ')
|
||||||
|
# Define a blank character
|
||||||
|
blank = single(' ')
|
||||||
|
|
||||||
|
# Make the canvas
|
||||||
|
lines = make_lines(width, height)
|
||||||
|
|
||||||
|
# Draw the sinoid
|
||||||
|
result = visit(lines, pattern, mark, blank)
|
||||||
|
|
||||||
|
# Output the result
|
||||||
|
print_lines(result)
|
||||||
51
ascii_writer/sinus.py
Normal file
51
ascii_writer/sinus.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from asciiWriter.patterns import sinus_vertical
|
||||||
|
from asciiWriter.utils import make_lines, visit, print_lines, merge
|
||||||
|
from asciiWriter.marks import sentence, space
|
||||||
|
|
||||||
|
# Define width and height of the output
|
||||||
|
width = 70
|
||||||
|
height = 25
|
||||||
|
|
||||||
|
# As we draw multiple sinoids we will collect
|
||||||
|
# them in a list of layers
|
||||||
|
layers = []
|
||||||
|
|
||||||
|
# Loop through a range of X, X in steps of X
|
||||||
|
# (the amount of loops is the amount of layers)
|
||||||
|
for x in range(-50, 50, 5):
|
||||||
|
# Set the pattern with the changing offset
|
||||||
|
pattern = sinus_vertical(period=10, amplitude=40, offset=x)
|
||||||
|
# We use a sentence to draw the text
|
||||||
|
mark = sentence('▚▒▓▞')
|
||||||
|
# Define a blank character
|
||||||
|
blank = space(' ')
|
||||||
|
|
||||||
|
# Make the canvas
|
||||||
|
lines = make_lines(width, height)
|
||||||
|
|
||||||
|
# Draw the sinoid, but add it to the list
|
||||||
|
result = visit(lines, pattern, mark, blank)
|
||||||
|
# Add it the result to the list of layers
|
||||||
|
layers.append(result)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Merge the layers into one layer again
|
||||||
|
merged = merge(width, height, blank(), layers)
|
||||||
|
|
||||||
|
# Print the result
|
||||||
|
print_lines(merged)
|
||||||
|
|
||||||
|
# Write the result in txt file
|
||||||
|
# with open("sinus.txt", 'w') as newFile:
|
||||||
|
# for els in merged:
|
||||||
|
# for el in els:
|
||||||
|
# # print(el)
|
||||||
|
# # write each item on a new line
|
||||||
|
# newFile.write(el)
|
||||||
|
# print('Done')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
BIN
font_pixellizer/Decor-Regular.ttf
Normal file
BIN
font_pixellizer/Decor-Regular.ttf
Normal file
Binary file not shown.
BIN
font_pixellizer/IBMPlexMono-Regular.ttf
Normal file
BIN
font_pixellizer/IBMPlexMono-Regular.ttf
Normal file
Binary file not shown.
160
font_pixellizer/README.md
Normal file
160
font_pixellizer/README.md
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
# Font Pixelizer
|
||||||
|
|
||||||
|
Ensemble d'outils Python pour transformer n'importe quelle typographie en version pixelisée avec paramètres.
|
||||||
|
|
||||||
|
## Fonctionnalités
|
||||||
|
|
||||||
|
- **Pixelisation complète** : Convertit une fonte vectorielle en version pixelisée
|
||||||
|
- **Contrôle de la taille des pixels** : De très détaillé à très pixellisé
|
||||||
|
- **Modification des proportions** : Hauteur d'x et largeur ajustables
|
||||||
|
- **Support monospace** : Option pour forcer un espacement fixe
|
||||||
|
- **Formes de pixels personnalisables** : Carré, rond, losange
|
||||||
|
|
||||||
|
## Dépendances
|
||||||
|
|
||||||
|
Ce site peut aider à l'installation: https://avoidsoftware.sarahgarcin.com/dependances.html
|
||||||
|
|
||||||
|
### Installer python fontforge
|
||||||
|
FontForge est un éditeur de polices de caractères libre et open source. En plus d’intégrer Python, FontForge installe généralement un module Python accessible à l’exécutable Python du système. FontForge est disponible dans son ensemble selon les termes de la GNU GPL version 3 ou toute version ultérieure. Cette extension permet la manipulation de typographies.
|
||||||
|
|
||||||
|
#### sur macOS
|
||||||
|
|
||||||
|
Nécessite Homebrew
|
||||||
|
|
||||||
|
- Ouvrir le terminal
|
||||||
|
-Lancer la commande ```brew install fontforge```
|
||||||
|
-Tester si Fontforge est bien exécutable par Python : ```python3 import fontforge, psMat```
|
||||||
|
-Si la commande ne fonctionne pas (Mac est un peu capricieux quand il s’agit de Fontforge), il faut déplacer le module Fontforge vers les dépendances de Python :
|
||||||
|
```sudo cp $(find $(brew --prefix fontforge)/. -name "fontforge.so") $(python3 -c "import site;print(site.getsitepackages()[0]);")```
|
||||||
|
```sudo cp $(find $(brew --prefix fontforge)/. -name "psMat.so") $(python3 -c "import site;print(site.getsitepackages()[0]);")```
|
||||||
|
|
||||||
|
#### sur Linux
|
||||||
|
- Ouvrir le Terminal en utilisateur root
|
||||||
|
- Mettre à jour le gestionnaire de paquets :
|
||||||
|
```sudo apt update```
|
||||||
|
```sudo apt upgrade```
|
||||||
|
- Installer Python Fontforge
|
||||||
|
```python3-fontforge```
|
||||||
|
|
||||||
|
|
||||||
|
### Installer les autres dépendances
|
||||||
|
```bash
|
||||||
|
pip install fontforge svgpathtools lxml --break-system-packages
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
### Script simple (une fonte)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 font_pixelizer.py votre_fonte.ttf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Paramètres modifiables dans le script :
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Taille des pixels
|
||||||
|
pixel_size = 80 # Plus grand = moins de détails, effet plus rétro
|
||||||
|
|
||||||
|
# Échelles de déformation
|
||||||
|
x_height_scale = 1.0 # 0.5 = écrasé, 2.0 = étiré verticalement
|
||||||
|
width_scale = 1.0 # 0.5 = condensé, 2.0 = étendu horizontalement
|
||||||
|
|
||||||
|
# Monospace
|
||||||
|
monospace = False # True pour forcer espacement fixe
|
||||||
|
monospace_width = None # Largeur fixe (None = auto-calculé)
|
||||||
|
|
||||||
|
# Style des pixels
|
||||||
|
pixel_shape = "square" # "square", "round", "diamond"
|
||||||
|
pixel_gap = 0 # Espace entre pixels (0 = collés)
|
||||||
|
round_corners = 0 # Rayon arrondi des coins (0 = coins droits)
|
||||||
|
|
||||||
|
# Export
|
||||||
|
new_family_name = "Pixel"
|
||||||
|
font_weight = "Regular"
|
||||||
|
export_formats = ["ttf"] # ["ttf", "otf", "woff", "woff2"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exemples de configuration
|
||||||
|
|
||||||
|
### Fonte rétro 8-bit classique
|
||||||
|
```python
|
||||||
|
pixel_size = 120
|
||||||
|
x_height_scale = 1.0
|
||||||
|
width_scale = 1.0
|
||||||
|
pixel_shape = "square"
|
||||||
|
pixel_gap = 0
|
||||||
|
monospace = True
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fonte pixel moderne arrondie
|
||||||
|
```python
|
||||||
|
pixel_size = 60
|
||||||
|
x_height_scale = 1.0
|
||||||
|
width_scale = 1.0
|
||||||
|
pixel_shape = "square"
|
||||||
|
pixel_gap = 2
|
||||||
|
round_corners = 8
|
||||||
|
monospace = False
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fonte condensée haute résolution
|
||||||
|
```python
|
||||||
|
pixel_size = 40
|
||||||
|
x_height_scale = 1.2
|
||||||
|
width_scale = 0.8
|
||||||
|
pixel_shape = "square"
|
||||||
|
monospace = False
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fonte rétro avec pixels espacés
|
||||||
|
```python
|
||||||
|
pixel_size = 100
|
||||||
|
x_height_scale = 1.0
|
||||||
|
width_scale = 1.0
|
||||||
|
pixel_shape = "round"
|
||||||
|
pixel_gap = 10
|
||||||
|
monospace = True
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Styles de pixels disponibles
|
||||||
|
|
||||||
|
### Square (carré)
|
||||||
|
```python
|
||||||
|
pixel_shape = "square"
|
||||||
|
round_corners = 0 # Coins droits
|
||||||
|
```
|
||||||
|
|
||||||
|
### Square arrondi
|
||||||
|
```python
|
||||||
|
pixel_shape = "square"
|
||||||
|
round_corners = 15 # Coins arrondis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Round (cercle)
|
||||||
|
```python
|
||||||
|
pixel_shape = "round"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Diamond (losange)
|
||||||
|
```python
|
||||||
|
pixel_shape = "diamond"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comment ça marche ?
|
||||||
|
|
||||||
|
1. **Extraction** : Le script extrait tous les glyphes de la fonte source en SVG
|
||||||
|
2. **Analyse** : Chaque glyphe est analysé pour déterminer sa forme
|
||||||
|
3. **Grille** : Une grille de pixels est calculée selon `pixel_size`
|
||||||
|
4. **Raycasting** : Pour chaque position de pixel, on teste si elle est dans le contour original
|
||||||
|
5. **Génération** : Les pixels sont créés sous forme de SVG
|
||||||
|
6. **Reconstruction** : Une nouvelle fonte est assemblée avec tous les glyphes pixelisés
|
||||||
|
7. **Export** : La fonte est exportée au format souhaité
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
GNU GPL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
505
font_pixellizer/font_pixelizer.py
Normal file
505
font_pixellizer/font_pixelizer.py
Normal file
|
|
@ -0,0 +1,505 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import fontforge, os, re, glob, shutil, sys
|
||||||
|
from svgpathtools import *
|
||||||
|
import lxml.etree as ET
|
||||||
|
import math
|
||||||
|
from random import randrange
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CONFIGURATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
fontname = sys.argv[1] # font source à pixeliser
|
||||||
|
|
||||||
|
# Paramètres de pixelisation
|
||||||
|
pixel_size = 30 # Taille des pixels en unités fonte (plus grand = moins de détails)
|
||||||
|
x_height_scale = 1 # Échelle de la hauteur d'x (0.5 = écrasé, 2.0 = étiré)
|
||||||
|
width_scale = 1 # Échelle de largeur (0.5 = condensé, 2.0 = étendu)
|
||||||
|
monospace = True # True pour forcer l'espacement fixe
|
||||||
|
monospace_width = None # Largeur fixe si monospace (None = auto-calculé)
|
||||||
|
|
||||||
|
# Style des pixels
|
||||||
|
pixel_shape = "square" # Options: "square", "round", "diamond"
|
||||||
|
pixel_gap = 0 # Espace entre les pixels (0 = pixels collés)
|
||||||
|
round_corners = 0 # Rayon d'arrondi des coins pour pixels carrés (0 = coins droits)
|
||||||
|
|
||||||
|
# Paramètres d'export
|
||||||
|
new_family_name = "Decor" # Nom de famille de la nouvelle typo
|
||||||
|
font_weight = "Regular" # Options: "Thin", "Light", "Regular", "Medium", "Bold", "Black"
|
||||||
|
font_style = "normal" # Options: "normal", "italic", "oblique"
|
||||||
|
export_formats = ["ttf"] # Formats d'export souhaités
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# INITIALISATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir("svg")
|
||||||
|
os.mkdir("svg2")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print("FONT PIXELIZER")
|
||||||
|
print("=" * 70)
|
||||||
|
print(f"Fonte source: {fontname}")
|
||||||
|
print(f"Taille pixel: {pixel_size}")
|
||||||
|
print(f"Échelle hauteur x: {x_height_scale}")
|
||||||
|
print(f"Échelle largeur: {width_scale}")
|
||||||
|
print(f"Monospace: {monospace}")
|
||||||
|
print(f"Forme pixel: {pixel_shape}")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
print("\nOuverture de la fonte source...")
|
||||||
|
font = fontforge.open(fontname)
|
||||||
|
font_ascent, font_descent = font.ascent, font.descent
|
||||||
|
|
||||||
|
# Stocker les infos des glyphes
|
||||||
|
glyph_info = {}
|
||||||
|
composite_glyphs = {}
|
||||||
|
|
||||||
|
print("Extraction des glyphes...")
|
||||||
|
for gly in font.glyphs():
|
||||||
|
glyph_name = gly.glyphname
|
||||||
|
is_composite = len(gly.references) > 0
|
||||||
|
|
||||||
|
if is_composite:
|
||||||
|
composite_glyphs[glyph_name] = {
|
||||||
|
'width': gly.width,
|
||||||
|
'unicode': gly.unicode,
|
||||||
|
'encoding': gly.encoding,
|
||||||
|
'references': []
|
||||||
|
}
|
||||||
|
for ref in gly.references:
|
||||||
|
composite_glyphs[glyph_name]['references'].append({
|
||||||
|
'glyph': ref[0],
|
||||||
|
'transform': ref[1]
|
||||||
|
})
|
||||||
|
print(f" Composite: {glyph_name} (unicode: {gly.unicode})")
|
||||||
|
else:
|
||||||
|
glyph_info[glyph_name] = {
|
||||||
|
'width': gly.width,
|
||||||
|
'unicode': gly.unicode,
|
||||||
|
'encoding': gly.encoding
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
gly.export("svg/" + glyph_name + ".svg")
|
||||||
|
print(f" Exporté: {glyph_name} (unicode: {gly.unicode})")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Erreur export {glyph_name}: {e}")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# FONCTIONS DE PIXELISATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def create_pixel_path(x, y, size, shape="square", gap=0, round_radius=0):
|
||||||
|
"""Crée un path SVG pour un pixel unique"""
|
||||||
|
actual_size = size - gap
|
||||||
|
half = actual_size / 2
|
||||||
|
|
||||||
|
if shape == "square":
|
||||||
|
if round_radius > 0:
|
||||||
|
# Carré avec coins arrondis
|
||||||
|
r = min(round_radius, half)
|
||||||
|
path_data = f"""
|
||||||
|
M {x - half + r},{y - half}
|
||||||
|
L {x + half - r},{y - half}
|
||||||
|
Q {x + half},{y - half} {x + half},{y - half + r}
|
||||||
|
L {x + half},{y + half - r}
|
||||||
|
Q {x + half},{y + half} {x + half - r},{y + half}
|
||||||
|
L {x - half + r},{y + half}
|
||||||
|
Q {x - half},{y + half} {x - half},{y + half - r}
|
||||||
|
L {x - half},{y - half + r}
|
||||||
|
Q {x - half},{y - half} {x - half + r},{y - half}
|
||||||
|
Z
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
# Carré simple
|
||||||
|
path_data = f"""
|
||||||
|
M {x - half},{y - half}
|
||||||
|
L {x + half},{y - half}
|
||||||
|
L {x + half},{y + half}
|
||||||
|
L {x - half},{y + half}
|
||||||
|
Z
|
||||||
|
"""
|
||||||
|
|
||||||
|
elif shape == "round":
|
||||||
|
# Cercle
|
||||||
|
path_data = f"""
|
||||||
|
M {x - half},{y}
|
||||||
|
A {half},{half} 0 1,0 {x + half},{y}
|
||||||
|
A {half},{half} 0 1,0 {x - half},{y}
|
||||||
|
Z
|
||||||
|
"""
|
||||||
|
|
||||||
|
elif shape == "diamond":
|
||||||
|
# Losange
|
||||||
|
path_data = f"""
|
||||||
|
M {x},{y - half}
|
||||||
|
L {x + half},{y}
|
||||||
|
L {x},{y + half}
|
||||||
|
L {x - half},{y}
|
||||||
|
Z
|
||||||
|
"""
|
||||||
|
|
||||||
|
return parse_path(path_data.replace("\n", " ").strip())
|
||||||
|
|
||||||
|
|
||||||
|
def pixelize_glyph(svg_path):
|
||||||
|
# Pixelise un glyphe en analysant sa forme et en plaçant des pixels
|
||||||
|
try:
|
||||||
|
# Lire le SVG original
|
||||||
|
paths, attributes = svg2paths(svg_path)
|
||||||
|
|
||||||
|
if len(paths) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Calculer la bounding box
|
||||||
|
xmin, xmax, ymin, ymax = paths[0].bbox()
|
||||||
|
for path in paths[1:]:
|
||||||
|
bbox = path.bbox()
|
||||||
|
xmin = min(xmin, bbox[0])
|
||||||
|
xmax = max(xmax, bbox[1])
|
||||||
|
ymin = min(ymin, bbox[2])
|
||||||
|
ymax = max(ymax, bbox[3])
|
||||||
|
|
||||||
|
width = xmax - xmin
|
||||||
|
height = ymax - ymin
|
||||||
|
|
||||||
|
if width == 0 or height == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Appliquer les échelles
|
||||||
|
scaled_height = height * x_height_scale
|
||||||
|
scaled_width = width * width_scale
|
||||||
|
|
||||||
|
# Décommenter ici pour avoir des tailles de pixels aléatoires
|
||||||
|
pixel_size = randrange(30, 60, 1)
|
||||||
|
|
||||||
|
# Calculer la grille de pixels
|
||||||
|
cols = max(1, int(scaled_width / pixel_size))
|
||||||
|
rows = max(1, int(scaled_height / pixel_size))
|
||||||
|
|
||||||
|
# Ajuster les positions pour centrer
|
||||||
|
start_x = xmin + (width - cols * pixel_size) / 2
|
||||||
|
start_y = ymin + (height - rows * pixel_size) / 2
|
||||||
|
|
||||||
|
pixel_paths = []
|
||||||
|
|
||||||
|
# Pour chaque position de pixel potentielle
|
||||||
|
for row in range(rows):
|
||||||
|
for col in range(cols):
|
||||||
|
# Centre du pixel
|
||||||
|
px = start_x + col * pixel_size + pixel_size / 2
|
||||||
|
py = start_y + row * pixel_size + pixel_size / 2
|
||||||
|
|
||||||
|
# Transformer le point selon les échelles
|
||||||
|
test_x = xmin + (px - xmin) / width_scale
|
||||||
|
test_y = ymin + (py - ymin) / x_height_scale
|
||||||
|
|
||||||
|
# Tester si ce point est à l'intérieur des contours originaux
|
||||||
|
point = complex(test_x, test_y)
|
||||||
|
is_inside = False
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
try:
|
||||||
|
# Utiliser la méthode de raycasting
|
||||||
|
# Compter combien de fois un rayon horizontal croise le contour
|
||||||
|
crossings = 0
|
||||||
|
for seg in path:
|
||||||
|
if hasattr(seg, 'start') and hasattr(seg, 'end'):
|
||||||
|
y1 = seg.start.imag
|
||||||
|
y2 = seg.end.imag
|
||||||
|
x1 = seg.start.real
|
||||||
|
x2 = seg.end.real
|
||||||
|
|
||||||
|
if (y1 <= test_y < y2) or (y2 <= test_y < y1):
|
||||||
|
if x1 + (test_y - y1) / (y2 - y1) * (x2 - x1) > test_x:
|
||||||
|
crossings += 1
|
||||||
|
|
||||||
|
if crossings % 2 == 1:
|
||||||
|
is_inside = True
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Si le point est à l'intérieur, créer un pixel
|
||||||
|
if is_inside:
|
||||||
|
pixel_path = create_pixel_path(
|
||||||
|
px, py, pixel_size,
|
||||||
|
shape=pixel_shape,
|
||||||
|
gap=pixel_gap,
|
||||||
|
round_radius=round_corners
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exemple de configuration avec de l'aléatoire
|
||||||
|
'''pixel_path = create_pixel_path(
|
||||||
|
px, py, randrange(30, 60, 1),
|
||||||
|
shape=pixel_shape,
|
||||||
|
gap=randrange(0, 10, 1),
|
||||||
|
round_radius=randrange(0, 10, 1)
|
||||||
|
)'''
|
||||||
|
|
||||||
|
pixel_paths.append(pixel_path)
|
||||||
|
|
||||||
|
if len(pixel_paths) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Créer le SVG de sortie
|
||||||
|
attr = {
|
||||||
|
"fill": "black",
|
||||||
|
"stroke": "none"
|
||||||
|
}
|
||||||
|
attrs = [attr] * len(pixel_paths)
|
||||||
|
|
||||||
|
# Calculer les nouvelles dimensions
|
||||||
|
output_width = cols * pixel_size
|
||||||
|
output_height = rows * pixel_size
|
||||||
|
|
||||||
|
wsvg(
|
||||||
|
pixel_paths,
|
||||||
|
attributes=attrs,
|
||||||
|
svg_attributes={
|
||||||
|
"width": output_width,
|
||||||
|
"height": output_height,
|
||||||
|
"viewBox": f"{start_x} {start_y} {output_width} {output_height}"
|
||||||
|
},
|
||||||
|
filename=svg_path.replace("svg/", "svg2/"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur pixelisation: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def makeFont(family_name, weight, style):
|
||||||
|
# Génère la fonte finale à partir des SVG pixelisés
|
||||||
|
svgDir = glob.glob("svg2/*.svg")
|
||||||
|
|
||||||
|
print("\nCréation d'une nouvelle fonte vide…")
|
||||||
|
newfont = fontforge.font()
|
||||||
|
|
||||||
|
newfont.encoding = "UnicodeFull"
|
||||||
|
newfont.ascent = int(font_ascent * x_height_scale)
|
||||||
|
newfont.descent = int(font_descent * x_height_scale)
|
||||||
|
newfont.em = newfont.ascent + newfont.descent
|
||||||
|
|
||||||
|
# Configuration dy nom de la font et de ses paramètres
|
||||||
|
newfont.familyname = family_name
|
||||||
|
newfont.fontname = family_name.replace(" ", "") + "-" + weight
|
||||||
|
newfont.fullname = family_name + " " + weight
|
||||||
|
newfont.weight = weight
|
||||||
|
|
||||||
|
newfont.appendSFNTName("English (US)", "Family", family_name)
|
||||||
|
newfont.appendSFNTName("English (US)", "SubFamily", weight)
|
||||||
|
newfont.appendSFNTName("English (US)", "Fullname", family_name + " " + weight)
|
||||||
|
newfont.appendSFNTName("English (US)", "PostScriptName", family_name.replace(" ", "") + "-" + weight)
|
||||||
|
|
||||||
|
if style == "italic":
|
||||||
|
newfont.italicangle = -12
|
||||||
|
newfont.fontname += "Italic"
|
||||||
|
newfont.fullname += " Italic"
|
||||||
|
elif style == "oblique":
|
||||||
|
newfont.italicangle = -12
|
||||||
|
newfont.fontname += "Oblique"
|
||||||
|
newfont.fullname += " Oblique"
|
||||||
|
|
||||||
|
weight_map = {
|
||||||
|
"Thin": 100, "ExtraLight": 200, "Light": 300, "Regular": 400,
|
||||||
|
"Medium": 500, "SemiBold": 600, "Bold": 700, "ExtraBold": 800, "Black": 900
|
||||||
|
}
|
||||||
|
newfont.os2_weight = weight_map.get(weight, 400)
|
||||||
|
|
||||||
|
# Calculer la largeur monospace si nécessaire
|
||||||
|
calculated_monospace_width = monospace_width
|
||||||
|
if monospace and calculated_monospace_width is None:
|
||||||
|
# Calculer la largeur moyenne des glyphes
|
||||||
|
widths = [info['width'] for info in glyph_info.values()]
|
||||||
|
calculated_monospace_width = int(sum(widths) / len(widths) * width_scale) if widths else 500
|
||||||
|
print(f"Largeur monospace calculée: {calculated_monospace_width}")
|
||||||
|
|
||||||
|
# Import des glyphes depuis SVG
|
||||||
|
print(f"\nImport des glyphes pixelisés...")
|
||||||
|
imported_count = 0
|
||||||
|
|
||||||
|
for glyph_path in svgDir:
|
||||||
|
try:
|
||||||
|
glyph_name = glyph_path.split("/")[-1].replace(".svg", "")
|
||||||
|
|
||||||
|
if glyph_name not in glyph_info:
|
||||||
|
continue
|
||||||
|
|
||||||
|
info = glyph_info[glyph_name]
|
||||||
|
|
||||||
|
# Créer le glyphe
|
||||||
|
if info['unicode'] != -1:
|
||||||
|
char = newfont.createChar(info['unicode'], glyph_name)
|
||||||
|
else:
|
||||||
|
char = newfont.createChar(-1, glyph_name)
|
||||||
|
|
||||||
|
# Appliquer la largeur
|
||||||
|
if monospace:
|
||||||
|
char.width = calculated_monospace_width
|
||||||
|
else:
|
||||||
|
char.width = int(info['width'] * width_scale)
|
||||||
|
|
||||||
|
char.importOutlines(glyph_path, scale=False)
|
||||||
|
|
||||||
|
# Centrer le glyphe si monospace
|
||||||
|
if monospace:
|
||||||
|
try:
|
||||||
|
char.left_side_bearing = int((calculated_monospace_width - char.width) / 2)
|
||||||
|
char.width = calculated_monospace_width
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
imported_count += 1
|
||||||
|
|
||||||
|
if imported_count % 50 == 0:
|
||||||
|
print(f" ... {imported_count} glyphes importés")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Erreur avec {glyph_name}: {e}")
|
||||||
|
|
||||||
|
print(f"✓ {imported_count} glyphes pixelisés importés")
|
||||||
|
|
||||||
|
# Copie des glyphes spéciaux
|
||||||
|
print("\nCopie des glyphes spéciaux...")
|
||||||
|
special_count = 0
|
||||||
|
|
||||||
|
for glyph_name, info in glyph_info.items():
|
||||||
|
unicode_val = info['unicode']
|
||||||
|
|
||||||
|
if unicode_val != -1 and unicode_val not in newfont:
|
||||||
|
try:
|
||||||
|
special_glyph = newfont.createChar(unicode_val, glyph_name)
|
||||||
|
if monospace:
|
||||||
|
special_glyph.width = calculated_monospace_width
|
||||||
|
else:
|
||||||
|
special_glyph.width = int(info['width'] * width_scale)
|
||||||
|
special_count += 1
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"✓ {special_count} glyphes spéciaux copiés")
|
||||||
|
|
||||||
|
# Reconstruction des composites
|
||||||
|
print("\nReconstruction des glyphes composites...")
|
||||||
|
composite_success = 0
|
||||||
|
|
||||||
|
for glyph_name, info in composite_glyphs.items():
|
||||||
|
try:
|
||||||
|
unicode_val = info['unicode']
|
||||||
|
|
||||||
|
if unicode_val == -1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
composite_glyph = newfont.createChar(unicode_val, glyph_name)
|
||||||
|
if monospace:
|
||||||
|
composite_glyph.width = calculated_monospace_width
|
||||||
|
else:
|
||||||
|
composite_glyph.width = int(info['width'] * width_scale)
|
||||||
|
|
||||||
|
all_refs_exist = True
|
||||||
|
for ref in info['references']:
|
||||||
|
if ref['glyph'] not in newfont:
|
||||||
|
all_refs_exist = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if not all_refs_exist:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for ref in info['references']:
|
||||||
|
# Adapter la transformation avec les échelles
|
||||||
|
transform = list(ref['transform'])
|
||||||
|
transform[0] *= width_scale # xx
|
||||||
|
transform[3] *= x_height_scale # yy
|
||||||
|
transform[4] *= width_scale # dx
|
||||||
|
transform[5] *= x_height_scale # dy
|
||||||
|
|
||||||
|
composite_glyph.addReference(ref['glyph'], tuple(transform))
|
||||||
|
|
||||||
|
composite_glyph.unlinkRef()
|
||||||
|
composite_success += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Erreur composite {glyph_name}: {e}")
|
||||||
|
|
||||||
|
print(f"✓ {composite_success} glyphes composites créés")
|
||||||
|
|
||||||
|
# Export
|
||||||
|
output_name = family_name.replace(" ", "") + "-" + weight
|
||||||
|
if style != "normal":
|
||||||
|
output_name += style.capitalize()
|
||||||
|
|
||||||
|
print(f"\nExport de la fonte...")
|
||||||
|
for fmt in export_formats:
|
||||||
|
try:
|
||||||
|
output_file = output_name + "." + fmt
|
||||||
|
if fmt == "otf":
|
||||||
|
newfont.generate(output_file, flags=("opentype",))
|
||||||
|
else:
|
||||||
|
newfont.generate(output_file)
|
||||||
|
print(f" ✓ Exporté: {output_file}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ Erreur export {fmt}: {e}")
|
||||||
|
|
||||||
|
newfont.close()
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# TRAITEMENT PRINCIPAL
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("PIXELISATION DES GLYPHES")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
processed = 0
|
||||||
|
failed = 0
|
||||||
|
empty = 0
|
||||||
|
|
||||||
|
for lettre in glob.glob("svg/*.svg"):
|
||||||
|
try:
|
||||||
|
result = pixelize_glyph(lettre)
|
||||||
|
if result:
|
||||||
|
processed += 1
|
||||||
|
if processed % 10 == 0:
|
||||||
|
print(f" ... {processed} glyphes traités")
|
||||||
|
else:
|
||||||
|
empty += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f" Erreur: {e}")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
print(f"\n✓ Traités: {processed}")
|
||||||
|
print(f"⚠ Vides: {empty}")
|
||||||
|
print(f"✗ Échecs: {failed}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("GÉNÉRATION DE LA FONTE")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
makeFont(new_family_name, font_weight, font_style)
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("TERMINÉ!")
|
||||||
|
print("=" * 70)
|
||||||
|
print(f"\nRésumé:")
|
||||||
|
print(f" - Famille: {new_family_name}")
|
||||||
|
print(f" - Poids: {font_weight}")
|
||||||
|
print(f" - Taille pixel: {pixel_size}")
|
||||||
|
print(f" - Échelle hauteur: {x_height_scale}")
|
||||||
|
print(f" - Échelle largeur: {width_scale}")
|
||||||
|
print(f" - Monospace: {monospace}")
|
||||||
|
print(f" - Formats: {', '.join(export_formats)}")
|
||||||
|
|
||||||
|
# Nettoyage
|
||||||
|
print("\nNettoyage…")
|
||||||
|
try:
|
||||||
|
shutil.rmtree("svg")
|
||||||
|
shutil.rmtree("svg2")
|
||||||
|
print("✓ Nettoyage terminé")
|
||||||
|
except:
|
||||||
|
print("⚠ Nettoyage impossible")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue