Prima::TextView - rich text browser widget


 use strict;
 use warnings;
 use Prima qw(TextView Application);

 my $w = Prima::MainWindow-> create(
     name => 'TextView example',

 my $t = $w->insert(TextView =>
     text     => 'Hello from TextView!',
     pack     => { expand => 1, fill => 'both' },

 # Create a single block that renders all the text using the default font
 my $tb = tb::block_create();
 my $text_width_px = $t->get_text_width($t->text);
 my $font_height_px = $t->font->height;
 $tb->[tb::BLK_WIDTH]  = $text_width_px;
 $tb->[tb::BLK_HEIGHT] = $font_height_px;
 $tb->[tb::BLK_BACKCOLOR] = cl::Back;
 $tb->[tb::BLK_FONT_SIZE] = int($font_height_px) + tb::F_HEIGHT;
 # Add an operation that draws the text:
 push @$tb, tb::text(0, length($t->text), $text_width_px);

 # Set the markup block(s) and recalculate the ymap
 $t->{blocks} = [$tb];

 # Additional step needed for horizontal scroll as well as per-character
 # selection:
 $t->paneSize($text_width_px, $font_height_px);

 run Prima;


Prima::TextView accepts blocks of formatted text, and provides basic functionality - scrolling and user selection. The text strings are stored as one large text chunk, available by the ::text and ::textRef properties. A block of a formatted text is an array with fixed-length header and the following instructions.

A special package tb:: provides the block constants and simple functions for text block access.


Prima::TextView is mainly the text block functions and helpers. It provides function for wrapping text block, calculating block dimensions, drawing and converting coordinates from (X,Y) to a block position. Prima::TextView is centered around the text functionality, and although any custom graphic of arbitrary complexity can be embedded in a text block, the internal coordinate system is used ( TEXT_OFFSET, BLOCK ), where TEXT_OFFSET is a text offset from the beginning of a block and BLOCK is an index of a block.

The functionality does not imply any text layout - this is up to the class descendants, they must provide they own layout policy. The only policy Prima::TextView requires is that blocks' BLK_TEXT_OFFSET field must be strictly increasing, and the block text chunks must not overlap. The text gaps are allowed though.

A text block basic drawing function includes change of color, backColor and font, and the painting of text strings. Other types of graphics can be achieved by supplying custom code.

block_draw CANVAS, BLOCK, X, Y

The block_draw draws BLOCK onto CANVAS in screen coordinates (X,Y). It can be used not only inside begin_paint/end_paint brackets; CANVAS can be an arbitrary Prima::Drawable descendant.

block_walk BLOCK, %OPTIONS

Cycles through block opcodes, calls supplied callbacks on each.

Coordinate system methods

Prima::TextView employs two its own coordinate systems: (X,Y)-document and (TEXT_OFFSET,BLOCK)-block.

The document coordinate system is isometric and measured in pixels. Its origin is located into the imaginary point of the beginning of the document ( not of the first block! ), in the upper-left pixel. X increases to the right, Y increases down. The block header values BLK_X and BLK_Y are in document coordinates, and the widget's pane extents ( regulated by ::paneSize, ::paneWidth and ::paneHeight properties ) are also in document coordinates.

The block coordinate system in an-isometric - its second axis, BLOCK, is an index of a text block in the widget's blocks storage, $self->{blocks}, and its first axis, TEXT_OFFSET is a text offset from the beginning of the block.

Below different coordinate system converters are described

screen2point, point2screen X, Y

screen2point accepts (X,Y) in the screen coordinates ( O is a lower left widget corner ), returns (X,Y) in document coordinates ( O is upper left corner of a document ). point2screen does the reverse.

xy2info X, Y

Accepts (X,Y) is document coordinates, returns (TEXT_OFFSET,BLOCK) coordinates, where TEXT_OFFSET is text offset from the beginning of a block ( not related to the big text chunk ) , and BLOCK is an index of a block.


Accepts (TEXT_OFFSET,BLOCK) coordinates, and returns (X,Y) in document coordinates of a block.

text2xoffset TEXT_OFFSET, BLOCK

Returns X coordinate where TEXT_OFFSET begins in a BLOCK index.


Accepts (TEXT_OFFSET,BLOCK) coordinates and returns the text offset with regard to the big text chunk.

text_offset2info TEXT_OFFSET

Accepts big text offset and returns (TEXT_OFFSET,BLOCK) coordinates

text_offset2block TEXT_OFFSET

Accepts big text offset and returns BLOCK coordinate.

Text selection

The text selection is performed automatically when the user selects a text region with a mouse. The selection is stored in (TEXT_OFFSET,BLOCK) coordinate pair, and is accessible via the ::selection property. If its value is assigned to (-1,-1,-1,-1) this indicates that there is no selection. For convenience the has_selection method is introduced.

Also, get_selected_text returns the text within the selection (or undef with no selection ), and copy copies automatically the selected text into the clipboard. The latter action is bound to Ctrl+Insert key combination.

A block with TEXT_OFFSET set to -1 will be treated as not containing any text, and therefore will not be able to get selected.

Event rectangles

Partly as an option for future development, partly as a hack a concept of 'event rectangles' was introduced. Currently, {contents} private variable points to an array of objects, equipped with on_mousedown, on_mousemove, and on_mouseup methods. These are called within the widget's mouse events, so the overloaded classes can define the interactive content without overloading the actual mouse events ( which is although easy but is dependent on Prima::TextView own mouse reactions ).

As an example Prima::PodView uses the event rectangles to catch the mouse events over the document links. Theoretically, every 'content' is to be bound with a separate logical layer; when the concept was designed, a html-browser was in mind, so such layers can be thought as ( in the html world ) links, image maps, layers, external widgets.

Currently, Prima::TextView::EventRectangles class is provided for such usage. Its property ::rectangles contains an array of rectangles, and the contains method returns an integer value, whether the passed coordinates are inside one of its rectangles or not; in the first case it is the rectangle index.


Dmitry Karasik, <>.


Prima::Drawable::TextBlock, Prima::PodView, examples/