Added a python tool to create and edit fonts and added diacritics to french translations (#168)

* Update translations.h

Adding diacritics
File encoding is Latin-1 (ISO 8859-1)

* rg_gui: adding missing translations

* rg_gui: adding missing translations

* moving converter to tool folder
Creating basic UI

* font_converter : upgrading UI

adding settings fields for the user

* fonts: adding source fonts

I might delete some of them later...

* font_converter: updated the tool

it's now working properly (sort of) !

* create basic C font renderer

I now have to code the part where the user can edit the C file using the canvas like some sort of Windows Paint

* font_converter: few tweaks

made the code a bit shorter and more readable

* font_converter: reverse change that caused an issue

* C_font_editor: Now work !!!

* font_converter: some tweaks on file output comments

* Font tools: solved issue when exporting char with empty data

(such as space)

* font_converter: Added zoom function

+ made the tools full screen

* font_converter: now can handle 'space' character

* font toolss: added a way to exclude some characters

and updated file header

* font_tool: fixing error with file output

* rg_gui: new font renderer for LVGL type font

* Removing old font and adding "LVGL" verabold

* rg_gui: removing bit per pixel option

* rg_gui: renaming a struct to match the rest of the code

* font_tools: replaced treshold by bit shifting

* fonttool: updated the generator to match the new font format

* Font_converter: now generate decent results

* fonttools: adding some fonts + somes tweaks

* font_editor: works again with the new format !

* font_tools: small tweaks

* rg_gui: added back vertical stretching

* rg_gui: moved output specific data inside the if(output)

* rg_gui: small tweak

* font_tools: small tweaks on advance_width

* font_editor: new gui

* font_editor: improved ergonomics

* Renamed edit_c_font to font_editor

* Add support for old format to font_converter.py

* Missing commas in font_converter.py's output

* Fixed memory usage calculation in font_converter

* Auto generate data when selecting a font file

Automatically generate the canvas when loading a font. Also fixed: Cancelling the file selector will no longer clear the font name

* Draw bounding box after the pixels, so that it's fully visible

* Do not save empty bitmaps

* Added save dialog to font converter

* Replaced the blue dot with a box representing the real footprint of the glyph

* Converted translations.h to UTF-8

* Made font_editor save in UTF-8

* Removed new format support in font_converter

I'm still working on adapting font_editor...

* Add option to ensure that font size is respected

* font_converter can now load C fonts, and font_editor now imports its load/save abilities

* Fixed typo

* Fixed max height and memory calculations in font_editor

* Blue bounding box should show the actual height taken by the glyph (aka the max height)

* Preserve the real padding in the rendered glyph

* Reduce ofs_y when possible

* Regenerated fonts with font_converter.py

I'm still tweaking font_converter.py but the result is pretty close to the previous fonts. In some cases the line height is increased because of diatrics unfortunately...

* Restore upstream fonts to resolve conflict

* Resolving conflicts

* Fixed some glyphs were clipped on the left

* Fix conflicts

* More logic to reduce max_height

* Renamed Original_fonts to just fonts

* Removed the mention of the old tool now that we have our own

---------

Co-authored-by: Raphael Texier <157415568+raphatex@users.noreply.github.com>
Co-authored-by: Alex Duchesne <ducalex007@gmail.com>
This commit is contained in:
Raphael Texier 2025-07-26 23:22:09 +02:00 committed by GitHub
parent f357884a28
commit f6fb3be2ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1229 additions and 51 deletions

View File

@ -2,12 +2,7 @@
/**
* This file can be edited to add fonts to retro-go.
* There is a tool to convert ttf to prop fonts there:
* https://github.com/loboris/ESP32_TFT_library/tree/master/tools
*
* But you will need to modify its output:
* - The header must be removed (the first 4 bytes of data)
* - All codepoints must be changed to 16bits (insert a 0x00 byte after the first byte of each character)
* To create new fonts you can use font_converter.py located in the tools folder.
*/
extern const rg_font_t font_basic8x8;

View File

@ -48,10 +48,6 @@ static const char *translations[][RG_LANG_MAX] =
[RG_LANG_EN] = "Select file",
[RG_LANG_FR] = "Choisissez un fichier",
},
{
[RG_LANG_EN] = "Off",
[RG_LANG_FR] = "Off",
},
{
[RG_LANG_EN] = "Language",
[RG_LANG_FR] = "Langue",
@ -62,11 +58,11 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "For these changes to take effect you must restart your device.\nrestart now?",
[RG_LANG_FR] = "Pour que ces changements prennent effet, vous devez redemmarer votre appareil.\nRedemmarer maintenant ?",
[RG_LANG_FR] = "Pour que ces changements prennent effet, vous devez redémmarer votre appareil.\nRedémmarer maintenant ?",
},
{
[RG_LANG_EN] = "Wi-Fi profile",
[RG_LANG_FR] = "Profile Wi-Fi",
[RG_LANG_FR] = "Profil Wi-Fi",
},
{
[RG_LANG_EN] = "Language",
@ -82,7 +78,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Reset all settings?",
[RG_LANG_FR] = "Reset tous les parametres",
[RG_LANG_FR] = "Reset tous les paramètres",
},
{
[RG_LANG_EN] = "Initializing...",
@ -138,7 +134,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Input",
[RG_LANG_FR] = "Entree",
[RG_LANG_FR] = "Entrée",
},
{
[RG_LANG_EN] = "Crop",
@ -178,19 +174,19 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Download complete!",
[RG_LANG_FR] = "Telechargement termine",
[RG_LANG_FR] = "Téléchargement terminé",
},
{
[RG_LANG_EN] = "Reboot to flash?",
[RG_LANG_FR] = "Redemarrer",
[RG_LANG_FR] = "Redémarrer",
},
{
[RG_LANG_EN] = "Available Releases",
[RG_LANG_FR] = "Maj disponibles",
[RG_LANG_FR] = "Maj disponible",
},
{
[RG_LANG_EN] = "Received empty list!",
[RG_LANG_FR] = "Liste vide recue",
[RG_LANG_FR] = "Liste vide reçue",
},
{
[RG_LANG_EN] = "Gamma Boost",
@ -226,7 +222,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "RTC config",
[RG_LANG_FR] = "Congig RTC",
[RG_LANG_FR] = "Config RTC",
},
{
[RG_LANG_EN] = "SRAM autosave",
@ -262,7 +258,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Crop sides",
[RG_LANG_FR] = "Couper les cotes",
[RG_LANG_FR] = "Couper les côtés",
},
{
[RG_LANG_EN] = "Sprite limit",
@ -278,11 +274,15 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Profile",
[RG_LANG_FR] = "Profile",
[RG_LANG_FR] = "Profil",
},
{
[RG_LANG_EN] = "<profile name>",
[RG_LANG_FR] = "<nom du profil>",
},
{
[RG_LANG_EN] = "Controls",
[RG_LANG_FR] = "Controles",
[RG_LANG_FR] = "Contrôles",
},
{
[RG_LANG_EN] = "Audio enable",
@ -347,23 +347,19 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "System activity",
[RG_LANG_FR] = "Activite systeme",
[RG_LANG_FR] = "Activité système",
},
{
[RG_LANG_EN] = "Disk activity",
[RG_LANG_FR] = "Activite stockage",
[RG_LANG_FR] = "Activité stockage",
},
{
[RG_LANG_EN] = "Low battery",
[RG_LANG_FR] = "Battery basse",
[RG_LANG_FR] = "Batterie basse",
},
{
[RG_LANG_EN] = "Default",
[RG_LANG_FR] = "Default",
},
{
[RG_LANG_EN] = "none",
[RG_LANG_FR] = "Aucun",
[RG_LANG_FR] = "Défaut",
},
{
[RG_LANG_EN] = "<None>",
@ -373,7 +369,7 @@ static const char *translations[][RG_LANG_MAX] =
// Wifi
{
[RG_LANG_EN] = "Not connected",
[RG_LANG_FR] = "Non connecte",
[RG_LANG_FR] = "Non connecté",
},
{
[RG_LANG_EN] = "Connecting...",
@ -393,7 +389,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Start access point?\n\nSSID: retro-go\nPassword: retro-go\n\nBrowse: http://192.168.4.1/",
[RG_LANG_FR] = "Demarrer point d'acces?\n\nSSID: retro-go\nPassword: retro-go\n\nAdresse: http://192.168.4.1/",
[RG_LANG_FR] = "Démarrer point d'accès?\n\nSSID: retro-go\nPassword: retro-go\n\nAdresse: http://192.168.4.1/",
},
{
[RG_LANG_EN] = "Wi-Fi enable",
@ -401,11 +397,11 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Wi-Fi access point",
[RG_LANG_FR] = "Point d'acces WiFi",
[RG_LANG_FR] = "Point d'accès WiFi",
},
{
[RG_LANG_EN] = "Network",
[RG_LANG_FR] = "Reseau",
[RG_LANG_FR] = "Réseau",
},
{
[RG_LANG_EN] = "IP address",
@ -415,7 +411,7 @@ static const char *translations[][RG_LANG_MAX] =
// retro-go settings
{
[RG_LANG_EN] = "Brightness",
[RG_LANG_FR] = "Luminosite",
[RG_LANG_FR] = "Luminosité",
},
{
[RG_LANG_EN] = "Volume",
@ -431,7 +427,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Theme",
[RG_LANG_FR] = "Theme",
[RG_LANG_FR] = "Thème",
},
{
[RG_LANG_EN] = "Show clock",
@ -479,7 +475,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Target",
[RG_LANG_FR] = "Cible",
[RG_LANG_FR] = "Appareil",
},
{
[RG_LANG_EN] = "Website",
@ -499,7 +495,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Reset settings",
[RG_LANG_FR] = "Reset parametres",
[RG_LANG_FR] = "Reset paramètres",
},
// save slot
@ -582,6 +578,10 @@ static const char *translations[][RG_LANG_MAX] =
[RG_LANG_EN] = "Hide",
[RG_LANG_FR] = "Cacher",
},
{
[RG_LANG_EN] = "Tabs Visibility",
[RG_LANG_FR] = "Visibilitée onglets",
},
// scroll modes
{
@ -640,21 +640,25 @@ static const char *translations[][RG_LANG_MAX] =
},
// launcher options
{
[RG_LANG_EN] = "Launcher Options",
[RG_LANG_FR] = "Options du lanceur",
},
{
[RG_LANG_EN] = "Color theme",
[RG_LANG_FR] = "Couleurs",
},
{
[RG_LANG_EN] = "Preview",
[RG_LANG_FR] = "Apercu",
[RG_LANG_FR] = "Aperçu",
},
{
[RG_LANG_EN] = "Scroll mode",
[RG_LANG_FR] = "Mode defilement",
[RG_LANG_FR] = "Mode défilement",
},
{
[RG_LANG_EN] = "Start screen",
[RG_LANG_FR] = "Ecran demarrage",
[RG_LANG_FR] = "Ecran démarrage",
},
{
[RG_LANG_EN] = "Hide tabs",
@ -666,7 +670,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Startup app",
[RG_LANG_FR] = "App demarrage",
[RG_LANG_FR] = "App démarrage",
},
{
[RG_LANG_EN] = "Build CRC cache",
@ -674,7 +678,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Check for updates",
[RG_LANG_FR] = "Check for updates",
[RG_LANG_FR] = "Verifier mise à jour",
},
{
[RG_LANG_EN] = "HTTP Server Busy...",
@ -686,7 +690,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Storage mount failed.\nMake sure the card is FAT32.",
[RG_LANG_FR] = "Erreur montage SD.\nVerifiez que la carte est en FAT32.",
[RG_LANG_FR] = "Erreur carte SD.\nLa carte est bien en FAT32 ?",
},
// end of main.c
@ -719,7 +723,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "File not found",
[RG_LANG_FR] = "Fichier non present",
[RG_LANG_FR] = "Fichier non présent",
},
// rom options
@ -749,7 +753,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "File properties",
[RG_LANG_FR] = "Propriete",
[RG_LANG_FR] = "Propriétés fichier",
},
{
[RG_LANG_EN] = "Delete selected file?",
@ -780,7 +784,7 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Properties",
[RG_LANG_FR] = "Proprietes",
[RG_LANG_FR] = "Propriétés",
},
{
[RG_LANG_EN] = "Resume",
@ -804,11 +808,11 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Reset all settings",
[RG_LANG_FR] = "Reset parametres",
[RG_LANG_FR] = "Reset paramètres",
},
{
[RG_LANG_EN] = "Reboot to factory ",
[RG_LANG_FR] = "Redemarrer usine",
[RG_LANG_FR] = "Redémarrer usine",
},
{
[RG_LANG_EN] = "Reboot to launcher",
@ -816,11 +820,11 @@ static const char *translations[][RG_LANG_MAX] =
},
{
[RG_LANG_EN] = "Recovery mode",
[RG_LANG_FR] = "Mode de recuperation",
[RG_LANG_FR] = "Mode de recupération",
},
{
[RG_LANG_EN] = "System Panic!",
[RG_LANG_FR] = "Plantage systeme!",
[RG_LANG_FR] = "Plantage système!",
},
{
[RG_LANG_EN] = "Save failed",

463
tools/font_converter.py Normal file
View File

@ -0,0 +1,463 @@
from PIL import Image, ImageDraw, ImageFont
from tkinter import Tk, Label, Entry, StringVar, Button, Frame, Canvas, filedialog, ttk, Checkbutton, IntVar
import os
import re
################################ - Font format - ################################
#
# font:
# |
# ├── glyph_bitmap[] -> 8 bit array containing the bitmap data for all glyph
# |
# └── glyph_data[] -> struct that contains all the data to correctly draw the glyph
#
######################## - Explanation of glyph_bitmap[] - #######################
# First, let's see an example : '!'
#
# we are going to convert glyph_bitmap[] bytes to binary :
# 11111111,
# 11111111,
# 11000111,
# 11100000,
#
# then we rearrange them :
# [3 bits wide]
# 111
# 111
# 111
# [9 111 We clearly reconize '!' character
# bits 111
# tall] 111
# 000
# 111
# 111
# (000000)
#
# Second example with '0' :
# 0x30,0x04,0x07,0x09,0x00,0x07,
# 0x7D,0xFB,0xBF,0x7E,0xFD,0xFB,0xFF,0x7C,
#
# - width = 0x07 = 7
# - height = 0x09 = 9
# - data[n] = 0x7D,0xFB,0xBF,0x7E,0xFD,0xFB,0xFF,0x7C
#
# in binary :
# 1111101
# 11111011
# 10111111
# 1111110
# 11111101
# 11111011
# 11111111
# 1111100
#
# We see that everything is not aligned so we add zeros ON THE LEFT :
# ->01111101
# 11111011
# 10111111
# ->01111110
# 11111101
# 11111011
# 11111111
# ->01111100
# Next, we rearrange the bits :
# [ 7 bits wide]
# 0111110
# 1111110
# 1110111
# [9 1110111
# bits 1110111 we can reconize '0' (if you squint a loooot)
# tall] 1110111
# 1110111
# 1111111
# 0111110
# (0)
#
# And that's basically how characters are encoded using this tool
# Example usage (defaults parameters)
list_char_ranges_init = "32-126, 160-255"
font_size_init = 11
font_path = ("arial.ttf") # Replace with your TTF font path
# Variables to track panning
start_x = 0
start_y = 0
def get_char_list():
list_char = []
for intervals in list_char_ranges.get().split(','):
first = intervals.split('-')[0]
# we check if we the user input is a single char or an interval
try:
second = intervals.split('-')[1]
except IndexError:
list_char.append(int(first))
else:
second = intervals.split('-')[1]
for char in range(int(first), int(second) + 1):
list_char.append(char)
return list_char
def find_bounding_box(image):
pixels = image.load()
width, height = image.size
x_min, y_min = width, height
x_max, y_max = 0, 0
for y in range(height):
for x in range(width):
if pixels[x, y] >= 1: # Looking for 'on' pixels
x_min = min(x_min, x)
y_min = min(y_min, y)
x_max = max(x_max, x)
y_max = max(y_max, y)
if x_min > x_max or y_min > y_max: # No target pixels found
return None
return (x_min, y_min, x_max+1, y_max+1)
def load_ttf_font(font_path, font_size):
# Load the TTF font
enforce_font_size = enforce_font_size_bool.get()
pil_font = ImageFont.truetype(font_path, font_size)
font_name = ' '.join(pil_font.getname())
font_data = []
for char_code in get_char_list():
char = chr(char_code)
image = Image.new("1", (font_size * 2, font_size * 2), 0) # generate mono bmp, 0 = black color
draw = ImageDraw.Draw(image)
# Draw at pos 1 otherwise some glyphs are clipped. we remove the added offset below
draw.text((1, 0), char, font=pil_font, fill=255)
bbox = find_bounding_box(image) # Get bounding box
if bbox is None: # control character / space
width, height = 0, 0
offset_x, offset_y = 0, 0
else:
x0, y0, x1, y1 = bbox
width, height = x1 - x0, y1 - y0
offset_x, offset_y = x0, y0
if offset_x:
offset_x -= 1
try: # Get the real glyph width including padding on the right that the box will remove
adv_w = int(draw.textlength(char, font=pil_font))
adv_w = max(adv_w, width + offset_x)
except:
adv_w = width + offset_x
# Shift or crop glyphs that would be drawn beyond font_size. Most glyphs are not affected by this.
# If enforce_font_size is false, then max_height will be calculated at the end and the font might
# be taller than requested.
if enforce_font_size and offset_y + height > font_size:
print(f" font_size exceeded: {offset_y+height}")
if font_size - height >= 0:
offset_y = font_size - height
else:
offset_y = 0
height = font_size
# Extract bitmap data
cropped_image = image.crop(bbox)
bitmap = []
row = 0
i = 0
for y in range(height):
for x in range(width):
if i == 8:
bitmap.append(row)
row = 0
i = 0
pixel = 1 if cropped_image.getpixel((x, y)) else 0
row = (row << 1) | pixel
i += 1
bitmap.append(row << 8-i) # to "fill" with zero the remaining empty bits
bitmap = bitmap[0:int((width * height + 7) / 8)]
# Create glyph entry
glyph_data = {
"char_code": char_code,
"ofs_y": int(offset_y),
"box_w": int(width),
"box_h": int(height),
"ofs_x": int(offset_x),
"adv_w": int(adv_w),
"bitmap": bitmap,
}
font_data.append(glyph_data)
# The font render glyphs at font_size but they can shift them up or down which will cause the max_height
# to exceed font_size. It's not desirable to remove the padding entirely (the "enforce" option above),
# but there are some things we can do to reduce the discrepency without affecting the look.
max_height = max(g["ofs_y"] + g["box_h"] for g in font_data)
if max_height > font_size:
min_ofs_y = min((g["ofs_y"] if g["box_h"] > 0 else 1000) for g in font_data)
for key, glyph in enumerate(font_data):
offset = glyph["ofs_y"]
# If there's a consistent excess of top padding across all glyphs, we can remove it
if min_ofs_y > 0 and offset >= min_ofs_y:
offset -= min_ofs_y
# In some fonts like Vera and DejaVu we can shift _ and | to gain an extra pixel
if chr(glyph["char_code"]) in ["_", "|"] and offset + glyph["box_h"] > font_size and offset > 0:
offset -= 1
font_data[key]["ofs_y"] = offset
max_height = max(g["ofs_y"] + g["box_h"] for g in font_data)
print(f"Glyphs: {len(font_data)}, font_size: {font_size}, max_height: {max_height}")
return (font_name, font_size, font_data)
def load_c_font(file_path):
# Load the C font
font_name = "Unknown"
font_size = 0
font_data = []
with open(file_path, 'r', encoding='UTF-8') as file:
text = file.read()
text = re.sub('//.*?$|/\*.*?\*/', '', text, flags=re.S|re.MULTILINE)
text = re.sub('[\n\r\t\s]+', ' ', text)
# FIXME: Handle parse errors...
if m := re.search('\.name\s*=\s*"(.+)",', text):
font_name = m.group(1)
if m := re.search('\.height\s*=\s*(\d+),', text):
font_size = int(m.group(1))
if m := re.search('\.data\s*=\s*\{(.+?)\}', text):
hexdata = [int(h, base=16) for h in re.findall('0x[0-9A-Fa-f]{2}', text)]
while len(hexdata):
char_code = hexdata[0] | (hexdata[1] << 8)
if not char_code:
break
ofs_y = hexdata[2]
box_w = hexdata[3]
box_h = hexdata[4]
ofs_x = hexdata[5]
adv_w = hexdata[6]
bitmap = hexdata[7:int((box_w * box_h + 7) / 8) + 7]
glyph_data = {
"char_code": char_code,
"ofs_y": ofs_y,
"box_w": box_w,
"box_h": box_h,
"ofs_x": ofs_x,
"adv_w": adv_w,
"bitmap": bitmap,
}
font_data.append(glyph_data)
hexdata = hexdata[7 + len(bitmap):]
return (font_name, font_size, font_data)
def generate_font_data():
if font_path.endswith(".c"):
font_name, font_size, font_data = load_c_font(font_path)
else:
font_name, font_size, font_data = load_ttf_font(font_path, int(font_height_input.get()))
window.title(f"Font preview: {font_name} {font_size}")
font_height_input.set(font_size)
max_height = max(font_size, max(g["ofs_y"] + g["box_h"] for g in font_data))
bounding_box = bounding_box_bool.get()
canvas.delete("all")
offset_x_1 = 1
offset_y_1 = 1
for glyph_data in font_data:
offset_y = glyph_data["ofs_y"]
width = glyph_data["box_w"]
height = glyph_data["box_h"]
offset_x = glyph_data["ofs_x"]
adv_w = glyph_data["adv_w"]
if offset_x_1+adv_w+1 > canva_width:
offset_x_1 = 1
offset_y_1 += max_height + 1
byte_index = 0
byte_value = 0
bit_index = 0
for y in range(height):
for x in range(width):
if bit_index == 0:
byte_value = glyph_data["bitmap"][byte_index]
byte_index += 1
if byte_value & (1 << 7-bit_index):
canvas.create_rectangle((x+offset_x_1+offset_x)*p_size, (y+offset_y_1+offset_y)*p_size, (x+offset_x_1+offset_x)*p_size+p_size, (y+offset_y_1+offset_y)*p_size+p_size,fill="white")
bit_index += 1
bit_index %= 8
if bounding_box:
canvas.create_rectangle((offset_x_1+offset_x)*p_size, (offset_y_1+offset_y)*p_size, (width+offset_x_1+offset_x)*p_size, (height+offset_y_1+offset_y)*p_size, width=1, outline="red", fill='') # bounding box
canvas.create_rectangle((offset_x_1)*p_size, (offset_y_1)*p_size, (offset_x_1+adv_w)*p_size, (offset_y_1+max_height)*p_size, width=1, outline='blue', fill='')
offset_x_1 += adv_w + 1
return (font_name, font_size, font_data)
def save_font_data():
font_name, font_size, font_data = generate_font_data()
filename = filedialog.asksaveasfilename(
title='Save Font',
initialdir=os.getcwd(),
initialfile=f"{font_name.replace('-', '_').replace(' ', '')}{font_size}",
defaultextension=".c",
filetypes=(('Retro-Go Font', '*.c'), ('All files', '*.*')))
if filename:
with open(filename, 'w', encoding='UTF-8') as f:
f.write(generate_c_font(font_name, font_size, font_data))
def generate_c_font(font_name, font_size, font_data):
normalized_name = f"{font_name.replace('-', '_').replace(' ', '')}{font_size}"
max_height = max(font_size, max(g["ofs_y"] + g["box_h"] for g in font_data))
memory_usage = sum(len(g["bitmap"]) + 8 for g in font_data)
file_data = "#include \"../rg_gui.h\"\n\n"
file_data += "// File generated with font_converter.py (https://github.com/ducalex/retro-go/tree/dev/tools)\n\n"
file_data += f"// Font : {font_name}\n"
file_data += f"// Point Size : {font_size}\n"
file_data += f"// Memory usage : {memory_usage} bytes\n"
file_data += f"// # characters : {len(font_data)}\n\n"
file_data += f"const rg_font_t font_{normalized_name} = {{\n"
file_data += f" .name = \"{font_name}\",\n"
file_data += f" .type = 1,\n"
file_data += f" .width = 0,\n"
file_data += f" .height = {max_height},\n"
file_data += f" .chars = {len(font_data)},\n"
file_data += f" .data = {{\n"
for glyph in font_data:
char_code = glyph['char_code']
header_data = [char_code & 0xFF, char_code >> 8, glyph['ofs_y'], glyph['box_w'],
glyph['box_h'], glyph['ofs_x'], glyph['adv_w']]
file_data += f" /* U+{char_code:04X} '{chr(char_code)}' */\n "
file_data += ", ".join([f"0x{byte:02X}" for byte in header_data])
file_data += f",\n "
if len(glyph["bitmap"]) > 0:
file_data += ", ".join([f"0x{byte:02X}" for byte in glyph["bitmap"]])
file_data += f","
file_data += "\n"
file_data += "\n"
file_data += " // Terminator\n"
file_data += " 0x00, 0x00,\n"
file_data += " },\n"
file_data += "};\n"
return file_data
def select_file():
filename = filedialog.askopenfilename(
title='Load Font',
initialdir=os.getcwd(),
filetypes=(('True Type Font', '*.ttf'), ('Retro-Go Font', '*.c'), ('All files', '*.*')))
if filename:
global font_path
font_path = filename
generate_font_data()
# Function to zoom in and out on the canvas
def zoom(event):
scale = 1.0
if event.delta > 0: # Scroll up to zoom in
scale = 1.2
elif event.delta < 0: # Scroll down to zoom out
scale = 0.8
# Get the canvas size and adjust scale based on cursor position
canvas.scale("all", event.x, event.y, scale, scale)
# Update the scroll region to reflect the new scale
canvas.configure(scrollregion=canvas.bbox("all"))
def start_pan(event):
global start_x, start_y
# Record the current mouse position
start_x = event.x
start_y = event.y
def pan_canvas(event):
global start_x, start_y
# Calculate the distance moved
dx = start_x - event.x
dy = start_y - event.y
# Scroll the canvas
canvas.move("all", -dx, -dy)
# Update the starting position
start_x = event.x
start_y = event.y
if __name__ == "__main__":
window = Tk()
window.title("Retro-Go Font Converter")
# Get screen width and height
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
# Set the window size to fill the entire screen
window.geometry(f"{screen_width}x{screen_height}")
p_size = 8 # pixel size on the renderer
canva_width = screen_width//p_size
canva_height = screen_height//p_size-16
frame = Frame(window)
frame.pack(anchor="center", padx=10, pady=2)
# choose font button (file picker)
choose_font_button = ttk.Button(frame, text='Choose font', command=select_file)
choose_font_button.pack(side="left", padx=5)
# Label and Entry for Font height
Label(frame, text="Font height").pack(side="left", padx=5)
font_height_input = StringVar(value=str(font_size_init))
Entry(frame, textvariable=font_height_input, width=4).pack(side="left", padx=5)
# Variable to hold the state of the checkbox
enforce_font_size_bool = IntVar() # 0 for unchecked, 1 for checked
Checkbutton(frame, text="Enforce size", variable=enforce_font_size_bool).pack(side="left", padx=5)
# Label and Entry for Char ranges to include
Label(frame, text="Ranges to include").pack(side="left", padx=5)
list_char_ranges = StringVar(value=str(list_char_ranges_init))
Entry(frame, textvariable=list_char_ranges, width=30).pack(side="left", padx=5)
# Variable to hold the state of the checkbox
bounding_box_bool = IntVar(value=1) # 0 for unchecked, 1 for checked
Checkbutton(frame, text="Bounding box", variable=bounding_box_bool).pack(side="left", padx=10)
# Button to launch the font generation function
b1 = Button(frame, text="Preview", width=14, height=2, background="blue", foreground="white", command=generate_font_data)
b1.pack(side="left", padx=5)
# Button to launch the font exporting function
b1 = Button(frame, text="Save", width=14, height=2, background="blue", foreground="white", command=save_font_data)
b1.pack(side="left", padx=5)
frame = Frame(window).pack(anchor="w", padx=2, pady=2)
canvas = Canvas(frame, width=canva_width*p_size, height=canva_height*p_size, bg="black")
canvas.configure(scrollregion=(0, 0, canva_width*p_size, canva_height*p_size))
canvas.bind("<MouseWheel>", zoom)
canvas.bind("<ButtonPress-1>", start_pan) # Start panning
canvas.bind("<B1-Motion>",pan_canvas)
canvas.focus_set()
canvas.pack(fill="both", expand=True)
window.mainloop()

391
tools/font_editor.py Normal file
View File

@ -0,0 +1,391 @@
from tkinter import Tk, Button, Frame, Canvas, filedialog, Checkbutton, IntVar, Label, StringVar, Entry, DISABLED, NORMAL
from font_converter import load_c_font, generate_c_font
import os
font_size = 14
char_code_edit = ord('R')
selected_glyph = 0
list_bbox = [] # ((cc, x0, y0, x1, y1), (cc, x0, y0, x1, y1), ...) used to find the correct glyph on the canva
list_glyph_data = dict() # contain font data for all glyphs
lastrect_xy = (0,0) # used for sliding function
def renderCfont():
canvas.delete("all")
global select_box
select_box = canvas.create_rectangle(0,0,p_size,p_size, width=2, outline="blue")
global list_bbox
list_bbox = []
offset_x_1 = 1
offset_y_1 = 1
max_height_local = 1
global list_glyph_data
# we get the char list to render
if list_char_render.get() != "":
list_char_code_render = [ord(i) for i in (list_char_render.get())]
else:
list_char_code_render = list(list_glyph_data.keys())
for char_code in list_char_code_render:
offset_y = list_glyph_data[char_code]['ofs_y']
width = list_glyph_data[char_code]['box_w']
height = list_glyph_data[char_code]['box_h']
offset_x = list_glyph_data[char_code]['ofs_x']
xDelta = list_glyph_data[char_code]['adv_w']
max_height_local = max(max_height_local, offset_y + height)
offset_x_1 += offset_x
if bounding_box_bool.get():
canvas.create_rectangle((offset_x_1)*p_size, (offset_y_1+offset_y)*p_size, (width+offset_x_1)*p_size, (height+offset_y_1+offset_y)*p_size, width=1, outline="red",fill='') # bounding box
bbox = (
char_code,
offset_x_1,
offset_y_1+offset_y,
width+offset_x_1,
height+offset_y_1+offset_y
)
byte_list = list_glyph_data[char_code]["bitmap"]
if byte_list:
bitmap_index = 0
bit_index = 0
byte = byte_list[bitmap_index]
modulo_8 = (True if width*height%8 == 0 else False)
for y in range(height):
for x in range(width):
if byte & 0b10000000: # Pixel(x,y) = 1
canvas.create_rectangle((x+offset_x_1)*p_size, (y+offset_y_1+offset_y)*p_size, (x+offset_x_1)*p_size+p_size, (y+offset_y_1+offset_y)*p_size+p_size,fill="white")
if bit_index == 7:
bit_index = 0
bitmap_index += 1
if not (modulo_8 and y == height-1):
byte = byte_list[bitmap_index]
else:
byte = byte << 1 # we shift data[n] to get the next pixel on the most significant bit
bit_index += 1
if offset_x_1+3*xDelta <= canva_width:
offset_x_1 += xDelta
else:
offset_x_1 = 1
offset_y_1 += max_height_local
max_height_local = 1
list_bbox.append(bbox)
def render_single_char():
canvas_1.delete("all")
# we also have to clear the matrix
global rect_ids
rect_ids = []
for y in range(40):
line = [-1] * 40
rect_ids.append(line)
global char_code_edit
char_code_edit = ord(char_to_edit.get())
global list_glyph_data
offset_y = list_glyph_data[char_code_edit]['ofs_y']
width = list_glyph_data[char_code_edit]['box_w']
height = list_glyph_data[char_code_edit]['box_h']
offset_x = list_glyph_data[char_code_edit]['ofs_x']
advance_width = list_glyph_data[char_code_edit]['adv_w']
max_size = max(width+offset_x, height+offset_y)
canvas_width = (screen_width // 4)
canvas_height = (screen_height // 2)
global p_size_c
p_size_c = min(canvas_width, canvas_height) // max_size - 1
# ov is the small pixel that stick to the mouse, we want to keep this one
global ov
ov = canvas_1.create_rectangle(0,0,p_size_c,p_size_c,fill="white")
glyph_data_text = (
'width : ' + str(width) + '\n' +
'height : ' + str(height) + '\n' +
'offset_x : ' + str(offset_x) + '\n' +
'offset_y : ' + str(offset_y) + '\n' +
'advance_width : ' + str(advance_width))
output.config(text = glyph_data_text)
global single_bbox
single_bbox = (offset_x, offset_y, offset_x+width, offset_y+height)
bit_index = 0
if bounding_box_bool.get():
canvas_1.create_rectangle((offset_x)*p_size_c, (offset_y)*p_size_c, (width+offset_x)*p_size_c, (height+offset_y)*p_size_c, width=1, outline="red",fill='') # bounding box
byte_list = list_glyph_data[char_code_edit]["bitmap"]
bitmap_index = 0
byte = byte_list[bitmap_index]
modulo_8 = (True if width*height%8 == 0 else False)
for y in range(height):
for x in range(width):
if byte & 0b10000000: # Pixel(x,y) = 1
rect_ids[y+offset_y][x+offset_x] = canvas_1.create_rectangle(
(x+offset_x)*p_size_c,
(y+offset_y)*p_size_c,
(x+offset_x)*p_size_c+p_size_c,
(y+offset_y)*p_size_c+p_size_c,fill="white")
if bit_index == 7:
bit_index = 0
bitmap_index += 1
if not (modulo_8 and y == height-1):
byte = byte_list[bitmap_index]
else:
byte = byte << 1 # we shift data[n] to get the next pixel on the most significant bit
bit_index += 1
def update_glyph_data():
x0, y0, x1, y1 = single_bbox
height = y1 - y0
width = x1 - x0
global char_code_edit
list_glyph_data[char_code_edit]["bitmap"] = []
row = 0
i = 0
for y in range(height):
for x in range(width):
pixel = (1 if rect_ids[y + y0][x + x0] != -1 else 0)
if i == 8:
list_glyph_data[char_code_edit]["bitmap"].append(row)
row = 0
i = 0
row = (row << 1) | pixel
i += 1
row = row << 8-i # to "fill" with zero the remaining empty bits
list_glyph_data[char_code_edit]["bitmap"].append(row)
save_font()
renderCfont()
def save_font():
global list_glyph_data
global font_path
font_name = os.path.splitext(os.path.basename(font_path))[0]
with open(font_path, 'w', encoding='UTF-8') as f:
f.write(generate_c_font(font_name, font_size, list_glyph_data.values()))
def extract_data():
global list_glyph_data
list_glyph_data.clear()
font_name, font_size, font_data = load_c_font(font_path)
for glyph in font_data:
list_glyph_data[glyph["char_code"]] = glyph
b1.config(state=NORMAL)
b3.config(state=NORMAL)
b4.config(state=NORMAL)
renderCfont()
def select_file():
filetypes = (
('c font', '*.c'),
('All files', '*.*')
)
filename = filedialog.askopenfilename(
title='Open a c Font',
initialdir=os.getcwd(),
filetypes=filetypes)
global font_path
font_path = filename
extract_data()
def motion(event):
x = event.x // p_size
y = event.y // p_size
global selected_glyph
for bbox in list_bbox:
cc, x0, y0, x1, y1 = bbox
if x >= x0 and y >= y0 and x <= x1 and y <= y1:
canvas.coords(select_box, x0*p_size-1, y0*p_size-1, x1*p_size+1, y1*p_size+1)
selected_glyph = cc
def click(event):
global char_to_edit
global selected_glyph
char_to_edit.set(chr(selected_glyph))
render_single_char()
def motion_1(event):
global x
global y
x = event.x
y = event.y
if x%p_size_c != 0:
x-= x%p_size_c
if y%p_size_c != 0:
y-= y%p_size_c
canvas_1.itemconfig(ov, fill="White")
canvas_1.coords(ov, x, y, x+p_size_c, y+p_size_c)
def click_1(event):
x_pixel = x//p_size_c
y_pixel = y//p_size_c
if rect_ids[y_pixel][x_pixel] == -1:
rect_ids[y_pixel][x_pixel] = canvas_1.create_rectangle(x, y, x + p_size_c, y + p_size_c, fill="white")
else:
canvas_1.delete(rect_ids[y_pixel][x_pixel])
rect_ids[y_pixel][x_pixel] = -1
canvas_1.itemconfig(ov, fill="Black") # Changes the fill color to black to "hide" it
def slide_1(event):
global x
global y
global lastrect_xy
x = event.x
y = event.y
if x%p_size_c != 0:
x-= x%p_size_c
if y%p_size_c != 0:
y-= y%p_size_c
canvas_1.coords(ov, x, y, x+p_size_c, y+p_size_c)
x_pixel = x//p_size_c
y_pixel = y//p_size_c
if rect_ids[y_pixel][x_pixel] != lastrect_xy:
if rect_ids[y_pixel][x_pixel] == -1:
rect_ids[y_pixel][x_pixel] = canvas_1.create_rectangle(x, y, x + p_size_c, y + p_size_c, fill="white")
else:
canvas_1.delete(rect_ids[y_pixel][x_pixel])
rect_ids[y_pixel][x_pixel] = -1
lastrect_xy = (y_pixel,x_pixel)
window = Tk()
window.title("C font editor")
# TODO : make it dynamic
p_size = 6 # pixel size on the global renderer
p_size_c = 24 # pixel size on the single char renderer
# Get screen width and height
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
# Set the window size to fill the entire screen
window.geometry(f"{screen_width}x{screen_height}")
char_edit_windows_width = (screen_width // 4)//p_size_c
char_edit_windows_height = (screen_height // 2)//p_size_c
canva_width = (screen_width-screen_width // 4)//p_size
canva_height = screen_height//p_size-16
frame = Frame(window)
frame.pack(anchor="center", padx=10, pady=2)
rect_ids = [] # This is gonna be used to store rectangles ids
for y in range(40):
line = [-1] * 40 # Create a new list for each row
rect_ids.append(line)
########## top ##########
# choose font button
choose_font_button = Button(frame, text='Choose C font', width=16, height=2, background="blue", foreground="white", command=select_file)
choose_font_button.pack(side="left", padx=5)
# Variable to hold the state of the checkbox
bounding_box_bool = IntVar() # 0 for unchecked, 1 for checked
checkbox = Checkbutton(frame, text="Bounding box", variable=bounding_box_bool)
checkbox.pack(side="left", padx=5)
# Label and Entry for String to render
Label(frame, text="String to render:").pack(side="left", padx=5)
list_char_render = StringVar(value=str(""))
Entry(frame, textvariable=list_char_render, width=50).pack(side="left", padx=5)
b1 = Button(frame, text="Render", width=12, height=2, background="blue", foreground="white", command=renderCfont)
b1.pack(side="left", padx=5)
########## end of top ##########
########## bottom ##########
frame_bottom = Frame(window)
frame_bottom.pack(anchor="s", padx=2, pady=2)
##### left side #####
frame_left = Frame(frame_bottom)
frame_left.pack(anchor="center", side="left", padx=2, pady=2)
# Label and Entry for Chars to render
Label(frame_left, text="Character to edit").pack(side="top", pady=2)
char_to_edit = StringVar(value=str("R"))
Entry(frame_left, textvariable=char_to_edit, width=4).pack(side="top", pady=2)
b3 = Button(frame_left, text="render", width=12, height=2, background="blue", foreground="white", command=render_single_char)
b3.pack(side="top", padx=5)
# display the glyph data
Label(frame_left, text="Glyph data :").pack(side="top", pady=2)
output = Label(frame_left, height = 5, width = 25, bg = "light cyan")
output.pack(side="top", padx=5)
b4 = Button(frame_left, text="save", width=12, height=2, background="green", foreground="white", command=update_glyph_data)
b4.pack(side="top", padx=5)
# disable buttons until a font is loaded
b1.config(state=DISABLED)
b3.config(state=DISABLED)
b4.config(state=DISABLED)
canvas_1 = Canvas(frame_left, width=char_edit_windows_width*p_size_c, height=char_edit_windows_height*p_size_c, bg="black")
canvas_1.pack(side="left", padx=5)
# ov is the small pixel that 'stick' to the mouse
ov = canvas_1.create_rectangle(0,0,p_size_c,p_size_c,fill="white")
canvas_1.focus_set()
canvas_1.bind('<Motion>', motion_1)
canvas_1.bind("<Button 1>",click_1)
canvas_1.bind("<B1-Motion>",slide_1)
##### end of left side #####
##### right side #####
frame_right = Frame(frame_bottom)
frame_right.pack(side="right", padx=2, pady=2)
canvas = Canvas(frame_right, width=canva_width*p_size, height=canva_height*p_size, bg="black")
canvas.pack(anchor="n", side="left", padx=5)
canvas.focus_set()
canvas.bind('<Motion>', motion)
canvas.bind("<Button 1>",click)
select_box = canvas.create_rectangle(0,0,p_size,p_size, width=2, outline="blue")
##### end of right side #####
########## end of bottom ##########
window.mainloop()

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

124
tools/fonts/COPYRIGHT.TXT Normal file
View File

@ -0,0 +1,124 @@
Bitstream Vera Fonts Copyright
The fonts have a generous copyright, allowing derivative works (as
long as "Bitstream" or "Vera" are not in the names), and full
redistribution (so long as they are not *sold* by themselves). They
can be be bundled, redistributed and sold with any software.
The fonts are distributed under the following copyright:
Copyright
=========
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute
the Font Software, including without limitation the rights to use,
copy, merge, publish, distribute, and/or sell copies of the Font
Software, and to permit persons to whom the Font Software is furnished
to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font
Software without prior written authorization from the Gnome Foundation
or Bitstream Inc., respectively. For further information, contact:
fonts at gnome dot org.
Copyright FAQ
=============
1. I don't understand the resale restriction... What gives?
Bitstream is giving away these fonts, but wishes to ensure its
competitors can't just drop the fonts as is into a font sale system
and sell them as is. It seems fair that if Bitstream can't make money
from the Bitstream Vera fonts, their competitors should not be able to
do so either. You can sell the fonts as part of any software package,
however.
2. I want to package these fonts separately for distribution and
sale as part of a larger software package or system. Can I do so?
Yes. A RPM or Debian package is a "larger software package" to begin
with, and you aren't selling them independently by themselves.
See 1. above.
3. Are derivative works allowed?
Yes!
4. Can I change or add to the font(s)?
Yes, but you must change the name(s) of the font(s).
5. Under what terms are derivative works allowed?
You must change the name(s) of the fonts. This is to ensure the
quality of the fonts, both to protect Bitstream and Gnome. We want to
ensure that if an application has opened a font specifically of these
names, it gets what it expects (though of course, using fontconfig,
substitutions could still could have occurred during font
opening). You must include the Bitstream copyright. Additional
copyrights can be added, as per copyright law. Happy Font Hacking!
6. If I have improvements for Bitstream Vera, is it possible they might get
adopted in future versions?
Yes. The contract between the Gnome Foundation and Bitstream has
provisions for working with Bitstream to ensure quality additions to
the Bitstream Vera font family. Please contact us if you have such
additions. Note, that in general, we will want such additions for the
entire family, not just a single font, and that you'll have to keep
both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
glyphs to the font, they must be stylistically in keeping with Vera's
design. Vera cannot become a "ransom note" font. Jim Lyles will be
providing a document describing the design elements used in Vera, as a
guide and aid for people interested in contributing to Vera.
7. I want to sell a software package that uses these fonts: Can I do so?
Sure. Bundle the fonts with your software and sell your software
with the fonts. That is the intent of the copyright.
8. If applications have built the names "Bitstream Vera" into them,
can I override this somehow to use fonts of my choosing?
This depends on exact details of the software. Most open source
systems and software (e.g., Gnome, KDE, etc.) are now converting to
use fontconfig (see www.fontconfig.org) to handle font configuration,
selection and substitution; it has provisions for overriding font
names and subsituting alternatives. An example is provided by the
supplied local.conf file, which chooses the family Bitstream Vera for
"sans", "serif" and "monospace". Other software (e.g., the XFree86
core server) has other mechanisms for font substitution.

BIN
tools/fonts/DejaVuSans.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tools/fonts/Vera-Bold.ttf Normal file

Binary file not shown.