This framework aims to make it easier to write font webapps in the style of Erik Demaine and Martin Demaine's Mathematical and Puzzle Fonts/Typefaces.
In particular, the following webapps currently use font-webapp:
- Cube Folding Font (code)
- Impossible Folding Font (code)
- Integer Sequence Font (code)
- Juggling Font (code)
- Orthogonal Fold & Cut Font (code)
- Path Puzzles Font (code)
- Spiral Galaxies Font (code)
- Strip Folding Font (code) [currently just uses furls]
- Sudoku Font (code)
- Tatamibari Font (code)
- Tetris Font (code) [currently just uses furls]
- Voronoi Font (code)
- Yin-Yang Font (code)
These webapps have a common structure:
- Text input field where the user can input a custom message
- Automatic rendering of text within the selected font (via some typeface-specific JavaScript code, typically in SVG via SVG.js)
- Options to select font within the typeface
- Automatic tracking of text and options in URL query (via furls)
The bulk of this framework is for positioning multiple glyphs to make the input text (a simple typography system). The updating triggered by form inputs (and URL sychronization) is handled by furls, but this framework automates the call to furls for you if you want.
You need to include <script>
tags for furls.js
and font-webapp.js
.
For example:
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
This will make several global classes available:
FontWebapp
is an abstract base class, so you shouldn't create one directly.
The following options can be specified for any derived class:
root
(required): DOM element or string query for DOM element (e.g."#output"
) for<div>
that will contain the rendering. This element is automatically sized to fill the remainder of the screen (after whatever appears above the root element).init()
: a function to call when the app is setup. For example, you might create symbols or groups for later rendering. Called withthis
set to theFontWebapp
instance.furls
: an instance ofFurls
if you want to initialize it yourself. By default, one is created and called with.addInputs()
,.syncState()
, and.syncClass()
.Furls
(defaultwindow.Furls
): reference toFurls
class from furls library.shouldRender(changed, state)
: a function that decides whether the text needs to be rerendered from scratch, wherechanged
is an object with a key for each changed input (the argument from the furlsstateChange
event) andstate
is the current state of all inputs (fromfurls.getState()
). The default behavior is "always render". Often it is helpful to define this function as an OR of variouschanged
fields, such as(changed) => changed.text || changed.puzzle
, that require explicit rerendering, while in other cases, implicitly modify the existing rendering using CSS classes from furls class synchronization. Called withthis
set to theFontWebapp
instance.beforeRender(state)
: called before doing a render, withthis
set to theFurls
instance andstate
set to the current state of all inputs (fromfurls.getState()
). When called,this.renderedGlyphs
is still the array of glyphs from the previous render. You can use this to prepare for comingrenderChar
calls, or to clean up a previous render.afterRender(state)
: called before doing a render, withthis
set to theFurls
instance andstate
set to the current state of all inputs (fromfurls.getState()
). The array of the just-rendered glyphs is available asthis.renderedGlyphs
. You can use this to postprocess after the sequence ofrenderChar
calls.afterMaybeRender(state, changed, rendered)
: similar toafterRender
, but called even ifshouldRender
returned false. There are two additional objects: thechanged
object (see above) and a booleanrendered
indicating whether there was an actual render. Useful e.g. to start/stop animations or partially rerender existing glyphs according tostate
, without these toggles requiring a rerender (shouldRender
can still returnfalse
butafterMaybeRender
can still control the rendered glyphs). Note thatchanged
will beundefined
if a render was forced viarender()
.
In addition, a FontWebapp
instance provides the following properties and
methods:
options
: the provided options objectroot
: DOM element referred to byoptions.root
furls
: Furls instancerender()
: Force rerendering of text. Returns the array of rendered glyphs, as available inrenderedGlyphs
.renderedGlyphs
: The array of all rendered glyphs returned byrenderChar
(see below) from the last render.destroy()
: Destroy any DOM/SVG rendered by this webappdownloadFile(filename, content, contentType)
: Cause the user to download a file with the specifiedfilename
,content
(string), andcontentType
. Also available asFontWebapp.downloadFile()
in case you want to download a file without/before creating aFontWebapp
instance.
new FontWebappSVG(options)
creates a new reactive app with SVG font rendering.
For this renderer, you need to include a <script>
tag for svg.js
(from SVG.js).
options
can be an object with any of the generic options above, plus
the following SVG-specific options:
renderChar(char, state, target)
(required): a function that renders a given character into SVG and returns a glyph object with details for layout. Called withthis
set to theFontWebappSVG
instance, withchar
equal to a single-character string,state
equal to the latest result fromfurls.getState()
, andtarget
equal to an SVG.js Group to render into (equal tothis.renderGroup
). The returned glyph can benull
/undefined
to indicate "font doesn't have that character", or an object with the following properties:element
(required): an SVG.js object representing the glyph. If the glyph consists of multiple SVG objects, wrap them in anSVG.G
group and return that. This element shouldn't have a transform applied to it, as the framework will set a translation to position the glyph.width
(required): width of glyph for layout purposes, in SVG units. Shouldn't include "overhangs" that should intrude into adjacent characters.height
(required): height of glyph for layout purposes, in SVG units. Shouldn't include depth that should intrude into adjacent lines.x
(default0
): minimum x coordinate.y
(default0
): minimum y coordinate.shiftY(shift)
: shift theelement
or some portion of it vertically by a specifiedshift
in order to align the bottoms of glyphs. Default behavior is to translate the entireelement
.
renderLine(line, state, target)
(alternative torenderChar
): a function that renders an entireline
of text into SVG. It can return an Array of glyphs, each as you would return fromrenderChar
, in which case they get rendered similarly; use this to e.g. process ligatures or other context-sensitive characters. Or it can return a single glyph for the line, if that is easier.spaceWidth
: horizontal space to leave for a space character, in SVG units. Unless specified, space characters are handled like any other character. Works withrenderChar
but notrenderLine
.blankHeight
: vertical space to leave for a blank line, in SVG units. Use this if you want nonblank lines to be immediately adjacent; if you want to add space between all lines, uselineKern
.charKern
(default0
): horizontal space to add between characters, in SVG units.lineKern
(default0
): vertical space to add between lines, in SVG units. If you just want to add height to blank lines, useblankHeight
.rootSVG
: DOM element or string query for DOM element for existing<svg>
element (within theoptions.root
element) to use for rendering. Use this if you want to predefine<defs>
or<symbol>
s or<style>
in the DOM. Otherwise, a root<svg>
element is created automatically within theoptions.root
element.SVG
(defaultwindow.SVG
): reference toSVG
class from SVG.js.minHeight
(default 100): minimum number of pixels forroot
element's vertical size.
In addition, a FontWebappSVG
instance provides the following properties and
methods:
svg
: an SVG.js instance.renderGroup
: an SVG.js Group where the text is currently rendered (cleared for each render).downloadSVG(filename, content)
: Cause the user to download an SVG file with the specifiedfilename
andcontent
(string), wherecontent
defaults to a raw dump of the current rendered SVG (svg.svg()
).
new FontWebappHTML(options)
creates a new reactive app with HTML font
rendering, where
- Each line is represented by a
<div class="line">
container. - Within each line, a glyph is represented by a
<div class="char">
containing arbitrary content. - Within each line, a space is represented by a
<div class="space">
.
options
can be an object with any of the generic options above, plus
the following SVG-specific options:
renderChar(char, state, target)
(required): a function that renders a given character into HTML and returns an arbitrary glyph object. Called withthis
set to theFontWebappHTML
instance, withchar
equal to a single-character string,state
equal to the latest result fromfurls.getState()
, andtarget
equal to an HTML element to render into. The returned glyph can benull
/undefined
to indicate "font doesn't have that character", or an arbitrary object. Any rendered elements should be added totarget
, e.g., viatarget.appendChild(element)
.linkIdenticalChars(glyphs, char)
: a function to call with all rendered glyphs (an array ofglyphs
as returned byrenderChar
) of the same characterchar
. This can be useful to link together multiple renderings of the same character.charWidth
(default150
): horizontal size of every glyph (<div class="char">
), in px units.spaceWidth
: horizontal space to leave for a space character, in px units. Unless specified, space characters are handled like any other character.charPadding
(default0
): padding space to add around all sides of all characters, in px units.charPaddingLeft
,charPaddingRight
,charPaddingTop
,charPaddingBottom
(default0
): padding space to add around specific sides of all characters, in px units.charKern
(default0
): horizontal space to add between characters, in px units.lineKern
(default0
): vertical space to add between lines, in px units.sizeSlider
: DOM element or string query for DOM element (e.g."#size"
) for<div>
to add a full-width slider to that controls character size. The initial value of the slider ischarWidth
(possibly clipped to be at most the slider width); dragging the slider effectively scales all px sizes above, includingcharWidth
,spaceWidth
,charPadding*
,charKern
, andlineKern
.sizeName
:name
attribute for<input type="range">
made bysizeSlider
. This causes the size to be tracked by Furls and thus preserved in the URL. If you use furls 0.8.0+, the size is automatically rounded to the nearest integer for the URL, and changes from sliding are marked as "minor" to prefer URL updates viareplaceState
.slider
: visual configuration for the createdsizeSlider
, which consists of a "thumb" that moves along a "track", plus a text label above it. All of the following options are in px units unless otherwise specified.text
(default'Character size'
): label above the slidertextSize
(default12
): font size for labeltextGap
(default2
): space between label and trackthumbBorderColor
(default'#000'
): thumb outline colorthumbBorderRadius
(default5
): thumb outline roundingthumbBorderWidth
(default2
): thumb outline thicknessthumbWidth
(default15
): thumb horizontal size, including outlinethumbHeight
(default30
): thumb vertical size, including outlinethumbShadow
(default2
): thumb shadow sizetrackBorderColor
(default'#000'
): track outline colortrackBorderRadius
(default2
): track outline roundingtrackBorderWidth
(default1.4
): track outline thicknesstrackHeight
(default7
): track vertical size, including outlinetrackColor
(default'#bbb'
): track colortrackFocusColor
(default'#c8c8c8'
): track color when focused (clicked)trackShadow
(default1
): track shadow size