Introduction
S# is a .NET scripting language. It is good for creating domain specific languages and embedding scripts into .NET application. Few days ago a great article revealing integration between S# and XAML in Silverlight was posted.
In this post I am going to show another useful application of S# language as an extensible markup for creating PDFs.
Obviously S# will not generate PDFs itself. There is a whole range of different components for producing PDF files. They are powerful in their functionality and may be used in .NET languages such as C# or S#.
I am going to use iText library. This is a library that allows you to generate PDF files on the fly. Here is a quote from official iText site: “Typically you won’t use it on your Desktop as you would use Acrobat or any other PDF application. Rather, you’ll build iText into your own applications so that you can automate the PDF creation and manipulation process.”
Let’s make a step further and wrap iText into S# scripting language to produce a markup language for generating PDF documents.
Goal
My aim is to get following script working and producing correct document like given on the picture below the script:
BeginDocument(“c:\\output.pdf”);
ActiveDocument.AddTitle(’Sample document’);
Paragraph(’Hello World’, ALIGN_CENTER);
Paragraph(’This is demo’, ALIGN_RIGHT, BLUE);
Paragraph(’This pdf was generated by S#’, GREEN);
BeginList();
ListItem(’One’);
ListItem(’Two’);
ListItem(’Three’);
EndList();
BeginTable(3);
Cell(’1′);
Cell(’One’);
Cell(Paragraph(’Description of One’, RED));
Cell(’2′);
Cell(’Two’);
Cell(’Description of Two’);
EndTable();
EndDocument();
Target PDF document:

Implementation
S# has many points for extensibility and customization. For the task purpose we will be using context customization via constants and custom functions.
Preferable pattern for such customization is to introduce a new facade over standard RuntimeHost class from S#:
public static class PdfRuntimeHost
{
public static void Initialize()
{
RuntimeHost.Initialize();
}
}
ScriptContext is a part Script instance. ScriptContext store run-time information about variables, functions, scopes, etc which will be used during script execution.
There are two static methods within Script class for compiling and executing code:
namespace Orbifold.SSharp
{
public class Script : IDisposable
{
public static Script Compile(string code);
public static object RunCode(string code);
…
}
Again, preferred pattern for customization is to create a new façade class which customizes ScriptContext for those methods. So, I will introduce two new methods to PdfRuntimeHost class:
public static class PdfRuntimeHost
{
public static void Initialize()
{
RuntimeHost.Initialize();
}
public static Script Compile(string code)
{
Script s = Script.Compile(code);
CustomizeScriptContext(s.Context);
return s;
}
public static object RunCode(string code)
{
IScriptContext context = new ScriptContext();
CustomizeScriptContext(context);
return Script.RunCode(code, context);
}. . .
Context customization is quite simple itself. I am going to introduce couple of constants:
context.SetItem(“ALIGN_CENTER”, “CENTER”);
context.SetItem(“ALIGN_LEFT”, “LEFT”);
context.SetItem(“ALIGN_RIGHT”, “RIGHT”);
context.SetItem(“RED”, new BaseColor(255, 0, 0));
context.SetItem(“GREEN”, new BaseColor(0, 255, 0));
context.SetItem(“BLUE”, new BaseColor(0, 0, 255));
There will be three variables:
context.SetItem(Functions.ActiveListVariable, null);
context.SetItem(Functions.ActiveTableVariable, null);
context.SetItem(Functions.ActiveDocumentVariable, null);
ActiveDocumentVariable – will store reference to active Document object
ActiveListVariable – will store reference to active list variable
ActiveTableVariable – will store reference to active table variable
Note that it is not possible to have nested objects, like list of lists, or table containing other table in one of cells. However, this limitation may be overcome by introducing stacks of active containers instead of single reference variable.
At the final step I will build functional structure of new language. Let’s consider Paragraph function as example (Other functions may be found in Functions.cs file in supplied solution file).
Paragraph function creates new paragraph object and automatically appends it to the document when necessary.
public static object Paragraph(IScriptContext context, object[] args)
{
Document document =
context.GetItemAs<Document>(ActiveDocumentVariable);
Paragraph pf = new Paragraph((string)args[0]);
if (args.Length == 2)
{
BaseColor color = args[1] as BaseColor;
if (color == null)
pf.SetAlignment((string)args[1]);
else
pf.Font.Color = color;
}
if (args.Length == 3)
{
pf.SetAlignment((string)args[1]);
pf.Font.Color = (BaseColor)args[2];
}
if (!IsContainerScope(context))
document.Add(pf);
return pf;
}
Now, each new function should be added into ScriptContext like this:
context.SetItem(“BeginDocument”,
new FunctionWrapper(Functions.BeginDocument));
Where FunctionWrapper is class implementing S#’s IInvokable interface. It takes method delegate as a first constructor parameter and executes this delegate as a result of invoking Invoke method with supplied parameters.
Conclusion
This example gives a good example of S# language customization. With a few simple steps you may create your own domain specific language. In this example such language simulates PDF markup.
But it is not only a markup. It is also a program and experienced users may write scripts which benefits from this:
BeginList();
for(i=0; i<10; i++)
ListItem(i.ToString());
EndList();
Downloads