[{"data":1,"prerenderedAt":3228},["ShallowReactive",2],{"blog-en-page-numbers-headers-footers":3},{"id":4,"title":5,"author":6,"body":9,"date":3193,"description":3194,"draft":3195,"extension":3196,"howTo":3197,"image":3219,"meta":3220,"navigation":134,"path":3221,"seo":3222,"stem":3223,"tags":3224,"updated":3219,"__hash__":3227},"blog/blog/027.page-numbers-headers-footers.md","Page numbers, headers, and footers that just work in Go PDFs",{"name":7,"url":8},"gpdf team","https://gpdf.dev",{"type":10,"value":11,"toc":3178},"minimark",[12,25,36,39,44,85,92,96,107,1479,1497,1501,1511,1514,1595,1611,1616,1622,1643,1651,1658,1662,1677,1944,1947,1954,1961,1965,1982,2309,2324,2332,2502,2511,2515,2518,2531,2540,2703,2712,2721,2731,2735,2750,3024,3031,3035,3038,3041,3047,3050,3054,3064,3070,3076,3086,3111,3121,3125,3128,3135,3139,3142,3159,3174],[13,14,15,16,20,21,24],"p",{},"A 60-page financial report. Someone opens page 12 in the print queue and asks one question: which page is this, and how many are left? If the footer just says ",[17,18,19],"code",{},"12",", nobody knows. It needs to say ",[17,22,23],{},"12 of 60",".",[13,26,27,28,31,32,35],{},"That ",[17,29,30],{},"60"," is the part most PDF libraries get wrong. Either the total page count isn't available at the time you write the footer, or it ships behind some ",[17,33,34],{},"AliasNbPages"," token you have to call after the build, or you end up rendering the document twice and discarding the first pass.",[13,37,38],{},"gpdf gets this right with two builder methods and a two-pass paginator. This is the post on how it works, what the API looks like, and the one rough edge you should know about.",[40,41,43],"h2",{"id":42},"tldr","TL;DR",[45,46,47,58,61,71,74],"ul",{},[48,49,50,53,54,57],"li",{},[17,51,52],{},"doc.Header(fn)"," and ",[17,55,56],{},"doc.Footer(fn)"," register a closure that runs on every page.",[48,59,60],{},"Inside that closure, use the same 12-column grid you use for body content.",[48,62,63,66,67,70],{},[17,64,65],{},"c.PageNumber()"," prints the current page number. ",[17,68,69],{},"c.TotalPages()"," prints the total.",[48,72,73],{},"The total is resolved in a second pass, after pagination completes. There is no two-pass build step you have to write yourself.",[48,75,76,77,80,81,84],{},"One sharp edge: there is no ",[17,78,79],{},"c.PageNumberOf(total)"," helper that prints ",[17,82,83],{},"\"3 of 12\""," as one inline string. You compose it from three columns. More on this below.",[13,86,87,88,91],{},"The code is real. Every snippet in this post is pulled from ",[17,89,90],{},"gpdf/_examples/builder/26_page_number_test.go",", which is part of the test suite.",[40,93,95],{"id":94},"the-whole-thing-in-one-file","The whole thing in one file",[13,97,98,99,102,103,106],{},"This is a complete program. Save it as ",[17,100,101],{},"main.go",", run ",[17,104,105],{},"go run main.go",", get a four-page PDF with a header on every page showing the total, and a footer showing the current page.",[108,109,114],"pre",{"className":110,"code":111,"language":112,"meta":113,"style":113},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","package main\n\nimport (\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/pdf\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := template.New(\n        template.WithPageSize(document.A4),\n        template.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    doc.Header(func(p *template.PageBuilder) {\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(6, func(c *template.ColBuilder) {\n                c.Text(\"Quarterly Report\", template.Bold(), template.FontSize(10))\n            })\n            r.Col(6, func(c *template.ColBuilder) {\n                c.TotalPages(template.AlignRight(), template.FontSize(9),\n                    template.TextColor(pdf.Gray(0.5)))\n            })\n        })\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Line(template.LineColor(pdf.RGBHex(0x1565C0)))\n                c.Spacer(document.Mm(3))\n            })\n        })\n    })\n\n    doc.Footer(func(p *template.PageBuilder) {\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Spacer(document.Mm(3))\n                c.Line(template.LineColor(pdf.Gray(0.7)))\n                c.Spacer(document.Mm(2))\n            })\n        })\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(6, func(c *template.ColBuilder) {\n                c.Text(\"Generated by gpdf\", template.FontSize(8),\n                    template.TextColor(pdf.Gray(0.5)))\n            })\n            r.Col(6, func(c *template.ColBuilder) {\n                c.PageNumber(template.AlignRight(), template.FontSize(8),\n                    template.TextColor(pdf.Gray(0.5)))\n            })\n        })\n    })\n\n    for i, title := range []string{\"Introduction\", \"Background\", \"Analysis\", \"Conclusion\"} {\n        page := doc.AddPage()\n        page.AutoRow(func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Text(title, template.FontSize(18), template.Bold())\n                c.Spacer(document.Mm(5))\n                c.Text(\"Body content for section \" + title + \".\")\n            })\n        })\n        _ = i\n    }\n\n    out, err := doc.Generate()\n    if err != nil {\n        panic(err)\n    }\n    _ = os.WriteFile(\"report.pdf\", out, 0o644)\n}\n","go","",[17,115,116,129,136,146,158,163,173,183,193,199,204,220,241,266,303,309,314,347,376,414,463,469,500,534,563,568,574,599,630,665,690,695,700,706,711,737,762,793,816,848,872,877,882,907,938,971,994,999,1030,1062,1085,1090,1095,1100,1105,1174,1193,1219,1250,1289,1313,1347,1352,1357,1369,1375,1380,1402,1418,1431,1436,1473],{"__ignoreMap":113},[117,118,121,125],"span",{"class":119,"line":120},"line",1,[117,122,124],{"class":123},"sMK4o","package",[117,126,128],{"class":127},"sBMFI"," main\n",[117,130,132],{"class":119,"line":131},2,[117,133,135],{"emptyLinePlaceholder":134},true,"\n",[117,137,139,143],{"class":119,"line":138},3,[117,140,142],{"class":141},"s7zQu","import",[117,144,145],{"class":123}," (\n",[117,147,149,152,155],{"class":119,"line":148},4,[117,150,151],{"class":123},"    \"",[117,153,154],{"class":127},"os",[117,156,157],{"class":123},"\"\n",[117,159,161],{"class":119,"line":160},5,[117,162,135],{"emptyLinePlaceholder":134},[117,164,166,168,171],{"class":119,"line":165},6,[117,167,151],{"class":123},[117,169,170],{"class":127},"github.com/gpdf-dev/gpdf/document",[117,172,157],{"class":123},[117,174,176,178,181],{"class":119,"line":175},7,[117,177,151],{"class":123},[117,179,180],{"class":127},"github.com/gpdf-dev/gpdf/pdf",[117,182,157],{"class":123},[117,184,186,188,191],{"class":119,"line":185},8,[117,187,151],{"class":123},[117,189,190],{"class":127},"github.com/gpdf-dev/gpdf/template",[117,192,157],{"class":123},[117,194,196],{"class":119,"line":195},9,[117,197,198],{"class":123},")\n",[117,200,202],{"class":119,"line":201},10,[117,203,135],{"emptyLinePlaceholder":134},[117,205,207,210,214,217],{"class":119,"line":206},11,[117,208,209],{"class":123},"func",[117,211,213],{"class":212},"s2Zo4"," main",[117,215,216],{"class":123},"()",[117,218,219],{"class":123}," {\n",[117,221,223,227,230,233,235,238],{"class":119,"line":222},12,[117,224,226],{"class":225},"sTEyZ","    doc ",[117,228,229],{"class":123},":=",[117,231,232],{"class":225}," template",[117,234,24],{"class":123},[117,236,237],{"class":212},"New",[117,239,240],{"class":123},"(\n",[117,242,244,247,249,252,255,258,260,263],{"class":119,"line":243},13,[117,245,246],{"class":225},"        template",[117,248,24],{"class":123},[117,250,251],{"class":212},"WithPageSize",[117,253,254],{"class":123},"(",[117,256,257],{"class":225},"document",[117,259,24],{"class":123},[117,261,262],{"class":225},"A4",[117,264,265],{"class":123},"),\n",[117,267,269,271,273,276,278,280,282,285,287,289,291,294,296,300],{"class":119,"line":268},14,[117,270,246],{"class":225},[117,272,24],{"class":123},[117,274,275],{"class":212},"WithMargins",[117,277,254],{"class":123},[117,279,257],{"class":225},[117,281,24],{"class":123},[117,283,284],{"class":212},"UniformEdges",[117,286,254],{"class":123},[117,288,257],{"class":225},[117,290,24],{"class":123},[117,292,293],{"class":212},"Mm",[117,295,254],{"class":123},[117,297,299],{"class":298},"sbssI","20",[117,301,302],{"class":123},"))),\n",[117,304,306],{"class":119,"line":305},15,[117,307,308],{"class":123},"    )\n",[117,310,312],{"class":119,"line":311},16,[117,313,135],{"emptyLinePlaceholder":134},[117,315,317,320,322,325,328,331,334,337,339,342,345],{"class":119,"line":316},17,[117,318,319],{"class":225},"    doc",[117,321,24],{"class":123},[117,323,324],{"class":212},"Header",[117,326,327],{"class":123},"(func(",[117,329,13],{"class":330},"sHdIc",[117,332,333],{"class":123}," *",[117,335,336],{"class":127},"template",[117,338,24],{"class":123},[117,340,341],{"class":127},"PageBuilder",[117,343,344],{"class":123},")",[117,346,219],{"class":123},[117,348,350,353,355,358,360,363,365,367,369,372,374],{"class":119,"line":349},18,[117,351,352],{"class":225},"        p",[117,354,24],{"class":123},[117,356,357],{"class":212},"AutoRow",[117,359,327],{"class":123},[117,361,362],{"class":330},"r",[117,364,333],{"class":123},[117,366,336],{"class":127},[117,368,24],{"class":123},[117,370,371],{"class":127},"RowBuilder",[117,373,344],{"class":123},[117,375,219],{"class":123},[117,377,379,382,384,387,389,392,395,398,401,403,405,407,410,412],{"class":119,"line":378},19,[117,380,381],{"class":225},"            r",[117,383,24],{"class":123},[117,385,386],{"class":212},"Col",[117,388,254],{"class":123},[117,390,391],{"class":298},"6",[117,393,394],{"class":123},",",[117,396,397],{"class":123}," func(",[117,399,400],{"class":330},"c",[117,402,333],{"class":123},[117,404,336],{"class":127},[117,406,24],{"class":123},[117,408,409],{"class":127},"ColBuilder",[117,411,344],{"class":123},[117,413,219],{"class":123},[117,415,417,420,422,425,427,430,434,436,438,440,442,445,448,450,452,455,457,460],{"class":119,"line":416},20,[117,418,419],{"class":225},"                c",[117,421,24],{"class":123},[117,423,424],{"class":212},"Text",[117,426,254],{"class":123},[117,428,429],{"class":123},"\"",[117,431,433],{"class":432},"sfazB","Quarterly Report",[117,435,429],{"class":123},[117,437,394],{"class":123},[117,439,232],{"class":225},[117,441,24],{"class":123},[117,443,444],{"class":212},"Bold",[117,446,447],{"class":123},"(),",[117,449,232],{"class":225},[117,451,24],{"class":123},[117,453,454],{"class":212},"FontSize",[117,456,254],{"class":123},[117,458,459],{"class":298},"10",[117,461,462],{"class":123},"))\n",[117,464,466],{"class":119,"line":465},21,[117,467,468],{"class":123},"            })\n",[117,470,472,474,476,478,480,482,484,486,488,490,492,494,496,498],{"class":119,"line":471},22,[117,473,381],{"class":225},[117,475,24],{"class":123},[117,477,386],{"class":212},[117,479,254],{"class":123},[117,481,391],{"class":298},[117,483,394],{"class":123},[117,485,397],{"class":123},[117,487,400],{"class":330},[117,489,333],{"class":123},[117,491,336],{"class":127},[117,493,24],{"class":123},[117,495,409],{"class":127},[117,497,344],{"class":123},[117,499,219],{"class":123},[117,501,503,505,507,510,512,514,516,519,521,523,525,527,529,532],{"class":119,"line":502},23,[117,504,419],{"class":225},[117,506,24],{"class":123},[117,508,509],{"class":212},"TotalPages",[117,511,254],{"class":123},[117,513,336],{"class":225},[117,515,24],{"class":123},[117,517,518],{"class":212},"AlignRight",[117,520,447],{"class":123},[117,522,232],{"class":225},[117,524,24],{"class":123},[117,526,454],{"class":212},[117,528,254],{"class":123},[117,530,531],{"class":298},"9",[117,533,265],{"class":123},[117,535,537,540,542,545,547,550,552,555,557,560],{"class":119,"line":536},24,[117,538,539],{"class":225},"                    template",[117,541,24],{"class":123},[117,543,544],{"class":212},"TextColor",[117,546,254],{"class":123},[117,548,549],{"class":225},"pdf",[117,551,24],{"class":123},[117,553,554],{"class":212},"Gray",[117,556,254],{"class":123},[117,558,559],{"class":298},"0.5",[117,561,562],{"class":123},")))\n",[117,564,566],{"class":119,"line":565},25,[117,567,468],{"class":123},[117,569,571],{"class":119,"line":570},26,[117,572,573],{"class":123},"        })\n",[117,575,577,579,581,583,585,587,589,591,593,595,597],{"class":119,"line":576},27,[117,578,352],{"class":225},[117,580,24],{"class":123},[117,582,357],{"class":212},[117,584,327],{"class":123},[117,586,362],{"class":330},[117,588,333],{"class":123},[117,590,336],{"class":127},[117,592,24],{"class":123},[117,594,371],{"class":127},[117,596,344],{"class":123},[117,598,219],{"class":123},[117,600,602,604,606,608,610,612,614,616,618,620,622,624,626,628],{"class":119,"line":601},28,[117,603,381],{"class":225},[117,605,24],{"class":123},[117,607,386],{"class":212},[117,609,254],{"class":123},[117,611,19],{"class":298},[117,613,394],{"class":123},[117,615,397],{"class":123},[117,617,400],{"class":330},[117,619,333],{"class":123},[117,621,336],{"class":127},[117,623,24],{"class":123},[117,625,409],{"class":127},[117,627,344],{"class":123},[117,629,219],{"class":123},[117,631,633,635,637,640,642,644,646,649,651,653,655,658,660,663],{"class":119,"line":632},29,[117,634,419],{"class":225},[117,636,24],{"class":123},[117,638,639],{"class":212},"Line",[117,641,254],{"class":123},[117,643,336],{"class":225},[117,645,24],{"class":123},[117,647,648],{"class":212},"LineColor",[117,650,254],{"class":123},[117,652,549],{"class":225},[117,654,24],{"class":123},[117,656,657],{"class":212},"RGBHex",[117,659,254],{"class":123},[117,661,662],{"class":298},"0x1565C0",[117,664,562],{"class":123},[117,666,668,670,672,675,677,679,681,683,685,688],{"class":119,"line":667},30,[117,669,419],{"class":225},[117,671,24],{"class":123},[117,673,674],{"class":212},"Spacer",[117,676,254],{"class":123},[117,678,257],{"class":225},[117,680,24],{"class":123},[117,682,293],{"class":212},[117,684,254],{"class":123},[117,686,687],{"class":298},"3",[117,689,462],{"class":123},[117,691,693],{"class":119,"line":692},31,[117,694,468],{"class":123},[117,696,698],{"class":119,"line":697},32,[117,699,573],{"class":123},[117,701,703],{"class":119,"line":702},33,[117,704,705],{"class":123},"    })\n",[117,707,709],{"class":119,"line":708},34,[117,710,135],{"emptyLinePlaceholder":134},[117,712,714,716,718,721,723,725,727,729,731,733,735],{"class":119,"line":713},35,[117,715,319],{"class":225},[117,717,24],{"class":123},[117,719,720],{"class":212},"Footer",[117,722,327],{"class":123},[117,724,13],{"class":330},[117,726,333],{"class":123},[117,728,336],{"class":127},[117,730,24],{"class":123},[117,732,341],{"class":127},[117,734,344],{"class":123},[117,736,219],{"class":123},[117,738,740,742,744,746,748,750,752,754,756,758,760],{"class":119,"line":739},36,[117,741,352],{"class":225},[117,743,24],{"class":123},[117,745,357],{"class":212},[117,747,327],{"class":123},[117,749,362],{"class":330},[117,751,333],{"class":123},[117,753,336],{"class":127},[117,755,24],{"class":123},[117,757,371],{"class":127},[117,759,344],{"class":123},[117,761,219],{"class":123},[117,763,765,767,769,771,773,775,777,779,781,783,785,787,789,791],{"class":119,"line":764},37,[117,766,381],{"class":225},[117,768,24],{"class":123},[117,770,386],{"class":212},[117,772,254],{"class":123},[117,774,19],{"class":298},[117,776,394],{"class":123},[117,778,397],{"class":123},[117,780,400],{"class":330},[117,782,333],{"class":123},[117,784,336],{"class":127},[117,786,24],{"class":123},[117,788,409],{"class":127},[117,790,344],{"class":123},[117,792,219],{"class":123},[117,794,796,798,800,802,804,806,808,810,812,814],{"class":119,"line":795},38,[117,797,419],{"class":225},[117,799,24],{"class":123},[117,801,674],{"class":212},[117,803,254],{"class":123},[117,805,257],{"class":225},[117,807,24],{"class":123},[117,809,293],{"class":212},[117,811,254],{"class":123},[117,813,687],{"class":298},[117,815,462],{"class":123},[117,817,819,821,823,825,827,829,831,833,835,837,839,841,843,846],{"class":119,"line":818},39,[117,820,419],{"class":225},[117,822,24],{"class":123},[117,824,639],{"class":212},[117,826,254],{"class":123},[117,828,336],{"class":225},[117,830,24],{"class":123},[117,832,648],{"class":212},[117,834,254],{"class":123},[117,836,549],{"class":225},[117,838,24],{"class":123},[117,840,554],{"class":212},[117,842,254],{"class":123},[117,844,845],{"class":298},"0.7",[117,847,562],{"class":123},[117,849,851,853,855,857,859,861,863,865,867,870],{"class":119,"line":850},40,[117,852,419],{"class":225},[117,854,24],{"class":123},[117,856,674],{"class":212},[117,858,254],{"class":123},[117,860,257],{"class":225},[117,862,24],{"class":123},[117,864,293],{"class":212},[117,866,254],{"class":123},[117,868,869],{"class":298},"2",[117,871,462],{"class":123},[117,873,875],{"class":119,"line":874},41,[117,876,468],{"class":123},[117,878,880],{"class":119,"line":879},42,[117,881,573],{"class":123},[117,883,885,887,889,891,893,895,897,899,901,903,905],{"class":119,"line":884},43,[117,886,352],{"class":225},[117,888,24],{"class":123},[117,890,357],{"class":212},[117,892,327],{"class":123},[117,894,362],{"class":330},[117,896,333],{"class":123},[117,898,336],{"class":127},[117,900,24],{"class":123},[117,902,371],{"class":127},[117,904,344],{"class":123},[117,906,219],{"class":123},[117,908,910,912,914,916,918,920,922,924,926,928,930,932,934,936],{"class":119,"line":909},44,[117,911,381],{"class":225},[117,913,24],{"class":123},[117,915,386],{"class":212},[117,917,254],{"class":123},[117,919,391],{"class":298},[117,921,394],{"class":123},[117,923,397],{"class":123},[117,925,400],{"class":330},[117,927,333],{"class":123},[117,929,336],{"class":127},[117,931,24],{"class":123},[117,933,409],{"class":127},[117,935,344],{"class":123},[117,937,219],{"class":123},[117,939,941,943,945,947,949,951,954,956,958,960,962,964,966,969],{"class":119,"line":940},45,[117,942,419],{"class":225},[117,944,24],{"class":123},[117,946,424],{"class":212},[117,948,254],{"class":123},[117,950,429],{"class":123},[117,952,953],{"class":432},"Generated by gpdf",[117,955,429],{"class":123},[117,957,394],{"class":123},[117,959,232],{"class":225},[117,961,24],{"class":123},[117,963,454],{"class":212},[117,965,254],{"class":123},[117,967,968],{"class":298},"8",[117,970,265],{"class":123},[117,972,974,976,978,980,982,984,986,988,990,992],{"class":119,"line":973},46,[117,975,539],{"class":225},[117,977,24],{"class":123},[117,979,544],{"class":212},[117,981,254],{"class":123},[117,983,549],{"class":225},[117,985,24],{"class":123},[117,987,554],{"class":212},[117,989,254],{"class":123},[117,991,559],{"class":298},[117,993,562],{"class":123},[117,995,997],{"class":119,"line":996},47,[117,998,468],{"class":123},[117,1000,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1028],{"class":119,"line":1001},48,[117,1003,381],{"class":225},[117,1005,24],{"class":123},[117,1007,386],{"class":212},[117,1009,254],{"class":123},[117,1011,391],{"class":298},[117,1013,394],{"class":123},[117,1015,397],{"class":123},[117,1017,400],{"class":330},[117,1019,333],{"class":123},[117,1021,336],{"class":127},[117,1023,24],{"class":123},[117,1025,409],{"class":127},[117,1027,344],{"class":123},[117,1029,219],{"class":123},[117,1031,1033,1035,1037,1040,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060],{"class":119,"line":1032},49,[117,1034,419],{"class":225},[117,1036,24],{"class":123},[117,1038,1039],{"class":212},"PageNumber",[117,1041,254],{"class":123},[117,1043,336],{"class":225},[117,1045,24],{"class":123},[117,1047,518],{"class":212},[117,1049,447],{"class":123},[117,1051,232],{"class":225},[117,1053,24],{"class":123},[117,1055,454],{"class":212},[117,1057,254],{"class":123},[117,1059,968],{"class":298},[117,1061,265],{"class":123},[117,1063,1065,1067,1069,1071,1073,1075,1077,1079,1081,1083],{"class":119,"line":1064},50,[117,1066,539],{"class":225},[117,1068,24],{"class":123},[117,1070,544],{"class":212},[117,1072,254],{"class":123},[117,1074,549],{"class":225},[117,1076,24],{"class":123},[117,1078,554],{"class":212},[117,1080,254],{"class":123},[117,1082,559],{"class":298},[117,1084,562],{"class":123},[117,1086,1088],{"class":119,"line":1087},51,[117,1089,468],{"class":123},[117,1091,1093],{"class":119,"line":1092},52,[117,1094,573],{"class":123},[117,1096,1098],{"class":119,"line":1097},53,[117,1099,705],{"class":123},[117,1101,1103],{"class":119,"line":1102},54,[117,1104,135],{"emptyLinePlaceholder":134},[117,1106,1108,1111,1114,1116,1119,1121,1124,1127,1131,1134,1136,1139,1141,1143,1146,1149,1151,1153,1155,1158,1160,1162,1164,1167,1169,1172],{"class":119,"line":1107},55,[117,1109,1110],{"class":141},"    for",[117,1112,1113],{"class":225}," i",[117,1115,394],{"class":123},[117,1117,1118],{"class":225}," title ",[117,1120,229],{"class":123},[117,1122,1123],{"class":141}," range",[117,1125,1126],{"class":123}," []",[117,1128,1130],{"class":1129},"spNyl","string",[117,1132,1133],{"class":123},"{",[117,1135,429],{"class":123},[117,1137,1138],{"class":432},"Introduction",[117,1140,429],{"class":123},[117,1142,394],{"class":123},[117,1144,1145],{"class":123}," \"",[117,1147,1148],{"class":432},"Background",[117,1150,429],{"class":123},[117,1152,394],{"class":123},[117,1154,1145],{"class":123},[117,1156,1157],{"class":432},"Analysis",[117,1159,429],{"class":123},[117,1161,394],{"class":123},[117,1163,1145],{"class":123},[117,1165,1166],{"class":432},"Conclusion",[117,1168,429],{"class":123},[117,1170,1171],{"class":123},"}",[117,1173,219],{"class":123},[117,1175,1177,1180,1182,1185,1187,1190],{"class":119,"line":1176},56,[117,1178,1179],{"class":225},"        page ",[117,1181,229],{"class":123},[117,1183,1184],{"class":225}," doc",[117,1186,24],{"class":123},[117,1188,1189],{"class":212},"AddPage",[117,1191,1192],{"class":123},"()\n",[117,1194,1196,1199,1201,1203,1205,1207,1209,1211,1213,1215,1217],{"class":119,"line":1195},57,[117,1197,1198],{"class":225},"        page",[117,1200,24],{"class":123},[117,1202,357],{"class":212},[117,1204,327],{"class":123},[117,1206,362],{"class":330},[117,1208,333],{"class":123},[117,1210,336],{"class":127},[117,1212,24],{"class":123},[117,1214,371],{"class":127},[117,1216,344],{"class":123},[117,1218,219],{"class":123},[117,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248],{"class":119,"line":1221},58,[117,1223,381],{"class":225},[117,1225,24],{"class":123},[117,1227,386],{"class":212},[117,1229,254],{"class":123},[117,1231,19],{"class":298},[117,1233,394],{"class":123},[117,1235,397],{"class":123},[117,1237,400],{"class":330},[117,1239,333],{"class":123},[117,1241,336],{"class":127},[117,1243,24],{"class":123},[117,1245,409],{"class":127},[117,1247,344],{"class":123},[117,1249,219],{"class":123},[117,1251,1253,1255,1257,1259,1261,1264,1266,1268,1270,1272,1274,1277,1280,1282,1284,1286],{"class":119,"line":1252},59,[117,1254,419],{"class":225},[117,1256,24],{"class":123},[117,1258,424],{"class":212},[117,1260,254],{"class":123},[117,1262,1263],{"class":225},"title",[117,1265,394],{"class":123},[117,1267,232],{"class":225},[117,1269,24],{"class":123},[117,1271,454],{"class":212},[117,1273,254],{"class":123},[117,1275,1276],{"class":298},"18",[117,1278,1279],{"class":123},"),",[117,1281,232],{"class":225},[117,1283,24],{"class":123},[117,1285,444],{"class":212},[117,1287,1288],{"class":123},"())\n",[117,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1311],{"class":119,"line":1291},60,[117,1293,419],{"class":225},[117,1295,24],{"class":123},[117,1297,674],{"class":212},[117,1299,254],{"class":123},[117,1301,257],{"class":225},[117,1303,24],{"class":123},[117,1305,293],{"class":212},[117,1307,254],{"class":123},[117,1309,1310],{"class":298},"5",[117,1312,462],{"class":123},[117,1314,1316,1318,1320,1322,1324,1326,1329,1331,1334,1336,1339,1341,1343,1345],{"class":119,"line":1315},61,[117,1317,419],{"class":225},[117,1319,24],{"class":123},[117,1321,424],{"class":212},[117,1323,254],{"class":123},[117,1325,429],{"class":123},[117,1327,1328],{"class":432},"Body content for section ",[117,1330,429],{"class":123},[117,1332,1333],{"class":123}," +",[117,1335,1118],{"class":225},[117,1337,1338],{"class":123},"+",[117,1340,1145],{"class":123},[117,1342,24],{"class":432},[117,1344,429],{"class":123},[117,1346,198],{"class":123},[117,1348,1350],{"class":119,"line":1349},62,[117,1351,468],{"class":123},[117,1353,1355],{"class":119,"line":1354},63,[117,1356,573],{"class":123},[117,1358,1360,1363,1366],{"class":119,"line":1359},64,[117,1361,1362],{"class":225},"        _ ",[117,1364,1365],{"class":123},"=",[117,1367,1368],{"class":225}," i\n",[117,1370,1372],{"class":119,"line":1371},65,[117,1373,1374],{"class":123},"    }\n",[117,1376,1378],{"class":119,"line":1377},66,[117,1379,135],{"emptyLinePlaceholder":134},[117,1381,1383,1386,1388,1391,1393,1395,1397,1400],{"class":119,"line":1382},67,[117,1384,1385],{"class":225},"    out",[117,1387,394],{"class":123},[117,1389,1390],{"class":225}," err ",[117,1392,229],{"class":123},[117,1394,1184],{"class":225},[117,1396,24],{"class":123},[117,1398,1399],{"class":212},"Generate",[117,1401,1192],{"class":123},[117,1403,1405,1408,1410,1413,1416],{"class":119,"line":1404},68,[117,1406,1407],{"class":141},"    if",[117,1409,1390],{"class":225},[117,1411,1412],{"class":123},"!=",[117,1414,1415],{"class":123}," nil",[117,1417,219],{"class":123},[117,1419,1421,1424,1426,1429],{"class":119,"line":1420},69,[117,1422,1423],{"class":212},"        panic",[117,1425,254],{"class":123},[117,1427,1428],{"class":225},"err",[117,1430,198],{"class":123},[117,1432,1434],{"class":119,"line":1433},70,[117,1435,1374],{"class":123},[117,1437,1439,1442,1444,1447,1449,1452,1454,1456,1459,1461,1463,1466,1468,1471],{"class":119,"line":1438},71,[117,1440,1441],{"class":225},"    _ ",[117,1443,1365],{"class":123},[117,1445,1446],{"class":225}," os",[117,1448,24],{"class":123},[117,1450,1451],{"class":212},"WriteFile",[117,1453,254],{"class":123},[117,1455,429],{"class":123},[117,1457,1458],{"class":432},"report.pdf",[117,1460,429],{"class":123},[117,1462,394],{"class":123},[117,1464,1465],{"class":225}," out",[117,1467,394],{"class":123},[117,1469,1470],{"class":298}," 0o644",[117,1472,198],{"class":123},[117,1474,1476],{"class":119,"line":1475},72,[117,1477,1478],{"class":123},"}\n",[13,1480,1481,1482,1485,1486,1489,1490,1493,1494,1496],{},"Four pages, each with a header line saying ",[17,1483,1484],{},"Quarterly Report ........ 4"," and a footer saying ",[17,1487,1488],{},"Generated by gpdf ........ 1"," through ",[17,1491,1492],{},"4",". The total ",[17,1495,1492],{}," appears on every header without you ever telling gpdf how many pages the document has — because gpdf doesn't know until after pagination, either.",[40,1498,1500],{"id":1499},"why-page-x-of-y-is-the-hard-part","Why \"Page X of Y\" is the hard part",[13,1502,1503,1506,1507,1510],{},[17,1504,1505],{},"Y"," is annoying because the layout engine doesn't know it when it's drawing page 1. Imagine a 50-page document where page 47 happens to be split across a page boundary because a table row didn't fit. The total is ",[17,1508,1509],{},"50"," only after the paginator finishes. Page 1's footer was drawn long before that.",[13,1512,1513],{},"Every PDF library hits this wall. Here's how the most-used Go ones get around it:",[1515,1516,1517,1530],"table",{},[1518,1519,1520],"thead",{},[1521,1522,1523,1527],"tr",{},[1524,1525,1526],"th",{},"Library",[1524,1528,1529],{},"\"Page X of Y\" approach",[1531,1532,1533,1552,1560,1568,1582],"tbody",{},[1521,1534,1535,1539],{},[1536,1537,1538],"td",{},"gofpdf",[1536,1540,1541,1544,1545,1548,1549,1551],{},[17,1542,1543],{},"pdf.AliasNbPages(\"{nb}\")"," — you write ",[17,1546,1547],{},"{nb}"," as literal text, then call this method, then it rewrites the PDF stream after the fact, replacing every occurrence of ",[17,1550,1547],{}," with the total. Works, but you have to remember to call it, and the placeholder is a magic string.",[1521,1553,1554,1557],{},[1536,1555,1556],{},"go-pdf/fpdf",[1536,1558,1559],{},"Same as gofpdf. (It's a fork.)",[1521,1561,1562,1565],{},[1536,1563,1564],{},"signintech/gopdf",[1536,1566,1567],{},"No first-class support. You compute the total yourself by building the document, counting pages, and rebuilding.",[1521,1569,1570,1573],{},[1536,1571,1572],{},"maroto v2",[1536,1574,1575,1576,1578,1579,1581],{},"Provides a ",[17,1577,324],{},"/",[17,1580,720],{}," registration similar to gpdf. Page totals are resolved by a similar two-pass approach internally. Slower because the underlying engine is gofpdf-based (~10× slower than gpdf on common workloads).",[1521,1583,1584,1587],{},[1536,1585,1586],{},"gpdf",[1536,1588,1589,1591,1592,1594],{},[17,1590,65],{}," / ",[17,1593,69],{}," — typed method calls, no magic strings, resolved by an internal second pass.",[13,1596,1597,1598,1600,1601,1604,1605,1607,1608,1610],{},"The gpdf approach is the only one where the page-number primitive is part of the typed builder API rather than a string token. If you typo ",[17,1599,1547],{}," as ",[17,1602,1603],{},"{nB}"," in gofpdf, you get a ",[17,1606,1603],{}," literally printed in your footer. With ",[17,1609,69],{},", the worst you can do is forget to call it — and then there's just no number, not a wrong one.",[1612,1613,1615],"h3",{"id":1614},"how-the-second-pass-works","How the second pass works",[13,1617,1618,1619,1621],{},"Internally, gpdf renders ",[17,1620,65],{}," as a placeholder string — a sentinel that no real font glyph will ever match. When the paginator finishes laying out every page and knows the total, it walks the rendered text instructions and substitutes:",[1623,1624,1625,1637],"ol",{},[48,1626,1627,1631,1632,53,1634,1636],{},[1628,1629,1630],"strong",{},"Pass one (paginate)",": render every page, including header and footer, treating ",[17,1633,1039],{},[17,1635,509],{}," as fixed-width tokens. Compute the total page count.",[48,1638,1639,1642],{},[1628,1640,1641],{},"Pass two (resolve)",": walk back through the page tree, find each sentinel, and replace it with the actual current/total page number.",[13,1644,1645,1646,1648,1649,24],{},"The width of the placeholder is sized to fit the largest possible number (rough heuristic based on the document's expected page count), so the post-substitution layout doesn't shift. In practice this means right-aligned page numbers stay aligned even when the digit count changes from ",[17,1647,531],{}," to ",[17,1650,459],{},[13,1652,1653,1654,1657],{},"You don't write the second pass. You don't render the document twice. You call ",[17,1655,1656],{},"doc.Generate()"," and get bytes.",[40,1659,1661],{"id":1660},"header-and-footer-are-just-normal-layout","Header and footer are just normal layout",[13,1663,1664,1665,1668,1669,1672,1673,1676],{},"This part trips up people coming from gofpdf, where ",[17,1666,1667],{},"SetHeaderFunc"," runs a callback at a fixed Y coordinate and you place text with absolute ",[17,1670,1671],{},"Cell(...)"," calls. In gpdf, the header closure receives a ",[17,1674,1675],{},"*template.PageBuilder"," — the same type the body uses. The grid is the same. Rows and columns are the same. Styling is the same.",[108,1678,1680],{"className":110,"code":1679,"language":112,"meta":113,"style":113},"doc.Header(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(2, func(c *template.ColBuilder) {\n            c.Image(\"logo.png\", template.ImageHeight(document.Mm(12)))\n        })\n        r.Col(8, func(c *template.ColBuilder) {\n            c.Text(\"Annual Report 2026\", template.Bold(), template.FontSize(14))\n        })\n        r.Col(2, func(c *template.ColBuilder) {\n            c.TotalPages(template.AlignRight())\n        })\n    })\n})\n",[17,1681,1682,1707,1732,1763,1805,1809,1839,1879,1883,1913,1931,1935,1939],{"__ignoreMap":113},[117,1683,1684,1687,1689,1691,1693,1695,1697,1699,1701,1703,1705],{"class":119,"line":120},[117,1685,1686],{"class":225},"doc",[117,1688,24],{"class":123},[117,1690,324],{"class":212},[117,1692,327],{"class":123},[117,1694,13],{"class":330},[117,1696,333],{"class":123},[117,1698,336],{"class":127},[117,1700,24],{"class":123},[117,1702,341],{"class":127},[117,1704,344],{"class":123},[117,1706,219],{"class":123},[117,1708,1709,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730],{"class":119,"line":131},[117,1710,1711],{"class":225},"    p",[117,1713,24],{"class":123},[117,1715,357],{"class":212},[117,1717,327],{"class":123},[117,1719,362],{"class":330},[117,1721,333],{"class":123},[117,1723,336],{"class":127},[117,1725,24],{"class":123},[117,1727,371],{"class":127},[117,1729,344],{"class":123},[117,1731,219],{"class":123},[117,1733,1734,1737,1739,1741,1743,1745,1747,1749,1751,1753,1755,1757,1759,1761],{"class":119,"line":138},[117,1735,1736],{"class":225},"        r",[117,1738,24],{"class":123},[117,1740,386],{"class":212},[117,1742,254],{"class":123},[117,1744,869],{"class":298},[117,1746,394],{"class":123},[117,1748,397],{"class":123},[117,1750,400],{"class":330},[117,1752,333],{"class":123},[117,1754,336],{"class":127},[117,1756,24],{"class":123},[117,1758,409],{"class":127},[117,1760,344],{"class":123},[117,1762,219],{"class":123},[117,1764,1765,1768,1770,1773,1775,1777,1780,1782,1784,1786,1788,1791,1793,1795,1797,1799,1801,1803],{"class":119,"line":148},[117,1766,1767],{"class":225},"            c",[117,1769,24],{"class":123},[117,1771,1772],{"class":212},"Image",[117,1774,254],{"class":123},[117,1776,429],{"class":123},[117,1778,1779],{"class":432},"logo.png",[117,1781,429],{"class":123},[117,1783,394],{"class":123},[117,1785,232],{"class":225},[117,1787,24],{"class":123},[117,1789,1790],{"class":212},"ImageHeight",[117,1792,254],{"class":123},[117,1794,257],{"class":225},[117,1796,24],{"class":123},[117,1798,293],{"class":212},[117,1800,254],{"class":123},[117,1802,19],{"class":298},[117,1804,562],{"class":123},[117,1806,1807],{"class":119,"line":160},[117,1808,573],{"class":123},[117,1810,1811,1813,1815,1817,1819,1821,1823,1825,1827,1829,1831,1833,1835,1837],{"class":119,"line":165},[117,1812,1736],{"class":225},[117,1814,24],{"class":123},[117,1816,386],{"class":212},[117,1818,254],{"class":123},[117,1820,968],{"class":298},[117,1822,394],{"class":123},[117,1824,397],{"class":123},[117,1826,400],{"class":330},[117,1828,333],{"class":123},[117,1830,336],{"class":127},[117,1832,24],{"class":123},[117,1834,409],{"class":127},[117,1836,344],{"class":123},[117,1838,219],{"class":123},[117,1840,1841,1843,1845,1847,1849,1851,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872,1874,1877],{"class":119,"line":175},[117,1842,1767],{"class":225},[117,1844,24],{"class":123},[117,1846,424],{"class":212},[117,1848,254],{"class":123},[117,1850,429],{"class":123},[117,1852,1853],{"class":432},"Annual Report 2026",[117,1855,429],{"class":123},[117,1857,394],{"class":123},[117,1859,232],{"class":225},[117,1861,24],{"class":123},[117,1863,444],{"class":212},[117,1865,447],{"class":123},[117,1867,232],{"class":225},[117,1869,24],{"class":123},[117,1871,454],{"class":212},[117,1873,254],{"class":123},[117,1875,1876],{"class":298},"14",[117,1878,462],{"class":123},[117,1880,1881],{"class":119,"line":185},[117,1882,573],{"class":123},[117,1884,1885,1887,1889,1891,1893,1895,1897,1899,1901,1903,1905,1907,1909,1911],{"class":119,"line":195},[117,1886,1736],{"class":225},[117,1888,24],{"class":123},[117,1890,386],{"class":212},[117,1892,254],{"class":123},[117,1894,869],{"class":298},[117,1896,394],{"class":123},[117,1898,397],{"class":123},[117,1900,400],{"class":330},[117,1902,333],{"class":123},[117,1904,336],{"class":127},[117,1906,24],{"class":123},[117,1908,409],{"class":127},[117,1910,344],{"class":123},[117,1912,219],{"class":123},[117,1914,1915,1917,1919,1921,1923,1925,1927,1929],{"class":119,"line":201},[117,1916,1767],{"class":225},[117,1918,24],{"class":123},[117,1920,509],{"class":212},[117,1922,254],{"class":123},[117,1924,336],{"class":225},[117,1926,24],{"class":123},[117,1928,518],{"class":212},[117,1930,1288],{"class":123},[117,1932,1933],{"class":119,"line":206},[117,1934,573],{"class":123},[117,1936,1937],{"class":119,"line":222},[117,1938,705],{"class":123},[117,1940,1941],{"class":119,"line":243},[117,1942,1943],{"class":123},"})\n",[13,1945,1946],{},"That's a header with a logo on the left, a title in the middle, and the total page count on the right. Notice the column spans add up to 12, the same as a body row.",[13,1948,1949,1950,1953],{},"Header height is measured automatically. gpdf calls your header closure once before laying out body content, measures the height of the rendered output, and subtracts that from the available body height on every page. Footer the same way. You don't pass a ",[17,1951,1952],{},"headerHeight"," parameter. If you add a row to the header, the body shrinks accordingly.",[13,1955,1956,1957,1960],{},"Both repeat on every page, including pages created by content overflow. If a long table spills into page 12, page 12 gets the header and footer. There's no ",[17,1958,1959],{},"firstPageOnly"," flag — see the gotchas section below.",[40,1962,1964],{"id":1963},"the-rough-edge-page-x-of-y-in-one-line","The rough edge: \"Page X of Y\" in one line",[13,1966,1967,1968,1971,1972,1975,1976,53,1979,1981],{},"Here's the one place I think the API could be better. There is no ",[17,1969,1970],{},"c.PageOf(\"Page %d of %d\")"," helper. To produce the literal string ",[17,1973,1974],{},"\"Page 3 of 12\""," you have to compose it from columns, because ",[17,1977,1978],{},"c.Text()",[17,1980,65],{}," are independent column children:",[108,1983,1985],{"className":110,"code":1984,"language":112,"meta":113,"style":113},"r.Col(12, func(c *template.ColBuilder) {\n    c.AutoRow(func(r *template.RowBuilder) {\n        r.Col(3, func(c *template.ColBuilder) {\n            c.Text(\"Page\", template.AlignRight())\n        })\n        r.Col(2, func(c *template.ColBuilder) {\n            c.PageNumber(template.AlignCenter())\n        })\n        r.Col(2, func(c *template.ColBuilder) {\n            c.Text(\"of\", template.AlignCenter())\n        })\n        r.Col(3, func(c *template.ColBuilder) {\n            c.TotalPages(template.AlignLeft())\n        })\n        r.Col(2, func(c *template.ColBuilder) {})\n    })\n})\n",[17,1986,1987,2017,2042,2072,2099,2103,2133,2152,2156,2186,2213,2217,2247,2266,2270,2301,2305],{"__ignoreMap":113},[117,1988,1989,1991,1993,1995,1997,1999,2001,2003,2005,2007,2009,2011,2013,2015],{"class":119,"line":120},[117,1990,362],{"class":225},[117,1992,24],{"class":123},[117,1994,386],{"class":212},[117,1996,254],{"class":123},[117,1998,19],{"class":298},[117,2000,394],{"class":123},[117,2002,397],{"class":123},[117,2004,400],{"class":330},[117,2006,333],{"class":123},[117,2008,336],{"class":127},[117,2010,24],{"class":123},[117,2012,409],{"class":127},[117,2014,344],{"class":123},[117,2016,219],{"class":123},[117,2018,2019,2022,2024,2026,2028,2030,2032,2034,2036,2038,2040],{"class":119,"line":131},[117,2020,2021],{"class":225},"    c",[117,2023,24],{"class":123},[117,2025,357],{"class":212},[117,2027,327],{"class":123},[117,2029,362],{"class":330},[117,2031,333],{"class":123},[117,2033,336],{"class":127},[117,2035,24],{"class":123},[117,2037,371],{"class":127},[117,2039,344],{"class":123},[117,2041,219],{"class":123},[117,2043,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070],{"class":119,"line":138},[117,2045,1736],{"class":225},[117,2047,24],{"class":123},[117,2049,386],{"class":212},[117,2051,254],{"class":123},[117,2053,687],{"class":298},[117,2055,394],{"class":123},[117,2057,397],{"class":123},[117,2059,400],{"class":330},[117,2061,333],{"class":123},[117,2063,336],{"class":127},[117,2065,24],{"class":123},[117,2067,409],{"class":127},[117,2069,344],{"class":123},[117,2071,219],{"class":123},[117,2073,2074,2076,2078,2080,2082,2084,2087,2089,2091,2093,2095,2097],{"class":119,"line":148},[117,2075,1767],{"class":225},[117,2077,24],{"class":123},[117,2079,424],{"class":212},[117,2081,254],{"class":123},[117,2083,429],{"class":123},[117,2085,2086],{"class":432},"Page",[117,2088,429],{"class":123},[117,2090,394],{"class":123},[117,2092,232],{"class":225},[117,2094,24],{"class":123},[117,2096,518],{"class":212},[117,2098,1288],{"class":123},[117,2100,2101],{"class":119,"line":160},[117,2102,573],{"class":123},[117,2104,2105,2107,2109,2111,2113,2115,2117,2119,2121,2123,2125,2127,2129,2131],{"class":119,"line":165},[117,2106,1736],{"class":225},[117,2108,24],{"class":123},[117,2110,386],{"class":212},[117,2112,254],{"class":123},[117,2114,869],{"class":298},[117,2116,394],{"class":123},[117,2118,397],{"class":123},[117,2120,400],{"class":330},[117,2122,333],{"class":123},[117,2124,336],{"class":127},[117,2126,24],{"class":123},[117,2128,409],{"class":127},[117,2130,344],{"class":123},[117,2132,219],{"class":123},[117,2134,2135,2137,2139,2141,2143,2145,2147,2150],{"class":119,"line":175},[117,2136,1767],{"class":225},[117,2138,24],{"class":123},[117,2140,1039],{"class":212},[117,2142,254],{"class":123},[117,2144,336],{"class":225},[117,2146,24],{"class":123},[117,2148,2149],{"class":212},"AlignCenter",[117,2151,1288],{"class":123},[117,2153,2154],{"class":119,"line":185},[117,2155,573],{"class":123},[117,2157,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184],{"class":119,"line":195},[117,2159,1736],{"class":225},[117,2161,24],{"class":123},[117,2163,386],{"class":212},[117,2165,254],{"class":123},[117,2167,869],{"class":298},[117,2169,394],{"class":123},[117,2171,397],{"class":123},[117,2173,400],{"class":330},[117,2175,333],{"class":123},[117,2177,336],{"class":127},[117,2179,24],{"class":123},[117,2181,409],{"class":127},[117,2183,344],{"class":123},[117,2185,219],{"class":123},[117,2187,2188,2190,2192,2194,2196,2198,2201,2203,2205,2207,2209,2211],{"class":119,"line":201},[117,2189,1767],{"class":225},[117,2191,24],{"class":123},[117,2193,424],{"class":212},[117,2195,254],{"class":123},[117,2197,429],{"class":123},[117,2199,2200],{"class":432},"of",[117,2202,429],{"class":123},[117,2204,394],{"class":123},[117,2206,232],{"class":225},[117,2208,24],{"class":123},[117,2210,2149],{"class":212},[117,2212,1288],{"class":123},[117,2214,2215],{"class":119,"line":206},[117,2216,573],{"class":123},[117,2218,2219,2221,2223,2225,2227,2229,2231,2233,2235,2237,2239,2241,2243,2245],{"class":119,"line":222},[117,2220,1736],{"class":225},[117,2222,24],{"class":123},[117,2224,386],{"class":212},[117,2226,254],{"class":123},[117,2228,687],{"class":298},[117,2230,394],{"class":123},[117,2232,397],{"class":123},[117,2234,400],{"class":330},[117,2236,333],{"class":123},[117,2238,336],{"class":127},[117,2240,24],{"class":123},[117,2242,409],{"class":127},[117,2244,344],{"class":123},[117,2246,219],{"class":123},[117,2248,2249,2251,2253,2255,2257,2259,2261,2264],{"class":119,"line":243},[117,2250,1767],{"class":225},[117,2252,24],{"class":123},[117,2254,509],{"class":212},[117,2256,254],{"class":123},[117,2258,336],{"class":225},[117,2260,24],{"class":123},[117,2262,2263],{"class":212},"AlignLeft",[117,2265,1288],{"class":123},[117,2267,2268],{"class":119,"line":268},[117,2269,573],{"class":123},[117,2271,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290,2292,2294,2296,2298],{"class":119,"line":305},[117,2273,1736],{"class":225},[117,2275,24],{"class":123},[117,2277,386],{"class":212},[117,2279,254],{"class":123},[117,2281,869],{"class":298},[117,2283,394],{"class":123},[117,2285,397],{"class":123},[117,2287,400],{"class":330},[117,2289,333],{"class":123},[117,2291,336],{"class":127},[117,2293,24],{"class":123},[117,2295,409],{"class":127},[117,2297,344],{"class":123},[117,2299,2300],{"class":123}," {})\n",[117,2302,2303],{"class":119,"line":311},[117,2304,705],{"class":123},[117,2306,2307],{"class":119,"line":316},[117,2308,1943],{"class":123},[13,2310,2311,2312,2315,2316,2319,2320,2323],{},"It works. Visually it looks fine. But it's four columns to express what most people would write as a one-line format string. I'd call this a paper cut. We've been considering adding a ",[17,2313,2314],{},"c.PageOf(format string, opts ...TextOption)"," helper that takes a ",[17,2317,2318],{},"fmt.Sprintf","-style template with ",[17,2321,2322],{},"%d"," placeholders for the two numbers. If you have an opinion on the shape of that API, the issue is open on GitHub.",[13,2325,2326,2327,53,2329,2331],{},"For now, the four-column approach is what's there. The pragmatic shortcut is to drop the prefix and just print ",[17,2328,65],{},[17,2330,69],{}," in two adjacent columns separated by a slash:",[108,2333,2335],{"className":110,"code":2334,"language":112,"meta":113,"style":113},"r.Col(6, func(c *template.ColBuilder) {\n    c.PageNumber(template.AlignRight())\n})\nr.Col(1, func(c *template.ColBuilder) {\n    c.Text(\"/\", template.AlignCenter())\n})\nr.Col(5, func(c *template.ColBuilder) {\n    c.TotalPages(template.AlignLeft())\n})\n",[17,2336,2337,2367,2385,2389,2420,2446,2450,2480,2498],{"__ignoreMap":113},[117,2338,2339,2341,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361,2363,2365],{"class":119,"line":120},[117,2340,362],{"class":225},[117,2342,24],{"class":123},[117,2344,386],{"class":212},[117,2346,254],{"class":123},[117,2348,391],{"class":298},[117,2350,394],{"class":123},[117,2352,397],{"class":123},[117,2354,400],{"class":330},[117,2356,333],{"class":123},[117,2358,336],{"class":127},[117,2360,24],{"class":123},[117,2362,409],{"class":127},[117,2364,344],{"class":123},[117,2366,219],{"class":123},[117,2368,2369,2371,2373,2375,2377,2379,2381,2383],{"class":119,"line":131},[117,2370,2021],{"class":225},[117,2372,24],{"class":123},[117,2374,1039],{"class":212},[117,2376,254],{"class":123},[117,2378,336],{"class":225},[117,2380,24],{"class":123},[117,2382,518],{"class":212},[117,2384,1288],{"class":123},[117,2386,2387],{"class":119,"line":138},[117,2388,1943],{"class":123},[117,2390,2391,2393,2395,2397,2399,2402,2404,2406,2408,2410,2412,2414,2416,2418],{"class":119,"line":148},[117,2392,362],{"class":225},[117,2394,24],{"class":123},[117,2396,386],{"class":212},[117,2398,254],{"class":123},[117,2400,2401],{"class":298},"1",[117,2403,394],{"class":123},[117,2405,397],{"class":123},[117,2407,400],{"class":330},[117,2409,333],{"class":123},[117,2411,336],{"class":127},[117,2413,24],{"class":123},[117,2415,409],{"class":127},[117,2417,344],{"class":123},[117,2419,219],{"class":123},[117,2421,2422,2424,2426,2428,2430,2432,2434,2436,2438,2440,2442,2444],{"class":119,"line":160},[117,2423,2021],{"class":225},[117,2425,24],{"class":123},[117,2427,424],{"class":212},[117,2429,254],{"class":123},[117,2431,429],{"class":123},[117,2433,1578],{"class":432},[117,2435,429],{"class":123},[117,2437,394],{"class":123},[117,2439,232],{"class":225},[117,2441,24],{"class":123},[117,2443,2149],{"class":212},[117,2445,1288],{"class":123},[117,2447,2448],{"class":119,"line":165},[117,2449,1943],{"class":123},[117,2451,2452,2454,2456,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476,2478],{"class":119,"line":175},[117,2453,362],{"class":225},[117,2455,24],{"class":123},[117,2457,386],{"class":212},[117,2459,254],{"class":123},[117,2461,1310],{"class":298},[117,2463,394],{"class":123},[117,2465,397],{"class":123},[117,2467,400],{"class":330},[117,2469,333],{"class":123},[117,2471,336],{"class":127},[117,2473,24],{"class":123},[117,2475,409],{"class":127},[117,2477,344],{"class":123},[117,2479,219],{"class":123},[117,2481,2482,2484,2486,2488,2490,2492,2494,2496],{"class":119,"line":185},[117,2483,2021],{"class":225},[117,2485,24],{"class":123},[117,2487,509],{"class":212},[117,2489,254],{"class":123},[117,2491,336],{"class":225},[117,2493,24],{"class":123},[117,2495,2263],{"class":212},[117,2497,1288],{"class":123},[117,2499,2500],{"class":119,"line":195},[117,2501,1943],{"class":123},[13,2503,2504,2507,2508,2510],{},[17,2505,2506],{},"3 / 12"," reads fine in a footer. The full ",[17,2509,1974],{}," looks nicer but costs you three more columns of fiddling.",[40,2512,2514],{"id":2513},"patterns-that-come-up","Patterns that come up",[13,2516,2517],{},"A few configurations people actually want.",[13,2519,2520,2523,2524,2526,2527,2530],{},[1628,2521,2522],{},"Header line under the title."," Add a second ",[17,2525,357],{}," with ",[17,2528,2529],{},"c.Line()",". That's what the example at the top does. The line spans the full content width because the row spans 12 columns.",[13,2532,2533,2536,2537,2539],{},[1628,2534,2535],{},"Footer centered with confidentiality notice."," One row, one column, ",[17,2538,2149],{},". The simplest case.",[108,2541,2543],{"className":110,"code":2542,"language":112,"meta":113,"style":113},"doc.Footer(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Confidential — Internal Use Only\",\n                template.AlignCenter(),\n                template.FontSize(8),\n                template.TextColor(pdf.Gray(0.5)))\n        })\n    })\n})\n",[17,2544,2545,2569,2593,2623,2643,2655,2669,2691,2695,2699],{"__ignoreMap":113},[117,2546,2547,2549,2551,2553,2555,2557,2559,2561,2563,2565,2567],{"class":119,"line":120},[117,2548,1686],{"class":225},[117,2550,24],{"class":123},[117,2552,720],{"class":212},[117,2554,327],{"class":123},[117,2556,13],{"class":330},[117,2558,333],{"class":123},[117,2560,336],{"class":127},[117,2562,24],{"class":123},[117,2564,341],{"class":127},[117,2566,344],{"class":123},[117,2568,219],{"class":123},[117,2570,2571,2573,2575,2577,2579,2581,2583,2585,2587,2589,2591],{"class":119,"line":131},[117,2572,1711],{"class":225},[117,2574,24],{"class":123},[117,2576,357],{"class":212},[117,2578,327],{"class":123},[117,2580,362],{"class":330},[117,2582,333],{"class":123},[117,2584,336],{"class":127},[117,2586,24],{"class":123},[117,2588,371],{"class":127},[117,2590,344],{"class":123},[117,2592,219],{"class":123},[117,2594,2595,2597,2599,2601,2603,2605,2607,2609,2611,2613,2615,2617,2619,2621],{"class":119,"line":138},[117,2596,1736],{"class":225},[117,2598,24],{"class":123},[117,2600,386],{"class":212},[117,2602,254],{"class":123},[117,2604,19],{"class":298},[117,2606,394],{"class":123},[117,2608,397],{"class":123},[117,2610,400],{"class":330},[117,2612,333],{"class":123},[117,2614,336],{"class":127},[117,2616,24],{"class":123},[117,2618,409],{"class":127},[117,2620,344],{"class":123},[117,2622,219],{"class":123},[117,2624,2625,2627,2629,2631,2633,2635,2638,2640],{"class":119,"line":148},[117,2626,1767],{"class":225},[117,2628,24],{"class":123},[117,2630,424],{"class":212},[117,2632,254],{"class":123},[117,2634,429],{"class":123},[117,2636,2637],{"class":432},"Confidential — Internal Use Only",[117,2639,429],{"class":123},[117,2641,2642],{"class":123},",\n",[117,2644,2645,2648,2650,2652],{"class":119,"line":160},[117,2646,2647],{"class":225},"                template",[117,2649,24],{"class":123},[117,2651,2149],{"class":212},[117,2653,2654],{"class":123},"(),\n",[117,2656,2657,2659,2661,2663,2665,2667],{"class":119,"line":165},[117,2658,2647],{"class":225},[117,2660,24],{"class":123},[117,2662,454],{"class":212},[117,2664,254],{"class":123},[117,2666,968],{"class":298},[117,2668,265],{"class":123},[117,2670,2671,2673,2675,2677,2679,2681,2683,2685,2687,2689],{"class":119,"line":175},[117,2672,2647],{"class":225},[117,2674,24],{"class":123},[117,2676,544],{"class":212},[117,2678,254],{"class":123},[117,2680,549],{"class":225},[117,2682,24],{"class":123},[117,2684,554],{"class":212},[117,2686,254],{"class":123},[117,2688,559],{"class":298},[117,2690,562],{"class":123},[117,2692,2693],{"class":119,"line":185},[117,2694,573],{"class":123},[117,2696,2697],{"class":119,"line":195},[117,2698,705],{"class":123},[117,2700,2701],{"class":119,"line":201},[117,2702,1943],{"class":123},[13,2704,2705,2708,2709,2711],{},[1628,2706,2707],{},"Logo on the left, page number on the right."," Two columns split 8/4 or 6/6. Image in the left column, page number in the right with ",[17,2710,518],{},". Done.",[13,2713,2714,2717,2718,2720],{},[1628,2715,2716],{},"Footer that says \"Continued on next page\" on non-last pages."," Not currently supported. The header/footer closure receives a ",[17,2719,341],{},", not the current page index, so you can't branch on \"is this the last page?\" from inside the closure. If you need this, you have to add the trailing line to your body content on every page except the last, which means you need to know the page count ahead of time — which defeats the purpose. It's on the list.",[13,2722,2723,2726,2727,2730],{},[1628,2724,2725],{},"Different header on the first page."," Same issue. The closure doesn't know which page it's rendering. For now, the workaround is to leave the header empty on page 1 by putting a tall spacer at the top of page 1's body, then start your real header from page 2 onward by content placement — clunky. A ",[17,2728,2729],{},"doc.HeaderOn(pages, fn)"," variant is in design.",[40,2732,2734],{"id":2733},"cjk-in-the-footer","CJK in the footer",[13,2736,2737,2738,2741,2742,2745,2746,2749],{},"Because gpdf renders TrueType subsets without CGO, you can put Japanese, Chinese, or Korean text into headers and footers as plain ",[17,2739,2740],{},"c.Text(...)"," calls. No ",[17,2743,2744],{},"AddUTF8Font"," dance, no ",[17,2747,2748],{},"tofu"," boxes if the font is loaded. The only requirement is that the font you use covers the characters you want:",[108,2751,2753],{"className":110,"code":2752,"language":112,"meta":113,"style":113},"doc := template.New(\n    template.WithPageSize(document.A4),\n    template.WithFont(\"NotoSansJP\", notoSansJPRegular),\n)\n\ndoc.Footer(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"社外秘\", template.FontFamily(\"NotoSansJP\"), template.FontSize(8))\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.PageNumber(template.AlignRight(), template.FontSize(8))\n        })\n    })\n})\n",[17,2754,2755,2770,2789,2814,2818,2822,2846,2870,2900,2948,2952,2982,3012,3016,3020],{"__ignoreMap":113},[117,2756,2757,2760,2762,2764,2766,2768],{"class":119,"line":120},[117,2758,2759],{"class":225},"doc ",[117,2761,229],{"class":123},[117,2763,232],{"class":225},[117,2765,24],{"class":123},[117,2767,237],{"class":212},[117,2769,240],{"class":123},[117,2771,2772,2775,2777,2779,2781,2783,2785,2787],{"class":119,"line":131},[117,2773,2774],{"class":225},"    template",[117,2776,24],{"class":123},[117,2778,251],{"class":212},[117,2780,254],{"class":123},[117,2782,257],{"class":225},[117,2784,24],{"class":123},[117,2786,262],{"class":225},[117,2788,265],{"class":123},[117,2790,2791,2793,2795,2798,2800,2802,2805,2807,2809,2812],{"class":119,"line":138},[117,2792,2774],{"class":225},[117,2794,24],{"class":123},[117,2796,2797],{"class":212},"WithFont",[117,2799,254],{"class":123},[117,2801,429],{"class":123},[117,2803,2804],{"class":432},"NotoSansJP",[117,2806,429],{"class":123},[117,2808,394],{"class":123},[117,2810,2811],{"class":225}," notoSansJPRegular",[117,2813,265],{"class":123},[117,2815,2816],{"class":119,"line":148},[117,2817,198],{"class":123},[117,2819,2820],{"class":119,"line":160},[117,2821,135],{"emptyLinePlaceholder":134},[117,2823,2824,2826,2828,2830,2832,2834,2836,2838,2840,2842,2844],{"class":119,"line":165},[117,2825,1686],{"class":225},[117,2827,24],{"class":123},[117,2829,720],{"class":212},[117,2831,327],{"class":123},[117,2833,13],{"class":330},[117,2835,333],{"class":123},[117,2837,336],{"class":127},[117,2839,24],{"class":123},[117,2841,341],{"class":127},[117,2843,344],{"class":123},[117,2845,219],{"class":123},[117,2847,2848,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868],{"class":119,"line":175},[117,2849,1711],{"class":225},[117,2851,24],{"class":123},[117,2853,357],{"class":212},[117,2855,327],{"class":123},[117,2857,362],{"class":330},[117,2859,333],{"class":123},[117,2861,336],{"class":127},[117,2863,24],{"class":123},[117,2865,371],{"class":127},[117,2867,344],{"class":123},[117,2869,219],{"class":123},[117,2871,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898],{"class":119,"line":185},[117,2873,1736],{"class":225},[117,2875,24],{"class":123},[117,2877,386],{"class":212},[117,2879,254],{"class":123},[117,2881,391],{"class":298},[117,2883,394],{"class":123},[117,2885,397],{"class":123},[117,2887,400],{"class":330},[117,2889,333],{"class":123},[117,2891,336],{"class":127},[117,2893,24],{"class":123},[117,2895,409],{"class":127},[117,2897,344],{"class":123},[117,2899,219],{"class":123},[117,2901,2902,2904,2906,2908,2910,2912,2915,2917,2919,2921,2923,2926,2928,2930,2932,2934,2936,2938,2940,2942,2944,2946],{"class":119,"line":195},[117,2903,1767],{"class":225},[117,2905,24],{"class":123},[117,2907,424],{"class":212},[117,2909,254],{"class":123},[117,2911,429],{"class":123},[117,2913,2914],{"class":432},"社外秘",[117,2916,429],{"class":123},[117,2918,394],{"class":123},[117,2920,232],{"class":225},[117,2922,24],{"class":123},[117,2924,2925],{"class":212},"FontFamily",[117,2927,254],{"class":123},[117,2929,429],{"class":123},[117,2931,2804],{"class":432},[117,2933,429],{"class":123},[117,2935,1279],{"class":123},[117,2937,232],{"class":225},[117,2939,24],{"class":123},[117,2941,454],{"class":212},[117,2943,254],{"class":123},[117,2945,968],{"class":298},[117,2947,462],{"class":123},[117,2949,2950],{"class":119,"line":201},[117,2951,573],{"class":123},[117,2953,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974,2976,2978,2980],{"class":119,"line":206},[117,2955,1736],{"class":225},[117,2957,24],{"class":123},[117,2959,386],{"class":212},[117,2961,254],{"class":123},[117,2963,391],{"class":298},[117,2965,394],{"class":123},[117,2967,397],{"class":123},[117,2969,400],{"class":330},[117,2971,333],{"class":123},[117,2973,336],{"class":127},[117,2975,24],{"class":123},[117,2977,409],{"class":127},[117,2979,344],{"class":123},[117,2981,219],{"class":123},[117,2983,2984,2986,2988,2990,2992,2994,2996,2998,3000,3002,3004,3006,3008,3010],{"class":119,"line":222},[117,2985,1767],{"class":225},[117,2987,24],{"class":123},[117,2989,1039],{"class":212},[117,2991,254],{"class":123},[117,2993,336],{"class":225},[117,2995,24],{"class":123},[117,2997,518],{"class":212},[117,2999,447],{"class":123},[117,3001,232],{"class":225},[117,3003,24],{"class":123},[117,3005,454],{"class":212},[117,3007,254],{"class":123},[117,3009,968],{"class":298},[117,3011,462],{"class":123},[117,3013,3014],{"class":119,"line":243},[117,3015,573],{"class":123},[117,3017,3018],{"class":119,"line":268},[117,3019,705],{"class":123},[117,3021,3022],{"class":119,"line":305},[117,3023,1943],{"class":123},[13,3025,3026,3027,3030],{},"The font you register is the font your header and footer use. The subset embedded in the final PDF includes only the glyphs that appear in the document. For a 60-page report with ",[17,3028,3029],{},"\"社外秘\""," in the footer, that's three glyphs from NotoSansJP, not 20,000.",[40,3032,3034],{"id":3033},"performance","Performance",[13,3036,3037],{},"This part matters if you're generating PDFs at scale.",[13,3039,3040],{},"The two-pass resolver isn't free, but it's cheap. On a 100-page document, the second pass takes under 50µs on an M1 — well under 1% of total generation time. gpdf's single-page benchmark is 13µs; the 100-page benchmark is 683µs. The page-number resolution is a constant factor independent of page complexity.",[13,3042,3043,3044,3046],{},"For comparison, gofpdf's ",[17,3045,34],{}," does a string replace over the entire content stream after compression decisions are made, which is slower and forces a recompression pass on stream objects that contain the alias. We measured this at roughly 2–4% of total time on a 100-page document in gofpdf's own benchmarks. The gpdf approach is faster because the replacement happens before stream encoding.",[13,3048,3049],{},"If you're rendering a million PDFs a day, the difference matters. If you're rendering ten, it doesn't.",[40,3051,3053],{"id":3052},"faq","FAQ",[13,3055,3056,3059,3060,3063],{},[1628,3057,3058],{},"Does the header/footer height count against the page margin?","\nYes. gpdf measures the rendered header and footer height once, then computes the available body height as ",[17,3061,3062],{},"pageHeight - top_margin - headerHeight - footerHeight - bottom_margin",". If you have a 20mm top margin and a 15mm header, your body starts at 35mm from the top of the page.",[13,3065,3066,3069],{},[1628,3067,3068],{},"Can the header height change per page?","\nNo. The header closure is evaluated once for measurement, and the result is fixed for the whole document. If you need a variable-height header per page, you have to design it around a fixed maximum height and leave whitespace.",[13,3071,3072,3075],{},[1628,3073,3074],{},"What happens on a page with no body content?","\ngpdf doesn't generate empty pages. If your body content fits on three pages, you get three pages. The header and footer appear on those three pages and nowhere else.",[13,3077,3078,3081,3082,3085],{},[1628,3079,3080],{},"Can I omit the header on landscape pages in a mixed-orientation document?","\nMixed orientations are supported via ",[17,3083,3084],{},"page.WithPageSize(...)"," on individual pages, but the header/footer closure is the same for all pages regardless of orientation. The right call here is usually to make a header that looks reasonable in both orientations, e.g. centered text rather than fixed-width logos.",[13,3087,3088,3091,3092,3095,3096,3099,3100,53,3103,3106,3107,3110],{},[1628,3089,3090],{},"Does this work with the JSON template input?","\nYes. The JSON schema has ",[17,3093,3094],{},"header",", ",[17,3097,3098],{},"footer",", and element types ",[17,3101,3102],{},"{\"type\": \"pageNumber\"}",[17,3104,3105],{},"{\"type\": \"totalPages\"}",". The ",[17,3108,3109],{},"gpdf/_examples/json/26_page_number_test.go"," test runs the same scenario through JSON instead of the builder API and compares against the same golden PDF.",[13,3112,3113,3116,3117,3120],{},[1628,3114,3115],{},"Does this work with Go's text/template input?","\nYes. The ",[17,3118,3119],{},"gpdf/_examples/gotemplate/26_page_number_test.go"," does the equivalent. Whichever entry point you use — builder, JSON, or Go template — the same two-pass paginator runs underneath.",[40,3122,3124],{"id":3123},"next-steps","Next steps",[13,3126,3127],{},"Headers, footers, and page numbers are the boring part of a report — but they're also the part that makes a report feel finished. If you've been hand-rolling these on top of a lower-level PDF library, the four lines in this post are the whole thing. Take the example, change the strings, ship.",[13,3129,3130,3131,3134],{},"The unresolved bits — ",[17,3132,3133],{},"c.PageOf(...)"," for single-string formatting, different header on the first page, \"last page\" detection — are on the list. If any of them block you, drop a note on the GitHub issue tracker. Concrete use cases shape the shape of the API more than abstract requests do.",[40,3136,3138],{"id":3137},"gpdf-を使ってみる","gpdf を使ってみる",[13,3140,3141],{},"gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK 対応。",[108,3143,3147],{"className":3144,"code":3145,"language":3146,"meta":113,"style":113},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[17,3148,3149],{"__ignoreMap":113},[117,3150,3151,3153,3156],{"class":119,"line":120},[117,3152,112],{"class":127},[117,3154,3155],{"class":432}," get",[117,3157,3158],{"class":432}," github.com/gpdf-dev/gpdf\n",[13,3160,3161,3168,3169],{},[3162,3163,3167],"a",{"href":3164,"rel":3165},"https://github.com/gpdf-dev/gpdf",[3166],"nofollow","⭐ Star on GitHub"," · ",[3162,3170,3173],{"href":3171,"rel":3172},"https://gpdf.dev/docs/quickstart",[3166],"Read the docs",[3175,3176,3177],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":113,"searchDepth":131,"depth":131,"links":3179},[3180,3181,3182,3185,3186,3187,3188,3189,3190,3191,3192],{"id":42,"depth":131,"text":43},{"id":94,"depth":131,"text":95},{"id":1499,"depth":131,"text":1500,"children":3183},[3184],{"id":1614,"depth":138,"text":1615},{"id":1660,"depth":131,"text":1661},{"id":1963,"depth":131,"text":1964},{"id":2513,"depth":131,"text":2514},{"id":2733,"depth":131,"text":2734},{"id":3033,"depth":131,"text":3034},{"id":3052,"depth":131,"text":3053},{"id":3123,"depth":131,"text":3124},{"id":3137,"depth":131,"text":3138},"2026-05-19","Add headers, footers, and 'Page X of Y' in Go PDFs with gpdf: two builder methods, a two-pass paginator that fills in the totals, no shims required.",false,"md",{"name":3198,"totalTime":3199,"tools":3200,"steps":3203},"Add headers, footers, and Page X of Y numbering to a Go PDF","PT15M",[3201,3202],"Go 1.22+","github.com/gpdf-dev/gpdf",[3204,3207,3210,3213,3216],{"name":3205,"text":3206},"Create the document with template.New","Call template.New with WithPageSize(document.A4) and WithMargins. The same constructor handles every page configuration.",{"name":3208,"text":3209},"Register the header with doc.Header","Pass a closure that takes a *template.PageBuilder. Inside it, use the same AutoRow and Col grid you use for body content. Add c.TotalPages() for the total.",{"name":3211,"text":3212},"Register the footer with doc.Footer","Pass another closure. Place c.PageNumber() in a column to print the current page number. Both header and footer repeat on every page including overflow.",{"name":3214,"text":3215},"Add page bodies with doc.AddPage","For each logical page, call doc.AddPage and fill it with rows and columns. The body height is automatically reduced by the measured header and footer height.",{"name":3217,"text":3218},"Render the PDF with doc.Generate","Call doc.Generate to get the []byte, or doc.Render(w) to stream into an io.Writer. The two-pass paginator resolves page number placeholders before the bytes are returned.",null,{},"/blog/page-numbers-headers-footers",{"title":5,"description":3194},"blog/027.page-numbers-headers-footers",[3225,3226],"tutorial","internals","69w850k3ddh3xrjaxLhOaWS-4XytqyJQmsi2FKS_eLk",1779199010079]