qbzf
v1.2.1
Published
quadratic bezier font format for rendering in a fragment shader
Downloads
8
Readme
qbzf
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.
example
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],
})
}
api
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')
var qbzf = QBZF(fontSrc, opts)
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
qbzf.curves
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]
qbzf.unitsPerEm
Font unit measurement from the original font file.
qbzf.measure(opts)
Return only the calculated units
, grid
, offset
and bbox
without writing to a grid texture.
var grid = qbzf.write(opts)
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
var atlas = Atlas(qbzf, opts)
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()
var id = atlas.add(opts)
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.
atlas.remove(id)
Remove a label by its id
.
atlas.clear()
Remove all labels.
var data = atlas.build()
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
.
vec4 curve = read_curve(QBZF qbzf, sampler2D grid_tex, sampler2D curve_tex, float i)
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
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).
install
For the qbzf
command:
npm install -g qbzf
For the library:
npm install qbzf
license
bsd