Skip to content

Instantly share code, notes, and snippets.

@deepakjois
Created April 28, 2012 22:32
Show Gist options
  • Save deepakjois/2522452 to your computer and use it in GitHub Desktop.
Save deepakjois/2522452 to your computer and use it in GitHub Desktop.
Diagrams SVG Backend Text Rendering
import Diagrams.Prelude
import Diagrams.Backend.SVG.CmdLine
eff = text "F" <> square 1 # lw 0
example = eff # rotateBy (1/7)
main = defaultMain example

Text primitive

A text primitive consists of the string contents and alignment specification, along with a transformation mapping from the local vector space of the text to the vector space in which it is embedded.

Text T2 TextAlignment String

Rendering text

Ideally the SVG rendered should be like the one below in file text.svg.

Text rendering

Note that we use font-size="1" to establish a baseline font-size, and then we use em units in other places to specify a relative font size.

-- FIXME implement
renderText (Text tr _ str) = undefined

Below are things we need to render the text. We ignore TextAlignment data for now. str is self evident (it is the text we need to render).

As for tr:

  • We need the translation of the origin of the text primitive in the final vector space. This can be easily obtained by transl tr. Then we apply that on a <g> node as shown in file text.svg below: <g transform="translate(50 50)">

  • We need the matrix for rotation, which corresponds to the amount of by which the y-axis has rotated in the final vector space. In the example program above we rotate by 1/7, which is roughly 51 degrees. However I could only get the expected end result by using rotate(-51) or matrix(0.62 -0.77 0.77 0.62 0 0) as shown in file text.svg below.

  • We need the final font size. Font size is effectively a measure of the height of the font, because we cannot control the width, which varies from glyph to glyph. You might think we can apply scaling in the matrix above and get away with a font size of 1, but I tried it and it doesnt work in many situations. So we need to somehow calculate the size of the font. I am not sure whether we can get this out of tr, but my intuition suggests that it is the scaling factor along the font's vertical axis in the final vector space.

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@deepakjois
Copy link
Author

Based on Brent's suggestion I tried using the transormation matrices and
directly applying them. That did not work very well at first, but I did notice
that the transformation matrix changes depending on the eventual height and
width of my SVG, that I specify using -w and -h CLI switches. Here are a few
examples for SVG mentioned above, at different

1x  <no params>  matrix(0.6234898018587336,-0.7818314824680297,0.7818314824680297,0.6234898018587336,0.7026606421633816,0.7026606421633816)
10x -w 10 -h 10  matrix(4.436635300499446,-5.563364699500554,5.563364699500554,4.436635300499446,5.0,5.0)
20x -w 20 -h 20  matrix(8.873270600998891,-11.126729399001109,11.126729399001109,8.873270600998891,10.0,10.0)

Could somebody help me see if there is way of deriving the 1x from the 10x
or the 20x transforms. That would be very helpful for what I am describing in
the next section.

Based on the data above, I tweaked my SVG output to something like this for a 400x400 size output

<g>
  <text
    transform="matrix(0.6234898018587336
                      ,-0.7818314824680297
                      ,0.7818314824680297
                      ,0.6234898018587336
                      ,200,200)"
    font-size="284.63242139794744em"
    dominant-baseline="middle"
    text-anchor="middle">F</text>
</g>

Note that I made the following tweaks:

  1. The font size is the magnitude of the vector, with the text transformation
    applied to the unitY matrix
  2. The transform matrix is the one given by the initial 1x matrix, with
    only the translation vector tweaked to conform to the final vector space.

@byorgey
Copy link

byorgey commented Apr 30, 2012

"You might think we can apply scaling in the matrix above and get away with a font size of 1, but I tried it and it doesnt work in many situations." --> could you be more specific?

@byorgey
Copy link

byorgey commented May 1, 2012

Those matrices are just simple rescalings of one another. Note that the "1x" matrix is not exactly 1/10th the "10x" matrix because the size of the diagram with no params is not actually 1x1; it is a 1x1 square rotated by 1/7, giving an actual width of

Prelude Diagrams.Prelude> width (square 1 # rotateBy (1/7) :: D R2)
1.4053212843267635

Hence we would expect a scaling factor of (10 / 1.4), or about 7.1. And sure enough: 4.4366 / 0.6234 = 7.1. This scaling is being done by the call to adjustDia2D; it determines the scaling factor simply by calling requiredScale (see http://hackage.haskell.org/packages/archive/diagrams-lib/0.5/doc/html/Diagrams-TwoD-Adjust.html). So you can call requiredScale manually to determine the scaling factor.

@byorgey
Copy link

byorgey commented May 1, 2012

One other thing: in the cairo rendering monad it's possible to get info about the font (height, extents, etc. etc.), but we can't do that while rendering SVG. So in terms of alignment, all we have available is what SVG provides in terms of attributes (like dominant-baseline, text-anchor, etc.). So it's perfectly OK if the SVG backend doesn't support the full range of text alignment options that the cairo backend does, but just does "best-effort". If people want more sophisticated text alignment they can use the SVGFonts package.

@deepakjois
Copy link
Author

@byorgey: Thanks for the pointer to adjustDia2D. I will look at it.

Regarding your comment about being more specific, let me provide a few examples.

examples/Text -w 100 -h 100  -o ~/tmp/text.svg 

Leads to (view source for the full code)

100x100

This is quite weird. This is what I meant when I said : "You might think we can apply scaling in the matrix above and get away with a font size of 1, but I tried it and it doesnt work". The text XML node looks like

<text transform="matrix(44.36635300499445,-55.63364699500555,55.63364699500555,44.36635300499445,50.0,50.0)" dominant-baseline="middle" text-anchor="middle">F</text>

However if I change the first four components of the transform to the ones from the non-scaled diagram, and adjust the font size to the magnitude of the unitY vector under the transform matrix above. I get :

10x100

The text node now looks like:

<text transform="matrix(0.6234898018587336,-0.7818314824680297,0.7818314824680297,0.6234898018587336,50.0,50.0)" font-size="71.15810534948686em" dominant-baseline="middle" text-anchor="middle">F</text>

Does that make more sense?

@deepakjois
Copy link
Author

So it's perfectly OK if the SVG backend doesn't support the full range of text alignment options that the cairo backend does, but just does "best-effort"

Agree. From what I read, tweaking the dominant-baseline and text-anchor should give us what we need for different values of TextAlignment, without having to calculate bounds.

@byorgey
Copy link

byorgey commented May 1, 2012

Hmm, in my browser the first example looks fine and the second example looks blank. Is that not the case for you?

@deepakjois
Copy link
Author

Which browser is that? Here is how it renders in Chrome:

Screenshot

@byorgey
Copy link

byorgey commented May 1, 2012

I'm using Firefox 11. Here's my screenshot:

Screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment