Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I have a script that makes fairly complex PDF reports from a set of SVG templates. The problem is that one of them templates contains a box of text that is generated by the script. Is there a good algorithm for figuring out the font size, line spacing, and line breaks so that the text fills the box? In other words, I want to be able to specify a font, the width and height of the box in pixels, and the text, and have the script set the font size and determine where the line breaks need to be and what the line spacing should be so that the text is as large as possible within the box.

Ideas?

Replies are listed 'Best First'.
Re: Formatting block of text
by rjt (Curate) on Jul 28, 2013 at 02:01 UTC

    The best suggestion I know of is to use Font::TTFMetrics (if you are using TrueType fonts or can find/convert your font to an equivalent), or Image::Magick for other font types. You would then probably have to go word by word (or break-by-break if you're using hyphenation) and calculate the length. The other option would be to use a D&C algorithm and keep splitting the string until it falls within the desired min/max width. With Font::TTFMetrics, you can calculate the width of a string something like so:

    use Font::TTFMetrics; my $resolution = 72; # 72dpi typical, 96, 100, 120 common my $pointsize = 12; # Whatever size the target text is my $string = 'Some string'; my $font = Font::TTFMetrics->new('/path/to/Font.ttf'); my $font_units = $font->string_width($string); my $pixels = $font_units * $pointsize * $resolution / (72 * $font->get_units_per_em); printf "Width of `%s' is %.2f font units, or %d pixels\n", $string, $font_units, $pixels;

    Hope this helps!

Re: Formatting block of text
by Loops (Curate) on Jul 28, 2013 at 03:10 UTC

    Building on what rjt posted, you can use Text::Flow to format your paragraph constrained to a number of pixels of your choosing. In rough form:

    use Text::Flow; use Font::TTFMetrics; my $text = 'asdfk lasdjf asd flkasd fkjasdf la ksdf asdlkja sdkf laksdf laksdj aj +sd lkasdf alksdj flkajsdl falsd flkasd asd flkaj sdlkfja lsf alsdkf lksad +jfl asdfkj laksdf a sdkjf laksdjf kasdf lkasdfkja sdflka sdf asd flkajds l +fka sdflak sdflkajsd fkjal j adsfkj alsdf ads Some string'; $text =~ s/\n/ /g; my $resolution = 72; # 72dpi typical, 96, 100, 120 common my $pointsize = 12; # Whatever size the target text is my $font = Font::TTFMetrics->new('georgia.ttf'); my $width = 200; # Constrain to this number of pixels in width. my $flow = Text::Flow->new( check_height => sub { return 1; # Should really enforce height constraint here }, wrapper => Text::Flow::Wrap->new( check_width => sub { my $string = shift; my $font_units = $font->string_width($string); my $pixels = $font_units * $pointsize * $resolution / (72 * $font->get_units_per_em); return $pixels < $width; }, ) ); my ($reflow) = $flow->flow($text); print $reflow;
    Produces:
    asdfk lasdjf asd flkasd fkjasdf la ksdf asdlkja sdkf laksdf laksdj ajsd lkasdf alksdj flkajsdl falsd flkasd asd flkaj sdlkfja lsf alsdkf lksadjfl asdfkj laksdf a sdkjf laksdjf kasdf lkasdfkja sdflka sdf asd flkajds lfka sdflak sdflkajsd fkjal j adsfkj alsdf ads Some string