Guide
Text and fonts
Write text with pdf-text, use the standard PDF fonts or your own with providePdfFonts, and learn how ngpdf falls back between fonts, wraps lines, hyphenates words, cuts text with max-lines and handles line height.
Text goes in pdf-text. It stays selectable and searchable in the PDF.
<pdf-page style="padding: 36; font-family: Times; font-size: 12; line-height: 1.4">
<pdf-text style="font-size: 18; font-weight: bold">Release notes</pdf-text>
<pdf-text>Body text set in Times-Roman.</pdf-text>
<pdf-text style="font-style: italic; color: #555555">An italic aside.</pdf-text>
<pdf-text style="font-family: monospace; font-size: 9">npm install @ngpdf/core</pdf-text>
<pdf-text style="text-align: center; text-decoration: line-through">Withdrawn</pdf-text>
</pdf-page>The default text is Helvetica, 10pt, black, with a line height of 1.2, aligned left.
Fonts
By default ngpdf uses the standard fonts that every PDF reader has, so nothing is embedded. Of the 14 standard PDF fonts, the 12 text faces are available: Helvetica, Times and Courier, each in regular, bold, italic and bold italic. Symbol and ZapfDingbats are not available. For other fonts and for characters beyond WinAnsi, register your own: see Custom fonts.
You pick a face with three properties:
| Property | Value | Result |
|---|---|---|
font-family |
contains courier or mono |
Courier |
contains times, or serif but not sans |
Times | |
| anything else | Helvetica | |
font-weight |
above 500 | bold |
| 500 or less | regular | |
font-style |
italic or oblique |
italic |
normal |
upright |
font-weight is a number from 1 to 1000 or one of the names below, in any case. Any other value throws ngpdf: invalid value "<value>" for font-weight on <tag>, and the message lists the allowed values. font-style is normal, italic or oblique; any other value throws the same way. The standard fonts have only a regular and a bold face, so 100 to 500 is regular and 600 to 900 is bold; a registered family can have a face for every weight.
| Name | Weight |
|---|---|
thin |
100 |
extralight |
200 |
light |
300 |
normal |
400 |
medium |
500 |
semibold |
600 |
bold |
700 |
extrabold |
800 |
black |
900 |
bolder |
from the inherited weight: below 350 gives 400, below 550 gives 700, below 900 gives 900, and 900 or more stays |
lighter |
from the inherited weight: below 100 stays, below 550 gives 100, below 750 gives 400, and 750 or more gives 700 |
When the value names no registered family, the family match ignores case and looks at the whole value, so 'Times New Roman', serif, Georgia, serif and serif all give Times, and Arial, Inter or sans-serif give Helvetica. ngpdf never throws for a family name.
The resulting font names in the PDF are Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique, Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic, Courier, Courier-Bold, Courier-Oblique and Courier-BoldOblique.
Custom fonts
Register fonts once with providePdfFonts, then name them in font-family:
// app.config.ts
import { providePdfFonts } from '@ngpdf/core';
export const appConfig: ApplicationConfig = {
providers: [
providePdfFonts([
{ family: 'Inter', src: '/fonts/Inter-Regular.woff2' },
{ family: 'Inter', src: '/fonts/Inter-Light.woff2', weight: 300 },
{ family: 'Inter', src: '/fonts/Inter-Bold.woff2', weight: 'bold' },
{ family: 'Noto Sans JP', src: '/fonts/NotoSansJP-Regular.ttf' },
]),
],
};<pdf-page style="font-family: Inter, 'Noto Sans JP'">
<pdf-text>Zoë, Łódź, Ελληνικά, Кириллица, 日本語</pdf-text>
</pdf-page>interface PdfFont {
readonly family: string;
readonly src: string | ArrayBuffer | Uint8Array | Blob;
readonly weight?: number | 'normal' | 'bold'; // 1 to 1000; 'normal' is 400 (default), 'bold' 700
readonly style?: 'normal' | 'italic'; // default 'normal'
}
function providePdfFonts(fonts: readonly PdfFont[]): EnvironmentProviders;- Formats: TrueType (
.ttf), OpenType (.otf), WOFF and WOFF2. Font collections (.ttc) are not supported. - Sources:
srcis a URL, a data URL, or the bytes. A URL is fetched like an image (same-origin credentials only), on the first render whose document names its family. A font no document names is never fetched, so a broken font file fails only the documents that use it. It is fetched once per injector and reused by later renders. A failed fetch is not kept, so the next render tries again. WithrenderToBufferfrom@ngpdf/core/server, a font URL is read only through itsfetchImage, and kept for that render only; see Server rendering.renderToBlobandPdfRenderer.toBlobfetch it as above, also on a server. - Embedding: each PDF embeds only the fonts its pages use, cut down to the characters it draws.
- Matching:
familyis matched in any case. Each face is oneweightandstyle; register one entry per face. Aweightoutside 1 to 1000 rejects the first render that uses the family withngpdf: invalid font weight "<weight>" for "<family>". - Missing faces: if a family has no face for the text's weight, ngpdf picks a weight as CSS does. From 400 to 500 it tries heavier weights up to 500 first, going up, then lighter weights going down, then heavier weights above 500, so with only whole hundreds 400 tries 500 first and 500 tries 400 first. Below 400 it tries lighter weights first, then heavier; above 500, heavier first, then lighter. Among the faces of that weight it takes the text's style, or else the upright one. It warns in development mode, naming the face it used (
ngpdf: font "Inter" has no medium face; using its regular face), and it never makes a bold or italic face up. - Where they apply: documents rendered with the injector that has the provider, or one below it.
providePdfFontsin a route'sprovidersadds fonts for that route's renders throughpdfResource,pdfDownloadandrenderToBlob, on top of the app's.PdfRendereris a root service, so it sees only the app's fonts. SeveralprovidePdfFontscalls in one injector add up.
The family list and fallback
font-family is read as a list. Each character is drawn in the first font of the list that has it:
- A registered family is used for the characters its font has.
- A standard name in the list covers WinAnsi. The names are, in any case:
Helvetica,Arial,sans-serifandsystem-uifor Helvetica;Times,Times New Roman,Times-Romanandseriffor Times;Courier,Courier New,monospaceandui-monospacefor Courier. - Names that are neither are skipped, including web fonts such as
Open SansorIBM Plex Monothat you have not registered. - When the list has registered families but no standard name, a standard font is added at the end, chosen from the whole value as above. So WinAnsi characters a custom font lacks are still drawn.
With font-family: Inter, 'Noto Sans JP', Latin text is drawn in Inter, Japanese in Noto Sans JP, and a € that neither had would be drawn in Helvetica. Kerning applies within each font's run of text, not across two fonts.
A character that no font in the list has is drawn as the first font's empty glyph, usually a box. In development mode ngpdf logs one warning per render listing them:
ngpdf: characters missing from every font in their font-family, drawn as an empty box: ☃
If the list starts with a standard font, such a character becomes ?, as below.
There is no right-to-left text yet, so Arabic and Hebrew are not laid out correctly, and color emoji fonts are not supported. Emoji can be drawn as images instead: see Emoji.
Emoji
A font draws emoji in one color at best, and the standard fonts draw them as ?. To draw them in color, give ngpdf a source of emoji images with providePdfEmoji. Each emoji is then drawn as a small inline image in the text.
// app.config.ts
import { providePdfEmoji } from '@ngpdf/core';
export const appConfig: ApplicationConfig = {
providers: [providePdfEmoji('/assets/emoji/{codepoints}.png')],
};<pdf-text>Shipped 👍🏽 to 🇧🇷 at 9️⃣ o'clock 🎉</pdf-text>type PdfEmojiImage = string | Uint8Array | Blob | ArrayBuffer;
type PdfEmojiSource =
| string
| ((emoji: string, codepoints: string) => PdfEmojiImage | null | Promise<PdfEmojiImage | null>);
function providePdfEmoji(source: PdfEmojiSource): EnvironmentProviders;- URL template: a string source is a URL in which
{codepoints}is replaced by the emoji's code points in lower-case hex, joined by-, without U+FE0F.👍🏽becomes1f44d-1f3fdand#️⃣becomes23-20e3. Emoji image sets name their files their own way: Twemoji keeps U+FE0F in some names, and Noto Emoji usesemoji_u,_and at least four hex digits. To match one exactly, use a function source. - Function: a function source gets the emoji and the same code points, and returns a PNG or JPEG as a URL, a data URL or the bytes, or a promise of one. It returns
nullto draw that emoji as text. - No images by default: ngpdf ships no emoji images and fetches nothing unless you provide a source. Without one, an emoji is text like any other character: a custom font in the
font-familylist that has the glyph draws it, and one that no font in the list has follows the fallback,?when the list starts with a standard font and the first font's empty glyph otherwise. - Where it applies: documents rendered with the injector that has the provider, or one below it, as with custom fonts. A provider in a child injector replaces the parent's.
A function that matches Noto Emoji's file names (emoji_u1f44d_1f3fd.png, emoji_u00a9.png), for a copy of its png/72 folder:
providePdfEmoji((_emoji, codepoints) => {
const name = codepoints
.split('-')
.map((hex) => hex.padStart(4, '0'))
.join('_');
return `/assets/noto/emoji_u${name}.png`;
});That folder has no flags: the Noto Emoji repository keeps them apart, in third_party/region-flags/png, named by region code (BR.png) or, for England, Scotland and Wales, by subdivision (GB-SCT.png). Copy those too and send each regional-indicator pair and each 🏴 tag sequence there:
const SUBDIVISIONS = new Set(['gbeng', 'gbsct', 'gbwls']);
providePdfEmoji((_emoji, codepoints) => {
const points = codepoints.split('-').map((hex) => parseInt(hex, 16));
if (points.length === 2 && points.every((point) => point >= 0x1f1e6 && point <= 0x1f1ff)) {
const region = String.fromCharCode(...points.map((point) => point - 0x1f1e6 + 65));
return `/assets/flags/${region}.png`;
}
// 🏴 followed by tag letters and a cancel tag, such as 🏴 (gbsct): GB-SCT.png.
if (points[0] === 0x1f3f4 && points.at(-1) === 0xe007f) {
const tag = String.fromCharCode(...points.slice(1, -1).map((point) => point - 0xe0000));
// Only the files Noto has: any other tag, which could hold "/" or "..", is drawn as text.
return SUBDIVISIONS.has(tag) ? `/assets/flags/GB-${tag.slice(2).toUpperCase()}.png` : null;
}
return `/assets/noto/emoji_u${points.map((point) => point.toString(16).padStart(4, '0')).join('_')}.png`;
});What counts as an emoji
A character as you see it (a grapheme cluster) is an emoji when:
- it contains U+FE0F (the emoji variation selector);
- or it is a ZWJ sequence that starts with a pictographic character (
Extended_Pictographic), such as 👨👩👧👦 or ⛹♀; - or it is a keycap: a digit,
#or*followed by U+20E3, with or without U+FE0F, such as#️⃣or1⃣; - or it is a modifier sequence: a character that takes a skin tone (
Emoji_Modifier_Base) followed by one, such as ✌🏽; - or it starts with a character that shows as an emoji by default, which covers 😀, a skin tone such as 👍🏽, flags such as 🇧🇷 and tag sequences such as 🏴.
©, ®, ™, the digits and other characters that show as text by default stay text on their own. They become emoji with U+FE0F after them, in a keycap (the digits, # and *), or, for a pictographic character, in a ZWJ sequence or with a skin tone, as above. A cluster whose second character is U+FE0E (the text variation selector) always stays text.
Layout
- Each emoji is a square 1 em on each side, at the
font-sizeof its text. The square's bottom sits on the font's descent and its top is 1 em above that, so it lines up with the text around it. An image that is not square is fitted inside the square, centered, without stretching, so a wide one ends above the descent. A writer whose font metrics give no descent puts the square's bottom on the baseline instead; the default pdfkit writer always gives one. - It takes the text's
letter-spacing, and lines break around it as around any character, never inside it. - An underline or a line-through runs under or through it like text. Its
colordoes not apply to the image. max-lines,text-align: justifyandtext-transformwork as they do for text. An ellipsis after an emoji is drawn in the font of the text around it.- The text of
pdf-page-numberis always drawn as text.
Loading
- Each distinct emoji is loaded once per render, together with the document's images and before layout. Six loads start at a time, and the next one starts when one of them ends or runs out of time. A load that runs out of time is no longer waited for, but a source function or a server
fetchImagestill running is not stopped, so more than 6 can still be in progress. The PDF embeds each image once, however often the emoji appears. - URLs are fetched like images, with same-origin credentials only. With
renderToBufferon the server, they are read only through itsfetchImage, like images (see Server rendering). An emoji whose URL is refused is drawn as text. - A render loads at most 500 distinct emoji. The rest are drawn as text, with one warning in development mode:
ngpdf: more than 500 distinct emoji; the rest are drawn as text. - An emoji whose image fails to load, is not a PNG or JPEG, is a PNG cut off after its header, is
null, has no width or height, or takes more than 10 seconds from the start of its load is drawn as text. A fetch that runs out of time is aborted. The 10 seconds cannot interrupt a source function while it runs synchronously: they only cut off waiting for its promise or for a fetch. Emoji often come from user data, so a missing one does not fail the render. In development mode ngpdf logs one warning per render listing them:ngpdf: emoji without an image, drawn as text: 🦤. - A JPEG is checked only up to its frame header, which gives its size, because pdfkit embeds JPEG data as it is. A JPEG cut off after that header is embedded, and how it shows depends on the PDF viewer.
Limits
- An emoji image is not text. Copying, searching and screen readers skip it. Giving it real text needs a tagged PDF, which ngpdf does not write yet.
- Only raster images (PNG and JPEG) work. Color font formats (COLR, sbix and CBDT) are not read.
Characters: WinAnsi
The standard fonts use the WinAnsi encoding (Windows-1252). Text in a custom font is not limited to it. It covers ASCII, the Latin-1 accented letters (á, ç, ñ, ü and so on) and a few more, such as €, “ ”, ‘ ’, –, —, …, • and ™.
An accented letter written as a base letter plus a combining mark, such as e followed by U+0301 (common in macOS file names), is drawn as the composed letter é when it has one. In a font-family list, a font that lacks the letter as written but has its composed form draws that form; a custom font that has the combining mark draws the text as written.
In a standard font, any other character is drawn as ?. That includes emoji (unless providePdfEmoji gives them images), Greek, Cyrillic, CJK, arrows and check marks. In development mode ngpdf logs one warning per render that lists every replaced character:
ngpdf: characters outside WinAnsi replaced with "?": ✓→
Some characters are handled first, in every font, so they are never drawn as ? or as a missing glyph:
- The Unicode spaces that locale data puts in pipe output, such as the thin space (U+2009) and the narrow no-break space (U+202F), and the other spaces from U+2000 to U+200A and U+205F, are drawn as a normal space. The narrow no-break space and the figure space (U+2007) become a no-break space (U+00A0), so
3:00 PMfromdate: 'short'still does not break between the time andPM. - Vertical tab, form feed, next line (U+0085), and the line and paragraph separators (U+2028, U+2029) are drawn as a normal space, so they collapse and trim like any other whitespace.
- The ideographic space (U+3000) and the Ogham space mark (U+1680) are kept for a custom font that has them, and drawn as a normal space when no font in the list has them and the list starts with a standard font. They are trimmed at the ends of the text but never collapsed.
- A zero-width space (U+200B) and U+FEFF (a byte order mark or zero-width no-break space) are removed. ngpdf does not break lines at them, so they are never drawn.
- A soft hyphen (U+00AD) is never drawn itself. It marks a place where a word may break, with a hyphen drawn at the end of the line; see Hyphenation.
The same applies to the text of pdf-page-number.
Wrapping
- By default, whitespace collapses as in CSS
white-space: normal: runs of spaces, tabs and line breaks become one space, and the ends are trimmed. A line break in your data does not start a new line unless you setwhite-space. - Lines wrap at Unicode line-break opportunities (UAX #14), so they break after spaces and hyphens, and not inside words.
- A word wider than its box is broken at the box edge, between characters as you see them (grapheme clusters), so an accent stays with its letter.
- Widths include the font's kerning, and the ligatures of a custom font.
- Words break with a hyphen at soft hyphens, and with
hyphens: autowhere your hyphenation function allows. See Hyphenation. - There is no right-to-left text.
White space
white-space sets which spaces and line breaks of the text are kept, and whether lines wrap at the box edge. It is inherited, and a pdf-span can set it. The values mean what they mean in CSS:
| Value | Spaces and tabs | Line breaks | Wraps |
|---|---|---|---|
normal (default) |
collapse | collapse | yes |
nowrap |
collapse | collapse | no |
pre |
kept | kept | no |
pre-line |
collapse | kept | yes |
pre-wrap |
kept | kept | yes |
An address or a note typed with line breaks keeps them with pre-line:
<pdf-text style="white-space: pre-line">{{ customer.address }}</pdf-text>- A line break is LF, CRLF or CR. Where line breaks are kept, each one starts a new line, and two in a row leave an empty line. A line break at the very end of the text adds no line.
- Under
pre-line, the spaces around a line break are dropped, and the ends of the text are trimmed. Underpreandpre-wrap, nothing is trimmed, so put the binding right against the tags:<pdf-text>{{ text }}</pdf-text>. - Under
preandpre-wrap, a tab moves to the next tab stop. Stops are every 8 space widths of the tab's font, counted from the start of the line, as with CSS's defaulttab-size. A tab is not underlined. There is notab-sizeproperty. - Under
pre-wrap, spaces and tabs at the end of a line hang past it and are not drawn. Underpre, they count in the line's width. - Text that does not wrap (
nowrap,pre) and is wider than its box is drawn past the edge, or clipped withoverflow: hidden. A word in it is never cut at the box edge or hyphenated. - A
nowraporprepdf-spaninside a wrapping paragraph does not break inside. - A kept line break ends the line before it, so
text-align: justifyleaves that line at the start, as it does the last line.max-linescounts lines across kept line breaks.
A long pdf-text breaks between lines at a page break, keeping orphans lines (default 2) before the break and widows lines (default 2) after it. break-inside: avoid keeps it whole. See Text across pages.
Hyphenation
hyphens sets where a word may break with a hyphen at the end of the line. It is inherited, and a pdf-span can set it for its own text.
| Value | Where words break |
|---|---|
manual (default) |
at soft hyphens (U+00AD, ­ in a template) |
none |
nowhere: soft hyphens are ignored, and still not drawn |
auto |
at soft hyphens, and where the function from providePdfHyphenation allows |
<pdf-text style="width: 90pt">Pay the extra­ordinary invoice today.</pdf-text>- A word is hyphenated only when it does not fit on what is left of the line. The line then takes as many pieces of it as fit, plus the hyphen. A word that does not fit even an empty line is cut at the box edge only when no piece fits.
- The hyphen is drawn in the font of the text it ends:
‐(U+2010) when the font has it,-otherwise. The standard fonts have no U+2010, so they draw-. It counts in the line's width, is underlined or struck through with the text, and is copied with it. - A soft hyphen where the line does not break draws nothing and takes no width.
- Justified text,
max-lines,letter-spacingandtext-transformwork on hyphenated lines. A line cut bymax-linesends with…, not a hyphen.
Automatic hyphenation
ngpdf ships no hyphenation patterns, because they are large and differ by language. For hyphens: auto, give it a function that cuts a word into the pieces it may break between:
type PdfHyphenate = (word: string, language: string | undefined) => readonly string[];
function providePdfHyphenation(hyphenate: PdfHyphenate): EnvironmentProviders;languageis thelanguageof thepdf-document, so one function can serve several languages.- The pieces must join back to the word:
'hyphenation'gives['hy', 'phen', 'ation']. A function that throws, or returns anything else, means that word is not hyphenated automatically, with one warning in development mode. Like any word too long for its box, it can still break at the box edge. - Only words of 5 letters or more are hyphenated, with at least 2 letters on each side of the break, as browsers do by default. Punctuation around a word stays outside it.
text-transformapplies first, so the function gets the transformed word.- A word that has soft hyphens breaks only at them.
- The function is called only for words that do not fit their line, at most once per word and render.
- Without the provider,
autobreaks at soft hyphens only, and warns once per render in development mode. - Like
providePdfFonts, it applies to documents rendered with its injector or one below it, so a route or a child injector can have its own.pdfResource,pdfDownloadandrenderToBlobsee a route's provider.PdfRendereris a root service, so it sees only the app's: to use a route's function, pass the route's injector torenderToBlobordownloadPdf, as below.
A pattern library, such as the hyphen package from npm, can back the function. hyphen ships no type declarations, so a TypeScript app also installs @types/hyphen:
npm install hyphen
npm install --save-dev @types/hyphenLoad it lazily, so its patterns stay out of your initial bundle and load only when a document needs them:
// pdf-hyphenation.ts
import { providePdfHyphenation } from '@ngpdf/core';
let hyphenateEn: ((text: string) => string) | undefined;
/** Loads the English patterns once; await it before rendering a hyphenated document. */
export async function loadHyphenation(): Promise<void> {
hyphenateEn ??= (await import('hyphen/en')).hyphenateSync;
}
// hyphenateSync puts a soft hyphen at each break, so splitting on it gives the pieces.
export const pdfHyphenation = providePdfHyphenation((word) =>
hyphenateEn ? hyphenateEn(word).split('\u00ad') : [word],
);// app.config.ts: providers: [pdfHyphenation]
// in the component that renders:
await loadHyphenation();
await this.pdf.download(ReportDocument, { fileName: 'report.pdf' });With pdfHyphenation in a route's providers instead, render with that route's injector, since PdfRenderer would not see it:
// in a component of that route
private readonly injector = inject(EnvironmentInjector);
async save(): Promise<void> {
await loadHyphenation();
await downloadPdf(ReportDocument, { injector: this.injector, fileName: 'report.pdf' });
}Cutting text to a number of lines
max-lines keeps at most that many lines of a pdf-text. When text was cut, the last kept line ends with an ellipsis: characters come off its end until the line and … fit the box. The ellipsis is drawn in the style of the last character kept on that line. When that character's font has no …, the ellipsis comes from the first font in the font-family list that has one, like any other character, keeping the size and color. If no font in the list has …, it is ... in the first font that has .. The standard fonts have both, and a list with registered families always holds one (see the fallback), so in practice the ellipsis is …. In a box too narrow for the ellipsis itself, the line is left empty. With text-align: justify, the cut line is not stretched, like a paragraph's last line.
<pdf-cell><pdf-text style="max-lines: 2">{{ product.description }}</pdf-text></pdf-cell>The box is as tall as the kept lines. max-lines: none, the default, keeps every line. The property is not inherited, so set it on the pdf-text itself.
Cutting text to its height
text-overflow: ellipsis on a pdf-text with a fixed height or max-height keeps the lines that fit fully in its content box (the height less padding and border). When text was cut, the last kept line ends with …, the same way as with max-lines.
<pdf-text style="height: 40; text-overflow: ellipsis">{{ product.description }}</pdf-text>- With
max-linesas well, the smaller number of lines wins. - The box keeps its height; only the lines drawn in it change.
- Without a fixed
heightormax-heighton thepdf-textitself,ellipsisdoes nothing. The height of a parent withoverflow: hiddendoes not count. - When not even the first line fits, it is still drawn and sticks out of the box, ending with
…when more text follows it. CSS would clip it; ngpdf keeps a line that can be read. A text of one line has nothing cut, so it gets no…. clip, the default, keeps every line, and lines past the height are drawn past the box unlessoverflow: hiddenclips them. The property is not inherited.- A text with a fixed height or
max-heightis never split across pages, so a cut text stays on one page.
Line height
| Value | Meaning |
|---|---|
1.4 |
1.4 × the font size. A bare number is always a multiplier, so 14 means 14 × the font size. |
140% |
1.4 × the font size |
14pt, 5mm, 0.2in |
a fixed line height |
normal |
keeps the inherited line height |
Write a length with its unit, such as line-height: 14pt, when you mean points. A length is divided by the element's own font size, and the resulting ratio is inherited.
Each line is font-size × line-height high, using the largest font size on the line, so a block of text in one size is number of lines × font-size × line-height high. As in CSS, the space the line height adds to the text (the leading) is split in half: one half above the text, one half below. With line-height: 3 a 10pt text sits in the middle of a 30pt line. A line height smaller than the font's height gives a negative half, so the text rises and can reach outside its line, as it does in a browser. Underlines and strike-throughs follow the text; a link's clickable area is the whole line.
In a line with runs of several sizes, each run's leading is split around that run, and the run that reaches highest above the baseline sets where the baseline goes. The line stays as tall as its largest font size times line-height. CSS can make such a line a little taller when the runs are in fonts with different proportions above and below the baseline; ngpdf keeps the height, so the lower part of such a run can reach that little bit past the line.
Alignment and decoration
text-align:left,center,rightorjustify. It aligns each line inside thepdf-textbox. In a row, apdf-textis only as wide as its text, so give it a width orflex: 1when you want alignment to show. Inside a column, the box takes the full width.justifywidens the gaps between words so that each line reaches both edges of the box. The last line of the paragraph stays at the left, as in CSS. So does a line with a single word, and a word wider than the box. A gap is a run of spaces or no-break spaces, and only gaps are widened: the space between letters stays as the font andletter-spacingset it. A paragraph split across pages keeps its lines justified up to its real last line.text-decoration:none,underlineorline-through. One value only.letter-spacing: a length added after each character, the last one on a line included, as in browsers. It can be negative;normalis 0. Wrapping, alignment, justified text andmax-linesall use the spaced width. A letter and its combining marks count as one character. As in CSS, spaced text drops the optional ligatures of a custom font, such asfi. Text extractors such aspdftotextguess spaces from the gaps between letters, so spacing above about a tenth of the font size can come out asN O R T Hwhen the text is copied.text-transform:none,uppercase,lowercaseorcapitalize. The text is transformed before it is measured, so lines break where the transformed text fits. Case changes don't depend on a locale, soßbecomesSS.capitalizeuppercases the first letter of each word, a word being what follows the start of the paragraph or a space, across spans; punctuation before the letter is skipped, and a word that starts with a digit is left as it is. The page number is never transformed.colorandopacityapply to the text.
Unlike CSS, all text styles, text-decoration included, are inherited by child elements.
Styled runs: pdf-span
Wrap part of a text in a pdf-span to draw it in other text styles:
<pdf-text style="font-size: 11pt">
Invoice <pdf-span style="font-weight: bold">#{{ number() }}</pdf-span> is
<pdf-span style="color: #b00020">{{ days() }} days overdue</pdf-span>.
</pdf-text>- A span inherits the text styles around it and can change
color,font-family,font-size,font-weight,font-style,text-decoration,letter-spacing,text-transform,hyphensandwhite-space, and move its text up or down withvertical-align(below). Spans nest. - The paragraph wraps as one text: a span boundary is not a place to break, and a span wraps over lines like the rest of the text. Kerning is not applied across two spans.
- Runs of different sizes share a baseline, and a line is as tall as its largest font size times the paragraph's
line-height. The baseline goes where the half-leading of the run that reaches highest puts it. A line grows when a raised or lowered run sticks out of it (see Superscript and subscript). line-heightandtext-alignbelong to thepdf-text. Other properties on a span, such as padding or a background, have no effect and get a warning in development mode.- A span is drawn in the fonts its own
font-familynames, with the same fallback as any text. @if,@forand components work inside apdf-textas anywhere else.
A pdf-text inside a pdf-text is drawn in the outer text's style, with a warning in development mode: use pdf-span instead. Elements other than pdf-span, pdf-link and text inside a pdf-text are not laid out.
Superscript and subscript
vertical-align on a pdf-span raises or lowers its text, for units, formulas and footnote markers:
<pdf-text>
42 m<pdf-span style="vertical-align: super">2</pdf-span> of floor, sealed against
H<pdf-span style="vertical-align: sub">2</pdf-span>O<pdf-span style="vertical-align: super">1</pdf-span>
</pdf-text>superraises the span's baseline by 0.33 times the font size around it, andsublowers it by 0.2 times. Both draw the span at 70% of the font size around it, unless the span sets its ownfont-size.- A length raises the text by that much, and a negative one lowers it:
vertical-align: 3pt. A percentage is of the span's line height, its font size times the paragraph'sline-height. Neither changes the size. baselineis the default. Like CSS,vertical-alignis not inherited, but a span inside a raised span sits on that span's baseline, so its ownvertical-alignadds to the move.- A moved run keeps the box every run has, its font size times
line-heightsplit around it by half-leading, and that box moves with it. The line grows where the moved box sticks out above the line's baseline or below its bottom, as in CSS. In 10pt Helvetica at the defaultline-height: 1.2, asuperspan makes its line about 0.7pt taller and asubspan about 1pt. - The underline or strike of a moved span moves with its text. So does the clickable area of a link around it, cut to the line so it never reaches into the lines above or below.
vertical-alignworks only on apdf-span. On any other element it has no effect, with a warning in development mode (ngpdf: vertical-align has no effect on <tag>).middle,top,text-topand the other CSS keywords throw.
Links: pdf-link
pdf-link makes text or a box open a URL when clicked in a PDF viewer:
<pdf-text>
Pay it at
<pdf-link [href]="payUrl" style="color: #1a56db; text-decoration: underline">example.com/pay</pdf-link>.
</pdf-text>
<pdf-link href="https://example.com/invoices/1024" style="padding: 8; border: 1 solid #1a56db">
<pdf-text>Open the invoice</pdf-text>
</pdf-link>- Inside a
pdf-text, a link is inline, like apdf-span: it takes the same text styles, and each piece of a line it covers is clickable. A link that wraps over two lines is two clickable areas. - Anywhere a
pdf-viewcan go, a link is a columnpdf-view, and its border box is the clickable area. - A link changes nothing visually: it has no default color or underline. Style it with
colorandtext-decoration. - Only absolute
http:,https:andmailto:URLs are written, in the normalized form the browser'sURLgives them (HTTP://Example.COMbecomeshttp://example.com/). Any other scheme (javascript:,file:,data:and so on), a relative URL or one that does not parse writes no link: the content is still drawn, with a warning in development mode naming the URL. Links often come from data, so a bad one does not fail the render. - Links do not nest: a
pdf-linkinside apdf-linkfails the render withngpdf: pdf-link inside a pdf-link, as nested links are invalid in HTML. - A clickable area is an upright rectangle. Inside a
transform, it is the smallest rectangle around the transformed area. It is cut at the bottom of the page's content area, but not by anoverflowclip: PDF viewers cannot clip a link area, so a link inside a clipped box stays clickable over its whole box. - A block link split across sheets is clickable on each sheet over its part. See Pagination.
Links to a place in the document
Give an element an anchor name, and a pdf-link with href="#name" jumps to it, as a table of contents does:
<pdf-text>See <pdf-link href="#prices" style="color: #1a56db">the price list</pdf-link>.</pdf-text>
<pdf-table anchor="prices" [columns]="['1fr', 80]">…</pdf-table>pdf-view,pdf-text,pdf-image,pdf-table,pdf-rowandpdf-celltakeanchor.- The link jumps to the sheet where the element's first piece lands, with the top left of that piece at the top left of the window, at the reader's zoom. For a text split across sheets that is its first lines; for an element in a fixed view, the first sheet.
- Names are trimmed, and an empty name is ignored. When two elements have the same name, the first one in the template keeps it, with a warning in development mode:
ngpdf: duplicate anchor "<name>". - A link to a name that no element has writes no link: the content is still drawn, with a warning in development mode.
- In the PDF, each anchor a link uses is a named destination, so a viewer can also open the file at it, as in
report.pdf#nameddest=prices. An anchor no link or bookmark uses writes nothing.
Bare text
Text written directly in an element other than pdf-text is laid out as an anonymous pdf-text with its parent's text styles:
<pdf-cell style="font-weight: bold">{{ total() }}</pdf-cell>
<!-- is laid out as -->
<pdf-cell style="font-weight: bold"><pdf-text>{{ total() }}</pdf-text></pdf-cell>Each run of text between elements becomes one anonymous box. Runs that are only whitespace are dropped. Text inside pdf-image and pdf-page-number, and text directly in pdf-document, is ignored.