Skip to content

Instantly share code, notes, and snippets.

@Revincx
Created May 8, 2024 08:37
Show Gist options
  • Save Revincx/946304b88463c136c0595333de917be0 to your computer and use it in GitHub Desktop.
Save Revincx/946304b88463c136c0595333de917be0 to your computer and use it in GitHub Desktop.
ttf2tgs

ttf2tgs

Export Font Glyphs to Telegram Animated Sticker Format

Requirements

  • Python 3.x

Install pip denpendcies

pip install -r requirements.txt

Note: If you use python virtual environments, do not forget to create and activate it before installing dependcies.

Usage

Assume that you have font file SomeFonts.ttf (or OpenType), then input all the characters you want to export in characters.txt.

python3 ttf2tgs.py SomeFonts.ttf characters.txt

And you will get Telegram Animated Stickers files under the tgs folder.

Options

options:
  -h, --help       show this help message and exit
  --output OUTPUT  target directory to the output TGS files, default ./tgs
  --scale SCALE    output image scale factor, less than 1.0
  --svg            save glyph SVG files

Thanks

  • fonttools - A library to manipulate font files from Python.
  • python-lottie - Python framework to manipulate lottie / telegram animated sticker files
import os,sys
from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.transformPen import TransformPen
from fontTools.misc.transform import Transform
from fontTools.pens.boundsPen import BoundsPen
from xml.etree.ElementTree import Element, tostring
from lottie.importers.svg import import_svg
from lottie.exporters.core import export_tgs
from argparse import ArgumentParser
output_path = "./tgs"
scale = 1.0
def export_glyph(font: TTFont, character: str):
glyph_set = font.getGlyphSet()
unicode_value = ord(character) # 获取字符的 Unicode 码点
glyph_name = font.getBestCmap().get(unicode_value) # 从字符映射表中获取 glyph 名称
if glyph_name:
glyph = glyph_set[glyph_name]
bpen = BoundsPen(glyph)
glyph.draw(bpen)
bounds = bpen.bounds
xMin, yMin, xMax, yMax = bounds
width = xMax - xMin
height = yMax - yMin
max_length = max(width, height)
# Center the glyph in the output image
dxOffset = 0
dyOffset = 0
if width > height:
dyOffset = (max_length - height) / 2
else:
dxOffset = (max_length - width) / 2
transform = Transform(1, 0, 0, -1, -xMin + dxOffset, height + yMin + dyOffset)
if scale != 1.0:
transform = Transform( # Affine Transform Matrix
scale, # xx
0, # xy
0, # yx
-scale, # yy
-xMin * scale + (1 - scale) * 0.5 * width + dxOffset, # dx
(height + yMin) * scale + (1 - scale) * 0.5 * height + dyOffset, # dy
)
spen = SVGPathPen(glyph)
tpen = TransformPen(spen, transform)
glyph.draw(tpen)
svg_root = Element(
"svg",
width=str(max_length),
height=str(max_length),
xmlns="http://www.w3.org/2000/svg",
)
svg_root.append(
Element("path", d=spen.getCommands(), fill="black", stroke="none")
)
with open(f"{output_path}/{gen_char_name(character)}.svg", "wb") as f:
f.write(tostring(svg_root))
else:
print(f"No glyph found for the character {character}")
def get_char_array(file_path: str):
with open(file_path, "r") as f:
str = f.read()
str = str.strip().replace(" ", "")
return list(str)
def gen_char_name(char: str):
# escape special characters
charname = char
if char == "\\":
charname = "backslash"
elif char == "/":
charname = "slash"
elif char == ":":
charname = "colon"
elif char == "*":
charname = "star"
elif char == "?":
charname = "question"
elif char == "\"":
charname = "quote"
elif char == "<":
charname = "lt"
elif char == ">":
charname = "gt"
elif char == "|":
charname = "pipe"
return f"{charname}_{ord(char)}"
class MyParser(ArgumentParser):
def error(self, message):
sys.stderr.write('error: %s\n' % message)
self.print_help()
sys.exit(2)
if __name__ == "__main__":
parser = MyParser(
description="Convert a TTF font to a Lottie-compatible JSON file"
)
parser.add_argument("font", type=str, help="Path to the TTF/OTF font file")
parser.add_argument(
"chars", type=str, help="Path to the file containing the characters to convert"
)
parser.add_argument("--output", type=str, help="Target directory to the output TGS files")
parser.add_argument(
"--scale",
type=float,
help="Output image scale factor, less than 1.0",
default=1.0,
)
parser.add_argument("--svg", action="store_true", help="Save SVG files")
args = parser.parse_args()
output_path = args.output if args.output else output_path
scale = args.scale if args.scale < 1 and args.scale > 0 else scale
try:
font = TTFont(args.font)
except Exception as e:
print(f"Incorrect font file: {e}")
exit()
chars = get_char_array(args.chars)
# mkdir output
if not os.path.exists(output_path):
os.makedirs(output_path)
for char in chars:
export_glyph(font, char)
svg = import_svg(f"{output_path}/{gen_char_name(char)}.svg")
tgs = export_tgs(svg, f"{output_path}/{gen_char_name(char)}.tgs")
if args.svg is False:
os.remove(f"{output_path}/{gen_char_name(char)}.svg")
print(f"Exported character '{char}' to {output_path}/{gen_char_name(char)}.tgs")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment