﻿<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>ADAM Blog</title><link>http://blogs.adamsoftware.net</link><description>About ADAM: tips and tricks, how to's and more</description><item><title>Extending Adam.JSGenerator in your application</title><link>http://blogs.adamsoftware.net/Web_Development/ExtendingAdamJSGeneratorinyourapplication.aspx</link><description>&lt;div&gt;&lt;p&gt;We’re working on adding better support for jQuery and JQuery UI in the next version of the &lt;a target='_blank' href="http://adamjsgenerator.codeplex.com"&gt;Adam.JSGenerator&lt;/a&gt; library, but there’s nothing that keeps you from adding your extensions to make your expressions more readable and maintainable.&lt;/p&gt;&lt;p&gt;For example, if we were to want support for a particular jQuery UI function today, it’s simply a matter of adding an extension method to your application, since most jQuery and jQuery UI functions are calls on a jQuery object, that can come from any sort of expression, be it a call to jQuery itself, a set stored in a variable, passed as a parameter, or the result of another jQuery/jQuery UI call (called chaining).&lt;/p&gt;&lt;p&gt;So what do we do?&lt;/p&gt;&lt;p&gt;First, we make a new static class. The name of this class doesn’t really matter, because we’ll be writing extension methods, but it’s nice to give it a helpful name and anything ending in “Helpers” seems to be the convention nowadays. So we could name this class JQueryUIHelpers:&lt;/p&gt;&lt;pre name="C#"&gt;
static class JQueryUIHelpers
{
}
&lt;/pre&gt;&lt;p&gt;Then, we write our extension method. An extension method is simply a static method in a static class with an extra keyword added to the first argument. This will let us call the extension method using a different syntax, and make it appear as if it’s a call on an object. Make no mistake, though, because it’s simply syntactic sugar.&lt;/p&gt;&lt;p&gt;In order for our extension method to work correctly, our first argument needs to be of type Expression and adorned with the ‘this’ modifier:&lt;/p&gt;&lt;pre name="C#"&gt;
public static CallOperationExpression Accordeon(this Expression source, object options)
{
}
&lt;/pre&gt;&lt;p&gt;As you can see, I’ve chosen the second argument to be a simple object. This is so I can accept any type to be passed as options to the accordeon() function. A lot of jQuery and jQuery UI functions accept a simple object to specify options, and this way I can use anonymous types. We return a CallOperationExpression for good form. Then it’s simply a matter of implementing the method:&lt;/p&gt;&lt;pre name="C#"&gt;
return source.Dot("accordeon").Call(Expression.FromObject(options));
&lt;/pre&gt;&lt;p&gt;Calling this new extension method is pretty straightforward. You only need to make sure that the static class is “visible” to where you call it, so you may have to add a “using” statement to the top.&lt;/p&gt;&lt;pre name="C#"&gt;
var call = JS.JQuery("#container").Accordeon(new {collapsible = true});
&lt;/pre&gt;&lt;p&gt;And that’s how it’s done.&lt;/p&gt;&lt;p&gt;As you can see, it makes our generation code a bit more readable. Your mileage may vary, of course. If you have a number of functions or that you use all over, and they’re calls on jQuery objects through jQuery or any sort of plugin you wrote, this might be a technique you could use to simplify things.&lt;/p&gt;&lt;p&gt;As always, feedback is welcome.&lt;/p&gt;&lt;/div&gt;</description></item><item><title>SharePoint Connector 3 released</title><link>http://blogs.adamsoftware.net/SharePoint_Connector/SharePointConnector3released.aspx</link><description>&lt;div&gt;&lt;p&gt;
                                We are proud to announce the release of the new ADAM SharePoint Connector 3, redesigned
                                to integrate seamlessly with Microsoft SharePoint 2010.
                            &lt;/p&gt;&lt;p&gt;
                                The new Connector closes the gap between Microsoft SharePoint and the ADAM Engine.
                                Rather than straddling the two systems, SharePoint Connector has been built right
                                into the heart of SharePoint so users never leave their familiar comfort zone when
                                they want to access ADAM-stored digital assets.
                            &lt;/p&gt;&lt;p&gt;
                                Using ADAM SharePoint Connector, your familiar Microsoft SharePoint workspace becomes
                                the viewer for all your files of any type, including graphics, video, Adobe® InDesign®,
                                etc.
                                &lt;br /&gt;
                                Record searching, browsing, viewing, editing and downloading are easily accessible,
                                while maintaining the granular security as set up in ADAM: only the right people
                                see the right assets.&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Adding inline images to table cells</title><link>http://blogs.adamsoftware.net/PageBuilder/Addinginlineimagestotablecells.aspx</link><description>&lt;div&gt;&lt;p&gt;
A few weeks ago we received the following request from one of our partners:
&lt;/p&gt;&lt;p&gt;&lt;cite&gt;
We would like to create a table with icons in the headers of the columns. 
It would be interesting to see a blog post showing some kind of custom dev similar to that. 
I have seen the blog entry of adding new columns to a table, but those cells were text cells. 
I suppose adding an inline image in a cell is a bit more complex.
&lt;/cite&gt;&lt;/p&gt;&lt;p&gt;
We always enjoy a challenge, so we decided to try this out. Consider the before and after screenshots pictured below.
On the left, a product item group is shown that uses regular field labels for the table column headers. This item group was
created using the default PageBuilder 4 behavior. On the right, you can see an item group where the labels 
for the column headers have been replaced with icons using a bit of custom code. 
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/7ee647955ffe45f9922aac428ac13281.jpg' /&gt;&lt;/p&gt;&lt;p&gt;
In order to achieve this effect, you can use an &lt;code&gt;ItemGroupBuilder&lt;/code&gt; or a &lt;code&gt;CatalogBuilder&lt;/code&gt; with a custom
&lt;code&gt;RecordResolveAction&lt;/code&gt;. We hook our custom code into the action by overriding the 
&lt;code&gt;OnResolve(ResolveTarget&amp;lt;Table&amp;gt;)&lt;/code&gt; method. This way we can replace the column headers in the tables
right after they have been resolved with product data.
&lt;/p&gt;&lt;p&gt;
The full code for the custom &lt;code&gt;RecordResolveAction&lt;/code&gt; is listed below. We assume that the images for the column
headers are stored in ADAM records and that we can find the record image for each label by doing a field value search (e.g.
the image record for the weight column has a field called "Label" with value "Weight"). Once we have loaded our image record, 
we create an
image element for it. Finally we clear the text items in the cell's paragraph and add the image
element to it, yielding an inline (or anchored) image element.
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using System.Linq;
using Adam.Core;
using Adam.Core.Records;
using Adam.Core.Search;
using Adam.DocMaker.Core;
using Adam.DocMaker.Core.Geometry;
using Adam.PageBuilder.Core.Resolve;

namespace CustomDev
{
 public class CustomRecordResolveAction : RecordResolveAction
 {
  public CustomRecordResolveAction(Application application) : base(application)
  {
  }

  protected override void OnResolve(ResolveTarget&amp;lt;Table&amp;gt; tableTarget)
  {
   base.OnResolve(tableTarget);

   // Iterate over the cells in the table header row.
   foreach (Cell cell in tableTarget.Item.Rows.First().Cells)
   {
    // Load the image that corresponds to the label in the column heading cell.
    string label = cell.ToText().Trim();
    Record imageRecord = new Record(App);
    imageRecord.Load(new SearchExpression(string.Format("Label='{0}'", label)));

    // Create a new image box to place in the cell.
    Document document = tableTarget.Document;
    RectangleD imageBox = new RectangleD(0, 0, 16, 16);
    ImageElement imageElement = document.CreateImageElement(imageBox);
    imageElement.ReplaceImage(ReplaceImageSource.Record, imageRecord.Id);
    imageElement.ObjectStyle = 
     document.ObjectStyles.Single(style =&amp;gt; style.Name == "Column Header");

    // Clear the contents of the cell and put the image in it instead.
    Paragraph paragraph = cell.Paragraphs.Single();    
    paragraph.Items.RemoveAll();
    paragraph.Items.Add(imageElement);
   }
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Note that we also apply an object style to the image element. This is optional but can be used to apply certain effects, like
frame fitting options, transparency, etc. Any style you want to use here must first be defined in the design template document.
&lt;/p&gt;&lt;p&gt;
That's it for today. If you too have been following this blog series and you are struggling with something that 
might make an interesting blog topic, please let us know. We always welcome all the feedback we can get.
&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Adam.JSGenerator v1.3 has been released</title><link>http://blogs.adamsoftware.net/Announcements/AdamJSGeneratorv13hasbeenreleased.aspx</link><description>&lt;div&gt;&lt;p&gt;
Earlier today, Adam.JSGenerator v1.3 was released to &lt;a target='_blank' href="http://adamjsgenerator.codeplex.com/"&gt;CodePlex&lt;/a&gt; and &lt;a target='_blank' href="http://nuget.org/packages/Adam.JSGenerator"&gt;NuGet&lt;/a&gt;. New features include support for the 'this' keyword as well as inserting statement snippets in addition to expression snippets. The main page on the CodePlex project site gives more details about what's new and improved.
&lt;/p&gt;&lt;p&gt;
What is Adam.JSGenerator? It's a .NET library that allows one to write C# code that emits JavaScript and is used in our own software to produce the JavaScript snippets required for Web development and in the code that interacts with Adobe products.
&lt;/p&gt;&lt;p&gt;
You can include Adam.JSGenerator easily in Visual Studio, provided that you have the NuGet package manager installed. To add it to a project, simply use the NuGet package manager, or at the NuGet command line type "Install-Package Adam.JSGenerator".
&lt;/p&gt;&lt;p&gt;
If you want to learn more, check out &lt;a target='_blank' href="http://blogs.adamsoftware.net/Announcements/IntroducingAdamJSGenerator.aspx"&gt;the original blogpost of 1.0&lt;/a&gt; and visit the &lt;a target='_blank' href="http://adamjsgenerator.codeplex.com"&gt;CodePlex project page&lt;/a&gt;.
&lt;/p&gt;&lt;/div&gt;</description></item><item><title>ABC Positioning Catalog builder</title><link>http://blogs.adamsoftware.net/PageBuilder/ABCPositioningCatalogbuilder.aspx</link><description>&lt;div&gt;&lt;p&gt;
                                To wrap up 2012 we will look at another advanced example of custom development for
                                PageBuilder: ABC Positioning.
                            &lt;/p&gt;&lt;p&gt;
                                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?
                            &lt;/p&gt;&lt;h3&gt;
                                Introduction&lt;/h3&gt;&lt;p&gt;
                                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.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;a target='_blank' href='http://blogs.adamsoftware.net/Files/da19223e4c43474e8b776aedb3cd43c2.jpg' target="new"&gt;&lt;img width="300px" src='http://blogs.adamsoftware.net/Files/da19223e4c43474e8b776aedb3cd43c2.jpg' /&gt;&lt;/a&gt;&lt;p&gt;
                                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.
                            &lt;/p&gt;&lt;h3&gt;
                                Templates&lt;/h3&gt;&lt;p&gt;
                                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.
                            &lt;/p&gt;&lt;img width="600px" src='http://blogs.adamsoftware.net/Files/14cf94bf08ba4f388d37a207df7605c6.jpg' /&gt;&lt;p&gt;
                                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.
                                &lt;br /&gt;
                                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.
                            &lt;/p&gt;&lt;img width="600px" src='http://blogs.adamsoftware.net/Files/412423e4cfcd48d09455f64daabe05bc.jpg' /&gt;&lt;h3&gt;
                                Creating the CatalogBuilder&lt;/h3&gt;&lt;p&gt;
                                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').&lt;/p&gt;&lt;pre name="c#"&gt;
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&amp;lt;char&amp;gt; charactersUsedOnPage = new List&amp;lt;char&amp;gt;();
            PagePaginateAction = new AbcPagePaginateAction(application, charactersUsedOnPage);
            RecordPaginateAction = new AbcRecordPaginateAction(application, charactersUsedOnPage);
        }

        private class AbcRecordPaginateAction : RecordPaginateAction {...}

        private class AbcPagePaginateAction : PagePaginateAction {...}
    }
}
             &lt;/pre&gt;&lt;p&gt;
                                As you can see a &lt;code&gt;charactersUsedOnPage&lt;/code&gt; 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.
                            &lt;/p&gt;&lt;h3&gt;
                                Creating the PagePaginateAction&lt;/h3&gt;&lt;p&gt;
                                We'll start with the &lt;code&gt;PagePaginateAction&lt;/code&gt;. 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 &lt;code&gt;OnPaginatedPage&lt;/code&gt; method.&lt;br /&gt;
                                Below you can find the code of the custom &lt;code&gt;PagePaginateAction&lt;/code&gt; class.
                            &lt;/p&gt;&lt;pre name="c#"&gt;
private class AbcPagePaginateAction : PagePaginateAction
{
    private readonly IList &amp;lt;char&amp;gt; _charactersUsedOnPage;

    public AbcPagePaginateAction(Application application, IList&amp;lt;char&amp;gt; charactersUsedOnPage) 
        : base(application)
    {
        _charactersUsedOnPage = charactersUsedOnPage;
    }

    // Clear the characters list.
    protected override void OnPaginatedPage(PageTarget pageTarget)
    {
        _charactersUsedOnPage.Clear();
    }
}
             &lt;/pre&gt;&lt;p&gt;
                                Pretty simple no? Lets continue with the real stuff.
                            &lt;/p&gt;&lt;h3&gt;
                                Creating the RecordPaginateAction&lt;/h3&gt;&lt;p&gt;
                                Let's take a look to the code in the custom &lt;code&gt;RecordPaginateAction&lt;/code&gt; class.
                                &lt;br /&gt;
                                As you can see, the constructor is equal to the one of our &lt;code&gt;PagePaginateAction&lt;/code&gt;.
                                &lt;code&gt;_boxPadding&lt;/code&gt; is the amount of padding that is used when placing a new
                                image into the green column. &lt;code&gt;_characters&lt;/code&gt; is the available list of characters
                                to be used for the items that are going to be placed on the page.
                            &lt;/p&gt;&lt;pre name="c#"&gt;
private class AbcRecordPaginateAction : RecordPaginateAction
{
    private const int _boxPadding = 10;
    private const string _characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  
    private readonly IList&amp;lt;char&amp;gt; _charactersUsedOnPage;

    public AbcRecordPaginateAction(Application application, IList&amp;lt;char&amp;gt; charactersUsedOnPage)
     : base(application)
    {
        _charactersUsedOnPage = charactersUsedOnPage;
    }

    protected override void OnPlaced(PlaceTarget placeTarget) {...}

    private static void AddItemGroup(
        Document document, 
        TextElement abcElement, 
        FileVersion fileVersion)
    {...}
}
             &lt;/pre&gt;&lt;p&gt;
                                We are overriding the &lt;code&gt;OnPlaced&lt;/code&gt; 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 &lt;code&gt;OnPlaced&lt;/code&gt; method. The inline comments
                                should be clear enough to see what's happening.
                            &lt;/p&gt;&lt;pre name="c#"&gt;
protected override void OnPlaced(PlaceTarget placeTarget)
{
    // Find the first available character for current page.
    char currentCharacter = _characters.ToCharArray().First(c =&amp;gt; !_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&amp;lt;TextElement&amp;gt;().Single(e =&amp;gt; e.Story.ToText() == "X");
    abcElement.Story.Paragraphs.Single().Items.OfType&amp;lt;Run&amp;gt;().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);
    }
}
                            &lt;/pre&gt;&lt;p&gt;
                                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.
                            &lt;/p&gt;&lt;pre name="c#"&gt;
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 =&amp;gt; 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 =&amp;gt; element.Shape.Bounds)
        .Single(b =&amp;gt; b.Height &amp;gt; 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 =&amp;gt; e.Shape.Bounds)
        .Where(b =&amp;gt; b.IntersectsWith(columnBounds))
        .Select(b =&amp;gt; b.Bottom - columnBounds.Top)
        .OrderByDescending(b =&amp;gt; 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);
}
                            &lt;/pre&gt;&lt;p&gt;
                                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!&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Category label path in linked items grid</title><link>http://blogs.adamsoftware.net/PIMS/Categorylabelpathinlinkeditemsgrid.aspx</link><description>&lt;div&gt;&lt;p&gt;
One of the many great features in PIM Studio is the linked items panel that allows you to link a product to other products.
In addition to linking them, you can also edit these linked products.
&lt;/p&gt;&lt;p&gt;
For the linked items panel we provided a way so you can configure many different types of columns.
Out of the box we provide a variety of column types, but we know we will never be able to concieve and create all the customer specific cases.
To tackle that, we have created the linked items panel so you can create your own types of columns.
&lt;/p&gt;&lt;p&gt;
In this post we will provide a column that will show the category paths of each linked product.
We will also show you how to add this new type of column to an existing linked items panel.
This way we hope to give you a very simple example that will help you create all the customer specific columns you need.
&lt;/p&gt;&lt;h3&gt;Creating a column&lt;/h3&gt;&lt;p&gt;
There is one base class you must inherit from in order to add the column to the linked items panel.
It is a .NET base class that is used in the columns collection of a .NET GridView: System.Web.UI.WebControls.DataControlField.
&lt;/p&gt;&lt;p&gt;
When inheriting the DataControlField type you need to implement the abstract CreateField method:
&lt;/p&gt;&lt;pre name="C#"&gt;
protected override DataControlField CreateField()
{
 return new CategoryPathsField();
}
&lt;/pre&gt;&lt;p&gt;
Only implementing the CreateField method will only get us a blank column.
If you want to add content you need to override the InitializeCell method.
The InitializeCell method is called each time a cell is created.
There is a small pitfall: this method is called for cells containing data but also for header and footer cells.
&lt;/p&gt;&lt;pre name="C#"&gt;
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
{
 if (cellType == DataControlCellType.DataCell)
 {
  // when the cellType is DataCell, it means you are in a cell that is part of a data row
  InitializeDataCell(cell);
 }
 else
 {
  base.InitializeCell(cell, cellType, rowState, rowIndex);
 }
}
&lt;/pre&gt;&lt;p&gt;
We now introduced a new method called InitializeDataCell which will create all the necessary setup for displaying something.
The method looks like this:
&lt;/p&gt;&lt;pre name="C#"&gt;
private void InitializeDataCell(DataControlFieldCell cell)
{
 // we create a Literal that will hold the category paths
 Literal placeHolderForCategoryPath = new Literal();

 placeHolderForCategoryPath.DataBinding += PlaceHolderForCategoryPath_DataBinding;

 // add the Literal to the control collection of the cell
 cell.Controls.Add(placeHolderForCategoryPath);
}
&lt;/pre&gt;&lt;p&gt;
You might think "why are you hooking into the DataBinding method" and "why aren't you setting any value".
The reason is simple: there is no data item available when InitializeCell is called.
You can only access the data item when the DataBinding event is fired.
That's why we are hooking in to the DataBinding event.
&lt;/p&gt;&lt;p&gt;
When the DataBinding event is fired on the Literal placeholder, we will fetch the data item and retrieve the category paths.
&lt;/p&gt;&lt;pre name="C#"&gt;
private void PlaceHolderForCategoryPath_DataBinding(object sender, EventArgs e)
{
 Literal placeHolderForCategoryPath = (Literal)sender;

 // fetch the data item that we will visualize the category paths of
 object dataItem = placeHolderForCategoryPath.Page.GetDataItem();

 IEnumerable&amp;lt;Guid&amp;gt; categoryIds = GetCategoryIdsOfDataItem(dataItem);

 IEnumerable&amp;lt;string&amp;gt; categoryLabelPaths = RetrieveLabelPaths(categoryIds);

 // display the category label paths in the Literal
 placeHolderForCategoryPath.Text = FormatLabelPathsForDisplay(categoryLabelPaths);
}

private IEnumerable&amp;lt;Guid&amp;gt; GetCategoryIdsOfDataItem(object dataItem)
{
 Product product = dataItem as Product;
 if (product == null)
 {
  return new Guid[0];
 }

 return product.Classifications.CopyManualClassificationIdsToArray();
}

private IEnumerable&amp;lt;string&amp;gt; RetrieveLabelPaths(IEnumerable&amp;lt;Guid&amp;gt; categoryIds)
{
 CategoryHelper categoryHelper = new CategoryHelper(AdamContext.GetApplication());
 IDictionary&amp;lt;Guid, LabelPath&amp;gt; labelPaths = categoryHelper.GetLabelPaths(categoryIds);
 return labelPaths.Values.Select(x =&amp;gt; x.ToString());
}

private string FormatLabelPathsForDisplay(IEnumerable&amp;lt;string&amp;gt; categoryLabelPaths)
{
 StringBuilder formattedPaths = new StringBuilder();

 foreach(string categoryLabelPath in categoryLabelPaths)
 {
  if (!string.IsNullOrEmpty(formattedPaths.ToString()))
  {
   formattedPaths.Append("&amp;lt;br/&amp;gt;");
  }

  formattedPaths.Append(HttpUtility.HtmlEncode(categoryLabelPath));
 }

 return formattedPaths.ToString();
}
&lt;/pre&gt;&lt;p&gt;
To make things nicer we will add a header text to the column like this:
&lt;/p&gt;&lt;pre name="C#"&gt;
public override string HeaderText
{
 get
 {
  // always show "Category paths" as the header of the column
  return "Category paths";
 }
 set
 {
 }
}
&lt;/pre&gt;&lt;h3&gt;Allowing the column to be added to the LinkedItemsPanel&lt;/h3&gt;&lt;p&gt;
We now have a column that can be added to a GridView.
 But we have to make sure that the column can be added to the xml configuration of the LinkedItemsPanel.
To do that, we must implement the Adam.Pims.UI.Controls.IConfigurableField interface.
The interface makes us implement two methods: Read and Write.
These two methods allow us to read and write xml configuration that is specific for this column.
If for instance you would like to be able to configure the header text of the column, these methods should be implemented.
In the Read method you would add code that reads the header text from the given xml element.
In the Write method you would add code that writes the header text in the given xml element.
Since we aren't doing anything special in this example, we do nothing in these methods:
&lt;/p&gt;&lt;pre name="C#"&gt;
public void Read(XElement element)
{
 // write code here in order to read configuration for this column from the element parameter
}

public void Write(XElement element)
{
 // write code here in order to write the configuration of this column to the element parameter
}
&lt;/pre&gt;&lt;h3&gt;Adding the column to the config of a LinkedItemsPanel&lt;/h3&gt;&lt;p&gt;
We still need to add a bit of xml to the configuration of linked items panel.
In the columns node of the xml setting you can add the following xml:
&lt;/p&gt;&lt;pre name="XML"&gt;
&amp;lt;add type="Adam.Blog.Pims.CategoryPathsField, Adam.Blog.Pims" /&amp;gt;
&lt;/pre&gt;&lt;h3&gt;Make sure your custom dll is accessible&lt;/h3&gt;&lt;p&gt;
For PIM Studio to pick up on the class, it needs to be able to find your dll.
To be able to access your dll you can add the following things:
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Add dll to GAC&lt;/li&gt;&lt;li&gt;Add dll to bin folder of website&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</description></item><item><title>Using custom InDesign tags in PageBuilder 4</title><link>http://blogs.adamsoftware.net/PageBuilder/UsingcustomInDesigntagsinPageBuilder4.aspx</link><description>&lt;div&gt;&lt;p&gt;
Today we will look at a more advanced way of extending PageBuilder through custom development.
In InDesign, you can add structured metadata to the content in your documents.
In fact, this is how the PageBuilder Tagger plug-in stores any layout options and links to ADAM in the templates you create.
But you can also define your own tags and attributes which can be accessed through the PageBuilder API.
&lt;/p&gt;&lt;p&gt;
Suppose you want to develop a wild new feature that is currently not covered by the out-of-the-box behavior.
Say you want to add a variable horizontal offset to the item groups that are added to your catalog.
An ADAM field will be used to store the desired offset for each record or product in the database.
Here's what a simple record in ADAM for an item group with an offset of 40 points might look like:
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/d24e7219e58b4051b138db1b5ea06a42.png' /&gt;&lt;/p&gt;&lt;p&gt;
To implement this feature, you write a custom &lt;code&gt;CatalogBuilder&lt;/code&gt; that offsets each item group after it has been added to the catalog (we will show some code later on).
Your new feature turns out to be quite popular among your customers, but you notice that each customer wants to use a different field to specify the offset. 
In fact, some of your customers want to use multiple offset fields for different design templates.
&lt;/p&gt;&lt;p&gt;
Ideally, you want to be able to specify the offset field that should be used within the design template itself.
That way your feature can be used in a much more generic way. So you tell your customers that they can choose the offset field
by tagging an arbitrary element in their design templates and adding an attribute with a predefined key to it.
Below you can see a design template where we have tagged an image and specified that the field with name "HorizontalOffset" should be used to determine the offset for each item group:
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/19316bce65f9485b850f45c1bff0f1db.png' /&gt;&lt;/p&gt;&lt;p&gt;
Let's take a look at some code now. As hinted on earlier, our custom &lt;code&gt;CatalogBuilder&lt;/code&gt; uses a 
custom &lt;code&gt;RecordPaginateAction&lt;/code&gt; which overrides the &lt;code&gt;OnPlaced&lt;/code&gt; method (in a way that is very
similar to previous blog posts). Here's the implementation for our &lt;code&gt;OffsetRecordPaginateAction&lt;/code&gt;:
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using System;
using System.Collections.Generic;
using System.Linq;
using Adam.Core;
using Adam.Core.Fields;
using Adam.Core.Records;
using Adam.DocMaker.Core;
using Adam.DocMaker.Core.Geometry;
using Adam.PageBuilder.Core.Extensions;
using Adam.PageBuilder.Core.Paginate;

namespace CustomTags
{
 public class OffsetRecordPaginateAction : RecordPaginateAction
 {
  private const string _attributeKey = "OffsetFieldName";

  public OffsetRecordPaginateAction(Application application) : base(application)
  {
  }

  protected override void OnPlaced(PlaceTarget placeTarget)
  {
   // Select the tags on the elements in the placed item group.
   IEnumerable&amp;lt;Tag&amp;gt; tags = placeTarget.Elements
    .Select(element =&amp;gt; element.Tag)
    .Where(tag =&amp;gt; tag != null);

   // Look for an attribute that specifies the name of the offset field to use.
   string fieldName = null;
   if (tags.Any(tag =&amp;gt; tag.Attributes.TryGetValue(_attributeKey, out fieldName)))
   {
    // Load the record that corresponds to the placed item group.
    Guid? recordId = placeTarget.Elements.GetResolvedRecordId();
    if (recordId.HasValue)
    {
     Record record = new Record(App);
     record.Load(recordId.Value);

     // If the offset field can be found in the current record,
     // use its value to offset all elements in the item group.
     NumericField field = record.Fields[fieldName].MyLanguage as NumericField;
     if (field != null)
     {
      double offset = (double)field.Value;
      foreach (Element element in placeTarget.Elements)
      {
       element.MoveBy(new SizeD(offset, 0));
      }
     }
    }
   }
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
The code and comments should be quite straightforward. The interesting bit is that custom tags and attributes
that were added in the InDesign template can be accessed through the PageBuilder API and can be used to implement
new functionality. While today's example may seem a bit far stretched, several partners have requested this kind of
extensibility. The next step could be to develop your own InDesign plug-in with an attractive UI to replace 
manual editing of tags and attributes.
&lt;/p&gt;&lt;p&gt;
Of course we still need to show what the result of running our code looks like (using the kind of records and design template shown above). 
In the catalog pictured below, each item group has been placed with a horizontal offset that was specified in the corresponding ADAM record:
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/be6be13f2a684c4ea16d0ecb3cd1d6a8.jpg' /&gt;&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Writing a custom Notification Agent</title><link>http://blogs.adamsoftware.net/Engine/WritingacustomNotificationAgent.aspx</link><description>&lt;div&gt;&lt;p&gt;
In this post, we'll walk you to the process of creating a custom Notification Agent for your maintenance jobs. 
Out of the box, there's a email agent that sends some information when a maintenance job has completed or failed.
&lt;/p&gt;&lt;pre name="XML"&gt;
&amp;lt;engines&amp;gt;
  &amp;lt;add name="MaintenanceJobEmailAgent" type="Adam.Core.Maintenance.MaintenanceJobEmailAgent, Adam.Core" /&amp;gt;
&amp;lt;/engines&amp;gt;
&lt;/pre&gt;&lt;p&gt;
In this post, we'll write a similar custom notification agent that will send a basic email by means of a web service. 
&lt;/p&gt;&lt;h3&gt;Writing a simple Web Service&lt;/h3&gt;&lt;p&gt;
Our email will be sent through a web service. Below you see a very simple (and rather naive) webmethod that will send an email with some basic status information of the maintenance job.
&lt;/p&gt;&lt;pre name="C#"&gt;
[WebMethod]
public void NotifyMaintenanceJob(string status, int totalTargets, int succeededTargets, int failedTargets)
{
    string body = string.Format("Total targets: {0}\r\nSucceeded targets: {1}\r\nFailed targets: {2}",
                                totalTargets, succeededTargets, failedTargets);
    SmtpClient mailClient = new SmtpClient("smtp.yoursmtpserver.com");
    mailClient.Send(@"noreply@mycompany.com",
                    @"kevin@mycompany.com", 
                    "Maintenance Job " + status, 
                    body);
}
&lt;/pre&gt;&lt;h3&gt;Writing a custom Notification Agent&lt;/h3&gt;&lt;p&gt;
Writing a custom notification agent is also really simple: just create a new class that inherits from MaintenanceJobNotificationAgent and implement its abstract method Execute. 
In the Execute method we will call the webmethod we created in our web service and pass the maintenance job parameters.
&lt;/p&gt;&lt;pre name="C#"&gt;
public class WebServiceNotificationAgent : MaintenanceJobNotificationAgent
{
 public WebServiceNotificationAgent(Application app) : base(app)
 {
 }

 public override void Execute(MaintenanceJob job)
 {
  NotificationServiceSoapClient client = 
   new NotificationServiceSoapClient(
    new BasicHttpBinding(), 
    new EndpointAddress(@"http://localhost:62196/NotificationService.asmx"));
  client.NotifyMaintenanceJob(job.Status.ToString(), job.Targets.Count, job.SucceededTargets.Count, job.FailedTargets.Count);
 }
}
&lt;/pre&gt;&lt;b&gt;Note:&lt;/b&gt; make sure you have deployed your webservice so it can be called by the notification agent.
&lt;h3&gt;Register the Notification Agent&lt;/h3&gt;&lt;p&gt;
When you are finished coding you only need to register and activate your custom provider into ADAM. 
First you need to register your custom assembly. 
Secondly, you need to add your notification agent to the list of registered notification agents in the .maintenanceJobNotificationAgents setting. 
&lt;pre name="XML"&gt;
&amp;lt;engines&amp;gt;
  &amp;lt;add name="MaintenanceJobEmailAgent" type="Adam.Core.Maintenance.MaintenanceJobEmailAgent, Adam.Core" /&amp;gt;
  &amp;lt;add name="WebServiceNotificationAgent" type="BlogNotificationAgent.WebServiceNotificationAgent, BlogNotificationAgent" /&amp;gt;
&amp;lt;/engines&amp;gt;
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
That's all there is to it! Next time your maintenance jobs are executed, you should receive a simple notification email looking something like this:
&lt;/p&gt;&lt;br /&gt;&lt;image src='http://blogs.adamsoftware.net/Files/a7ddf78cb3494970a3cd6f6810f7bcb7.png' /&gt;&lt;/div&gt;</description></item><item><title>How to suffix repeated product headers</title><link>http://blogs.adamsoftware.net/PageBuilder/Howtosuffixrepeatedproductheaders.aspx</link><description>&lt;div&gt;&lt;p&gt;
One of the key new features of PageBuilder 4 is the ability to automatically split item groups for 
single products across multiple columns or pages in a catalog. The PageBuilder plug-in for InDesign allows you 
to specify which boxes in your templates are suitable locations for breaking up the design and which text
boxes can be split up when the layout demands it:
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/3d213b05f8de40c0b15ed4ff1a2171b7.png' /&gt;&lt;/p&gt;&lt;p&gt;
You can also specify that a certain box in your design template should be repeated on the next
page or column whenever an item group is split up. This way you can ensure that each column or page 
starts with a product title or identifier to provide additional context while browsing the catalog.
&lt;/p&gt;&lt;p&gt;
You may want to add an ellipsis or other suffix to repeated product headers to distinguish them from the initial 
product headers. In this blog post we show how to achieve this with just a few lines of custom code.
&lt;/p&gt;&lt;p&gt;
The most straightforward place to add our suffix to the repeated product headers is in the &lt;code&gt;OnPlaced&lt;/code&gt; 
method of a custom &lt;code&gt;RecordPaginateAction&lt;/code&gt;. This method is called whenever the elements of an item group
have been placed in the catalog. The placed elements, which may be split across multiple pages or columns, are passed to 
the method through the &lt;code&gt;PlaceTarget&lt;/code&gt; argument. The code below shows how to iterate over the elements
and add a suffix to each repeated product header:
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using System.Collections.Generic;
using System.Linq;
using Adam.Core;
using Adam.DocMaker.Core;
using Adam.PageBuilder.Core.Build;
using Adam.PageBuilder.Core.Paginate;

class CustomRepeatBoxCatalogBuilder : CatalogBuilder
{
 public CustomRepeatBoxCatalogBuilder(Application application) : base(application)
 {
  RecordPaginateAction = new CustomRecordPaginateAction(application);
 }
}

class CustomRecordPaginateAction : RecordPaginateAction
{
 private const string _suffix = " […]";

 public CustomRecordPaginateAction(Application application) : base(application)
 {
 }

 protected override void OnPlaced(PlaceTarget placeTarget)
 {
  IEnumerable&amp;lt;Element&amp;gt; elements = placeTarget.Elements;

  foreach (TextElement element in elements.OfType&amp;lt;TextElement&amp;gt;().Where(e =&amp;gt; e.IsRepeatBox(elements)))
  {
   Run lastRun = element.Story.Paragraphs.Last().Items.OfType&amp;lt;Run&amp;gt;().Last();
   lastRun.Value += _suffix;
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
This code sample uses a hardcoded suffix string, but you could also use the &lt;code&gt;Tag&lt;/code&gt; property of the 
&lt;code&gt;CatalogBuilder&lt;/code&gt; to pass the suffix from the PageBuilder Studio UI to the custom code, yielding
a more flexible solution for the end user.
&lt;/p&gt;&lt;p&gt;
Checking whether a text element is a repeated product header is done through an extension method that looks for 
another element in the item group with the same content and a preceding position (either on a previous page or
in a previous column):
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using System.Collections.Generic;
using System.Linq;
using Adam.DocMaker.Core;

static class ElementExtensions
{
 public static bool IsRepeatBox(this Element element, IEnumerable&amp;lt;Element&amp;gt; referenceElements)
 {
  return referenceElements.Any(e =&amp;gt; (e.ToText() == element.ToText()) &amp;amp;&amp;amp; e.Precedes(element));
 }

 public static bool Precedes(this Element element, Element referenceElement)
 {
  return (element.OwnerSpread.Index &amp;lt; referenceElement.OwnerSpread.Index)
   || ((element.OwnerSpread.Index == referenceElement.OwnerSpread.Index)
    &amp;amp;&amp;amp; (element.Shape.Bounds.Left &amp;lt; referenceElement.Shape.Bounds.Left));
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
And that pretty much wraps it for today. Here's what the result might look like:
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/bed37ec8f1e54ca9abb4799c4df2d275.png' /&gt;&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Why CIOs are increasingly interested in DAM</title><link>http://blogs.adamsoftware.net/Sales_&amp;_Marketing/WhyCIOsareincreasinglyinterestedinDAM.aspx</link><description>&lt;div&gt;&lt;p&gt; New technologies have created many new types of marketing and have increased the relative importance of marketing activities within (global) corporations. Marketing however has not been subject to the same levels of accountability found in other domains, such as Production. The easy ride for Marketing is now set to end, however, as CEOs increase pressure on CMOs to show greater responsibility for their revenue performance. Adding to this pressure are rapidly evolving marketing challenges such as globalization, online media distribution, process-based organization, and cross-channel communication.
  &lt;/p&gt;&lt;p&gt;These developments will mean CMOs and CIOs have to work together in new ways to establish a common roadmap for responding to new pressures. A core element of this new alliance will be the integration of a DAM platform into Marketing's infrastructure. &lt;/p&gt;&lt;p&gt; To find out why CIOs are increasingly interested in DAM, read our new &lt;a target='_blank' href="http://www.adamsoftware.net/en/knowledge-base/business-information/white-papers/why-cios-are-increasingly-interested-in-dam/"&gt;White Paper&lt;/a&gt; and tune in to our &lt;a target='_blank' href="http://www.adamsoftware.net/en/knowledge-base/webcasts-and-webinars/future-webcasts-sign-up/webinar-signup-why-cios-are-increasingly-interested-in-dam/"&gt;webinar&lt;/a&gt; on the 10th of November.&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Generating an index for your automated catalog</title><link>http://blogs.adamsoftware.net/PageBuilder/Generatinganindexforyourautomatedcatalog.aspx</link><description>&lt;div&gt;&lt;p&gt;
In our ongoing series on custom development for PageBuilder, we illustrate how the PageBuilder 4 API can be used
to customize the default behavior, to extend the available features, or to implement entirely new features on top of the 
existing functionality. 
&lt;/p&gt;&lt;p&gt;
Today's topic discusses the possibility to dynamically add an index to your automated catalogs. 
In order to get a clear idea of what we want to achieve, here is a screenshot showing the first page of our 
auto-generated index:
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/c4f63d554a7e4364b4a2fdf8ae9a7e9d.png' /&gt;&lt;/p&gt;&lt;p&gt;
There are two basic steps involved in generating an index: building the index and outputting it to the document.
Our index is represented in memory using a &lt;code&gt;Dictionary&lt;/code&gt; which maps keywords to sets of page numbers.
Building the index is accomplished through a custom &lt;code&gt;RecordPaginateAction&lt;/code&gt;: whenever a product is added
to the catalog, the corresponding keywords and page numbers are added to the index. Outputting the index is achieved
with a custom &lt;code&gt;DocumentAction&lt;/code&gt; which is executed after all products have been added to the catalog.
This is what the code for our custom &lt;code&gt;CatalogBuilder&lt;/code&gt; looks like:
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using System.Collections.Generic;
using System.Linq;
using Adam.Core;
using Adam.PageBuilder.Core;
using Adam.PageBuilder.Core.Build;

using Index = System.Collections.Generic.Dictionary&amp;lt;string, System.Collections.Generic.HashSet&amp;lt;int&amp;gt;&amp;gt;;

namespace AutoIndexEngine
{
 class AutoIndexCatalogBuilder : CatalogBuilder
 {
  private readonly Index _index = new Index();

  public AutoIndexCatalogBuilder(Application application) : base(application) 
  {
   RecordPaginateAction = new BuildIndexAction(application, _index);
  }

  protected override IEnumerable&amp;lt;DocumentAction&amp;gt; Actions
  {
   get
   {
    return base.Actions.Concat(new List&amp;lt;DocumentAction&amp;gt; { new OutputIndexAction(App, _index) });
   }
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
The implementation of the &lt;code&gt;BuildIndexAction&lt;/code&gt; that builds our index is also pretty straightforward. 
The code in the &lt;code&gt;OnPlaced&lt;/code&gt; method is executed whenever a new product has been placed in the catalog. 
Both the product record and the page number can be retrieved through the &lt;code&gt;PlaceTarget&lt;/code&gt; argument that 
is passed along to this method. We assume each record has a &lt;code&gt;Keywords&lt;/code&gt; field containing a comma-separated 
list of keywords for the product:
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using System.Collections.Generic;
using System.Linq;
using Adam.Core;
using Adam.Core.Fields;
using Adam.Core.Records;
using Adam.PageBuilder.Core.Paginate;

using Index = System.Collections.Generic.Dictionary&amp;lt;string, System.Collections.Generic.HashSet&amp;lt;int&amp;gt;&amp;gt;;

namespace AutoIndexEngine
{
 class BuildIndexAction : RecordPaginateAction
 {
  private readonly Index _index;

  public BuildIndexAction(Application application, Index index) : base(application)
  {
   _index = index;
  }

  protected override void OnPlaced(PlaceTarget placeTarget)
  {
   RecordPlaceTarget productTarget = (RecordPlaceTarget)placeTarget;

   Record record = new Record(App);
   record.Load(productTarget.ResolvedRecordId.Value);

   TextField field = (TextField)record.Fields["Keywords"].MyLanguage;
   string keywords = field.Value;

   int pageNumber = placeTarget.Elements.First().Pages.Single().PageNumber;

   foreach (string keyword in keywords.Split(','))
   {
    if (!_index.ContainsKey(keyword))
    {
     _index[keyword] = new HashSet&amp;lt;int&amp;gt;();
    }
    _index[keyword].Add(pageNumber);
   }
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Our &lt;code&gt;OutputIndexAction&lt;/code&gt; implementation makes extensive use of the DocMaker 3.2 API.
We start by adding the required additional pages and text boxes to the document. All text boxes are linked
together (sharing the same story) so all content automatically flows from one text box to another. Most of the work here
consists of calculating the bounds for each box, depending on the desired number of columns and space between the columns.
&lt;/p&gt;&lt;p&gt;
Once all page boxes have been added, we process our index structure and add content to the index story.
The keywords are sorted alphabetically and grouped by first character. Then we start adding the paragraphs and formatted runs 
containing the first characters, keywords and aggregated page numbers: 
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Adam.Core;
using Adam.DocMaker.Core;
using Adam.DocMaker.Core.Geometry;
using Adam.PageBuilder.Core;

using Index = System.Collections.Generic.Dictionary&amp;lt;string, System.Collections.Generic.HashSet&amp;lt;int&amp;gt;&amp;gt;;
using IndexEntry = System.Collections.Generic.KeyValuePair&amp;lt;string, System.Collections.Generic.HashSet&amp;lt;int&amp;gt;&amp;gt;;

namespace AutoIndexEngine
{
 class OutputIndexAction : DocumentAction
 {
  private const int _pageCount = 3;
  private const int _columnCount = 4;
  private const int _boxPadding = 10;

  private const string _defaultFont = "Calibri";
  private static readonly FontColor _defaultColor = new FontColor(37, 64, 143);

  private const string _titleFont = "Harlow Solid Italic";
  private static readonly FontColor _titleColor = new FontColor(65, 173, 73);

  private const string _title = "Catalog Index";

  private readonly Index _index;

  public OutputIndexAction(Application application, Index index) : base(application)
  {
   _index = index;
  }

  protected override void OnExecute(Document document)
  {
   Story indexStory = AddPageBoxes(document);

   IEnumerable&amp;lt;IGrouping&amp;lt;char, IndexEntry&amp;gt;&amp;gt; indexEntryGroups 
    = _index.OrderBy(pair =&amp;gt; pair.Key).GroupBy(pair =&amp;gt; pair.Key.ToUpperInvariant().First());

   foreach (IGrouping&amp;lt;char, IndexEntry&amp;gt; indexEntryGroup in indexEntryGroups)
   {
    char firstCharacter = indexEntryGroup.Key;

    indexStory.AddParagraph().AddRun(firstCharacter.ToString(), _defaultFont, _defaultColor, 36, true);

    foreach (IndexEntry pair in indexEntryGroup)
    {
     Paragraph paragraph = indexStory.AddParagraph();

     string pageNumbers = pair.Value.Aggregate(
      new StringBuilder(),
      (builder, pageNumber) =&amp;gt; builder.Append(", " + pageNumber),
      builder =&amp;gt; builder.ToString().Substring(2));

     paragraph.AddRun(string.Format("{0}: ", pair.Key), _defaultFont, _defaultColor, 12, true);
     paragraph.AddRun(pageNumbers, _defaultFont, _defaultColor, 12, false);
    }
   }
  }

  private static Story AddPageBoxes(Document document)
  {
   Story story = null;
   for (int pageIndex = 0; pageIndex &amp;lt; _pageCount; ++pageIndex)
   {
    Page page = document.AddPage();
    page.MasterSpread = null;
    RectangleD bounds = page.ContentBounds;

    if (story == null)
    {
     // When adding the first box, we also add the title box and subtract the needed area.
     const double boxHeight = 40;
     page
      .AddBox(new RectangleD(bounds.X, bounds.Y, bounds.Width, boxHeight), null)
      .AddParagraph()
      .AddRun(_title, _titleFont, _titleColor, 24, true);

     const double offset = boxHeight + _boxPadding;
     bounds = new RectangleD(bounds.X, bounds.Y + offset, bounds.Width, bounds.Height - offset);
    }

    double textBoxWidth = (bounds.Width - (_columnCount - 1) * _boxPadding) / _columnCount;

    for (int columnIndex = 0; columnIndex &amp;lt; _columnCount; ++columnIndex)
    {
     double left = bounds.X + columnIndex * (textBoxWidth + _boxPadding);
     RectangleD boxBounds = new RectangleD(left, bounds.Y, textBoxWidth, bounds.Height);
     story = page.AddBox(boxBounds, story);
    }
   }
   return story;
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
For the sake of completeness, here is the implementation of the few extension methods that are conveniently used in the above code:
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using Adam.DocMaker.Core;
using Adam.DocMaker.Core.Geometry;

namespace AutoIndexEngine
{
 static class IndexExtensions
 {
  public static Story AddBox(this Page page, RectangleD boxBounds, Story indexStory)
  {
   if (indexStory != null)
   {
    TextElement box = page.OwnerDocument.CreateTextElement(boxBounds, indexStory);
    page.OwnerSpread.Elements.Add(box);
    return indexStory;
   }
   else
   {
    TextElement box = page.OwnerDocument.CreateTextElement(boxBounds);
    page.OwnerSpread.Elements.Add(box);
    return box.Story;
   }
  }

  public static Paragraph AddParagraph(this Story story)
  {
   Paragraph paragraph = story.OwnerDocument.CreateParagraph();
   story.Paragraphs.Add(paragraph);
   return paragraph;
  }

  public static Run AddRun(this Paragraph paragraph, string value, string font, FontColor color, int size, bool bold)
  {
   Run run = paragraph.OwnerDocument.CreateRun();
   paragraph.Items.Add(run);

   run.Value = value;
   run.Formatting.FontSize = size;
   run.Formatting.Bold = bold;
   run.Formatting.FontFamily = font;
   run.Formatting.FontColor = color;

   return run;
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
All classes should be compiled together in a class library that is registered either in the GAC or in the ADAM
database. You can then use the &lt;code&gt;AutoIndexCatalogBuilder&lt;/code&gt; type to build your catalogs from within PageBuilder Studio, 
as explained in previous blog posts and in the PageBuilder 4 Developer Guide.
&lt;/p&gt;&lt;p&gt;
Of course all code in this blog post is meant for illustrative purposes only and should not be used directly in production. 
Note that all font properties and layout parameters are hard-coded in constant values. A more flexible approach would use the 
&lt;code&gt;Tag&lt;/code&gt; property to pass these parameters from the PageBuilder Studio UI to the &lt;code&gt;CatalogBuilder&lt;/code&gt; class.
&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Change in the license key request process</title><link>http://blogs.adamsoftware.net/Announcements/Changeinthelicensekeyrequestprocess.aspx</link><description>&lt;div&gt;&lt;p&gt;Dear customer/partner,&lt;/p&gt;&lt;p&gt;In order to streamline the license key request flow and to shorten the delays during implementation of some ADAM engines, we have slightly changed our license key request process.&lt;/p&gt;&lt;p&gt;To facilitate the transfer of essential information for a license key request, we have created an Excel sheet that bundles all required data.&lt;/p&gt;&lt;p&gt;Note: You are not obliged to use this &lt;a target='_blank' href='http://blogs.adamsoftware.net/Files/33cb4564fe624748b2af497a29613a17.xlsx'&gt;Excel form&lt;/a&gt;. If you choose not to (for example when sending a license key request by e-mail), please make sure that you send us all the information that is listed in the form. Any missing information means we will have to re-contact you to gather the missing data which will lead to unnecessary delays in the process.&lt;/p&gt;&lt;p&gt;If you are planning to do a new installation you can speed up the license key creation process by sending us the completed form upfront. During the installation process itself, you’ll only need to send us the database ID using the same file produced by the ADAM Software installer as you already do today.&lt;/p&gt;&lt;p&gt;If you are planning to upgrade an existing ADAM installation and you plan to keep using the same database, you can send us the completed Excel form some days in advance and you will receive your new key prior to starting the actual upgrade.&lt;/p&gt;&lt;p&gt;In both cases, the Excel form or a mail containing the same data can be sent to &lt;a target='_blank' href="mailto:licenserequests@adamsoftware.net"&gt;licenserequests@adamsoftware.net&lt;/a&gt;&lt;/p&gt;&lt;p&gt;We hope that this modified license key request process will smoothen and speed up your ADAM installation and upgrade process.&lt;/p&gt;&lt;p&gt;If you would have any questions on this matter, do not hesitate to contact &lt;a target='_blank' href="mailto:licenserequests@adamsoftware.net"&gt;licenserequests@adamsoftware.net&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</description></item><item><title>The ADAM 4.6 Roadshow - Part 2</title><link>http://blogs.adamsoftware.net/Engine/TheADAM46RoadshowPart2.aspx</link><description>&lt;div&gt;&lt;p&gt;
                                With ADAM 4.6 released some time ago, it's time to bring some of its cool new
                                features into the daylight. Today I'm revealing the addition of a separate user
                                group permission (role) for modifying field values.&lt;/p&gt;&lt;h3&gt;
                                Field Group Permissions Demystified&lt;/h3&gt;&lt;p&gt;
                                Up until ADAM 4.6, there have always been two permissions (roles) for setting security
                                on fields from the user/user group perspective:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Manage profile and fields of field group &lt;i&gt;X&lt;/i&gt;&lt;/li&gt;&lt;li&gt;Read profile and fields of field group &lt;i&gt;X&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;a target='_blank' href='http://blogs.adamsoftware.net/Files/536daefb029c4ff0960b61b93bac9505.jpg' target="_blank"&gt;&lt;img src='http://blogs.adamsoftware.net/Files/536daefb029c4ff0960b61b93bac9505.jpg' width="600px" /&gt;&lt;/a&gt;&lt;p&gt;
                                The first permission allows users or user groups to edit the profile and fields
                                in the field group. This means the user or group has &lt;i&gt;full control&lt;/i&gt; on the
                                field group and can change its name and members. It is important to know that this
                                permission has &lt;b&gt;nothing&lt;/b&gt; to do with the actual value of any fields
                                in the subject field group.&lt;/p&gt;&lt;p&gt;
                                The latter permission means that the field group and any fields within are visible
                                to the user or user group, allowing them to &lt;b&gt;edit&lt;/b&gt; the values as
                                well (for as long as access to the field has not been denied elsewhere). You should
                                note that this permission excludes the former permission: one cannot edit the profile
                                and members of field groups without explicitly having read permissions on said field
                                group.&lt;/p&gt;&lt;p&gt;
                                Summarizing, in order to be able to edit the value of a field, you only need the
                                "Read profile and fields of field group &lt;i&gt;X&lt;/i&gt;" permission, however,
                                denying this permission renders the field invisible to the user. In order to add
                                fields to a field group, or change the field group's name, you'll need "Manage
                                profile and fields of field group &lt;i&gt;X&lt;/i&gt;" &lt;b&gt;and&lt;/b&gt; "Read
                                profile and fields of field group &lt;i&gt;X&lt;/i&gt;".&lt;/p&gt;&lt;h3&gt;
                                But I Wanted It Read-only, Not Gone :-(&lt;/h3&gt;&lt;p&gt;
                                Some use cases require that fields are rendered read-only rather than hidden when
                                a user does not have the permission to modify its value. As you can deduce from
                                the previous section, in the dark times before ADAM 4.6, this was not possible...&lt;/p&gt;&lt;p&gt;
                                But, those days are now gone for good. As of this version, you have three permissions
                                for controlling how a user sees a field group:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Manage profile and fields of field group &lt;i&gt;X&lt;/i&gt;&lt;/li&gt;&lt;li&gt;Read profile and fields of field group &lt;i&gt;X&lt;/i&gt;&lt;/li&gt;&lt;li&gt;Change field values of field group &lt;i&gt;X&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;a target='_blank' href='http://blogs.adamsoftware.net/Files/3f237d8a670a4f86a6aebe7c1bcf5cb7.jpg' target="_blank"&gt;&lt;img src='http://blogs.adamsoftware.net/Files/3f237d8a670a4f86a6aebe7c1bcf5cb7.jpg' width="600px" /&gt;&lt;/a&gt;&lt;p&gt;
                                The last one, and also the new one, is used to control whether or not the user/user
                                group can &lt;b&gt;edit&lt;/b&gt; the value of fields in the field group.&lt;/p&gt;&lt;p&gt;
                                This means that the purpose of the "Read profile and fields of field group
                                &lt;i&gt;X&lt;/i&gt;" permission has changed to controlling the &lt;b&gt;visibility&lt;/b&gt;
                                of the field.&lt;/p&gt;&lt;h3&gt;
                                How About Upgrading?&lt;/h3&gt;&lt;p&gt;
                                When upgrading to ADAM 4.6, nothing changes to your current settings! The "Read
                                profile and fields of field group &lt;i&gt;X&lt;/i&gt;" permission is simply split and
                                the new "Change field values of field group &lt;i&gt;X&lt;/i&gt;" permission is
                                given the same value as the former.&lt;/p&gt;&lt;p&gt;
                                The only thing you need to do is getting used to using the new permission, and think
                                of this whenever you unexpectedly find a read-only field.&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Visitors revisited with DocMaker 3.2</title><link>http://blogs.adamsoftware.net/DocMaker_Studio/VisitorsrevisitedwithDocMaker32.aspx</link><description>&lt;div&gt;&lt;p&gt;
In a previous blog post, we illustrated how to implement a Visitor pattern on top of the DocMaker 3.1 API.
This approach allowed to quickly define new document operations without needing to worry about the details of the
Document data structure.
&lt;/p&gt;&lt;p&gt;
With DocMaker 3.2, it becomes even easier to implement document visitors because the Visitor pattern has now been 
implemented as part of the public API. Both the &lt;code&gt;IVisitor&lt;/code&gt; interface  and the &lt;code&gt;DefaultVisitor&lt;/code&gt; 
implementation are defined within the &lt;code&gt;Adam.DocMaker.Core&lt;/code&gt; namespace. Each relevant class in the 
API implements an &lt;code&gt;Accept&lt;/code&gt; method, allowing it to accept any visitor that implements the 
&lt;code&gt;IVisitor&lt;/code&gt; interface.
&lt;/p&gt;&lt;p&gt;
We will take a look at the same example as in our previous blog post, i.e. a visitor collecting all paragraph styles
that are actively being used in the document. This is what the implementation of our visitor looks like using DocMaker 3.2:
&lt;/p&gt;&lt;p&gt;&lt;pre name="C#"&gt;
using Adam.DocMaker.Core;
using System.Collections.Generic;
using System.Linq;

namespace VisitorSample
{
 public class CollectParagraphStylesVisitor : DefaultVisitor
 {
  private readonly List&amp;lt;ParagraphStyle&amp;gt; _styles = new List&amp;lt;ParagraphStyle&amp;gt;();

  public IEnumerable&amp;lt;ParagraphStyle&amp;gt; CollectedStyles
  {
   get
   {
    return _styles.Distinct();
   }
  }

  public override bool Visit(Paragraph paragraph)
  {
   _styles.Add(paragraph.ParagraphStyle);
   return true;
  }
 }
}
&lt;/pre&gt;
This is pretty much the same code we used before. The main difference is that the &lt;code&gt;DefaultVisitor&lt;/code&gt; base 
class is now implemented as part of the DocMaker API. Also note that the &lt;code&gt;Visit&lt;/code&gt; method returns a boolean 
value designating whether the items in the paragraph should be visited by the visitor. In this case we return 
&lt;code&gt;true&lt;/code&gt; because a paragraph may contain tables with more paragraphs.
&lt;/p&gt;&lt;p&gt;
The code for visiting a document is also identical to the code we used before:
&lt;pre name="C#"&gt;
public static IEnumerable&amp;lt;ParagraphStyle&amp;gt; GetActiveParagraphStyles(Document document)
{
 CollectParagraphStylesVisitor visitor = new CollectParagraphStylesVisitor();
 document.Accept(visitor);
 return visitor.CollectedStyles;
}
&lt;/pre&gt;
Here the only difference is that the &lt;code&gt;Accept&lt;/code&gt; method is no longer an extension method but is instead 
implemented directly in the &lt;code&gt;Document&lt;/code&gt; class. With DocMaker 3.2, you can implement document visitors 
just like you could before, but you no longer need to implement the Visitor pattern yourself.
&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Extending a table with extra columns</title><link>http://blogs.adamsoftware.net/PageBuilder/Extendingatablewithextracolumns.aspx</link><description>&lt;div&gt;&lt;p&gt;
                                We &lt;a target='_blank' href="http://blogs.adamsoftware.net/Announcements/PageBuilder4Released.aspx" target="new"&gt;promised&lt;/a&gt; to add some interesting and advanced samples on how
                                the new PageBuilder can be extended to your needs.
                            &lt;/p&gt;&lt;p&gt;
                                Our third custom development example makes it possible to dynamically add columns
                                to an existing table, depending on the data available in your database structure.
                            &lt;/p&gt;&lt;p&gt;
                                Suppose you are building a product that contains a description, an image, and a
                                table. The table contains the SKUS of the product (each row is a SKU record). The
                                columns in the table represent fields of these SKU records.
                            &lt;/p&gt;&lt;p&gt;
                                Take a look at following previews. The left image is an itemGroup built with the
                                out of the box PageBuilder. As you can see there are three Field columns in the
                                table, because the template contained three columns.
                                &lt;br /&gt;
                                The image on the right is the result of the custom engine we will discuss below.
                                The table now consists of 3 extra columns, added by the custom code.
                                &lt;br /&gt;
                                Let's take a look how to accomplish this.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;img width="250px" src='http://blogs.adamsoftware.net/Files/e6f10abea51a46db995988deff65b0a0.jpg' /&gt;      
                            &lt;img width="250px" src='http://blogs.adamsoftware.net/Files/f657104cd5c34634b2e4a311c54b484f.jpg' /&gt;&lt;p&gt;
                                We are building ItemGroups, so what we need is a custom &lt;code&gt;ItemGroupBuilder&lt;/code&gt;:
                            &lt;/p&gt;&lt;pre name="c#"&gt;
using Adam.Core;
using Adam.PageBuilder.Core.Build;

namespace ExtendedModelEngine
{
    class ExtendedModelItemGroupBuilder : ItemGroupBuilder
    {
        public ExtendedModelItemGroupBuilder(Application application)
            : base(application)
        {
            // Pass the ItemGroupBuilder, so we can access its properties from 
            // within the ResolveAction.
            RecordResolveAction = new ExtendedModelResolveAction(application, this);
        }
    }
}
             &lt;/pre&gt;&lt;p&gt;
                                For this functionality we will add additional information while resolving, so we
                                need to override the &lt;code&gt;RecordResolveAction&lt;/code&gt;.
                            &lt;/p&gt;&lt;p&gt;
                                We want to override the &lt;code&gt;OnResolve&lt;/code&gt; method with the &lt;code&gt;TableTarget&lt;/code&gt;
                                of this action. As there is only one table in our design, we do not need to check
                                if this is the correct table.
                                &lt;br /&gt;
                                It's important to know that we want to add our additional columns, after the whole
                                table was filled. Therefore, the &lt;code&gt;OnResolve&lt;/code&gt; method of the base class
                                must be called first.&lt;br /&gt;&lt;/p&gt;&lt;pre name="c#"&gt;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Adam.Core;
using Adam.Core.Fields;
using Adam.Core.Records;
using Adam.Tools.ExceptionHandler;
using Adam.DocMaker.Core;
using Adam.PageBuilder.Core.Extensions;
using Adam.PageBuilder.Core.Resolve;

namespace ExtendedModelEngine
{
    private class ExtendedModelResolveAction : RecordResolveAction
    {
        private readonly ExtendedModelItemGroupBuilder _builder;
        private string _linkFieldName;
        private string _caseFieldName;

        public ExtendedModelResolveAction(Application application, ExtendedModelItemGroupBuilder builder)
            : base(application)
        {
            _builder = builder;
        }

        protected override void OnResolve(ResolveTarget&amp;lt;Table&amp;gt; tableTarget)
        {
            base.OnResolve(tableTarget);

            _linkFieldName = XDocument.Parse(_builder.Tag).Elements("linkField").Single().Value;
            _caseFieldName = XDocument.Parse(_builder.Tag).Elements("caseField").Single().Value;
            FieldDefinitionHelper helper = new FieldDefinitionHelper(App);

            // Select all body rows
            Table table = tableTarget.Item;

            // Find the new column titles and the rows that must be filled in.
            SortedDictionary&amp;lt;string, List&amp;lt;int&amp;gt;&amp;gt; extraColumns = FindNewColumnsInformation(table);

            // Create and fill the new columns.
            CreateNewColumns(table, extraColumns);

            // Adjust the width of all columns.
            AdjustColumnWidths(table, extraColumns.Count);
        }
    }
}
             &lt;/pre&gt;&lt;p&gt;
                                We have added the possibility to provide your custom &lt;code&gt;ItemGroupBuilder&lt;/code&gt;
                                with a Tag. This tag (a &lt;code&gt;String&lt;/code&gt;) can contain anything you want. For
                                this demo we have chosen for a xml format that contains all fieldNames necessary
                                for extending our table. Let's take a look to the xml structure:
                                &lt;br /&gt;&lt;/p&gt;&lt;pre name="xml"&gt;
&amp;lt;fields&amp;gt;
  &amp;lt;linkField&amp;gt;Cases&amp;lt;/linkField&amp;gt;
  &amp;lt;caseField&amp;gt;CaseName&amp;lt;/caseField&amp;gt;
&amp;lt;/fields&amp;gt;
                            &lt;/pre&gt;&lt;p&gt;
                                The first field 'Cases' is a &lt;code&gt;RecordLinkField&lt;/code&gt;. The product record contains
                                this field, and has 0, 1, 2, 3 or 4 links. Each link has the &lt;code&gt;TextField&lt;/code&gt;
                                'CaseName'. We will use the name of the case as the header of a new column. Suppose
                                there are 4 different cases (maximum 4 links), and the possibilities are Gigbag,
                                Soft case, Hard case and Deluxe case. Each guitar model (SKUs) may have a case,
                                or can have more than 1 available cases. When no model of this guitar has a particular
                                case, the column will not be shown. In the example above, you can see that this
                                guitar has no model with a Deluxe casing.&lt;br /&gt;
                                When a model has a case available, this will be shown by a symbol in the corresponding
                                row/column.
                            &lt;/p&gt;&lt;p&gt;
                                As you can see in the above code sample, there are three submethods. &lt;code&gt;SortedDictionary&lt;/code&gt;
                                will collect all information necessary for adding the columns. Which columns are
                                needed, and what models support each case.&lt;br /&gt;&lt;code&gt;CreateNewColumns&lt;/code&gt; will create the necessary columns and enter the correct
                                information.&lt;br /&gt;&lt;code&gt;AdjustColumnsWidths&lt;/code&gt; recalculates the width of the columns. This is
                                needed because when a column is added in InDesign, the table will not get bigger,
                                but the available table space will be devided between the number of columns. We
                                want to give all the columns our own width, because the new columns need less space
                                than the existing columns.
                            &lt;/p&gt;&lt;p&gt;
                                We start with collecting all necessary info. We are using a &lt;code&gt;SortedDictionary&lt;/code&gt;
                                with the case name as a key. In this way the columns will also be sorted by case
                                name. The value of the &lt;code&gt;SortedDictionary&lt;/code&gt; contains a &lt;code&gt;List&lt;/code&gt;
                                with the indices of body rows in the table. Only the models that have a specific
                                case will be added in this list.
                            &lt;/p&gt;&lt;pre name="c#"&gt;
private SortedDictionary&amp;lt;string, List&amp;lt;int&amp;gt;&amp;gt; FindNewColumnsInformation(Table table)
{
    // Only select the body rows of the table
    Row[] bodyRows = table.Rows.Where(r =&amp;gt; r.RowType == RowType.Body).ToArray();

    SortedDictionary&amp;lt;string, List&amp;lt;int&amp;gt;&amp;gt; newColumns = new SortedDictionary&amp;lt;string, List&amp;lt;int&amp;gt;&amp;gt;();
    for (int rowIndex = 0; rowIndex &amp;lt; bodyRows.Length; rowIndex++)
    {
        // The run must contain a record.
        Run run = (Run)bodyRows[rowIndex].Cells.First().Paragraphs.First().Items.First();
        Guid? linkId = run.GetExtractedRecordId();

        // Load the record and get the field value.
        Record record = new Record(App);
        record.Load(linkId.Value);

        FieldContainer modelsLinkField = record.Fields[_linkFieldName];
        RecordLinkField field = (RecordLinkField)modelsLinkField.MyLanguage;

        Record modelRecord = new Record(App);
        foreach (RecordLinkItem item in field.Children)
        {
            modelRecord.Load(item.RecordId);
            FieldContainer modelsField = modelRecord.Fields[_modelFieldName];

            string modelName = ((TextField)modelsField.MyLanguage).Value;
            // If our dictionary does not contain the new column, add to dictionary.
            if (!newColumns.ContainsKey(modelName))
            {
                newColumns.Add(modelName, new List&amp;lt;int&amp;gt;());
            }
            // Add the index of current row.
            newColumns[modelName].Add(rowIndex);
        }
    }
    return newColumns;
}
                                &lt;/pre&gt;&lt;p&gt;
                                Once the information is complete, we can start by adding the columns to the table.
                                For each key in the &lt;code&gt;SortedDictionary&lt;/code&gt; a column is added. In every cell,
                                a single &lt;code&gt;Paragraph&lt;/code&gt; with a single &lt;code&gt;Run&lt;/code&gt; must be created.
                                A header &lt;code&gt;Run&lt;/code&gt; contains the key, the &lt;code&gt;Run&lt;/code&gt;s in a body row
                                contain a special character. In this case a bullet in the 'Windings 2' font.&lt;/p&gt;&lt;pre name="c#"&gt;
private static void CreateNewColumns(Table table, SortedDictionary&amp;lt;string, List&amp;lt;int&amp;gt;&amp;gt; extraColumns)
{
    Row[] bodyRows = table.Rows.Where(r =&amp;gt; r.RowType == RowType.Body).ToArray();

    Document document = table.OwnerDocument;
    foreach (string columnName in extraColumns.Keys)
    {
        table.AddColumn();

        // Fill the columnName in the header of the column.
        Row headerRow = table.Rows.Where(r =&amp;gt; r.RowType == RowType.Header).Single();
        if (headerRow != null)
        {
            // As an example we are creating a paragraph from a template. In this way the formatting 
            // and the contents of the template will be copied to our new one. So we only need to replace
            // the value of the Run instead of creating it.
            Paragraph paragraph = document.CreateParagraph(headerRow.Cells.First().Paragraphs.First());
            ((Run)paragraph.Items.Single()).Value = columnName;
            headerRow.Cells.Last().Paragraphs.Add(paragraph);
        }

        // Every row in the column that contains this option is given a special Wingdings character.
        foreach (int rowIndex in extraColumns[columnName])
        {
            Paragraph paragraph = document.CreateParagraph();
            ParagraphFormatting formatting = bodyRows[rowIndex].Cells.First().Paragraphs.First().Formatting;
            paragraph.Formatting.Alignment = formatting.Alignment;

            Run run = document.CreateRun();
            run.Value = Char.ConvertFromUtf32('\u0050');
            run.Formatting.FontFamily = "Wingdings 2";
            run.Formatting.Bold = true;
            paragraph.Items.Add(run);

            bodyRows[rowIndex].Cells.Last().Paragraphs.Add(paragraph);
        }
    }
}
                                &lt;/pre&gt;&lt;p&gt;
                                As a last step we need to resize the columns. The new columns are given a fixed
                                width, the original columns are recalculated.&lt;/p&gt;&lt;pre name="c#"&gt;
private static void AdjustColumnWidths(Table table, int columnCount)
{
    // The original columns need to be resized to fit the contents,
    // the new columns are given the value of 30 points.
    const int newColumnWidth = 30;
    int originalColumns = table.ColumnCount - columnCount;
    int newColumns = columnCount;

    double resizedColumnWidth = (table.Width - (newColumns * newColumnWidth)) / originalColumns;

    // Start with existing columns
    for (int i = 0; i &amp;lt; originalColumns; i++)
    {
        table.SetColumnWidth(i, resizedColumnWidth);
    }
    // New columns
    for (int i = originalColumns; i &amp;lt; table.ColumnCount; i++)
    {
        table.SetColumnWidth(i, newColumnWidth);
    }
}
                                &lt;/pre&gt;&lt;p&gt;
                                This ends our third PageBuilder 4.0 Blog post. As a wrap up we'd like to remind
                                you that you can also extend the Tag information in this code. It is by example
                                possible to add the fontFamily and the character code to the xml to make it possible
                                to change the 'bullets' in the columns. In this way different combinations can be
                                tested with small effort...&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Adding an ad to your catalog</title><link>http://blogs.adamsoftware.net/PageBuilder/Addinganadtoyourcatalog.aspx</link><description>&lt;div&gt;&lt;p&gt;
 Lets say we are creating a new version of the yellow pages, and we want to add an advertisement on every even page. In our &lt;a target='_blank' href="http://blogs.adamsoftware.net/PageBuilder/Addingvariablepageheaderstoyourautomatedcatalog.aspx"&gt;previous blogpost&lt;/a&gt; we've seen how easy it is to add a variable header into your automated catalog trough custom development. Now for this requirement we should do more or less the same.
&lt;/p&gt;&lt;p&gt;
 So, as a first step, we create a new class that implements the CatalogBuilder baseclass. Second, create a new PagePaginateAction who will be responsible for adding the ad into our yellow page catalog.
&lt;/p&gt;&lt;pre name="C#"&gt;
 using Adam.Core;
 using Adam.PageBuilder.Core.Build;

 namespace AdvertisingEngine
 {
  public class YellowPageAdvertisingCatalogBuilder : CatalogBuilder
  {
   public YellowPageAdvertisingCatalogBuilder(Application application) : base(application)
   {
    PagePaginateAction = new YellowPageAdvertisingPaginateAction(application);
   }
  }
  
  public class YellowPageAdvertisingPaginateAction : PagePaginateAction
  {
   public YellowPageAdvertisingPaginateAction(Application application) : base(application)
   {
   }

   protected override void OnPaginatingPage(PageTarget pageTarget)
   {
    Page page = pageTarget.Page;
    if (page.PageNumber % 2 == 1)
    {
     Guid classificationId = page.MasterSpread.Elements.GetResolvedClassificationId().Value;
     Classification classification = new Classification(App);
     classification.Load(classificationId);

     string expression = string.Format(
      CultureInfo.InvariantCulture,
      "File.Version.FileName = {0}.jpg",
      classification.Name);
      
     Record advertisementRecord = new Record(App);
     advertisementRecord.Load(new SearchExpression(expression));

     IReadOnlyImage image = advertisementRecord.Files.LatestMaster.GetPreview();
     double aspectRatio = (double)image.Height / image.Width;

     RectangleD columnBounds = page.ColumnBounds.Last();
     PointD location = new PointD(columnBounds.Left, columnBounds.Top);
     SizeD imageBoxSize = new SizeD(columnBounds.Width, columnBounds.Width * aspectRatio);
     RectangleD imageBounds = new RectangleD(location, imageBoxSize);

     ImageElement imageElement = page.OwnerDocument.CreateImageElement(imageBounds);
     imageElement.ReplaceImage(ReplaceImageSource.Record, advertisementRecord.Id);
     page.OwnerSpread.Elements.Add(imageElement);
    }
   }
  }
 }
&lt;/pre&gt;&lt;p&gt;
 For adding the advertisement to the catalog, we override the &lt;i&gt;OnPaginatingPage&lt;/i&gt;. This method is called when a page is about to be paginated. In this piece of code, I assume that your ad is inside the resolved classification (you can get this through the &lt;i&gt;GetResolvedClassificationId&lt;/i&gt; extension method) and has the same name of this classification. Ofcourse you can apply any other valid search expression to find your advertisement.
&lt;/p&gt;&lt;p&gt;
 The next step is calculating the aspectratio of the image. Once we have that, we can easily add the image through the DocMaker API.  
 This can be accomplished by creating a new ImageElement and adding it to the paginated page. Now we only have to register our dll into ADAM and ad a new custom provider to the PageBuilder build catalog engines. This is explained &lt;a target='_blank' href="http://blogs.adamsoftware.net/PageBuilder/Addingvariablepageheaderstoyourautomatedcatalog.aspx"&gt;here&lt;/a&gt;&lt;/p&gt;&lt;p&gt;That's it for now, but stay tuned for more PageBuilder goodies in the coming weeks.&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Generating movie previews in the cloud</title><link>http://blogs.adamsoftware.net/Engine/Generatingmoviepreviewsinthecloud.aspx</link><description>&lt;div&gt;&lt;p&gt;&lt;b&gt;Cloud Computing?&lt;/b&gt;&lt;p&gt;&lt;/p&gt;
Lately everybody is talking about it. So at ADAM's PlayDay last week, I decided to have a look what these 
"cloud services" could mean within an ADAM context. 
&lt;/p&gt;&lt;h3&gt;Why consider the "cloud"?&lt;/h3&gt;&lt;p&gt;
The biggest advantage of cloud computing is that it provides us a bunch of services and resources that are accessible with minimal configuration.
In this blog post, I will show you how to implement a Media Engine that uses a cloud based video software solution, &lt;a target='_blank' href="http://www.zencoder.com"&gt;Zencoder&lt;/a&gt;, 
to generate movie previews. 
&lt;/p&gt;&lt;h4&gt;About Zencoder&lt;/h4&gt;&lt;p&gt;
Zencoder is cloud-based video and audio encoding software as a service (&lt;a target='_blank' href="http://en.wikipedia.org/wiki/Software_as_a_service"&gt;SaaS&lt;/a&gt;). It is hosted on &lt;a target='_blank' href="http://aws.amazon.com/"&gt;Amazon Web Services&lt;/a&gt;. 
Zencoder provides many, many encoding features, but for this post we'll stick to the basics, i.e. creating a preview and some thumbnails of a movie file.
&lt;/p&gt;&lt;h4&gt;Advantages&lt;/h4&gt;
The main advantages of using a cloud based encoding solutions like Zencoder are:
&lt;li&gt;&lt;b&gt;Computing power&lt;/b&gt;: video encoding is a very intensive operation that requires a lot of CPU/GPU power. Instead of having to buy one or more high-end servers, 
you can benefit from the immense and very scalable computing power available in the cloud.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Pricing model&lt;/b&gt;: instead of having to buy very expensive licenses, cloud based solutions often offer different plans accustomed to your usage of the services. 
Zencoder for instance offers a "pay-as-you-go" formula (for lightweight usage) up to a "growth" license suitable for the most demanding customers. For testing purposes, 
there's even a completely free test license available (off course with some encoding limitations).&lt;/li&gt;&lt;li&gt;&lt;b&gt;No third party tool installation and configuration&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;No codecs need to be installed&lt;/b&gt;&lt;/li&gt;&lt;h3&gt;Writing a cloud based Media Engine&lt;/h3&gt;&lt;p&gt;
First thing I did, was signup for a Test license on the Zencoder website. You'll receive an API key which is passed with each Zencoder request.
&lt;/p&gt;&lt;p&gt;
Next up is choosing your way of communicating with the Zencoder API. This API is REST based, so it's possible to only use plain old HTTP GET/POST/PUT requests and some JSON. 
However, there are already a couple of managed code libraries available which make it even simpler to send our encoding requests. In this post, I've used the excellent open source 
(MIT license) &lt;a target='_blank' href="https://github.com/ChadBurggraf/zencoder-cs"&gt;C# Zencoder library&lt;/a&gt; of Chad Burggraf.
&lt;/p&gt;&lt;h4&gt;Setup&lt;/h4&gt;&lt;p&gt;
Before going further with the actual implementation, let's discuss how Zencoder processes video encoding requests.
&lt;/p&gt;&lt;p&gt;
When you want to encode a video, evidently it needs to be accessible to Zencoder. Therefore we need to put in on a place where Zencoder can upload and process it. 
There are four options available:
&lt;ol&gt;&lt;li&gt;HTTP/HTTPS&lt;/li&gt;&lt;li&gt;FTP/SFTP&lt;/li&gt;&lt;li&gt;&lt;a target='_blank' href="http://aws.amazon.com/s3/"&gt;Amazon S3&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a target='_blank' href="http://www.rackspace.com/cloud/cloud_hosting_products/files/"&gt;Rackspace Cloud Files&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;
Once the encoding job has completed, Zencoder can upload the generated previews to any of the last three of these options. 
If none of these options are provided to the request, Zencoder will host the outputs on its own storage for 24 hours, from where they can be downloaded.
&lt;/p&gt;&lt;p&gt;
For this demo, let's simply put the movie files into the public folder of a dropbox account. This folder can be accessed by anyone through an HTTP address 
(e.g. http://dl.dropbox.com/u/&amp;lt;account_number&amp;gt;/plane.avi). 
Needless to say, this is NOT the way to go for production processes :-)
&lt;/p&gt;&lt;p&gt;
Afterwards, we will download the generated outputs ourselves from the Zencoder storage.
&lt;/p&gt;&lt;h4&gt;Implementation&lt;/h4&gt;&lt;p&gt;
So let's look at the implementation now. Once you've made your movie available through any of the described options, you're ready to send the encoding request to Zencoder.
&lt;/p&gt;&lt;pre name="C#"&gt;
Zencoder zencoder = new Zencoder("your-api-key-goes-here");
CreateJobResponse response = zencoder.CreateJob(action.Path,
    new[]
    {
        new Output
        {
            Label = "converted",
            FileName = Guid.NewGuid()+".mp4", // output file name
            Thumbnails = new []{new Thumbnails{ Label = "Thumbs", Number = 3}}, // generated 3 thumbnails
   // Note: check zencoder documentation for many more encoding options.
        }
    });
&lt;/pre&gt;&lt;p&gt;
Notice the response object we get on line 2. This will tell us if the encoding job was correctly created and provides us with the details of the created job (e.g. job id).
Since the Zencoder API is completely asynchronous, you'll need this information to get the progress status of your job.
&lt;/p&gt;&lt;pre name="C#"&gt;
JobProgressResponse progress = zencoder.JobProgress(response.Outputs.First().Id);
while (progress.State != OutputState.Finished)
{
...
}
&lt;/pre&gt;
Note: Zencoder also has an extensive notification framework that you can use, to automatically get notified when a job has completed or failed (through HTTP, email or custom handlers). 
&lt;p&gt;
Once the job has completed we can get the location of the generated files and download them.
&lt;/p&gt;&lt;pre name="C#"&gt;
Job details = zencoder.JobDetails(response.Id).Job;
using (WebClient webClient = new WebClient())
{
 // download the movie preview
 Uri url = new Uri(details.OutputMediaFiles.First().Url);
 string previewFileName = App.GetTemporaryFile("mp4");
 webClient.DownloadFile(url, previewFileName);
 action.MoviePreviews.Add(new MoviePreview(previewFileName));

 // download the image previews
 foreach (Thumbnail thumbnail in details.Thumbnails)
 {
  url = new Uri(thumbnail.Url);
  previewFileName = App.GetTemporaryFile("png");
  webClient.DownloadFile(url, previewFileName);
  action.ImagePreviews.Add(new Preview(previewFileName, -1));
 }
}
&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;b&gt;Cloud Computing!&lt;/b&gt;&lt;p&gt;
I hope this post has given you a small taster on how you can easily benefit from cloud services within ADAM. 
I've added a simple sample media engine that uses Zencoder to this post. To play with it, just create a free test account at Zencoder and put in your API key.
&lt;/p&gt;&lt;/div&gt;</description></item><item><title>The ADAM 4.6 Roadshow - Part 1</title><link>http://blogs.adamsoftware.net/Engine/TheADAM46RoadshowPart1.aspx</link><description>&lt;div&gt;&lt;p&gt;
    With ADAM 4.6 released 
    some time ago, it's time to bring some of its cool new 
    features into the daylight. In this article we are going to explore the 
    enhancements made to the already powerful maintenance framework that will make 
    it, well, epic.&lt;/p&gt;&lt;h3&gt;
                                Maintenance Framework Changes&lt;/h3&gt;&lt;p&gt;
                                In short, maintenance jobs can now have dependencies in the form of other 
                                maintenance jobs. This means that a job can only start execution after all 
                                dependencies have been executed. This works on any level, so you can basically 
                                chain multiple maintenance jobs together and they will nicely wait in line for 
                                their predecessors to execute.&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/ccb7ed3980d3419b897612bdf9d17be3.jpg' alt="The last job in line will only execute when all previous jobs have been executed sequentially." /&gt;&lt;/p&gt;&lt;p&gt;
                                Additionally, the splitting feature (you know, that thing that kicks in when you 
                                try to start maintenance jobs with lots of targets) was modified so it makes use 
                                of these dependencies. In concreto, you get a dependent maintenance job for each 
                                batch of targets and one job to rule them all, and report the results.&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/c8825be4686f46fa89a5bd9af1c205a9.jpg' alt="The main job has three split jobs, and will wait for them to execute before starting." /&gt;&lt;/p&gt;&lt;p&gt;
                                Now, a not very far-off scenario for this is to split a large maintenance job 
                                and let it execute on multiple machines and thus harvest the power of the many. 
                                This can be easily done by just setting up multiple maintenance machines (we 
                                lovingly call it a &lt;i&gt;maintenance farm&lt;/i&gt;) and just start creating large 
                                jobs. The only limitation here is that every machine in the farm has sufficient 
                                access to all shares and all data required for working its magic.&lt;/p&gt;&lt;h3&gt;
                                Ordering Framework Changes&lt;/h3&gt;&lt;p&gt;
                                Additionally, the ordering framework (which is based on the maintenance 
                                framework as you should know), has received support for splitting large orders. 
                                This means that you can execute an order and do the hard work of resizing, 
                                cropping, adding green clovers and red hearts on your &lt;i&gt;maintenance farm&lt;/i&gt;; 
                                and provide the results much faster than would be possible running on only one 
                                single machne.&lt;/p&gt;&lt;p&gt;
                                This functionality was developed on top of the maintenance framework changes 
                                introducing dependencies between maintenance jobs, or how one additional feature 
                                can provide many possibilities.&lt;/p&gt;&lt;/div&gt;</description></item><item><title>Adding variable page headers to your automated catalog</title><link>http://blogs.adamsoftware.net/PageBuilder/Addingvariablepageheaderstoyourautomatedcatalog.aspx</link><description>&lt;div&gt;&lt;p&gt;
When building an automated catalog using PageBuilder 4, you can specify a custom provider engine that
extends or changes the default behavior. In today's blog post we take a look at a concrete example.
&lt;/p&gt;&lt;p&gt;
Suppose you want to add running headers to your catalog, giving summarized information
about the products on each page (not unlike the page headers found in phone books or dictionaries).
&lt;/p&gt;&lt;p&gt;
We can accomplish this by implementing a custom &lt;code&gt;CatalogBuilder&lt;/code&gt;, which inherits all default functionality from
its base class, but also adds the functionality of adding page headers in a custom 
&lt;code&gt;PagePaginateAction&lt;/code&gt;:
&lt;pre name="C#"&gt;
using Adam.Core;
using Adam.PageBuilder.Core.Build;

namespace PageHeaderEngine
{
 class PageHeaderCatalogBuilder : CatalogBuilder
 {
  public PageHeaderCatalogBuilder(Application application) : base(application)
  {
   PagePaginateAction = new PageHeaderPaginateAction(application);
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
The most sensible time to add our header to each page is when all products have been added to that page, right before 
the next page is added to the catalog. This means we want to override the &lt;code&gt;OnPaginatedPage&lt;/code&gt; method of the 
&lt;code&gt;PagePaginateAction&lt;/code&gt;:
&lt;pre name="C#"&gt;
using System;
using System.Collections.Generic;
using System.Linq;
using Adam.Core;
using Adam.Core.Records;
using Adam.PageBuilder.Core.Extensions;
using Adam.PageBuilder.Core.Paginate;

namespace PageHeaderEngine
{
 class PageHeaderPaginateAction : PagePaginateAction
 {
  public PageHeaderPaginateAction(Application application) : base(application)
  {
  }

  protected override void OnPaginatedPage(PageTarget pageTarget)
  {
   IEnumerable&amp;lt;Guid&amp;gt; recordIds = pageTarget.GroupsOnPage
    .Select(group =&amp;gt; group.GetResolvedRecordId())
    .Where(id =&amp;gt; id.HasValue)
    .Select(id =&amp;gt; id.Value);

   RecordCollection recordCollection = new RecordCollection(App);
   recordCollection.Load(recordIds.ToList());
   IEnumerable&amp;lt;string&amp;gt; productIds = recordCollection.Cast&amp;lt;Record&amp;gt;()
    .Select(record =&amp;gt; record.Fields["ProductID"].MyLanguage.ToString());

   if (productIds.Any())
   {
    string pageHeader = 
     string.Format("{0}-{1}", productIds.First(), productIds.Last());
    AddHeaderBox(pageTarget.Page, pageHeader);
   }
  }
 }
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
The &lt;code&gt;PageTarget&lt;/code&gt; that is passed to the &lt;code&gt;OnPaginatedPage&lt;/code&gt; method contains a list with all item 
groups that have been placed on the page. For each item group, we can obtain the id of the record that has been used
to build the item group (using the &lt;code&gt;GetResolvedRecordId&lt;/code&gt; extension method).
&lt;/p&gt;&lt;p&gt;
Next we load the relevant records 
and collect the corresponding product identifiers. Of course you could use any other record field at this point,
depending on what information you want to show in the page header. In the example above, the page header string 
contains the id of the first and the last product on the page, separated by a hyphen.
&lt;/p&gt;&lt;p&gt;
Finally, in the &lt;code&gt;AddHeaderBox&lt;/code&gt; helper method, we use the DocMaker API to add a formatted run containing 
our page header string to a new text box in the top left corner of the current page:
&lt;pre name="C#"&gt;
private static void AddHeaderBox(Page page, string pageHeader)
{
 Document document = page.OwnerDocument;

 RectangleD bounds = new RectangleD(page.Bounds.Location, new SizeD(120, 20));
 TextElement headerBox = document.CreateTextElement(bounds.Offset(40, 40));
 page.OwnerSpread.Elements.Add(headerBox);

 Paragraph paragraph = document.CreateParagraph();
 headerBox.Story.Paragraphs.Add(paragraph);

 Run run = document.CreateRun();
 run.Value = pageHeader;
 run.Formatting.FontFamily = "Calibri";
 run.Formatting.FontSize = 14;
 run.Formatting.FontColor = new FontColor(65, 173, 73);
 paragraph.Items.Add(run);
}
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
That is all the code we are going to write today. We now need to compile our class library into an assembly which we can
register in the GAC or in the ADAM database (how to do this is explained in detail in the ADAM Developer Guide). Next
we add our custom provider to the &lt;b&gt;PageBuilder build catalog engines&lt;/b&gt; setting:
&lt;pre name="XML"&gt;
&amp;lt;engines&amp;gt;
  &amp;lt;add name="Default" 
    type="Adam.PageBuilder.Core.Build.CatalogBuilder, Adam.PageBuilder.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=3266306e8df4a2d3" /&amp;gt;
  &amp;lt;add name="PageHeaderEngine" 
    type="PageHeaderEngine.PageHeaderCatalogBuilder, PageHeaderEngine" /&amp;gt;
&amp;lt;/engines&amp;gt;
&lt;/pre&gt;&lt;/p&gt;&lt;p&gt;
Now we can select our &lt;b&gt;PageHeaderEngine&lt;/b&gt; when building a catalog in PageBuilder Studio.
The resulting catalog will look the same as before, but each page will contain a header with the identifiers
of the first and the last product paginated on that page, making it easy for customers to locate a product
based on its product id (assuming you choose to sort the products in the catalog by product id).
&lt;/p&gt;&lt;p&gt;
The usual warning applies that the above code is meant for illustrative purposes and should not be used directly in a
production environment. At the very least you should check for error conditions to make the code less vulnerable. 
&lt;/p&gt;&lt;/div&gt;</description></item><item><title>DocMaker 3.2 Released</title><link>http://blogs.adamsoftware.net/Announcements/DocMaker32Released.aspx</link><description>&lt;div&gt;&lt;p&gt;
Together with PageBuilder 4, we released a new version of DocMaker, our popular Web2Print solution.
&lt;/p&gt;&lt;p&gt;
DocMaker 3.2 is mostly an API upgrade, so there will be a lot of new material to cover in our developer blog 
in the coming months. New API features include adding new pages to a document, adding text boxes and images,
moving and resizing elements, applying object styles, executing custom scripts, and much more.
&lt;/p&gt;&lt;p&gt;
Some of the new features also show up in the rich text editor in DocMaker Studio. Can you spot the seven differences
between the screenshot below and what it would have looked like in previous versions of DocMaker?
&lt;/p&gt;&lt;p&gt;&lt;img src='http://blogs.adamsoftware.net/Files/420fa17822b54ca5a0d93874febbb656.png' width="600" /&gt;&lt;/p&gt;&lt;/div&gt;</description></item></channel></rss>
