Annotations
An annotation is an object that sits on top of a page rather than in its content: a sticky note, a
link, a highlight, a stamp, an attached file. A reader draws it, and the person reading can often
open, hide, move or print it separately from the page. Use an annotation when the mark is a comment
on the page or something to click. When the mark is part of the page itself, draw it with XGraphics
instead.
Every annotation type is in the PdfPinata.Pdf.Annotations namespace of the core PdfPinata
package. You add one to a page with page.Annotations.Add(annotation).
Place an annotation on the page
An annotation's position is in PDF's own page coordinates: points measured up from the
bottom-left corner of the page. XGraphics draws in coordinates measured down from the top left.
To convert, use the graphics object's transformer:
gfx.Transformer.WorldToDefaultPage(XRect)returns the rectangle in page coordinates. Wrap it in aPdfRectangleto setRectangle.gfx.Transformer.WorldToDefaultPage(XPoint)converts a single point. Use it for the two ends of a line annotation.
The conversion also applies any transform you have set on the XGraphics, so the annotation lands on
what you drew at the same coordinates.
Notes
A PdfTextAnnotation is a sticky note. The reader draws its icon and shows Contents in a pop-up.
Title is the label in the pop-up's title bar, usually the author's name.
var opened = new PdfTextAnnotation();
page.Annotations.Add(opened);
opened.Icon = PdfTextAnnotationIcon.Comment;
opened.Open = true;
opened.Color = XColors.CornflowerBlue;
opened.Opacity = 0.85;
opened.Title = "Open on arrival";
opened.Contents = "Open = true, so a reader shows the popup without being asked. "
+ "Color tints the note and Opacity applies to the whole annotation.";
opened.Rectangle = new PdfRectangle(
gfx.Transformer.WorldToDefaultPage(new XRect(56, 510, 20, 20)));
Icon takes a PdfTextAnnotationIcon: Comment, Help, Insert, Key, NewParagraph, Note or
Paragraph. Open = true shows the pop-up when the document opens. Color tints the note, and
Opacity applies to the whole annotation.
Links
XGraphics has one method per kind of link. Each takes a rectangle in drawing coordinates and
converts it for you:
Link("The PdfPinata repository",
"gfx.AddWebLink(rect, url) - a URI action. PDFKit calls this link().",
rect => secondGfx.AddWebLink(rect, "https://github.com/PinataLabs/PdfPinata"));
Link("Jump to the parity table on page three",
"gfx.AddDocumentLink(rect, 3) - a GoTo by one-based page number.",
rect => secondGfx.AddDocumentLink(rect, 3));
Link("Back to the text markup on page one",
"gfx.AddNamedLink(rect, \"markup\") against gfx.AddNamedDestination on page one - "
+ "PDFKit's goTo().",
rect => secondGfx.AddNamedLink(rect, "markup"));
AddWebLink(rect, url)opens a web address.AddDocumentLink(rect, pageNumber)goes to a page of the same document. The page number starts at 1.AddNamedLink(rect, name)goes to a named destination. Create the destination withgfx.AddNamedDestination(name, point)on the page it belongs to.
Each method returns the PdfLinkAnnotation, so you can set Contents on it afterwards. PdfPage has
the same methods, taking a PdfRectangle in page coordinates, plus AddFileLink for a file on disk.
PdfPage.AddDocumentLink(rect, pageNumber, destinationTop) also says how far down the target page to
land. A link is only a clickable area: draw the text and any underline yourself.
Highlight, underline and strike out
Four classes mark a run of text: PdfHighlightAnnotation, PdfUnderlineAnnotation,
PdfStrikeOutAnnotation and PdfSquigglyAnnotation. Each covers one or more quadrilaterals that you
add with AddQuad, in page coordinates. The demo measures the run of text with MeasureString,
converts the box, and adds it:
// The annotation has to be on the page before a quad is added: AddQuad builds the
// /QuadPoints array against the document that owns it, and rebuilds the appearance.
T Mark<T>(T annotation, string line, string run, XPoint baseline)
where T : PdfTextMarkupAnnotation
{
page.Annotations.Add(annotation);
annotation.AddQuad(gfx.Transformer.WorldToDefaultPage(RunOf(line, run, baseline, body)));
return annotation;
}
Text that wraps onto a second line needs one quadrilateral per line. Add them all to the same annotation; the annotation's rectangle grows to enclose them.
var wrapped = new PdfHighlightAnnotation();
page.Annotations.Add(wrapped);
wrapped.Color = XColors.Gold;
wrapped.Opacity = 0.55;
wrapped.Title = "PdfPinata";
wrapped.Contents = "Two quads, one annotation.";
wrapped.AddQuad(gfx.Transformer.WorldToDefaultPage(
RunOf(First, "past the end of a line is one annotation with", firstAt, body)));
wrapped.AddQuad(gfx.Transformer.WorldToDefaultPage(
RunOf(Second, "two quadrilaterals in it", secondAt, body)));
Color and Opacity belong to the annotation, not to the page, so you can mark the same text twice.
A highlight is drawn in the Multiply blend mode, so the text shows through the colour. If you add no
quadrilaterals, the annotation marks its Rectangle. ClearQuads removes them all.
Shapes, lines and free text
Readers draw /Square, /Circle, /Line and /FreeText annotations only from an appearance stream,
so PdfPinata draws that stream for you and redraws it when you change a property. None of the demos
shows these four, so here is a short example:
// A rectangle with a red border and a pale fill.
PdfSquareAnnotation box = new PdfSquareAnnotation(document)
{
Rectangle = new PdfRectangle(gfx.Transformer.WorldToDefaultPage(new XRect(56, 600, 200, 80))),
Color = XColors.Firebrick,
Interior = XColors.MistyRose,
BorderWidth = 2,
Contents = "Check these figures",
};
page.Annotations.Add(box);
// An arrow between two points.
PdfLineAnnotation arrow = new PdfLineAnnotation(document);
arrow.SetLine(
gfx.Transformer.WorldToDefaultPage(new XPoint(300, 700)),
gfx.Transformer.WorldToDefaultPage(new XPoint(420, 640)));
arrow.EndEnding = PdfLineEnding.ClosedArrow;
arrow.Interior = XColors.Black;
page.Annotations.Add(arrow);
// Text shown on the page itself rather than in a pop-up.
PdfFreeTextAnnotation label = new PdfFreeTextAnnotation(document)
{
Rectangle = new PdfRectangle(gfx.Transformer.WorldToDefaultPage(new XRect(56, 720, 220, 40))),
Font = new XFont("Arial", 10), // any family your font resolver serves
TextColor = XColors.DarkBlue,
Contents = "Figures from the March survey.",
};
page.Annotations.Add(label);
PdfSquareAnnotationandPdfCircleAnnotationfill theirRectangle.Coloris the border,Interiorthe fill andBorderWidththe border width in points. A circle is an ellipse that fits the rectangle.PdfLineAnnotationgoes fromStarttoEnd.SetLinemoves both at once.StartEndingandEndEndingtake aPdfLineEnding, such asOpenArrow,ClosedArrow,CircleorDiamond, andInteriorfills them.PdfFreeTextAnnotationdrawsContentsinFontandTextColor, wrapped to its rectangle.Alignmenttakes anXParagraphAlignment(fromPdfPinata.Drawing.Layout).Coloris the background, and the border usesTextColor.
Stamps
A PdfRubberStampAnnotation shows one of 15 standard stamps, such as Draft, Approved,
Confidential or Final. The reader draws the stamp.
for (var index = 0; index < stamps.Length; index++)
{
double x = 56 + index * 120;
var stamp = new PdfRubberStampAnnotation(document)
{
Icon = stamps[index],
Title = "PdfPinata",
Contents = stamps[index] + " stamp",
Rectangle = new PdfRectangle(
secondGfx.Transformer.WorldToDefaultPage(new XRect(x, 450, 104, 34)))
};
second.Annotations.Add(stamp);
secondGfx.DrawString(stamps[index].ToString(), noteFont, XBrushes.Black,
new XPoint(x, 498));
}
For a stamp with your own artwork, draw it on an XForm with XGraphics.FromForm and pass the form
to SetAppearance.
File attachments
A PdfFileAttachmentAnnotation carries a file inside the PDF and shows an icon the reader can open
it from. PdfEmbeddedFile holds the bytes, PdfFileSpecification names them, and the annotation
points at the specification. Both types are in PdfPinata.Pdf.Advanced.
var payload = Encoding.UTF8.GetBytes(
"This file is carried inside the PDF, as the /EF stream of a file specification.\r\n"
+ "Open the paperclip on the page to save it out again.\r\n");
var embedded = new PdfEmbeddedFile(document, payload)
{
MimeType = "text/plain"
};
var specification =
new PdfFileSpecification(document, "readme.txt", embedded);
var attachment = new PdfFileAttachmentAnnotation(document)
{
File = specification,
Icon = PdfFileAttachmentAnnotation.IconType.Paperclip,
Title = "PdfPinata",
Contents = "readme.txt, carried inside this document.",
Rectangle = new PdfRectangle(
secondGfx.Transformer.WorldToDefaultPage(new XRect(56, 350, 18, 18)))
};
second.Annotations.Add(attachment);
Icon takes Graph, PushPin, Paperclip or Tag. The constructor sets
PdfAnnotationFlags.Locked, so a reader will not let the person drag the icon away.
Other annotation types
PdfGenericAnnotation takes any subtype name, for example
new PdfGenericAnnotation(document, "/Polygon"). Write its entries through Elements, and give it a
drawing with SetAppearance.
Flags takes PdfAnnotationFlags on any annotation: Hidden, Print, NoZoom, NoRotate,
NoView, ReadOnly, Locked and others.
Things to know
- Coordinates go up from the bottom. Every rectangle and point is in page coordinates. If an
annotation appears in the wrong place, check that you converted it with
gfx.Transformer.WorldToDefaultPage. - Some annotations are drawn by the reader. Note icons, attachment icons and standard stamps have no drawing in the file. A renderer that draws only appearance streams shows nothing for them.
- A line sets its own rectangle.
PdfLineAnnotationworks outRectanglefrom its ends and line endings. If you assignRectangle, the line overwrites it. - An empty shape removes its drawing. A square or circle with no border and no fill, or a shape smaller than one point in either direction, has no appearance.
- Free text needs a font resolver. A
PdfFreeTextAnnotationdraws text, so a font resolver must be registered before it is added to a page. See Installation. - Annotations read from a file are generic. In a document you opened,
page.Annotations[index]returns aPdfGenericAnnotation, not the typed class. ItsSubtypeproperty says what kind it is, andElementsholds its entries. - Justified free text is stored as left-aligned. PDF has no code for justified text in a free text annotation. PdfPinata draws it justified, but a reader that redraws it aligns it left.
- Annotations are appended.
page.AnnotationshasAddandRemove, but noInsert. - Not everything is covered. There are no classes for polygon, polyline, ink or pop-up annotations. A line has no caption or leader lines, and free text has no callout line.
See it in action
The Annotations demo marks up text four ways, adds notes with every icon, three kinds of link, a file attachment and four stamps.
The demo's last page compares PdfPinata with PDFKit and lists line and free text annotations as
missing. That entry is out of date: PdfLineAnnotation and PdfFreeTextAnnotation are available,
as shown above.
The full Annotations demo
const string Sans = "Liberation Sans";
var document = new PdfDocument();
document.Info.Title = "Annotations";
var titleFont = new XFont(Sans, 18, XFontStyle.Bold);
var headingFont = new XFont(Sans, 9, XFontStyle.Bold);
var body = new XFont(Sans, 11);
var noteFont = new XFont(Sans, 7.5);
// ---- Page one: text markup, and notes ----------------------------------------
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
void Title(XGraphics on, string text)
{
on.DrawString(text, titleFont, XBrushes.Black, new XPoint(56, 68));
on.DrawLine(new XPen(XColors.SteelBlue, 1.5), 56, 78, 539, 78);
}
void Heading(XGraphics on, string text, double y)
{
on.DrawString(text.ToUpperInvariant(), headingFont, XBrushes.SteelBlue,
new XPoint(56, y));
on.DrawLine(XPens.LightGray, 56, y + 5, 539, y + 5);
}
void Note(XGraphics on, string text, double y)
{
on.DrawString(text, noteFont, XBrushes.DimGray, new XPoint(56, y));
}
Title(gfx, "Annotations");
Note(gfx, "An annotation is not page content. Nothing here is drawn by XGraphics - a reader "
+ "paints it, and can hide it.", 94);
// A markup annotation covers quadrilaterals given in default page space, so the run of
// text to be marked has to be measured and then converted out of drawing coordinates.
// The ratio below is how PdfPinata itself turns a font into a baseline offset.
double AscentOf(XFont font) => font.GetHeight() * font.CellAscent / font.CellSpace;
XRect RunOf(string line, string run, XPoint baseline, XFont font)
{
var start = line.IndexOf(run, StringComparison.Ordinal);
var before = gfx.MeasureString(line[..start], font).Width;
var width = gfx.MeasureString(run, font).Width;
var ascent = AscentOf(font);
// The box a reader would draw a selection over: from the ascender down past the
// baseline by what is left of the line.
return new XRect(baseline.X + before, baseline.Y - ascent, width,
font.GetHeight());
}
// The annotation has to be on the page before a quad is added: AddQuad builds the
// /QuadPoints array against the document that owns it, and rebuilds the appearance.
T Mark<T>(T annotation, string line, string run, XPoint baseline)
where T : PdfTextMarkupAnnotation
{
page.Annotations.Add(annotation);
annotation.AddQuad(gfx.Transformer.WorldToDefaultPage(RunOf(line, run, baseline, body)));
return annotation;
}
Heading(gfx, "Text markup", 124);
(string Line, string Run, Func<PdfTextMarkupAnnotation> Make, string Caption)[] markups =
{
("Highlight marks a run of text with a wash of colour.", "a wash of colour",
() => new PdfHighlightAnnotation(), "PdfHighlightAnnotation - PDFKit's highlight()"),
("Underline draws a line along the foot of the run.", "along the foot",
() => new PdfUnderlineAnnotation(), "PdfUnderlineAnnotation - PDFKit's underline()"),
("Strike out draws through the middle of it instead.", "through the middle",
() => new PdfStrikeOutAnnotation(), "PdfStrikeOutAnnotation - PDFKit's strike()"),
("Squiggly draws the wavy line a spell checker uses.", "the wavy line",
() => new PdfSquigglyAnnotation(), "PdfSquigglyAnnotation - PDFKit has no squiggly()")
};
double y = 154;
foreach (var each
in markups)
{
var baseline = new XPoint(56, y);
gfx.DrawString(each.Line, body, XBrushes.Black, baseline);
Mark(each.Make(), each.Line, each.Run, baseline);
Note(gfx, each.Caption, y + 11);
y += 40;
}
// Colour and opacity belong to the annotation rather than to the drawing, so the same
// text can be marked twice over without the page content knowing.
const string Twice = "One run, marked twice: green underneath and a strike over the top.";
var twiceAt = new XPoint(56, y);
gfx.DrawString(Twice, body, XBrushes.Black, twiceAt);
var green = Mark(new PdfHighlightAnnotation(), Twice, "marked twice",
twiceAt);
green.Color = XColors.LightGreen;
green.Contents = "Highlight with a colour of its own";
Mark(new PdfStrikeOutAnnotation(), Twice, "marked twice", twiceAt).Color = XColors.Crimson;
Note(gfx, "Color is the annotation's, not the page's. Opacity applies to the whole markup.",
y + 11);
y += 40;
// One annotation, two quadrilaterals. This is how a markup follows a selection that
// wraps: the quads are the lines, and /Rect becomes the box around both.
const string First = "A markup that runs past the end of a line is one annotation with";
const string Second = "two quadrilaterals in it, not two annotations.";
var firstAt = new XPoint(56, y);
var secondAt = new XPoint(56, y + 16);
gfx.DrawString(First, body, XBrushes.Black, firstAt);
gfx.DrawString(Second, body, XBrushes.Black, secondAt);
var wrapped = new PdfHighlightAnnotation();
page.Annotations.Add(wrapped);
wrapped.Color = XColors.Gold;
wrapped.Opacity = 0.55;
wrapped.Title = "PdfPinata";
wrapped.Contents = "Two quads, one annotation.";
wrapped.AddQuad(gfx.Transformer.WorldToDefaultPage(
RunOf(First, "past the end of a line is one annotation with", firstAt, body)));
wrapped.AddQuad(gfx.Transformer.WorldToDefaultPage(
RunOf(Second, "two quadrilaterals in it", secondAt, body)));
Note(gfx, "AddQuad twice. /Rect is recomputed as the box around every quad.", y + 27);
// ---- Notes -------------------------------------------------------------------
Heading(gfx, "Notes - PDFKit's note()", 424);
Note(gfx, "A note has no appearance of its own: the reader draws the icon, at whatever "
+ "size it likes.", 444);
PdfTextAnnotationIcon[] icons =
{
PdfTextAnnotationIcon.Comment,
PdfTextAnnotationIcon.Note,
PdfTextAnnotationIcon.Help,
PdfTextAnnotationIcon.Key,
PdfTextAnnotationIcon.Insert,
PdfTextAnnotationIcon.NewParagraph,
PdfTextAnnotationIcon.Paragraph
};
for (var index = 0; index < icons.Length; index++)
{
double x = 56 + index * 68;
var sticky = new PdfTextAnnotation();
page.Annotations.Add(sticky);
sticky.Icon = icons[index];
sticky.Title = "PdfPinata";
sticky.Subject = icons[index].ToString();
sticky.Contents = $"The {icons[index]} icon. Every note carries a title, a subject and "
+ "this text, which is what a reader shows in the popup.";
sticky.CreationDate = new DateTime(2026, 1, 1, 9, 0, 0, DateTimeKind.Utc);
sticky.Color = XColors.Goldenrod;
sticky.Rectangle = new PdfRectangle(
gfx.Transformer.WorldToDefaultPage(new XRect(x, 456, 20, 20)));
gfx.DrawString(icons[index].ToString(), noteFont, XBrushes.Black, new XPoint(x, 494));
}
var opened = new PdfTextAnnotation();
page.Annotations.Add(opened);
opened.Icon = PdfTextAnnotationIcon.Comment;
opened.Open = true;
opened.Color = XColors.CornflowerBlue;
opened.Opacity = 0.85;
opened.Title = "Open on arrival";
opened.Contents = "Open = true, so a reader shows the popup without being asked. "
+ "Color tints the note and Opacity applies to the whole annotation.";
opened.Rectangle = new PdfRectangle(
gfx.Transformer.WorldToDefaultPage(new XRect(56, 510, 20, 20)));
Note(gfx, "Open = true on this one - its popup should already be showing.", 544);
// The place page two links back to. A named destination is a name in the document's
// name tree, so a link can point at it without knowing a page number.
gfx.AddNamedDestination("markup", new XPoint(56, 124));
// ---- Page two: links, attachments, stamps ------------------------------------
var second = document.AddPage();
var secondGfx = XGraphics.FromPdfPage(second);
Title(secondGfx, "Links, attachments and stamps");
Heading(secondGfx, "Links - PDFKit's link() and goTo()", 116);
double linkY = 146;
void Link(string label, string caption, Action<XRect> add)
{
var size = secondGfx.MeasureString(label, body);
secondGfx.DrawString(label, body, XBrushes.MediumBlue, new XPoint(56, linkY));
// The underline is drawn by hand: a link annotation is a hot area, not a decoration.
secondGfx.DrawLine(new XPen(XColors.MediumBlue, 0.6),
56, linkY + 2, 56 + size.Width, linkY + 2);
var ascent = AscentOf(body);
add(new XRect(56, linkY - ascent, size.Width, body.GetHeight()));
Note(secondGfx, caption, linkY + 13);
linkY += 44;
}
Link("The PdfPinata repository",
"gfx.AddWebLink(rect, url) - a URI action. PDFKit calls this link().",
rect => secondGfx.AddWebLink(rect, "https://github.com/PinataLabs/PdfPinata"));
Link("Jump to the parity table on page three",
"gfx.AddDocumentLink(rect, 3) - a GoTo by one-based page number.",
rect => secondGfx.AddDocumentLink(rect, 3));
Link("Back to the text markup on page one",
"gfx.AddNamedLink(rect, \"markup\") against gfx.AddNamedDestination on page one - "
+ "PDFKit's goTo().",
rect => secondGfx.AddNamedLink(rect, "markup"));
// A link annotation is a PdfLinkAnnotation like any other, so the returned object can
// still be given the fields every annotation has.
var titledSize = secondGfx.MeasureString("A link with a tooltip", body);
secondGfx.DrawString("A link with a tooltip", body, XBrushes.MediumBlue,
new XPoint(56, linkY));
secondGfx.DrawLine(new XPen(XColors.MediumBlue, 0.6),
56, linkY + 2, 56 + titledSize.Width, linkY + 2);
var described = secondGfx.AddWebLink(
new XRect(56, linkY - AscentOf(body), titledSize.Width, body.GetHeight()),
"https://www.pdfa.org/");
described.Contents = "Shown as a tooltip while the pointer is over the link.";
Note(secondGfx, "AddWebLink returns the annotation, so /Contents can be set on it "
+ "afterwards.", linkY + 13);
// ---- An attachment -----------------------------------------------------------
Heading(secondGfx, "File attachment - PDFKit's fileAnnotation()", 330);
var payload = Encoding.UTF8.GetBytes(
"This file is carried inside the PDF, as the /EF stream of a file specification.\r\n"
+ "Open the paperclip on the page to save it out again.\r\n");
var embedded = new PdfEmbeddedFile(document, payload)
{
MimeType = "text/plain"
};
var specification =
new PdfFileSpecification(document, "readme.txt", embedded);
var attachment = new PdfFileAttachmentAnnotation(document)
{
File = specification,
Icon = PdfFileAttachmentAnnotation.IconType.Paperclip,
Title = "PdfPinata",
Contents = "readme.txt, carried inside this document.",
Rectangle = new PdfRectangle(
secondGfx.Transformer.WorldToDefaultPage(new XRect(56, 350, 18, 18)))
};
second.Annotations.Add(attachment);
Note(secondGfx, "PdfEmbeddedFile holds the bytes, PdfFileSpecification names them, and the "
+ "annotation points at it.", 384);
Note(secondGfx, "The constructor sets PdfAnnotationFlags.Locked, so a reader will not let "
+ "it be dragged off the page.", 396);
Note(secondGfx, "Like a note's, the paperclip is the reader's own drawing - so a renderer "
+ "that paints only appearance streams shows nothing above.", 408);
// ---- A rubber stamp ----------------------------------------------------------
Heading(secondGfx, "Rubber stamp - PDFKit has no equivalent", 430);
PdfRubberStampAnnotationIcon[] stamps =
{
PdfRubberStampAnnotationIcon.Draft,
PdfRubberStampAnnotationIcon.Confidential,
PdfRubberStampAnnotationIcon.ForComment,
PdfRubberStampAnnotationIcon.Final
};
for (var index = 0; index < stamps.Length; index++)
{
double x = 56 + index * 120;
var stamp = new PdfRubberStampAnnotation(document)
{
Icon = stamps[index],
Title = "PdfPinata",
Contents = stamps[index] + " stamp",
Rectangle = new PdfRectangle(
secondGfx.Transformer.WorldToDefaultPage(new XRect(x, 450, 104, 34)))
};
second.Annotations.Add(stamp);
secondGfx.DrawString(stamps[index].ToString(), noteFont, XBrushes.Black,
new XPoint(x, 498));
}
Note(secondGfx, "Fifteen standard names, drawn by the reader. A stamp with artwork of its "
+ "own would need an appearance stream.", 520);
// ---- Page three: what PDFKit has that this does not ---------------------------
var third = document.AddPage();
var thirdGfx = XGraphics.FromPdfPage(third);
Title(thirdGfx, "Parity with PDFKit's annotations");
Note(thirdGfx, "pdfkit.org/docs/annotations.html documents eleven methods. Nine of them "
+ "have something here; two do not.", 94);
var mono = new XFont("Source Code Pro", 8);
(string PdfKit, string Here)[] parity =
[
("note(x, y, w, h, contents)", "PdfTextAnnotation"),
("link(x, y, w, h, url)", "gfx.AddWebLink / PdfLinkAnnotation.CreateWebLink"),
("goTo(x, y, w, h, name)", "gfx.AddNamedLink / gfx.AddDocumentLink"),
("highlight(x, y, w, h)", "PdfHighlightAnnotation"),
("underline(x, y, w, h)", "PdfUnderlineAnnotation"),
("strike(x, y, w, h)", "PdfStrikeOutAnnotation"),
("fileAnnotation(x, y, w, h, file)", "PdfFileAttachmentAnnotation"),
("lineAnnotation(x1, y1, x2, y2)", "MISSING - no /Line annotation"),
("rectAnnotation(x, y, w, h)", "PdfSquareAnnotation"),
("ellipseAnnotation(x, y, w, h)", "PdfCircleAnnotation"),
("textAnnotation(x, y, w, h, text)", "MISSING - no /FreeText annotation")
];
double rowY = 132;
thirdGfx.DrawString("PDFKit", headingFont, XBrushes.SteelBlue, new XPoint(56, rowY));
thirdGfx.DrawString("PdfPinata", headingFont, XBrushes.SteelBlue, new XPoint(260, rowY));
rowY += 6;
thirdGfx.DrawLine(XPens.LightGray, 56, rowY, 539, rowY);
rowY += 18;
foreach (var row in parity)
{
var missing = row.Here.StartsWith("MISSING", StringComparison.Ordinal);
XBrush brush = missing ? XBrushes.Crimson : XBrushes.Black;
thirdGfx.DrawString(row.PdfKit, mono, XBrushes.Black, new XPoint(56, rowY));
thirdGfx.DrawString(row.Here, mono, brush, new XPoint(260, rowY));
rowY += 17;
}
rowY += 20;
Heading(thirdGfx, "And what this has that PDFKit does not", rowY);
rowY += 26;
(string What, string Why)[] extras =
[
("PdfSquigglyAnnotation", "the fourth text markup subtype"),
("PdfRubberStampAnnotation", "fifteen standard stamp names"),
("PdfAnnotation.Opacity", "/CA, applied to the whole annotation"),
("PdfAnnotation.Flags", "Hidden, Print, Locked, NoZoom and the rest"),
("PdfTextMarkupAnnotation.AddQuad", "many quads under one annotation")
];
foreach (var row in extras)
{
thirdGfx.DrawString(row.What, mono, XBrushes.Black, new XPoint(56, rowY));
thirdGfx.DrawString(row.Why, noteFont, XBrushes.DimGray, new XPoint(260, rowY));
rowY += 17;
}
rowY += 22;
thirdGfx.DrawString(
"The two missing subtypes are appearance-bearing: a viewer will not draw a /Line from",
noteFont, XBrushes.DimGray, new XPoint(56, rowY));
rowY += 12;
thirdGfx.DrawString(
"its endpoints alone, so adding them means writing appearance streams the way",
noteFont, XBrushes.DimGray, new XPoint(56, rowY));
rowY += 12;
thirdGfx.DrawString(
"PdfTextMarkupAnnotation already does. Until then, draw the shape with XGraphics.",
noteFont, XBrushes.DimGray, new XPoint(56, rowY));
rowY += 18;
thirdGfx.DrawString(
"Nor can a caller supply one: PdfAnnotation is abstract with no public way to set",
noteFont, XBrushes.DimGray, new XPoint(56, rowY));
rowY += 12;
thirdGfx.DrawString(
"/Subtype, PdfGenericAnnotation is internal, and PdfAnnotations.Add takes neither -",
noteFont, XBrushes.DimGray, new XPoint(56, rowY));
rowY += 12;
thirdGfx.DrawString(
"so a subtype this library has no class for cannot be added through the typed API.",
noteFont, XBrushes.DimGray, new XPoint(56, rowY));