Skip to content

Instantly share code, notes, and snippets.

@maettig
Forked from aemkei/LICENSE.txt
Created December 11, 2011 00:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maettig/1457270 to your computer and use it in GitHub Desktop.
Save maettig/1457270 to your computer and use it in GitHub Desktop.
140byt.es SVG font renderer

While trying to create a 140 bytes version of the 140byt.es logo I came up with a generic SVG font renderer. You can type what you want and save the resulting SVG image as a file. Click here to see it in action. Tested with Firefox and Opera.

I failed in golfing the 140byt.es logo down to 140 bytes. The smallest approximation is a 9 byte HTML file with nothing else than 140byt es, but that's not cool. Adding some CSS does not help since it's always the wrong font. My SVG renderer outputs a 300 bytes file which can be optimized and gzipped to about 200 bytes (don't forget to use gzip -9 -n).

Besides, here are my best approximations of the 140bytes Twitter logo in 365 bytes (238 bytes as .svgz) and the JS logo in 138 bytes:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background:#9CD" viewBox="0 0 46 18">
  <path d="M6 6c4 0 4 0 4 6m9 -6c0 5 6 5 6 0m0 0v6M37 6c-4 0 -4 6 0 6 4 0 4 -6 0 -6" id="1" stroke-linecap="round" fill="none"/>
  <use xlink:href="#1" stroke-width="8" stroke="#FFF"/>
  <use xlink:href="#1" stroke-width="3" stroke="#9CD"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25" style="background:#FD2">
  <text x="5" y="23" font-family="Arial">JS</text>
</svg>
function(a, b, c, d) //text and three dummy arguments
{
d = ''; //compile path for the current string
for (b = 0; c = a.charAt(b++); ) //for each character in the string
d += z[c] || //append path from the character map or
z._; //use the underscore for undefined characters
b = /[hlm]([--9]+)/g; //match first number from all horizontal/line/move commands
for (; c = b.exec(d); ) //for each match in the compiled path
y[2] += +c[1]; //expand viewBox by the matched width, converted to a number
x += d; //append the compiled path to the XML fragment
return this //make the method call chainable
}
function(a,b,c,d){d='';for(b=0;c=a.charAt(b++);)d+=z[c]||z._;b=/[hlm]([--9]+)/g;for(;c=b.exec(d);)y[2]+=+c[1];x+=d;return this}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 Thiemo Mättig <http://maettig.com/>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "140byt.es SVG font renderer",
"description": "Renders SVG images with the 140byt.es logo font.",
"keywords": [
"font",
"logo",
"svg"
]
}
<!DOCTYPE html>
<style type="text/css">
* { background: #333; color: #FFF; font-size: 1.5em; text-align: center; }
h1 { cursor: pointer; min-height: 200px; }
h1, h1 * { background: #FFF; max-height: 200px; vertical-align: top; }
</style>
<h1 title="Click to open SVG image" onclick="location.href='data:image/svg+xml,'+escape(this.innerHTML)">140byt es</h1>
<input onkeyup="refreshLogo(this.value)" value="140byt es">
<script type="text/javascript">
var svg = function(w, x, y, z)
{
z = { //character map, each path must start and end on the baseline
' ': 'm1 0', //move 1 forward on the baseline without drawing a line
'!': 'm1 -3v1.99m0 1v.01',
'"': 'm1 -3v1m1 -1v1m0 2',
'#': 'm1 -2h3m-3 1h3m-2 -2v3m1 -3v3m1 0',
"'": 'm1 -3v1m0 2',
'+': 'm1 -1h2h-1v1v-2m1 2',
',': 'm1 1v-1', //move 1 forward and 1 down, then draw a vertical line 1 up
'-': 'm1 -1h1m0 1', //move 1 forward and 1 up, draw horizontal line, move 1 down to the baseline
'.': 'm1 -.01v.01',
'/': 'm1 0l1 -2m0 2',
'0': 'm3 0h-2v-2h2z', //move 3 forward, draw 2 backward, 2 up, 2 forward and close the subpath
'1': 'm1 -2h1v2',
'2': 'm1 -2h2v1m-2 0v1h2',
'3': 'm1 -2h2v1h-1h1v1h-2h2',
'4': 'm1 -2v1h2v-1v2',
'5': 'm3 -2h-2v1m2 0v1h-2h2',
'6': 'm1 -1h2v1h-2v-2h2m0 2',
'7': 'm1 -2h2v2',
'8': 'm3 0h-2v-2h2v1h-2h2z',
'9': 'm3 -1h-2v-1h2v2',
':': 'm1 -2v.01m0 2v-.01',
';': 'm1 -1.01v.01m0 2v-1',
'<': 'm2 -2l-1 1l1 1', //move forward and up, draw two diagonal lines
'>': 'm1 -2l1 1l-1 1m1 0',
'@': 'm2 -2v1h2v-2h-3v3h3',
'\\': 'm1 -2l1 2',
'_': 'm1 0h1',
'a': 'm1 -1v1h2v-2h-2m2 2',
'b': 'm3 0h-2v-3v1h2z',
'c': 'm3 -2h-2v2h2',
'd': 'm3 0h-2v-2h2v-1z',
'e': 'm3 -1v-1h-2v2h2',
'f': 'm2 -3h-1v3v-2h1m0 2',
'g': 'm3 -2h-2v2h2v-1v1',
'h': 'm1 0v-3v1h2v2',
'i': 'm1 -2v2',
'j': 'm1 1h1v-3v2',
'k': 'm1 -3v3l2 -2l-1 1l1 1',
'l': 'm1 -3v3h1',
'm': 'm1 0v-2h1.5v2v-2h1.5v2',
'n': 'm1 0v-2h2v2',
'o': 'm3 0h-2v-2h2z',
'p': 'm3 0h-2v1v-3h2z',
'q': 'm3 1v-3h-2v2h2',
'r': 'm1 0v-2h1m0 2',
's': 'm3 -2h-2v1m2 0v1h-2h2',
't': 'm2 -2h-1v-1v3h1',
'u': 'm1 -2v2h2v-2v2',
'v': 'm1 -2l1 2l1 -2m0 2',
'w': 'm1 -2v2h1.5v-2v2h1.5v-2v2',
'x': 'm1 0l2 -2m-2 0l2 2',
'y': 'm1 -2v2h1v1v-1h1v-2v2',
'z': 'm1 -2h2l-2 2h2',
'|': 'm1 -3v3'
}
y = [0, -4, 1, 6] //viewBox with x, y, width, height
x = '><path d="'; //XML fragment
w = [ //array of default styles with fixed positions
'fill:none', //w[0]
'stroke:#000', //w[1]
'stroke-linecap:round', //w[2]
'stroke-linejoin:round', //w[3]
'stroke-width:.9' //w[4]
]
this.color = function(a) { return this.setStyle(1, a) }
this.linecap = function(a) { return this.setStyle(2, a) }
this.linejoin = function(a) { return this.setStyle(3, a) }
this.width = function(a) { return this.setStyle(4, a) }
this.setStyle = function(a, b)
{
w[a] = w[a].replace(/:.*/, ':' + b);
return this //make all method calls chainable
}
this.padding = function(a) //can not be called multiple times
{
a = --a || -1; //default padding is 1
y = [y[0] - a, y[1] - a, y[2] + a * 2, y[3] + a * 2];
return this
}
// 127 bytes
this.text = function(a, b, c, d) //can be called multiple times
{
d = ''; //compile path for the current string
for (b = 0; c = a.charAt(b++); ) //for each character in the string
d += z[c] || //append path from the character map or
z._; //use the underscore for undefined characters
b = /[hlm]([--9]+)/g; //match first number from all horizontal/line/move commands
for (; c = b.exec(d); ) //for each match in the compiled path
y[2] += +c[1]; //expand viewBox by the matched width, converted to a number
x += d; //append the compiled path to the XML fragment
return this //make the method call chainable
}
this.background = function(a) //can not be called multiple times
{
// This way of setting a background color (instead of adding a colored rect)
// works in Firefox and Opera but is ignored when loading the SVG in Inkscape.
x = ' style="background:' + (a || '#FFF') + '"' + x;
return this
}
this.toString = function()
{
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="' + y.join(' ') + '"' +
x + '" style="' + w.join(';') + '"/></svg>'
}
}
var container = document.getElementsByTagName('H1')[0];
var refreshLogo = function(a, b)
{
container.innerHTML = new svg().color('#333').text(a.toLowerCase());
// A more complex example:
//container.innerHTML = new svg().background('green').color('white').padding(3).width(.5).text(a);
// Instead of embedding the SVG in HTML we can create an image object with a data URI.
// This is a little more compatible but flickers when replacing the image.
//b = new Image();
//b.src = 'data:image/svg+xml,' + escape(new svg().text(a));
//container.replaceChild(b, container.firstChild);
}
refreshLogo(container.firstChild.data);
// Create optional favicon, because we can. ;-)
var l = document.createElement('LINK');
l.rel = 'shortcut icon';
// Opera is able to render the background color but does not when using the SVG as favicon.
// Firefox does not display the SVG favicon when reloading the page.
l.href = 'data:image/svg+xml,' + escape(new svg().background('#FD2').padding(.7).width(.6).text('js'));
document.getElementsByTagName('HEAD')[0].appendChild(l);
</script>
@atk
Copy link

atk commented Dec 11, 2011

You are aware that the 140byt.es logo is already made in canvas with a 140bytes snippet?

@maettig
Copy link
Author

maettig commented Dec 12, 2011

Your canvas tool (is this the right gist?) is also very cool, even if it uses eval. ;-) My original intention was to squish the data into 140 bytes, regardless of the technique, but it turns out this is impossible. I choose SVG since I like it a lot more because it's a world of objects instead of a pixel canvas.

@atk
Copy link

atk commented Dec 12, 2011

Thank you. It was not possible to do this trick without eval. I like SVG, too, but canvas (especially with shims like excanvas) is much better supported. The whole logo data is currently 763bytes long. but that's an unoptimized array that could probably be packed rather simple. Even moreso, I'd like to look for an algorythmic way to store that data (preferably in <=140bytes), but I haven't found one yet.

@maettig
Copy link
Author

maettig commented Dec 13, 2011

When compressing the 140byt.es logo data from [6,10],[8,10,11,10,11,16],... into something like FJ,HJKJKP,... (each number becomes an ASCII character) it fits in about 200 bytes. To make this 140 bytes you need to simplify the logo a lot (like I did with my SVG font). Would be a shame since the logo is so nice.

@atk
Copy link

atk commented Mar 20, 2012

We could use Sebastian's LZW packer for that...

@maettig
Copy link
Author

maettig commented Mar 20, 2012

This gist by @sebastien-p? To output .svgz instead of plain SVG? Yea, that would be awesome. Edit: My bad. LZW is not gzip.

@atk
Copy link

atk commented Mar 21, 2012

No, to compress the input even further. Gzip is a mix between LZW and huffman. Incidentially, I do have a huffman compressor in JS (http://tinyjs.sourceforge.net/tiny-huffman.js), but it's nowhere near 140bytes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment