This is a comparative study, similar to a game I made in that I implement the same behavior in two different languages for practice and comparison.
Excluding the data structure for the font, instead present in the GitHub repo, I’ve tried to emphasize similarities:
const charHeight = 8;
const pixelSize = 3;
const lineHeight = charHeight * pixelSize + 10;
const magenta = 'magenta';
const black = 'black';
const widthBetweenCharacters = pixelSize;
const filledPoint = 'O';
const canvas = document.querySelector('#canvas');
const context = canvas.getContext('2d');
const setPixel = (x, y, strokeColor) => {
context.fillStyle = strokeColor;
context.fillRect(x, y, pixelSize, pixelSize);
};
const setPixel = require("./draw.js");
const trimLn = (ln) => ln.trim();
const isPointFilled = (pnt) => pnt === filledPoint;
const drawPntOfCh = (x, y, color, chIndex) => (pixel, pixelX) => {
if (isPointFilled(pixel)) {
setPixel(x + pixelX * pixelSize, y + chIndex * pixelSize, color);
}
};
const drawPntsOfCh = (x, y, color) => (ch, chIndex) =>
ch.split("").forEach(drawPntOfCh(x, y, color, chIndex));
const getCharWidth = (ch) => ch.split("\n")[2].trim().length;
const sumLnWidth = (acc, ch) =>
acc + getCharWidth(font[ch]) * pixelSize + widthBetweenCharacters;
const getRelativeX = (ln) => ln.split("").reduce(sumLnWidth, 0);
const drawCharacter = (ln, x, y, color) => (ch, chX) =>
font[ch]
.split("\n")
.map(trimLn)
.forEach(
drawPntsOfCh(
x + getRelativeX(ln.slice(0, chX)),
y,
ch in font ? color : magenta
)
);
const drawLine = (x, y, color) => (ln, lnY = 0) =>
ln.trim().length > 0 &&
ln.split("").forEach(drawCharacter(ln, x, y + lnY * lineHeight, color));
const drawText = (text, color, x, y) =>
text.split("\n").length === 1
? drawLine(x, y, color)(text.toUpperCase())
: text.toUpperCase().split("\n").forEach(drawLine(x, y, color));
drawText(example, black, 50, 48);
import Graphics.Gloss
import qualified Data.Map as Map
import Data.Maybe
import Font -- data structure for the font
import Graphics.Gloss.Interface.Pure.Game
import Text
type Position = (Int, Int)
type ChFontData = [(Int, [(Int, Char)])]
window ∷ Display
window = InWindow "Pixly Typography" (1024, 768) (0, 0)
main ∷ IO ()
main = display window white picture
where
picture = pictures $ printText (0, 0) "Hello, Pixly!"
printText ∷ Position → [Char] → [Picture]
printText pos text =
concatMap (\(i, ch) → drawChar ch pos (take i text)) $
zip [0 .. length text] text
pixelSize ∷ Int
pixelSize = 3
background ∷ Color
background = white
forgroundColor ∷ Color
forgroundColor = black
widthBetweenChs ∷ Int
widthBetweenChs = pixelSize
formatLine ∷ [a] → [(Int, a)]
formatLine lns = zip [0 .. length lns] lns
formatCh ∷ [[Char]] → ChFontData
formatCh = formatLine . map formatLine
getLinesOfCh ∷ Char → ChFontData
getLinesOfCh =
formatCh .
reverse . filter (/= "") . (lines . fromMaybe []) . flip Map.lookup font
getCharWidth ∷ Char → Int
getCharWidth = length . snd . head . getLinesOfCh
isPntFull ∷ Char → Bool
isPntFull ch = ch == 'O'
setPixel ∷ Color → (Float, Float) → Picture
setPixel rectColor (x, y) =
translate x y $
color rectColor $
rectangleSolid (fromIntegral pixelSize) (fromIntegral pixelSize)
drawChar ∷ Char → Position → [Char] → [Picture]
drawChar ch pos text =
concatMap (\fontData → drawPtnLn fontData pos text) $ getLinesOfCh ch
sumLns ∷ Int → Char → Int
sumLns acc x = acc + getCharWidth x * pixelSize + widthBetweenChs
lineLen ∷ [Char] → Int
lineLen = foldl sumLns 0
drawPtnLn ∷ (Int, [(Int, Char)]) → Position → [Char] → [Picture]
drawPtnLn (y2, ln) pos textSoFar =
map (\(x2, ch) → drawLnPart (x2, y2) ch pos (lineLen textSoFar)) ln
drawLnPart ∷ Position → Char → Position → Int → Picture
drawLnPart (x2, y2) ch (x1, y1) currLen =
drawPtn
(x1 * pixelSize + currLen + x2 * pixelSize, y1 * pixelSize + y2 * pixelSize)
ch
drawPtn ∷ Position → Char → Picture
drawPtn (x, y) ch =
setPixel
(if isPntFull ch
then forgroundColor
else background)
( fromIntegral x - ((1024 / 2) - (fromIntegral pixelSize / 2))
, fromIntegral y - ((768 / 2) - (fromIntegral pixelSize / 2)))