Haskell Diagrams: Why does text not have length or width? - haskell

I would like to create a text box that I can set to have a particular width and height. However, the text box that I create appears to not have a width and height in the first place. E.g. the following code:
main = do
putStrLn $ show $ width $ myText
putStrLn $ show $ height $ myText
mainWith myText
myText :: Diagram B
myText = text "here" # lw 1 # fontSizeL 0.2 # fc grey # scaleX 1
results in a blank diagram (with no text), and prints "0.0" as the width and height of the text box:
ghc --make Main.hs && ./Main -o circle.svg -w 400
[1 of 1] Compiling Main ( Main.hs, Main.o )
Linking Main ...
0.0
0.0
In order to get the text to appear it seem that I need to place it on top of something else. E.g. the following code:
main = do
putStrLn $ show $ width $ myDiagram
putStrLn $ show $ height $ myDiagram
mainWith myDiagram
myDiagram :: Diagram B
myDiagram = myText <> myBackground
myText :: Diagram B
myText = text "here" # lw 1 # fontSizeL 0.2 # fc grey # scaleX 1
myBackground :: Diagram B
myBackground = rect 1 1 # fc black
produces the grey text within a black background as expected.
Why does the text box not have a size?
How can I set the length and width of a text box to a certain value?

Why does the text box not have a size?
Because text handling is not only highly dependent on the backend used to render the diagrams (cf. the User's Guide) but also potentially dependent on how fonts are configured in the system used to run the program.
How can I set the length and width of a text box to a certain value?
Use text handling functions provided by your chosen backend, if they exist. For instance, diagrams-cairo provides functions that use Pango to render text on its own, though with the significant annoyance of them being IO functions, as they need to query font information from the system.
Use SVGFonts to stroke the text independently of the backend. There are a few inconveniences with this approach as well (limited choice of fonts unless you follow the instructions in the documentation to convert your chosen font to the SVGFonts format yourself; fonts with large sets of characters might lead to a small but noticeable delay when they are first used by your program, as they have to go through an initial processing), but at least you don't have to bother with IO or backend-specific quirks.

Related

Is there a container type in Haskell Diagrams?

I am trying to map out some data in Diagrams. I am completely new to Diagrams, but I essentially want to have a rectangle, that grows when I put other diagrams atop of it. I have scoured the docs, but havn't found anything.
Does there exist such a shape, or a way to know how much to scaleY?
If all you need is a rectangle surrounding some diagram(s), boundingRect might be enough. It could look like this (note the pad is entirely optional):
-- Arbitrary example, taken from the manual.
contents :: Diagram B
contents = c ||| hrule 1 ||| c
where
c = circle 1 <> vrule 2
-- Adding a bounding rectangle around it, with a little padding.
example :: Diagram B
example = contents <> bounds
where
bounds = boundingRect (contents # pad 1.1)
# lc red

How do find the width of a Text Picture in gloss?

In the Haskell Gloss library, one draws text with the Text constructor of the Picture type. But how, then does one find the width (and height) of such a picture?
Here's how text is rendered in Gloss:
Text str
-> do
GL.blend $= GL.Disabled
GL.preservingMatrix $ GLUT.renderString GLUT.Roman str
GL.blend $= GL.Enabled
The important point here is that it calls renderString. Looking at the documentation for renderString, we immediately see two other useful functions: stringWidth and fontHeight. As such, you can get your width and height like this:
import Graphics.UI.GLUT.Fonts
do
width <- stringWidth Roman str
height <- fontHeight Roman

How can I obtain a Dynamic Bool that corresponds to the mouse being over a certain element?

Suppose I have a button and a square, like this:
main = mainWidget $ do
x <- button "Change color."
y <- toggle False x
z <- mapDyn style y
elDynAttr "div" z blank
style :: Bool -> Map String String
style b | b = "style" =: "height: 10ex; width: 10ex; background-color: #f00;"
| otherwise = "style" =: "height: 10ex; width: 10ex; background-color: #900;"
The colour of the square will alternate between bright and dark red when the button is pressed.
I want to replace the button with a green rectangle in such a way that, when mouse pointer is over it, the red square is bright, otherwise dark. For bonus points, I would like the green rectangle to be a div tabindex=0 and to make sure the red square is bright when it has focus, but, once I understand the general approach, I will likely be able to figure that out by myself.
My guess is that there is some API that allows adding event handlers to el, but I did not find one so far. And maybe there is a different approch altogether, that I could not know?
The stuff you need is all found in Reflex.Dom.Widget.Basic. If you create a widget with a function that has a prime in its name, such as el', you can get a hold of an object of type El, which is a member of HasDomEvent. domEvent, the only method of this class, lets you extract an event stream corresponding to one of a selection of event names. Mixing the events together and converting them to an appropriate Dynamic is then done with some usual Reflex tooling found in Reflex.Class and Reflex.Dynamic. You do need to familiarize yourself with these interfaces, but there is a helpful quick reference.
(Make sure to use the documentation matching the version of Reflex you build against, as there are significant changes between versions.)
One way to encode your case is as follows:
main = mainWidget $ do
y <- greenBox
z <- mapDyn style y
elDynAttr "div" z blank
style :: Bool -> Map String String
style ... -- Abridged for brevity.
greenBox :: MonadWidget t m => m (Dynamic t Bool)
greenBox = do
(e, _) <- elAttr' "div" ("style" =: "height: 10ex; width: 10ex; background-color: #0c0;") blank
let
eEnter = domEvent Mouseenter e
eLeave = domEvent Mouseleave e
holdDyn False . leftmost $ [True <$ eEnter, False <$ eLeave]
What we do here is create a widget for the green box that returns a Dynamic indicating whether it is "activated". Everything else is just as it were in your initial example.
Thanks to /u/dalaing for advising me on this.

How can I draw two shapes in a time sequence using Gloss?

I have a simple window displaying two shapes like so:
import Graphics.Gloss
circles = pictures [Translate 80 0 one, Translate (-80) 0 two]
main = display (InWindow "t" (400,400) (800,0)) white circles
one = Color red $ Circle 80
two = Color blue $ Circle 50
I am new to Gloss, but from what I gather "display" just displays a static image once main (i.e. my module) is run so you cant make an animation using "display" right?
What I want to do is to run my program with these shapes, but instead of displaying them both at once, I want to first display one circle, the next second the other circle like some kind of animation.
So far I can only do something static and display both circles at once immediately when the program is run. But I want them to appear after each other like Run the program -> (0 sec) Blank screen -> (1 sec) One of the circles is drawn -> (2 sec) the other circle is drawn -> The window now displays circles until I close it.
This should be so simple using the "animate" function but I can't figure it out. If anyone is out there with knowledge, please consider helping! It would really make my day.
You use animate in order to draw a picture depending on the animation time:
main = animate (InWindow "t" (400,400) (800,0)) white draw
draw :: Float -> Picture
draw t
| t <= 1 = blank -- in the first second
| t <= 2 = pictures [Translate 80 0 one] -- between 1s and 2s
| otherwise = pictures [Translate 80 0 one, Translate (-80) 0 two] -- afterwards

Diagrams boundingRect including the lines' width

Is there an equivalent to the boundingRect function which includes the diagram's lines width(*), so that each line, however thick it is, is entirely contained within the bounding rectangle? (the boundingRect function "ignores" their thickness and parts of the lines stay outside the bounding rectangle).
(*) My question is for lines with width expressed in local units.
Unfortunately there's no way to do this automatically yet. The easiest way would be to frame the diagram before finding the boundingRect. Since you're using the local units you just need to frame half the local width used in the diagram (add half of the line width used for the bounding rectangle if that has a line too).
Here's a simple example:
{-# LANGUAGE GADTs #-}
import Diagrams.Prelude
import Diagrams.Backend.Rasterific.CmdLine
main :: IO ()
main = mainWith $ frame 1 rects
rects :: Diagram B
rects = hsep 1 $ map (dia <>) [br1, br2, br3]
where
br1 = boundingRect dia # lwL 0.2 # lc red
br2 = boundingRect (frame 0.1 dia) # fc dodgerblue # lw none
br3 = boundingRect (frame 0.2 dia) # lwL 0.2 # lc red
dia :: Diagram B
dia = circle 3 # fc orange # lwL 0.2
A more general solution would be to draw the offset curves of each path using the local line width in the Diagram and find the bounding box of that. Diagrams.TwoD.Offset can almost do this but I don't think it works for all cases.

Resources