The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

PDL::Graphics::Prima::SizeSpec - a module for handling size specifications

SYNOPSIS

 use PDL::Graphics::Prima::SizeSpec;

 # Create a SizeSpec parser for your window/widget
 my $parser = PDL::Graphics::Prima::SizeSpec::Parser->new($my_window);

 # Create a dynamic size-computing object from a string:
 my $size = $parser->parse('10%width + 2em - 5px');

 # Get the size in pixels using the size method:
 print "That amounts to ", $size->size, " pixels\n";

 # stringification is mostly round-tripable:
 print "The original spec was $size\n";

 # use the spec directly in numeric evaluations
 print "Too big\n" if $size > 100;

 # Instead of a string, you can provide a hashref to the parser
 my $size = $parser->parse({em => 2, px => -5,
     pctwidth => 10});


 ### Create more sophisticated parsers by subclassing ###
 package My::Parser;
 our @ISA = qw(PDL::Graphics::Prima::SizeSpec::Parser);

 sub size_spec_pctcolwidth {
     my ($self, $percent) = @_;

     # Return a closure that takes no arguments and which
     # returns pixels for a given column width percentage
     return sub {
         return $self->{widget}->column_width * $percent / 100;
     }
 }

 # Now I can create a parser...
 my $newspaper_parser = My::Parser->new($newspaper);
 # ... and use the parser to create a size spec
 #     that knows how to parse '20%colwidth'
 my $size_spec = $newspaper_parser->parse($size);

DESCRIPTION

If you've ever worked with CSS, you know that you can specify the sizes of things using lots of different units. PDL::Graphics::Prima::SizeSpec is my attempt at providing a similar sort of capability for Prima widgets.

The primary purpose of a SizeSpec object is to compute pixel distances for you. However, the SizeSpec object is dynamic insofar as it responds to changes in the associated widget. If you have a SizeSpec that depends upon the height or width of the widget, and the widget gets resized, then the SizeSpec's numeric value will change in response. Likewise if your SizeSpec depends upon the em-width and you change your widget's font.

In practice, SizeSpec objects are little more than thin wrappers around a collection of closures. Once you've used a parser to obtain a SizeSpec object, you can easily obtain the number of pixels associated with the SizeSpec by evaluating it in numerical context, or by calling the size method:

 my $margin = $margin_spec->size;

If you know that certain units are associated with dynamic behavior, you can inquire as to whether a given unit is used:

 if ($margin_spec->uses_unit('%width')) {
     ...
 }

You do not build SizeSpecs directly with a call to new. Instead, SizeSpecs are built by SizeSpec parsers. The parser's job is to validate the input and construct closures that efficiently map the specified units to pixel values. These closures are at the heart of the SizeSpec objects. Parsers also produce the string representation of the SizeSpec that is used when the SizeSpec is stringified.

Parse objects provide only a single user-level method: parse. This method accepts either a string containing an arithmetic combination of units (i.e. '5em - 2px') or a hashref of unit/value pairs. The string form is usually more convenient for simple specifications, but you can do clever things with the hashref form. For example, the value associated with the hashref can be an object that overloads addition, such as a PDL. In that case, the final size given by the SizeSpec will itself be a PDL, not a scalar.

A SizeSpec's stringification is usually round-tripable, except when you parse a hashref that includes objects. The stringification is meant to be human readable and simply stringifies the object itself, thus discarding class information. (I have gone to the pains of handling large PDL objects in a sensible way: PDLs with more than 10 elements are stringified to '[PDL]'.)

Both SizeSpecs and SizeSpec parsers assume they are working with a single widget, specified as the first argument to the parser's constructor. If you want to use an identical SizeSpec string on two different widgets, you need to build a separate parser for each widget.

The base parser class knows how to handle the following units:

px

Raw pixel counts.

em

The width of the letter M

%width, pctwidth

A percentage of the widget's width.

%height, pctheight

A percentage of the widget's height.

line

A multiple of the font (i.e. line) height.

Unitless numbers are treated as pixels. Pixel sizes for all of these are obtained from the parser's widget, which is the first argument provided to the constructor.

The general way of adding new units or overriding the interpretation of known units is to subclass the parser, discussed in "SUBCLASSING". If you just want to create a single parser object with special unit handling, you can specify a key/subref pair in the parser constructor call. The key must be a SizeSpec unit name, described under "SUBCLASSING", and the method must be identical to a SizeSpec unit method. In short, the associated subref should accept the parser object and the multiple of the unit as arguments, and return a closure that takes no arguments and returns a size in pixels for the associated quantity. Thus the newspaper example written above could have been written as

 my $newspaper_parser = PDL::Graphics::Prima::SizeSpec::Parser->new(
     $newspaper,
     size_spec_pctcolwidth => sub {
         my ($self, $percent) = @_;
         return sub {
             return $self->{widget}->column_width * $percent / 100;
         }
     },
 );

As another example, if you want to create a parser for which the line unit refers to the widget's drawn line width, you could do this:

 my $parser = PDL::Graphics::Prima::SizeSpec::Parser->new(
     $canvas,
     size_spec_line => sub {
         my ($self, $multiple) = @_;
         return sub { $canvas->lineWidth * $multiple  };
     },
 );

Parsers with ad-hoc units may be convenient, but are difficult to extend. If you want to create a parser that others can use, consider creating a subclass of PDL::Graphics::Prima::SizeSpec::Parser.

SUBCLASSING

If you find yourself creating lots parsers with the same specialized parsing units, you should consider creating a parser subclass. You add new units by providing methods with the prefix size_spec_.

NAMING UNITS

You are free to use any unit name you want so long as it can be used as a function name with the prefix size_spec_. For example, you may want to have a unit called day_of_week. In that case, you would have a method called size_spec_day_of_week:

 sub size_spec_day_of_week {
     my ($self, $amount) = @_;
     return sub {
         ...
     };
 }

PDL::Graphics::Prima::SizeSpec::Parser also knows how to handle units that include the percent sign, %. For a unit such as %colwidth, you would have a method called size_spec_pctcolwidth. Note that the amount given is not divided by 100 for you, as demonstrated in the colwidth example in the "SYNOPSIS".

One final unit name of importance is the unitless unit. The normal interpretation of unitless numbers is as pixels. However, PDL::Graphics::Prima uses SizeSpec parsers for which raw numbers correspond to x- or y-locations on the plot. If you need to overload the unitless unit, you would use the method name size_spec_unitless.

UNIT METHODS

A unit method should take the parser object and the amount obtained by the parser, and it should return a closure which expects no arguments and which computes and returns its the amount's equivalent size in pixels. Note that if you need to refer to the widget to obtain a property for your unit, you should refer to the widet using the widget key of the parse object. This will avoid circular references.

Here is an example of the implementation of the %width size specification:

 # %width is based on the widget's width
 sub size_spec_pctwidth {
     my ($self, $amount) = @_;
     return sub { return $amount / 100 * $self->{widget}->width };
 }

AUTHOR

David Mertens (dcmertens.perl@gmail.com)

ADDITIONAL MODULES

Here is the full list of modules in this distribution:

PDL::Graphics::Prima

Defines the Plot widget for use in Prima applications

PDL::Graphics::Prima::Axis

Specifies the behavior of axes (but not the scaling)

PDL::Graphics::Prima::DataSet

Specifies the behavior of DataSets

PDL::Graphics::Prima::Limits

Defines the lm:: namespace

PDL::Graphics::Prima::Palette

Specifies a collection of different color palettes

PDL::Graphics::Prima::PlotType

Defines the different ways to visualize your data

PDL::Graphics::Prima::ReadLine

Encapsulates all interaction with the Term::ReadLine family of modules.

PDL::Graphics::Prima::Scaling

Specifies different kinds of scaling, including linear and logarithmic

PDL::Graphics::Prima::Simple

Defines a number of useful functions for generating simple and not-so-simple plots

PDL::Graphics::Prima::SizeSpec

Compute pixel distances from meaningful units

LICENSE AND COPYRIGHT

Unless otherwise stated, all contributions in code and documentation are copyright (c) their respective authors, all rights reserved.

Portions of this module's code are copyright (c) 2011 The Board of Trustees at the University of Illinois.

Portions of this module's code are copyright (c) 2011-2013 Northwestern University.

Portions of this module's code are copyright (c) 2013-2014 Dickinson College.

This module's documentation is copyright (c) 2011-2014 David Mertens.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.