A first line written from the specification's point of view lands at y = 770. The reasoning is sound on paper: ISO 32000-1 puts the origin of user space in the lower-left corner, an A4 page is 842 points tall, so 770 sits one inch below the top edge. Run that program against a component that measures from the top instead and the greeting lands about an inch above the bottom edge, with the rest of the page empty above it. Add a second page, and the carefully chosen font turns back into 12-point Arial. Both surprises come from the same two decisions every PDF program makes on its first page: which font is current, and which corner the numbers count from
The smallest HotPDF program in Delphi
The HotPDF Hello World is a console program with four calls in a fixed order: set document properties, BeginDoc, draw with SetFont and TextOut, then EndDoc. Everything else is ordinary Delphi. Here it is, writing one page that greets the reader in eleven languages:
program HelloWorld;
{$APPTYPE CONSOLE}
uses
Winapi.Windows, // DEFAULT_CHARSET; on pre-XE2 Delphi use plain Windows
HPDFDoc; // HotPDF main unit
procedure CreateHelloWorld(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
// Document-wide settings: all of these go before BeginDoc
Pdf.FileName := FileName;
Pdf.AutoLaunch := False; // default is True: opens the viewer after EndDoc
Pdf.Compression := cmFlateDecode; // default is cmNone
Pdf.FontEmbedding := True; // already the default, stated for the reader
Pdf.EnableFontSubsetting := True; // embed only the glyphs actually drawn
Pdf.BeginDoc; // creates page 1 (A4)
// The 4th argument defaults to ANSI_CHARSET; pass DEFAULT_CHARSET explicitly
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 14, DEFAULT_CHARSET);
// X, Y in points from the TOP-LEFT corner; Y is the top edge of the line
Pdf.CurrentPage.TextOut(72, 72, 0, 'Hello, Delphi PDF world!'); // English
Pdf.CurrentPage.TextOut(72, 102, 0, 'Hola, mundo Delphi PDF!'); // Spanish
Pdf.CurrentPage.TextOut(72, 132, 0, 'Hallo, Delphi PDF Welt!'); // German
Pdf.CurrentPage.TextOut(72, 162, 0, 'Bonjour, monde PDF Delphi!'); // French
Pdf.CurrentPage.TextOut(72, 192, 0, 'Ciao, mondo Delphi PDF!'); // Italian
Pdf.CurrentPage.TextOut(72, 222, 0, 'Olá, mundo Delphi PDF!'); // Portuguese
Pdf.CurrentPage.TextOut(72, 252, 0, 'Здравствуйте, Delphi PDF мир!'); // Russian
Pdf.CurrentPage.TextOut(72, 282, 0, 'こんにちは、Delphi PDFの世界!'); // Japanese
Pdf.CurrentPage.TextOut(72, 312, 0, 'Merhaba, Delphi PDF dünyası!'); // Turkish
Pdf.CurrentPage.TextOut(72, 342, 0, '你好,Delphi PDF世界'); // Chinese
Pdf.CurrentPage.TextOut(72, 372, 0, '여보세요, Delphi PDF 세계!'); // Korean
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
begin
CreateHelloWorld('HelloWorld.pdf');
Writeln('Wrote HelloWorld.pdf');
end.

THotPDF.Create(nil) paired with try/finally ... Free is the usual Delphi ownership pattern, and producing a PDF does not change it. The lines march downward from Y = 72, 30 points apart, which leaves comfortable leading for 14-point text. Run it and HelloWorld.pdf appears in the working directory: one page, compressed content, the font embedded as a subset
Document properties belong before BeginDoc
BeginDoc starts the document and creates its first page, so anything that shapes the whole file has to be assigned ahead of it. HotPDF enforces this for some properties rather than leaving it to convention. Assign Compression after BeginDoc and the setter raises an exception with the message Cannot set Compression value - document in progress.; AutoLaunch is guarded the same way. Loud is good here. Put every document setting in one block above BeginDoc and you never see that message
Two of the defaults deserve a second look. Compression starts out as cmNone, so a program that never touches it writes uncompressed content streams; for a page of text that is merely wasteful, for a report with hundreds of pages it is the first thing anyone notices about the file size. AutoLaunch starts out as True, which means a console tool opens the finished PDF in the associated viewer the moment EndDoc returns. That is pleasant at your desk and wrong inside a service, a scheduled job or a build agent. For those environments, set it to False in code; for CI runs where you cannot change the code, the HOTPDF_SUPPRESS_AUTO_LAUNCH environment variable set to 1 switches the launch off as well
Font handling is where we take a side. FontEmbedding is True by default, so the face travels with the file and renders on a machine that never had Arial Unicode MS installed. EnableFontSubsetting, on the other hand, is off by default to keep full fonts available for editing workflows. For a generated greeting, invoice or report, turn subsetting on. Arial Unicode MS covers a huge range of scripts, and embedding the whole face to show eleven short lines would make the file megabytes larger for no visible benefit. Leave it off only when someone downstream will edit the text and needs glyphs you did not draw
Where is (0, 0) on a HotPDF page?
HotPDF measures page coordinates from the top-left corner with Y increasing downward, the same frame a VCL TCanvas uses. At the default Resolution of 72, one unit is one point, 1/72 inch. The PDF file itself still stores bottom-up coordinates, as ISO 32000-1 §8.3.2.3 requires; HotPDF converts every Y you pass by subtracting it from the page height when it writes the content stream
The arithmetic behind the opening example is short. The default page that BeginDoc and AddPage create is A4, 595 by 842 points. A line meant to sit one inch from the top is Y = 72 in HotPDF terms and y = 770 in raw PDF terms, because 842 − 72 = 770. Pass 770 to TextOut and the text goes 770 points down from the top, which is 72 points above the bottom edge
There is a second, smaller trap in the same call. By default the Y you pass is the top edge of the text, and HotPDF moves down by the text height before placing the baseline. Code ported from GDI usually expects exactly that. Code ported from another PDF library, or from your own content-stream writer, usually expects Y to be the baseline, and every line comes out one text height lower than planned. THPDFPage.TextFromBaseLine(True) switches the current page to baseline positioning:
// Bridge for coordinates that come from a bottom-up source (a PDF layout,
// a spec diagram, another library). Helper written for this article,
// not part of HotPDF. Valid at the default Resolution of 72
function FromBottomUp(Page: THPDFPage; PdfY: Single): Single;
begin
Result := Page.Height - PdfY;
end;
procedure DrawFromPdfCoordinates(Pdf: THotPDF);
begin
Pdf.CurrentPage.SetFont('Arial', [], 12);
// Y now names the baseline, the way PDF's own text operators place glyphs
Pdf.CurrentPage.TextFromBaseLine(True);
// Baseline 770 points above the bottom edge, i.e. Y = 72 from the top
Pdf.CurrentPage.TextOut(72, FromBottomUp(Pdf.CurrentPage, 770), 0,
'Baseline one inch below the top edge');
end;
Resolution is the other setting that changes what the numbers mean. Set THotPDF.Resolution to 96 and X/Y coordinates, margins and TextWidth results are expressed in 1/96 inch, which is handy when the layout was designed in screen pixels. Page Width and Height keep reporting points, though, so layout code that mixes page bounds with drawing coordinates should read CurrentPage.UserWidth and CurrentPage.UserHeight, which come back in the same units as X and Y. At 72 the two pairs agree. If you would rather think in millimeters, the MeasurementUnit property (muPoints by default, with muMM, muInches, muCentimeters, muTwips and muPixels available) is the cleaner switch. Pick one convention per program. Never stack them
Why does page two print in 12-point Arial?
Because every new page starts from a known state. THotPDF.AddPage makes the new page current, sizes it A4 and calls SetFont('Arial', [], 12) on it, keeping the size at 12 points whatever the Resolution. BeginDoc creates page one through the same path, which is why the Hello World sets its font right after BeginDoc and not before. The consequence for multi-page output is easy to miss: the SetFont you called on page one does not carry over. Latin lines on page two still look fine in Arial, just smaller. The CJK lines are the giveaway, since Arial has no glyphs for them and the reader shows empty boxes or nothing at all
A predictable starting font is the right design for a page object: drawing code for page five does not silently depend on whatever page four happened to leave behind. The cost is that page setup has to be repeated, and the cleanest way to repeat it is a single procedure that every page goes through, including the first:
uses
Winapi.Windows, Vcl.Graphics, HPDFDoc;
// Your own helper: one place that knows what a fresh page needs
procedure PreparePage(Pdf: THotPDF; IsFirst: Boolean);
begin
if not IsFirst then
Pdf.AddPage; // resets the font to Arial 12
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 14, DEFAULT_CHARSET);
end;
procedure WriteGreetings(Pdf: THotPDF; Lines: array of string);
const
TopMargin = 72;
Leading = 30;
LinesPerPage = 20;
var
I, Row: Integer;
begin
PreparePage(Pdf, True); // page 1 already exists after BeginDoc
Row := 0;
for I := 0 to High(Lines) do
begin
if Row = LinesPerPage then
begin
PreparePage(Pdf, False);
Row := 0;
end;
Pdf.CurrentPage.TextOut(72, TopMargin + Row * Leading, 0, Lines[I]);
Inc(Row);
end;
// A bold heading needs its own SetFont, then the body font comes back
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial Unicode MS', [fsBold], 18, DEFAULT_CHARSET);
Pdf.CurrentPage.TextOut(72, 72, 0, 'Appendix');
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 14, DEFAULT_CHARSET);
Pdf.CurrentPage.TextOut(72, 110, 0, 'Body text after the heading');
end;
If your pages also switch to baseline positioning with TextFromBaseLine, put that call in PreparePage too, next to the font. Keeping every per-page setting in one procedure means a new requirement, a color or a different size, gets added once and reaches every page
One font for many scripts, and where it stops
A single SetFont can carry Latin, Cyrillic, Japanese and Chinese on one page because Arial Unicode MS bundles glyphs for a very wide range of scripts in one face. The fourth argument of SetFont is the Windows character set, and its default is ANSI_CHARSET, which ties font selection to the Western code page. Passing DEFAULT_CHARSET (value 1) lets Windows pick the face by name instead, which is what a multilingual page wants
The font has to exist on the machine that builds the PDF. HotPDF embeds whatever font Windows resolves the name to, and when Arial Unicode MS is missing, Windows substitutes another face without complaint. The output then embeds the substitute, and lines in scripts that face does not cover come out as empty boxes. A build server is the usual place this shows up, because nobody installed Office fonts there. Check the embedded font list of the first file a new machine produces before trusting it with a batch run
When no single face covers every string, TextOutWithFallback takes the same arguments as TextOut, resolves a font for each character, draws each run with the face that can render it and restores your original font afterward. Right-to-left scripts such as Arabic and Hebrew need more than glyph coverage: they need shaping and reversed run order, which is the territory of the RtLTextOut function, usually paired with a TrueType file loaded through RegisterUnicodeTTF
From here the natural next steps are size, style and rotation of text, and placing several blocks deliberately on one page, which the TextOut sample works through. Rules, boxes and images use the same CurrentPage object and the same top-left coordinates, so everything above about origins and per-page setup applies to them unchanged
The HotPDF Delphi Component page covers the rest of the drawing API for Delphi and C++Builder, from shapes and images to forms, once this Hello World runs cleanly against your own fonts