Custom Page Elements
Custom Page Elements are used to output PDF code to a page or can utilize other already existing page element objects.
Creating custom page elements require understanding of PDF operators and using them to create a PDF. Core Suite provides methods encapsulating these operators. For example, the CS operator, which sets the color space for a stroking operation, has the corresponding write_CS method.
PageElement API
All page elements inherit from the PageElement base class and override its Draw method.
Custom page elements MUST extend the PageElement class and MUST override the Draw method.
Draw
The PageElement.Draw method accepts a PageWriter object.
public abstract void Draw(PageWriter writer)
MustOverride Sub Draw(writer As PageWriter)
When a PageElement instance is added to the page this method is called. In the method's body you add PDF operators using the methods of the PageWriter object.
- Use the SetXXXX (i.e. SetLeading, SetColor, SetGraphicsMode, etc.) methods to set the state on page.
- Then use the Write_XX (i.e. Write_l_, Write_m_, Write_re) methods to add the PDF graphic or text operators to the page. Most of the PDF operators have a Write_XXXX method associated with them. If the operator only differs by case, the lower case operator has a trailing "_" character. This is done to comply with the Common Language Specification (CLS).
A RotatingPageElement base class is also included. You can inherit from this to automatically provide support for rotating your page element.
When working with page elements, you do not have to be concerned with compression, encryption or PDF object numbering. This is all handled internally by Core Suite.
Custom PageElement Guidelines
- Always use the SetXXXX method to set graphics and text states. This allows the minimum amount of PDF code to be output to the page and allows the next page element that is called to properly set the state that it needs. If you are not going to use these methods you will need to use the
q
andQ
operators to store the current state and then restore it before leaving the Draw method. - Always use a top margin to bottom margin and left margin to right margin coordinate system for the properties of your page element. Remember that PDF uses a bottom edge to top edge and left edge to right edge coordinate system that is complex and confusing to most users. This complexity should be hidden from the user of your page element. The GetPDFX, GetPDFY methods are provided on the PageWriter Dimensions property to help with this translation.
- Use the X, Y, Width, and Height properties where applicable.
- Use the built-in Color, LineStyle, and Font objects wherever possible. This makes your Page Element function in the same fashion as the built-in page elements and makes it easier for users to use them.
Development Tips
When developing custom page elements, set the document's compression level to zero and don't use security or encryption. Create a simple application that adds one page to the PDF document and adds your custom page element to that page. This makes it possible for you to output the PDF document to a file and view the PDF output from your page element in a text editor.
PDF Resources
An excellent resource for PDF development is "PDF Reference" published by Adobe. This can be downloaded free of charge from the Adobe site. This resource includes documentation of all of the PDF graphic and text operators.
Examples
The following two examples illustrate creating custom page elements. The first customizes a TextArea page element and the second customizes a TaggablePageElement and creates a polygon.
Text Area Example
The following code illustrates customizing a TextArea page element.
using ceTe.DynamicPDF;
using ceTe.DynamicPDF.IO;
namespace DynamicPDFCoreSuite.Examples
{
public class CustomElement : ceTe.DynamicPDF.PageElements.TextArea
{
public CustomElement(string text, float x, float y, float width, float height) : base(text, x, y, width, height)
{ }
public override void Draw(PageWriter writer)
{
Font font = Font.CourierBold;
writer.SetTextMode();
writer.SetFont(font, 25f);
writer.SetTextRenderingMode(TextRenderingMode.FillAndStroke);
writer.SetStrokeColor(RgbColor.Red);
writer.SetFillColor(RgbColor.LightBlue);
writer.SetLeading(font.GetDefaultLeading(12));
writer.Write_Tm(base.X, base.Y);
writer.Write_SQuote(base.Text.ToCharArray(), 0, base.Text.Length, false);
}
}
}
Imports ceTe.DynamicPDF
Imports ceTe.DynamicPDF.IO
Namespace DynamicPDFCoreSuite.Examples
Public Class CustomElement
Inherits ceTe.DynamicPDF.PageElements.TextArea
Public Sub New(ByVal text As String, ByVal x As Single, ByVal y As Single, ByVal width As Single, ByVal height As Single)
MyBase.New(text, x, y, width, height)
End Sub
Public Overrides Sub Draw(ByVal writer As PageWriter)
Dim font As Font = Font.CourierBold
writer.SetTextMode()
writer.SetFont(font, 25.0F)
writer.SetTextRenderingMode(TextRenderingMode.FillAndStroke)
writer.SetStrokeColor(RgbColor.Red)
writer.SetFillColor(RgbColor.LightBlue)
writer.SetLeading(font.GetDefaultLeading(12))
writer.Write_Tm(MyBase.X, MyBase.Y)
writer.Write_SQuote(MyBase.Text.ToCharArray(), 0, MyBase.Text.Length, False)
End Sub
End Class
End Namespace
Polygon Example
The following code illustrates creating a Polygon by extending the TaggablePageElement class.
using ceTe.DynamicPDF;
using ceTe.DynamicPDF.IO;
using ceTe.DynamicPDF.PageElements;
namespace DynamicPDFCoreSuite.Examples
{
class Polygon : TaggablePageElement
{
private float[] xCoordinates;
private float[] yCoordinates;
private float borderWidth;
private Color fillColor;
private Color borderColor;
private LineStyle borderStyle;
private const float defaultForBorderWidth = 1.0f;
private static Color defaultForFillColor = null;
private static Color defaultForBorderColor = Grayscale.Black;
private static LineStyle defaultForStyle = LineStyle.Solid;
public Polygon(float[] xCoordinates, float[] yCoordinates)
{
this.xCoordinates = xCoordinates;
this.yCoordinates = yCoordinates;
this.fillColor = fillColor;
this.borderColor = borderColor;
if (borderWidth <= 0)
this.borderWidth = 0;
this.borderWidth = borderWidth;
this.borderStyle = borderStyle;
}
public LineStyle BorderStyle
{
get { return borderStyle; }
set { borderStyle = value; }
}
public float[] XCoordinates
{
get { return xCoordinates; }
set { xCoordinates = value; }
}
public float[] YCoordinates
{
get { return yCoordinates; }
set { yCoordinates = value; }
}
public float BorderWidth
{
get { return borderWidth; }
set
{
if (value <= 0)
borderWidth = 0;
else
borderWidth = value;
}
}
public Color BorderColor
{
get { return borderColor; }
set { borderColor = value; }
}
public Color FillColor
{
get { return fillColor; }
set { fillColor = value; }
}
public override void Draw(PageWriter writer)
{
writer.SetRelativeToState(base.RelativeTo, base.IgnoreMargins);
bool draw = true;
bool fill, stroke;
if (borderWidth > 0 && fillColor != null)
{
stroke = true;
fill = true;
}
else if (borderWidth > 0)
{
stroke = true;
fill = false;
}
else if (fillColor != null)
{
fill = true;
stroke = false;
}
else
{
stroke = false;
fill = false;
draw = false;
}
if (borderStyle == LineStyle.None)
stroke = false;
if (draw)
{
if (xCoordinates.Length == yCoordinates.Length && xCoordinates.Length > 2)
{
writer.SetGraphicsMode();
if (stroke && fill)
{
writer.SetLineWidth(borderWidth);
writer.SetStrokeColor(borderColor);
writer.SetLineStyle(borderStyle);
writer.SetLineCap(LineCap.Butt);
writer.SetFillColor(fillColor);
}
else if (stroke)
{
writer.SetLineWidth(borderWidth);
writer.SetStrokeColor(borderColor);
writer.SetLineStyle(borderStyle);
writer.SetLineCap(LineCap.Butt);
}
else if (fill)
{
writer.SetFillColor(fillColor);
}
writer.Write_m_(xCoordinates[0], yCoordinates[0]);
for (int i = 1; i < xCoordinates.Length; i++)
{
writer.Write_l_(xCoordinates[i], yCoordinates[i]);
}
writer.Write_l_(xCoordinates[0], yCoordinates[0]);
if (fill)
{
if (stroke) writer.Write_b_();
else writer.Write_f();
}
else writer.Write_s_();
}
else
{
throw new GeneratorException("Coordinates are wrong");
}
}
}
}
}
Imports ceTe.DynamicPDF
Imports ceTe.DynamicPDF.IO
Imports ceTe.DynamicPDF.PageElements
Namespace DynamicPDFCoreSuite.Examples
Public Class Polygon
Inherits TaggablePageElement
Private _xCoordinates As Single()
Private _yCoordinates As Single()
Private _borderWidth As Single
Private _fillColor As Color
Private _borderColor As Color
Private _borderStyle As LineStyle
Private Const defaultForBorderWidth As Single = 1.0F
Private Shared ReadOnly defaultForFillColor As Color = Nothing
Private Shared ReadOnly defaultForBorderColor As Color = Grayscale.Black
Private Shared ReadOnly defaultForStyle As LineStyle = LineStyle.Solid
Public Sub New(xCoordinates As Single(), yCoordinates As Single())
Me.xCoordinates = xCoordinates
Me.YCoordinates = yCoordinates
Me.fillColor = fillColor
Me.borderColor = borderColor
If borderWidth <= 0 Then
Me.borderWidth = 0
End If
Me.borderWidth = borderWidth
Me.borderStyle = borderStyle
End Sub
Public Property BorderStyle As LineStyle
Get
Return _borderStyle
End Get
Set(value As LineStyle)
_borderStyle = value
End Set
End Property
Public Property XCoordinates As Single()
Get
Return _xCoordinates
End Get
Set(value As Single())
_xCoordinates = value
End Set
End Property
Public Property YCoordinates As Single()
Get
Return _yCoordinates
End Get
Set(value As Single())
_yCoordinates = value
End Set
End Property
Public Property BorderWidth As Single
Get
Return _borderWidth
End Get
Set(value As Single)
If value <= 0 Then
_borderWidth = 0
Else
_borderWidth = value
End If
End Set
End Property
Public Property BorderColor As Color
Get
Return _borderColor
End Get
Set(value As Color)
_borderColor = value
End Set
End Property
Public Property FillColor As Color
Get
Return _fillColor
End Get
Set(value As Color)
_fillColor = value
End Set
End Property
Public Overrides Sub Draw(writer As PageWriter)
writer.SetRelativeToState(MyBase.RelativeTo, MyBase.IgnoreMargins)
Dim draw As Boolean = True
Dim fill As Boolean, stroke As Boolean
If borderWidth > 0 AndAlso fillColor IsNot Nothing Then
stroke = True
fill = True
ElseIf borderWidth > 0 Then
stroke = True
fill = False
ElseIf fillColor IsNot Nothing Then
fill = True
stroke = False
Else
stroke = False
fill = False
draw = False
End If
If BorderStyle.Equals(LineStyle.None) Then
stroke = False
End If
If draw Then
If xCoordinates.Length = yCoordinates.Length AndAlso xCoordinates.Length > 2 Then
writer.SetGraphicsMode()
If stroke AndAlso fill Then
writer.SetLineWidth(borderWidth)
writer.SetStrokeColor(borderColor)
writer.SetLineStyle(borderStyle)
writer.SetLineCap(LineCap.Butt)
writer.SetFillColor(fillColor)
ElseIf stroke Then
writer.SetLineWidth(borderWidth)
writer.SetStrokeColor(borderColor)
writer.SetLineStyle(borderStyle)
writer.SetLineCap(LineCap.Butt)
ElseIf fill Then
writer.SetFillColor(fillColor)
End If
writer.Write_m_(xCoordinates(0), yCoordinates(0))
For i As Integer = 1 To xCoordinates.Length - 1
writer.Write_l_(xCoordinates(i), yCoordinates(i))
Next
writer.Write_l_(xCoordinates(0), yCoordinates(0))
If fill Then
If stroke Then
writer.Write_b_()
Else
writer.Write_f()
End If
Else
writer.Write_s_()
End If
Else
Throw New GeneratorException("Coordinates are wrong")
End If
End If
End Sub
End Class
End Namespace
Refer to the following code if you wish to see an example of both custom classes in action.