I was watching a video from NDC by Dylan Beattie the other day on The Story of Typography and a particular section stood out to me.
The talk ranges from the initial start of writing symbols to the formation of typography as an art form and the creation of eInk displays. Well worth a watch if you have some time to spare.
The part that was interesting to me was the discussion around early word processors and the ASCII character standard. Early word processors were not able to store the amount of data required to fully render a font, so characters were rendered using a bitmap system.
The ASCII character code for the capital letter A is 65, so when you type the letter A on the keyboard the number 65 is sent to a character generator. Normally stored as a ROM chip, this character generator would return the number 581171396.
A -> 65 -> 581171396
This number doesn't look like anything and in fact has no relation to the letter we started with. The computer, however, reads this number in binary, which looks like this 00100010101000111111100011000100.
If we strip off the last two characters from this list we can then split the string every 5 characters.
00100 01010 10001 11111 10001 10001
This still doesn't look like much, but we can rearrange this into 5x6 grid, which then forms the character we are looking for. In this case, the letter "A" is formed.
00100 ..X..
01010 .X.X.
10001 -> X...X
11111 XXXXX
10001 X...X
10001 X...X
On the early computer screens this series of numbers is kept in a ROM chip and was used to translate the ASCII character code into a bitmap, which was then used to light up LEDs in the interface.
In this article I will look at building the character map as an array and then use this array to translate a string into an image using the bitmap.
Building The Characters
As I didn't want to figure out the number for every character I wanted, the first thing I did was to create an array of the bitmaps I needed. Here is the first letter.
$numbers = [];
$numbers['A'] = [
'00100',
'01010',
'10001',
'11111',
'10001',
'10001',
];
We need an entry for a space, which is pretty simple to do.
// Create the space.
$numbers[' '] = 0;
Now all we need to do is translate the numbers array bitmaps into their equivalent decimal number. To do this we need to loop over them and implode the array into a single binary number (appending the "00" to the end). This is then passed through bindec() to translate the binary value into a decimal number.
// Convert the bitmaps into decimal.
foreach ($numbers as $letter => $number) {
$binary = implode($number) . '00';
$dec = bindec($binary);
$numbers[$letter] = $dec;
}
With the bitmaps translated into numbers we can then convert them into a static array that we can use as the bitmap lookup table.
// Generate the map file.
$string = '';
$string .= '<?php' . PHP_EOL;
$string .= '$map = [' . PHP_EOL;
foreach ($numbers as $letter => $number) {
$string .= "\t'" . $letter . "' => " . $number . ',' . PHP_EOL;
}
$string .= '];' . PHP_EOL;
file_put_contents('map.php', $string);
This will generate a PHP file called map.php, which we can include into any script we need to use. The contents of the map.php file is essentially a $map array with a bunch of numbers.
Here's A, B, and C from that file.
<?php
$map = [
'A' => 581171396,
'B' => 4101839096,
'C' => 1952516280,
If you are interested in seeing the full list of characters then you can do so in this GitHub Gist.
With that in place we can now start rendering characters.
Rendering A Character
To render the character we need to translate the letter back into the binary number. This is done by looking up the letter we want to translate from the $map array and converting that value into a binary value. As we use the sprintf() function to do this we are actually left with a string that we can then manipulate.
require 'map.php';
$letter = "A";
// Get the number that corresponds to the letter.
$number = $map[$letter];
// Convert the number into a string representation of the binary number.
$binary = sprintf("%032b", $number);
The string can then be split into 5 parts, discarding part 6 as this contains two characters that we don't need for the letter.
// Split the binary string into blocks of 5 bits.
$parts = str_split($binary, 5);
// Remove the last part as this will be the trailing bits that aren't rendered.
unset($parts[6]);
The $parts array now contains our bitmap.
Using this information we can then translate this into an image using the following code. Each section of the parts array is looped over and we draw a red box if there is a "1" in that location.
// Initialise the image.
$img = imagecreatetruecolor(70, 80);
// Set the image colours.
$background = imagecolorallocate($img, 0, 0, 0);
$foreground = imagecolorallocate($img, 255, 0, 0);
$pixelSize = 10;
$offsetX = 10;
$startX = 10;
$startY = 10;
// Loop over the parts and construct the letter.
foreach ($parts as $part) {
$part = str_split($part);
$startX = $offsetX;
foreach ($part as $pixel) {
$x1 = $startX;
$y1 = $startY;
$x2 = $startX + $pixelSize;
$y2 = $startY + $pixelSize;
if ($pixel == '1') {
imagefilledrectangle($img, $x1, $y1, $x2, $y2, $foreground);
} else {
imagefilledrectangle($img, $x1, $y1, $x2, $y2, $background);
}
$startX += $pixelSize;
}
$startY += $pixelSize;
}
imagepng($img, 'bitmap.png');
imagedestroy($img);
Running this code with the letter "A" produces the following image.
This is looking good, but let's take this a step further and render a full string instead of just a single letter.
Rendering A String
Now that we have a single character being rendered in an image we can expand this to print out a string. All we need to do is, calculate how big the image will be depending on how many characters are in the string. Then we need to step through each character and print them out in turn.
The following function takes in a string and a map and will render the string as an image. I have included a wrap parameter in order to wrap the generation of the image into different lines. Most of the complexity of this function surrounds the calculation of the size of the image and the offset of the character from the top of the image.
As the key components are working I'll not go through every line of function in detail.
/**
* Render the string into a bitmapped image.
*
* @param string $string
* The string to render.
* @param array $map
* The map of letters to their numeric values.
* @param int $wrap
* The optional value to wrap the text. Defaults to 80 characters.
*/
function renderString(string $string, array $map, int $wrap = 80):void {
$letterSpacing = 10;
$pixelSize = 10;
// Letter size is always pixel size * 5;
$letterSize = $pixelSize * 5;
if (strlen($string) < $wrap) {
// The string is shorter than the wrap value, so work out the size.
$height = $letterSize + ($letterSpacing * 2) + $letterSpacing;
$width = (strlen($string) * $letterSize) + (strlen($string) * $letterSpacing) + $letterSpacing;
}
else {
// Calculate a wrap factor.
$wrapFactor = ceil((strlen($string)) / $wrap);
// The string is longer than the wrap, so calculate the .
$height = (($wrapFactor) * $letterSize) + ($wrapFactor * $letterSpacing * 2) + $letterSpacing;
$width = ($wrap * $letterSize) + ($wrap * $letterSpacing) + $letterSpacing;
}
// Initialise the image.
$img = imagecreatetruecolor($width, $height);
// Set the image colours.
$background = imagecolorallocate($img, 0, 0, 0);
$foreground = imagecolorallocate($img, 255, 0, 0);
// Initialise some variables to keep track of the letter and the offset values.
$countY = 0;
$countX = 0;
$startY = (floor($countY / $wrap) * ($letterSize + $letterSpacing)) + $letterSpacing;
$offsetX = $letterSpacing;
// Sprint the string and loop over each caracter.
foreach (str_split($string) as $letter) {
if (!isset($map[$letter])) {
continue;
}
if ($countX === 0 && $letter == ' ' && $wrap > 1) {
continue;
}
// Get the number that corresponds to the letter.
$number = $map[$letter];
// Convert the number into a string representation of the binary number.
$binary = sprintf("%032b", $number);
// Split the binary string into blocks of 5 bits.
$parts = str_split($binary, 5);
// Remove the last part as this will be the trailing bits that aren't
// rendered.
unset($parts[6]);
// Loop over the parts and construct the letter.
foreach ($parts as $part) {
$part = str_split($part);
$startX = $offsetX;
foreach ($part as $pixel) {
$x1 = $startX;
$y1 = $startY;
$x2 = $startX + $pixelSize;
$y2 = $startY + $pixelSize;
if ($pixel == '1') {
imagefilledrectangle($img, $x1, $y1, $x2, $y2, $foreground);
} else {
imagefilledrectangle($img, $x1, $y1, $x2, $y2, $background);
}
$startX += $pixelSize;
}
$startY += $pixelSize;
}
$countY++;
$countX++;
// Recalculate the start position of Y.
$startY = (floor($countY / $wrap) * ($letterSize + $letterSpacing + $letterSpacing)) + $letterSpacing;
// Calculate the start position of X.
$offsetX += $letterSize + $letterSpacing;
if ($countX >= $wrap) {
// If the count of X is greater than the wrap value then reset X.
$countX = 0;
$offsetX = $letterSpacing;
}
}
imagepng($img, 'bitmap.png');
imagedestroy($img);
}
We can run this function in the following way.
renderString('THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 0123456789', $map, 25);
This produces the following image.
This function works really well.
Generating characters like this is old school. It was a necessity when storage and computing powers were tiny and displays consisted of an array of lights. Since then, there has been many advances in computer power and the ability to generate fonts in a variety of shapes and sizes.
If you want to have a go with this function yourself then you can find the full list of characters in a GitHub gist. Use that list to generate a map.php file and pass this to the above function. Let me know if you found this useful.
Add new comment