Last active
August 23, 2024 15:26
-
-
Save jleedev/b8a7de8d4be1bbade5feded0ea83f8b9 to your computer and use it in GitHub Desktop.
sdf font preview
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import io | |
from pathlib import Path | |
import sys | |
import unicodedata | |
from google.protobuf.message_factory import GetMessages | |
from google.protobuf.descriptor_pb2 import FileDescriptorSet | |
glyphs_pb = b'\n\xe3\x02\n\x0cglyphs.proto\x12\x0bllmr.glyphs"\x9d\x01\n\x05glyph\x12\x0e\n\x02id\x18\x01 \x02(\rR\x02id\x12\x16\n\x06bitmap\x18\x02 \x01(\x0cR\x06bitmap\x12\x14\n\x05width\x18\x03 \x02(\rR\x05width\x12\x16\n\x06height\x18\x04 \x02(\rR\x06height\x12\x12\n\x04left\x18\x05 \x02(\x11R\x04left\x12\x10\n\x03top\x18\x06 \x02(\x11R\x03top\x12\x18\n\x07advance\x18\x07 \x02(\rR\x07advance"a\n\tfontstack\x12\x12\n\x04name\x18\x01 \x02(\tR\x04name\x12\x14\n\x05range\x18\x02 \x02(\tR\x05range\x12*\n\x06glyphs\x18\x03 \x03(\x0b2\x12.llmr.glyphs.glyphR\x06glyphs"?\n\x06glyphs\x12.\n\x06stacks\x18\x01 \x03(\x0b2\x16.llmr.glyphs.fontstackR\x06stacks*\x05\x08\x10\x10\x80@B\x02H\x03' | |
message_classes = GetMessages(FileDescriptorSet.FromString(glyphs_pb).file) | |
glyph = message_classes["llmr.glyphs.glyph"] | |
fontstack = message_classes["llmr.glyphs.fontstack"] | |
glyphs = message_classes["llmr.glyphs.glyphs"] | |
""" | |
Numbers are cribbed from mapbox/tiny-sdf/blob/main/index.html: | |
Default parameters: scale=128, halo=0.55, gamma=2 | |
White is drawn with a buffer of <halo> +/- u_gamma | |
Black is drawn with a buffer of 0.75 +/- u_gamma | |
where u_gamma = <gamma> * 1.4142 / <scale> | |
When the glyphs are generated, the signed distance is computed, negative is | |
inside and positive is outside, 0 is on the contour. This is biased by | |
adding 0.25 and encoded as an unsigned byte, 0-255. | |
The fragment shader then reads the signed distance value from the texture, | |
and computes: | |
float dist = texture2D(u_texture, v_texcoord).r; | |
float alpha = smoothstep(u_buffer - u_gamma, u_buffer + u_gamma, dist); | |
gl_FragColor = vec4(u_color.rgb, alpha * u_color.a); | |
""" | |
def clamp(x, minVal, maxVal): | |
return min(max(x, minVal), maxVal) | |
def smoothstep(edge0, edge1, x): | |
t = clamp((x - edge0) / (edge1 - edge0), 0, 1) | |
return t * t * (3 - 2 * t) | |
def mix(x, y, a): | |
return x * (1 - a) + y * a | |
def point(n): | |
assert n in range(256) | |
dist = n / 255 | |
base = 0.8 | |
ga = 0.0221 | |
alpha_wh = smoothstep(0.55 - ga, 0.55 + ga, dist) | |
alpha_bl = smoothstep(0.75 - ga, 0.75 + ga, dist) | |
gr = mix(base, mix(1, 0, alpha_bl), alpha_wh) | |
r = int(gr * 255) | |
g = r | |
b = r | |
s = "\033\13348;2;%d;%d;%dm" % (r, g, b) | |
s += " " | |
s += "\033\1330m" | |
return s | |
def gray(mem): | |
out = io.StringIO() | |
for row in mem.tolist(): | |
for b in row: | |
out.write(point(b)) | |
out.write("\n") | |
return out.getvalue() | |
def show_font(path): | |
files = list(Path(path).glob("*.pbf")) | |
files.sort(key=lambda p: int(p.stem.split("-")[0])) | |
for f in files: | |
print(f) | |
show_pbf_file(f) | |
def name(c): | |
try: | |
return unicodedata.name(c) | |
except ValueError: | |
return None | |
def show_pbf_file(f): | |
for stack in glyphs.FromString(f.read_bytes()).stacks: | |
print(stack.name, stack.range) | |
for g in stack.glyphs: | |
if not g.bitmap: | |
continue | |
pr = type(g)() | |
pr.CopyFrom(g) | |
pr.ClearField("bitmap") | |
buf = memoryview(g.bitmap).cast("B", ((g.height + 6, g.width + 6))) | |
pic = gray(buf) | |
print(f"U+{g.id:04X}", name(chr(g.id))) | |
print(repr(pr).replace('\n', ' ')) | |
print(pic) | |
sys.stdout.flush() | |
def main(argv): | |
args = argv[1:] | |
if not args: | |
print("Specify path to PBF font stack") | |
return 1 | |
for arg in args: | |
show_font(arg) | |
if __name__ == "__main__": | |
sys.exit(main(sys.argv)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment