mirror of
https://github.com/ryanoasis/nerd-fonts.git
synced 2025-01-19 03:12:07 +02:00
Add original-source.otf generator script
[why] When we add a custom glyph (or want to update Seti) the process is rather laborious and we are needed to change the font and the accompanying `i_seti.sh` in sync. [how] We use a data file to map icon (svg) filenames to codepoints and readable names. That file is parsed and the font and info file is created (overwritten in the repo); and could then be easily committed. This can be a CI workflow. Having a dedicated mapping file (`icons.tsv`) enables us to have stable codepoints for the same symbol over time. Changes in codepoint allocation can be checked in git. Having the font autogenerated help guarantee that the icons are all likely scaled. We rescale them all to the same size and mid-position. That is not needed for font-patcher, because it rescales and shifts again based on to-be-patched font metrics. But it certainly is better for a view into the original-source font. Sizes and position are still roughly equivalent to the hand positioned glyphs. Signed-off-by: Fini Jastrow <ulf.fini.jastrow@desy.de>
This commit is contained in:
parent
5a9b44749f
commit
328b8a2d22
175
bin/scripts/generate-original-source.py
Executable file
175
bin/scripts/generate-original-source.py
Executable file
@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
# Nerd Fonts Version: 2.2.2
|
||||
# Script Version: 1.0.0
|
||||
# Generates original-source.otf from individual glyphs
|
||||
#
|
||||
# Idea & original code taken from
|
||||
# https://github.com/lukas-w/font-logos/blob/v1.0.1/scripts/generate-font.py
|
||||
|
||||
import os
|
||||
import re
|
||||
import fontforge
|
||||
import psMat
|
||||
|
||||
# Double-quotes required here, for version-bump.sh:
|
||||
version = "2.2.2"
|
||||
|
||||
start_codepoint = 0xE4FA
|
||||
codepoint_shift = 0x0100 # shift introduced by font-patcher
|
||||
|
||||
vector_datafile = 'icons.tsv'
|
||||
vectorsdir = '../../src/svgs'
|
||||
fontfile = 'original-source.otf'
|
||||
fontdir = '../../src/glyphs'
|
||||
glyphsetfile = 'i_seti.sh'
|
||||
glyphsetsdir = 'lib'
|
||||
|
||||
def hasGaps(data, start_codepoint):
|
||||
""" Takes a list of integers and checks that it contains no gaps """
|
||||
for i in range(min(data) + 1, max(data)):
|
||||
if not i in data:
|
||||
print("Gap at offset {}".format(i - start_codepoint))
|
||||
return True
|
||||
return False
|
||||
|
||||
def iconFileLineOk(parts):
|
||||
""" Check one line for course errors, decide if it shall be skipped """
|
||||
if parts[0].startswith('#'):
|
||||
# Comment lines start with '#'
|
||||
return False
|
||||
if len(parts) != 2 and len(parts) != 3:
|
||||
print('Unexpected data on the line "{}"'.format(line.strip()))
|
||||
return False
|
||||
if int(parts[0]) < 0:
|
||||
print('Offset must be positive on line "{}", ignoring'.format(line.strip()))
|
||||
return False
|
||||
return True
|
||||
|
||||
def addLineToData(data, parts, codepoint):
|
||||
""" Add one line to the data. Return (success, is_alias) """
|
||||
ali = False
|
||||
if codepoint in data:
|
||||
data[codepoint][0] += [ parts[1] ]
|
||||
if len(parts) > 2 and data[codepoint][1] != parts[2]:
|
||||
print('Conflicting filename for {}, ignoring {}'.format(codepoint, parts[2]))
|
||||
return False, False
|
||||
ali = True
|
||||
else:
|
||||
data[codepoint] = [[parts[1]], parts[2]]
|
||||
return True, ali
|
||||
|
||||
def readIconFile(filename, start_codepoint):
|
||||
""" Read the database with codepoints, names and files """
|
||||
# First line of the file is the header, it is ignored
|
||||
# All other lines are one line for one glyph
|
||||
# Elements in each line are tab separated (any amount consecutive of tabs)
|
||||
# First element is the offset, 2nd is name, 3rd is filename
|
||||
# For aliases the 3rd can be ommited on an additional line
|
||||
data = {}
|
||||
num = 0
|
||||
ali = 0
|
||||
with open(filename, 'r') as f:
|
||||
for line in f.readlines():
|
||||
parts = re.split('\t+', line.strip())
|
||||
if not iconFileLineOk(parts):
|
||||
continue
|
||||
offset = int(parts[0])
|
||||
codepoint = start_codepoint + offset
|
||||
if re.search('[^a-zA-Z0-9_]', parts[1]):
|
||||
print('Invalid characters in name: "{}" replaced by "_"'.format(parts[1]))
|
||||
parts[1] = re.sub('[^a-zA-Z0-9_]', '_', parts[1])
|
||||
added = addLineToData(data, parts, codepoint)
|
||||
if not added[0]:
|
||||
continue
|
||||
num += 1
|
||||
if added[1]:
|
||||
ali += 1
|
||||
print('Read glyph data successfully with {} entries ({} aliases)'.format(num, ali))
|
||||
return (data, num, ali)
|
||||
|
||||
def widthFromBB(bb):
|
||||
""" Calculate glyph width from BoundingBox data """
|
||||
return bb[2] - bb[0]
|
||||
|
||||
def heightFromBB(bb):
|
||||
""" Calculate glyph height from BoundingBox data """
|
||||
return bb[3] - bb[1]
|
||||
|
||||
def calcShift(left1, width1, left2, width2):
|
||||
""" Calculate shift needed to center '2' in '1' """
|
||||
return width1 / 2 + left1 - width2 / 2 - left2
|
||||
|
||||
def addIcon(codepoint, name, filename):
|
||||
""" Add one outline file and rescale/move """
|
||||
dBB = [120, 0, 1000-120, 900] # just some nice sizes
|
||||
filename = os.path.join(vectorsdir, filename)
|
||||
glyph = font.createChar(codepoint, name)
|
||||
glyph.importOutlines(filename)
|
||||
gBB = glyph.boundingBox()
|
||||
scale_x = widthFromBB(dBB) / widthFromBB(gBB)
|
||||
scale_y = heightFromBB(dBB) / heightFromBB(gBB)
|
||||
scale = scale_y if scale_y < scale_x else scale_x
|
||||
glyph.transform(psMat.scale(scale, scale))
|
||||
gBB = glyph.boundingBox() # re-get after scaling (rounding errors)
|
||||
glyph.transform(psMat.translate(
|
||||
calcShift(dBB[0], widthFromBB(dBB), gBB[0], widthFromBB(gBB)),
|
||||
calcShift(dBB[1], heightFromBB(dBB), gBB[1], heightFromBB(gBB))))
|
||||
glyph.width = int(dBB[2] + dBB[0])
|
||||
glyph.manualHints = True
|
||||
|
||||
def createGlyphInfo(icon_datasets, filepathname, into):
|
||||
""" Write the glyphinfo file """
|
||||
with open(filepathname, 'w', encoding = 'utf8') as f:
|
||||
f.write(u'#!/usr/bin/env bash\n')
|
||||
f.write(intro)
|
||||
f.write(u'# Script Version: (autogenerated)\n')
|
||||
f.write(u'test -n "$__i_seti_loaded" && return || __i_seti_loaded=1\n')
|
||||
for codepoint, data in icon_datasets.items():
|
||||
f.write(u"i='{}' {}=$i\n".format(chr(codepoint),data[0][0]))
|
||||
for alias in data[0][1:]:
|
||||
f.write(u" {}=${}\n".format(alias, data[0][0]))
|
||||
f.write(u'unset i\n')
|
||||
|
||||
|
||||
### Lets go
|
||||
|
||||
print('\n[Nerd Fonts] Glyph collection font generator {}\n'.format(version))
|
||||
|
||||
font = fontforge.font()
|
||||
font.fontname = 'NerdFontFileTypes-Regular'
|
||||
font.fullname = 'Nerd Font File Types Regular'
|
||||
font.familyname = 'Nerd Font File Types'
|
||||
font.em = 1024
|
||||
font.encoding = 'UnicodeFull'
|
||||
|
||||
# Add valid space glyph to avoid "unknown character" box on IE11
|
||||
glyph = font.createChar(32)
|
||||
glyph.width = 200
|
||||
|
||||
font.sfntRevision = None # Auto-set (refreshed) by fontforge
|
||||
font.version = version
|
||||
font.copyright = 'Nerd Fonts'
|
||||
font.appendSFNTName('English (US)', 'Version', version)
|
||||
font.appendSFNTName('English (US)', 'Vendor URL', 'https://github.com/ryanoasis/nerd-fonts')
|
||||
font.appendSFNTName('English (US)', 'Copyright', 'Nerd Fonts')
|
||||
|
||||
icon_datasets, _, num_aliases = readIconFile(os.path.join(vectorsdir, vector_datafile), start_codepoint)
|
||||
gaps = ' (with gaps)' if hasGaps(icon_datasets.keys(), start_codepoint) else ''
|
||||
|
||||
for codepoint, data in icon_datasets.items():
|
||||
addIcon(codepoint, data[0][0], data[1])
|
||||
num_icons = len(icon_datasets)
|
||||
|
||||
print('Generating {} with {} glyphs'.format(fontfile, num_icons))
|
||||
font.generate(os.path.join(fontdir, fontfile))
|
||||
|
||||
# We create the font, but ... patch it in on other codepoints :-}
|
||||
icon_datasets = { code + codepoint_shift : data for (code, data) in icon_datasets.items() }
|
||||
|
||||
intro = u'# Seti-UI + Custom ({} icons, {} aliases)\n'.format(num_icons, num_aliases)
|
||||
intro += u'# Codepoints: {:X}-{:X}{}\n'.format(min(icon_datasets.keys()), max(icon_datasets.keys()), gaps)
|
||||
intro += u'# Nerd Fonts Version: {}\n'.format(version)
|
||||
|
||||
print('Generating GlyphInfo {}'.format(glyphsetfile))
|
||||
createGlyphInfo(icon_datasets, os.path.join(glyphsetsdir, glyphsetfile), intro)
|
||||
print('Finished')
|
77
src/svgs/icons.tsv
Normal file
77
src/svgs/icons.tsv
Normal file
@ -0,0 +1,77 @@
|
||||
# This file defines the codepoints for the individual glyphs in
|
||||
# original-source.otf and their names in the CSS and on the cheat
|
||||
# sheet (via i_seti.sh).
|
||||
#
|
||||
# If you add a svg to the directory you need to add also a line here.
|
||||
# Keep the numbers consecutive.
|
||||
# You can add aliases by adding a new line with the same offset but
|
||||
# different name; omit the filename on those lines.
|
||||
# Use generate-original-source.py to regenerate the font used to patch.
|
||||
#
|
||||
# offset name (in i_seti.sh) filename (.svg)
|
||||
#
|
||||
0 i_custom_folder_npm npm-folder.svg
|
||||
1 i_custom_folder_git git-folder.svg
|
||||
1 i_custom_folder_git_branch
|
||||
2 i_custom_folder_config config-folder.svg
|
||||
3 i_custom_folder_github octocat-folder.svg
|
||||
4 i_custom_folder_open open-folder.svg
|
||||
5 i_custom_folder folder.svg
|
||||
6 i_seti_stylus stylus.svg
|
||||
7 i_seti_project project.svg
|
||||
8 i_seti_play_arrow play-arrow.svg
|
||||
9 i_seti_sass sass.svg
|
||||
10 i_seti_rails rails.svg
|
||||
11 i_seti_ruby ruby.svg
|
||||
12 i_seti_python python.svg
|
||||
13 i_seti_heroku heroku.svg
|
||||
14 i_seti_php php.svg
|
||||
15 i_seti_markdown markdown.svg
|
||||
16 i_seti_license license.svg
|
||||
17 i_seti_json less.svg
|
||||
17 i_seti_less
|
||||
18 i_seti_javascript javascript.svg
|
||||
19 i_seti_image image.svg
|
||||
20 i_seti_html html.svg
|
||||
21 i_seti_mustache handlebars.svg
|
||||
22 i_seti_gulp gulp.svg
|
||||
23 i_seti_grunt grunt.svg
|
||||
24 i_seti_default file.svg
|
||||
24 i_seti_text
|
||||
25 i_seti_folder seti-folder.svg
|
||||
26 i_seti_css css.svg
|
||||
27 i_seti_config config.svg
|
||||
28 i_seti_npm npm.svg
|
||||
29 i_seti_home home.svg
|
||||
30 i_seti_ejs html.svg
|
||||
31 i_seti_xml rss.svg
|
||||
32 i_seti_bower bower.svg
|
||||
33 i_seti_coffee coffeescript.svg
|
||||
33 i_seti_cjsx
|
||||
34 i_seti_twig twig.svg
|
||||
35 i_custom_cpp c++.svg
|
||||
36 i_custom_c c.svg
|
||||
37 i_seti_haskell haskell.svg
|
||||
38 i_seti_lua lua.svg
|
||||
39 i_indent_line separator.svg
|
||||
39 i_indentation_line
|
||||
39 i_indent_dotted_guide
|
||||
40 i_seti_karma karma.svg
|
||||
41 i_seti_favicon favourite.svg
|
||||
42 i_seti_julia julia.svg
|
||||
43 i_seti_react react.svg
|
||||
44 i_custom_go go2.svg
|
||||
45 i_seti_go go.svg
|
||||
46 i_seti_typescript typescript.svg
|
||||
47 i_custom_msdos ms-dos.svg
|
||||
48 i_custom_windows windows.svg
|
||||
49 i_custom_vim vim.svg
|
||||
50 i_custom_elm elm.svg
|
||||
51 i_custom_elixir elixir.svg
|
||||
52 i_custom_electron electron.svg
|
||||
53 i_custom_crystal crystal.svg
|
||||
54 i_custom_purescript purescript.svg
|
||||
55 i_custom_puppet puppet.svg
|
||||
56 i_custom_emacs emacs.svg
|
||||
57 i_custom_orgmode orgmode.svg
|
||||
58 i_custom_kotlin kotlin.svg
|
Can't render this file because it has a wrong number of fields in line 11.
|
Loading…
x
Reference in New Issue
Block a user