next up previous contents
Next: Selection Up: Rendering Previous: The basic XML contradiction   Contents

A "Visitor" solves the basic contradiction

The class X2XPlacer works according to the "Visitor" design pattern. Imagine you are an owner of a restaurant and Saturday is a big party. And you like to invite the inhabitants of a certain district of your town.

You have almost the same problem: You have to place the persons at tables with 4, 6, 8, 12 seats but you don't know:

On the other hand: The interested persons have no chance to solve the problem because they don't know: The solution is a visitor! A person how has some plan of your restaurant and the available seats and tables. The visitor walks from door to door and asks the inhabitants whether they would take part in your party and what their preferences are. The visitor places the interested persons and tries to solve some small conflicts.

So far the parable! In X2XEdit there is a class X2XRenderer which has one X2XPlacer object - the visitor. The X2XRenderer serves as the "car" of the visitor. It traverses the DOM tree and thus, transports the X2XPlacer to every node. The X2XPlacer in turn makes some investigations to get the preferences of this node.

Note! The placer places only X2XGlyphs not X2XContainers. But, of course, the knowledge of the placer is necessary to compute the bounding polygons of the X2XContainers (see below).

An important detail is: The X2XPlacer assigns an index (some kind of seat reservation) to every X2XGlyph. This index grows up from top to bottom and from left to right. This enables a fast search for a certain X2XGlyph (see below).

The X2XPlacer keeps track of already placed X2XGlyphs. The data structure is the lineArray:

It is an X2XDynArray which holds (pointers to) structs of type lineDescr which in turn has an X2XDynArray of (pointers to) X2XGlyphs. Every X2XDynArray represents a line and holds (pointers to) the X2XGlyphs placed at this line.

Note! This is not a copy of the X2XGlyphs attached to the text nodes in DOM tree. Instead of this a pointer to the same object is used!

This is important, because this way the X2XPlacer can detect equality between an object in DOM tree and an object in lineArray.

Lets regard the co-operation between X2XRenderer and X2XPlacer in source text. It is an excerpt from X2XRenderer::placeItemsR method:

  ...
  else if (X2XResource::isContainerExceptTableStaff(node->name))  {
     firstIdx_ = placer_->getActualIdx();
     container = (X2XContainer *) node->_private;
     placer_->configureBeforePlacement(container, showLabels_);
     yBefore = placer_->getMaxYTotal();
     oldalignment = placer_->setCurrentHalignment(
     	                       container->getRequestedHAlignment());
     maxx = placeItemsR(node->children);
     lastIdx_ = placer_->getActualIdx();
     placer_->configureAfterPlacement(container);
     changeAdjustment(node->children, container->getRequestedHAlignment());
     container->computeBoundingPolygon(placer_, firstIdx_,
                                         lastIdx_, maxx, yBefore);
     placer_->setCurrentHalignment(oldalignment);
  }
  else ...

The renderer asks the X2Xlacer (placer_ is a pointer to an object of class X2XPlacer) for the actual (means the next) index. This way the index of the first X2XGlyph in X2XContainer is known. It gets the actual X2XContainer (container is a pointer to an object of class X2XContainer) and instructs the placer to do all actions necessary before placement. This can be a line break or insertion of some additional vertical space. The placer asks the container, what its wishes are. To be able to do this it gets a pointer to the container. The placer adjusts some internal variables according to this wishes. Now the top Y position of the container is known. This is assigned to the variable yBefore.

Furthermore, it instructs the placer to set the alignment policy requested by the container. The return value is the alignment policy valid so far. The renderer assigns this to variable oldalignment.

After that the method calls itself and performs the placement of the child nodes and X2XGlyphs. The return value is the maximum X value used to place the children. Note! The child placement causes an index increment. Thus, lastIdx_ gets the index(+1) of the last X2XGlyph in X2XContainer.

The next step is to instruct the placer to do all necessary after the container's placement. Again the placer gets the pointer to the container to be able to make the appropriate investigations.

The call to changeAdjustment is necessary for right and center aligned boxes. Center and right alignment means: Distribute the space at the end of each line by shifting the X2XGlyphs to the right. This can be done not until this space is known. And space is known not until the whole line is placed. Therefore, the adjustment change is be performed not until the lines are placed.

Because the placer has knowledge about the line structure it can instruct all X2XGlyphs in a line to move some pixels to the right.

Now all X2XGlyphs are at the right place. And the X2XRenderer instructs the X2XContainer to compute the bounding polygon. To do this the X2XContainer gets the placer, the index of the first and last(+1) X2XGlyph in container, the maximum X value used to place the children in container and the Y position before placing the first X2XGlyph in X2XContainer.

Remember: The placer can identify the first and last X2XGlyph by index. Thus, the X2XContainer has access to the first and to the last X2XGlyph. Because these X2XGlyphs are already placed, they can tell about their X and Y position.

This information is sufficient to compute the bounding polygon.

Now lets see what happens if the X2XRenderer encounters a text node:

   switch (node->type) {
   ...
      case XML_TEXT_NODE :
         if (node->_private == 0)  break;
         placer_->placeGlyphArray(
                 (X2XDynArray<X2XGlyph> *) node->_private);
         break;
   }
The X2XPlacer's method placeGlyphArray is called. Remember, the _private field is a pointer to an X2XDynArray which holds the X2XGlyph objects (of subclass X2XLetter in this case). The placer keeps track of the actual X and Y position and the next index. Assume the next X2XLetter is the letter 'y' from word "really":
If the current X position is 378 and the current Y position 200 the X2XLetter object is placed at (378, 200) and if current index is 102 the placer assigns the index 102 and increments the current index to 103.

As mentioned above every X2XGlyph (and X2XLetter is a subclass of X2XGlyph) knows its dimensions even if not already placed. Thus, the placer can ask the 'y' about its X dimension (fortunately, Gtk provides some means to calculate this value). Assume the X dimension is 6 the X2XPlacer increments the current X position by 6 to 384.

In the same manner the following space character is placed:

The placer can ask every glyph whether it is a whitespace character. And, of course, the space character would answer: "Yes!". This causes the placer to set and internal variable lastWhiteSpaceIdx_ to 103, the index of the space character.

In the same manner it would place 'c', 'o', and 'm'. But now a problem emerges:

Assume the right paper margin is at 400. That means the 'm' is outside of the usable paper width. In this situation the placer remembers the index of the last white space and places all following X2XGlyphs at the next line:

What next? If all X2XGlyphs are placed and all X2XContainers have calculated their bounding polygons the whole DOM tree can be drawn. To do this the class X2XTreePainter offers a method X2XTreePainter::drawPreparedTreeR which performs this task, excerpt:

switch (node->type) {
   case XML_ELEMENT_NODE: 
      ...
      else if (X2XResource::isNormalContainer(node->name)) {
         container = (X2XContainer *) node->_private;
         container->draw(showLabels_);
         drawPreparedTreeR(node->children, true);
      }
      ...
      break;
   case XML_TEXT_NODE:
      glyphlist = (X2XDynArray<X2XGlyph> *) node->_private;
      idx = 0;
      glyph = glyphlist->getAt(idx++);
      while (glyph)  {
         glyph->draw(showLabels_);
         glyph = glyphlist->getAt(idx++);

      }
      break;
	...

As you can see, this method simply calls the draw methods of all X2XGlyphs and containers. Because every X2XElement is placed it knows its position and thus, draws itself at the correct place.


next up previous contents
Next: Selection Up: Rendering Previous: The basic XML contradiction   Contents
2004-05-17