Created
May 1, 2016 02:14
-
-
Save jtmcdole/8e303a6cd9aa668f3b858422684bef2c to your computer and use it in GitHub Desktop.
simple canvas font texture atlas
This file contains 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 'dart:async'; | |
import 'dart:html'; | |
import 'dart:convert'; | |
// add <link href='https://fonts.googleapis.com/css?family=Roboto+Condensed' rel='stylesheet' type='text/css'> to html | |
// Draw red top, green ascent, and blue height | |
bool drawBounds = true || window.location.href.contains('drawBounds'); | |
bool drawBox = true || window.location.href.contains('drawBox'); | |
main() async { | |
await genFontMap(height: 18); | |
await genFontMap(height: 24); | |
await genFontMap(height: 32); | |
await genFontMap(height: 18, font: 'Roboto'); | |
await genFontMap(height: 18, font: 'Roboto Condensed'); | |
await genFontMap(height: 24, font: 'Roboto'); | |
await genFontMap(height: 24, font: 'Roboto Condensed'); | |
await genFontMap(height: 36, font: 'Roboto'); | |
await genFontMap(height: 36, font: 'Roboto Condensed'); | |
} | |
var str = new String.fromCharCodes(new List.generate(0xFF - 0x20, (i) => i)); | |
Future<Map> getHeight(font) async { | |
var ascent; | |
var descent; | |
var height; | |
var block = new DivElement(); | |
block.style | |
..display = 'inline-block' | |
..width = '1px' | |
..height = '0px'; | |
var text = new SpanElement() | |
..style.font = font | |
..text = str; | |
var div = new DivElement(); | |
div.style.whiteSpace = 'nowrap'; | |
div..append(text)..append(block); | |
document.body.append(div); | |
block.style.verticalAlign = 'baseline'; | |
await new Future.value(); | |
ascent = block.offsetTop - text.offsetTop; | |
block.style.verticalAlign = 'bottom'; | |
await new Future.value(); | |
height = block.offsetTop - text.offsetTop; | |
descent = height - ascent; | |
print('$font: height:$height descent:$descent ascent:$ascent'); | |
div.remove(); | |
return {'descent': descent, 'ascent': ascent, 'height': height}; | |
} | |
marker(can, x, y, style) { | |
can | |
..strokeStyle = style | |
..beginPath() | |
..lineWidth = 1 | |
..moveTo(0, y) | |
..lineTo(x, y) | |
..closePath() | |
..stroke(); | |
} | |
genFontMap( | |
{int height: 32, String font: 'monospace', String style: '#fff'}) async { | |
const int canWidth = 512; | |
var json = await getHeight('${height}px $font'); | |
var ascent = json['ascent']; | |
var rHeight = json['height']; | |
var descent = json['descent']; | |
document.body.append(new Element.html("<span>$font $json</span>") | |
..style.font = '${height}px $font'); | |
document.body.append(new Element.br()); | |
await new Future.delayed(const Duration(milliseconds: 200)); | |
var ele = new CanvasElement(width: canWidth, height: 512); | |
var can = ele.context2D; | |
can.textBaseline = 'top'; | |
can.fillStyle = style; | |
can.font = '${height}px $font'; | |
Map characters = {}; | |
json['characters'] = characters; | |
List<num> widths = new List.filled(0xFF, 0); | |
try { | |
can.clearRect(0, 0, 512, 512); | |
var x = 2.5; | |
var y = 2.5; | |
for (int i = 0x20; i < 0xFF; i++) { | |
if (i == 0x7F) { | |
i += 32; | |
continue; | |
} | |
var char = new String.fromCharCode(i); | |
var metrics = can.measureText(char); | |
widths[i] = metrics.width; | |
if ((x + metrics.width) > canWidth) { | |
y += rHeight + 2; | |
x = 2.5; | |
} | |
if (x == 2.5) { | |
if (drawBounds) { | |
marker(can, canWidth, y, 'rgba(255, 0, 0, 1.0)'); | |
marker(can, canWidth, y + ascent, 'rgba(0, 255, 0, 1.0)'); | |
marker(can, canWidth, y + rHeight, 'rgba(0, 0, 255, 1.0)'); | |
} | |
} | |
if (drawBox) { | |
can.strokeStyle = 'rgba(255, 255, 0, 1.0)'; | |
can.rect(x.toInt() + .5, y.toInt() + .5, metrics.width.ceil(), rHeight); | |
can.stroke(); | |
} | |
characters[char] = [x.toInt(), y.toInt(), metrics.width]; | |
can.fillText(new String.fromCharCode(i), x, y); | |
x += metrics.width.ceil() + 2.5; | |
} | |
y += rHeight; | |
var kerning = {}; | |
json['kerning'] = kerning; | |
// collect kerning now we have all single letter widths | |
for (int left = 0x20; left < 0xFF; left++) { | |
if (left == 0x7F) { | |
left += 32; | |
continue; | |
} | |
for (int right = 0x20; right < 0xFF; right++) { | |
if (right == 0x7F) { | |
right += 32; | |
continue; | |
} | |
var sum = widths[left] + widths[right]; | |
var pair = new String.fromCharCodes([left, right]); | |
var cWidth = can.measureText(pair).width; | |
cWidth = cWidth - sum; | |
if (cWidth != 0.0) { | |
kerning[pair] = cWidth; | |
} | |
} | |
} | |
// force into power of 2 textur | |
if (y < 32) | |
y = 32; | |
else if (y < 64) | |
y = 64; | |
else if (y < 128) | |
y = 128; | |
else if (y < 256) | |
y = 256; | |
else if (y < 512) y = 512; | |
var ele2 = new CanvasElement(width: canWidth, height: y); | |
var can2 = ele2.context2D; | |
can2.drawImage(ele, 0, 0); | |
json['font_height_px'] = height; | |
json['font'] = font; | |
json['style'] = style; | |
json['img_width'] = canWidth; | |
json['img_height'] = y; | |
print('$font: $canWidth x $y'); | |
document.body.append(ele2); | |
document.body.append(new Element.br()); | |
var filename = '$font.$height'; | |
var img = new AnchorElement() | |
..text = '$filename.data' | |
..download = '$filename.data'; | |
img.href = 'data:application/octet-stream;base64,' | |
'${BASE64.encode(can2.getImageData(0, 0, canWidth, y).data)}'; | |
document.body.append(img); | |
document.body.append(new Element.br()); | |
document.body.append(new AnchorElement() | |
..download = '$filename.json' | |
..text = '$filename.json' | |
..href = 'data:text/html;charset=utf-8,${JSON.encode(json)}'); | |
document.body.append(new Element.br()); | |
} catch (e, s) { | |
print('error: $e, stack: $s'); | |
} | |
return json; | |
} |
This file contains 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
<html> | |
<head> | |
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300,300italic,400italic,700,700italic' rel='stylesheet' type='text/css'> | |
<link href='https://fonts.googleapis.com/css?family=Roboto+Condensed:400,300,300italic,400italic,700,700italic' rel='stylesheet' type='text/css'> | |
<script defer type="application/dart" src="canvas_font_texture.dart"></script> | |
<script defer src="packages/browser/dart.js"></script> | |
<style> | |
a { | |
color: #49F; | |
} | |
body { | |
background: #666; | |
} | |
</style> | |
</head> | |
<body> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment