Footnotes
A footnote puts a small mark in the text and the note itself at the foot of the page that mark lands on. PinataLayout does the layout for you. Before it lays out the paragraph that carries the mark, it keeps room for the note at the foot of the page. The body text never runs into the notes, and the page breaks where it has to. Numbering is worked out after layout, so the numbers stay in order when text moves from one page to another.
Footnotes are part of the PinataLayout.Rendering package, like the rest of the layout engine.
Add a footnote
Call AddFootnote(text) at the point where the mark belongs. The mark goes after the text added
before it. Paragraph, FormattedText and Hyperlink all have AddFootnote, so a note can hang
off a bold run or a link as well as plain text:
var opening = first.AddParagraph(
"A footnote is a paragraph element like any other run of text");
opening.AddFootnote(
"This is the note. It is attached to the word before the mark, and it appears at the "
+ "foot of whichever page that word ends up on.");
opening.AddText(
", so it goes wherever a run can go: in a plain paragraph, inside formatted text, or "
+ "inside a hyperlink");
opening.AddFormattedText(" - here inside a bold run", TextFormat.Bold)
.AddFootnote("Attached from inside a FormattedText, and numbered in reading order.");
opening.AddText(".");
The mark is drawn as a superscript. The note is laid out in its own column below a short rule at the foot of the page, with the mark hanging to its left.
Notes with more than a line of text
AddFootnote() with no argument returns an empty Footnote. A footnote holds block content, like a
small section: add paragraphs to it with AddParagraph, or a table or an image with AddTable and
AddImage.
var blockContent = first.AddParagraph(
"A note is not a string. Footnote.Elements is block content, so a note can hold more "
+ "than one paragraph");
var longNote = blockContent.AddFootnote();
longNote.AddParagraph(
"The first paragraph of a note that has two. Everything PinataLayout can put in a section "
+ "can go in here.");
longNote.AddParagraph(
"The second. Both are laid out into a column the width of the text above, indented by "
+ "the width of the mark, so the mark stands in the margin and the lines all line up.");
blockContent.AddText(", and a table or an image as readily as a paragraph.");
Style the notes
Notes are set in the predefined Footnote style, which is based on Normal. Change that style
rather than each note. Space between notes comes from the style's ParagraphFormat.SpaceAfter;
nothing else is added.
// The notes are set in this predefined style. It is based on Normal and exists whether or
// not anybody touches it, so a document that says nothing about footnotes still gets a
// sensible one - and a document that wants them smaller says so here rather than on each.
var footnote = document.Styles[StyleNames.Footnote];
footnote.Font.Size = 8;
footnote.Font.Color = Colors.Black;
The four settings
Four properties on Document shape every footnote in it. Each applies to the whole document.
FootnoteNumberingRule decides when the numbers start again:
| Value | Numbers restart |
|---|---|
RestartPage (the default) | On every page |
RestartSection | At every section |
RestartContinuous | Never: one sequence for the whole document |
The default restarts on every page, which most reports do not want. Set the rule you mean:
// Continuous numbering, so the marks on this page carry on from each other rather than
// starting again. The default is RestartPage, which is worth knowing and is why this line
// is here at all - see the last page.
document.FootnoteNumberingRule = FootnoteNumberingRule.RestartContinuous;
FootnoteNumberStyle decides what a mark looks like: Arabic (1, 2, 3, the default),
LowercaseLetter (a, b, c), UppercaseLetter (A, B, C), LowercaseRoman (i, ii, iii) or
UppercaseRoman (I, II, III).
FootnoteStartingNumber is the first number of each sequence. Any value below 1, including the
default of 0, starts at 1.
FootnoteLocation decides where the block of notes sits. BottomOfPage, the default, puts it
at the foot of the text area. BeneathText puts it directly under the last line of text on the page.
Both keep the same room free while the page is laid out, so they differ only on a page that is not
full. On a full page they are the same place.
Mark a note yourself
Set Footnote.Reference to show your own mark, such as an asterisk or a dagger, in place of a
number. A note with its own mark is left out of the count, so the numbered notes around it do not
skip a number:
var ownMark = first.AddParagraph(
"A note can carry a mark of the caller's own instead of a number");
ownMark.AddFootnote(
"Marked with an asterisk rather than a number, by setting Reference. A note marked "
+ "this way is left out of the counting, so the numbers around it do not skip.")
.Reference = "*";
ownMark.AddText(", which is what Footnote.Reference is for.");
var after = first.AddParagraph("The numbering carries on regardless");
after.AddFootnote("The fourth note, and the third number - the starred one did not count.");
after.AddText(".");
Things to know
- Numbering restarts on every page unless you say otherwise.
RestartPageis the default. SetFootnoteNumberingRuletoRestartContinuousfor one sequence through the document. - A footnote must be in a paragraph of the section itself. A footnote in a table cell, a text
frame, a header or footer, or another footnote throws a
NotSupportedExceptionwhen the document is rendered. None of those has a page foot of its own to put the note at. Move the footnote into a paragraph in the section, or write its text where it stands. - A note is never split across pages. It is laid out whole on the page that carries its mark. Keep footnotes short: a note taller than the space left on any page overfills that page.
- A paragraph that breaks across pages takes all its notes to the second page. If a mark falls in the part of the paragraph on the first page, its note still appears on the next page.
- The separator rule is fixed. It is a thin line a third of the column wide, drawn once on each page that has notes. There is no setting to change it.
- Tagged output links the mark to its note. In a tagged document the mark is a
Referenceelement and the note aNoteelement. See Accessibility.
See it in action
The Footnotes demo runs over five pages. It shows notes on formatted text, a note of two paragraphs, a note with its own mark, the current number style, a short page where the block stays at the foot of the sheet, and continuous numbering carried across a page break.
The full Footnotes demo
protected override PdfDocument Build(DemoContext context)
{
var document = new Document
{
Info =
{
Title = "Footnotes"
}
};
var normal = document.Styles[StyleNames.Normal];
normal.Font.Name = "Liberation Serif";
normal.Font.Size = 10.5;
normal.ParagraphFormat.SpaceAfter = Unit.FromPoint(6);
var heading = document.Styles[StyleNames.Heading1];
heading.Font.Name = "Liberation Sans";
heading.Font.Size = 17;
heading.Font.Bold = true;
heading.ParagraphFormat.SpaceAfter = Unit.FromPoint(8);
// The notes are set in this predefined style. It is based on Normal and exists whether or
// not anybody touches it, so a document that says nothing about footnotes still gets a
// sensible one - and a document that wants them smaller says so here rather than on each.
var footnote = document.Styles[StyleNames.Footnote];
footnote.Font.Size = 8;
footnote.Font.Color = Colors.Black;
var caption = document.Styles.AddStyle("Caption", StyleNames.Normal);
caption.Font.Size = 8.5;
caption.Font.Italic = true;
caption.Font.Color = Colors.DimGray;
// Continuous numbering, so the marks on this page carry on from each other rather than
// starting again. The default is RestartPage, which is worth knowing and is why this line
// is here at all - see the last page.
document.FootnoteNumberingRule = FootnoteNumberingRule.RestartContinuous;
// ----- page one: what a footnote is -----
var first = document.AddSection();
first.PageSetup.TopMargin = Unit.FromCentimeter(2.5);
first.AddParagraph("A note at the foot of the page").Style = StyleNames.Heading1;
var opening = first.AddParagraph(
"A footnote is a paragraph element like any other run of text");
opening.AddFootnote(
"This is the note. It is attached to the word before the mark, and it appears at the "
+ "foot of whichever page that word ends up on.");
opening.AddText(
", so it goes wherever a run can go: in a plain paragraph, inside formatted text, or "
+ "inside a hyperlink");
opening.AddFormattedText(" - here inside a bold run", TextFormat.Bold)
.AddFootnote("Attached from inside a FormattedText, and numbered in reading order.");
opening.AddText(".");
first.AddParagraph(
"The mark in the running text is drawn as a superscript, at the reduced size the font "
+ "asks for. The note itself is not part of this paragraph and takes no room in it: it "
+ "is laid out separately and drawn in a band at the foot of the page.");
var blockContent = first.AddParagraph(
"A note is not a string. Footnote.Elements is block content, so a note can hold more "
+ "than one paragraph");
var longNote = blockContent.AddFootnote();
longNote.AddParagraph(
"The first paragraph of a note that has two. Everything PinataLayout can put in a section "
+ "can go in here.");
longNote.AddParagraph(
"The second. Both are laid out into a column the width of the text above, indented by "
+ "the width of the mark, so the mark stands in the margin and the lines all line up.");
blockContent.AddText(", and a table or an image as readily as a paragraph.");
first.AddParagraph(
"Room for the note comes off the page before the paragraph carrying its mark is laid "
+ "out, so the page breaks where it should and the body text never runs into the "
+ "block. That is the whole of the layout problem, and it is why a footnote is a "
+ "feature rather than a line of drawing code.").Style = "Caption";
var ownMark = first.AddParagraph(
"A note can carry a mark of the caller's own instead of a number");
ownMark.AddFootnote(
"Marked with an asterisk rather than a number, by setting Reference. A note marked "
+ "this way is left out of the counting, so the numbers around it do not skip.")
.Reference = "*";
ownMark.AddText(", which is what Footnote.Reference is for.");
var after = first.AddParagraph("The numbering carries on regardless");
after.AddFootnote("The fourth note, and the third number - the starred one did not count.");
after.AddText(".");
// ----- page two: the five number styles -----
var styles = document.AddSection();
styles.PageSetup.TopMargin = Unit.FromCentimeter(2.5);
styles.AddParagraph("Five ways to mark a note").Style = StyleNames.Heading1;
styles.AddParagraph(
"FootnoteNumberStyle decides what the generated mark looks like. It is a document-wide "
+ "setting, so the three notes below are all in the style named at the end of this "
+ "sentence: " + document.FootnoteNumberStyle + ".");
var marks = styles.AddParagraph("Three notes in a row");
marks.AddFootnote("The first.");
marks.AddText(", one after another");
marks.AddFootnote("The second.");
marks.AddText(", so the sequence is visible");
marks.AddFootnote("The third.");
marks.AddText(".");
styles.AddParagraph(
"The five values are Arabic (1, 2, 3), LowercaseLetter (a, b, c), UppercaseLetter "
+ "(A, B, C), LowercaseRoman (i, ii, iii) and UppercaseRoman (I, II, III). "
+ "FootnoteStartingNumber moves where the sequence begins; left alone it starts at "
+ "one, because the property's own default is zero and a first note marked \"0\" would "
+ "be a strange thing to print.").Style = "Caption";
// ----- page three: where the block goes -----
var beneath = document.AddSection();
beneath.PageSetup.TopMargin = Unit.FromCentimeter(2.5);
beneath.AddParagraph("Where the block sits on the page").Style = StyleNames.Heading1;
beneath.AddParagraph(
"FootnoteLocation has two values. BottomOfPage - the default, and what every page of "
+ "this document uses - pins the block to the foot of the text area however little the "
+ "page holds. BeneathText puts it directly under the last thing laid out, which on a "
+ "page like this one is a long way higher up.");
var shortPage = beneath.AddParagraph("This page stops here");
shortPage.AddFootnote(
"Pinned to the foot of the sheet, a long way below the line it belongs to, because "
+ "this document uses BottomOfPage.");
shortPage.AddText(".");
beneath.AddParagraph(
"The note above is at the foot of the sheet, a long way below the line it belongs to, "
+ "because this document leaves FootnoteLocation alone. Setting it to BeneathText would "
+ "draw the same note immediately under that line instead, and would change nothing "
+ "else about this page. The setting belongs to the document rather than to the page, "
+ "so one document cannot show both - set it and run the demo again to see the other.");
beneath.AddParagraph(
"Both reserve the same room while the page is being formatted, so neither can collide "
+ "with the body text. They differ only in where the room that was kept clear is "
+ "actually used. On a full page the two are the same place.").Style = "Caption";
// ----- pages four and five: the numbering rule -----
var rule = document.AddSection();
rule.PageSetup.TopMargin = Unit.FromCentimeter(2.5);
rule.AddParagraph("Where the numbering starts again").Style = StyleNames.Heading1;
rule.AddParagraph(
"FootnoteNumberingRule decides what the sequence counts within. RestartContinuous "
+ "numbers the whole document, which is what this one does and what most reports want. "
+ "RestartSection begins again at each section. RestartPage begins again on each page.");
var acrossOne = rule.AddParagraph("A note on this page");
acrossOne.AddFootnote("Numbered on from every note before it in the document.");
acrossOne.AddText(", and another on the next.");
rule.AddParagraph(
"RestartPage is the enum's first value and therefore its default, so a document that "
+ "says nothing gets notes numbered from one on every page. That is rarely what "
+ "anybody means, and it is the reason this demo sets the rule explicitly at the top.")
.Style = "Caption";
rule.AddPageBreak();
var acrossTwo = rule.AddParagraph("The note on the page after");
acrossTwo.AddFootnote(
"Under RestartContinuous this carries on from the page before. Under the default it "
+ "would be number one again.");
acrossTwo.AddText(".");
var renderer = new PdfDocumentRenderer(unicode: true) { Document = document };
renderer.RenderDocument();