quadratic bezier font format designed to render in a fragment shader
status: somewhat usable but many numeric stability issues
This package comes with 3 sections: a javascript library, glsl shader code, and a command-line tool.
Use the command-line tool to generate font data from a ttf file to pass into the javascript api.
The javascript api generates textures that can be read by the shader code.
To run this example: budo ok.js -- -t glslify
var glsl = require('glslify')
var regl = require('regl')()
var QBZF = require('qbzf')
var draw = null
window.addEventListener('resize', frame)
var data = { curves: null, grid: null }
fromData(Uint8Array.from([ // hard-coded data for o and k glyphs
113,98,122,102,49,10,128,16,24,107,196,18,244,2,244,2,0,184,18,168,24,220,8,0,
2,168,24,216,8,0,2,173,14,224,25,198,7,132,11,0,233,27,151,8,132,29,167,9,157,
11,0,211,26,198,8,2,197,8,211,8,0,52,111,202,19,226,1,226,1,57,234,17,246,17,
178,29,190,15,2,0,243,6,0,211,3,231,1,255,3,229,1,171,1,247,4,2,0,4,145,3,170,
1,249,4,136,4,229,1,214,3,229,1,2,0,246,6,0,210,3,232,1,136,4,232,1,172,1,248,
4,2,0,4,142,3,171,1,246,4,255,3,234,1,209,3,234,1,0,184,2,2,0,164,11,0,242,5,
183,2,186,6,183,2,146,2,223,6,2,0,4,165,4,145,2,223,6,177,6,183,2,241,5,183,2,
2,0,161,11,0,243,5,184,2,171,6,186,2,143,2,224,6,2,0,4,168,4,144,2,224,6,186,
6,184,2,244,5,184,2
]))
// or load from a file:
// fetch('djvsans').then(r => r.arrayBuffer()).then(r => fromData(new Uint8Array(r)))
function fromData(buf) {
var qbzf = new QBZF(buf)
data.curves = qbzf.curves
data.curves.texture = regl.texture(data.curves)
data.grid = qbzf.write({
text: 'ok',
strokeWidth: 10, // in units
})
data.grid.texture = regl.texture(data.grid)
draw = build(data.grid.n)
frame()
}
function frame() {
regl.poll()
regl.clear({ color: [0,0,0,1], depth: true })
if (data) draw(data)
}
function build(n) {
return regl({
frag: glsl`
precision highp float;
#pragma glslify: QBZF = require('qbzf/h')
#pragma glslify: create_qbzf = require('qbzf/create')
#pragma glslify: read_curve = require('qbzf/read')
varying vec2 vpos;
uniform sampler2D curveTex, gridTex;
uniform vec2 curveSize, gridUnits, gridSize, gridDim;
uniform float gridN, strokeWidth;
void main() {
vec2 uv = vpos*0.5+0.5;
QBZF qbzf = create_qbzf(
uv, gridN, gridSize, gridUnits, gridDim,
gridTex, curveSize
);
float ldist = 1e30;
for (int i = 0; i < ${n}; i++) {
vec4 curve = read_curve(qbzf, gridTex, curveTex, float(i));
if (curve.x < 0.5) break;
qbzf.count += curve.y;
ldist = min(ldist,length(curve.zw));
}
float a = 5.0; // aliasing width in font units
float outline = 1.0-smoothstep(strokeWidth-a,strokeWidth+a,ldist);
vec3 fill = vec3(0,0,0);
vec3 stroke = vec3(1,1,1);
vec3 bg = vec3(0.5,0,1);
vec3 c = mix(
mix(bg,stroke,outline),
mix(stroke,fill,smoothstep(ldist,0.0,a)),
mod(qbzf.count,2.0)
);
gl_FragColor = vec4(c,1);
}
`,
vert: `
precision highp float;
attribute vec2 position;
varying vec2 vpos;
void main() {
vpos = position;
gl_Position = vec4(position,0,1);
}
`,
uniforms: {
curveTex: regl.prop('curves.texture'),
curveSize: regl.prop('curves.size'),
gridTex: regl.prop('grid.texture'),
gridUnits: regl.prop('grid.units'),
gridSize: regl.prop('grid.grid'),
gridDim: regl.prop('grid.dimension'),
gridN: n,
strokeWidth: regl.prop('grid.strokeWidth'),
},
attributes: { position: [-4,-4,-4,+4,+4,+0] },
elements: [0,1,2],
})
}
var QBZF = require('qbzf')
var Atlas = require('qbzf/atlas')
#pragma glslify: QBZF = require('qbzf/qbzf.h')
#pragma glslify: create_qbzf = require('qbzf/create')
#pragma glslify: read_curve = require('qbzf/read')
Create a new qbzf
instance from a Uint8Array
of fontSrc
(created by the qbzf command) and:
opts.density
- default[200,200]
opts.epsilon
- default1e-8
Curve texture data with:
qbzf.curves.data
- Uint8Array of curve texture dataqbzf.curves.width
- width in pixels of the curve textureqbzf.curves.height
- height in pixels of the curve textureqbzf.curves.size
- 2-element array of[width,height]
Font unit measurement from the original font file.
Return only the calculated units
, grid
, offset
and bbox
without writing to a grid texture.
Create a grid texture from opts
:
opts.text
- string to stamp glyphs into output grid textureopts.offset
-[x,y]
translation in unitsopts.padding
- padding in units as[left,bottom,right,top]
opts.strokeWidth
- width of stroke in units. default 0opts.data
-Uint8Array
to write data into. created if not provided.
The resulting grid
object has:
grid.data
- uint8array of grid texture data to read fromgrid.width
- width of grid texture in pixelsgrid.height
- height of grid texture in pixelsgrid.dimension
- 2-element array of[grid.width,grid.height]
grid.units
- size of font data in font unitsgrid.grid
- dimensions of logical gridgrid.n
- number of curves stored in each cell
Create an atlas
to manage multiple labels at once using one texture per each n
and so one draw
call per n
along with corresponding label geometry.
opts.attributes
- array of strings to set additional attributes inatlas.add()
which will also appear alongside other records inatlas.build()
Add a text label with all the arguments to qbzf.write()
plus:
opts.height
- height of 1 em in output coordinate scaleopts.location
- translation in output coordinate scale (default:[0,0]
)opts.id
- set the id directly. must be unique
Returns the id provided or an assigned unique id
if none was provided.
Remove a label by its id
.
Remove all labels.
data
is an object mapping n
keys to a grid g
with:
g.curves
- reference toqbzf.curves
g.positions
- vec2 attribute for coordinates in output coordinate scaleg.uv
- vec2 attribute for each label's coordinates (0 to 1 in x and y)g.cells
- element indexes for triangle geometryg.offsets
- float attribute for pixel offsetg.units
- vec2 attribute for size of label in font unitsg.size
- vec2 attribute for grid dimensions in number of cellsg.id
- id assigned to label (float unless set to something else)
These are merged with everything set in opts.attributes
.
QBZF qbzf = create(vec2 uv, float n, vec2 size, vec2 units, vec3 dim, sampler2D grid_tex, vec2 curve_size)
Create a glsl QBZF
struct from the given parameters:
vec2 uv
- texture coordinates for lookup (0 to 1 in each dimension)float n
- number of curves per cellvec2 size
- dimensions of logical gridvec3 dim
- dimensions of grid in pixels and pixel offset to start readingsampler2D grid_tex
- grid texture datavec2 curve_size
- dimensions of curve texture in pixels
The qbzf.count
is initialized with the number of crossings for the right support for the given
uv
(either 0 or 1).
QBZF qbzf = create(vec2 uv, float n, vec2 size, vec2 units, vec2 dim, sampler2D grid_tex, vec2 curve_size)
Alias for create(uv, n, size, units, vec3(dim,0), grid_tex, curve_size)
.
This sets the offset to 0
.
Read curve i
referenced the current cell in grid_tex
with a lookup into curve_tex
and
calculate:
curve.x
- index of the curve plus one.0.0
if there is no curve for this index.curve.y
- number of raycast crossingscurve.z
- distance from the current point to the curve in x componentcurve.w
- distance from the current point to the curve in y component
Sum every curve.y
with qbzf.count
to get the total number of crossings.
If the number of corssings is even, you're outside the polygon and if odd you're inside.
usage: qbzf (INFILE)
-i INFILE Read from this font file.
-o OUTFILE Write bezier text output to this file. Default: "-" (stdout)
-l --list List all codes from the input ttf font file.
-m --meta List unitsPerEm and glyph unicodes as ranges for each INFILE,
which can be a ttf or qbzf font file.
-u --unicode Only include glyphs with these unicode values.
-c --char Only include glyphs with these character values.
-x --index Only include glyphs with these indexes.
-h --help Show this message.
-v --version Print software version (1.1.0).
Unicode and index lists can be specified with multiple flags (-u 97 -u 98 -u 99)
or with comma-separated lists (-u 97,98,99).
Char lists can be specified with multiple flags or comma-separated lists.
For a literal comma, use "-c,".
Unicodes can be specified in hexadecimal with a leading "u": (-u u0061).
For the qbzf
command:
npm install -g qbzf
For the library:
npm install qbzf
bsd