To wrap up 2011 we will look at another advanced example of custom development for
PageBuilder: ABC Positioning.
What is ABC Positioning? For each resolved product that's being added to a page
or spread there will be added some extra information on another place of the page.
The extra information needs to be visually linked to the resolved product. How to
achieve this?
Introduction
In this sample we want to create a catalog where the image of each product is seperated
from the product (metadata) itself. In other words, each page is divided in two:
the textual information of the product and the image corresponding to each product.
Maybe a screenshot will make it clear. Following image is a page taken from the
resulting catalog.

This is a left page of the catalog. As you can see it contains 6 products (A-F).
Each product contains a title, description and a table. This information was added
using the out of the box PageBuilder functionality: the design template with basic
textboxes and corresponding tags. The left column (images) are added using custom
code. The letters (A-F) are there to link each image to a product. This is done
by placing each letter in the upperleft corner of the resolved template and above
each image. The numbering restarts for each page.
Templates
The image below is the layout of the Master Template. The left and the right page
contains 3 columns, but only 2 of these columns are used to paste the products (the
purple area, 'margins' in the master template). The green rectangle is preservered
for the images we will add using custom code. With the out of the box PageBuilder,
these green columns would stay empty.

The last screenshot is the design template. Again you can see three columns, this
is only to make it easier to determine the width for this design. For this example
the width of a product does not exceed the width of the columns in the master template.
In this way each products' width fits perfectly in 1 column.
The three textboxes (CT_name, Ct_description, table) will be filled with data from
the ADAM Database. The last textbox (upper left corner) only contains an X and is
not tagged. Again, without the custom code, each product in the resulting catalog
would have this box with an 'X' in it.

Creating the CatalogBuilder
To accomplish this we need a custom CatalogBuilder (we are creating a catalog),
with a custom RecordPaginateAction (to update the letter 'X' and to add the image)
and a custom PagePaginateAction (to be able to restart each page with the letter
'A').
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
using System;
using System.Collections.Generic;
using System.Linq;
using Adam.Core;
using Adam.Core.Records;
using Adam.DocMaker.Core;
using Adam.DocMaker.Core.Geometry;
using Adam.PageBuilder.Core.Build;
using Adam.PageBuilder.Core.Paginate;
namespace ABCPositioning
{
internal class AbcCatalogBuilder : CatalogBuilder
{
public AbcCatalogBuilder(Application application) : base(application)
{
List<char> charactersUsedOnPage = new List<char>();
PagePaginateAction = new AbcPagePaginateAction(application, charactersUsedOnPage);
RecordPaginateAction = new AbcRecordPaginateAction(application, charactersUsedOnPage);
}
private class AbcRecordPaginateAction : RecordPaginateAction {...}
private class AbcPagePaginateAction : PagePaginateAction {...}
}
}
|
As you can see a charactersUsedOnPage is used in both Action constructors.
In here, every character that will be used in a page will be added. In this way,
both actions can access the same data.
Creating the PagePaginateAction
We'll start with the PagePaginateAction. The only thing that needs
to be done here is to clear the characters list every time the page is full. This
can be accomplished by overriding the OnPaginatedPage method.
Below you can find the code of the custom PagePaginateAction class.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private class AbcPagePaginateAction : PagePaginateAction
{
private readonly IList <char> _charactersUsedOnPage;
public AbcPagePaginateAction(Application application, IList<char> charactersUsedOnPage)
: base(application)
{
_charactersUsedOnPage = charactersUsedOnPage;
}
// Clear the characters list.
protected override void OnPaginatedPage(PageTarget pageTarget)
{
_charactersUsedOnPage.Clear();
}
}
|
Pretty simple no? Lets continue with the real stuff.
Creating the RecordPaginateAction
Let's take a look to the code in the custom RecordPaginateAction class.
As you can see, the constructor is equal to the one of our PagePaginateAction.
_boxPadding is the amount of padding that is used when placing a new
image into the green column. _characters is the available list of characters
to be used for the items that are going to be placed on the page.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private class AbcRecordPaginateAction : RecordPaginateAction
{
private const int _boxPadding = 10;
private const string _characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private readonly IList<char> _charactersUsedOnPage;
public AbcRecordPaginateAction(Application application, IList<char> charactersUsedOnPage)
: base(application)
{
_charactersUsedOnPage = charactersUsedOnPage;
}
protected override void OnPlaced(PlaceTarget placeTarget) {...}
private static void AddItemGroup(
Document document,
TextElement abcElement,
FileVersion fileVersion)
{...}
}
|
We are overriding the OnPlaced method, cause after an ItemGroup was
added to the page, we want to update the letter 'X' and add the corresponding image.
The following code snippet shows the OnPlaced method. The inline comments
should be clear enough to see what's happening.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
protected override void OnPlaced(PlaceTarget placeTarget)
{
// Find the first available character for current page.
char currentCharacter = _characters.ToCharArray().First(c => !_charactersUsedOnPage.Contains(c));
_charactersUsedOnPage.Add(currentCharacter);
// Add the current character to the original placed item group.
// Find the textElement: we can search on contents being equal to 'X'.
// Then update the text to the current character.
TextElement abcElement =
placeTarget.Elements.OfType<TextElement>().Single(e => e.Story.ToText() == "X");
abcElement.Story.Paragraphs.Single().Items.OfType<Run>().Single().Value = currentCharacter.ToString();
// Load the image record and add the image to our page.
// The resolved record can be found using our placeTarget.
Record imageRecord = new Record(abcElement.App);
imageRecord.Load(((RecordPlaceTarget)placeTarget).ResolvedRecordId.Value);
FileVersion fileVersion = imageRecord.Files.LatestMaster;
if (fileVersion != null)
{
AddItemGroup(placeTarget.Document, abcElement, fileVersion);
}
}
|
The only thing that remains now is to add our image (and a duplicate of the character
element) to the correct place in our green column. Following snippet contains that
code.
| C# |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
private static void AddItemGroup(Document document, TextElement abcElement, FileVersion fileVersion)
{
// Find the rectangle in the masterpage to place the image in.
Page page = document.Pages.Last();
Page masterPage = document.MasterSpreads.Last().Pages.Single(p => p.Side == page.Side);
// Select the bounds of the green rectangle.
// The offset is used to let this work for both left and right pages.
RectangleD bounds = masterPage.Elements
.Select(element => element.Shape.Bounds)
.Single(b => b.Height > 500)
.Offset(page.Bounds.Left - masterPage.Bounds.Left, 0);
// Find the elements already in the column, and get the bottom of the bounding box.
RectangleD columnBounds = bounds;
double offset = page.Elements
.Select(e => e.Shape.Bounds)
.Where(b => b.IntersectsWith(columnBounds))
.Select(b => b.Bottom - columnBounds.Top)
.OrderByDescending(b => b)
.FirstOrDefault();
// Add the boxPadding offset.
bounds = columnBounds.Offset(_boxPadding, _boxPadding + offset);
// Clone the text element containing the letter and move it to correct location.
TextElement clonedAbcElement = document.CreateTextElement(abcElement);
clonedAbcElement.MoveTo(bounds.Location);
abcElement.OwnerSpread.Elements.Add(clonedAbcElement);
// Calculate the size and location for the image.
bounds = bounds.Offset(0, 20);
double imageWidth = bounds.Width - (2 * _boxPadding);
IReadOnlyImage image = fileVersion.GetPreview();
SizeD imageSize = new SizeD(imageWidth, imageWidth * image.Height / image.Width);
RectangleD imageBounds = new RectangleD(bounds.Location, imageSize);
// Create the imageElement and add it to the document.
ImageElement imageElement = document.CreateImageElement(imageBounds);
imageElement.ReplaceImage(ReplaceImageSource.FileVersion, fileVersion.Id);
abcElement.OwnerSpread.Elements.Add(imageElement);
}
|
This concludes our last PageBuilder blog post of the year. Hopefully we shared some
cool custom development cases the last few months. We are eager to see some mind
blowing custom creations from you guys out there!