Pangrams for typographers
For our own debut webservice, let’s provide something for the typographers of the world. Dealing with different fonts in browsers can be a hassle. To know if they are being displayed properly you would have to set up lots of tiresome and redundant HTML. A perfect chance to bring a service to bear.
- pangram
- n, (Greek: pan gramma, “every letter”) a piece of text which uses every letter of the alphabet; pangrams are frequently used to display Roman typefaces; also holoalphabetic sentence. (source Wikipedia)
Extreme JavaScript
Snicker away, scoff and mock. Extreme programming and JavaScript likely seem mutually exclusive but XP is a philosophy, not a rule set or a FIFO stack of elitist programming languages.
An excellent online text for designing software that will match clients’ needs and expectations is Extreme Perl. One of the excellent recommendations therein is the use of stories and story cards. This same technique has long enjoyed popularity in another industry where cargo containers of money and resources need to be managed tightly: film.
A simple graphic representation, especially in the client’s pen, goes miles to communicating the things that can be elusive, or worse, debatable, when expressed verbally. It’s the 1,000 word shorthand. Everyone involved can look at it and say yes without wondering what they really just agreed to.
Our requirements list is based on typical typographical display needs. Pangrams are most often used to showcase typefaces (the correct term for what has come to be known instead as fonts). Here’s our simplistic story card, a display sample for American Typewriter Condensed.
That’s what we’ll try to emulate. With visual requirements in front of us, verbal requirements are easy to write.
Requirements
- Provide a pangram service via remotely called JavaScript.
- For variety, provide for different pangrams on request or randomly select one if none is specified.
- Allow the font family group to be selected by the user from standards: book, sans, ornate, mono.
- Allow the specific font to be overridden on request.
- Provide for cascading font sizes.
A bad habit of many, if not most, hackers in just about every language from JS to C++ is starting to code before you’re sure what you really want the end result to be.
The two best ways to force—did we say force? We meant gently guide—code design and creation to conform to the goals of a project, namely satisfying the user and client and keeping the phones quiet at the help desk, are writing tests and doing UI specifications before a byte of code is nibbled.
Our applications are a bit light, and difficult besides, for software QA and testing. The UI, therefore, is where we should begin. If we know the data we’re dealing with and how users expect to provide it, the battle is mostly over.
We are using the idiom. One local script to set configuration and then we’ll call the remote script. First, we check what the local configuration might look like with all the possible settings we intend to support.
While mock ups and story cards of the final version are valuable, the real trick is user interaction. How will a thing be used, and maybe the most overlooked but really obvious concern: how will a thing be misused. It will be.
UI: a sample pangram.js call
<script type="text/javascript"> <!-- pangram = 'sphinx'; family = 'book'; font = 'bookman old style'; sizes = [ 8, 10, 12, 14 ]; showSize = 1; showFace = 1; //--> </script> <script type="text/javascript" src="http://elektrum.org/js/pangram.js"></script>
With both our requirements and our interface coming together there has already been scope creep. We didn’t intend to support echoing additional information like showing the size and name with the samples but the tester sneaked it by the project manager after having a chat with the client.
For the love of a unified workplace, don’t let this happen. Have the client revise the story card and send the tester and the PM on a team building retreat.
The revised story card
The supported control variables
pangram
- One of a preset list: fox, sphinx, french, dutch, and abc. We named them by recognizable keywords. Supporting more is possible, of course. If no pangram is specified, we’ll select one at random.
family
- One of the standard groups: book, sans, ornate, mono. We leave out Gothic, black, and a few other edge cases which are barely known, let alone supported, outside of the typographical world.
font
- This is an override. It will try to put the indicated font first in the list before the family values.
sizes
- An array of point sizes. We could also allow other measures supported
by CSS but points are standard and the
revisions at this point will cost the client extra. If
sizes
is omitted or misused, the pangram will write out just once at 12 points. showSize
- Boolean. If there is a true value, show the sizes like the story card.
showFace
- Boolean. If true, show the list of faces we’re trying to display; it’s still up to browser whether they’re available for display.
The typeface lists are an optimistic implementation in the interest of clarity and brevity. We could use hundreds of names quite easily in search of, say, a Gothic or a black face font in an attempt to find one in every browser on every OS.
The typefaces
var Family = new Object({book:['caslon','garamond','serif'] ,ornate:['zapfino','fantasy','cursive'] ,sans:['optima','frutiger','sans-serif'] ,mono:['monaco','courier','mono-space'] });
Now we have access to an array of CSS face names.
Family['book'].join(', ')
,
for example, gets us: 'caslon', 'garamond', 'arrus', 'serif'
.
The pangrams
var Pangram = new Object(); Pangram['fox'] = 'The quick brown fox jumps over the lazy dog.'; Pangram['abc'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'; Pangram['abc123'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMm,.!?0123456789'; Pangram['sphinx'] = 'Sphinx of black quartz judge my vow.'; Pangram['french'] = 'Portez ce vieux bon whiskys au juge blond qui fume.'; Pangram['dutch'] = 'Sexy qua lijf, doch bang voor het zwempak.';
A Google search for “pangram” will turn up hundreds more. You could even create originals for your own use.
Data structures are in place. Time to write the programmatic parts of
the code in our server side file pangram.js
. Revisit the UI.
Client call to pangram.js
<script type="text/javascript"> <!-- pangram = 'sphinx'; family = 'book'; font = 'bookman old style'; sizes = [ 8, 10, 12, 14 ]; showSize = 1; showFace = 1; //--> </script> <script type="text/javascript" src="http://elektrum.org/js/pangram.js"></script>
Before we start using the data handed off we have to assume the user
will do it wrong. So we need to use the technique we looked at in Remotely called JavaScript with local configuration to get at variables through the
window
object. That way if they’re missing, it’s just a
missing property instead of a fatal error.
We’ll just bring everything in under the same name to avoid confusion.
Filter argument variables safely
var pangram = window.pangram; var font = window.font; var sizes = window.sizes; // pseudo-boolean with ternary operator var showSize = window.showSize ? 1 : 0; // same idea as showSize but more "correct" var showFace = new Boolean( window.showFace );
Now that our data is safe to look at, the first interaction with the
client’s call is to check what pangram, if any, was requested. We try
to look it up in the Pangram
object. If it returns a
pangram, great, we use it. If not, we select a pangram from
Pangram
at random. We’ll need a function,
chooseRandom()
, to do it.
Which pangram will we display
var display = Pangram[pangram] || chooseRandom();
display
contains the text of the pangram we’ll display.
If the user failed to specify a pangram
in the
configuration script, or if he specified a non-existent one,
chooseRandom()
kicks in to select.
As an object, or an associative array, with named keys,
Pangram
cannot be accessed by numeric index like
Pangram[1]
, so it’s a bad candidate for random access. We
build up a simple intermediary array of the key names to make it
simpler.
We could easily hard code an array matching Pangram
’s
keys but it would be a mistake. They might get out of sync or have
accidental spelling differences, etc. Never type something twice
that’s available programmatically. You’ll make mistakes. Compilers
generally don’t.
chooseRandom()
function chooseRandom () { var Name = new Array(); for ( var i in Pangram ) { Name.push(i); } var randIndex = Math.floor( Math.random() * Name.length ); var name = Name[randIndex]; return Pangram[name]; }
Next, check the typeface variables and select a default if no valid family was provided.
Family choice and size validation or default
var family = Family[family] || Family['book']; if ( font ) { // we're writing this to the screen, must be clean font = font.replace(/</g, '<'); font = font.replace(/>/g, '>'); family.unshift( font ); } if (!( sizes && sizes.constructor == Array )) { sizes = new Array(10,12,18,24); }
All the logic is done. Time to work on the display. We set up some CSS
information which we’ll inline with style
attributes in
our HTML. We want a bounding box so we can control things like the
background color and text color. That way if the service ends up being
used on a yellow page with pink text at, least one part of the page
will look good.
We also want to define the various elements from the story card. The
pangrams themselves. A headline of the fonts which are being used, or
being tried; remember we are at the mercy of the browser as to whether
a given font will render or not. We let the head
style
handle our capitalization too. It’s a bit sad that this simple
functionality is native to CSS but not JavaScript (we develop a JavaScript
version as a prototype of the String()
object in Configurable greeking).
We need the mini point size indicators along the left. And lastly we add a little branding caption.
A little foresight is needed for these to look good. Font sizes over
30 point are likely to make a pangram wider than any given browser
window. We account for that by hiding the over flow of the
bound
box.
Style information
var bound = 'border:1px solid black;overflow:hidden;' + 'background-color:white;' var p_style = 'padding:3px 3px 3px 5px;position:relative;' + 'white-space:pre;'; var head = 'position:relative;text-transform:capitalize;' + 'text-align:center;font-weight:bold;color:#fff;' + 'background-color:black;font-size:14pt;padding:0 4px 0 3px;'; var pt = 'position:relative;color:#99a;float:left;' + 'font-size:9pt;width:3em;text-align:right;clear:left;' + 'vertical-align:text-bottom;padding-right:4px;margin-left:-1ex;'; var brand_caption = 'font-size:x-small;text-align:right;' + 'margin-top:1px;padding:2px;font-style:italic;'
Branding
// branding stamp, feel free to change to your own site var service_from = 'http://feather.elektrum.org/';
We’re ready to start writing output by opening the bounding box.
document.write('<div style="' + bound + '">');
We write out with the headline if showFace
is true.
Show the font names upon request
if ( showFace ) { document.write('<div style="' + head + '">' + family.join(', ') + '</div>'); }
Now write out the pangram at as many sizes as were specified or if none was, use the default array of sizes we set up.
Write out the pangrams at various sizes
for ( var i in sizes ) { var style = p_style + 'font-family:' + family.join(',') + ';'; style += 'font-size:' + sizes[i] + 'pt;'; document.write('<div style="' + style + '">'); if ( showSize ) { document.write('<div style="' + pt + '">' + sizes[i] + 'pt</div>'); } document.write( display + "</div>\n"); }
That’s the end, close our bounding box for well-formed HTML. This is not a minor concern. If your HTML is not perfect, it might completely trash the layout of the page its imported into. Your follow up service might not be greeted warmly if you’ve generated a reputation for screwing up websites with your first.
Well-formedness counts
document.write('</div>');
Putting it all together
The client call again
<script type="text/javascript"> <!-- pangram = 'sphinx'; family = 'book'; font = 'bookman old style'; sizes = [ 8, 10, 12, 14 ]; showSize = 1; showFace = 1; //--> </script> <script type="text/javascript" src="http://elektrum.org/js/pangram.js"></script>
The pangram service
pangram.js compleat
var bound = 'border:1px solid black;overflow:hidden;' + 'background-color:white;' var p_style = 'padding:3px 3px 3px 5px;position:relative;' + 'white-space:pre;'; var head = 'position:relative;text-transform:capitalize;' + 'text-align:center;font-weight:bold;color:#fff;' + 'background-color:black;font-size:14pt;padding:0 4px 0 3px;'; var pt = 'position:relative;color:#99a;float:left;' + 'font-size:9pt;width:3em;text-align:right;clear:left;' + 'vertical-align:text-bottom;padding-right:4px;margin-left:-1ex;'; var brand_caption = 'font-size:x-small;text-align:right;' + 'margin-top:1px;padding:2px;font-style:italic;' var Family = new Object({book:['caslon','garamond','serif'] ,ornate:['zapfino','fantasy','cursive'] ,sans:['optima','frutiger','sans-serif'] ,mono:['monaco','courier','mono-space'] }); var Pangram = new Object(); Pangram['fox'] = 'The quick brown fox jumps over the lazy dog.'; Pangram['abc'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'; Pangram['abc123'] = 'AaBbCcDdEeFfGgHhIiJjKkLlMm,.!?0123456789'; Pangram['sphinx'] = 'Sphinx of black quartz judge my vow.'; Pangram['french'] = 'Portez ce vieux bon whiskys au juge blond qui fume.'; Pangram['dutch'] = 'Sexy qua lijf, doch bang voor het zwempak.'; // branding stamp, feel free to change to your own site var service_from = 'http://feather.elektrum.org/'; var pangram = window.pangram; var font = window.font; var sizes = window.sizes; // pseudo-boolean with ternary operator var showSize = window.showSize ? 1 : 0; // same idea as showSize but more "correct" var showFace = new Boolean( window.showFace ); var display = Pangram[pangram] || chooseRandom(); document.write('<div style="' + bound + '">'); var family = Family[family] || Family['book']; if ( font ) { // we're writing this to the screen, must be clean font = font.replace(/</g, '<'); font = font.replace(/>/g, '>'); family.unshift( font ); } if (!( sizes && sizes.constructor == Array )) { sizes = new Array(10,12,18,24); } if ( showFace ) { document.write('<div style="' + head + '">' + family.join(', ') + '</div>'); } for ( var i in sizes ) { var style = p_style + 'font-family:' + family.join(',') + ';'; style += 'font-size:' + sizes[i] + 'pt;'; document.write('<div style="' + style + '">'); if ( showSize ) { document.write('<div style="' + pt + '">' + sizes[i] + 'pt</div>'); } document.write( display + "</div>\n"); } document.write('<div style="' + brand_caption + '">pangram service from ' + '<b><a href="'+ service_from + '">' + service_from + '</a></b></div>'); document.write('</div>'); function chooseRandom () { var Name = new Array(); for ( var i in Pangram ) { Name.push(i); } var randIndex = Math.floor( Math.random() * Name.length ); var name = Name[randIndex]; return Pangram[name]; }
Usage
To see various ways the service can be used, turn the page to Pangrams in action. Only after we see it in use, which is the core of UAT, will we be able to evaluate the project.