More Haskell Diagrams: Wrapping Text
Working with text, especially wrapping it, can be tricky when generating images with Haskell's diagrams library. In this blog post, we will write a literate Haskell program to generate an image with text that fits in a box and wraps if we want so.
Problem
Since I am getting more invested in diagrams, I want to be equipped with additional superpowers to deal with text.
Working with text in diagrams is not as straightforward as I thought. Indeed, the primary function to work with text is text
and its documentation says:
Note that it takes up no space, as text size information is not available.
Bummer! This means we cannot calculate the size of the text to wrap it in a limited area.
Solution
It seems that there is a nice library to work with text: SVGFonts. It advertised itself as "Native font support for the diagrams framework". Also do not let the name fool you:
Note that this package can be used with any diagrams backend, not just the SVG backend. The SVG-font format is easy to parse and was therefore chosen for a font library completely written in Haskell.
Program
We will use the 4 Haskell packages for this Literate Haskell program: diagrams, diagrams-cairo, SVGFonts and markdown-unlit.
Let's import our modules of interest:
import Diagrams.Backend.Cairo
import Diagrams.Prelude
import qualified Graphics.SVGFonts as F
import qualified Graphics.SVGFonts.Wrap as F.Wrap
import Data.Maybe (fromMaybe)
import System.Environment (getArgs)
We will render a few diagrams with our program. Let's implement our entry point what will render these diagrams:
main :: IO ()
main = do
dir <- head <$> getArgs
render dir "diagram1.png" diagram1
render dir "diagram2.png" diagram2
render dir "diagram3.png" diagram3
render dir "diagram4.png" diagram4
where
render dpath fname = renderCairo (dpath <> "/" <> fname) (mkSizeSpec2D (Just 800) Nothing)
This is how we will run our blog post:
runhaskell \
-pgmLmarkdown-unlit \
content/posts/2024-08-13_haskell-diagrams-text.lhs \
static/assets/media/posts/haskell-diagrams-text
We need an inspiring text to work with:
nice :: String
nice = "Any application that can be written in JavaScript, will eventually be written in JavaScript."
OK, let's put it out there, on a rectable of size 16x9
:
diagram1 :: Diagram B
diagram1 =
(text nice) `atop` (rect 16 9 # bg white)
Let's fit it in a box of size 16x9
:
diagram2 :: Diagram B
diagram2 =
(text nice # fontSizeL 0.25) `atop` (rect 16 9 # bg white)
We can not afford playing with the font size everytime. How about giving a shot to the fit_width
function from SVGFonts?
diagram3 :: Diagram B
diagram3 =
(nice # F.svgText def # F.fit_width 16 # F.set_envelope # fc black # lw none # center) `atop` (rect 16 9 # bg white)
Yes, there is a lot more ceremony. But knowing the canvas' width is enough for us.
Ok, let's wrap it. This is going to be tricky, if not too difficult:
diagram4 :: Diagram B
diagram4 =
bgFrame 0 white $ vsep 0.2 (fmap putTxt (fromMaybe ["what happened?"] mLines))
where
mLines = F.Wrap.wrapText def 1 [(F.Wrap.splitAtSpaces, (14 :: Double, 16))] nice
putTxt = lw none . fc black . F.set_envelope . F.svgText def
Wrap-Up
SVGFonts is quite interesting. For my purpose here, i.e. wrapping text, the magic function is wrapText. It takes text options, height, split strategies and a text. I am having difficulties in understanding the split strategies. I will need to dig deeper. But, if the split boundaries are wide enough, it will wrap the text. Since I am not re-implementing LaTeX, but generating completely unnecessary images, I am confident that I can work with this SVGFonts library.
Subscribe to my newsletter
Read articles from Vehbi Sinan Tunalioglu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Vehbi Sinan Tunalioglu
Vehbi Sinan Tunalioglu
My name is Sinan. I am a computer programmer and a life-style entrepreneur. You can check my LinkedIn and GitHub profile pages for more information, and send an email to vst@vsthost.com to contact me. I am re-publishing my technical blog posts on hashnode. My website is available on thenegation.com, and its source code is available on GitHub.