[{"data":1,"prerenderedAt":53874},["ShallowReactive",2],{"blog-tag-en-templates":3},[4,3229,4870,6999,8532,9862,12954,18083,19259,20742,22098,27409,29315,30494,31395,33067,35475,36582,38084,39153,40373,41666,43357,44997,46557,47869,49216],{"id":5,"title":6,"author":7,"body":10,"date":3194,"description":3195,"draft":3196,"extension":3197,"howTo":3198,"image":3220,"meta":3221,"navigation":135,"path":3222,"seo":3223,"stem":3224,"tags":3225,"updated":3220,"__hash__":3228},"blog/blog/027.page-numbers-headers-footers.md","Page numbers, headers, and footers that just work in Go PDFs",{"name":8,"url":9},"gpdf team","https://gpdf.dev",{"type":11,"value":12,"toc":3179},"minimark",[13,26,37,40,45,86,93,97,108,1480,1498,1502,1512,1515,1596,1612,1617,1623,1644,1652,1659,1663,1678,1945,1948,1955,1962,1966,1983,2310,2325,2333,2503,2512,2516,2519,2532,2541,2704,2713,2722,2732,2736,2751,3025,3032,3036,3039,3042,3048,3051,3055,3065,3071,3077,3087,3112,3122,3126,3129,3136,3140,3143,3160,3175],[14,15,16,17,21,22,25],"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 ",[18,19,20],"code",{},"12",", nobody knows. It needs to say ",[18,23,24],{},"12 of 60",".",[14,27,28,29,32,33,36],{},"That ",[18,30,31],{},"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 ",[18,34,35],{},"AliasNbPages"," token you have to call after the build, or you end up rendering the document twice and discarding the first pass.",[14,38,39],{},"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.",[41,42,44],"h2",{"id":43},"tldr","TL;DR",[46,47,48,59,62,72,75],"ul",{},[49,50,51,54,55,58],"li",{},[18,52,53],{},"doc.Header(fn)"," and ",[18,56,57],{},"doc.Footer(fn)"," register a closure that runs on every page.",[49,60,61],{},"Inside that closure, use the same 12-column grid you use for body content.",[49,63,64,67,68,71],{},[18,65,66],{},"c.PageNumber()"," prints the current page number. ",[18,69,70],{},"c.TotalPages()"," prints the total.",[49,73,74],{},"The total is resolved in a second pass, after pagination completes. There is no two-pass build step you have to write yourself.",[49,76,77,78,81,82,85],{},"One sharp edge: there is no ",[18,79,80],{},"c.PageNumberOf(total)"," helper that prints ",[18,83,84],{},"\"3 of 12\""," as one inline string. You compose it from three columns. More on this below.",[14,87,88,89,92],{},"The code is real. Every snippet in this post is pulled from ",[18,90,91],{},"gpdf/_examples/builder/26_page_number_test.go",", which is part of the test suite.",[41,94,96],{"id":95},"the-whole-thing-in-one-file","The whole thing in one file",[14,98,99,100,103,104,107],{},"This is a complete program. Save it as ",[18,101,102],{},"main.go",", run ",[18,105,106],{},"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.",[109,110,115],"pre",{"className":111,"code":112,"language":113,"meta":114,"style":114},"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","",[18,116,117,130,137,147,159,164,174,184,194,200,205,221,242,267,304,310,315,348,377,415,464,470,501,535,564,569,575,600,631,666,691,696,701,707,712,738,763,794,817,849,873,878,883,908,939,972,995,1000,1031,1063,1086,1091,1096,1101,1106,1175,1194,1220,1251,1290,1314,1348,1353,1358,1370,1376,1381,1403,1419,1432,1437,1474],{"__ignoreMap":114},[118,119,122,126],"span",{"class":120,"line":121},"line",1,[118,123,125],{"class":124},"sMK4o","package",[118,127,129],{"class":128},"sBMFI"," main\n",[118,131,133],{"class":120,"line":132},2,[118,134,136],{"emptyLinePlaceholder":135},true,"\n",[118,138,140,144],{"class":120,"line":139},3,[118,141,143],{"class":142},"s7zQu","import",[118,145,146],{"class":124}," (\n",[118,148,150,153,156],{"class":120,"line":149},4,[118,151,152],{"class":124},"    \"",[118,154,155],{"class":128},"os",[118,157,158],{"class":124},"\"\n",[118,160,162],{"class":120,"line":161},5,[118,163,136],{"emptyLinePlaceholder":135},[118,165,167,169,172],{"class":120,"line":166},6,[118,168,152],{"class":124},[118,170,171],{"class":128},"github.com/gpdf-dev/gpdf/document",[118,173,158],{"class":124},[118,175,177,179,182],{"class":120,"line":176},7,[118,178,152],{"class":124},[118,180,181],{"class":128},"github.com/gpdf-dev/gpdf/pdf",[118,183,158],{"class":124},[118,185,187,189,192],{"class":120,"line":186},8,[118,188,152],{"class":124},[118,190,191],{"class":128},"github.com/gpdf-dev/gpdf/template",[118,193,158],{"class":124},[118,195,197],{"class":120,"line":196},9,[118,198,199],{"class":124},")\n",[118,201,203],{"class":120,"line":202},10,[118,204,136],{"emptyLinePlaceholder":135},[118,206,208,211,215,218],{"class":120,"line":207},11,[118,209,210],{"class":124},"func",[118,212,214],{"class":213},"s2Zo4"," main",[118,216,217],{"class":124},"()",[118,219,220],{"class":124}," {\n",[118,222,224,228,231,234,236,239],{"class":120,"line":223},12,[118,225,227],{"class":226},"sTEyZ","    doc ",[118,229,230],{"class":124},":=",[118,232,233],{"class":226}," template",[118,235,25],{"class":124},[118,237,238],{"class":213},"New",[118,240,241],{"class":124},"(\n",[118,243,245,248,250,253,256,259,261,264],{"class":120,"line":244},13,[118,246,247],{"class":226},"        template",[118,249,25],{"class":124},[118,251,252],{"class":213},"WithPageSize",[118,254,255],{"class":124},"(",[118,257,258],{"class":226},"document",[118,260,25],{"class":124},[118,262,263],{"class":226},"A4",[118,265,266],{"class":124},"),\n",[118,268,270,272,274,277,279,281,283,286,288,290,292,295,297,301],{"class":120,"line":269},14,[118,271,247],{"class":226},[118,273,25],{"class":124},[118,275,276],{"class":213},"WithMargins",[118,278,255],{"class":124},[118,280,258],{"class":226},[118,282,25],{"class":124},[118,284,285],{"class":213},"UniformEdges",[118,287,255],{"class":124},[118,289,258],{"class":226},[118,291,25],{"class":124},[118,293,294],{"class":213},"Mm",[118,296,255],{"class":124},[118,298,300],{"class":299},"sbssI","20",[118,302,303],{"class":124},"))),\n",[118,305,307],{"class":120,"line":306},15,[118,308,309],{"class":124},"    )\n",[118,311,313],{"class":120,"line":312},16,[118,314,136],{"emptyLinePlaceholder":135},[118,316,318,321,323,326,329,332,335,338,340,343,346],{"class":120,"line":317},17,[118,319,320],{"class":226},"    doc",[118,322,25],{"class":124},[118,324,325],{"class":213},"Header",[118,327,328],{"class":124},"(func(",[118,330,14],{"class":331},"sHdIc",[118,333,334],{"class":124}," *",[118,336,337],{"class":128},"template",[118,339,25],{"class":124},[118,341,342],{"class":128},"PageBuilder",[118,344,345],{"class":124},")",[118,347,220],{"class":124},[118,349,351,354,356,359,361,364,366,368,370,373,375],{"class":120,"line":350},18,[118,352,353],{"class":226},"        p",[118,355,25],{"class":124},[118,357,358],{"class":213},"AutoRow",[118,360,328],{"class":124},[118,362,363],{"class":331},"r",[118,365,334],{"class":124},[118,367,337],{"class":128},[118,369,25],{"class":124},[118,371,372],{"class":128},"RowBuilder",[118,374,345],{"class":124},[118,376,220],{"class":124},[118,378,380,383,385,388,390,393,396,399,402,404,406,408,411,413],{"class":120,"line":379},19,[118,381,382],{"class":226},"            r",[118,384,25],{"class":124},[118,386,387],{"class":213},"Col",[118,389,255],{"class":124},[118,391,392],{"class":299},"6",[118,394,395],{"class":124},",",[118,397,398],{"class":124}," func(",[118,400,401],{"class":331},"c",[118,403,334],{"class":124},[118,405,337],{"class":128},[118,407,25],{"class":124},[118,409,410],{"class":128},"ColBuilder",[118,412,345],{"class":124},[118,414,220],{"class":124},[118,416,418,421,423,426,428,431,435,437,439,441,443,446,449,451,453,456,458,461],{"class":120,"line":417},20,[118,419,420],{"class":226},"                c",[118,422,25],{"class":124},[118,424,425],{"class":213},"Text",[118,427,255],{"class":124},[118,429,430],{"class":124},"\"",[118,432,434],{"class":433},"sfazB","Quarterly Report",[118,436,430],{"class":124},[118,438,395],{"class":124},[118,440,233],{"class":226},[118,442,25],{"class":124},[118,444,445],{"class":213},"Bold",[118,447,448],{"class":124},"(),",[118,450,233],{"class":226},[118,452,25],{"class":124},[118,454,455],{"class":213},"FontSize",[118,457,255],{"class":124},[118,459,460],{"class":299},"10",[118,462,463],{"class":124},"))\n",[118,465,467],{"class":120,"line":466},21,[118,468,469],{"class":124},"            })\n",[118,471,473,475,477,479,481,483,485,487,489,491,493,495,497,499],{"class":120,"line":472},22,[118,474,382],{"class":226},[118,476,25],{"class":124},[118,478,387],{"class":213},[118,480,255],{"class":124},[118,482,392],{"class":299},[118,484,395],{"class":124},[118,486,398],{"class":124},[118,488,401],{"class":331},[118,490,334],{"class":124},[118,492,337],{"class":128},[118,494,25],{"class":124},[118,496,410],{"class":128},[118,498,345],{"class":124},[118,500,220],{"class":124},[118,502,504,506,508,511,513,515,517,520,522,524,526,528,530,533],{"class":120,"line":503},23,[118,505,420],{"class":226},[118,507,25],{"class":124},[118,509,510],{"class":213},"TotalPages",[118,512,255],{"class":124},[118,514,337],{"class":226},[118,516,25],{"class":124},[118,518,519],{"class":213},"AlignRight",[118,521,448],{"class":124},[118,523,233],{"class":226},[118,525,25],{"class":124},[118,527,455],{"class":213},[118,529,255],{"class":124},[118,531,532],{"class":299},"9",[118,534,266],{"class":124},[118,536,538,541,543,546,548,551,553,556,558,561],{"class":120,"line":537},24,[118,539,540],{"class":226},"                    template",[118,542,25],{"class":124},[118,544,545],{"class":213},"TextColor",[118,547,255],{"class":124},[118,549,550],{"class":226},"pdf",[118,552,25],{"class":124},[118,554,555],{"class":213},"Gray",[118,557,255],{"class":124},[118,559,560],{"class":299},"0.5",[118,562,563],{"class":124},")))\n",[118,565,567],{"class":120,"line":566},25,[118,568,469],{"class":124},[118,570,572],{"class":120,"line":571},26,[118,573,574],{"class":124},"        })\n",[118,576,578,580,582,584,586,588,590,592,594,596,598],{"class":120,"line":577},27,[118,579,353],{"class":226},[118,581,25],{"class":124},[118,583,358],{"class":213},[118,585,328],{"class":124},[118,587,363],{"class":331},[118,589,334],{"class":124},[118,591,337],{"class":128},[118,593,25],{"class":124},[118,595,372],{"class":128},[118,597,345],{"class":124},[118,599,220],{"class":124},[118,601,603,605,607,609,611,613,615,617,619,621,623,625,627,629],{"class":120,"line":602},28,[118,604,382],{"class":226},[118,606,25],{"class":124},[118,608,387],{"class":213},[118,610,255],{"class":124},[118,612,20],{"class":299},[118,614,395],{"class":124},[118,616,398],{"class":124},[118,618,401],{"class":331},[118,620,334],{"class":124},[118,622,337],{"class":128},[118,624,25],{"class":124},[118,626,410],{"class":128},[118,628,345],{"class":124},[118,630,220],{"class":124},[118,632,634,636,638,641,643,645,647,650,652,654,656,659,661,664],{"class":120,"line":633},29,[118,635,420],{"class":226},[118,637,25],{"class":124},[118,639,640],{"class":213},"Line",[118,642,255],{"class":124},[118,644,337],{"class":226},[118,646,25],{"class":124},[118,648,649],{"class":213},"LineColor",[118,651,255],{"class":124},[118,653,550],{"class":226},[118,655,25],{"class":124},[118,657,658],{"class":213},"RGBHex",[118,660,255],{"class":124},[118,662,663],{"class":299},"0x1565C0",[118,665,563],{"class":124},[118,667,669,671,673,676,678,680,682,684,686,689],{"class":120,"line":668},30,[118,670,420],{"class":226},[118,672,25],{"class":124},[118,674,675],{"class":213},"Spacer",[118,677,255],{"class":124},[118,679,258],{"class":226},[118,681,25],{"class":124},[118,683,294],{"class":213},[118,685,255],{"class":124},[118,687,688],{"class":299},"3",[118,690,463],{"class":124},[118,692,694],{"class":120,"line":693},31,[118,695,469],{"class":124},[118,697,699],{"class":120,"line":698},32,[118,700,574],{"class":124},[118,702,704],{"class":120,"line":703},33,[118,705,706],{"class":124},"    })\n",[118,708,710],{"class":120,"line":709},34,[118,711,136],{"emptyLinePlaceholder":135},[118,713,715,717,719,722,724,726,728,730,732,734,736],{"class":120,"line":714},35,[118,716,320],{"class":226},[118,718,25],{"class":124},[118,720,721],{"class":213},"Footer",[118,723,328],{"class":124},[118,725,14],{"class":331},[118,727,334],{"class":124},[118,729,337],{"class":128},[118,731,25],{"class":124},[118,733,342],{"class":128},[118,735,345],{"class":124},[118,737,220],{"class":124},[118,739,741,743,745,747,749,751,753,755,757,759,761],{"class":120,"line":740},36,[118,742,353],{"class":226},[118,744,25],{"class":124},[118,746,358],{"class":213},[118,748,328],{"class":124},[118,750,363],{"class":331},[118,752,334],{"class":124},[118,754,337],{"class":128},[118,756,25],{"class":124},[118,758,372],{"class":128},[118,760,345],{"class":124},[118,762,220],{"class":124},[118,764,766,768,770,772,774,776,778,780,782,784,786,788,790,792],{"class":120,"line":765},37,[118,767,382],{"class":226},[118,769,25],{"class":124},[118,771,387],{"class":213},[118,773,255],{"class":124},[118,775,20],{"class":299},[118,777,395],{"class":124},[118,779,398],{"class":124},[118,781,401],{"class":331},[118,783,334],{"class":124},[118,785,337],{"class":128},[118,787,25],{"class":124},[118,789,410],{"class":128},[118,791,345],{"class":124},[118,793,220],{"class":124},[118,795,797,799,801,803,805,807,809,811,813,815],{"class":120,"line":796},38,[118,798,420],{"class":226},[118,800,25],{"class":124},[118,802,675],{"class":213},[118,804,255],{"class":124},[118,806,258],{"class":226},[118,808,25],{"class":124},[118,810,294],{"class":213},[118,812,255],{"class":124},[118,814,688],{"class":299},[118,816,463],{"class":124},[118,818,820,822,824,826,828,830,832,834,836,838,840,842,844,847],{"class":120,"line":819},39,[118,821,420],{"class":226},[118,823,25],{"class":124},[118,825,640],{"class":213},[118,827,255],{"class":124},[118,829,337],{"class":226},[118,831,25],{"class":124},[118,833,649],{"class":213},[118,835,255],{"class":124},[118,837,550],{"class":226},[118,839,25],{"class":124},[118,841,555],{"class":213},[118,843,255],{"class":124},[118,845,846],{"class":299},"0.7",[118,848,563],{"class":124},[118,850,852,854,856,858,860,862,864,866,868,871],{"class":120,"line":851},40,[118,853,420],{"class":226},[118,855,25],{"class":124},[118,857,675],{"class":213},[118,859,255],{"class":124},[118,861,258],{"class":226},[118,863,25],{"class":124},[118,865,294],{"class":213},[118,867,255],{"class":124},[118,869,870],{"class":299},"2",[118,872,463],{"class":124},[118,874,876],{"class":120,"line":875},41,[118,877,469],{"class":124},[118,879,881],{"class":120,"line":880},42,[118,882,574],{"class":124},[118,884,886,888,890,892,894,896,898,900,902,904,906],{"class":120,"line":885},43,[118,887,353],{"class":226},[118,889,25],{"class":124},[118,891,358],{"class":213},[118,893,328],{"class":124},[118,895,363],{"class":331},[118,897,334],{"class":124},[118,899,337],{"class":128},[118,901,25],{"class":124},[118,903,372],{"class":128},[118,905,345],{"class":124},[118,907,220],{"class":124},[118,909,911,913,915,917,919,921,923,925,927,929,931,933,935,937],{"class":120,"line":910},44,[118,912,382],{"class":226},[118,914,25],{"class":124},[118,916,387],{"class":213},[118,918,255],{"class":124},[118,920,392],{"class":299},[118,922,395],{"class":124},[118,924,398],{"class":124},[118,926,401],{"class":331},[118,928,334],{"class":124},[118,930,337],{"class":128},[118,932,25],{"class":124},[118,934,410],{"class":128},[118,936,345],{"class":124},[118,938,220],{"class":124},[118,940,942,944,946,948,950,952,955,957,959,961,963,965,967,970],{"class":120,"line":941},45,[118,943,420],{"class":226},[118,945,25],{"class":124},[118,947,425],{"class":213},[118,949,255],{"class":124},[118,951,430],{"class":124},[118,953,954],{"class":433},"Generated by gpdf",[118,956,430],{"class":124},[118,958,395],{"class":124},[118,960,233],{"class":226},[118,962,25],{"class":124},[118,964,455],{"class":213},[118,966,255],{"class":124},[118,968,969],{"class":299},"8",[118,971,266],{"class":124},[118,973,975,977,979,981,983,985,987,989,991,993],{"class":120,"line":974},46,[118,976,540],{"class":226},[118,978,25],{"class":124},[118,980,545],{"class":213},[118,982,255],{"class":124},[118,984,550],{"class":226},[118,986,25],{"class":124},[118,988,555],{"class":213},[118,990,255],{"class":124},[118,992,560],{"class":299},[118,994,563],{"class":124},[118,996,998],{"class":120,"line":997},47,[118,999,469],{"class":124},[118,1001,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027,1029],{"class":120,"line":1002},48,[118,1004,382],{"class":226},[118,1006,25],{"class":124},[118,1008,387],{"class":213},[118,1010,255],{"class":124},[118,1012,392],{"class":299},[118,1014,395],{"class":124},[118,1016,398],{"class":124},[118,1018,401],{"class":331},[118,1020,334],{"class":124},[118,1022,337],{"class":128},[118,1024,25],{"class":124},[118,1026,410],{"class":128},[118,1028,345],{"class":124},[118,1030,220],{"class":124},[118,1032,1034,1036,1038,1041,1043,1045,1047,1049,1051,1053,1055,1057,1059,1061],{"class":120,"line":1033},49,[118,1035,420],{"class":226},[118,1037,25],{"class":124},[118,1039,1040],{"class":213},"PageNumber",[118,1042,255],{"class":124},[118,1044,337],{"class":226},[118,1046,25],{"class":124},[118,1048,519],{"class":213},[118,1050,448],{"class":124},[118,1052,233],{"class":226},[118,1054,25],{"class":124},[118,1056,455],{"class":213},[118,1058,255],{"class":124},[118,1060,969],{"class":299},[118,1062,266],{"class":124},[118,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084],{"class":120,"line":1065},50,[118,1067,540],{"class":226},[118,1069,25],{"class":124},[118,1071,545],{"class":213},[118,1073,255],{"class":124},[118,1075,550],{"class":226},[118,1077,25],{"class":124},[118,1079,555],{"class":213},[118,1081,255],{"class":124},[118,1083,560],{"class":299},[118,1085,563],{"class":124},[118,1087,1089],{"class":120,"line":1088},51,[118,1090,469],{"class":124},[118,1092,1094],{"class":120,"line":1093},52,[118,1095,574],{"class":124},[118,1097,1099],{"class":120,"line":1098},53,[118,1100,706],{"class":124},[118,1102,1104],{"class":120,"line":1103},54,[118,1105,136],{"emptyLinePlaceholder":135},[118,1107,1109,1112,1115,1117,1120,1122,1125,1128,1132,1135,1137,1140,1142,1144,1147,1150,1152,1154,1156,1159,1161,1163,1165,1168,1170,1173],{"class":120,"line":1108},55,[118,1110,1111],{"class":142},"    for",[118,1113,1114],{"class":226}," i",[118,1116,395],{"class":124},[118,1118,1119],{"class":226}," title ",[118,1121,230],{"class":124},[118,1123,1124],{"class":142}," range",[118,1126,1127],{"class":124}," []",[118,1129,1131],{"class":1130},"spNyl","string",[118,1133,1134],{"class":124},"{",[118,1136,430],{"class":124},[118,1138,1139],{"class":433},"Introduction",[118,1141,430],{"class":124},[118,1143,395],{"class":124},[118,1145,1146],{"class":124}," \"",[118,1148,1149],{"class":433},"Background",[118,1151,430],{"class":124},[118,1153,395],{"class":124},[118,1155,1146],{"class":124},[118,1157,1158],{"class":433},"Analysis",[118,1160,430],{"class":124},[118,1162,395],{"class":124},[118,1164,1146],{"class":124},[118,1166,1167],{"class":433},"Conclusion",[118,1169,430],{"class":124},[118,1171,1172],{"class":124},"}",[118,1174,220],{"class":124},[118,1176,1178,1181,1183,1186,1188,1191],{"class":120,"line":1177},56,[118,1179,1180],{"class":226},"        page ",[118,1182,230],{"class":124},[118,1184,1185],{"class":226}," doc",[118,1187,25],{"class":124},[118,1189,1190],{"class":213},"AddPage",[118,1192,1193],{"class":124},"()\n",[118,1195,1197,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218],{"class":120,"line":1196},57,[118,1198,1199],{"class":226},"        page",[118,1201,25],{"class":124},[118,1203,358],{"class":213},[118,1205,328],{"class":124},[118,1207,363],{"class":331},[118,1209,334],{"class":124},[118,1211,337],{"class":128},[118,1213,25],{"class":124},[118,1215,372],{"class":128},[118,1217,345],{"class":124},[118,1219,220],{"class":124},[118,1221,1223,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243,1245,1247,1249],{"class":120,"line":1222},58,[118,1224,382],{"class":226},[118,1226,25],{"class":124},[118,1228,387],{"class":213},[118,1230,255],{"class":124},[118,1232,20],{"class":299},[118,1234,395],{"class":124},[118,1236,398],{"class":124},[118,1238,401],{"class":331},[118,1240,334],{"class":124},[118,1242,337],{"class":128},[118,1244,25],{"class":124},[118,1246,410],{"class":128},[118,1248,345],{"class":124},[118,1250,220],{"class":124},[118,1252,1254,1256,1258,1260,1262,1265,1267,1269,1271,1273,1275,1278,1281,1283,1285,1287],{"class":120,"line":1253},59,[118,1255,420],{"class":226},[118,1257,25],{"class":124},[118,1259,425],{"class":213},[118,1261,255],{"class":124},[118,1263,1264],{"class":226},"title",[118,1266,395],{"class":124},[118,1268,233],{"class":226},[118,1270,25],{"class":124},[118,1272,455],{"class":213},[118,1274,255],{"class":124},[118,1276,1277],{"class":299},"18",[118,1279,1280],{"class":124},"),",[118,1282,233],{"class":226},[118,1284,25],{"class":124},[118,1286,445],{"class":213},[118,1288,1289],{"class":124},"())\n",[118,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309,1312],{"class":120,"line":1292},60,[118,1294,420],{"class":226},[118,1296,25],{"class":124},[118,1298,675],{"class":213},[118,1300,255],{"class":124},[118,1302,258],{"class":226},[118,1304,25],{"class":124},[118,1306,294],{"class":213},[118,1308,255],{"class":124},[118,1310,1311],{"class":299},"5",[118,1313,463],{"class":124},[118,1315,1317,1319,1321,1323,1325,1327,1330,1332,1335,1337,1340,1342,1344,1346],{"class":120,"line":1316},61,[118,1318,420],{"class":226},[118,1320,25],{"class":124},[118,1322,425],{"class":213},[118,1324,255],{"class":124},[118,1326,430],{"class":124},[118,1328,1329],{"class":433},"Body content for section ",[118,1331,430],{"class":124},[118,1333,1334],{"class":124}," +",[118,1336,1119],{"class":226},[118,1338,1339],{"class":124},"+",[118,1341,1146],{"class":124},[118,1343,25],{"class":433},[118,1345,430],{"class":124},[118,1347,199],{"class":124},[118,1349,1351],{"class":120,"line":1350},62,[118,1352,469],{"class":124},[118,1354,1356],{"class":120,"line":1355},63,[118,1357,574],{"class":124},[118,1359,1361,1364,1367],{"class":120,"line":1360},64,[118,1362,1363],{"class":226},"        _ ",[118,1365,1366],{"class":124},"=",[118,1368,1369],{"class":226}," i\n",[118,1371,1373],{"class":120,"line":1372},65,[118,1374,1375],{"class":124},"    }\n",[118,1377,1379],{"class":120,"line":1378},66,[118,1380,136],{"emptyLinePlaceholder":135},[118,1382,1384,1387,1389,1392,1394,1396,1398,1401],{"class":120,"line":1383},67,[118,1385,1386],{"class":226},"    out",[118,1388,395],{"class":124},[118,1390,1391],{"class":226}," err ",[118,1393,230],{"class":124},[118,1395,1185],{"class":226},[118,1397,25],{"class":124},[118,1399,1400],{"class":213},"Generate",[118,1402,1193],{"class":124},[118,1404,1406,1409,1411,1414,1417],{"class":120,"line":1405},68,[118,1407,1408],{"class":142},"    if",[118,1410,1391],{"class":226},[118,1412,1413],{"class":124},"!=",[118,1415,1416],{"class":124}," nil",[118,1418,220],{"class":124},[118,1420,1422,1425,1427,1430],{"class":120,"line":1421},69,[118,1423,1424],{"class":213},"        panic",[118,1426,255],{"class":124},[118,1428,1429],{"class":226},"err",[118,1431,199],{"class":124},[118,1433,1435],{"class":120,"line":1434},70,[118,1436,1375],{"class":124},[118,1438,1440,1443,1445,1448,1450,1453,1455,1457,1460,1462,1464,1467,1469,1472],{"class":120,"line":1439},71,[118,1441,1442],{"class":226},"    _ ",[118,1444,1366],{"class":124},[118,1446,1447],{"class":226}," os",[118,1449,25],{"class":124},[118,1451,1452],{"class":213},"WriteFile",[118,1454,255],{"class":124},[118,1456,430],{"class":124},[118,1458,1459],{"class":433},"report.pdf",[118,1461,430],{"class":124},[118,1463,395],{"class":124},[118,1465,1466],{"class":226}," out",[118,1468,395],{"class":124},[118,1470,1471],{"class":299}," 0o644",[118,1473,199],{"class":124},[118,1475,1477],{"class":120,"line":1476},72,[118,1478,1479],{"class":124},"}\n",[14,1481,1482,1483,1486,1487,1490,1491,1494,1495,1497],{},"Four pages, each with a header line saying ",[18,1484,1485],{},"Quarterly Report ........ 4"," and a footer saying ",[18,1488,1489],{},"Generated by gpdf ........ 1"," through ",[18,1492,1493],{},"4",". The total ",[18,1496,1493],{}," appears on every header without you ever telling gpdf how many pages the document has — because gpdf doesn't know until after pagination, either.",[41,1499,1501],{"id":1500},"why-page-x-of-y-is-the-hard-part","Why \"Page X of Y\" is the hard part",[14,1503,1504,1507,1508,1511],{},[18,1505,1506],{},"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 ",[18,1509,1510],{},"50"," only after the paginator finishes. Page 1's footer was drawn long before that.",[14,1513,1514],{},"Every PDF library hits this wall. Here's how the most-used Go ones get around it:",[1516,1517,1518,1531],"table",{},[1519,1520,1521],"thead",{},[1522,1523,1524,1528],"tr",{},[1525,1526,1527],"th",{},"Library",[1525,1529,1530],{},"\"Page X of Y\" approach",[1532,1533,1534,1553,1561,1569,1583],"tbody",{},[1522,1535,1536,1540],{},[1537,1538,1539],"td",{},"gofpdf",[1537,1541,1542,1545,1546,1549,1550,1552],{},[18,1543,1544],{},"pdf.AliasNbPages(\"{nb}\")"," — you write ",[18,1547,1548],{},"{nb}"," as literal text, then call this method, then it rewrites the PDF stream after the fact, replacing every occurrence of ",[18,1551,1548],{}," with the total. Works, but you have to remember to call it, and the placeholder is a magic string.",[1522,1554,1555,1558],{},[1537,1556,1557],{},"go-pdf/fpdf",[1537,1559,1560],{},"Same as gofpdf. (It's a fork.)",[1522,1562,1563,1566],{},[1537,1564,1565],{},"signintech/gopdf",[1537,1567,1568],{},"No first-class support. You compute the total yourself by building the document, counting pages, and rebuilding.",[1522,1570,1571,1574],{},[1537,1572,1573],{},"maroto v2",[1537,1575,1576,1577,1579,1580,1582],{},"Provides a ",[18,1578,325],{},"/",[18,1581,721],{}," 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).",[1522,1584,1585,1588],{},[1537,1586,1587],{},"gpdf",[1537,1589,1590,1592,1593,1595],{},[18,1591,66],{}," / ",[18,1594,70],{}," — typed method calls, no magic strings, resolved by an internal second pass.",[14,1597,1598,1599,1601,1602,1605,1606,1608,1609,1611],{},"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 ",[18,1600,1548],{}," as ",[18,1603,1604],{},"{nB}"," in gofpdf, you get a ",[18,1607,1604],{}," literally printed in your footer. With ",[18,1610,70],{},", the worst you can do is forget to call it — and then there's just no number, not a wrong one.",[1613,1614,1616],"h3",{"id":1615},"how-the-second-pass-works","How the second pass works",[14,1618,1619,1620,1622],{},"Internally, gpdf renders ",[18,1621,66],{}," 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:",[1624,1625,1626,1638],"ol",{},[49,1627,1628,1632,1633,54,1635,1637],{},[1629,1630,1631],"strong",{},"Pass one (paginate)",": render every page, including header and footer, treating ",[18,1634,1040],{},[18,1636,510],{}," as fixed-width tokens. Compute the total page count.",[49,1639,1640,1643],{},[1629,1641,1642],{},"Pass two (resolve)",": walk back through the page tree, find each sentinel, and replace it with the actual current/total page number.",[14,1645,1646,1647,1649,1650,25],{},"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 ",[18,1648,532],{}," to ",[18,1651,460],{},[14,1653,1654,1655,1658],{},"You don't write the second pass. You don't render the document twice. You call ",[18,1656,1657],{},"doc.Generate()"," and get bytes.",[41,1660,1662],{"id":1661},"header-and-footer-are-just-normal-layout","Header and footer are just normal layout",[14,1664,1665,1666,1669,1670,1673,1674,1677],{},"This part trips up people coming from gofpdf, where ",[18,1667,1668],{},"SetHeaderFunc"," runs a callback at a fixed Y coordinate and you place text with absolute ",[18,1671,1672],{},"Cell(...)"," calls. In gpdf, the header closure receives a ",[18,1675,1676],{},"*template.PageBuilder"," — the same type the body uses. The grid is the same. Rows and columns are the same. Styling is the same.",[109,1679,1681],{"className":111,"code":1680,"language":113,"meta":114,"style":114},"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",[18,1682,1683,1708,1733,1764,1806,1810,1840,1880,1884,1914,1932,1936,1940],{"__ignoreMap":114},[118,1684,1685,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706],{"class":120,"line":121},[118,1686,1687],{"class":226},"doc",[118,1689,25],{"class":124},[118,1691,325],{"class":213},[118,1693,328],{"class":124},[118,1695,14],{"class":331},[118,1697,334],{"class":124},[118,1699,337],{"class":128},[118,1701,25],{"class":124},[118,1703,342],{"class":128},[118,1705,345],{"class":124},[118,1707,220],{"class":124},[118,1709,1710,1713,1715,1717,1719,1721,1723,1725,1727,1729,1731],{"class":120,"line":132},[118,1711,1712],{"class":226},"    p",[118,1714,25],{"class":124},[118,1716,358],{"class":213},[118,1718,328],{"class":124},[118,1720,363],{"class":331},[118,1722,334],{"class":124},[118,1724,337],{"class":128},[118,1726,25],{"class":124},[118,1728,372],{"class":128},[118,1730,345],{"class":124},[118,1732,220],{"class":124},[118,1734,1735,1738,1740,1742,1744,1746,1748,1750,1752,1754,1756,1758,1760,1762],{"class":120,"line":139},[118,1736,1737],{"class":226},"        r",[118,1739,25],{"class":124},[118,1741,387],{"class":213},[118,1743,255],{"class":124},[118,1745,870],{"class":299},[118,1747,395],{"class":124},[118,1749,398],{"class":124},[118,1751,401],{"class":331},[118,1753,334],{"class":124},[118,1755,337],{"class":128},[118,1757,25],{"class":124},[118,1759,410],{"class":128},[118,1761,345],{"class":124},[118,1763,220],{"class":124},[118,1765,1766,1769,1771,1774,1776,1778,1781,1783,1785,1787,1789,1792,1794,1796,1798,1800,1802,1804],{"class":120,"line":149},[118,1767,1768],{"class":226},"            c",[118,1770,25],{"class":124},[118,1772,1773],{"class":213},"Image",[118,1775,255],{"class":124},[118,1777,430],{"class":124},[118,1779,1780],{"class":433},"logo.png",[118,1782,430],{"class":124},[118,1784,395],{"class":124},[118,1786,233],{"class":226},[118,1788,25],{"class":124},[118,1790,1791],{"class":213},"ImageHeight",[118,1793,255],{"class":124},[118,1795,258],{"class":226},[118,1797,25],{"class":124},[118,1799,294],{"class":213},[118,1801,255],{"class":124},[118,1803,20],{"class":299},[118,1805,563],{"class":124},[118,1807,1808],{"class":120,"line":161},[118,1809,574],{"class":124},[118,1811,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838],{"class":120,"line":166},[118,1813,1737],{"class":226},[118,1815,25],{"class":124},[118,1817,387],{"class":213},[118,1819,255],{"class":124},[118,1821,969],{"class":299},[118,1823,395],{"class":124},[118,1825,398],{"class":124},[118,1827,401],{"class":331},[118,1829,334],{"class":124},[118,1831,337],{"class":128},[118,1833,25],{"class":124},[118,1835,410],{"class":128},[118,1837,345],{"class":124},[118,1839,220],{"class":124},[118,1841,1842,1844,1846,1848,1850,1852,1855,1857,1859,1861,1863,1865,1867,1869,1871,1873,1875,1878],{"class":120,"line":176},[118,1843,1768],{"class":226},[118,1845,25],{"class":124},[118,1847,425],{"class":213},[118,1849,255],{"class":124},[118,1851,430],{"class":124},[118,1853,1854],{"class":433},"Annual Report 2026",[118,1856,430],{"class":124},[118,1858,395],{"class":124},[118,1860,233],{"class":226},[118,1862,25],{"class":124},[118,1864,445],{"class":213},[118,1866,448],{"class":124},[118,1868,233],{"class":226},[118,1870,25],{"class":124},[118,1872,455],{"class":213},[118,1874,255],{"class":124},[118,1876,1877],{"class":299},"14",[118,1879,463],{"class":124},[118,1881,1882],{"class":120,"line":186},[118,1883,574],{"class":124},[118,1885,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912],{"class":120,"line":196},[118,1887,1737],{"class":226},[118,1889,25],{"class":124},[118,1891,387],{"class":213},[118,1893,255],{"class":124},[118,1895,870],{"class":299},[118,1897,395],{"class":124},[118,1899,398],{"class":124},[118,1901,401],{"class":331},[118,1903,334],{"class":124},[118,1905,337],{"class":128},[118,1907,25],{"class":124},[118,1909,410],{"class":128},[118,1911,345],{"class":124},[118,1913,220],{"class":124},[118,1915,1916,1918,1920,1922,1924,1926,1928,1930],{"class":120,"line":202},[118,1917,1768],{"class":226},[118,1919,25],{"class":124},[118,1921,510],{"class":213},[118,1923,255],{"class":124},[118,1925,337],{"class":226},[118,1927,25],{"class":124},[118,1929,519],{"class":213},[118,1931,1289],{"class":124},[118,1933,1934],{"class":120,"line":207},[118,1935,574],{"class":124},[118,1937,1938],{"class":120,"line":223},[118,1939,706],{"class":124},[118,1941,1942],{"class":120,"line":244},[118,1943,1944],{"class":124},"})\n",[14,1946,1947],{},"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.",[14,1949,1950,1951,1954],{},"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 ",[18,1952,1953],{},"headerHeight"," parameter. If you add a row to the header, the body shrinks accordingly.",[14,1956,1957,1958,1961],{},"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 ",[18,1959,1960],{},"firstPageOnly"," flag — see the gotchas section below.",[41,1963,1965],{"id":1964},"the-rough-edge-page-x-of-y-in-one-line","The rough edge: \"Page X of Y\" in one line",[14,1967,1968,1969,1972,1973,1976,1977,54,1980,1982],{},"Here's the one place I think the API could be better. There is no ",[18,1970,1971],{},"c.PageOf(\"Page %d of %d\")"," helper. To produce the literal string ",[18,1974,1975],{},"\"Page 3 of 12\""," you have to compose it from columns, because ",[18,1978,1979],{},"c.Text()",[18,1981,66],{}," are independent column children:",[109,1984,1986],{"className":111,"code":1985,"language":113,"meta":114,"style":114},"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",[18,1987,1988,2018,2043,2073,2100,2104,2134,2153,2157,2187,2214,2218,2248,2267,2271,2302,2306],{"__ignoreMap":114},[118,1989,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016],{"class":120,"line":121},[118,1991,363],{"class":226},[118,1993,25],{"class":124},[118,1995,387],{"class":213},[118,1997,255],{"class":124},[118,1999,20],{"class":299},[118,2001,395],{"class":124},[118,2003,398],{"class":124},[118,2005,401],{"class":331},[118,2007,334],{"class":124},[118,2009,337],{"class":128},[118,2011,25],{"class":124},[118,2013,410],{"class":128},[118,2015,345],{"class":124},[118,2017,220],{"class":124},[118,2019,2020,2023,2025,2027,2029,2031,2033,2035,2037,2039,2041],{"class":120,"line":132},[118,2021,2022],{"class":226},"    c",[118,2024,25],{"class":124},[118,2026,358],{"class":213},[118,2028,328],{"class":124},[118,2030,363],{"class":331},[118,2032,334],{"class":124},[118,2034,337],{"class":128},[118,2036,25],{"class":124},[118,2038,372],{"class":128},[118,2040,345],{"class":124},[118,2042,220],{"class":124},[118,2044,2045,2047,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071],{"class":120,"line":139},[118,2046,1737],{"class":226},[118,2048,25],{"class":124},[118,2050,387],{"class":213},[118,2052,255],{"class":124},[118,2054,688],{"class":299},[118,2056,395],{"class":124},[118,2058,398],{"class":124},[118,2060,401],{"class":331},[118,2062,334],{"class":124},[118,2064,337],{"class":128},[118,2066,25],{"class":124},[118,2068,410],{"class":128},[118,2070,345],{"class":124},[118,2072,220],{"class":124},[118,2074,2075,2077,2079,2081,2083,2085,2088,2090,2092,2094,2096,2098],{"class":120,"line":149},[118,2076,1768],{"class":226},[118,2078,25],{"class":124},[118,2080,425],{"class":213},[118,2082,255],{"class":124},[118,2084,430],{"class":124},[118,2086,2087],{"class":433},"Page",[118,2089,430],{"class":124},[118,2091,395],{"class":124},[118,2093,233],{"class":226},[118,2095,25],{"class":124},[118,2097,519],{"class":213},[118,2099,1289],{"class":124},[118,2101,2102],{"class":120,"line":161},[118,2103,574],{"class":124},[118,2105,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2130,2132],{"class":120,"line":166},[118,2107,1737],{"class":226},[118,2109,25],{"class":124},[118,2111,387],{"class":213},[118,2113,255],{"class":124},[118,2115,870],{"class":299},[118,2117,395],{"class":124},[118,2119,398],{"class":124},[118,2121,401],{"class":331},[118,2123,334],{"class":124},[118,2125,337],{"class":128},[118,2127,25],{"class":124},[118,2129,410],{"class":128},[118,2131,345],{"class":124},[118,2133,220],{"class":124},[118,2135,2136,2138,2140,2142,2144,2146,2148,2151],{"class":120,"line":176},[118,2137,1768],{"class":226},[118,2139,25],{"class":124},[118,2141,1040],{"class":213},[118,2143,255],{"class":124},[118,2145,337],{"class":226},[118,2147,25],{"class":124},[118,2149,2150],{"class":213},"AlignCenter",[118,2152,1289],{"class":124},[118,2154,2155],{"class":120,"line":186},[118,2156,574],{"class":124},[118,2158,2159,2161,2163,2165,2167,2169,2171,2173,2175,2177,2179,2181,2183,2185],{"class":120,"line":196},[118,2160,1737],{"class":226},[118,2162,25],{"class":124},[118,2164,387],{"class":213},[118,2166,255],{"class":124},[118,2168,870],{"class":299},[118,2170,395],{"class":124},[118,2172,398],{"class":124},[118,2174,401],{"class":331},[118,2176,334],{"class":124},[118,2178,337],{"class":128},[118,2180,25],{"class":124},[118,2182,410],{"class":128},[118,2184,345],{"class":124},[118,2186,220],{"class":124},[118,2188,2189,2191,2193,2195,2197,2199,2202,2204,2206,2208,2210,2212],{"class":120,"line":202},[118,2190,1768],{"class":226},[118,2192,25],{"class":124},[118,2194,425],{"class":213},[118,2196,255],{"class":124},[118,2198,430],{"class":124},[118,2200,2201],{"class":433},"of",[118,2203,430],{"class":124},[118,2205,395],{"class":124},[118,2207,233],{"class":226},[118,2209,25],{"class":124},[118,2211,2150],{"class":213},[118,2213,1289],{"class":124},[118,2215,2216],{"class":120,"line":207},[118,2217,574],{"class":124},[118,2219,2220,2222,2224,2226,2228,2230,2232,2234,2236,2238,2240,2242,2244,2246],{"class":120,"line":223},[118,2221,1737],{"class":226},[118,2223,25],{"class":124},[118,2225,387],{"class":213},[118,2227,255],{"class":124},[118,2229,688],{"class":299},[118,2231,395],{"class":124},[118,2233,398],{"class":124},[118,2235,401],{"class":331},[118,2237,334],{"class":124},[118,2239,337],{"class":128},[118,2241,25],{"class":124},[118,2243,410],{"class":128},[118,2245,345],{"class":124},[118,2247,220],{"class":124},[118,2249,2250,2252,2254,2256,2258,2260,2262,2265],{"class":120,"line":244},[118,2251,1768],{"class":226},[118,2253,25],{"class":124},[118,2255,510],{"class":213},[118,2257,255],{"class":124},[118,2259,337],{"class":226},[118,2261,25],{"class":124},[118,2263,2264],{"class":213},"AlignLeft",[118,2266,1289],{"class":124},[118,2268,2269],{"class":120,"line":269},[118,2270,574],{"class":124},[118,2272,2273,2275,2277,2279,2281,2283,2285,2287,2289,2291,2293,2295,2297,2299],{"class":120,"line":306},[118,2274,1737],{"class":226},[118,2276,25],{"class":124},[118,2278,387],{"class":213},[118,2280,255],{"class":124},[118,2282,870],{"class":299},[118,2284,395],{"class":124},[118,2286,398],{"class":124},[118,2288,401],{"class":331},[118,2290,334],{"class":124},[118,2292,337],{"class":128},[118,2294,25],{"class":124},[118,2296,410],{"class":128},[118,2298,345],{"class":124},[118,2300,2301],{"class":124}," {})\n",[118,2303,2304],{"class":120,"line":312},[118,2305,706],{"class":124},[118,2307,2308],{"class":120,"line":317},[118,2309,1944],{"class":124},[14,2311,2312,2313,2316,2317,2320,2321,2324],{},"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 ",[18,2314,2315],{},"c.PageOf(format string, opts ...TextOption)"," helper that takes a ",[18,2318,2319],{},"fmt.Sprintf","-style template with ",[18,2322,2323],{},"%d"," placeholders for the two numbers. If you have an opinion on the shape of that API, the issue is open on GitHub.",[14,2326,2327,2328,54,2330,2332],{},"For now, the four-column approach is what's there. The pragmatic shortcut is to drop the prefix and just print ",[18,2329,66],{},[18,2331,70],{}," in two adjacent columns separated by a slash:",[109,2334,2336],{"className":111,"code":2335,"language":113,"meta":114,"style":114},"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",[18,2337,2338,2368,2386,2390,2421,2447,2451,2481,2499],{"__ignoreMap":114},[118,2339,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366],{"class":120,"line":121},[118,2341,363],{"class":226},[118,2343,25],{"class":124},[118,2345,387],{"class":213},[118,2347,255],{"class":124},[118,2349,392],{"class":299},[118,2351,395],{"class":124},[118,2353,398],{"class":124},[118,2355,401],{"class":331},[118,2357,334],{"class":124},[118,2359,337],{"class":128},[118,2361,25],{"class":124},[118,2363,410],{"class":128},[118,2365,345],{"class":124},[118,2367,220],{"class":124},[118,2369,2370,2372,2374,2376,2378,2380,2382,2384],{"class":120,"line":132},[118,2371,2022],{"class":226},[118,2373,25],{"class":124},[118,2375,1040],{"class":213},[118,2377,255],{"class":124},[118,2379,337],{"class":226},[118,2381,25],{"class":124},[118,2383,519],{"class":213},[118,2385,1289],{"class":124},[118,2387,2388],{"class":120,"line":139},[118,2389,1944],{"class":124},[118,2391,2392,2394,2396,2398,2400,2403,2405,2407,2409,2411,2413,2415,2417,2419],{"class":120,"line":149},[118,2393,363],{"class":226},[118,2395,25],{"class":124},[118,2397,387],{"class":213},[118,2399,255],{"class":124},[118,2401,2402],{"class":299},"1",[118,2404,395],{"class":124},[118,2406,398],{"class":124},[118,2408,401],{"class":331},[118,2410,334],{"class":124},[118,2412,337],{"class":128},[118,2414,25],{"class":124},[118,2416,410],{"class":128},[118,2418,345],{"class":124},[118,2420,220],{"class":124},[118,2422,2423,2425,2427,2429,2431,2433,2435,2437,2439,2441,2443,2445],{"class":120,"line":161},[118,2424,2022],{"class":226},[118,2426,25],{"class":124},[118,2428,425],{"class":213},[118,2430,255],{"class":124},[118,2432,430],{"class":124},[118,2434,1579],{"class":433},[118,2436,430],{"class":124},[118,2438,395],{"class":124},[118,2440,233],{"class":226},[118,2442,25],{"class":124},[118,2444,2150],{"class":213},[118,2446,1289],{"class":124},[118,2448,2449],{"class":120,"line":166},[118,2450,1944],{"class":124},[118,2452,2453,2455,2457,2459,2461,2463,2465,2467,2469,2471,2473,2475,2477,2479],{"class":120,"line":176},[118,2454,363],{"class":226},[118,2456,25],{"class":124},[118,2458,387],{"class":213},[118,2460,255],{"class":124},[118,2462,1311],{"class":299},[118,2464,395],{"class":124},[118,2466,398],{"class":124},[118,2468,401],{"class":331},[118,2470,334],{"class":124},[118,2472,337],{"class":128},[118,2474,25],{"class":124},[118,2476,410],{"class":128},[118,2478,345],{"class":124},[118,2480,220],{"class":124},[118,2482,2483,2485,2487,2489,2491,2493,2495,2497],{"class":120,"line":186},[118,2484,2022],{"class":226},[118,2486,25],{"class":124},[118,2488,510],{"class":213},[118,2490,255],{"class":124},[118,2492,337],{"class":226},[118,2494,25],{"class":124},[118,2496,2264],{"class":213},[118,2498,1289],{"class":124},[118,2500,2501],{"class":120,"line":196},[118,2502,1944],{"class":124},[14,2504,2505,2508,2509,2511],{},[18,2506,2507],{},"3 / 12"," reads fine in a footer. The full ",[18,2510,1975],{}," looks nicer but costs you three more columns of fiddling.",[41,2513,2515],{"id":2514},"patterns-that-come-up","Patterns that come up",[14,2517,2518],{},"A few configurations people actually want.",[14,2520,2521,2524,2525,2527,2528,2531],{},[1629,2522,2523],{},"Header line under the title."," Add a second ",[18,2526,358],{}," with ",[18,2529,2530],{},"c.Line()",". That's what the example at the top does. The line spans the full content width because the row spans 12 columns.",[14,2533,2534,2537,2538,2540],{},[1629,2535,2536],{},"Footer centered with confidentiality notice."," One row, one column, ",[18,2539,2150],{},". The simplest case.",[109,2542,2544],{"className":111,"code":2543,"language":113,"meta":114,"style":114},"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",[18,2545,2546,2570,2594,2624,2644,2656,2670,2692,2696,2700],{"__ignoreMap":114},[118,2547,2548,2550,2552,2554,2556,2558,2560,2562,2564,2566,2568],{"class":120,"line":121},[118,2549,1687],{"class":226},[118,2551,25],{"class":124},[118,2553,721],{"class":213},[118,2555,328],{"class":124},[118,2557,14],{"class":331},[118,2559,334],{"class":124},[118,2561,337],{"class":128},[118,2563,25],{"class":124},[118,2565,342],{"class":128},[118,2567,345],{"class":124},[118,2569,220],{"class":124},[118,2571,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590,2592],{"class":120,"line":132},[118,2573,1712],{"class":226},[118,2575,25],{"class":124},[118,2577,358],{"class":213},[118,2579,328],{"class":124},[118,2581,363],{"class":331},[118,2583,334],{"class":124},[118,2585,337],{"class":128},[118,2587,25],{"class":124},[118,2589,372],{"class":128},[118,2591,345],{"class":124},[118,2593,220],{"class":124},[118,2595,2596,2598,2600,2602,2604,2606,2608,2610,2612,2614,2616,2618,2620,2622],{"class":120,"line":139},[118,2597,1737],{"class":226},[118,2599,25],{"class":124},[118,2601,387],{"class":213},[118,2603,255],{"class":124},[118,2605,20],{"class":299},[118,2607,395],{"class":124},[118,2609,398],{"class":124},[118,2611,401],{"class":331},[118,2613,334],{"class":124},[118,2615,337],{"class":128},[118,2617,25],{"class":124},[118,2619,410],{"class":128},[118,2621,345],{"class":124},[118,2623,220],{"class":124},[118,2625,2626,2628,2630,2632,2634,2636,2639,2641],{"class":120,"line":149},[118,2627,1768],{"class":226},[118,2629,25],{"class":124},[118,2631,425],{"class":213},[118,2633,255],{"class":124},[118,2635,430],{"class":124},[118,2637,2638],{"class":433},"Confidential — Internal Use Only",[118,2640,430],{"class":124},[118,2642,2643],{"class":124},",\n",[118,2645,2646,2649,2651,2653],{"class":120,"line":161},[118,2647,2648],{"class":226},"                template",[118,2650,25],{"class":124},[118,2652,2150],{"class":213},[118,2654,2655],{"class":124},"(),\n",[118,2657,2658,2660,2662,2664,2666,2668],{"class":120,"line":166},[118,2659,2648],{"class":226},[118,2661,25],{"class":124},[118,2663,455],{"class":213},[118,2665,255],{"class":124},[118,2667,969],{"class":299},[118,2669,266],{"class":124},[118,2671,2672,2674,2676,2678,2680,2682,2684,2686,2688,2690],{"class":120,"line":176},[118,2673,2648],{"class":226},[118,2675,25],{"class":124},[118,2677,545],{"class":213},[118,2679,255],{"class":124},[118,2681,550],{"class":226},[118,2683,25],{"class":124},[118,2685,555],{"class":213},[118,2687,255],{"class":124},[118,2689,560],{"class":299},[118,2691,563],{"class":124},[118,2693,2694],{"class":120,"line":186},[118,2695,574],{"class":124},[118,2697,2698],{"class":120,"line":196},[118,2699,706],{"class":124},[118,2701,2702],{"class":120,"line":202},[118,2703,1944],{"class":124},[14,2705,2706,2709,2710,2712],{},[1629,2707,2708],{},"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 ",[18,2711,519],{},". Done.",[14,2714,2715,2718,2719,2721],{},[1629,2716,2717],{},"Footer that says \"Continued on next page\" on non-last pages."," Not currently supported. The header/footer closure receives a ",[18,2720,342],{},", 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.",[14,2723,2724,2727,2728,2731],{},[1629,2725,2726],{},"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 ",[18,2729,2730],{},"doc.HeaderOn(pages, fn)"," variant is in design.",[41,2733,2735],{"id":2734},"cjk-in-the-footer","CJK in the footer",[14,2737,2738,2739,2742,2743,2746,2747,2750],{},"Because gpdf renders TrueType subsets without CGO, you can put Japanese, Chinese, or Korean text into headers and footers as plain ",[18,2740,2741],{},"c.Text(...)"," calls. No ",[18,2744,2745],{},"AddUTF8Font"," dance, no ",[18,2748,2749],{},"tofu"," boxes if the font is loaded. The only requirement is that the font you use covers the characters you want:",[109,2752,2754],{"className":111,"code":2753,"language":113,"meta":114,"style":114},"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",[18,2755,2756,2771,2790,2815,2819,2823,2847,2871,2901,2949,2953,2983,3013,3017,3021],{"__ignoreMap":114},[118,2757,2758,2761,2763,2765,2767,2769],{"class":120,"line":121},[118,2759,2760],{"class":226},"doc ",[118,2762,230],{"class":124},[118,2764,233],{"class":226},[118,2766,25],{"class":124},[118,2768,238],{"class":213},[118,2770,241],{"class":124},[118,2772,2773,2776,2778,2780,2782,2784,2786,2788],{"class":120,"line":132},[118,2774,2775],{"class":226},"    template",[118,2777,25],{"class":124},[118,2779,252],{"class":213},[118,2781,255],{"class":124},[118,2783,258],{"class":226},[118,2785,25],{"class":124},[118,2787,263],{"class":226},[118,2789,266],{"class":124},[118,2791,2792,2794,2796,2799,2801,2803,2806,2808,2810,2813],{"class":120,"line":139},[118,2793,2775],{"class":226},[118,2795,25],{"class":124},[118,2797,2798],{"class":213},"WithFont",[118,2800,255],{"class":124},[118,2802,430],{"class":124},[118,2804,2805],{"class":433},"NotoSansJP",[118,2807,430],{"class":124},[118,2809,395],{"class":124},[118,2811,2812],{"class":226}," notoSansJPRegular",[118,2814,266],{"class":124},[118,2816,2817],{"class":120,"line":149},[118,2818,199],{"class":124},[118,2820,2821],{"class":120,"line":161},[118,2822,136],{"emptyLinePlaceholder":135},[118,2824,2825,2827,2829,2831,2833,2835,2837,2839,2841,2843,2845],{"class":120,"line":166},[118,2826,1687],{"class":226},[118,2828,25],{"class":124},[118,2830,721],{"class":213},[118,2832,328],{"class":124},[118,2834,14],{"class":331},[118,2836,334],{"class":124},[118,2838,337],{"class":128},[118,2840,25],{"class":124},[118,2842,342],{"class":128},[118,2844,345],{"class":124},[118,2846,220],{"class":124},[118,2848,2849,2851,2853,2855,2857,2859,2861,2863,2865,2867,2869],{"class":120,"line":176},[118,2850,1712],{"class":226},[118,2852,25],{"class":124},[118,2854,358],{"class":213},[118,2856,328],{"class":124},[118,2858,363],{"class":331},[118,2860,334],{"class":124},[118,2862,337],{"class":128},[118,2864,25],{"class":124},[118,2866,372],{"class":128},[118,2868,345],{"class":124},[118,2870,220],{"class":124},[118,2872,2873,2875,2877,2879,2881,2883,2885,2887,2889,2891,2893,2895,2897,2899],{"class":120,"line":186},[118,2874,1737],{"class":226},[118,2876,25],{"class":124},[118,2878,387],{"class":213},[118,2880,255],{"class":124},[118,2882,392],{"class":299},[118,2884,395],{"class":124},[118,2886,398],{"class":124},[118,2888,401],{"class":331},[118,2890,334],{"class":124},[118,2892,337],{"class":128},[118,2894,25],{"class":124},[118,2896,410],{"class":128},[118,2898,345],{"class":124},[118,2900,220],{"class":124},[118,2902,2903,2905,2907,2909,2911,2913,2916,2918,2920,2922,2924,2927,2929,2931,2933,2935,2937,2939,2941,2943,2945,2947],{"class":120,"line":196},[118,2904,1768],{"class":226},[118,2906,25],{"class":124},[118,2908,425],{"class":213},[118,2910,255],{"class":124},[118,2912,430],{"class":124},[118,2914,2915],{"class":433},"社外秘",[118,2917,430],{"class":124},[118,2919,395],{"class":124},[118,2921,233],{"class":226},[118,2923,25],{"class":124},[118,2925,2926],{"class":213},"FontFamily",[118,2928,255],{"class":124},[118,2930,430],{"class":124},[118,2932,2805],{"class":433},[118,2934,430],{"class":124},[118,2936,1280],{"class":124},[118,2938,233],{"class":226},[118,2940,25],{"class":124},[118,2942,455],{"class":213},[118,2944,255],{"class":124},[118,2946,969],{"class":299},[118,2948,463],{"class":124},[118,2950,2951],{"class":120,"line":202},[118,2952,574],{"class":124},[118,2954,2955,2957,2959,2961,2963,2965,2967,2969,2971,2973,2975,2977,2979,2981],{"class":120,"line":207},[118,2956,1737],{"class":226},[118,2958,25],{"class":124},[118,2960,387],{"class":213},[118,2962,255],{"class":124},[118,2964,392],{"class":299},[118,2966,395],{"class":124},[118,2968,398],{"class":124},[118,2970,401],{"class":331},[118,2972,334],{"class":124},[118,2974,337],{"class":128},[118,2976,25],{"class":124},[118,2978,410],{"class":128},[118,2980,345],{"class":124},[118,2982,220],{"class":124},[118,2984,2985,2987,2989,2991,2993,2995,2997,2999,3001,3003,3005,3007,3009,3011],{"class":120,"line":223},[118,2986,1768],{"class":226},[118,2988,25],{"class":124},[118,2990,1040],{"class":213},[118,2992,255],{"class":124},[118,2994,337],{"class":226},[118,2996,25],{"class":124},[118,2998,519],{"class":213},[118,3000,448],{"class":124},[118,3002,233],{"class":226},[118,3004,25],{"class":124},[118,3006,455],{"class":213},[118,3008,255],{"class":124},[118,3010,969],{"class":299},[118,3012,463],{"class":124},[118,3014,3015],{"class":120,"line":244},[118,3016,574],{"class":124},[118,3018,3019],{"class":120,"line":269},[118,3020,706],{"class":124},[118,3022,3023],{"class":120,"line":306},[118,3024,1944],{"class":124},[14,3026,3027,3028,3031],{},"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 ",[18,3029,3030],{},"\"社外秘\""," in the footer, that's three glyphs from NotoSansJP, not 20,000.",[41,3033,3035],{"id":3034},"performance","Performance",[14,3037,3038],{},"This part matters if you're generating PDFs at scale.",[14,3040,3041],{},"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.",[14,3043,3044,3045,3047],{},"For comparison, gofpdf's ",[18,3046,35],{}," 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.",[14,3049,3050],{},"If you're rendering a million PDFs a day, the difference matters. If you're rendering ten, it doesn't.",[41,3052,3054],{"id":3053},"faq","FAQ",[14,3056,3057,3060,3061,3064],{},[1629,3058,3059],{},"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 ",[18,3062,3063],{},"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.",[14,3066,3067,3070],{},[1629,3068,3069],{},"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.",[14,3072,3073,3076],{},[1629,3074,3075],{},"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.",[14,3078,3079,3082,3083,3086],{},[1629,3080,3081],{},"Can I omit the header on landscape pages in a mixed-orientation document?","\nMixed orientations are supported via ",[18,3084,3085],{},"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.",[14,3088,3089,3092,3093,3096,3097,3100,3101,54,3104,3107,3108,3111],{},[1629,3090,3091],{},"Does this work with the JSON template input?","\nYes. The JSON schema has ",[18,3094,3095],{},"header",", ",[18,3098,3099],{},"footer",", and element types ",[18,3102,3103],{},"{\"type\": \"pageNumber\"}",[18,3105,3106],{},"{\"type\": \"totalPages\"}",". The ",[18,3109,3110],{},"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.",[14,3113,3114,3117,3118,3121],{},[1629,3115,3116],{},"Does this work with Go's text/template input?","\nYes. The ",[18,3119,3120],{},"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.",[41,3123,3125],{"id":3124},"next-steps","Next steps",[14,3127,3128],{},"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.",[14,3130,3131,3132,3135],{},"The unresolved bits — ",[18,3133,3134],{},"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.",[41,3137,3139],{"id":3138},"gpdf-を使ってみる","gpdf を使ってみる",[14,3141,3142],{},"gpdf は Go の PDF 生成ライブラリ。MIT、ゼロ依存、CJK 対応。",[109,3144,3148],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[18,3149,3150],{"__ignoreMap":114},[118,3151,3152,3154,3157],{"class":120,"line":121},[118,3153,113],{"class":128},[118,3155,3156],{"class":433}," get",[118,3158,3159],{"class":433}," github.com/gpdf-dev/gpdf\n",[14,3161,3162,3169,3170],{},[3163,3164,3168],"a",{"href":3165,"rel":3166},"https://github.com/gpdf-dev/gpdf",[3167],"nofollow","⭐ Star on GitHub"," · ",[3163,3171,3174],{"href":3172,"rel":3173},"https://gpdf.dev/docs/quickstart",[3167],"Read the docs",[3176,3177,3178],"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":114,"searchDepth":132,"depth":132,"links":3180},[3181,3182,3183,3186,3187,3188,3189,3190,3191,3192,3193],{"id":43,"depth":132,"text":44},{"id":95,"depth":132,"text":96},{"id":1500,"depth":132,"text":1501,"children":3184},[3185],{"id":1615,"depth":139,"text":1616},{"id":1661,"depth":132,"text":1662},{"id":1964,"depth":132,"text":1965},{"id":2514,"depth":132,"text":2515},{"id":2734,"depth":132,"text":2735},{"id":3034,"depth":132,"text":3035},{"id":3053,"depth":132,"text":3054},{"id":3124,"depth":132,"text":3125},{"id":3138,"depth":132,"text":3139},"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":3199,"totalTime":3200,"tools":3201,"steps":3204},"Add headers, footers, and Page X of Y numbering to a Go PDF","PT15M",[3202,3203],"Go 1.22+","github.com/gpdf-dev/gpdf",[3205,3208,3211,3214,3217],{"name":3206,"text":3207},"Create the document with template.New","Call template.New with WithPageSize(document.A4) and WithMargins. The same constructor handles every page configuration.",{"name":3209,"text":3210},"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":3212,"text":3213},"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":3215,"text":3216},"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":3218,"text":3219},"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":6,"description":3195},"blog/027.page-numbers-headers-footers",[3226,3227],"tutorial","internals","69w850k3ddh3xrjaxLhOaWS-4XytqyJQmsi2FKS_eLk",{"id":3230,"title":3231,"author":3232,"body":3236,"date":4859,"description":4860,"draft":3196,"extension":3197,"howTo":3220,"image":3220,"meta":4861,"navigation":135,"path":4862,"seo":4863,"stem":4864,"tags":4865,"updated":3220,"__hash__":4869},"blog/blog/026.gpdf-vs-wkhtmltopdf-vs-chromium.md","gpdf vs wkhtmltopdf vs Chromium-based PDF generation",{"name":3233,"url":3234,"avatar":3235},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":3237,"toc":4845},[3238,3240,3250,3253,3256,3260,3376,3383,3386,3389,3392,3395,3399,3402,3405,3423,3426,3432,3438,3441,3445,3448,3454,3460,3478,3499,3502,3505,3509,3512,4054,4071,4078,4082,4085,4166,4177,4180,4183,4186,4189,4193,4196,4299,4309,4313,4316,4319,4330,4333,4337,4340,4345,4426,4437,4442,4490,4504,4509,4723,4730,4733,4735,4740,4748,4753,4756,4761,4764,4769,4780,4785,4791,4795,4798,4810,4818,4822,4842],[41,3239,44],{"id":43},[14,3241,3242,3243,3245,3246,3249],{},"wkhtmltopdf was archived in January 2023. Headless Chromium (Puppeteer, Playwright, chromedp, go-rod) works but ships a ~170 MB browser binary, holds 50–120 MB resident per concurrent request, and adds 300–800 ms of cold start. ",[1629,3244,1587],{}," generates a single PDF page in ",[1629,3247,3248],{},"13 µs"," with zero dependencies and no headless browser — at the price of not rendering arbitrary HTML+CSS.",[14,3251,3252],{},"Decision rule for the rest of the post: if your spec is \"designer hands me a Tailwind page, make it pixel-perfect,\" Chromium is still the right tool. If your spec is \"invoice, statement, report, certificate, label,\" the native path is a different category of cost.",[14,3254,3255],{},"Bias disclosure: we ship gpdf. The benchmark code is public, the trade-offs section names what we gave up, and the use-case matrix doesn't pretend gpdf wins everything.",[41,3257,3259],{"id":3258},"the-three-architectures-side-by-side","The three architectures, side by side",[1516,3261,3262,3287],{},[1519,3263,3264],{},[1522,3265,3266,3269,3272,3275,3278,3281,3284],{},[1525,3267,3268],{},"Approach",[1525,3270,3271],{},"Example tools",[1525,3273,3274],{},"Render engine",[1525,3276,3277],{},"Binary footprint",[1525,3279,3280],{},"RSS / req",[1525,3282,3283],{},"Cold start",[1525,3285,3286],{},"License",[1532,3288,3289,3314,3345],{},[1522,3290,3291,3296,3299,3302,3305,3308,3311],{},[1537,3292,3293],{},[1629,3294,3295],{},"wkhtmltopdf",[1537,3297,3298],{},"wkhtmltopdf CLI",[1537,3300,3301],{},"QtWebKit fork (~2014)",[1537,3303,3304],{},"~40 MB",[1537,3306,3307],{},"~30–80 MB",[1537,3309,3310],{},"~150 ms",[1537,3312,3313],{},"LGPLv3",[1522,3315,3316,3321,3324,3327,3332,3337,3342],{},[1537,3317,3318],{},[1629,3319,3320],{},"Chromium-based",[1537,3322,3323],{},"Puppeteer, Playwright, chromedp, go-rod",[1537,3325,3326],{},"Blink + V8 (real Chromium)",[1537,3328,3329],{},[1629,3330,3331],{},"~170 MB",[1537,3333,3334],{},[1629,3335,3336],{},"~50–120 MB",[1537,3338,3339],{},[1629,3340,3341],{},"~300–800 ms",[1537,3343,3344],{},"BSD + redistribution constraints",[1522,3346,3347,3352,3355,3358,3363,3368,3373],{},[1537,3348,3349],{},[1629,3350,3351],{},"Native (gpdf)",[1537,3353,3354],{},"gpdf, signintech/gopdf, gofpdf†",[1537,3356,3357],{},"Pure Go PDF writer",[1537,3359,3360],{},[1629,3361,3362],{},"0 deps",[1537,3364,3365],{},[1629,3366,3367],{},"~2–10 MB",[1537,3369,3370],{},[1629,3371,3372],{},"0 ms",[1537,3374,3375],{},"MIT",[14,3377,3378,3379,25],{},"† gofpdf and go-pdf/fpdf are both archived; the rest of the Go landscape is in our ",[3163,3380,3382],{"href":3381},"/blog/go-pdf-library-showdown-2026","2026 showdown",[14,3384,3385],{},"Three things to read off this table before we explain them.",[14,3387,3388],{},"One: wkhtmltopdf's \"binary footprint\" is misleadingly small. The byte count is low because its WebKit fork stopped tracking upstream more than a decade ago. The CVE backlog is not low.",[14,3390,3391],{},"Two: Chromium isn't a PDF library — it's a browser that happens to print. Every cost in that column is a browser cost.",[14,3393,3394],{},"Three: the gap between \"0 ms cold start\" and \"300 ms cold start\" isn't interesting if you run a long-lived server that generates a PDF once an hour. It's existential for serverless (Lambda, Cloud Run, Workers) and for \"1,000 PDFs as fast as possible\" batch jobs.",[41,3396,3398],{"id":3397},"wkhtmltopdf-in-2026-the-state-of-the-art","wkhtmltopdf in 2026: the state of the art",[14,3400,3401],{},"You may not need this section. If your team is already off wkhtmltopdf, skip to \"Chromium-based generation.\"",[14,3403,3404],{},"For everyone else: wkhtmltopdf development effectively stopped in 2022, the project was archived in January 2023, and the maintainer's parting note recommended Chromium as the replacement. The reason was infrastructural. wkhtmltopdf's renderer is QtWebKit, a fork of WebKit that hasn't tracked upstream since around 2014. Qt itself deprecated QtWebKit in 2016 in favor of QtWebEngine (which wraps Chromium). The fork that wkhtmltopdf still uses is a 12-year-old browser engine.",[14,3406,3407,3408,3096,3411,3414,3415,3418,3419,3422],{},"Concretely, this means: modern CSS — full flex spec, grid, custom properties at scale, ",[18,3409,3410],{},"aspect-ratio",[18,3412,3413],{},":has()",", container queries, ",[18,3416,3417],{},"gap"," in flex, modern color functions — either renders wrong or doesn't render. Web fonts via ",[18,3420,3421],{},"@font-face"," mostly work; web fonts with variable axes don't. SVG support is partial. WOFF2 support landed late and is buggy.",[14,3424,3425],{},"So \"use wkhtmltopdf\" in 2026 has two meanings, both bad.",[14,3427,3428,3431],{},[1629,3429,3430],{},"You're on an upstream version that ships unpatched WebKit code."," Security teams will eventually flag this, and \"the project is archived\" is not a remediation plan. The last release shipped in 2020. CVE work since then has been done by Linux distros backporting fixes, not by upstream.",[14,3433,3434,3437],{},[1629,3435,3436],{},"You're maintaining a private fork."," Someone has to read Qt and WebKit source, backport patches, and rebuild for every platform you ship. We've seen this done. The cost is a full-time engineer who would rather be doing anything else.",[14,3439,3440],{},"The migration question is whether you replace wkhtmltopdf with Chromium (high fidelity, high cost) or with a native PDF generator (low cost, no HTML/CSS). That's the rest of this post.",[41,3442,3444],{"id":3443},"chromium-based-generation-what-you-actually-pay-for","Chromium-based generation: what you actually pay for",[14,3446,3447],{},"Headless Chromium is the right tool when you actually need a browser. The cost shows up in four places.",[14,3449,3450,3453],{},[1629,3451,3452],{},"The binary."," Chromium itself is ~170 MB. Playwright bundles a known-good build; Puppeteer downloads one on install (~280 MB across the three browsers it supports). In a container image, this is your largest layer by an order of magnitude. In a Lambda zip with the 250 MB limit, it's the entire deployment.",[14,3455,3456,3459],{},[1629,3457,3458],{},"Per-process memory."," A fresh Chromium process opens at ~50 MB resident. Loading a non-trivial page (real CSS, web fonts, a couple of images) pulls that to 80–120 MB. The numbers vary with the page. The floor doesn't.",[14,3461,3462,3465,3466,3469,3470,3473,3474,3477],{},[1629,3463,3464],{},"Cold start."," Spawning Chromium and navigating to ",[18,3467,3468],{},"about:blank"," is ~300 ms on a warm machine. Adding ",[18,3471,3472],{},"await page.goto(url)"," plus a real page load plus font fetching plus ",[18,3475,3476],{},"await page.pdf()"," is more typically 500 ms to 2 seconds on first request. Keeping a Chromium process warm in a pool helps; it doesn't help on serverless, where you pay the cold start on every scale-up event.",[14,3479,3480,3483,3484,3487,3488,3487,3491,3494,3495,3498],{},[1629,3481,3482],{},"Operational surface."," A browser is a continent of decisions you didn't intend to make: how to handle CSP, when to wait for ",[18,3485,3486],{},"networkidle"," vs ",[18,3489,3490],{},"load",[18,3492,3493],{},"domcontentloaded",", whether to disable JS, how to set ",[18,3496,3497],{},"--disable-dev-shm-usage"," on Docker, what to do when the browser process leaks. None of it is hard. All of it is debugging that you would rather not be doing.",[14,3500,3501],{},"There's an honest counter. When you need the fidelity, you need it. A marketing PDF designed by someone who hands you a Figma export and a Tailwind page, with custom fonts, gradients, and SVG icons that have to render exactly — that's a Chromium job. Trying to do it with a declarative document API will burn a week and produce something the designer rejects on the first review.",[14,3503,3504],{},"So the question isn't \"Chromium yes/no.\" It's \"is what I'm rendering actually a webpage.\"",[41,3506,3508],{"id":3507},"gpdf-native-rendering-without-a-browser","gpdf: native rendering without a browser",[14,3510,3511],{},"gpdf is in the third category — a native Go PDF writer. No HTML, no CSS, no headless browser. You describe the document in Go (or JSON, or Go templates) and the library emits PDF bytes directly.",[109,3513,3515],{"className":111,"code":3514,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPaperSize(document.A4),\n        gpdf.WithMargin(document.Mm(20)),\n    )\n\n    doc.AddPage(func(p *template.PageBuilder) {\n        p.Row(document.Mm(12), func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Text(\"Invoice\", template.FontSize(24), template.Bold())\n            })\n        })\n        p.Row(document.Mm(8), func(r *template.RowBuilder) {\n            r.Col(6, func(c *template.ColBuilder) {\n                c.Text(\"Acme Corp\", template.FontSize(11))\n            })\n            r.Col(6, func(c *template.ColBuilder) {\n                c.Text(\"INV-2026-0517\", template.FontSize(11), template.AlignRight())\n            })\n        })\n        // ...rows + tables + totals...\n    })\n\n    out, _ := os.Create(\"invoice.pdf\")\n    defer out.Close()\n    doc.Write(out)\n}\n",[18,3516,3517,3523,3527,3533,3541,3545,3553,3561,3569,3573,3577,3587,3603,3623,3647,3651,3655,3679,3718,3748,3788,3792,3796,3834,3864,3896,3900,3930,3969,3973,3977,3983,3987,3991,4020,4034,4050],{"__ignoreMap":114},[118,3518,3519,3521],{"class":120,"line":121},[118,3520,125],{"class":124},[118,3522,129],{"class":128},[118,3524,3525],{"class":120,"line":132},[118,3526,136],{"emptyLinePlaceholder":135},[118,3528,3529,3531],{"class":120,"line":139},[118,3530,143],{"class":142},[118,3532,146],{"class":124},[118,3534,3535,3537,3539],{"class":120,"line":149},[118,3536,152],{"class":124},[118,3538,155],{"class":128},[118,3540,158],{"class":124},[118,3542,3543],{"class":120,"line":161},[118,3544,136],{"emptyLinePlaceholder":135},[118,3546,3547,3549,3551],{"class":120,"line":166},[118,3548,152],{"class":124},[118,3550,3203],{"class":128},[118,3552,158],{"class":124},[118,3554,3555,3557,3559],{"class":120,"line":176},[118,3556,152],{"class":124},[118,3558,171],{"class":128},[118,3560,158],{"class":124},[118,3562,3563,3565,3567],{"class":120,"line":186},[118,3564,152],{"class":124},[118,3566,191],{"class":128},[118,3568,158],{"class":124},[118,3570,3571],{"class":120,"line":196},[118,3572,199],{"class":124},[118,3574,3575],{"class":120,"line":202},[118,3576,136],{"emptyLinePlaceholder":135},[118,3578,3579,3581,3583,3585],{"class":120,"line":207},[118,3580,210],{"class":124},[118,3582,214],{"class":213},[118,3584,217],{"class":124},[118,3586,220],{"class":124},[118,3588,3589,3591,3593,3596,3598,3601],{"class":120,"line":223},[118,3590,227],{"class":226},[118,3592,230],{"class":124},[118,3594,3595],{"class":226}," gpdf",[118,3597,25],{"class":124},[118,3599,3600],{"class":213},"NewDocument",[118,3602,241],{"class":124},[118,3604,3605,3608,3610,3613,3615,3617,3619,3621],{"class":120,"line":244},[118,3606,3607],{"class":226},"        gpdf",[118,3609,25],{"class":124},[118,3611,3612],{"class":213},"WithPaperSize",[118,3614,255],{"class":124},[118,3616,258],{"class":226},[118,3618,25],{"class":124},[118,3620,263],{"class":226},[118,3622,266],{"class":124},[118,3624,3625,3627,3629,3632,3634,3636,3638,3640,3642,3644],{"class":120,"line":269},[118,3626,3607],{"class":226},[118,3628,25],{"class":124},[118,3630,3631],{"class":213},"WithMargin",[118,3633,255],{"class":124},[118,3635,258],{"class":226},[118,3637,25],{"class":124},[118,3639,294],{"class":213},[118,3641,255],{"class":124},[118,3643,300],{"class":299},[118,3645,3646],{"class":124},")),\n",[118,3648,3649],{"class":120,"line":306},[118,3650,309],{"class":124},[118,3652,3653],{"class":120,"line":312},[118,3654,136],{"emptyLinePlaceholder":135},[118,3656,3657,3659,3661,3663,3665,3667,3669,3671,3673,3675,3677],{"class":120,"line":317},[118,3658,320],{"class":226},[118,3660,25],{"class":124},[118,3662,1190],{"class":213},[118,3664,328],{"class":124},[118,3666,14],{"class":331},[118,3668,334],{"class":124},[118,3670,337],{"class":128},[118,3672,25],{"class":124},[118,3674,342],{"class":128},[118,3676,345],{"class":124},[118,3678,220],{"class":124},[118,3680,3681,3683,3685,3688,3690,3692,3694,3696,3698,3700,3702,3704,3706,3708,3710,3712,3714,3716],{"class":120,"line":350},[118,3682,353],{"class":226},[118,3684,25],{"class":124},[118,3686,3687],{"class":213},"Row",[118,3689,255],{"class":124},[118,3691,258],{"class":226},[118,3693,25],{"class":124},[118,3695,294],{"class":213},[118,3697,255],{"class":124},[118,3699,20],{"class":299},[118,3701,1280],{"class":124},[118,3703,398],{"class":124},[118,3705,363],{"class":331},[118,3707,334],{"class":124},[118,3709,337],{"class":128},[118,3711,25],{"class":124},[118,3713,372],{"class":128},[118,3715,345],{"class":124},[118,3717,220],{"class":124},[118,3719,3720,3722,3724,3726,3728,3730,3732,3734,3736,3738,3740,3742,3744,3746],{"class":120,"line":379},[118,3721,382],{"class":226},[118,3723,25],{"class":124},[118,3725,387],{"class":213},[118,3727,255],{"class":124},[118,3729,20],{"class":299},[118,3731,395],{"class":124},[118,3733,398],{"class":124},[118,3735,401],{"class":331},[118,3737,334],{"class":124},[118,3739,337],{"class":128},[118,3741,25],{"class":124},[118,3743,410],{"class":128},[118,3745,345],{"class":124},[118,3747,220],{"class":124},[118,3749,3750,3752,3754,3756,3758,3760,3763,3765,3767,3769,3771,3773,3775,3778,3780,3782,3784,3786],{"class":120,"line":417},[118,3751,420],{"class":226},[118,3753,25],{"class":124},[118,3755,425],{"class":213},[118,3757,255],{"class":124},[118,3759,430],{"class":124},[118,3761,3762],{"class":433},"Invoice",[118,3764,430],{"class":124},[118,3766,395],{"class":124},[118,3768,233],{"class":226},[118,3770,25],{"class":124},[118,3772,455],{"class":213},[118,3774,255],{"class":124},[118,3776,3777],{"class":299},"24",[118,3779,1280],{"class":124},[118,3781,233],{"class":226},[118,3783,25],{"class":124},[118,3785,445],{"class":213},[118,3787,1289],{"class":124},[118,3789,3790],{"class":120,"line":466},[118,3791,469],{"class":124},[118,3793,3794],{"class":120,"line":472},[118,3795,574],{"class":124},[118,3797,3798,3800,3802,3804,3806,3808,3810,3812,3814,3816,3818,3820,3822,3824,3826,3828,3830,3832],{"class":120,"line":503},[118,3799,353],{"class":226},[118,3801,25],{"class":124},[118,3803,3687],{"class":213},[118,3805,255],{"class":124},[118,3807,258],{"class":226},[118,3809,25],{"class":124},[118,3811,294],{"class":213},[118,3813,255],{"class":124},[118,3815,969],{"class":299},[118,3817,1280],{"class":124},[118,3819,398],{"class":124},[118,3821,363],{"class":331},[118,3823,334],{"class":124},[118,3825,337],{"class":128},[118,3827,25],{"class":124},[118,3829,372],{"class":128},[118,3831,345],{"class":124},[118,3833,220],{"class":124},[118,3835,3836,3838,3840,3842,3844,3846,3848,3850,3852,3854,3856,3858,3860,3862],{"class":120,"line":537},[118,3837,382],{"class":226},[118,3839,25],{"class":124},[118,3841,387],{"class":213},[118,3843,255],{"class":124},[118,3845,392],{"class":299},[118,3847,395],{"class":124},[118,3849,398],{"class":124},[118,3851,401],{"class":331},[118,3853,334],{"class":124},[118,3855,337],{"class":128},[118,3857,25],{"class":124},[118,3859,410],{"class":128},[118,3861,345],{"class":124},[118,3863,220],{"class":124},[118,3865,3866,3868,3870,3872,3874,3876,3879,3881,3883,3885,3887,3889,3891,3894],{"class":120,"line":566},[118,3867,420],{"class":226},[118,3869,25],{"class":124},[118,3871,425],{"class":213},[118,3873,255],{"class":124},[118,3875,430],{"class":124},[118,3877,3878],{"class":433},"Acme Corp",[118,3880,430],{"class":124},[118,3882,395],{"class":124},[118,3884,233],{"class":226},[118,3886,25],{"class":124},[118,3888,455],{"class":213},[118,3890,255],{"class":124},[118,3892,3893],{"class":299},"11",[118,3895,463],{"class":124},[118,3897,3898],{"class":120,"line":571},[118,3899,469],{"class":124},[118,3901,3902,3904,3906,3908,3910,3912,3914,3916,3918,3920,3922,3924,3926,3928],{"class":120,"line":577},[118,3903,382],{"class":226},[118,3905,25],{"class":124},[118,3907,387],{"class":213},[118,3909,255],{"class":124},[118,3911,392],{"class":299},[118,3913,395],{"class":124},[118,3915,398],{"class":124},[118,3917,401],{"class":331},[118,3919,334],{"class":124},[118,3921,337],{"class":128},[118,3923,25],{"class":124},[118,3925,410],{"class":128},[118,3927,345],{"class":124},[118,3929,220],{"class":124},[118,3931,3932,3934,3936,3938,3940,3942,3945,3947,3949,3951,3953,3955,3957,3959,3961,3963,3965,3967],{"class":120,"line":602},[118,3933,420],{"class":226},[118,3935,25],{"class":124},[118,3937,425],{"class":213},[118,3939,255],{"class":124},[118,3941,430],{"class":124},[118,3943,3944],{"class":433},"INV-2026-0517",[118,3946,430],{"class":124},[118,3948,395],{"class":124},[118,3950,233],{"class":226},[118,3952,25],{"class":124},[118,3954,455],{"class":213},[118,3956,255],{"class":124},[118,3958,3893],{"class":299},[118,3960,1280],{"class":124},[118,3962,233],{"class":226},[118,3964,25],{"class":124},[118,3966,519],{"class":213},[118,3968,1289],{"class":124},[118,3970,3971],{"class":120,"line":633},[118,3972,469],{"class":124},[118,3974,3975],{"class":120,"line":668},[118,3976,574],{"class":124},[118,3978,3979],{"class":120,"line":693},[118,3980,3982],{"class":3981},"sHwdD","        // ...rows + tables + totals...\n",[118,3984,3985],{"class":120,"line":698},[118,3986,706],{"class":124},[118,3988,3989],{"class":120,"line":703},[118,3990,136],{"emptyLinePlaceholder":135},[118,3992,3993,3995,3997,4000,4002,4004,4006,4009,4011,4013,4016,4018],{"class":120,"line":709},[118,3994,1386],{"class":226},[118,3996,395],{"class":124},[118,3998,3999],{"class":226}," _ ",[118,4001,230],{"class":124},[118,4003,1447],{"class":226},[118,4005,25],{"class":124},[118,4007,4008],{"class":213},"Create",[118,4010,255],{"class":124},[118,4012,430],{"class":124},[118,4014,4015],{"class":433},"invoice.pdf",[118,4017,430],{"class":124},[118,4019,199],{"class":124},[118,4021,4022,4025,4027,4029,4032],{"class":120,"line":714},[118,4023,4024],{"class":142},"    defer",[118,4026,1466],{"class":226},[118,4028,25],{"class":124},[118,4030,4031],{"class":213},"Close",[118,4033,1193],{"class":124},[118,4035,4036,4038,4040,4043,4045,4048],{"class":120,"line":740},[118,4037,320],{"class":226},[118,4039,25],{"class":124},[118,4041,4042],{"class":213},"Write",[118,4044,255],{"class":124},[118,4046,4047],{"class":226},"out",[118,4049,199],{"class":124},[118,4051,4052],{"class":120,"line":765},[118,4053,1479],{"class":124},[14,4055,4056,4057,4060,4061,3107,4064,4066,4067,25],{},"That's the whole stack. No Chromium binary in the container. No ",[18,4058,4059],{},"npm install puppeteer",". No ",[18,4062,4063],{},"page.goto",[18,4065,4042],{}," call emits the PDF straight to the writer — for a one-page invoice, ",[3163,4068,4070],{"href":4069},"/blog/why-gpdf-is-faster","~13 µs of CPU time",[14,4072,4073,4074,4077],{},"What we gave up to get there: the renderer doesn't know what ",[18,4075,4076],{},"display: flex"," means. It knows rows, columns (12-column grid), text runs, images, tables, barcodes. For most of the documents people generate at scale — invoices, statements, receipts, reports, certificates, labels, packing slips — that vocabulary is enough. For the rest (marketing PDFs, designer-led brochures, anything that started life as a webpage), it isn't.",[41,4079,4081],{"id":4080},"the-performance-comparison","The performance comparison",[14,4083,4084],{},"Benchmarking these three categories against each other is methodologically gnarly because they're solving slightly different problems. We'll do it anyway. The fair comparison is \"same end product, three implementations\": a one-page invoice with a header, a 4×10 line-item table, and totals.",[1516,4086,4087,4105],{},[1519,4088,4089],{},[1522,4090,4091,4094,4096,4099],{},[1525,4092,4093],{},"Workload",[1525,4095,1587],{},[1525,4097,4098],{},"wkhtmltopdf (CLI)",[1525,4100,4101,4102,345],{},"Chromium (Playwright ",[18,4103,4104],{},"page.pdf()",[1532,4106,4107,4122,4138,4152],{},[1522,4108,4109,4112,4116,4119],{},[1537,4110,4111],{},"Single-page invoice",[1537,4113,4114],{},[1629,4115,3248],{},[1537,4117,4118],{},"~140 ms",[1537,4120,4121],{},"~280 ms (warm) / ~1.2 s (cold)",[1522,4123,4124,4127,4132,4135],{},[1537,4125,4126],{},"100-page paginated report",[1537,4128,4129],{},[1629,4130,4131],{},"683 µs",[1537,4133,4134],{},"~3.4 s",[1537,4136,4137],{},"~6.1 s (warm)",[1522,4139,4140,4143,4146,4149],{},[1537,4141,4142],{},"Peak RSS during single request",[1537,4144,4145],{},"~5 MB",[1537,4147,4148],{},"~70 MB",[1537,4150,4151],{},"~120 MB",[1522,4153,4154,4157,4160,4163],{},[1537,4155,4156],{},"Container image size impact",[1537,4158,4159],{},"0",[1537,4161,4162],{},"+40 MB",[1537,4164,4165],{},"+170 MB",[14,4167,4168,4169,4176],{},"Apple M1, Go 1.25 for gpdf; wkhtmltopdf 0.12.6 binary; Playwright 1.42 with bundled Chromium. The gpdf benchmark code is in ",[3163,4170,4173],{"href":4171,"rel":4172},"https://github.com/gpdf-dev/gpdf/tree/main/_benchmark",[3167],[18,4174,4175],{},"_benchmark/"," — clone and re-run on your hardware.",[14,4178,4179],{},"Two numbers worth dwelling on.",[14,4181,4182],{},"The single-page invoice gap is roughly 22,000×. Most of it isn't the rendering itself — it's the cost of starting and tearing down a browser process per request. If you keep a Playwright pool warm, you cut that by ~4×; you're still at four orders of magnitude.",[14,4184,4185],{},"The 100-page report gap is roughly 9,000×. The rendering cost dominates there, and the constant overhead of \"start a browser\" amortizes. Even amortized, Chromium pays per-element layout costs that a native PDF writer skips.",[14,4187,4188],{},"The peak RSS number is the one that bites in production. A single Chromium process holding ~120 MB during a 6-second job means a 4 GB container can handle roughly 30 concurrent reports. The same container running gpdf handles thousands.",[41,4190,4192],{"id":4191},"when-each-approach-wins","When each approach wins",[14,4194,4195],{},"This isn't a \"gpdf wins everything\" matrix. It isn't supposed to be. Real architecture decisions look like this.",[1516,4197,4198,4211],{},[1519,4199,4200],{},[1522,4201,4202,4205,4208],{},[1525,4203,4204],{},"Use case",[1525,4206,4207],{},"Right tool",[1525,4209,4210],{},"Why",[1532,4212,4213,4224,4234,4245,4255,4266,4277,4288],{},[1522,4214,4215,4218,4221],{},[1537,4216,4217],{},"Marketing PDF from a Figma + Tailwind design",[1537,4219,4220],{},"Chromium (Playwright)",[1537,4222,4223],{},"Fidelity to designer intent matters more than cost.",[1522,4225,4226,4229,4231],{},[1537,4227,4228],{},"50,000 monthly statements at month-end",[1537,4230,1587],{},[1537,4232,4233],{},"Cost per document × volume = real money. No CSS needed.",[1522,4235,4236,4239,4242],{},[1537,4237,4238],{},"One-off \"designer asked for a brochure\"",[1537,4240,4241],{},"Chromium (or InDesign)",[1537,4243,4244],{},"Volume is low, CSS is high. Use the right tool once.",[1522,4246,4247,4250,4252],{},[1537,4248,4249],{},"Invoices for a SaaS billing system",[1537,4251,1587],{},[1537,4253,4254],{},"Volume scales with revenue. Cold start matters. Layout is structured.",[1522,4256,4257,4260,4263],{},[1537,4258,4259],{},"Tax forms / regulatory filings (PDF/A)",[1537,4261,4262],{},"gpdf (or unidoc)",[1537,4264,4265],{},"PDF/A conformance, signing, audit trails. Browsers don't do these.",[1522,4267,4268,4271,4274],{},[1537,4269,4270],{},"Report from a BI dashboard with chart screenshots",[1537,4272,4273],{},"Chromium",[1537,4275,4276],{},"The chart is the point. PDF is the export.",[1522,4278,4279,4282,4285],{},[1537,4280,4281],{},"\"Print the Markdown\" / docs PDF",[1537,4283,4284],{},"gpdf or Chromium",[1537,4286,4287],{},"Either works. Trade cost for fidelity.",[1522,4289,4290,4293,4296],{},[1537,4291,4292],{},"Legacy wkhtmltopdf migration",[1537,4294,4295],{},"gpdf if HTML was simple; Chromium if CSS was real",[1537,4297,4298],{},"Audit the templates first.",[14,4300,4301,4302,3487,4305,4308],{},"The pattern: ",[1629,4303,4304],{},"volume × per-request cost",[1629,4306,4307],{},"design fidelity",". If the first axis dominates, native wins. If the second axis dominates, Chromium wins. wkhtmltopdf doesn't sit anywhere on this matrix in 2026.",[41,4310,4312],{"id":4311},"the-trade-off-we-dont-pretend-doesnt-exist","The trade-off we don't pretend doesn't exist",[14,4314,4315],{},"We've been honest about this throughout but it deserves a section of its own.",[14,4317,4318],{},"gpdf doesn't render HTML or CSS. If your existing system is \"we have an HTML email template that we also print to PDF,\" migrating to gpdf means rewriting the template against gpdf's builder API. For a single template, that's an afternoon. For a library of 30 designer-maintained marketing templates, it's a project.",[14,4320,4321,4322,4324,4325,4329],{},"We also don't render web fonts via ",[18,4323,3421],{},". You pass TTF/OTF files to gpdf at document construction time. CJK fonts in particular are first-class — we wrote about ",[3163,4326,4328],{"href":4327},"/blog/japanese-pdf-in-go","why we render CJK without CGO"," — but the developer has to ship the font file.",[14,4331,4332],{},"What we don't compromise on: speed, memory, deployability, dependency footprint. The trade-off is paid in feature surface, not in production cost. We think most teams generating high-volume structured documents have been overpaying for a browser they didn't need, and the right answer for those teams is the native path. We do not think we're the right answer for every team.",[41,4334,4336],{"id":4335},"code-the-same-invoice-three-ways","Code: the same invoice, three ways",[14,4338,4339],{},"If you want to see what the API differences actually feel like, here are the three implementations side by side.",[14,4341,4342],{},[1629,4343,4344],{},"Chromium (Playwright, Node):",[109,4346,4350],{"className":4347,"code":4348,"language":4349,"meta":114,"style":114},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const { chromium } = require('playwright');\nconst fs = require('fs');\n\n(async () => {\n  const browser = await chromium.launch();\n  const page = await browser.newPage();\n  const html = fs.readFileSync('invoice.html', 'utf8');\n  await page.setContent(html, { waitUntil: 'networkidle' });\n  await page.pdf({\n    path: 'invoice.pdf',\n    format: 'A4',\n    margin: { top: '20mm', bottom: '20mm', left: '20mm', right: '20mm' },\n  });\n  await browser.close();\n})();\n","js",[18,4351,4352,4357,4362,4366,4371,4376,4381,4386,4391,4396,4401,4406,4411,4416,4421],{"__ignoreMap":114},[118,4353,4354],{"class":120,"line":121},[118,4355,4356],{},"const { chromium } = require('playwright');\n",[118,4358,4359],{"class":120,"line":132},[118,4360,4361],{},"const fs = require('fs');\n",[118,4363,4364],{"class":120,"line":139},[118,4365,136],{"emptyLinePlaceholder":135},[118,4367,4368],{"class":120,"line":149},[118,4369,4370],{},"(async () => {\n",[118,4372,4373],{"class":120,"line":161},[118,4374,4375],{},"  const browser = await chromium.launch();\n",[118,4377,4378],{"class":120,"line":166},[118,4379,4380],{},"  const page = await browser.newPage();\n",[118,4382,4383],{"class":120,"line":176},[118,4384,4385],{},"  const html = fs.readFileSync('invoice.html', 'utf8');\n",[118,4387,4388],{"class":120,"line":186},[118,4389,4390],{},"  await page.setContent(html, { waitUntil: 'networkidle' });\n",[118,4392,4393],{"class":120,"line":196},[118,4394,4395],{},"  await page.pdf({\n",[118,4397,4398],{"class":120,"line":202},[118,4399,4400],{},"    path: 'invoice.pdf',\n",[118,4402,4403],{"class":120,"line":207},[118,4404,4405],{},"    format: 'A4',\n",[118,4407,4408],{"class":120,"line":223},[118,4409,4410],{},"    margin: { top: '20mm', bottom: '20mm', left: '20mm', right: '20mm' },\n",[118,4412,4413],{"class":120,"line":244},[118,4414,4415],{},"  });\n",[118,4417,4418],{"class":120,"line":269},[118,4419,4420],{},"  await browser.close();\n",[118,4422,4423],{"class":120,"line":306},[118,4424,4425],{},"})();\n",[14,4427,4428,4429,4432,4433,4436],{},"Plus ",[18,4430,4431],{},"invoice.html"," (which you maintain), plus the bundled Chromium binary (~170 MB), plus a way to handle font loading (web fonts? embedded base64? ",[18,4434,4435],{},"--font-render-hinting","?). Works beautifully on a Tailwind template; the maintenance surface is the HTML.",[14,4438,4439],{},[1629,4440,4441],{},"wkhtmltopdf (shell):",[109,4443,4445],{"className":3145,"code":4444,"language":3147,"meta":114,"style":114},"wkhtmltopdf --enable-local-file-access \\\n  --margin-top 20mm --margin-bottom 20mm --margin-left 20mm --margin-right 20mm \\\n  invoice.html invoice.pdf\n",[18,4446,4447,4457,4482],{"__ignoreMap":114},[118,4448,4449,4451,4454],{"class":120,"line":121},[118,4450,3295],{"class":128},[118,4452,4453],{"class":433}," --enable-local-file-access",[118,4455,4456],{"class":226}," \\\n",[118,4458,4459,4462,4465,4468,4470,4473,4475,4478,4480],{"class":120,"line":132},[118,4460,4461],{"class":433},"  --margin-top",[118,4463,4464],{"class":433}," 20mm",[118,4466,4467],{"class":433}," --margin-bottom",[118,4469,4464],{"class":433},[118,4471,4472],{"class":433}," --margin-left",[118,4474,4464],{"class":433},[118,4476,4477],{"class":433}," --margin-right",[118,4479,4464],{"class":433},[118,4481,4456],{"class":226},[118,4483,4484,4487],{"class":120,"line":139},[118,4485,4486],{"class":433},"  invoice.html",[118,4488,4489],{"class":433}," invoice.pdf\n",[14,4491,4492,4493,4496,4497,4500,4501,4503],{},"Plus the wkhtmltopdf binary, plus an HTML template that avoids CSS that QtWebKit-2014 doesn't understand (in practice: no ",[18,4494,4495],{},"grid",", careful with ",[18,4498,4499],{},"flex",", no ",[18,4502,3413],{},", custom properties partially work). Plus the security conversation when the binary is flagged by an audit.",[14,4505,4506],{},[1629,4507,4508],{},"gpdf (Go):",[109,4510,4512],{"className":111,"code":4511,"language":113,"meta":114,"style":114},"doc := gpdf.NewDocument(\n    gpdf.WithPaperSize(document.A4),\n    gpdf.WithMargin(document.Mm(20)),\n)\ndoc.AddPage(func(p *template.PageBuilder) {\n    invoiceHeader(p, \"INV-2026-0517\", \"Acme Corp\")\n    invoiceTable(p, lineItems)\n    invoiceTotals(p, subtotal, tax, total)\n})\nout, _ := os.Create(\"invoice.pdf\")\ndefer out.Close()\ndoc.Write(out)\n",[18,4513,4514,4528,4547,4569,4573,4597,4624,4640,4666,4670,4696,4709],{"__ignoreMap":114},[118,4515,4516,4518,4520,4522,4524,4526],{"class":120,"line":121},[118,4517,2760],{"class":226},[118,4519,230],{"class":124},[118,4521,3595],{"class":226},[118,4523,25],{"class":124},[118,4525,3600],{"class":213},[118,4527,241],{"class":124},[118,4529,4530,4533,4535,4537,4539,4541,4543,4545],{"class":120,"line":132},[118,4531,4532],{"class":226},"    gpdf",[118,4534,25],{"class":124},[118,4536,3612],{"class":213},[118,4538,255],{"class":124},[118,4540,258],{"class":226},[118,4542,25],{"class":124},[118,4544,263],{"class":226},[118,4546,266],{"class":124},[118,4548,4549,4551,4553,4555,4557,4559,4561,4563,4565,4567],{"class":120,"line":139},[118,4550,4532],{"class":226},[118,4552,25],{"class":124},[118,4554,3631],{"class":213},[118,4556,255],{"class":124},[118,4558,258],{"class":226},[118,4560,25],{"class":124},[118,4562,294],{"class":213},[118,4564,255],{"class":124},[118,4566,300],{"class":299},[118,4568,3646],{"class":124},[118,4570,4571],{"class":120,"line":149},[118,4572,199],{"class":124},[118,4574,4575,4577,4579,4581,4583,4585,4587,4589,4591,4593,4595],{"class":120,"line":161},[118,4576,1687],{"class":226},[118,4578,25],{"class":124},[118,4580,1190],{"class":213},[118,4582,328],{"class":124},[118,4584,14],{"class":331},[118,4586,334],{"class":124},[118,4588,337],{"class":128},[118,4590,25],{"class":124},[118,4592,342],{"class":128},[118,4594,345],{"class":124},[118,4596,220],{"class":124},[118,4598,4599,4602,4604,4606,4608,4610,4612,4614,4616,4618,4620,4622],{"class":120,"line":166},[118,4600,4601],{"class":213},"    invoiceHeader",[118,4603,255],{"class":124},[118,4605,14],{"class":226},[118,4607,395],{"class":124},[118,4609,1146],{"class":124},[118,4611,3944],{"class":433},[118,4613,430],{"class":124},[118,4615,395],{"class":124},[118,4617,1146],{"class":124},[118,4619,3878],{"class":433},[118,4621,430],{"class":124},[118,4623,199],{"class":124},[118,4625,4626,4629,4631,4633,4635,4638],{"class":120,"line":176},[118,4627,4628],{"class":213},"    invoiceTable",[118,4630,255],{"class":124},[118,4632,14],{"class":226},[118,4634,395],{"class":124},[118,4636,4637],{"class":226}," lineItems",[118,4639,199],{"class":124},[118,4641,4642,4645,4647,4649,4651,4654,4656,4659,4661,4664],{"class":120,"line":186},[118,4643,4644],{"class":213},"    invoiceTotals",[118,4646,255],{"class":124},[118,4648,14],{"class":226},[118,4650,395],{"class":124},[118,4652,4653],{"class":226}," subtotal",[118,4655,395],{"class":124},[118,4657,4658],{"class":226}," tax",[118,4660,395],{"class":124},[118,4662,4663],{"class":226}," total",[118,4665,199],{"class":124},[118,4667,4668],{"class":120,"line":196},[118,4669,1944],{"class":124},[118,4671,4672,4674,4676,4678,4680,4682,4684,4686,4688,4690,4692,4694],{"class":120,"line":202},[118,4673,4047],{"class":226},[118,4675,395],{"class":124},[118,4677,3999],{"class":226},[118,4679,230],{"class":124},[118,4681,1447],{"class":226},[118,4683,25],{"class":124},[118,4685,4008],{"class":213},[118,4687,255],{"class":124},[118,4689,430],{"class":124},[118,4691,4015],{"class":433},[118,4693,430],{"class":124},[118,4695,199],{"class":124},[118,4697,4698,4701,4703,4705,4707],{"class":120,"line":207},[118,4699,4700],{"class":142},"defer",[118,4702,1466],{"class":226},[118,4704,25],{"class":124},[118,4706,4031],{"class":213},[118,4708,1193],{"class":124},[118,4710,4711,4713,4715,4717,4719,4721],{"class":120,"line":223},[118,4712,1687],{"class":226},[118,4714,25],{"class":124},[118,4716,4042],{"class":213},[118,4718,255],{"class":124},[118,4720,4047],{"class":226},[118,4722,199],{"class":124},[14,4724,4725,4726,4729],{},"Plus three Go functions you wrote against the builder API. No template files, no binary dependency, no separate render step. Deployable as a single Go binary into a ",[18,4727,4728],{},"FROM scratch"," container.",[14,4731,4732],{},"The right way to read these isn't \"which is shortest.\" It's \"which surface area do I want to maintain.\" Chromium's surface is HTML + CSS + a browser; wkhtmltopdf's surface is HTML + CSS + a decade-old browser; gpdf's surface is Go.",[41,4734,3054],{"id":3053},[14,4736,4737],{},[1629,4738,4739],{},"Is wkhtmltopdf really unusable in 2026?",[14,4741,4742,4743,4747],{},"Unusable is strong. ",[4744,4745,4746],"em",{},"Inadvisable"," is the right word. It still runs, it still produces PDFs, and for simple templates it produces correct PDFs. The reasons not to start a new project with it: the project is archived, the WebKit fork is a 2014 codebase, security audits will flag it, and the official replacement guidance is \"use Chromium.\" If you have wkhtmltopdf in production today, you have time to migrate; you don't have time to add new dependencies on it.",[14,4749,4750],{},[1629,4751,4752],{},"Can't I just run Chromium and accept the cost?",[14,4754,4755],{},"For most workloads, yes. The decision matrix above puts marketing PDFs and designer-led documents firmly in Chromium's column. The reason this post exists is that Chromium is also being used for invoices, statements, and reports — workloads where the browser is paying for fidelity the document doesn't need. That's where the cost shows up in the AWS bill.",[14,4757,4758],{},[1629,4759,4760],{},"What about HTML-to-PDF without Chromium, like html2pdf or jsPDF?",[14,4762,4763],{},"Those are browser-side JS libraries that render HTML to canvas to PDF. Fidelity is significantly worse than Chromium (most modern CSS doesn't work) and perf is worse than native (you're rendering twice: HTML → canvas → PDF). They have a niche — client-side PDF generation in the browser without a server — but they're not in the same comparison.",[14,4765,4766],{},[1629,4767,4768],{},"Does gpdf support PDF/A or digital signatures?",[14,4770,4771,4772,4775,4776,4779],{},"Yes. ",[18,4773,4774],{},"gpdf.WithPDFA(...)"," for PDF/A-1b and PDF/A-2b conformance, ",[18,4777,4778],{},"gpdf.SignDocument(...)"," for PKCS#7 signatures including RFC 3161 timestamping. Both are in the core MIT library — no add-on, no commercial license.",[14,4781,4782],{},[1629,4783,4784],{},"How does gpdf compare to other Go PDF libraries (not browsers)?",[14,4786,4787,4788,25],{},"Different question. Short version: gofpdf and go-pdf/fpdf are archived; signintech/gopdf is maintained but low-level (no layout grid); Maroto v2 is maintained but built on archived gofpdf; unidoc is commercial. The full comparison is in ",[3163,4789,4790],{"href":3381},"Go PDF Library Showdown 2026",[41,4792,4794],{"id":4793},"try-gpdf","Try gpdf",[14,4796,4797],{},"gpdf is a Go PDF library. MIT, zero dependencies, native CJK.",[109,4799,4800],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,4801,4802],{"__ignoreMap":114},[118,4803,4804,4806,4808],{"class":120,"line":121},[118,4805,113],{"class":128},[118,4807,3156],{"class":433},[118,4809,3159],{"class":433},[14,4811,4812,3169,4815],{},[3163,4813,3168],{"href":3165,"rel":4814},[3167],[3163,4816,3174],{"href":3172,"rel":4817},[3167],[41,4819,4821],{"id":4820},"next-reads","Next reads",[46,4823,4824,4830,4835],{},[49,4825,4826,4829],{},[3163,4827,4828],{"href":4069},"Why gpdf is 10–30× faster than other Go PDF libraries"," — the architecture behind the numbers in this post",[49,4831,4832,4834],{},[3163,4833,4790],{"href":3381}," — the comparison among Go-native libraries",[49,4836,4837,4841],{},[3163,4838,4840],{"href":4839},"/blog/gofpdf-migration","Migrate from gofpdf to gpdf"," — if you're moving off an archived library",[3176,4843,4844],{},"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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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":114,"searchDepth":132,"depth":132,"links":4846},[4847,4848,4849,4850,4851,4852,4853,4854,4855,4856,4857,4858],{"id":43,"depth":132,"text":44},{"id":3258,"depth":132,"text":3259},{"id":3397,"depth":132,"text":3398},{"id":3443,"depth":132,"text":3444},{"id":3507,"depth":132,"text":3508},{"id":4080,"depth":132,"text":4081},{"id":4191,"depth":132,"text":4192},{"id":4311,"depth":132,"text":4312},{"id":4335,"depth":132,"text":4336},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"2026-05-18","wkhtmltopdf is archived. Chromium ships a 170 MB browser per request. gpdf renders a page in 13 µs with no browser. Here's the honest 2026 comparison.",{},"/blog/gpdf-vs-wkhtmltopdf-vs-chromium",{"title":3231,"description":4860},"blog/026.gpdf-vs-wkhtmltopdf-vs-chromium",[4866,4867,4868],"comparison","benchmark","migration","0eH3uCpob7UdMIuEWuS-V5k5t3TZWcbEZ9JZNBnOk8g",{"id":4871,"title":4872,"author":4873,"body":4874,"date":5632,"description":6970,"draft":3196,"extension":3197,"howTo":6971,"image":3220,"meta":6991,"navigation":135,"path":6992,"seo":6993,"stem":6994,"tags":6995,"updated":3220,"__hash__":6998},"blog/blog/025.nest-row-in-col.md","How do I nest a Row inside a Col in gpdf?",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":4875,"toc":6957},[4876,4880,4911,4913,4950,4954,4964,5238,5269,5273,5276,5870,5887,5895,5901,6317,6335,6339,6350,6566,6579,6583,6598,6653,6669,6673,6679,6884,6891,6895,6929,6931,6934,6946,6954],[41,4877,4879],{"id":4878},"the-question-in-other-words","The question, in other words",[14,4881,4882,4883,54,4886,4889,4890,4892,4893,4895,4896,4898,4899,4902,4903,4906,4907,4910],{},"You've used Bootstrap or Tailwind, where ",[18,4884,4885],{},".row",[18,4887,4888],{},".col"," nest freely. You can put a ",[18,4891,4885],{}," inside a ",[18,4894,4888],{}," inside another ",[18,4897,4885],{}," and the grid keeps cascading. You sit down with ",[3163,4900,1587],{"href":3165,"rel":4901},[3167],", see the same ",[18,4904,4905],{},"r.Col(span, fn)"," idiom, and reach for ",[18,4908,4909],{},"c.Row(...)"," inside the column callback. It's not there. Was that an oversight?",[41,4912,44],{"id":43},[14,4914,4915,4916,4919,4920,4922,4923,3096,4925,3096,4927,3096,4930,3096,4933,3096,4936,4938,4939,1579,4941,4943,4944,4946,4947,4949],{},"No. ",[1629,4917,4918],{},"gpdf's 12-column grid is flat on purpose."," ",[18,4921,410],{}," only accepts content — ",[18,4924,425],{},[18,4926,1773],{},[18,4928,4929],{},"Table",[18,4931,4932],{},"Box",[18,4934,4935],{},"List",[18,4937,675],{}," — and ",[18,4940,3687],{},[18,4942,358],{}," live on ",[18,4945,342],{},", not on ",[18,4948,410],{},". If you came here looking for the syntax, there isn't one. Read on for the three things that replace it.",[41,4951,4953],{"id":4952},"the-shape-of-the-api","The shape of the API",[14,4955,4956,4957,4959,4960,4963],{},"Here's what ",[18,4958,410],{},"'s method set actually contains (from ",[18,4961,4962],{},"gpdf/template/grid.go","):",[109,4965,4967],{"className":111,"code":4966,"language":113,"meta":114,"style":114},"func (c *ColBuilder) Text(text string, opts ...TextOption)\nfunc (c *ColBuilder) Image(src []byte, opts ...ImageOption)\nfunc (c *ColBuilder) Box(fn func(c *ColBuilder), opts ...BoxOption)\nfunc (c *ColBuilder) Table(header []string, rows [][]string, opts ...TableOption)\nfunc (c *ColBuilder) Line(opts ...LineOption)\nfunc (c *ColBuilder) List(items []string, opts ...ListOption)\nfunc (c *ColBuilder) Spacer(height document.Value)\n// …PageNumber, TotalPages, RichText, QRCode, Barcode\n",[18,4968,4969,5010,5048,5089,5135,5164,5201,5233],{"__ignoreMap":114},[118,4970,4971,4973,4976,4979,4982,4984,4986,4989,4991,4994,4997,4999,5002,5005,5008],{"class":120,"line":121},[118,4972,210],{"class":124},[118,4974,4975],{"class":124}," (",[118,4977,4978],{"class":331},"c ",[118,4980,4981],{"class":124},"*",[118,4983,410],{"class":128},[118,4985,345],{"class":124},[118,4987,4988],{"class":213}," Text",[118,4990,255],{"class":124},[118,4992,4993],{"class":331},"text",[118,4995,4996],{"class":1130}," string",[118,4998,395],{"class":124},[118,5000,5001],{"class":331}," opts",[118,5003,5004],{"class":124}," ...",[118,5006,5007],{"class":128},"TextOption",[118,5009,199],{"class":124},[118,5011,5012,5014,5016,5018,5020,5022,5024,5027,5029,5032,5034,5037,5039,5041,5043,5046],{"class":120,"line":132},[118,5013,210],{"class":124},[118,5015,4975],{"class":124},[118,5017,4978],{"class":331},[118,5019,4981],{"class":124},[118,5021,410],{"class":128},[118,5023,345],{"class":124},[118,5025,5026],{"class":213}," Image",[118,5028,255],{"class":124},[118,5030,5031],{"class":331},"src",[118,5033,1127],{"class":124},[118,5035,5036],{"class":1130},"byte",[118,5038,395],{"class":124},[118,5040,5001],{"class":331},[118,5042,5004],{"class":124},[118,5044,5045],{"class":128},"ImageOption",[118,5047,199],{"class":124},[118,5049,5050,5052,5054,5056,5058,5060,5062,5065,5067,5070,5072,5074,5076,5078,5080,5082,5084,5087],{"class":120,"line":139},[118,5051,210],{"class":124},[118,5053,4975],{"class":124},[118,5055,4978],{"class":331},[118,5057,4981],{"class":124},[118,5059,410],{"class":128},[118,5061,345],{"class":124},[118,5063,5064],{"class":213}," Box",[118,5066,255],{"class":124},[118,5068,5069],{"class":331},"fn",[118,5071,398],{"class":124},[118,5073,401],{"class":331},[118,5075,334],{"class":124},[118,5077,410],{"class":128},[118,5079,1280],{"class":124},[118,5081,5001],{"class":331},[118,5083,5004],{"class":124},[118,5085,5086],{"class":128},"BoxOption",[118,5088,199],{"class":124},[118,5090,5091,5093,5095,5097,5099,5101,5103,5106,5108,5110,5112,5114,5116,5119,5122,5124,5126,5128,5130,5133],{"class":120,"line":149},[118,5092,210],{"class":124},[118,5094,4975],{"class":124},[118,5096,4978],{"class":331},[118,5098,4981],{"class":124},[118,5100,410],{"class":128},[118,5102,345],{"class":124},[118,5104,5105],{"class":213}," Table",[118,5107,255],{"class":124},[118,5109,3095],{"class":331},[118,5111,1127],{"class":124},[118,5113,1131],{"class":1130},[118,5115,395],{"class":124},[118,5117,5118],{"class":331}," rows",[118,5120,5121],{"class":124}," [][]",[118,5123,1131],{"class":1130},[118,5125,395],{"class":124},[118,5127,5001],{"class":331},[118,5129,5004],{"class":124},[118,5131,5132],{"class":128},"TableOption",[118,5134,199],{"class":124},[118,5136,5137,5139,5141,5143,5145,5147,5149,5152,5154,5157,5159,5162],{"class":120,"line":161},[118,5138,210],{"class":124},[118,5140,4975],{"class":124},[118,5142,4978],{"class":331},[118,5144,4981],{"class":124},[118,5146,410],{"class":128},[118,5148,345],{"class":124},[118,5150,5151],{"class":213}," Line",[118,5153,255],{"class":124},[118,5155,5156],{"class":331},"opts",[118,5158,5004],{"class":124},[118,5160,5161],{"class":128},"LineOption",[118,5163,199],{"class":124},[118,5165,5166,5168,5170,5172,5174,5176,5178,5181,5183,5186,5188,5190,5192,5194,5196,5199],{"class":120,"line":166},[118,5167,210],{"class":124},[118,5169,4975],{"class":124},[118,5171,4978],{"class":331},[118,5173,4981],{"class":124},[118,5175,410],{"class":128},[118,5177,345],{"class":124},[118,5179,5180],{"class":213}," List",[118,5182,255],{"class":124},[118,5184,5185],{"class":331},"items",[118,5187,1127],{"class":124},[118,5189,1131],{"class":1130},[118,5191,395],{"class":124},[118,5193,5001],{"class":331},[118,5195,5004],{"class":124},[118,5197,5198],{"class":128},"ListOption",[118,5200,199],{"class":124},[118,5202,5203,5205,5207,5209,5211,5213,5215,5218,5220,5223,5226,5228,5231],{"class":120,"line":176},[118,5204,210],{"class":124},[118,5206,4975],{"class":124},[118,5208,4978],{"class":331},[118,5210,4981],{"class":124},[118,5212,410],{"class":128},[118,5214,345],{"class":124},[118,5216,5217],{"class":213}," Spacer",[118,5219,255],{"class":124},[118,5221,5222],{"class":331},"height",[118,5224,5225],{"class":128}," document",[118,5227,25],{"class":124},[118,5229,5230],{"class":128},"Value",[118,5232,199],{"class":124},[118,5234,5235],{"class":120,"line":186},[118,5236,5237],{"class":3981},"// …PageNumber, TotalPages, RichText, QRCode, Barcode\n",[14,5239,5240,5241,4060,5243,4060,5245,3107,5247,5250,5251,5254,5255,5257,5258,5261,5262,5265,5266,5268],{},"No ",[18,5242,3687],{},[18,5244,358],{},[18,5246,387],{},[18,5248,5249],{},"Col → Row"," path doesn't exist as a method, and ",[18,5252,5253],{},"c.Box(fn, ...)"," is the closest thing — but ",[18,5256,4932],{}," accepts another ",[18,5259,5260],{},"*ColBuilder",", not a row. You can nest ",[1629,5263,5264],{},"columns inside columns"," (sort of, via ",[18,5267,4932],{},"), but you can't open a new horizontal row inside a column. That's the constraint.",[41,5270,5272],{"id":5271},"idiom-1-sibling-rows-at-the-page-level","Idiom 1 — Sibling rows at the page level",[14,5274,5275],{},"This is what 90% of \"nested row\" patterns actually want.",[109,5277,5279],{"className":111,"code":5278,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(15))),\n    )\n    page := doc.AddPage()\n\n    // What you wanted to write (but can't):\n    //\n    //   page.AutoRow(func(r *template.RowBuilder) {\n    //       r.Col(8, func(c *template.ColBuilder) {\n    //           c.Row(...) ❌ doesn't exist\n    //       })\n    //   })\n\n    // What you write instead:\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(8, func(c *template.ColBuilder) {\n            c.Text(\"Article title\", template.FontSize(18), template.Bold())\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"2026-05-16\")\n        })\n    })\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(8, func(c *template.ColBuilder) {\n            c.Text(\"Lead paragraph spans the same 8-wide column.\")\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"by Taiki Noda\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    _ = os.WriteFile(\"flat.pdf\", data, 0o644)\n}\n",[18,5280,5281,5287,5291,5297,5306,5314,5318,5326,5334,5342,5346,5350,5360,5374,5392,5423,5427,5442,5446,5451,5456,5461,5466,5471,5476,5481,5485,5490,5515,5545,5584,5588,5618,5637,5641,5645,5669,5699,5718,5722,5752,5771,5775,5779,5783,5802,5814,5830,5834,5866],{"__ignoreMap":114},[118,5282,5283,5285],{"class":120,"line":121},[118,5284,125],{"class":124},[118,5286,129],{"class":128},[118,5288,5289],{"class":120,"line":132},[118,5290,136],{"emptyLinePlaceholder":135},[118,5292,5293,5295],{"class":120,"line":139},[118,5294,143],{"class":142},[118,5296,146],{"class":124},[118,5298,5299,5301,5304],{"class":120,"line":149},[118,5300,152],{"class":124},[118,5302,5303],{"class":128},"log",[118,5305,158],{"class":124},[118,5307,5308,5310,5312],{"class":120,"line":161},[118,5309,152],{"class":124},[118,5311,155],{"class":128},[118,5313,158],{"class":124},[118,5315,5316],{"class":120,"line":166},[118,5317,136],{"emptyLinePlaceholder":135},[118,5319,5320,5322,5324],{"class":120,"line":176},[118,5321,152],{"class":124},[118,5323,3203],{"class":128},[118,5325,158],{"class":124},[118,5327,5328,5330,5332],{"class":120,"line":186},[118,5329,152],{"class":124},[118,5331,171],{"class":128},[118,5333,158],{"class":124},[118,5335,5336,5338,5340],{"class":120,"line":196},[118,5337,152],{"class":124},[118,5339,191],{"class":128},[118,5341,158],{"class":124},[118,5343,5344],{"class":120,"line":202},[118,5345,199],{"class":124},[118,5347,5348],{"class":120,"line":207},[118,5349,136],{"emptyLinePlaceholder":135},[118,5351,5352,5354,5356,5358],{"class":120,"line":223},[118,5353,210],{"class":124},[118,5355,214],{"class":213},[118,5357,217],{"class":124},[118,5359,220],{"class":124},[118,5361,5362,5364,5366,5368,5370,5372],{"class":120,"line":244},[118,5363,227],{"class":226},[118,5365,230],{"class":124},[118,5367,3595],{"class":226},[118,5369,25],{"class":124},[118,5371,3600],{"class":213},[118,5373,241],{"class":124},[118,5375,5376,5378,5380,5382,5384,5386,5388,5390],{"class":120,"line":269},[118,5377,3607],{"class":226},[118,5379,25],{"class":124},[118,5381,252],{"class":213},[118,5383,255],{"class":124},[118,5385,258],{"class":226},[118,5387,25],{"class":124},[118,5389,263],{"class":226},[118,5391,266],{"class":124},[118,5393,5394,5396,5398,5400,5402,5404,5406,5408,5410,5412,5414,5416,5418,5421],{"class":120,"line":306},[118,5395,3607],{"class":226},[118,5397,25],{"class":124},[118,5399,276],{"class":213},[118,5401,255],{"class":124},[118,5403,258],{"class":226},[118,5405,25],{"class":124},[118,5407,285],{"class":213},[118,5409,255],{"class":124},[118,5411,258],{"class":226},[118,5413,25],{"class":124},[118,5415,294],{"class":213},[118,5417,255],{"class":124},[118,5419,5420],{"class":299},"15",[118,5422,303],{"class":124},[118,5424,5425],{"class":120,"line":312},[118,5426,309],{"class":124},[118,5428,5429,5432,5434,5436,5438,5440],{"class":120,"line":317},[118,5430,5431],{"class":226},"    page ",[118,5433,230],{"class":124},[118,5435,1185],{"class":226},[118,5437,25],{"class":124},[118,5439,1190],{"class":213},[118,5441,1193],{"class":124},[118,5443,5444],{"class":120,"line":350},[118,5445,136],{"emptyLinePlaceholder":135},[118,5447,5448],{"class":120,"line":379},[118,5449,5450],{"class":3981},"    // What you wanted to write (but can't):\n",[118,5452,5453],{"class":120,"line":417},[118,5454,5455],{"class":3981},"    //\n",[118,5457,5458],{"class":120,"line":466},[118,5459,5460],{"class":3981},"    //   page.AutoRow(func(r *template.RowBuilder) {\n",[118,5462,5463],{"class":120,"line":472},[118,5464,5465],{"class":3981},"    //       r.Col(8, func(c *template.ColBuilder) {\n",[118,5467,5468],{"class":120,"line":503},[118,5469,5470],{"class":3981},"    //           c.Row(...) ❌ doesn't exist\n",[118,5472,5473],{"class":120,"line":537},[118,5474,5475],{"class":3981},"    //       })\n",[118,5477,5478],{"class":120,"line":566},[118,5479,5480],{"class":3981},"    //   })\n",[118,5482,5483],{"class":120,"line":571},[118,5484,136],{"emptyLinePlaceholder":135},[118,5486,5487],{"class":120,"line":577},[118,5488,5489],{"class":3981},"    // What you write instead:\n",[118,5491,5492,5495,5497,5499,5501,5503,5505,5507,5509,5511,5513],{"class":120,"line":602},[118,5493,5494],{"class":226},"    page",[118,5496,25],{"class":124},[118,5498,358],{"class":213},[118,5500,328],{"class":124},[118,5502,363],{"class":331},[118,5504,334],{"class":124},[118,5506,337],{"class":128},[118,5508,25],{"class":124},[118,5510,372],{"class":128},[118,5512,345],{"class":124},[118,5514,220],{"class":124},[118,5516,5517,5519,5521,5523,5525,5527,5529,5531,5533,5535,5537,5539,5541,5543],{"class":120,"line":633},[118,5518,1737],{"class":226},[118,5520,25],{"class":124},[118,5522,387],{"class":213},[118,5524,255],{"class":124},[118,5526,969],{"class":299},[118,5528,395],{"class":124},[118,5530,398],{"class":124},[118,5532,401],{"class":331},[118,5534,334],{"class":124},[118,5536,337],{"class":128},[118,5538,25],{"class":124},[118,5540,410],{"class":128},[118,5542,345],{"class":124},[118,5544,220],{"class":124},[118,5546,5547,5549,5551,5553,5555,5557,5560,5562,5564,5566,5568,5570,5572,5574,5576,5578,5580,5582],{"class":120,"line":668},[118,5548,1768],{"class":226},[118,5550,25],{"class":124},[118,5552,425],{"class":213},[118,5554,255],{"class":124},[118,5556,430],{"class":124},[118,5558,5559],{"class":433},"Article title",[118,5561,430],{"class":124},[118,5563,395],{"class":124},[118,5565,233],{"class":226},[118,5567,25],{"class":124},[118,5569,455],{"class":213},[118,5571,255],{"class":124},[118,5573,1277],{"class":299},[118,5575,1280],{"class":124},[118,5577,233],{"class":226},[118,5579,25],{"class":124},[118,5581,445],{"class":213},[118,5583,1289],{"class":124},[118,5585,5586],{"class":120,"line":693},[118,5587,574],{"class":124},[118,5589,5590,5592,5594,5596,5598,5600,5602,5604,5606,5608,5610,5612,5614,5616],{"class":120,"line":698},[118,5591,1737],{"class":226},[118,5593,25],{"class":124},[118,5595,387],{"class":213},[118,5597,255],{"class":124},[118,5599,1493],{"class":299},[118,5601,395],{"class":124},[118,5603,398],{"class":124},[118,5605,401],{"class":331},[118,5607,334],{"class":124},[118,5609,337],{"class":128},[118,5611,25],{"class":124},[118,5613,410],{"class":128},[118,5615,345],{"class":124},[118,5617,220],{"class":124},[118,5619,5620,5622,5624,5626,5628,5630,5633,5635],{"class":120,"line":703},[118,5621,1768],{"class":226},[118,5623,25],{"class":124},[118,5625,425],{"class":213},[118,5627,255],{"class":124},[118,5629,430],{"class":124},[118,5631,5632],{"class":433},"2026-05-16",[118,5634,430],{"class":124},[118,5636,199],{"class":124},[118,5638,5639],{"class":120,"line":709},[118,5640,574],{"class":124},[118,5642,5643],{"class":120,"line":714},[118,5644,706],{"class":124},[118,5646,5647,5649,5651,5653,5655,5657,5659,5661,5663,5665,5667],{"class":120,"line":740},[118,5648,5494],{"class":226},[118,5650,25],{"class":124},[118,5652,358],{"class":213},[118,5654,328],{"class":124},[118,5656,363],{"class":331},[118,5658,334],{"class":124},[118,5660,337],{"class":128},[118,5662,25],{"class":124},[118,5664,372],{"class":128},[118,5666,345],{"class":124},[118,5668,220],{"class":124},[118,5670,5671,5673,5675,5677,5679,5681,5683,5685,5687,5689,5691,5693,5695,5697],{"class":120,"line":765},[118,5672,1737],{"class":226},[118,5674,25],{"class":124},[118,5676,387],{"class":213},[118,5678,255],{"class":124},[118,5680,969],{"class":299},[118,5682,395],{"class":124},[118,5684,398],{"class":124},[118,5686,401],{"class":331},[118,5688,334],{"class":124},[118,5690,337],{"class":128},[118,5692,25],{"class":124},[118,5694,410],{"class":128},[118,5696,345],{"class":124},[118,5698,220],{"class":124},[118,5700,5701,5703,5705,5707,5709,5711,5714,5716],{"class":120,"line":796},[118,5702,1768],{"class":226},[118,5704,25],{"class":124},[118,5706,425],{"class":213},[118,5708,255],{"class":124},[118,5710,430],{"class":124},[118,5712,5713],{"class":433},"Lead paragraph spans the same 8-wide column.",[118,5715,430],{"class":124},[118,5717,199],{"class":124},[118,5719,5720],{"class":120,"line":819},[118,5721,574],{"class":124},[118,5723,5724,5726,5728,5730,5732,5734,5736,5738,5740,5742,5744,5746,5748,5750],{"class":120,"line":851},[118,5725,1737],{"class":226},[118,5727,25],{"class":124},[118,5729,387],{"class":213},[118,5731,255],{"class":124},[118,5733,1493],{"class":299},[118,5735,395],{"class":124},[118,5737,398],{"class":124},[118,5739,401],{"class":331},[118,5741,334],{"class":124},[118,5743,337],{"class":128},[118,5745,25],{"class":124},[118,5747,410],{"class":128},[118,5749,345],{"class":124},[118,5751,220],{"class":124},[118,5753,5754,5756,5758,5760,5762,5764,5767,5769],{"class":120,"line":875},[118,5755,1768],{"class":226},[118,5757,25],{"class":124},[118,5759,425],{"class":213},[118,5761,255],{"class":124},[118,5763,430],{"class":124},[118,5765,5766],{"class":433},"by Taiki Noda",[118,5768,430],{"class":124},[118,5770,199],{"class":124},[118,5772,5773],{"class":120,"line":880},[118,5774,574],{"class":124},[118,5776,5777],{"class":120,"line":885},[118,5778,706],{"class":124},[118,5780,5781],{"class":120,"line":910},[118,5782,136],{"emptyLinePlaceholder":135},[118,5784,5785,5788,5790,5792,5794,5796,5798,5800],{"class":120,"line":941},[118,5786,5787],{"class":226},"    data",[118,5789,395],{"class":124},[118,5791,1391],{"class":226},[118,5793,230],{"class":124},[118,5795,1185],{"class":226},[118,5797,25],{"class":124},[118,5799,1400],{"class":213},[118,5801,1193],{"class":124},[118,5803,5804,5806,5808,5810,5812],{"class":120,"line":974},[118,5805,1408],{"class":142},[118,5807,1391],{"class":226},[118,5809,1413],{"class":124},[118,5811,1416],{"class":124},[118,5813,220],{"class":124},[118,5815,5816,5819,5821,5824,5826,5828],{"class":120,"line":997},[118,5817,5818],{"class":226},"        log",[118,5820,25],{"class":124},[118,5822,5823],{"class":213},"Fatal",[118,5825,255],{"class":124},[118,5827,1429],{"class":226},[118,5829,199],{"class":124},[118,5831,5832],{"class":120,"line":1002},[118,5833,1375],{"class":124},[118,5835,5836,5838,5840,5842,5844,5846,5848,5850,5853,5855,5857,5860,5862,5864],{"class":120,"line":1033},[118,5837,1442],{"class":226},[118,5839,1366],{"class":124},[118,5841,1447],{"class":226},[118,5843,25],{"class":124},[118,5845,1452],{"class":213},[118,5847,255],{"class":124},[118,5849,430],{"class":124},[118,5851,5852],{"class":433},"flat.pdf",[118,5854,430],{"class":124},[118,5856,395],{"class":124},[118,5858,5859],{"class":226}," data",[118,5861,395],{"class":124},[118,5863,1471],{"class":299},[118,5865,199],{"class":124},[118,5867,5868],{"class":120,"line":1065},[118,5869,1479],{"class":124},[14,5871,5872,5873,5875,5876,5879,5880,5882,5883,5886],{},"The two ",[18,5874,358],{},"s share the same ",[18,5877,5878],{},"8+4"," spans, so the columns line up visually. There's no sub-grid; there's a flat sequence of rows that happen to use the same carve. The output is identical to what you'd get from a CSS layout that nested ",[18,5881,4885],{}," inside ",[18,5884,5885],{},".col-8"," — because the only thing the nested form bought you was syntactic locality, and gpdf prefers you spend that on width consistency instead.",[41,5888,5890,5891,5894],{"id":5889},"idiom-2-cbox-for-visual-grouping","Idiom 2 — ",[18,5892,5893],{},"c.Box"," for visual grouping",[14,5896,5897,5898,5900],{},"If the real motivation was \"I want a bordered card with two stacked elements inside this column,\" you wanted ",[18,5899,4932],{},", not a sub-row:",[109,5902,5904],{"className":111,"code":5903,"language":113,"meta":114,"style":114},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(6, func(c *template.ColBuilder) {\n        c.Box(func(c *template.ColBuilder) {\n            c.Text(\"Bill to\", template.Bold())\n            c.Text(\"Acme GmbH\")\n            c.Text(\"Hamburg, Germany\")\n        },\n            template.WithBoxBorder(template.Border(\n                template.BorderWidth(document.Pt(1)),\n                template.BorderColor(pdf.RGBHex(0xBDBDBD)),\n            )),\n            template.WithBoxPadding(document.UniformEdges(document.Mm(4))),\n        )\n    })\n    r.Col(6, func(c *template.ColBuilder) {\n        c.Box(func(c *template.ColBuilder) {\n            c.Text(\"Ship to\", template.Bold())\n            c.Text(\"Same as billing\")\n        },\n            template.WithBoxPadding(document.UniformEdges(document.Mm(4))),\n        )\n    })\n})\n",[18,5905,5906,5931,5962,5987,6014,6033,6052,6057,6078,6102,6126,6131,6162,6167,6171,6201,6225,6252,6271,6275,6305,6309,6313],{"__ignoreMap":114},[118,5907,5908,5911,5913,5915,5917,5919,5921,5923,5925,5927,5929],{"class":120,"line":121},[118,5909,5910],{"class":226},"page",[118,5912,25],{"class":124},[118,5914,358],{"class":213},[118,5916,328],{"class":124},[118,5918,363],{"class":331},[118,5920,334],{"class":124},[118,5922,337],{"class":128},[118,5924,25],{"class":124},[118,5926,372],{"class":128},[118,5928,345],{"class":124},[118,5930,220],{"class":124},[118,5932,5933,5936,5938,5940,5942,5944,5946,5948,5950,5952,5954,5956,5958,5960],{"class":120,"line":132},[118,5934,5935],{"class":226},"    r",[118,5937,25],{"class":124},[118,5939,387],{"class":213},[118,5941,255],{"class":124},[118,5943,392],{"class":299},[118,5945,395],{"class":124},[118,5947,398],{"class":124},[118,5949,401],{"class":331},[118,5951,334],{"class":124},[118,5953,337],{"class":128},[118,5955,25],{"class":124},[118,5957,410],{"class":128},[118,5959,345],{"class":124},[118,5961,220],{"class":124},[118,5963,5964,5967,5969,5971,5973,5975,5977,5979,5981,5983,5985],{"class":120,"line":139},[118,5965,5966],{"class":226},"        c",[118,5968,25],{"class":124},[118,5970,4932],{"class":213},[118,5972,328],{"class":124},[118,5974,401],{"class":331},[118,5976,334],{"class":124},[118,5978,337],{"class":128},[118,5980,25],{"class":124},[118,5982,410],{"class":128},[118,5984,345],{"class":124},[118,5986,220],{"class":124},[118,5988,5989,5991,5993,5995,5997,5999,6002,6004,6006,6008,6010,6012],{"class":120,"line":149},[118,5990,1768],{"class":226},[118,5992,25],{"class":124},[118,5994,425],{"class":213},[118,5996,255],{"class":124},[118,5998,430],{"class":124},[118,6000,6001],{"class":433},"Bill to",[118,6003,430],{"class":124},[118,6005,395],{"class":124},[118,6007,233],{"class":226},[118,6009,25],{"class":124},[118,6011,445],{"class":213},[118,6013,1289],{"class":124},[118,6015,6016,6018,6020,6022,6024,6026,6029,6031],{"class":120,"line":161},[118,6017,1768],{"class":226},[118,6019,25],{"class":124},[118,6021,425],{"class":213},[118,6023,255],{"class":124},[118,6025,430],{"class":124},[118,6027,6028],{"class":433},"Acme GmbH",[118,6030,430],{"class":124},[118,6032,199],{"class":124},[118,6034,6035,6037,6039,6041,6043,6045,6048,6050],{"class":120,"line":166},[118,6036,1768],{"class":226},[118,6038,25],{"class":124},[118,6040,425],{"class":213},[118,6042,255],{"class":124},[118,6044,430],{"class":124},[118,6046,6047],{"class":433},"Hamburg, Germany",[118,6049,430],{"class":124},[118,6051,199],{"class":124},[118,6053,6054],{"class":120,"line":176},[118,6055,6056],{"class":124},"        },\n",[118,6058,6059,6062,6064,6067,6069,6071,6073,6076],{"class":120,"line":186},[118,6060,6061],{"class":226},"            template",[118,6063,25],{"class":124},[118,6065,6066],{"class":213},"WithBoxBorder",[118,6068,255],{"class":124},[118,6070,337],{"class":226},[118,6072,25],{"class":124},[118,6074,6075],{"class":213},"Border",[118,6077,241],{"class":124},[118,6079,6080,6082,6084,6087,6089,6091,6093,6096,6098,6100],{"class":120,"line":196},[118,6081,2648],{"class":226},[118,6083,25],{"class":124},[118,6085,6086],{"class":213},"BorderWidth",[118,6088,255],{"class":124},[118,6090,258],{"class":226},[118,6092,25],{"class":124},[118,6094,6095],{"class":213},"Pt",[118,6097,255],{"class":124},[118,6099,2402],{"class":299},[118,6101,3646],{"class":124},[118,6103,6104,6106,6108,6111,6113,6115,6117,6119,6121,6124],{"class":120,"line":202},[118,6105,2648],{"class":226},[118,6107,25],{"class":124},[118,6109,6110],{"class":213},"BorderColor",[118,6112,255],{"class":124},[118,6114,550],{"class":226},[118,6116,25],{"class":124},[118,6118,658],{"class":213},[118,6120,255],{"class":124},[118,6122,6123],{"class":299},"0xBDBDBD",[118,6125,3646],{"class":124},[118,6127,6128],{"class":120,"line":207},[118,6129,6130],{"class":124},"            )),\n",[118,6132,6133,6135,6137,6140,6142,6144,6146,6148,6150,6152,6154,6156,6158,6160],{"class":120,"line":223},[118,6134,6061],{"class":226},[118,6136,25],{"class":124},[118,6138,6139],{"class":213},"WithBoxPadding",[118,6141,255],{"class":124},[118,6143,258],{"class":226},[118,6145,25],{"class":124},[118,6147,285],{"class":213},[118,6149,255],{"class":124},[118,6151,258],{"class":226},[118,6153,25],{"class":124},[118,6155,294],{"class":213},[118,6157,255],{"class":124},[118,6159,1493],{"class":299},[118,6161,303],{"class":124},[118,6163,6164],{"class":120,"line":244},[118,6165,6166],{"class":124},"        )\n",[118,6168,6169],{"class":120,"line":269},[118,6170,706],{"class":124},[118,6172,6173,6175,6177,6179,6181,6183,6185,6187,6189,6191,6193,6195,6197,6199],{"class":120,"line":306},[118,6174,5935],{"class":226},[118,6176,25],{"class":124},[118,6178,387],{"class":213},[118,6180,255],{"class":124},[118,6182,392],{"class":299},[118,6184,395],{"class":124},[118,6186,398],{"class":124},[118,6188,401],{"class":331},[118,6190,334],{"class":124},[118,6192,337],{"class":128},[118,6194,25],{"class":124},[118,6196,410],{"class":128},[118,6198,345],{"class":124},[118,6200,220],{"class":124},[118,6202,6203,6205,6207,6209,6211,6213,6215,6217,6219,6221,6223],{"class":120,"line":312},[118,6204,5966],{"class":226},[118,6206,25],{"class":124},[118,6208,4932],{"class":213},[118,6210,328],{"class":124},[118,6212,401],{"class":331},[118,6214,334],{"class":124},[118,6216,337],{"class":128},[118,6218,25],{"class":124},[118,6220,410],{"class":128},[118,6222,345],{"class":124},[118,6224,220],{"class":124},[118,6226,6227,6229,6231,6233,6235,6237,6240,6242,6244,6246,6248,6250],{"class":120,"line":317},[118,6228,1768],{"class":226},[118,6230,25],{"class":124},[118,6232,425],{"class":213},[118,6234,255],{"class":124},[118,6236,430],{"class":124},[118,6238,6239],{"class":433},"Ship to",[118,6241,430],{"class":124},[118,6243,395],{"class":124},[118,6245,233],{"class":226},[118,6247,25],{"class":124},[118,6249,445],{"class":213},[118,6251,1289],{"class":124},[118,6253,6254,6256,6258,6260,6262,6264,6267,6269],{"class":120,"line":350},[118,6255,1768],{"class":226},[118,6257,25],{"class":124},[118,6259,425],{"class":213},[118,6261,255],{"class":124},[118,6263,430],{"class":124},[118,6265,6266],{"class":433},"Same as billing",[118,6268,430],{"class":124},[118,6270,199],{"class":124},[118,6272,6273],{"class":120,"line":379},[118,6274,6056],{"class":124},[118,6276,6277,6279,6281,6283,6285,6287,6289,6291,6293,6295,6297,6299,6301,6303],{"class":120,"line":417},[118,6278,6061],{"class":226},[118,6280,25],{"class":124},[118,6282,6139],{"class":213},[118,6284,255],{"class":124},[118,6286,258],{"class":226},[118,6288,25],{"class":124},[118,6290,285],{"class":213},[118,6292,255],{"class":124},[118,6294,258],{"class":226},[118,6296,25],{"class":124},[118,6298,294],{"class":213},[118,6300,255],{"class":124},[118,6302,1493],{"class":299},[118,6304,303],{"class":124},[118,6306,6307],{"class":120,"line":466},[118,6308,6166],{"class":124},[118,6310,6311],{"class":120,"line":472},[118,6312,706],{"class":124},[118,6314,6315],{"class":120,"line":503},[118,6316,1944],{"class":124},[14,6318,6319,5257,6321,6323,6324,6327,6328,6330,6331,6334],{},[18,6320,4932],{},[18,6322,5260],{},", which means everything stacked inside is ",[1629,6325,6326],{},"vertical",". You can't split a Box horizontally either — for that, you go back to Idiom 1. But for the \"card\" pattern that nested-row syntax often gets reached for, this is the right tool. The ",[18,6329,5893],{}," line in ",[18,6332,6333],{},"gpdf/template/grid.go:246"," is the only nesting the grid does, and it's deliberately one-dimensional.",[41,6336,6338],{"id":6337},"idiom-3-plan-the-sub-grid-into-12-columns-directly","Idiom 3 — Plan the sub-grid into 12 columns directly",[14,6340,6341,6342,6345,6346,6349],{},"Sometimes you genuinely want a 2-column layout inside what feels like a half-page section: a thumbnail and a caption inside the left half of the page, a paragraph on the right. The instinct is ",[18,6343,6344],{},"Col(6) > Row > Col(6) + Col(6)",". The flat equivalent is just ",[18,6347,6348],{},"Col(3) + Col(3) + Col(6)",":",[109,6351,6353],{"className":111,"code":6352,"language":113,"meta":114,"style":114},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(3, func(c *template.ColBuilder) {\n        c.Image(thumbBytes)\n    })\n    r.Col(3, func(c *template.ColBuilder) {\n        c.Text(\"Photo by Ansel Adams\", template.Italic())\n        c.Text(\"1942\")\n    })\n    r.Col(6, func(c *template.ColBuilder) {\n        c.Text(\"The body paragraph fills the right half of the page.\")\n    })\n})\n",[18,6354,6355,6379,6409,6424,6428,6458,6486,6505,6509,6539,6558,6562],{"__ignoreMap":114},[118,6356,6357,6359,6361,6363,6365,6367,6369,6371,6373,6375,6377],{"class":120,"line":121},[118,6358,5910],{"class":226},[118,6360,25],{"class":124},[118,6362,358],{"class":213},[118,6364,328],{"class":124},[118,6366,363],{"class":331},[118,6368,334],{"class":124},[118,6370,337],{"class":128},[118,6372,25],{"class":124},[118,6374,372],{"class":128},[118,6376,345],{"class":124},[118,6378,220],{"class":124},[118,6380,6381,6383,6385,6387,6389,6391,6393,6395,6397,6399,6401,6403,6405,6407],{"class":120,"line":132},[118,6382,5935],{"class":226},[118,6384,25],{"class":124},[118,6386,387],{"class":213},[118,6388,255],{"class":124},[118,6390,688],{"class":299},[118,6392,395],{"class":124},[118,6394,398],{"class":124},[118,6396,401],{"class":331},[118,6398,334],{"class":124},[118,6400,337],{"class":128},[118,6402,25],{"class":124},[118,6404,410],{"class":128},[118,6406,345],{"class":124},[118,6408,220],{"class":124},[118,6410,6411,6413,6415,6417,6419,6422],{"class":120,"line":139},[118,6412,5966],{"class":226},[118,6414,25],{"class":124},[118,6416,1773],{"class":213},[118,6418,255],{"class":124},[118,6420,6421],{"class":226},"thumbBytes",[118,6423,199],{"class":124},[118,6425,6426],{"class":120,"line":149},[118,6427,706],{"class":124},[118,6429,6430,6432,6434,6436,6438,6440,6442,6444,6446,6448,6450,6452,6454,6456],{"class":120,"line":161},[118,6431,5935],{"class":226},[118,6433,25],{"class":124},[118,6435,387],{"class":213},[118,6437,255],{"class":124},[118,6439,688],{"class":299},[118,6441,395],{"class":124},[118,6443,398],{"class":124},[118,6445,401],{"class":331},[118,6447,334],{"class":124},[118,6449,337],{"class":128},[118,6451,25],{"class":124},[118,6453,410],{"class":128},[118,6455,345],{"class":124},[118,6457,220],{"class":124},[118,6459,6460,6462,6464,6466,6468,6470,6473,6475,6477,6479,6481,6484],{"class":120,"line":166},[118,6461,5966],{"class":226},[118,6463,25],{"class":124},[118,6465,425],{"class":213},[118,6467,255],{"class":124},[118,6469,430],{"class":124},[118,6471,6472],{"class":433},"Photo by Ansel Adams",[118,6474,430],{"class":124},[118,6476,395],{"class":124},[118,6478,233],{"class":226},[118,6480,25],{"class":124},[118,6482,6483],{"class":213},"Italic",[118,6485,1289],{"class":124},[118,6487,6488,6490,6492,6494,6496,6498,6501,6503],{"class":120,"line":176},[118,6489,5966],{"class":226},[118,6491,25],{"class":124},[118,6493,425],{"class":213},[118,6495,255],{"class":124},[118,6497,430],{"class":124},[118,6499,6500],{"class":433},"1942",[118,6502,430],{"class":124},[118,6504,199],{"class":124},[118,6506,6507],{"class":120,"line":186},[118,6508,706],{"class":124},[118,6510,6511,6513,6515,6517,6519,6521,6523,6525,6527,6529,6531,6533,6535,6537],{"class":120,"line":196},[118,6512,5935],{"class":226},[118,6514,25],{"class":124},[118,6516,387],{"class":213},[118,6518,255],{"class":124},[118,6520,392],{"class":299},[118,6522,395],{"class":124},[118,6524,398],{"class":124},[118,6526,401],{"class":331},[118,6528,334],{"class":124},[118,6530,337],{"class":128},[118,6532,25],{"class":124},[118,6534,410],{"class":128},[118,6536,345],{"class":124},[118,6538,220],{"class":124},[118,6540,6541,6543,6545,6547,6549,6551,6554,6556],{"class":120,"line":202},[118,6542,5966],{"class":226},[118,6544,25],{"class":124},[118,6546,425],{"class":213},[118,6548,255],{"class":124},[118,6550,430],{"class":124},[118,6552,6553],{"class":433},"The body paragraph fills the right half of the page.",[118,6555,430],{"class":124},[118,6557,199],{"class":124},[118,6559,6560],{"class":120,"line":207},[118,6561,706],{"class":124},[118,6563,6564],{"class":120,"line":223},[118,6565,1944],{"class":124},[14,6567,6568,6571,6572,6574,6575,6578],{},[18,6569,6570],{},"3 + 3"," together equal ",[18,6573,392],{},", so the thumbnail/caption pair occupies the left half exactly. Twelve factors into 2, 3, 4, and 6, so a nested grid almost always collapses cleanly. If your nested grid was ",[18,6576,6577],{},"Col(8) > Row > Col(7) + Col(5)",", that doesn't collapse — but those numbers don't mean anything in a real document either. Pick the flat version that does.",[41,6580,6582],{"id":6581},"why-no-nesting","Why no nesting",[14,6584,6585,6586,6589,6590,6593,6594,6597],{},"A flat grid resolves widths in one pass. The row is a percentage of the page width minus margins. Each ",[18,6587,6588],{},"Col(span)"," is ",[18,6591,6592],{},"span / 12"," of that. Done. There is no recursion, no width-of-a-width-of-a-width, no parent context to thread through the layout engine. The line in ",[18,6595,6596],{},"grid.go"," that computes column width is exactly one line:",[109,6599,6601],{"className":111,"code":6600,"language":113,"meta":114,"style":114},"Width: document.Pct(float64(col.span) / float64(gridColumns) * 100),\n",[18,6602,6603],{"__ignoreMap":114},[118,6604,6605,6608,6610,6612,6614,6617,6619,6622,6624,6627,6629,6631,6633,6636,6639,6641,6644,6646,6648,6651],{"class":120,"line":121},[118,6606,6607],{"class":226},"Width",[118,6609,6349],{"class":124},[118,6611,5225],{"class":226},[118,6613,25],{"class":124},[118,6615,6616],{"class":213},"Pct",[118,6618,255],{"class":124},[118,6620,6621],{"class":1130},"float64",[118,6623,255],{"class":124},[118,6625,6626],{"class":226},"col",[118,6628,25],{"class":124},[118,6630,118],{"class":226},[118,6632,345],{"class":124},[118,6634,6635],{"class":124}," /",[118,6637,6638],{"class":1130}," float64",[118,6640,255],{"class":124},[118,6642,6643],{"class":226},"gridColumns",[118,6645,345],{"class":124},[118,6647,334],{"class":124},[118,6649,6650],{"class":299}," 100",[118,6652,266],{"class":124},[14,6654,6655,6656,5882,6659,5882,6662,6665,6666,6668],{},"Add nesting and that line becomes a tree walk. Suddenly you need to decide what ",[18,6657,6658],{},"Col(6)",[18,6660,6661],{},"Col(8)",[18,6663,6664],{},"Col(12)"," means — is ",[18,6667,392],{}," 50% of the parent column, or 50% of the row, or 50% of the page? Bootstrap chose \"50% of the parent\" and added breakpoints and gutters to make that bearable. PDFs don't have breakpoints. PDFs don't have a fluid container. Borrowing the nesting idiom would import three problems we don't have, in exchange for syntactic shorthand we don't need.",[41,6670,6672],{"id":6671},"but-i-really-want-syntactic-locality","\"But I really want syntactic locality\"",[14,6674,6675,6676,6678],{},"Fair. The downside of flattening is that two ",[18,6677,358],{}," calls that conceptually belong together can drift apart in the source as you edit. A small helper closes that gap:",[109,6680,6682],{"className":111,"code":6681,"language":113,"meta":114,"style":114},"func card(page *template.PageBuilder, title, body string) {\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(title, template.Bold())\n        })\n    })\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(body)\n        })\n    })\n}\n",[18,6683,6684,6719,6743,6773,6795,6799,6803,6827,6857,6872,6876,6880],{"__ignoreMap":114},[118,6685,6686,6688,6691,6693,6695,6697,6699,6701,6703,6705,6708,6710,6713,6715,6717],{"class":120,"line":121},[118,6687,210],{"class":124},[118,6689,6690],{"class":213}," card",[118,6692,255],{"class":124},[118,6694,5910],{"class":331},[118,6696,334],{"class":124},[118,6698,337],{"class":128},[118,6700,25],{"class":124},[118,6702,342],{"class":128},[118,6704,395],{"class":124},[118,6706,6707],{"class":331}," title",[118,6709,395],{"class":124},[118,6711,6712],{"class":331}," body",[118,6714,4996],{"class":1130},[118,6716,345],{"class":124},[118,6718,220],{"class":124},[118,6720,6721,6723,6725,6727,6729,6731,6733,6735,6737,6739,6741],{"class":120,"line":132},[118,6722,5494],{"class":226},[118,6724,25],{"class":124},[118,6726,358],{"class":213},[118,6728,328],{"class":124},[118,6730,363],{"class":331},[118,6732,334],{"class":124},[118,6734,337],{"class":128},[118,6736,25],{"class":124},[118,6738,372],{"class":128},[118,6740,345],{"class":124},[118,6742,220],{"class":124},[118,6744,6745,6747,6749,6751,6753,6755,6757,6759,6761,6763,6765,6767,6769,6771],{"class":120,"line":139},[118,6746,1737],{"class":226},[118,6748,25],{"class":124},[118,6750,387],{"class":213},[118,6752,255],{"class":124},[118,6754,20],{"class":299},[118,6756,395],{"class":124},[118,6758,398],{"class":124},[118,6760,401],{"class":331},[118,6762,334],{"class":124},[118,6764,337],{"class":128},[118,6766,25],{"class":124},[118,6768,410],{"class":128},[118,6770,345],{"class":124},[118,6772,220],{"class":124},[118,6774,6775,6777,6779,6781,6783,6785,6787,6789,6791,6793],{"class":120,"line":149},[118,6776,1768],{"class":226},[118,6778,25],{"class":124},[118,6780,425],{"class":213},[118,6782,255],{"class":124},[118,6784,1264],{"class":226},[118,6786,395],{"class":124},[118,6788,233],{"class":226},[118,6790,25],{"class":124},[118,6792,445],{"class":213},[118,6794,1289],{"class":124},[118,6796,6797],{"class":120,"line":161},[118,6798,574],{"class":124},[118,6800,6801],{"class":120,"line":166},[118,6802,706],{"class":124},[118,6804,6805,6807,6809,6811,6813,6815,6817,6819,6821,6823,6825],{"class":120,"line":176},[118,6806,5494],{"class":226},[118,6808,25],{"class":124},[118,6810,358],{"class":213},[118,6812,328],{"class":124},[118,6814,363],{"class":331},[118,6816,334],{"class":124},[118,6818,337],{"class":128},[118,6820,25],{"class":124},[118,6822,372],{"class":128},[118,6824,345],{"class":124},[118,6826,220],{"class":124},[118,6828,6829,6831,6833,6835,6837,6839,6841,6843,6845,6847,6849,6851,6853,6855],{"class":120,"line":186},[118,6830,1737],{"class":226},[118,6832,25],{"class":124},[118,6834,387],{"class":213},[118,6836,255],{"class":124},[118,6838,20],{"class":299},[118,6840,395],{"class":124},[118,6842,398],{"class":124},[118,6844,401],{"class":331},[118,6846,334],{"class":124},[118,6848,337],{"class":128},[118,6850,25],{"class":124},[118,6852,410],{"class":128},[118,6854,345],{"class":124},[118,6856,220],{"class":124},[118,6858,6859,6861,6863,6865,6867,6870],{"class":120,"line":196},[118,6860,1768],{"class":226},[118,6862,25],{"class":124},[118,6864,425],{"class":213},[118,6866,255],{"class":124},[118,6868,6869],{"class":226},"body",[118,6871,199],{"class":124},[118,6873,6874],{"class":120,"line":202},[118,6875,574],{"class":124},[118,6877,6878],{"class":120,"line":207},[118,6879,706],{"class":124},[118,6881,6882],{"class":120,"line":223},[118,6883,1479],{"class":124},[14,6885,6886,6887,6890],{},"The locality lives in your function, not in the API. gpdf doesn't ship ",[18,6888,6889],{},"card"," because it's three lines and your version will fit your document better than ours would.",[41,6892,6894],{"id":6893},"related-recipes","Related recipes",[46,6896,6897,6904,6914,6921],{},[49,6898,6899,6903],{},[3163,6900,6902],{"href":6901},"/blog/12-column-grid","How does the 12-column grid work in gpdf?"," — the grid itself, in detail",[49,6905,6906,6909,6910,6913],{},[3163,6907,6908],{"href":6901},"How do I align a column to the right edge of a row?"," — empty ",[18,6911,6912],{},"Col(n)"," as a spacer trick",[49,6915,6916,6920],{},[3163,6917,6919],{"href":6918},"/blog/invoice-pdf-go-under-50-lines","Generate an invoice PDF in Go in under 50 lines"," — a flat-grid layout that handles a full document",[49,6922,6923,6928],{},[3163,6924,6927],{"href":6925,"rel":6926},"https://gpdf.dev/docs/guide/layout",[3167],"Layout guide"," — full reference for rows, columns, and Box",[41,6930,4794],{"id":4793},[14,6932,6933],{},"gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, native CJK support.",[109,6935,6936],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,6937,6938],{"__ignoreMap":114},[118,6939,6940,6942,6944],{"class":120,"line":121},[118,6941,113],{"class":128},[118,6943,3156],{"class":433},[118,6945,3159],{"class":433},[14,6947,6948,3169,6951],{},[3163,6949,3168],{"href":3165,"rel":6950},[3167],[3163,6952,3174],{"href":3172,"rel":6953},[3167],[3176,6955,6956],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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);}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 .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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":114,"searchDepth":132,"depth":132,"links":6958},[6959,6960,6961,6962,6963,6965,6966,6967,6968,6969],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":4952,"depth":132,"text":4953},{"id":5271,"depth":132,"text":5272},{"id":5889,"depth":132,"text":6964},"Idiom 2 — c.Box for visual grouping",{"id":6337,"depth":132,"text":6338},{"id":6581,"depth":132,"text":6582},{"id":6671,"depth":132,"text":6672},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"You can't — ColBuilder has no Row method in gpdf. The 12-column grid is flat by design. Here are the three idioms that replace nested rows.",{"name":6972,"totalTime":6973,"tools":6974,"steps":6975},"Replace a nested Row-in-Col layout in gpdf with a flat-grid idiom","PT10M",[3202,3203],[6976,6979,6982,6985,6988],{"name":6977,"text":6978},"Stop reaching for c.Row","ColBuilder has no Row or AutoRow method. page.AutoRow and page.Row only exist on PageBuilder. If your editor's autocomplete doesn't show c.Row, that's the API telling you no — not a missing import.",{"name":6980,"text":6981},"Decide what you actually needed the nested row for","Three cases cover almost everything. (1) Two visual rows that share a column boundary — you wanted a sub-layout. (2) A header and a body inside a card — you wanted visual grouping. (3) A 2×2 grid inside one column — you wanted a sub-grid.",{"name":6983,"text":6984},"For a sub-layout, use sibling AutoRows at the page level","Stack two page.AutoRow calls with the same Col spans. The columns line up visually because the row width is the same. You don't lose anything by flattening — rows are already independent.",{"name":6986,"text":6987},"For visual grouping, use c.Box inside the column","c.Box(fn, WithBoxBorder, WithBoxBackground, WithBoxPadding) wraps a stack of content in a styled container. It is not a sub-grid — it accepts Text, Image, Table, etc., not Row — but it gives you the card-with-border look that 80% of nested-row attempts were really chasing.",{"name":6989,"text":6990},"For a real sub-grid, plan it at finer 12-column spans","Instead of nesting a 6+6 inside a Col(6), express the whole layout as 3+3+6 at the top level. The 12-column grid factors into halves, thirds, quarters, and sixths — most nested patterns collapse cleanly into one flat row.",{},"/blog/nest-row-in-col",{"title":4872,"description":6970},"blog/025.nest-row-in-col",[6996,3226,6997],"recipe","templates","_Xl3YciuvNUOo4PaQExUwxF920y6ygRnp4p9dgcJkj0",{"id":7000,"title":7001,"author":7002,"body":7003,"date":8505,"description":8506,"draft":3196,"extension":3197,"howTo":8507,"image":3220,"meta":8526,"navigation":135,"path":8527,"seo":8528,"stem":8529,"tags":8530,"updated":3220,"__hash__":8531},"blog/blog/024.multi-page-table.md","How do I make a table span multiple pages?",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":7004,"toc":8493},[7005,7007,7014,7016,7022,7048,7066,7070,7081,7933,7940,7944,7966,7969,7991,7995,8002,8196,8211,8215,8218,8336,8347,8351,8385,8389,8430,8432,8466,8468,8470,8482,8490],[41,7006,4879],{"id":4878},[14,7008,7009,7010,7013],{},"I have a report — invoice line items, a transaction log, a 300-row export — and it obviously won't fit on one A4 page. In a Go PDF library, what do I have to do to make the table flow onto page 2, page 3, and so on, with the header reappearing at the top each time? In ",[3163,7011,1587],{"href":3165,"rel":7012},[3167],", the answer is short.",[41,7015,44],{"id":43},[14,7017,7018,7019,7021],{},"Nothing. You write one ",[18,7020,4929],{}," call, give it all your rows, and gpdf paginates it:",[109,7023,7025],{"className":111,"code":7024,"language":113,"meta":114,"style":114},"c.Table(header, rows) // rows has 300 entries — gpdf splits it across pages\n",[18,7026,7027],{"__ignoreMap":114},[118,7028,7029,7031,7033,7035,7037,7039,7041,7043,7045],{"class":120,"line":121},[118,7030,401],{"class":226},[118,7032,25],{"class":124},[118,7034,4929],{"class":213},[118,7036,255],{"class":124},[118,7038,3095],{"class":226},[118,7040,395],{"class":124},[118,7042,5118],{"class":226},[118,7044,345],{"class":124},[118,7046,7047],{"class":3981}," // rows has 300 entries — gpdf splits it across pages\n",[14,7049,7050,7051,7053,7054,7057,7058,7061,7062,7065],{},"The body is split row by row across as many pages as it needs. The ",[18,7052,3095],{}," slice is ",[1629,7055,7056],{},"re-emitted at the top of every continuation page"," automatically — same column widths, same styling. There is no ",[18,7059,7060],{},"PageBreak()"," method to call, no ",[18,7063,7064],{},"MaxRowsPerPage"," option, no row-counting loop. Overflow is the layout engine's job, not yours.",[41,7067,7069],{"id":7068},"working-code","Working code",[14,7071,7072,7073,103,7075,7078,7079,25],{},"A complete program that produces a multi-page table. Save as ",[18,7074,102],{},[18,7076,7077],{},"go run .",", get ",[18,7080,1459],{},[109,7082,7084],{"className":111,"code":7083,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\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 := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    brand := pdf.RGBHex(0x1A237E)\n\n    header := []string{\"Date\", \"Invoice #\", \"Customer\", \"Amount\"}\n    rows := make([][]string, 0, 200)\n    for i := 1; i \u003C= 200; i++ {\n        rows = append(rows, []string{\n            fmt.Sprintf(\"2026-%02d-%02d\", (i%6)+1, (i%28)+1),\n            fmt.Sprintf(\"INV-%05d\", 10000+i),\n            fmt.Sprintf(\"Customer #%d\", i),\n            fmt.Sprintf(\"$%d.00\", 100+i*7),\n        })\n    }\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"2026 Invoice Ledger\", template.FontSize(18), template.Bold())\n            c.Spacer(document.Mm(4))\n\n            c.Table(header, rows,\n                template.ColumnWidths(20, 20, 40, 20),\n                template.TableHeaderStyle(\n                    template.TextColor(pdf.White),\n                    template.BgColor(brand),\n                ),\n            )\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"report.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,7085,7086,7092,7096,7102,7111,7119,7127,7131,7139,7147,7155,7163,7167,7171,7181,7195,7213,7243,7247,7251,7272,7276,7325,7352,7383,7407,7469,7500,7525,7562,7566,7570,7574,7588,7612,7642,7681,7703,7707,7725,7754,7765,7784,7800,7805,7810,7814,7818,7822,7840,7852,7866,7870,7911,7925,7929],{"__ignoreMap":114},[118,7087,7088,7090],{"class":120,"line":121},[118,7089,125],{"class":124},[118,7091,129],{"class":128},[118,7093,7094],{"class":120,"line":132},[118,7095,136],{"emptyLinePlaceholder":135},[118,7097,7098,7100],{"class":120,"line":139},[118,7099,143],{"class":142},[118,7101,146],{"class":124},[118,7103,7104,7106,7109],{"class":120,"line":149},[118,7105,152],{"class":124},[118,7107,7108],{"class":128},"fmt",[118,7110,158],{"class":124},[118,7112,7113,7115,7117],{"class":120,"line":161},[118,7114,152],{"class":124},[118,7116,5303],{"class":128},[118,7118,158],{"class":124},[118,7120,7121,7123,7125],{"class":120,"line":166},[118,7122,152],{"class":124},[118,7124,155],{"class":128},[118,7126,158],{"class":124},[118,7128,7129],{"class":120,"line":176},[118,7130,136],{"emptyLinePlaceholder":135},[118,7132,7133,7135,7137],{"class":120,"line":186},[118,7134,152],{"class":124},[118,7136,3203],{"class":128},[118,7138,158],{"class":124},[118,7140,7141,7143,7145],{"class":120,"line":196},[118,7142,152],{"class":124},[118,7144,171],{"class":128},[118,7146,158],{"class":124},[118,7148,7149,7151,7153],{"class":120,"line":202},[118,7150,152],{"class":124},[118,7152,181],{"class":128},[118,7154,158],{"class":124},[118,7156,7157,7159,7161],{"class":120,"line":207},[118,7158,152],{"class":124},[118,7160,191],{"class":128},[118,7162,158],{"class":124},[118,7164,7165],{"class":120,"line":223},[118,7166,199],{"class":124},[118,7168,7169],{"class":120,"line":244},[118,7170,136],{"emptyLinePlaceholder":135},[118,7172,7173,7175,7177,7179],{"class":120,"line":269},[118,7174,210],{"class":124},[118,7176,214],{"class":213},[118,7178,217],{"class":124},[118,7180,220],{"class":124},[118,7182,7183,7185,7187,7189,7191,7193],{"class":120,"line":306},[118,7184,227],{"class":226},[118,7186,230],{"class":124},[118,7188,3595],{"class":226},[118,7190,25],{"class":124},[118,7192,3600],{"class":213},[118,7194,241],{"class":124},[118,7196,7197,7199,7201,7203,7205,7207,7209,7211],{"class":120,"line":312},[118,7198,3607],{"class":226},[118,7200,25],{"class":124},[118,7202,252],{"class":213},[118,7204,255],{"class":124},[118,7206,1587],{"class":226},[118,7208,25],{"class":124},[118,7210,263],{"class":226},[118,7212,266],{"class":124},[118,7214,7215,7217,7219,7221,7223,7225,7227,7229,7231,7233,7235,7237,7239,7241],{"class":120,"line":317},[118,7216,3607],{"class":226},[118,7218,25],{"class":124},[118,7220,276],{"class":213},[118,7222,255],{"class":124},[118,7224,258],{"class":226},[118,7226,25],{"class":124},[118,7228,285],{"class":213},[118,7230,255],{"class":124},[118,7232,258],{"class":226},[118,7234,25],{"class":124},[118,7236,294],{"class":213},[118,7238,255],{"class":124},[118,7240,300],{"class":299},[118,7242,303],{"class":124},[118,7244,7245],{"class":120,"line":350},[118,7246,309],{"class":124},[118,7248,7249],{"class":120,"line":379},[118,7250,136],{"emptyLinePlaceholder":135},[118,7252,7253,7256,7258,7261,7263,7265,7267,7270],{"class":120,"line":417},[118,7254,7255],{"class":226},"    brand ",[118,7257,230],{"class":124},[118,7259,7260],{"class":226}," pdf",[118,7262,25],{"class":124},[118,7264,658],{"class":213},[118,7266,255],{"class":124},[118,7268,7269],{"class":299},"0x1A237E",[118,7271,199],{"class":124},[118,7273,7274],{"class":120,"line":466},[118,7275,136],{"emptyLinePlaceholder":135},[118,7277,7278,7281,7283,7285,7287,7289,7291,7294,7296,7298,7300,7303,7305,7307,7309,7312,7314,7316,7318,7321,7323],{"class":120,"line":472},[118,7279,7280],{"class":226},"    header ",[118,7282,230],{"class":124},[118,7284,1127],{"class":124},[118,7286,1131],{"class":1130},[118,7288,1134],{"class":124},[118,7290,430],{"class":124},[118,7292,7293],{"class":433},"Date",[118,7295,430],{"class":124},[118,7297,395],{"class":124},[118,7299,1146],{"class":124},[118,7301,7302],{"class":433},"Invoice #",[118,7304,430],{"class":124},[118,7306,395],{"class":124},[118,7308,1146],{"class":124},[118,7310,7311],{"class":433},"Customer",[118,7313,430],{"class":124},[118,7315,395],{"class":124},[118,7317,1146],{"class":124},[118,7319,7320],{"class":433},"Amount",[118,7322,430],{"class":124},[118,7324,1479],{"class":124},[118,7326,7327,7330,7332,7335,7338,7340,7342,7345,7347,7350],{"class":120,"line":503},[118,7328,7329],{"class":226},"    rows ",[118,7331,230],{"class":124},[118,7333,7334],{"class":213}," make",[118,7336,7337],{"class":124},"([][]",[118,7339,1131],{"class":1130},[118,7341,395],{"class":124},[118,7343,7344],{"class":299}," 0",[118,7346,395],{"class":124},[118,7348,7349],{"class":299}," 200",[118,7351,199],{"class":124},[118,7353,7354,7356,7359,7361,7364,7367,7369,7372,7374,7376,7378,7381],{"class":120,"line":537},[118,7355,1111],{"class":142},[118,7357,7358],{"class":226}," i ",[118,7360,230],{"class":124},[118,7362,7363],{"class":299}," 1",[118,7365,7366],{"class":124},";",[118,7368,7358],{"class":226},[118,7370,7371],{"class":124},"\u003C=",[118,7373,7349],{"class":299},[118,7375,7366],{"class":124},[118,7377,1114],{"class":226},[118,7379,7380],{"class":124},"++",[118,7382,220],{"class":124},[118,7384,7385,7388,7390,7393,7395,7398,7400,7402,7404],{"class":120,"line":566},[118,7386,7387],{"class":226},"        rows ",[118,7389,1366],{"class":124},[118,7391,7392],{"class":213}," append",[118,7394,255],{"class":124},[118,7396,7397],{"class":226},"rows",[118,7399,395],{"class":124},[118,7401,1127],{"class":124},[118,7403,1131],{"class":1130},[118,7405,7406],{"class":124},"{\n",[118,7408,7409,7412,7414,7417,7419,7421,7424,7428,7431,7433,7435,7437,7439,7442,7445,7447,7450,7452,7454,7456,7458,7460,7463,7465,7467],{"class":120,"line":571},[118,7410,7411],{"class":226},"            fmt",[118,7413,25],{"class":124},[118,7415,7416],{"class":213},"Sprintf",[118,7418,255],{"class":124},[118,7420,430],{"class":124},[118,7422,7423],{"class":433},"2026-",[118,7425,7427],{"class":7426},"swJcz","%02d",[118,7429,7430],{"class":433},"-",[118,7432,7427],{"class":7426},[118,7434,430],{"class":124},[118,7436,395],{"class":124},[118,7438,4975],{"class":124},[118,7440,7441],{"class":226},"i",[118,7443,7444],{"class":124},"%",[118,7446,392],{"class":299},[118,7448,7449],{"class":124},")+",[118,7451,2402],{"class":299},[118,7453,395],{"class":124},[118,7455,4975],{"class":124},[118,7457,7441],{"class":226},[118,7459,7444],{"class":124},[118,7461,7462],{"class":299},"28",[118,7464,7449],{"class":124},[118,7466,2402],{"class":299},[118,7468,266],{"class":124},[118,7470,7471,7473,7475,7477,7479,7481,7484,7487,7489,7491,7494,7496,7498],{"class":120,"line":577},[118,7472,7411],{"class":226},[118,7474,25],{"class":124},[118,7476,7416],{"class":213},[118,7478,255],{"class":124},[118,7480,430],{"class":124},[118,7482,7483],{"class":433},"INV-",[118,7485,7486],{"class":7426},"%05d",[118,7488,430],{"class":124},[118,7490,395],{"class":124},[118,7492,7493],{"class":299}," 10000",[118,7495,1339],{"class":124},[118,7497,7441],{"class":226},[118,7499,266],{"class":124},[118,7501,7502,7504,7506,7508,7510,7512,7515,7517,7519,7521,7523],{"class":120,"line":602},[118,7503,7411],{"class":226},[118,7505,25],{"class":124},[118,7507,7416],{"class":213},[118,7509,255],{"class":124},[118,7511,430],{"class":124},[118,7513,7514],{"class":433},"Customer #",[118,7516,2323],{"class":7426},[118,7518,430],{"class":124},[118,7520,395],{"class":124},[118,7522,1114],{"class":226},[118,7524,266],{"class":124},[118,7526,7527,7529,7531,7533,7535,7537,7540,7542,7545,7547,7549,7551,7553,7555,7557,7560],{"class":120,"line":633},[118,7528,7411],{"class":226},[118,7530,25],{"class":124},[118,7532,7416],{"class":213},[118,7534,255],{"class":124},[118,7536,430],{"class":124},[118,7538,7539],{"class":433},"$",[118,7541,2323],{"class":7426},[118,7543,7544],{"class":433},".00",[118,7546,430],{"class":124},[118,7548,395],{"class":124},[118,7550,6650],{"class":299},[118,7552,1339],{"class":124},[118,7554,7441],{"class":226},[118,7556,4981],{"class":124},[118,7558,7559],{"class":299},"7",[118,7561,266],{"class":124},[118,7563,7564],{"class":120,"line":668},[118,7565,574],{"class":124},[118,7567,7568],{"class":120,"line":693},[118,7569,1375],{"class":124},[118,7571,7572],{"class":120,"line":698},[118,7573,136],{"emptyLinePlaceholder":135},[118,7575,7576,7578,7580,7582,7584,7586],{"class":120,"line":703},[118,7577,5431],{"class":226},[118,7579,230],{"class":124},[118,7581,1185],{"class":226},[118,7583,25],{"class":124},[118,7585,1190],{"class":213},[118,7587,1193],{"class":124},[118,7589,7590,7592,7594,7596,7598,7600,7602,7604,7606,7608,7610],{"class":120,"line":709},[118,7591,5494],{"class":226},[118,7593,25],{"class":124},[118,7595,358],{"class":213},[118,7597,328],{"class":124},[118,7599,363],{"class":331},[118,7601,334],{"class":124},[118,7603,337],{"class":128},[118,7605,25],{"class":124},[118,7607,372],{"class":128},[118,7609,345],{"class":124},[118,7611,220],{"class":124},[118,7613,7614,7616,7618,7620,7622,7624,7626,7628,7630,7632,7634,7636,7638,7640],{"class":120,"line":714},[118,7615,1737],{"class":226},[118,7617,25],{"class":124},[118,7619,387],{"class":213},[118,7621,255],{"class":124},[118,7623,20],{"class":299},[118,7625,395],{"class":124},[118,7627,398],{"class":124},[118,7629,401],{"class":331},[118,7631,334],{"class":124},[118,7633,337],{"class":128},[118,7635,25],{"class":124},[118,7637,410],{"class":128},[118,7639,345],{"class":124},[118,7641,220],{"class":124},[118,7643,7644,7646,7648,7650,7652,7654,7657,7659,7661,7663,7665,7667,7669,7671,7673,7675,7677,7679],{"class":120,"line":740},[118,7645,1768],{"class":226},[118,7647,25],{"class":124},[118,7649,425],{"class":213},[118,7651,255],{"class":124},[118,7653,430],{"class":124},[118,7655,7656],{"class":433},"2026 Invoice Ledger",[118,7658,430],{"class":124},[118,7660,395],{"class":124},[118,7662,233],{"class":226},[118,7664,25],{"class":124},[118,7666,455],{"class":213},[118,7668,255],{"class":124},[118,7670,1277],{"class":299},[118,7672,1280],{"class":124},[118,7674,233],{"class":226},[118,7676,25],{"class":124},[118,7678,445],{"class":213},[118,7680,1289],{"class":124},[118,7682,7683,7685,7687,7689,7691,7693,7695,7697,7699,7701],{"class":120,"line":765},[118,7684,1768],{"class":226},[118,7686,25],{"class":124},[118,7688,675],{"class":213},[118,7690,255],{"class":124},[118,7692,258],{"class":226},[118,7694,25],{"class":124},[118,7696,294],{"class":213},[118,7698,255],{"class":124},[118,7700,1493],{"class":299},[118,7702,463],{"class":124},[118,7704,7705],{"class":120,"line":796},[118,7706,136],{"emptyLinePlaceholder":135},[118,7708,7709,7711,7713,7715,7717,7719,7721,7723],{"class":120,"line":819},[118,7710,1768],{"class":226},[118,7712,25],{"class":124},[118,7714,4929],{"class":213},[118,7716,255],{"class":124},[118,7718,3095],{"class":226},[118,7720,395],{"class":124},[118,7722,5118],{"class":226},[118,7724,2643],{"class":124},[118,7726,7727,7729,7731,7734,7736,7738,7740,7743,7745,7748,7750,7752],{"class":120,"line":851},[118,7728,2648],{"class":226},[118,7730,25],{"class":124},[118,7732,7733],{"class":213},"ColumnWidths",[118,7735,255],{"class":124},[118,7737,300],{"class":299},[118,7739,395],{"class":124},[118,7741,7742],{"class":299}," 20",[118,7744,395],{"class":124},[118,7746,7747],{"class":299}," 40",[118,7749,395],{"class":124},[118,7751,7742],{"class":299},[118,7753,266],{"class":124},[118,7755,7756,7758,7760,7763],{"class":120,"line":875},[118,7757,2648],{"class":226},[118,7759,25],{"class":124},[118,7761,7762],{"class":213},"TableHeaderStyle",[118,7764,241],{"class":124},[118,7766,7767,7769,7771,7773,7775,7777,7779,7782],{"class":120,"line":880},[118,7768,540],{"class":226},[118,7770,25],{"class":124},[118,7772,545],{"class":213},[118,7774,255],{"class":124},[118,7776,550],{"class":226},[118,7778,25],{"class":124},[118,7780,7781],{"class":226},"White",[118,7783,266],{"class":124},[118,7785,7786,7788,7790,7793,7795,7798],{"class":120,"line":885},[118,7787,540],{"class":226},[118,7789,25],{"class":124},[118,7791,7792],{"class":213},"BgColor",[118,7794,255],{"class":124},[118,7796,7797],{"class":226},"brand",[118,7799,266],{"class":124},[118,7801,7802],{"class":120,"line":910},[118,7803,7804],{"class":124},"                ),\n",[118,7806,7807],{"class":120,"line":941},[118,7808,7809],{"class":124},"            )\n",[118,7811,7812],{"class":120,"line":974},[118,7813,574],{"class":124},[118,7815,7816],{"class":120,"line":997},[118,7817,706],{"class":124},[118,7819,7820],{"class":120,"line":1002},[118,7821,136],{"emptyLinePlaceholder":135},[118,7823,7824,7826,7828,7830,7832,7834,7836,7838],{"class":120,"line":1033},[118,7825,5787],{"class":226},[118,7827,395],{"class":124},[118,7829,1391],{"class":226},[118,7831,230],{"class":124},[118,7833,1185],{"class":226},[118,7835,25],{"class":124},[118,7837,1400],{"class":213},[118,7839,1193],{"class":124},[118,7841,7842,7844,7846,7848,7850],{"class":120,"line":1065},[118,7843,1408],{"class":142},[118,7845,1391],{"class":226},[118,7847,1413],{"class":124},[118,7849,1416],{"class":124},[118,7851,220],{"class":124},[118,7853,7854,7856,7858,7860,7862,7864],{"class":120,"line":1088},[118,7855,5818],{"class":226},[118,7857,25],{"class":124},[118,7859,5823],{"class":213},[118,7861,255],{"class":124},[118,7863,1429],{"class":226},[118,7865,199],{"class":124},[118,7867,7868],{"class":120,"line":1093},[118,7869,1375],{"class":124},[118,7871,7872,7874,7876,7878,7880,7882,7884,7886,7888,7890,7892,7894,7896,7898,7900,7903,7905,7907,7909],{"class":120,"line":1098},[118,7873,1408],{"class":142},[118,7875,1391],{"class":226},[118,7877,230],{"class":124},[118,7879,1447],{"class":226},[118,7881,25],{"class":124},[118,7883,1452],{"class":213},[118,7885,255],{"class":124},[118,7887,430],{"class":124},[118,7889,1459],{"class":433},[118,7891,430],{"class":124},[118,7893,395],{"class":124},[118,7895,5859],{"class":226},[118,7897,395],{"class":124},[118,7899,1471],{"class":299},[118,7901,7902],{"class":124},");",[118,7904,1391],{"class":226},[118,7906,1413],{"class":124},[118,7908,1416],{"class":124},[118,7910,220],{"class":124},[118,7912,7913,7915,7917,7919,7921,7923],{"class":120,"line":1103},[118,7914,5818],{"class":226},[118,7916,25],{"class":124},[118,7918,5823],{"class":213},[118,7920,255],{"class":124},[118,7922,1429],{"class":226},[118,7924,199],{"class":124},[118,7926,7927],{"class":120,"line":1108},[118,7928,1375],{"class":124},[118,7930,7931],{"class":120,"line":1177},[118,7932,1479],{"class":124},[14,7934,7935,7936,7939],{},"200 rows on A4 lands on roughly eight pages. On every one of them the dark-blue header sits at the top; the body picks up exactly where the previous page stopped. The only thing in that code that hints at \"multi-page\" is the ",[18,7937,7938],{},"200"," in the loop bound.",[41,7941,7943],{"id":7942},"how-it-works","How it works",[14,7945,7946,7947,7950,7951,7954,7955,7960,7961,7965],{},"Worth understanding so you trust it. When the layout engine lays out the table, it measures body rows in order and adds them to the current page until the next row would exceed the available height. The rows that didn't fit become an ",[4744,7948,7949],{},"overflow table"," — a new ",[18,7952,7953],{},"*document.Table"," carrying the ",[1629,7956,7957,7958],{},"same ",[18,7959,325],{},", the ",[1629,7962,7957,7963],{},[18,7964,721],{},", and the leftover body rows. gpdf flushes the laid-out part to the page, opens the next page, and feeds the overflow table back into the layout engine with the new page's height. Repeat until there's nothing left over.",[14,7967,7968],{},"Two things fall out of that design:",[46,7970,7971,7981],{},[49,7972,7973,7980],{},[1629,7974,7975,7976,7979],{},"The header repeats because it lives in ",[18,7977,7978],{},"tbl.Header",", not in your loop."," The overflow table reuses the same slice, so it re-renders identically on every page. You get this for free.",[49,7982,7983,7986,7987,7990],{},[1629,7984,7985],{},"There's no \"header doesn't fit\" edge case."," The engine reserves space for the header ",[4744,7988,7989],{},"before"," measuring how many body rows fit. If a page can't hold the header plus at least one body row, the whole table is pushed to the next page instead of being split awkwardly.",[41,7992,7994],{"id":7993},"footers-that-repeat-too","Footers that repeat too",[14,7996,7997,7998,8001],{},"If you want a totals row (or a \"page summary\") that also appears at the bottom of every page, that's ",[18,7999,8000],{},"document.Table.Footer"," — available when you build the table at the document layer instead of through the builder:",[109,8003,8005],{"className":111,"code":8004,"language":113,"meta":114,"style":114},"import \"github.com/gpdf-dev/gpdf/document\"\n\ntbl := &document.Table{\n    Columns: []document.TableColumn{\n        {Width: document.Pct(20)}, {Width: document.Pct(20)},\n        {Width: document.Auto},    {Width: document.Pct(20)},\n    },\n    Header: headerRows, // []document.TableRow\n    Body:   bodyRows,\n    Footer: []document.TableRow{footerRow},\n}\n",[18,8006,8007,8017,8021,8039,8057,8099,8136,8141,8156,8168,8192],{"__ignoreMap":114},[118,8008,8009,8011,8013,8015],{"class":120,"line":121},[118,8010,143],{"class":142},[118,8012,1146],{"class":124},[118,8014,171],{"class":128},[118,8016,158],{"class":124},[118,8018,8019],{"class":120,"line":132},[118,8020,136],{"emptyLinePlaceholder":135},[118,8022,8023,8026,8028,8031,8033,8035,8037],{"class":120,"line":139},[118,8024,8025],{"class":226},"tbl ",[118,8027,230],{"class":124},[118,8029,8030],{"class":124}," &",[118,8032,258],{"class":128},[118,8034,25],{"class":124},[118,8036,4929],{"class":128},[118,8038,7406],{"class":124},[118,8040,8041,8044,8046,8048,8050,8052,8055],{"class":120,"line":149},[118,8042,8043],{"class":226},"    Columns",[118,8045,6349],{"class":124},[118,8047,1127],{"class":124},[118,8049,258],{"class":128},[118,8051,25],{"class":124},[118,8053,8054],{"class":128},"TableColumn",[118,8056,7406],{"class":124},[118,8058,8059,8062,8064,8066,8068,8070,8072,8074,8076,8079,8082,8084,8086,8088,8090,8092,8094,8096],{"class":120,"line":161},[118,8060,8061],{"class":124},"        {",[118,8063,6607],{"class":226},[118,8065,6349],{"class":124},[118,8067,5225],{"class":226},[118,8069,25],{"class":124},[118,8071,6616],{"class":213},[118,8073,255],{"class":124},[118,8075,300],{"class":299},[118,8077,8078],{"class":124},")},",[118,8080,8081],{"class":124}," {",[118,8083,6607],{"class":226},[118,8085,6349],{"class":124},[118,8087,5225],{"class":226},[118,8089,25],{"class":124},[118,8091,6616],{"class":213},[118,8093,255],{"class":124},[118,8095,300],{"class":299},[118,8097,8098],{"class":124},")},\n",[118,8100,8101,8103,8105,8107,8109,8111,8114,8117,8120,8122,8124,8126,8128,8130,8132,8134],{"class":120,"line":166},[118,8102,8061],{"class":124},[118,8104,6607],{"class":226},[118,8106,6349],{"class":124},[118,8108,5225],{"class":226},[118,8110,25],{"class":124},[118,8112,8113],{"class":226},"Auto",[118,8115,8116],{"class":124},"},",[118,8118,8119],{"class":124},"    {",[118,8121,6607],{"class":226},[118,8123,6349],{"class":124},[118,8125,5225],{"class":226},[118,8127,25],{"class":124},[118,8129,6616],{"class":213},[118,8131,255],{"class":124},[118,8133,300],{"class":299},[118,8135,8098],{"class":124},[118,8137,8138],{"class":120,"line":176},[118,8139,8140],{"class":124},"    },\n",[118,8142,8143,8146,8148,8151,8153],{"class":120,"line":186},[118,8144,8145],{"class":226},"    Header",[118,8147,6349],{"class":124},[118,8149,8150],{"class":226}," headerRows",[118,8152,395],{"class":124},[118,8154,8155],{"class":3981}," // []document.TableRow\n",[118,8157,8158,8161,8163,8166],{"class":120,"line":196},[118,8159,8160],{"class":226},"    Body",[118,8162,6349],{"class":124},[118,8164,8165],{"class":226},"   bodyRows",[118,8167,2643],{"class":124},[118,8169,8170,8173,8175,8177,8179,8181,8184,8186,8189],{"class":120,"line":202},[118,8171,8172],{"class":226},"    Footer",[118,8174,6349],{"class":124},[118,8176,1127],{"class":124},[118,8178,258],{"class":128},[118,8180,25],{"class":124},[118,8182,8183],{"class":128},"TableRow",[118,8185,1134],{"class":124},[118,8187,8188],{"class":226},"footerRow",[118,8190,8191],{"class":124},"},\n",[118,8193,8194],{"class":120,"line":207},[118,8195,1479],{"class":124},[14,8197,8198,8199,8201,8202,8205,8206,8210],{},"The ",[18,8200,721],{}," slice repeats on every continuation page, same mechanism as the header. The builder's ",[18,8203,8204],{},"c.Table(...)"," doesn't expose a footer because most short tables don't need one — when you do, you've left the common-case zone, and ",[3163,8207,8209],{"href":8208},"/blog/tables-in-go-pdfs","the deep-dive on tables"," walks through the document layer.",[41,8212,8214],{"id":8213},"forcing-the-table-to-start-on-a-fresh-page","Forcing the table to start on a fresh page",[14,8216,8217],{},"There's no per-table \"begin on a new page\" option. You do it at the page level — add a page before the row that holds the table:",[109,8219,8221],{"className":111,"code":8220,"language":113,"meta":114,"style":114},"doc.AddPage() // the table below starts at the top of this page\npage2 := doc.AddPage()\npage2.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Table(header, rows /* , opts... */)\n    })\n})\n",[18,8222,8223,8236,8251,8276,8306,8328,8332],{"__ignoreMap":114},[118,8224,8225,8227,8229,8231,8233],{"class":120,"line":121},[118,8226,1687],{"class":226},[118,8228,25],{"class":124},[118,8230,1190],{"class":213},[118,8232,217],{"class":124},[118,8234,8235],{"class":3981}," // the table below starts at the top of this page\n",[118,8237,8238,8241,8243,8245,8247,8249],{"class":120,"line":132},[118,8239,8240],{"class":226},"page2 ",[118,8242,230],{"class":124},[118,8244,1185],{"class":226},[118,8246,25],{"class":124},[118,8248,1190],{"class":213},[118,8250,1193],{"class":124},[118,8252,8253,8256,8258,8260,8262,8264,8266,8268,8270,8272,8274],{"class":120,"line":139},[118,8254,8255],{"class":226},"page2",[118,8257,25],{"class":124},[118,8259,358],{"class":213},[118,8261,328],{"class":124},[118,8263,363],{"class":331},[118,8265,334],{"class":124},[118,8267,337],{"class":128},[118,8269,25],{"class":124},[118,8271,372],{"class":128},[118,8273,345],{"class":124},[118,8275,220],{"class":124},[118,8277,8278,8280,8282,8284,8286,8288,8290,8292,8294,8296,8298,8300,8302,8304],{"class":120,"line":149},[118,8279,5935],{"class":226},[118,8281,25],{"class":124},[118,8283,387],{"class":213},[118,8285,255],{"class":124},[118,8287,20],{"class":299},[118,8289,395],{"class":124},[118,8291,398],{"class":124},[118,8293,401],{"class":331},[118,8295,334],{"class":124},[118,8297,337],{"class":128},[118,8299,25],{"class":124},[118,8301,410],{"class":128},[118,8303,345],{"class":124},[118,8305,220],{"class":124},[118,8307,8308,8310,8312,8314,8316,8318,8320,8323,8326],{"class":120,"line":161},[118,8309,5966],{"class":226},[118,8311,25],{"class":124},[118,8313,4929],{"class":213},[118,8315,255],{"class":124},[118,8317,3095],{"class":226},[118,8319,395],{"class":124},[118,8321,8322],{"class":226}," rows ",[118,8324,8325],{"class":3981},"/* , opts... */",[118,8327,199],{"class":124},[118,8329,8330],{"class":120,"line":166},[118,8331,706],{"class":124},[118,8333,8334],{"class":120,"line":176},[118,8335,1944],{"class":124},[14,8337,8338,8339,8342,8343,8346],{},"That's the only \"page break\" control you need for tables, because the table's ",[4744,8340,8341],{},"internal"," breaks are handled for you and the ",[4744,8344,8345],{},"external"," one is just \"where does this block start.\"",[41,8348,8350],{"id":8349},"what-you-dont-get","What you don't get",[46,8352,8353,8359,8375],{},[49,8354,8355,8358],{},[1629,8356,8357],{},"\"Keep these rows together.\""," Every body row is split-eligible. There's no annotation that says \"row group 4–7 must stay on one page.\" It's a known gap. If an invoice line item plus its sub-rows really must not be torn across a page, the workaround is to start a fresh page before that group, or build the table at the document layer and insert your own break hints.",[49,8360,8361,4919,8364,8366,8367,8370,8371,8374],{},[1629,8362,8363],{},"A footer on the last page only.",[18,8365,8000],{}," repeats on ",[4744,8368,8369],{},"every"," page by design (per-page column totals are the common case). For a one-shot grand total at the document end, append it as a separate block ",[4744,8372,8373],{},"after"," the table, not inside it.",[49,8376,8377,8380,8381,8384],{},[1629,8378,8379],{},"Page-of-N in the table itself."," \"Page 3 of 8\" belongs in the document footer, not the table. See ",[3163,8382,8383],{"href":8208},"page numbers, headers, and footers"," for where that lives.",[41,8386,8388],{"id":8387},"mistakes-that-cost-ten-minutes","Mistakes that cost ten minutes",[46,8390,8391,8401,8415,8424],{},[49,8392,8393,8400],{},[1629,8394,8395,8396,8399],{},"Looking for a ",[18,8397,8398],{},"PageBreak"," option."," There isn't one and you don't want one — if you're calling it manually you've already lost. Just feed all the rows.",[49,8402,8403,8406,8407,8410,8411,8414],{},[1629,8404,8405],{},"Splitting your data into per-page chunks yourself."," People do ",[18,8408,8409],{},"rows[0:40]"," on page 1, ",[18,8412,8413],{},"rows[40:80]"," on page 2… Don't. You'll get the row math wrong, the last page will be short, and the header styling will drift. Hand gpdf the whole slice.",[49,8416,8417,8420,8421,8423],{},[1629,8418,8419],{},"Expecting the header on page 1 only."," Some libraries do that. gpdf repeats it on ",[4744,8422,8369],{}," page, which is what you want for a report someone prints and flips through.",[49,8425,8426,8429],{},[1629,8427,8428],{},"A 6 MB CJK font on a 150-page table."," The font is subset to the glyphs actually used, so this is fine — the output stays small. But if you somehow disabled subsetting, a long table is where it bites. Leave subsetting on (it's the default).",[41,8431,6894],{"id":6893},[46,8433,8434,8444,8454,8461],{},[49,8435,8436,8439,8440,8443],{},[3163,8437,8438],{"href":8208},"Tables in Go PDFs: column widths, striped rows, page breaks"," — the long form, including ",[18,8441,8442],{},"document.Table"," and footers.",[49,8445,8446,8450,8451,8453],{},[3163,8447,8449],{"href":8448},"/blog/table-column-widths","How do I set custom column widths for a table?"," — ",[18,8452,7733],{}," corner cases.",[49,8455,8456,8460],{},[3163,8457,8459],{"href":8458},"/blog/zebra-striped-table-rows","How do I create striped (zebra) table rows?"," — and how stripes stay consistent across a page break.",[49,8462,8463,8465],{},[3163,8464,6919],{"href":6918}," — a real document with a table that paginates.",[41,8467,4794],{"id":4793},[14,8469,6933],{},[109,8471,8472],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,8473,8474],{"__ignoreMap":114},[118,8475,8476,8478,8480],{"class":120,"line":121},[118,8477,113],{"class":128},[118,8479,3156],{"class":433},[118,8481,3159],{"class":433},[14,8483,8484,3169,8487],{},[3163,8485,3168],{"href":3165,"rel":8486},[3167],[3163,8488,3174],{"href":3172,"rel":8489},[3167],[3176,8491,8492],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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);}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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}",{"title":114,"searchDepth":132,"depth":132,"links":8494},[8495,8496,8497,8498,8499,8500,8501,8502,8503,8504],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":7068,"depth":132,"text":7069},{"id":7942,"depth":132,"text":7943},{"id":7993,"depth":132,"text":7994},{"id":8213,"depth":132,"text":8214},{"id":8349,"depth":132,"text":8350},{"id":8387,"depth":132,"text":8388},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-05-12","You don't do anything. Feed gpdf a table with more rows than fit, and it paginates the body and repeats the header on every page automatically.",{"name":8508,"totalTime":6973,"tools":8509,"steps":8510},"Render a table that spans multiple pages in gpdf with a repeating header",[3202,3203],[8511,8514,8517,8520,8523],{"name":8512,"text":8513},"Build one Table call inside a Col","Inside page.AutoRow → r.Col(12, ...), call c.Table(header, rows). Header is []string, rows is [][]string. Don't loop, don't count rows, don't call any page-break method.",{"name":8515,"text":8516},"Feed it more rows than fit on a page","Append as many body rows as your data has. When the body overflows the current page, gpdf's layout engine produces an overflow table with the remaining rows and continues it on the next page.",{"name":8518,"text":8519},"Let gpdf repeat the header","The Header slice is carried into every continuation page automatically — same styling, same column widths. There is no option to enable this; it's the default behavior.",{"name":8521,"text":8522},"Use document.Table.Footer for per-page totals","Drop to &document.Table{Header, Body, Footer} when you want a footer row (column totals, page label) that also repeats at the bottom of every page.",{"name":8524,"text":8525},"Call doc.AddPage() before the table to force a fresh start","If you need the table to begin at the top of a new page rather than flowing from wherever the previous content ended, add a page first. There is no per-table 'start on new page' option.",{},"/blog/multi-page-table",{"title":7001,"description":8506},"blog/024.multi-page-table",[6996,3226,6997],"vRfwQm6lGJqUQrzATuPVisSxnHKH4KSxlInxh8H99EY",{"id":8533,"title":8534,"author":8535,"body":8536,"date":9834,"description":9835,"draft":3196,"extension":3197,"howTo":9836,"image":3220,"meta":9855,"navigation":135,"path":9856,"seo":9857,"stem":9858,"tags":9859,"updated":3220,"__hash__":9861},"blog/blog/023.mix-two-fonts-in-paragraph.md","How do I mix two fonts in the same paragraph in gpdf?",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":8537,"toc":9824},[8538,8540,8543,8547,8561,8673,8676,8691,8695,9375,9390,9394,9412,9419,9431,9492,9502,9506,9509,9523,9718,9739,9743,9766,9768,9797,9799,9801,9813,9821],[41,8539,4879],{"id":4878},[14,8541,8542],{},"I have one paragraph — a sentence, a label, a table cell — and I want part of it in one font and part of it in another. A code snippet in monospace inside a line of Helvetica. A Japanese name in Noto Sans JP next to an ASCII order ID. How do I switch fonts mid-paragraph without breaking the text into separate blocks?",[41,8544,8546],{"id":8545},"the-quick-answer","The quick answer",[14,8548,8549,8552,8553,8556,8557,8560],{},[18,8550,8551],{},"c.Text"," is the wrong tool here. It applies one ",[18,8554,8555],{},"document.Style"," — one font family included — to the whole string. The tool you want is ",[18,8558,8559],{},"c.RichText",", where every span carries its own style:",[109,8562,8564],{"className":111,"code":8563,"language":113,"meta":114,"style":114},"c.RichText(func(rt *template.RichTextBuilder) {\n    rt.Span(\"Run \")\n    rt.Span(\"gofmt ./...\", template.FontFamily(\"Courier\"))\n    rt.Span(\" before you commit.\")\n})\n",[18,8565,8566,8593,8614,8650,8669],{"__ignoreMap":114},[118,8567,8568,8570,8572,8575,8577,8580,8582,8584,8586,8589,8591],{"class":120,"line":121},[118,8569,401],{"class":226},[118,8571,25],{"class":124},[118,8573,8574],{"class":213},"RichText",[118,8576,328],{"class":124},[118,8578,8579],{"class":331},"rt",[118,8581,334],{"class":124},[118,8583,337],{"class":128},[118,8585,25],{"class":124},[118,8587,8588],{"class":128},"RichTextBuilder",[118,8590,345],{"class":124},[118,8592,220],{"class":124},[118,8594,8595,8598,8600,8603,8605,8607,8610,8612],{"class":120,"line":132},[118,8596,8597],{"class":226},"    rt",[118,8599,25],{"class":124},[118,8601,8602],{"class":213},"Span",[118,8604,255],{"class":124},[118,8606,430],{"class":124},[118,8608,8609],{"class":433},"Run ",[118,8611,430],{"class":124},[118,8613,199],{"class":124},[118,8615,8616,8618,8620,8622,8624,8626,8629,8631,8633,8635,8637,8639,8641,8643,8646,8648],{"class":120,"line":139},[118,8617,8597],{"class":226},[118,8619,25],{"class":124},[118,8621,8602],{"class":213},[118,8623,255],{"class":124},[118,8625,430],{"class":124},[118,8627,8628],{"class":433},"gofmt ./...",[118,8630,430],{"class":124},[118,8632,395],{"class":124},[118,8634,233],{"class":226},[118,8636,25],{"class":124},[118,8638,2926],{"class":213},[118,8640,255],{"class":124},[118,8642,430],{"class":124},[118,8644,8645],{"class":433},"Courier",[118,8647,430],{"class":124},[118,8649,463],{"class":124},[118,8651,8652,8654,8656,8658,8660,8662,8665,8667],{"class":120,"line":149},[118,8653,8597],{"class":226},[118,8655,25],{"class":124},[118,8657,8602],{"class":213},[118,8659,255],{"class":124},[118,8661,430],{"class":124},[118,8663,8664],{"class":433}," before you commit.",[118,8666,430],{"class":124},[118,8668,199],{"class":124},[118,8670,8671],{"class":120,"line":161},[118,8672,1944],{"class":124},[14,8674,8675],{},"Three spans, two fonts, one paragraph. The layout engine line-breaks across the span boundaries the way a word processor would, so the monospace fragment flows inline with the Helvetica around it.",[14,8677,8678,8680,8681,8683,8684,54,8687,8690],{},[18,8679,8645],{}," works with no ",[18,8682,2798],{}," call because it's one of the PDF Standard 14 fonts — every viewer already has it, same as ",[18,8685,8686],{},"Helvetica",[18,8688,8689],{},"Times-Roman",". If your second font is a TrueType file you supply (a brand font, a CJK font), you register it once and refer to it by name. More on that below.",[41,8692,8694],{"id":8693},"working-code-helvetica-courier-no-font-files","Working code (Helvetica + Courier, no font files)",[109,8696,8698],{"className":111,"code":8697,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\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 := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.RichText(func(rt *template.RichTextBuilder) {\n                rt.Span(\"Run \")\n                rt.Span(\"gofmt ./...\", template.FontFamily(\"Courier\"))\n                rt.Span(\" before every commit. \")\n                rt.Span(\"It is not optional\", template.Bold(), template.Italic())\n                rt.Span(\".\")\n            })\n            c.RichText(func(rt *template.RichTextBuilder) {\n                rt.Span(\"The field is \")\n                rt.Span(\"created_at\", template.FontFamily(\"Courier\"), template.TextColor(pdf.RGBHex(0xB00020)))\n                rt.Span(\" — not \")\n                rt.Span(\"createdAt\", template.FontFamily(\"Courier\"))\n                rt.Span(\".\")\n            })\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"mixed-fonts.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,8699,8700,8706,8710,8716,8724,8732,8736,8744,8752,8760,8768,8772,8776,8786,8800,8818,8848,8852,8856,8870,8894,8924,8948,8967,9001,9020,9055,9073,9077,9101,9120,9176,9195,9230,9248,9252,9256,9260,9264,9282,9294,9308,9312,9353,9367,9371],{"__ignoreMap":114},[118,8701,8702,8704],{"class":120,"line":121},[118,8703,125],{"class":124},[118,8705,129],{"class":128},[118,8707,8708],{"class":120,"line":132},[118,8709,136],{"emptyLinePlaceholder":135},[118,8711,8712,8714],{"class":120,"line":139},[118,8713,143],{"class":142},[118,8715,146],{"class":124},[118,8717,8718,8720,8722],{"class":120,"line":149},[118,8719,152],{"class":124},[118,8721,5303],{"class":128},[118,8723,158],{"class":124},[118,8725,8726,8728,8730],{"class":120,"line":161},[118,8727,152],{"class":124},[118,8729,155],{"class":128},[118,8731,158],{"class":124},[118,8733,8734],{"class":120,"line":166},[118,8735,136],{"emptyLinePlaceholder":135},[118,8737,8738,8740,8742],{"class":120,"line":176},[118,8739,152],{"class":124},[118,8741,3203],{"class":128},[118,8743,158],{"class":124},[118,8745,8746,8748,8750],{"class":120,"line":186},[118,8747,152],{"class":124},[118,8749,171],{"class":128},[118,8751,158],{"class":124},[118,8753,8754,8756,8758],{"class":120,"line":196},[118,8755,152],{"class":124},[118,8757,181],{"class":128},[118,8759,158],{"class":124},[118,8761,8762,8764,8766],{"class":120,"line":202},[118,8763,152],{"class":124},[118,8765,191],{"class":128},[118,8767,158],{"class":124},[118,8769,8770],{"class":120,"line":207},[118,8771,199],{"class":124},[118,8773,8774],{"class":120,"line":223},[118,8775,136],{"emptyLinePlaceholder":135},[118,8777,8778,8780,8782,8784],{"class":120,"line":244},[118,8779,210],{"class":124},[118,8781,214],{"class":213},[118,8783,217],{"class":124},[118,8785,220],{"class":124},[118,8787,8788,8790,8792,8794,8796,8798],{"class":120,"line":269},[118,8789,227],{"class":226},[118,8791,230],{"class":124},[118,8793,3595],{"class":226},[118,8795,25],{"class":124},[118,8797,3600],{"class":213},[118,8799,241],{"class":124},[118,8801,8802,8804,8806,8808,8810,8812,8814,8816],{"class":120,"line":306},[118,8803,3607],{"class":226},[118,8805,25],{"class":124},[118,8807,252],{"class":213},[118,8809,255],{"class":124},[118,8811,1587],{"class":226},[118,8813,25],{"class":124},[118,8815,263],{"class":226},[118,8817,266],{"class":124},[118,8819,8820,8822,8824,8826,8828,8830,8832,8834,8836,8838,8840,8842,8844,8846],{"class":120,"line":312},[118,8821,3607],{"class":226},[118,8823,25],{"class":124},[118,8825,276],{"class":213},[118,8827,255],{"class":124},[118,8829,258],{"class":226},[118,8831,25],{"class":124},[118,8833,285],{"class":213},[118,8835,255],{"class":124},[118,8837,258],{"class":226},[118,8839,25],{"class":124},[118,8841,294],{"class":213},[118,8843,255],{"class":124},[118,8845,300],{"class":299},[118,8847,303],{"class":124},[118,8849,8850],{"class":120,"line":317},[118,8851,309],{"class":124},[118,8853,8854],{"class":120,"line":350},[118,8855,136],{"emptyLinePlaceholder":135},[118,8857,8858,8860,8862,8864,8866,8868],{"class":120,"line":379},[118,8859,5431],{"class":226},[118,8861,230],{"class":124},[118,8863,1185],{"class":226},[118,8865,25],{"class":124},[118,8867,1190],{"class":213},[118,8869,1193],{"class":124},[118,8871,8872,8874,8876,8878,8880,8882,8884,8886,8888,8890,8892],{"class":120,"line":417},[118,8873,5494],{"class":226},[118,8875,25],{"class":124},[118,8877,358],{"class":213},[118,8879,328],{"class":124},[118,8881,363],{"class":331},[118,8883,334],{"class":124},[118,8885,337],{"class":128},[118,8887,25],{"class":124},[118,8889,372],{"class":128},[118,8891,345],{"class":124},[118,8893,220],{"class":124},[118,8895,8896,8898,8900,8902,8904,8906,8908,8910,8912,8914,8916,8918,8920,8922],{"class":120,"line":466},[118,8897,1737],{"class":226},[118,8899,25],{"class":124},[118,8901,387],{"class":213},[118,8903,255],{"class":124},[118,8905,20],{"class":299},[118,8907,395],{"class":124},[118,8909,398],{"class":124},[118,8911,401],{"class":331},[118,8913,334],{"class":124},[118,8915,337],{"class":128},[118,8917,25],{"class":124},[118,8919,410],{"class":128},[118,8921,345],{"class":124},[118,8923,220],{"class":124},[118,8925,8926,8928,8930,8932,8934,8936,8938,8940,8942,8944,8946],{"class":120,"line":472},[118,8927,1768],{"class":226},[118,8929,25],{"class":124},[118,8931,8574],{"class":213},[118,8933,328],{"class":124},[118,8935,8579],{"class":331},[118,8937,334],{"class":124},[118,8939,337],{"class":128},[118,8941,25],{"class":124},[118,8943,8588],{"class":128},[118,8945,345],{"class":124},[118,8947,220],{"class":124},[118,8949,8950,8953,8955,8957,8959,8961,8963,8965],{"class":120,"line":503},[118,8951,8952],{"class":226},"                rt",[118,8954,25],{"class":124},[118,8956,8602],{"class":213},[118,8958,255],{"class":124},[118,8960,430],{"class":124},[118,8962,8609],{"class":433},[118,8964,430],{"class":124},[118,8966,199],{"class":124},[118,8968,8969,8971,8973,8975,8977,8979,8981,8983,8985,8987,8989,8991,8993,8995,8997,8999],{"class":120,"line":537},[118,8970,8952],{"class":226},[118,8972,25],{"class":124},[118,8974,8602],{"class":213},[118,8976,255],{"class":124},[118,8978,430],{"class":124},[118,8980,8628],{"class":433},[118,8982,430],{"class":124},[118,8984,395],{"class":124},[118,8986,233],{"class":226},[118,8988,25],{"class":124},[118,8990,2926],{"class":213},[118,8992,255],{"class":124},[118,8994,430],{"class":124},[118,8996,8645],{"class":433},[118,8998,430],{"class":124},[118,9000,463],{"class":124},[118,9002,9003,9005,9007,9009,9011,9013,9016,9018],{"class":120,"line":566},[118,9004,8952],{"class":226},[118,9006,25],{"class":124},[118,9008,8602],{"class":213},[118,9010,255],{"class":124},[118,9012,430],{"class":124},[118,9014,9015],{"class":433}," before every commit. ",[118,9017,430],{"class":124},[118,9019,199],{"class":124},[118,9021,9022,9024,9026,9028,9030,9032,9035,9037,9039,9041,9043,9045,9047,9049,9051,9053],{"class":120,"line":571},[118,9023,8952],{"class":226},[118,9025,25],{"class":124},[118,9027,8602],{"class":213},[118,9029,255],{"class":124},[118,9031,430],{"class":124},[118,9033,9034],{"class":433},"It is not optional",[118,9036,430],{"class":124},[118,9038,395],{"class":124},[118,9040,233],{"class":226},[118,9042,25],{"class":124},[118,9044,445],{"class":213},[118,9046,448],{"class":124},[118,9048,233],{"class":226},[118,9050,25],{"class":124},[118,9052,6483],{"class":213},[118,9054,1289],{"class":124},[118,9056,9057,9059,9061,9063,9065,9067,9069,9071],{"class":120,"line":577},[118,9058,8952],{"class":226},[118,9060,25],{"class":124},[118,9062,8602],{"class":213},[118,9064,255],{"class":124},[118,9066,430],{"class":124},[118,9068,25],{"class":433},[118,9070,430],{"class":124},[118,9072,199],{"class":124},[118,9074,9075],{"class":120,"line":602},[118,9076,469],{"class":124},[118,9078,9079,9081,9083,9085,9087,9089,9091,9093,9095,9097,9099],{"class":120,"line":633},[118,9080,1768],{"class":226},[118,9082,25],{"class":124},[118,9084,8574],{"class":213},[118,9086,328],{"class":124},[118,9088,8579],{"class":331},[118,9090,334],{"class":124},[118,9092,337],{"class":128},[118,9094,25],{"class":124},[118,9096,8588],{"class":128},[118,9098,345],{"class":124},[118,9100,220],{"class":124},[118,9102,9103,9105,9107,9109,9111,9113,9116,9118],{"class":120,"line":668},[118,9104,8952],{"class":226},[118,9106,25],{"class":124},[118,9108,8602],{"class":213},[118,9110,255],{"class":124},[118,9112,430],{"class":124},[118,9114,9115],{"class":433},"The field is ",[118,9117,430],{"class":124},[118,9119,199],{"class":124},[118,9121,9122,9124,9126,9128,9130,9132,9135,9137,9139,9141,9143,9145,9147,9149,9151,9153,9155,9157,9159,9161,9163,9165,9167,9169,9171,9174],{"class":120,"line":693},[118,9123,8952],{"class":226},[118,9125,25],{"class":124},[118,9127,8602],{"class":213},[118,9129,255],{"class":124},[118,9131,430],{"class":124},[118,9133,9134],{"class":433},"created_at",[118,9136,430],{"class":124},[118,9138,395],{"class":124},[118,9140,233],{"class":226},[118,9142,25],{"class":124},[118,9144,2926],{"class":213},[118,9146,255],{"class":124},[118,9148,430],{"class":124},[118,9150,8645],{"class":433},[118,9152,430],{"class":124},[118,9154,1280],{"class":124},[118,9156,233],{"class":226},[118,9158,25],{"class":124},[118,9160,545],{"class":213},[118,9162,255],{"class":124},[118,9164,550],{"class":226},[118,9166,25],{"class":124},[118,9168,658],{"class":213},[118,9170,255],{"class":124},[118,9172,9173],{"class":299},"0xB00020",[118,9175,563],{"class":124},[118,9177,9178,9180,9182,9184,9186,9188,9191,9193],{"class":120,"line":698},[118,9179,8952],{"class":226},[118,9181,25],{"class":124},[118,9183,8602],{"class":213},[118,9185,255],{"class":124},[118,9187,430],{"class":124},[118,9189,9190],{"class":433}," — not ",[118,9192,430],{"class":124},[118,9194,199],{"class":124},[118,9196,9197,9199,9201,9203,9205,9207,9210,9212,9214,9216,9218,9220,9222,9224,9226,9228],{"class":120,"line":703},[118,9198,8952],{"class":226},[118,9200,25],{"class":124},[118,9202,8602],{"class":213},[118,9204,255],{"class":124},[118,9206,430],{"class":124},[118,9208,9209],{"class":433},"createdAt",[118,9211,430],{"class":124},[118,9213,395],{"class":124},[118,9215,233],{"class":226},[118,9217,25],{"class":124},[118,9219,2926],{"class":213},[118,9221,255],{"class":124},[118,9223,430],{"class":124},[118,9225,8645],{"class":433},[118,9227,430],{"class":124},[118,9229,463],{"class":124},[118,9231,9232,9234,9236,9238,9240,9242,9244,9246],{"class":120,"line":709},[118,9233,8952],{"class":226},[118,9235,25],{"class":124},[118,9237,8602],{"class":213},[118,9239,255],{"class":124},[118,9241,430],{"class":124},[118,9243,25],{"class":433},[118,9245,430],{"class":124},[118,9247,199],{"class":124},[118,9249,9250],{"class":120,"line":714},[118,9251,469],{"class":124},[118,9253,9254],{"class":120,"line":740},[118,9255,574],{"class":124},[118,9257,9258],{"class":120,"line":765},[118,9259,706],{"class":124},[118,9261,9262],{"class":120,"line":796},[118,9263,136],{"emptyLinePlaceholder":135},[118,9265,9266,9268,9270,9272,9274,9276,9278,9280],{"class":120,"line":819},[118,9267,5787],{"class":226},[118,9269,395],{"class":124},[118,9271,1391],{"class":226},[118,9273,230],{"class":124},[118,9275,1185],{"class":226},[118,9277,25],{"class":124},[118,9279,1400],{"class":213},[118,9281,1193],{"class":124},[118,9283,9284,9286,9288,9290,9292],{"class":120,"line":851},[118,9285,1408],{"class":142},[118,9287,1391],{"class":226},[118,9289,1413],{"class":124},[118,9291,1416],{"class":124},[118,9293,220],{"class":124},[118,9295,9296,9298,9300,9302,9304,9306],{"class":120,"line":875},[118,9297,5818],{"class":226},[118,9299,25],{"class":124},[118,9301,5823],{"class":213},[118,9303,255],{"class":124},[118,9305,1429],{"class":226},[118,9307,199],{"class":124},[118,9309,9310],{"class":120,"line":880},[118,9311,1375],{"class":124},[118,9313,9314,9316,9318,9320,9322,9324,9326,9328,9330,9333,9335,9337,9339,9341,9343,9345,9347,9349,9351],{"class":120,"line":885},[118,9315,1408],{"class":142},[118,9317,1391],{"class":226},[118,9319,230],{"class":124},[118,9321,1447],{"class":226},[118,9323,25],{"class":124},[118,9325,1452],{"class":213},[118,9327,255],{"class":124},[118,9329,430],{"class":124},[118,9331,9332],{"class":433},"mixed-fonts.pdf",[118,9334,430],{"class":124},[118,9336,395],{"class":124},[118,9338,5859],{"class":226},[118,9340,395],{"class":124},[118,9342,1471],{"class":299},[118,9344,7902],{"class":124},[118,9346,1391],{"class":226},[118,9348,1413],{"class":124},[118,9350,1416],{"class":124},[118,9352,220],{"class":124},[118,9354,9355,9357,9359,9361,9363,9365],{"class":120,"line":910},[118,9356,5818],{"class":226},[118,9358,25],{"class":124},[118,9360,5823],{"class":213},[118,9362,255],{"class":124},[118,9364,1429],{"class":226},[118,9366,199],{"class":124},[118,9368,9369],{"class":120,"line":941},[118,9370,1375],{"class":124},[118,9372,9373],{"class":120,"line":974},[118,9374,1479],{"class":124},[14,9376,9377,9378,9380,9381,3096,9383,9386,9387,9389],{},"The body text stays in Helvetica (the default), the inline identifiers switch to Courier, and one span layers bold + italic on top of the default font. No ",[18,9379,2798],{},", no embedded font data — the PDF references ",[18,9382,8686],{},[18,9384,9385],{},"Helvetica-BoldOblique",", and ",[18,9388,8645],{}," as non-embedded Type 1 entries that every reader already has.",[41,9391,9393],{"id":9392},"what-richtext-does-with-the-spans","What RichText does with the spans",[14,9395,9396,9397,9400,9401,9404,9405,9407,9408,9411],{},"Each ",[18,9398,9399],{},"rt.Span"," becomes a ",[18,9402,9403],{},"document.RichTextFragment"," with its own copy of the style. A span you call with no options inherits the block style — which for ",[18,9406,8574],{}," is the column's default, i.e. the document's default font and size. A span you call with ",[18,9409,9410],{},"template.FontFamily(\"Courier\")"," gets exactly that field overridden and keeps everything else.",[14,9413,9414,9415,9418],{},"At layout time gpdf splits every fragment into word-level runs, measures each run with ",[1629,9416,9417],{},"that run's own font metrics"," — this is why a Courier word and a Helvetica word on the same line get the right widths — and then greedily packs runs into lines. All runs on a line share one baseline, so a 24 pt span sitting next to a 12 pt span lines up at the bottom and the line's height grows to fit the tall one.",[14,9420,9421,9422,6589,9424,9427,9428,25],{},"One distinction trips people up: the second argument to ",[18,9423,8559],{},[4744,9425,9426],{},"paragraph-level"," style, the per-span options are ",[4744,9429,9430],{},"fragment-level",[1516,9432,9433,9443],{},[1519,9434,9435],{},[1522,9436,9437,9440],{},[1525,9438,9439],{},"Option",[1525,9441,9442],{},"Where it belongs",[1532,9444,9445,9470],{},[1522,9446,9447,9465],{},[1537,9448,9449,3096,9451,3096,9453,3096,9455,3096,9457,3096,9459,3096,9462],{},[18,9450,2926],{},[18,9452,455],{},[18,9454,445],{},[18,9456,6483],{},[18,9458,545],{},[18,9460,9461],{},"Underline",[18,9463,9464],{},"Strikethrough",[1537,9466,9467,9468],{},"per-span — pass to each ",[18,9469,9399],{},[1522,9471,9472,9487],{},[1537,9473,9474,1592,9476,1592,9478,1592,9480,9483,9484],{},[18,9475,2264],{},[18,9477,2150],{},[18,9479,519],{},[18,9481,9482],{},"AlignJustify",", line height, ",[18,9485,9486],{},"TextIndent",[1537,9488,9489,9490],{},"paragraph-level — pass as the second arg to ",[18,9491,8559],{},[14,9493,9494,9495,9498,9499,9501],{},"Putting ",[18,9496,9497],{},"AlignRight()"," on an individual ",[18,9500,9399],{}," does nothing; alignment is a property of the line, not the fragment.",[41,9503,9505],{"id":9504},"the-real-case-a-latin-font-next-to-a-cjk-font","The real case: a Latin font next to a CJK font",[14,9507,9508],{},"Monospace-inside-a-sentence is the easy version. The one people actually fight with is mixing a Western font with a CJK font on one line — an English label and a Japanese value, a product code and a 商品名. Two things to know.",[14,9510,9511,9512,9515,9516,9518,9519,9522],{},"First, ",[1629,9513,9514],{},"gpdf does not pick a font by script",". If a span's family is ",[18,9517,8686],{}," and the text is ",[18,9520,9521],{},"日本語",", you get tofu boxes — Helvetica has no CJK glyphs, and gpdf will not silently reach for some other registered font to cover them. Put the CJK family on the CJK span yourself:",[109,9524,9526],{"className":111,"code":9525,"language":113,"meta":114,"style":114},"ttf, _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", ttf),\n)\n// ...\nc.RichText(func(rt *template.RichTextBuilder) {\n    rt.Span(\"Customer: \")                                 // default → Helvetica\n    rt.Span(\"山田 太郎\", template.FontFamily(\"NotoSansJP\")) // CJK → Noto Sans JP\n    rt.Span(\"  (ID 10293)\")                               // back to Helvetica\n})\n",[18,9527,9528,9557,9561,9575,9598,9602,9607,9631,9653,9692,9714],{"__ignoreMap":114},[118,9529,9530,9533,9535,9537,9539,9541,9543,9546,9548,9550,9553,9555],{"class":120,"line":121},[118,9531,9532],{"class":226},"ttf",[118,9534,395],{"class":124},[118,9536,3999],{"class":226},[118,9538,230],{"class":124},[118,9540,1447],{"class":226},[118,9542,25],{"class":124},[118,9544,9545],{"class":213},"ReadFile",[118,9547,255],{"class":124},[118,9549,430],{"class":124},[118,9551,9552],{"class":433},"NotoSansJP-Regular.ttf",[118,9554,430],{"class":124},[118,9556,199],{"class":124},[118,9558,9559],{"class":120,"line":132},[118,9560,136],{"emptyLinePlaceholder":135},[118,9562,9563,9565,9567,9569,9571,9573],{"class":120,"line":139},[118,9564,2760],{"class":226},[118,9566,230],{"class":124},[118,9568,3595],{"class":226},[118,9570,25],{"class":124},[118,9572,3600],{"class":213},[118,9574,241],{"class":124},[118,9576,9577,9579,9581,9583,9585,9587,9589,9591,9593,9596],{"class":120,"line":149},[118,9578,4532],{"class":226},[118,9580,25],{"class":124},[118,9582,2798],{"class":213},[118,9584,255],{"class":124},[118,9586,430],{"class":124},[118,9588,2805],{"class":433},[118,9590,430],{"class":124},[118,9592,395],{"class":124},[118,9594,9595],{"class":226}," ttf",[118,9597,266],{"class":124},[118,9599,9600],{"class":120,"line":161},[118,9601,199],{"class":124},[118,9603,9604],{"class":120,"line":166},[118,9605,9606],{"class":3981},"// ...\n",[118,9608,9609,9611,9613,9615,9617,9619,9621,9623,9625,9627,9629],{"class":120,"line":176},[118,9610,401],{"class":226},[118,9612,25],{"class":124},[118,9614,8574],{"class":213},[118,9616,328],{"class":124},[118,9618,8579],{"class":331},[118,9620,334],{"class":124},[118,9622,337],{"class":128},[118,9624,25],{"class":124},[118,9626,8588],{"class":128},[118,9628,345],{"class":124},[118,9630,220],{"class":124},[118,9632,9633,9635,9637,9639,9641,9643,9646,9648,9650],{"class":120,"line":186},[118,9634,8597],{"class":226},[118,9636,25],{"class":124},[118,9638,8602],{"class":213},[118,9640,255],{"class":124},[118,9642,430],{"class":124},[118,9644,9645],{"class":433},"Customer: ",[118,9647,430],{"class":124},[118,9649,345],{"class":124},[118,9651,9652],{"class":3981},"                                 // default → Helvetica\n",[118,9654,9655,9657,9659,9661,9663,9665,9668,9670,9672,9674,9676,9678,9680,9682,9684,9686,9689],{"class":120,"line":196},[118,9656,8597],{"class":226},[118,9658,25],{"class":124},[118,9660,8602],{"class":213},[118,9662,255],{"class":124},[118,9664,430],{"class":124},[118,9666,9667],{"class":433},"山田 太郎",[118,9669,430],{"class":124},[118,9671,395],{"class":124},[118,9673,233],{"class":226},[118,9675,25],{"class":124},[118,9677,2926],{"class":213},[118,9679,255],{"class":124},[118,9681,430],{"class":124},[118,9683,2805],{"class":433},[118,9685,430],{"class":124},[118,9687,9688],{"class":124},"))",[118,9690,9691],{"class":3981}," // CJK → Noto Sans JP\n",[118,9693,9694,9696,9698,9700,9702,9704,9707,9709,9711],{"class":120,"line":202},[118,9695,8597],{"class":226},[118,9697,25],{"class":124},[118,9699,8602],{"class":213},[118,9701,255],{"class":124},[118,9703,430],{"class":124},[118,9705,9706],{"class":433},"  (ID 10293)",[118,9708,430],{"class":124},[118,9710,345],{"class":124},[118,9712,9713],{"class":3981},"                               // back to Helvetica\n",[118,9715,9716],{"class":120,"line":207},[118,9717,1944],{"class":124},[14,9719,9720,9721,9724,9725,9728,9729,9731,9732,9734,9735,9738],{},"Second — and this is the part worth saying out loud — most Japanese CJK fonts already carry decent Latin glyphs. Noto Sans JP, IPAex, Source Han Sans: all of them draw ",[18,9722,9723],{},"ID 10293"," perfectly well. So before you reach for a per-span mix, ask whether you actually want two fonts or just got there by habit. If the whole document is Japanese-with-some-ASCII, the simplest thing is ",[18,9726,9727],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 11)"," and never mix at all. Reach for ",[18,9730,8574],{}," + ",[18,9733,2926],{}," when you genuinely want a ",[4744,9736,9737],{},"different look"," — a clean geometric Latin face for the numbers, a humanist CJK face for the prose — not just to make the script render.",[41,9740,9742],{"id":9741},"when-ctext-is-still-fine","When c.Text is still fine",[14,9744,9745,9746,9748,9749,9752,9753,9755,9756,9758,9759,9762,9763,9765],{},"If the whole string is one font, keep using ",[18,9747,8551],{}," — it's lighter and reads better. ",[18,9750,9751],{},"c.Text(\"発行日: 2026-05-11\", template.FontFamily(\"NotoSansJP\"))"," is one font for the whole line, and ",[18,9754,8551],{}," handles it. ",[18,9757,8574],{}," earns its keep only when the style changes ",[4744,9760,9761],{},"inside"," the string. Don't wrap a single-style line in a ",[18,9764,8574],{}," callback just because you can.",[41,9767,6894],{"id":6893},[46,9769,9770,9780,9787],{},[49,9771,9772,9776,9777,9779],{},[3163,9773,9775],{"href":9774},"/blog/bold-italic-together","How do I use bold and italic together in gpdf?"," — the same ",[18,9778,8574],{}," span mechanism, applied to weight and slant instead of family",[49,9781,9782,9786],{},[3163,9783,9785],{"href":9784},"/blog/add-custom-truetype-font","How do I add a custom TrueType font to gpdf?"," — registering the second font you want to mix in",[49,9788,9789,9793,9794,9796],{},[3163,9790,9792],{"href":9791},"/blog/embed-japanese-font","How do I embed a Japanese font in gpdf?"," — the ",[18,9795,2798],{}," walkthrough for the CJK side of a mixed-font line",[41,9798,4794],{"id":4793},[14,9800,6933],{},[109,9802,9803],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,9804,9805],{"__ignoreMap":114},[118,9806,9807,9809,9811],{"class":120,"line":121},[118,9808,113],{"class":128},[118,9810,3156],{"class":433},[118,9812,3159],{"class":433},[14,9814,9815,3169,9818],{},[3163,9816,3168],{"href":3165,"rel":9817},[3167],[3163,9819,3174],{"href":3172,"rel":9820},[3167],[3176,9822,9823],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":114,"searchDepth":132,"depth":132,"links":9825},[9826,9827,9828,9829,9830,9831,9832,9833],{"id":4878,"depth":132,"text":4879},{"id":8545,"depth":132,"text":8546},{"id":8693,"depth":132,"text":8694},{"id":9392,"depth":132,"text":9393},{"id":9504,"depth":132,"text":9505},{"id":9741,"depth":132,"text":9742},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-05-11","To mix fonts in one paragraph in gpdf, call c.RichText and set template.FontFamily on each span — c.Text only styles the whole string at once.",{"name":9837,"totalTime":6973,"tools":9838,"steps":9839},"Mix two font families inside one gpdf paragraph",[3202],[9840,9843,9846,9849,9852],{"name":9841,"text":9842},"Reach for c.RichText, not c.Text","c.Text applies one Style to the whole string. To change fonts mid-paragraph, call c.RichText(func(rt) { ... }) and add each piece as its own rt.Span.",{"name":9844,"text":9845},"Set template.FontFamily on the span you want different","Call rt.Span(\"gofmt ./...\", template.FontFamily(\"Courier\")). A span with no FontFamily inherits the document's default family.",{"name":9847,"text":9848},"Use Standard 14 names to skip font registration","Helvetica, Courier, and Times (with their Bold/Oblique variants) ship inside every PDF viewer, so mixing them needs no WithFont call.",{"name":9850,"text":9851},"Register a TTF first if the second font is custom or CJK","For a TrueType family, call gpdf.WithFont(\"NotoSansJP\", ttfBytes) once at construction, then reference \"NotoSansJP\" by name in a span. gpdf does not fall back by script.",{"name":9853,"text":9854},"Stack FontSize, Bold, and TextColor on the same span","Each span carries its own Style, so rt.Span(\"BIG\", template.FontSize(24)) sets size while its siblings keep theirs; the line height tracks the tallest span.",{},"/blog/mix-two-fonts-in-paragraph",{"title":8534,"description":9835},"blog/023.mix-two-fonts-in-paragraph",[6996,3226,9860],"cjk","qXmW_roA7-57forWRTCX1nEIlXi9NtmfjmvABl14brQ",{"id":9863,"title":8438,"author":9864,"body":9865,"date":12924,"description":12925,"draft":3196,"extension":3197,"howTo":12926,"image":3220,"meta":12949,"navigation":135,"path":8208,"seo":12950,"stem":12951,"tags":12952,"updated":3220,"__hash__":12953},"blog/blog/022.tables-in-go-pdfs.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":9866,"toc":12907},[9867,9869,9875,9993,10009,10012,10016,10025,10028,10059,10062,10064,10067,10114,10120,10217,10223,10227,10237,11154,11157,11171,11175,11185,11203,11209,11215,11253,11259,11290,11297,11301,11328,11338,11355,11358,11378,11388,11392,11395,11405,11429,11432,11452,11455,11462,11466,11471,11890,11925,11934,11942,11948,11952,11955,12016,12032,12038,12041,12235,12238,12242,12245,12381,12390,12401,12405,12408,12698,12701,12708,12712,12717,12741,12746,12770,12775,12778,12783,12796,12801,12807,12815,12822,12826,12833,12840,12843,12845,12847,12859,12867,12871,12904],[41,9868,44],{"id":43},[14,9870,9871,9874],{},[1629,9872,9873],{},"Tables are the part of PDF generation that wrecks weekends."," Column widths that don't add up, header rows that disappear on page 2, stripes drawn by a row loop with an off-by-one. gpdf collapses the whole thing into one call:",[109,9876,9878],{"className":111,"code":9877,"language":113,"meta":114,"style":114},"c.Table(header, rows,\n    template.ColumnWidths(40, 15, 20, 25),\n    template.TableHeaderStyle(template.TextColor(pdf.White), template.BgColor(brand)),\n    template.TableStripe(pdf.RGBHex(0xF5F5F5)),\n)\n",[18,9879,9880,9898,9927,9965,9989],{"__ignoreMap":114},[118,9881,9882,9884,9886,9888,9890,9892,9894,9896],{"class":120,"line":121},[118,9883,401],{"class":226},[118,9885,25],{"class":124},[118,9887,4929],{"class":213},[118,9889,255],{"class":124},[118,9891,3095],{"class":226},[118,9893,395],{"class":124},[118,9895,5118],{"class":226},[118,9897,2643],{"class":124},[118,9899,9900,9902,9904,9906,9908,9911,9913,9916,9918,9920,9922,9925],{"class":120,"line":132},[118,9901,2775],{"class":226},[118,9903,25],{"class":124},[118,9905,7733],{"class":213},[118,9907,255],{"class":124},[118,9909,9910],{"class":299},"40",[118,9912,395],{"class":124},[118,9914,9915],{"class":299}," 15",[118,9917,395],{"class":124},[118,9919,7742],{"class":299},[118,9921,395],{"class":124},[118,9923,9924],{"class":299}," 25",[118,9926,266],{"class":124},[118,9928,9929,9931,9933,9935,9937,9939,9941,9943,9945,9947,9949,9951,9953,9955,9957,9959,9961,9963],{"class":120,"line":139},[118,9930,2775],{"class":226},[118,9932,25],{"class":124},[118,9934,7762],{"class":213},[118,9936,255],{"class":124},[118,9938,337],{"class":226},[118,9940,25],{"class":124},[118,9942,545],{"class":213},[118,9944,255],{"class":124},[118,9946,550],{"class":226},[118,9948,25],{"class":124},[118,9950,7781],{"class":226},[118,9952,1280],{"class":124},[118,9954,233],{"class":226},[118,9956,25],{"class":124},[118,9958,7792],{"class":213},[118,9960,255],{"class":124},[118,9962,7797],{"class":226},[118,9964,3646],{"class":124},[118,9966,9967,9969,9971,9974,9976,9978,9980,9982,9984,9987],{"class":120,"line":149},[118,9968,2775],{"class":226},[118,9970,25],{"class":124},[118,9972,9973],{"class":213},"TableStripe",[118,9975,255],{"class":124},[118,9977,550],{"class":226},[118,9979,25],{"class":124},[118,9981,658],{"class":213},[118,9983,255],{"class":124},[118,9985,9986],{"class":299},"0xF5F5F5",[118,9988,3646],{"class":124},[118,9990,9991],{"class":120,"line":161},[118,9992,199],{"class":124},[14,9994,9995,9996,9999,10000,10002,10003,10005,10006,10008],{},"That handles widths, stripes, and ",[1629,9997,9998],{},"automatic header repeat on every page break",". No row loop. No ",[18,10001,8398],{}," option. The layout engine notices when the table won't fit and re-emits the ",[18,10004,325],{}," slice at the top of the next page. For colspan, rowspan, or a footer that repeats too, you drop one layer down to ",[18,10007,8442],{}," — same building blocks, more control.",[14,10010,10011],{},"This post is about why those are the three things that matter, what gpdf does for each, and where the abstraction stops on purpose.",[41,10013,10015],{"id":10014},"why-this-article-exists","Why this article exists",[14,10017,10018,10021,10022,10024],{},[3163,10019,1587],{"href":3165,"rel":10020},[3167]," is a Go library for generating PDFs. It's MIT, zero dependencies, and renders a single page in about 13 µs. The high-level table API is tiny — eight ",[18,10023,5132],{}," constructors — but the design pressure on it is enormous, because tables are where most PDF projects get stuck.",[14,10026,10027],{},"The three things that wreck a table in Go PDF land:",[1624,10029,10030,10043,10049],{},[49,10031,10032,10035,10036,54,10039,10042],{},[1629,10033,10034],{},"Column widths."," The web has CSS ",[18,10037,10038],{},"\u003Ccol>",[18,10040,10041],{},"colgroup",". PDF has nothing. You either compute every column width yourself in points, or you accept whatever the library gives you — usually equal splits.",[49,10044,10045,10048],{},[1629,10046,10047],{},"Striped rows."," You want every other body row tinted gray for readability. Most low-level libraries make you write the row loop and track parity yourself, which is the source of half the table-rendering bugs in any codebase.",[49,10050,10051,10054,10055,10058],{},[1629,10052,10053],{},"Page breaks."," A 200-row report doesn't fit on one A4 page. The library has to (a) split the body somewhere reasonable, (b) close the page, (c) start a new one, and (d) ",[1629,10056,10057],{},"repeat the header on the new page"," so the reader knows what column they're looking at. Forget any one of these and the table is unusable.",[14,10060,10061],{},"This post walks through how gpdf solves each, and what trade-offs the design makes. If you only want copy-paste recipes, the per-option recipes are linked at the bottom. This is the long form for people who want to know whether they can trust the API before they commit a ten-thousand-row monthly statement to it.",[41,10063,4953],{"id":4952},[14,10065,10066],{},"There's one entry point in the builder layer:",[109,10068,10070],{"className":111,"code":10069,"language":113,"meta":114,"style":114},"func (c *ColBuilder) Table(header []string, rows [][]string, opts ...TableOption)\n",[18,10071,10072],{"__ignoreMap":114},[118,10073,10074,10076,10078,10080,10082,10084,10086,10088,10090,10092,10094,10096,10098,10100,10102,10104,10106,10108,10110,10112],{"class":120,"line":121},[118,10075,210],{"class":124},[118,10077,4975],{"class":124},[118,10079,4978],{"class":331},[118,10081,4981],{"class":124},[118,10083,410],{"class":128},[118,10085,345],{"class":124},[118,10087,5105],{"class":213},[118,10089,255],{"class":124},[118,10091,3095],{"class":331},[118,10093,1127],{"class":124},[118,10095,1131],{"class":1130},[118,10097,395],{"class":124},[118,10099,5118],{"class":331},[118,10101,5121],{"class":124},[118,10103,1131],{"class":1130},[118,10105,395],{"class":124},[118,10107,5001],{"class":331},[118,10109,5004],{"class":124},[118,10111,5132],{"class":128},[118,10113,199],{"class":124},[14,10115,10116,10117,10119],{},"Header is a slice of strings, rows is a slice-of-slices of strings, and the variadic ",[18,10118,5156],{}," configure everything else. Eight option constructors exist:",[1516,10121,10122,10131],{},[1519,10123,10124],{},[1522,10125,10126,10128],{},[1525,10127,9439],{},[1525,10129,10130],{},"What it controls",[1532,10132,10133,10143,10153,10163,10173,10183,10193,10207],{},[1522,10134,10135,10140],{},[1537,10136,10137],{},[18,10138,10139],{},"ColumnWidths(...float64)",[1537,10141,10142],{},"Per-column widths as percentages of the parent Col",[1522,10144,10145,10150],{},[1537,10146,10147],{},[18,10148,10149],{},"TableHeaderStyle(...TextOption)",[1537,10151,10152],{},"Header background and text color",[1522,10154,10155,10160],{},[1537,10156,10157],{},[18,10158,10159],{},"TableStripe(pdf.Color)",[1537,10161,10162],{},"Background color for alternating body rows",[1522,10164,10165,10170],{},[1537,10166,10167],{},[18,10168,10169],{},"TableCellVAlign(document.VerticalAlign)",[1537,10171,10172],{},"Vertical alignment for body cells (top/middle/bottom)",[1522,10174,10175,10180],{},[1537,10176,10177],{},[18,10178,10179],{},"WithTableBorder(BorderSpec)",[1537,10181,10182],{},"Outer frame around the entire table",[1522,10184,10185,10190],{},[1537,10186,10187],{},[18,10188,10189],{},"WithTableCellBorder(BorderSpec)",[1537,10191,10192],{},"Same border around every cell — the grid look",[1522,10194,10195,10200],{},[1537,10196,10197],{},[18,10198,10199],{},"WithTableBorderCollapse(bool)",[1537,10201,10202,10203,10206],{},"CSS ",[18,10204,10205],{},"border-collapse: collapse"," semantics",[1522,10208,10209,10214],{},[1537,10210,10211],{},[18,10212,10213],{},"WithTableBackground(pdf.Color)",[1537,10215,10216],{},"Fill behind the entire table",[14,10218,10219,10220,10222],{},"That's the whole surface. Anything you can build in the builder you build with these eight. Anything beyond — colspan, rowspan, a footer, fixed-pt widths — is a ",[18,10221,8442],{}," call instead. We'll get there.",[41,10224,10226],{"id":10225},"working-code-a-six-month-invoice-ledger","Working code: a six-month invoice ledger",[14,10228,10229,10230,103,10232,7078,10234,25],{},"Here's a complete, runnable program that produces a multi-page striped invoice ledger. Save as ",[18,10231,102],{},[18,10233,7077],{},[18,10235,10236],{},"ledger.pdf",[109,10238,10240],{"className":111,"code":10239,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\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 := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    brand := pdf.RGBHex(0x1A237E)\n    stripe := pdf.RGBHex(0xF5F5F5)\n    hairline := template.Border(\n        template.BorderWidth(document.Pt(0.5)),\n        template.BorderColor(pdf.Gray(0.85)),\n    )\n\n    header := []string{\"Date\", \"Invoice #\", \"Customer\", \"Amount\"}\n    rows := make([][]string, 0, 120)\n    for i := 1; i \u003C= 120; i++ {\n        rows = append(rows, []string{\n            fmt.Sprintf(\"2026-%02d-%02d\", (i%6)+1, (i%28)+1),\n            fmt.Sprintf(\"INV-%05d\", 10000+i),\n            fmt.Sprintf(\"Customer #%d\", i),\n            fmt.Sprintf(\"$%d.00\", 100+i*7),\n        })\n    }\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"H1 2026 Ledger\", template.FontSize(18), template.Bold())\n            c.Spacer(document.Mm(4))\n\n            c.Table(header, rows,\n                template.ColumnWidths(20, 20, 40, 20),\n                template.TableHeaderStyle(\n                    template.TextColor(pdf.White),\n                    template.BgColor(brand),\n                ),\n                template.TableStripe(stripe),\n                template.WithTableCellBorder(hairline),\n            )\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"ledger.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,10241,10242,10248,10252,10258,10266,10274,10282,10286,10294,10302,10310,10318,10322,10326,10336,10350,10368,10398,10402,10406,10424,10443,10458,10480,10503,10507,10511,10555,10578,10604,10624,10676,10704,10728,10762,10766,10770,10774,10788,10812,10842,10881,10903,10907,10925,10951,10961,10979,10993,10997,11012,11028,11032,11036,11040,11044,11062,11074,11088,11092,11132,11146,11150],{"__ignoreMap":114},[118,10243,10244,10246],{"class":120,"line":121},[118,10245,125],{"class":124},[118,10247,129],{"class":128},[118,10249,10250],{"class":120,"line":132},[118,10251,136],{"emptyLinePlaceholder":135},[118,10253,10254,10256],{"class":120,"line":139},[118,10255,143],{"class":142},[118,10257,146],{"class":124},[118,10259,10260,10262,10264],{"class":120,"line":149},[118,10261,152],{"class":124},[118,10263,7108],{"class":128},[118,10265,158],{"class":124},[118,10267,10268,10270,10272],{"class":120,"line":161},[118,10269,152],{"class":124},[118,10271,5303],{"class":128},[118,10273,158],{"class":124},[118,10275,10276,10278,10280],{"class":120,"line":166},[118,10277,152],{"class":124},[118,10279,155],{"class":128},[118,10281,158],{"class":124},[118,10283,10284],{"class":120,"line":176},[118,10285,136],{"emptyLinePlaceholder":135},[118,10287,10288,10290,10292],{"class":120,"line":186},[118,10289,152],{"class":124},[118,10291,3203],{"class":128},[118,10293,158],{"class":124},[118,10295,10296,10298,10300],{"class":120,"line":196},[118,10297,152],{"class":124},[118,10299,171],{"class":128},[118,10301,158],{"class":124},[118,10303,10304,10306,10308],{"class":120,"line":202},[118,10305,152],{"class":124},[118,10307,181],{"class":128},[118,10309,158],{"class":124},[118,10311,10312,10314,10316],{"class":120,"line":207},[118,10313,152],{"class":124},[118,10315,191],{"class":128},[118,10317,158],{"class":124},[118,10319,10320],{"class":120,"line":223},[118,10321,199],{"class":124},[118,10323,10324],{"class":120,"line":244},[118,10325,136],{"emptyLinePlaceholder":135},[118,10327,10328,10330,10332,10334],{"class":120,"line":269},[118,10329,210],{"class":124},[118,10331,214],{"class":213},[118,10333,217],{"class":124},[118,10335,220],{"class":124},[118,10337,10338,10340,10342,10344,10346,10348],{"class":120,"line":306},[118,10339,227],{"class":226},[118,10341,230],{"class":124},[118,10343,3595],{"class":226},[118,10345,25],{"class":124},[118,10347,3600],{"class":213},[118,10349,241],{"class":124},[118,10351,10352,10354,10356,10358,10360,10362,10364,10366],{"class":120,"line":312},[118,10353,3607],{"class":226},[118,10355,25],{"class":124},[118,10357,252],{"class":213},[118,10359,255],{"class":124},[118,10361,1587],{"class":226},[118,10363,25],{"class":124},[118,10365,263],{"class":226},[118,10367,266],{"class":124},[118,10369,10370,10372,10374,10376,10378,10380,10382,10384,10386,10388,10390,10392,10394,10396],{"class":120,"line":317},[118,10371,3607],{"class":226},[118,10373,25],{"class":124},[118,10375,276],{"class":213},[118,10377,255],{"class":124},[118,10379,258],{"class":226},[118,10381,25],{"class":124},[118,10383,285],{"class":213},[118,10385,255],{"class":124},[118,10387,258],{"class":226},[118,10389,25],{"class":124},[118,10391,294],{"class":213},[118,10393,255],{"class":124},[118,10395,300],{"class":299},[118,10397,303],{"class":124},[118,10399,10400],{"class":120,"line":350},[118,10401,309],{"class":124},[118,10403,10404],{"class":120,"line":379},[118,10405,136],{"emptyLinePlaceholder":135},[118,10407,10408,10410,10412,10414,10416,10418,10420,10422],{"class":120,"line":417},[118,10409,7255],{"class":226},[118,10411,230],{"class":124},[118,10413,7260],{"class":226},[118,10415,25],{"class":124},[118,10417,658],{"class":213},[118,10419,255],{"class":124},[118,10421,7269],{"class":299},[118,10423,199],{"class":124},[118,10425,10426,10429,10431,10433,10435,10437,10439,10441],{"class":120,"line":466},[118,10427,10428],{"class":226},"    stripe ",[118,10430,230],{"class":124},[118,10432,7260],{"class":226},[118,10434,25],{"class":124},[118,10436,658],{"class":213},[118,10438,255],{"class":124},[118,10440,9986],{"class":299},[118,10442,199],{"class":124},[118,10444,10445,10448,10450,10452,10454,10456],{"class":120,"line":472},[118,10446,10447],{"class":226},"    hairline ",[118,10449,230],{"class":124},[118,10451,233],{"class":226},[118,10453,25],{"class":124},[118,10455,6075],{"class":213},[118,10457,241],{"class":124},[118,10459,10460,10462,10464,10466,10468,10470,10472,10474,10476,10478],{"class":120,"line":503},[118,10461,247],{"class":226},[118,10463,25],{"class":124},[118,10465,6086],{"class":213},[118,10467,255],{"class":124},[118,10469,258],{"class":226},[118,10471,25],{"class":124},[118,10473,6095],{"class":213},[118,10475,255],{"class":124},[118,10477,560],{"class":299},[118,10479,3646],{"class":124},[118,10481,10482,10484,10486,10488,10490,10492,10494,10496,10498,10501],{"class":120,"line":537},[118,10483,247],{"class":226},[118,10485,25],{"class":124},[118,10487,6110],{"class":213},[118,10489,255],{"class":124},[118,10491,550],{"class":226},[118,10493,25],{"class":124},[118,10495,555],{"class":213},[118,10497,255],{"class":124},[118,10499,10500],{"class":299},"0.85",[118,10502,3646],{"class":124},[118,10504,10505],{"class":120,"line":566},[118,10506,309],{"class":124},[118,10508,10509],{"class":120,"line":571},[118,10510,136],{"emptyLinePlaceholder":135},[118,10512,10513,10515,10517,10519,10521,10523,10525,10527,10529,10531,10533,10535,10537,10539,10541,10543,10545,10547,10549,10551,10553],{"class":120,"line":577},[118,10514,7280],{"class":226},[118,10516,230],{"class":124},[118,10518,1127],{"class":124},[118,10520,1131],{"class":1130},[118,10522,1134],{"class":124},[118,10524,430],{"class":124},[118,10526,7293],{"class":433},[118,10528,430],{"class":124},[118,10530,395],{"class":124},[118,10532,1146],{"class":124},[118,10534,7302],{"class":433},[118,10536,430],{"class":124},[118,10538,395],{"class":124},[118,10540,1146],{"class":124},[118,10542,7311],{"class":433},[118,10544,430],{"class":124},[118,10546,395],{"class":124},[118,10548,1146],{"class":124},[118,10550,7320],{"class":433},[118,10552,430],{"class":124},[118,10554,1479],{"class":124},[118,10556,10557,10559,10561,10563,10565,10567,10569,10571,10573,10576],{"class":120,"line":602},[118,10558,7329],{"class":226},[118,10560,230],{"class":124},[118,10562,7334],{"class":213},[118,10564,7337],{"class":124},[118,10566,1131],{"class":1130},[118,10568,395],{"class":124},[118,10570,7344],{"class":299},[118,10572,395],{"class":124},[118,10574,10575],{"class":299}," 120",[118,10577,199],{"class":124},[118,10579,10580,10582,10584,10586,10588,10590,10592,10594,10596,10598,10600,10602],{"class":120,"line":633},[118,10581,1111],{"class":142},[118,10583,7358],{"class":226},[118,10585,230],{"class":124},[118,10587,7363],{"class":299},[118,10589,7366],{"class":124},[118,10591,7358],{"class":226},[118,10593,7371],{"class":124},[118,10595,10575],{"class":299},[118,10597,7366],{"class":124},[118,10599,1114],{"class":226},[118,10601,7380],{"class":124},[118,10603,220],{"class":124},[118,10605,10606,10608,10610,10612,10614,10616,10618,10620,10622],{"class":120,"line":668},[118,10607,7387],{"class":226},[118,10609,1366],{"class":124},[118,10611,7392],{"class":213},[118,10613,255],{"class":124},[118,10615,7397],{"class":226},[118,10617,395],{"class":124},[118,10619,1127],{"class":124},[118,10621,1131],{"class":1130},[118,10623,7406],{"class":124},[118,10625,10626,10628,10630,10632,10634,10636,10638,10640,10642,10644,10646,10648,10650,10652,10654,10656,10658,10660,10662,10664,10666,10668,10670,10672,10674],{"class":120,"line":693},[118,10627,7411],{"class":226},[118,10629,25],{"class":124},[118,10631,7416],{"class":213},[118,10633,255],{"class":124},[118,10635,430],{"class":124},[118,10637,7423],{"class":433},[118,10639,7427],{"class":7426},[118,10641,7430],{"class":433},[118,10643,7427],{"class":7426},[118,10645,430],{"class":124},[118,10647,395],{"class":124},[118,10649,4975],{"class":124},[118,10651,7441],{"class":226},[118,10653,7444],{"class":124},[118,10655,392],{"class":299},[118,10657,7449],{"class":124},[118,10659,2402],{"class":299},[118,10661,395],{"class":124},[118,10663,4975],{"class":124},[118,10665,7441],{"class":226},[118,10667,7444],{"class":124},[118,10669,7462],{"class":299},[118,10671,7449],{"class":124},[118,10673,2402],{"class":299},[118,10675,266],{"class":124},[118,10677,10678,10680,10682,10684,10686,10688,10690,10692,10694,10696,10698,10700,10702],{"class":120,"line":698},[118,10679,7411],{"class":226},[118,10681,25],{"class":124},[118,10683,7416],{"class":213},[118,10685,255],{"class":124},[118,10687,430],{"class":124},[118,10689,7483],{"class":433},[118,10691,7486],{"class":7426},[118,10693,430],{"class":124},[118,10695,395],{"class":124},[118,10697,7493],{"class":299},[118,10699,1339],{"class":124},[118,10701,7441],{"class":226},[118,10703,266],{"class":124},[118,10705,10706,10708,10710,10712,10714,10716,10718,10720,10722,10724,10726],{"class":120,"line":703},[118,10707,7411],{"class":226},[118,10709,25],{"class":124},[118,10711,7416],{"class":213},[118,10713,255],{"class":124},[118,10715,430],{"class":124},[118,10717,7514],{"class":433},[118,10719,2323],{"class":7426},[118,10721,430],{"class":124},[118,10723,395],{"class":124},[118,10725,1114],{"class":226},[118,10727,266],{"class":124},[118,10729,10730,10732,10734,10736,10738,10740,10742,10744,10746,10748,10750,10752,10754,10756,10758,10760],{"class":120,"line":709},[118,10731,7411],{"class":226},[118,10733,25],{"class":124},[118,10735,7416],{"class":213},[118,10737,255],{"class":124},[118,10739,430],{"class":124},[118,10741,7539],{"class":433},[118,10743,2323],{"class":7426},[118,10745,7544],{"class":433},[118,10747,430],{"class":124},[118,10749,395],{"class":124},[118,10751,6650],{"class":299},[118,10753,1339],{"class":124},[118,10755,7441],{"class":226},[118,10757,4981],{"class":124},[118,10759,7559],{"class":299},[118,10761,266],{"class":124},[118,10763,10764],{"class":120,"line":714},[118,10765,574],{"class":124},[118,10767,10768],{"class":120,"line":740},[118,10769,1375],{"class":124},[118,10771,10772],{"class":120,"line":765},[118,10773,136],{"emptyLinePlaceholder":135},[118,10775,10776,10778,10780,10782,10784,10786],{"class":120,"line":796},[118,10777,5431],{"class":226},[118,10779,230],{"class":124},[118,10781,1185],{"class":226},[118,10783,25],{"class":124},[118,10785,1190],{"class":213},[118,10787,1193],{"class":124},[118,10789,10790,10792,10794,10796,10798,10800,10802,10804,10806,10808,10810],{"class":120,"line":819},[118,10791,5494],{"class":226},[118,10793,25],{"class":124},[118,10795,358],{"class":213},[118,10797,328],{"class":124},[118,10799,363],{"class":331},[118,10801,334],{"class":124},[118,10803,337],{"class":128},[118,10805,25],{"class":124},[118,10807,372],{"class":128},[118,10809,345],{"class":124},[118,10811,220],{"class":124},[118,10813,10814,10816,10818,10820,10822,10824,10826,10828,10830,10832,10834,10836,10838,10840],{"class":120,"line":851},[118,10815,1737],{"class":226},[118,10817,25],{"class":124},[118,10819,387],{"class":213},[118,10821,255],{"class":124},[118,10823,20],{"class":299},[118,10825,395],{"class":124},[118,10827,398],{"class":124},[118,10829,401],{"class":331},[118,10831,334],{"class":124},[118,10833,337],{"class":128},[118,10835,25],{"class":124},[118,10837,410],{"class":128},[118,10839,345],{"class":124},[118,10841,220],{"class":124},[118,10843,10844,10846,10848,10850,10852,10854,10857,10859,10861,10863,10865,10867,10869,10871,10873,10875,10877,10879],{"class":120,"line":875},[118,10845,1768],{"class":226},[118,10847,25],{"class":124},[118,10849,425],{"class":213},[118,10851,255],{"class":124},[118,10853,430],{"class":124},[118,10855,10856],{"class":433},"H1 2026 Ledger",[118,10858,430],{"class":124},[118,10860,395],{"class":124},[118,10862,233],{"class":226},[118,10864,25],{"class":124},[118,10866,455],{"class":213},[118,10868,255],{"class":124},[118,10870,1277],{"class":299},[118,10872,1280],{"class":124},[118,10874,233],{"class":226},[118,10876,25],{"class":124},[118,10878,445],{"class":213},[118,10880,1289],{"class":124},[118,10882,10883,10885,10887,10889,10891,10893,10895,10897,10899,10901],{"class":120,"line":880},[118,10884,1768],{"class":226},[118,10886,25],{"class":124},[118,10888,675],{"class":213},[118,10890,255],{"class":124},[118,10892,258],{"class":226},[118,10894,25],{"class":124},[118,10896,294],{"class":213},[118,10898,255],{"class":124},[118,10900,1493],{"class":299},[118,10902,463],{"class":124},[118,10904,10905],{"class":120,"line":885},[118,10906,136],{"emptyLinePlaceholder":135},[118,10908,10909,10911,10913,10915,10917,10919,10921,10923],{"class":120,"line":910},[118,10910,1768],{"class":226},[118,10912,25],{"class":124},[118,10914,4929],{"class":213},[118,10916,255],{"class":124},[118,10918,3095],{"class":226},[118,10920,395],{"class":124},[118,10922,5118],{"class":226},[118,10924,2643],{"class":124},[118,10926,10927,10929,10931,10933,10935,10937,10939,10941,10943,10945,10947,10949],{"class":120,"line":941},[118,10928,2648],{"class":226},[118,10930,25],{"class":124},[118,10932,7733],{"class":213},[118,10934,255],{"class":124},[118,10936,300],{"class":299},[118,10938,395],{"class":124},[118,10940,7742],{"class":299},[118,10942,395],{"class":124},[118,10944,7747],{"class":299},[118,10946,395],{"class":124},[118,10948,7742],{"class":299},[118,10950,266],{"class":124},[118,10952,10953,10955,10957,10959],{"class":120,"line":974},[118,10954,2648],{"class":226},[118,10956,25],{"class":124},[118,10958,7762],{"class":213},[118,10960,241],{"class":124},[118,10962,10963,10965,10967,10969,10971,10973,10975,10977],{"class":120,"line":997},[118,10964,540],{"class":226},[118,10966,25],{"class":124},[118,10968,545],{"class":213},[118,10970,255],{"class":124},[118,10972,550],{"class":226},[118,10974,25],{"class":124},[118,10976,7781],{"class":226},[118,10978,266],{"class":124},[118,10980,10981,10983,10985,10987,10989,10991],{"class":120,"line":1002},[118,10982,540],{"class":226},[118,10984,25],{"class":124},[118,10986,7792],{"class":213},[118,10988,255],{"class":124},[118,10990,7797],{"class":226},[118,10992,266],{"class":124},[118,10994,10995],{"class":120,"line":1033},[118,10996,7804],{"class":124},[118,10998,10999,11001,11003,11005,11007,11010],{"class":120,"line":1065},[118,11000,2648],{"class":226},[118,11002,25],{"class":124},[118,11004,9973],{"class":213},[118,11006,255],{"class":124},[118,11008,11009],{"class":226},"stripe",[118,11011,266],{"class":124},[118,11013,11014,11016,11018,11021,11023,11026],{"class":120,"line":1088},[118,11015,2648],{"class":226},[118,11017,25],{"class":124},[118,11019,11020],{"class":213},"WithTableCellBorder",[118,11022,255],{"class":124},[118,11024,11025],{"class":226},"hairline",[118,11027,266],{"class":124},[118,11029,11030],{"class":120,"line":1093},[118,11031,7809],{"class":124},[118,11033,11034],{"class":120,"line":1098},[118,11035,574],{"class":124},[118,11037,11038],{"class":120,"line":1103},[118,11039,706],{"class":124},[118,11041,11042],{"class":120,"line":1108},[118,11043,136],{"emptyLinePlaceholder":135},[118,11045,11046,11048,11050,11052,11054,11056,11058,11060],{"class":120,"line":1177},[118,11047,5787],{"class":226},[118,11049,395],{"class":124},[118,11051,1391],{"class":226},[118,11053,230],{"class":124},[118,11055,1185],{"class":226},[118,11057,25],{"class":124},[118,11059,1400],{"class":213},[118,11061,1193],{"class":124},[118,11063,11064,11066,11068,11070,11072],{"class":120,"line":1196},[118,11065,1408],{"class":142},[118,11067,1391],{"class":226},[118,11069,1413],{"class":124},[118,11071,1416],{"class":124},[118,11073,220],{"class":124},[118,11075,11076,11078,11080,11082,11084,11086],{"class":120,"line":1222},[118,11077,5818],{"class":226},[118,11079,25],{"class":124},[118,11081,5823],{"class":213},[118,11083,255],{"class":124},[118,11085,1429],{"class":226},[118,11087,199],{"class":124},[118,11089,11090],{"class":120,"line":1253},[118,11091,1375],{"class":124},[118,11093,11094,11096,11098,11100,11102,11104,11106,11108,11110,11112,11114,11116,11118,11120,11122,11124,11126,11128,11130],{"class":120,"line":1292},[118,11095,1408],{"class":142},[118,11097,1391],{"class":226},[118,11099,230],{"class":124},[118,11101,1447],{"class":226},[118,11103,25],{"class":124},[118,11105,1452],{"class":213},[118,11107,255],{"class":124},[118,11109,430],{"class":124},[118,11111,10236],{"class":433},[118,11113,430],{"class":124},[118,11115,395],{"class":124},[118,11117,5859],{"class":226},[118,11119,395],{"class":124},[118,11121,1471],{"class":299},[118,11123,7902],{"class":124},[118,11125,1391],{"class":226},[118,11127,1413],{"class":124},[118,11129,1416],{"class":124},[118,11131,220],{"class":124},[118,11133,11134,11136,11138,11140,11142,11144],{"class":120,"line":1316},[118,11135,5818],{"class":226},[118,11137,25],{"class":124},[118,11139,5823],{"class":213},[118,11141,255],{"class":124},[118,11143,1429],{"class":226},[118,11145,199],{"class":124},[118,11147,11148],{"class":120,"line":1350},[118,11149,1375],{"class":124},[118,11151,11152],{"class":120,"line":1355},[118,11153,1479],{"class":124},[14,11155,11156],{},"120 rows on A4 spills onto roughly five pages. On every page the dark-blue header reappears at the top, the body picks up where it left off, the alternating gray stripe stays consistent across the page break. You don't have to touch any of that.",[14,11158,11159,11160,11163,11164,11167,11168,11170],{},"The thing to look at in this snippet is what's ",[4744,11161,11162],{},"missing",": there is no row loop, no page-counter, no manual ",[18,11165,11166],{},"if i == lastRowOnPage"," check, no ",[18,11169,7060],{}," call, no header re-render. The four lines of options say what the table looks like; the engine handles when and where to split.",[41,11172,11174],{"id":11173},"column-widths-what-the-percentages-actually-mean","Column widths: what the percentages actually mean",[14,11176,11177,11180,11181,11184],{},[18,11178,11179],{},"ColumnWidths(40, 15, 20, 25)"," looks like CSS ",[18,11182,11183],{},"\u003Ccol width=\"40%\">",". It nearly is, with three sharp edges worth knowing.",[14,11186,11187,11190,11191,11194,11195,11198,11199,11202],{},[1629,11188,11189],{},"The percentage is of the parent Col, not the page."," A ",[18,11192,11193],{},"r.Col(6, ...)"," claims half the row's content width. A table inside that Col with ",[18,11196,11197],{},"ColumnWidths(50, 50)"," produces two columns each at 25% of the ",[4744,11200,11201],{},"row"," width, not 50%. The percentages are local to wherever the table lives. This matters when you move a table from a full-width row into a side-by-side layout — the option call doesn't change.",[14,11204,11205,11208],{},[1629,11206,11207],{},"No normalization."," If your widths sum to 90, you get 10% empty space on the right. If they sum to 110, the rightmost column overflows the parent and bleeds into wherever the page lets it. gpdf trusts your arithmetic. There's no warning, and there shouldn't be — auto-correcting the values you wrote is worse than the bug.",[14,11210,11211,11214],{},[1629,11212,11213],{},"Trailing missing values auto-distribute."," Pass fewer widths than you have columns and the remaining columns split the leftover equally:",[109,11216,11218],{"className":111,"code":11217,"language":113,"meta":114,"style":114},"// Five-column table, three widths given.\ntemplate.ColumnWidths(40, 10, 20)\n// → 40% / 10% / 20% / 15% / 15%   (30% split between the trailing two)\n",[18,11219,11220,11225,11248],{"__ignoreMap":114},[118,11221,11222],{"class":120,"line":121},[118,11223,11224],{"class":3981},"// Five-column table, three widths given.\n",[118,11226,11227,11229,11231,11233,11235,11237,11239,11242,11244,11246],{"class":120,"line":132},[118,11228,337],{"class":226},[118,11230,25],{"class":124},[118,11232,7733],{"class":213},[118,11234,255],{"class":124},[118,11236,9910],{"class":299},[118,11238,395],{"class":124},[118,11240,11241],{"class":299}," 10",[118,11243,395],{"class":124},[118,11245,7742],{"class":299},[118,11247,199],{"class":124},[118,11249,11250],{"class":120,"line":139},[118,11251,11252],{"class":3981},"// → 40% / 10% / 20% / 15% / 15%   (30% split between the trailing two)\n",[14,11254,11255,11256,11258],{},"That's a useful trick for \"I care about these specific columns; let the rest sort themselves out.\" Pass ",[18,11257,4159],{}," explicitly for a column to mark it as auto:",[109,11260,11262],{"className":111,"code":11261,"language":113,"meta":114,"style":114},"template.ColumnWidths(0, 30, 30) // → 40% / 30% / 30% on a three-column table\n",[18,11263,11264],{"__ignoreMap":114},[118,11265,11266,11268,11270,11272,11274,11276,11278,11281,11283,11285,11287],{"class":120,"line":121},[118,11267,337],{"class":226},[118,11269,25],{"class":124},[118,11271,7733],{"class":213},[118,11273,255],{"class":124},[118,11275,4159],{"class":299},[118,11277,395],{"class":124},[118,11279,11280],{"class":299}," 30",[118,11282,395],{"class":124},[118,11284,11280],{"class":299},[118,11286,345],{"class":124},[118,11288,11289],{"class":3981}," // → 40% / 30% / 30% on a three-column table\n",[14,11291,11292,11293,11296],{},"For a deep dive on the widths-and-percentages corner cases, see ",[3163,11294,11295],{"href":8448},"the column widths recipe",". The summary: percentages cover 95% of layouts, and when they don't, you drop one layer down — described later in this post.",[41,11298,11300],{"id":11299},"stripes-the-row-loop-you-dont-write","Stripes: the row loop you don't write",[109,11302,11304],{"className":111,"code":11303,"language":113,"meta":114,"style":114},"template.TableStripe(pdf.RGBHex(0xF5F5F5))\n",[18,11305,11306],{"__ignoreMap":114},[118,11307,11308,11310,11312,11314,11316,11318,11320,11322,11324,11326],{"class":120,"line":121},[118,11309,337],{"class":226},[118,11311,25],{"class":124},[118,11313,9973],{"class":213},[118,11315,255],{"class":124},[118,11317,550],{"class":226},[118,11319,25],{"class":124},[118,11321,658],{"class":213},[118,11323,255],{"class":124},[118,11325,9986],{"class":299},[118,11327,463],{"class":124},[14,11329,11330,11331,11333,11334,11337],{},"That's it. gpdf walks the body rows with index ",[18,11332,7441],{}," from 0 and tints rows where ",[18,11335,11336],{},"i % 2 == 1",". The header is its own slice and isn't counted, so the first body row sits clean and the second is shaded — the Bootstrap convention.",[14,11339,11340,11341,54,11343,11346,11347,11350,11351,11354],{},"Why this option exists at all: in ",[18,11342,1539],{},[18,11344,11345],{},"gopdf",", you write the loop yourself, set ",[18,11348,11349],{},"SetFillColor"," per row, and call ",[18,11352,11353],{},"CellFormat"," with the fill flag. It's eight or ten lines of code and the off-by-one error rate is high enough that StackOverflow has a dedicated set of answers for it. Pulling that into one option means the bug class disappears.",[14,11356,11357],{},"The constraints are deliberate:",[46,11359,11360,11366,11372],{},[49,11361,11362,11365],{},[1629,11363,11364],{},"One stripe color, not two."," No \"alternate between blue and gray.\" The page is already white, so the no-stripe row is automatically white. Asking for a third color cycle is asking the reader to think harder, and zebra striping is supposed to do the opposite.",[49,11367,11368,11371],{},[1629,11369,11370],{},"No way to flip parity."," First body row is always plain, second is always tinted. If you really want it inverted, prepend a blank row to your data. You don't, because nobody actually wants that.",[49,11373,11374,11377],{},[1629,11375,11376],{},"Stripes cross page breaks correctly."," Body row 14 stays parity-14 even when it lands on page 2. The engine carries the index across the split.",[14,11379,11380,11381,11384,11385,11387],{},"For color choice and the dark-theme variant, the ",[3163,11382,11383],{"href":8458},"zebra stripes recipe"," has the palette discussion. For this post, the point is that a property of the ",[4744,11386,1516],{}," (the alternation) is configured at the table call, not at the row level.",[41,11389,11391],{"id":11390},"page-breaks-the-part-thats-actually-hard","Page breaks: the part that's actually hard",[14,11393,11394],{},"This is where most Go PDF stories fall apart, and it's also the place where gpdf's design pays off most.",[14,11396,11397,11398,11404],{},"The simple version: ",[1629,11399,11400,11401,11403],{},"write a table with more rows than fit on one page, and gpdf paginates it for you. The ",[18,11402,325],{}," slice is repeated at the top of every continuation page."," No options to enable it. No method to call. It's the default behavior of the layout engine.",[14,11406,11407,11408,11411,11412,11415,11416,11418,11419,11421,11422,11424,11425,11428],{},"The real version is more interesting. The block layout engine (",[18,11409,11410],{},"document/layout/block.go",") lays out the table with the available height. When the body doesn't fit, the result includes an ",[18,11413,11414],{},"Overflow"," field — a new ",[18,11417,7953],{}," with the same ",[18,11420,325],{},", the same ",[18,11423,721],{},", and the ",[4744,11426,11427],{},"remaining"," body rows. The page system flushes the laid-out portion to the current page, opens the next page, and feeds the overflow table back into the layout engine with the new page's available height. Repeat until the overflow is empty.",[14,11430,11431],{},"Two consequences fall out of this design:",[1624,11433,11434,11446],{},[49,11435,11436,11442,11443,11445],{},[1629,11437,11438,11439,11441],{},"The header lives in ",[18,11440,7978],{},", not in the loop."," Because the overflow table reuses the same ",[18,11444,325],{}," slice, the header repeats automatically on every continuation page. Same styling, same column widths, same everything.",[49,11447,11448,11451],{},[1629,11449,11450],{},"There's no \"header doesn't fit on this page\" edge case to think about."," The layout engine reserves space for the header before measuring how many body rows fit. If the page can't hold the header plus at least one body row, the entire table is pushed to the next page.",[14,11453,11454],{},"Footers — when you use them at the document layer — work the same way: they're carried on every continuation page automatically.",[14,11456,11457,11458,11461],{},"The pieces you don't get: a \"keep this row group together\" annotation, page break suppression on a specific row, or \"start this table on a fresh page.\" The first two are TODOs. The third you do at the page level — ",[18,11459,11460],{},"doc.AddPage()"," before the row that contains the table.",[41,11463,11465],{"id":11464},"when-youve-outgrown-the-builder-api","When you've outgrown the builder API",[14,11467,11468,11469,25],{},"The builder is good for the cases that are common. When you need cell spanning, fixed-point widths, a footer that repeats, or anything that mixes content types per cell, you drop to ",[18,11470,8442],{},[109,11472,11474],{"className":111,"code":11473,"language":113,"meta":114,"style":114},"import (\n    \"github.com/gpdf-dev/gpdf/document\"\n)\n\nfooter := document.TableRow{\n    Cells: []document.TableCell{\n        {\n            Content: []document.DocumentNode{\n                &document.Text{Content: \"Total\", TextStyle: document.DefaultStyle()},\n            },\n            ColSpan: 3, // ← span the first three columns\n            RowSpan: 1,\n        },\n        {\n            Content: []document.DocumentNode{\n                &document.Text{Content: \"$48,720.00\", TextStyle: document.DefaultStyle()},\n            },\n            ColSpan: 1,\n            RowSpan: 1,\n        },\n    },\n}\n\ntbl := &document.Table{\n    Columns: []document.TableColumn{\n        {Width: document.Pct(20)},\n        {Width: document.Pct(20)},\n        {Width: document.Auto},\n        {Width: document.Pt(80)}, // fixed 80pt regardless of page width\n    },\n    Header: /* ... */,\n    Body:   /* ... */,\n    Footer: []document.TableRow{footer},\n}\n",[18,11475,11476,11482,11490,11494,11498,11513,11531,11536,11554,11596,11601,11616,11627,11631,11635,11651,11688,11692,11702,11712,11716,11720,11724,11728,11744,11760,11780,11800,11816,11840,11844,11855,11866,11886],{"__ignoreMap":114},[118,11477,11478,11480],{"class":120,"line":121},[118,11479,143],{"class":142},[118,11481,146],{"class":124},[118,11483,11484,11486,11488],{"class":120,"line":132},[118,11485,152],{"class":124},[118,11487,171],{"class":128},[118,11489,158],{"class":124},[118,11491,11492],{"class":120,"line":139},[118,11493,199],{"class":124},[118,11495,11496],{"class":120,"line":149},[118,11497,136],{"emptyLinePlaceholder":135},[118,11499,11500,11503,11505,11507,11509,11511],{"class":120,"line":161},[118,11501,11502],{"class":226},"footer ",[118,11504,230],{"class":124},[118,11506,5225],{"class":128},[118,11508,25],{"class":124},[118,11510,8183],{"class":128},[118,11512,7406],{"class":124},[118,11514,11515,11518,11520,11522,11524,11526,11529],{"class":120,"line":166},[118,11516,11517],{"class":226},"    Cells",[118,11519,6349],{"class":124},[118,11521,1127],{"class":124},[118,11523,258],{"class":128},[118,11525,25],{"class":124},[118,11527,11528],{"class":128},"TableCell",[118,11530,7406],{"class":124},[118,11532,11533],{"class":120,"line":176},[118,11534,11535],{"class":124},"        {\n",[118,11537,11538,11541,11543,11545,11547,11549,11552],{"class":120,"line":186},[118,11539,11540],{"class":226},"            Content",[118,11542,6349],{"class":124},[118,11544,1127],{"class":124},[118,11546,258],{"class":128},[118,11548,25],{"class":124},[118,11550,11551],{"class":128},"DocumentNode",[118,11553,7406],{"class":124},[118,11555,11556,11559,11561,11563,11565,11567,11570,11572,11574,11577,11579,11581,11584,11586,11588,11590,11593],{"class":120,"line":196},[118,11557,11558],{"class":124},"                &",[118,11560,258],{"class":128},[118,11562,25],{"class":124},[118,11564,425],{"class":128},[118,11566,1134],{"class":124},[118,11568,11569],{"class":226},"Content",[118,11571,6349],{"class":124},[118,11573,1146],{"class":124},[118,11575,11576],{"class":433},"Total",[118,11578,430],{"class":124},[118,11580,395],{"class":124},[118,11582,11583],{"class":226}," TextStyle",[118,11585,6349],{"class":124},[118,11587,5225],{"class":226},[118,11589,25],{"class":124},[118,11591,11592],{"class":213},"DefaultStyle",[118,11594,11595],{"class":124},"()},\n",[118,11597,11598],{"class":120,"line":202},[118,11599,11600],{"class":124},"            },\n",[118,11602,11603,11606,11608,11611,11613],{"class":120,"line":207},[118,11604,11605],{"class":226},"            ColSpan",[118,11607,6349],{"class":124},[118,11609,11610],{"class":299}," 3",[118,11612,395],{"class":124},[118,11614,11615],{"class":3981}," // ← span the first three columns\n",[118,11617,11618,11621,11623,11625],{"class":120,"line":223},[118,11619,11620],{"class":226},"            RowSpan",[118,11622,6349],{"class":124},[118,11624,7363],{"class":299},[118,11626,2643],{"class":124},[118,11628,11629],{"class":120,"line":244},[118,11630,6056],{"class":124},[118,11632,11633],{"class":120,"line":269},[118,11634,11535],{"class":124},[118,11636,11637,11639,11641,11643,11645,11647,11649],{"class":120,"line":306},[118,11638,11540],{"class":226},[118,11640,6349],{"class":124},[118,11642,1127],{"class":124},[118,11644,258],{"class":128},[118,11646,25],{"class":124},[118,11648,11551],{"class":128},[118,11650,7406],{"class":124},[118,11652,11653,11655,11657,11659,11661,11663,11665,11667,11669,11672,11674,11676,11678,11680,11682,11684,11686],{"class":120,"line":312},[118,11654,11558],{"class":124},[118,11656,258],{"class":128},[118,11658,25],{"class":124},[118,11660,425],{"class":128},[118,11662,1134],{"class":124},[118,11664,11569],{"class":226},[118,11666,6349],{"class":124},[118,11668,1146],{"class":124},[118,11670,11671],{"class":433},"$48,720.00",[118,11673,430],{"class":124},[118,11675,395],{"class":124},[118,11677,11583],{"class":226},[118,11679,6349],{"class":124},[118,11681,5225],{"class":226},[118,11683,25],{"class":124},[118,11685,11592],{"class":213},[118,11687,11595],{"class":124},[118,11689,11690],{"class":120,"line":317},[118,11691,11600],{"class":124},[118,11693,11694,11696,11698,11700],{"class":120,"line":350},[118,11695,11605],{"class":226},[118,11697,6349],{"class":124},[118,11699,7363],{"class":299},[118,11701,2643],{"class":124},[118,11703,11704,11706,11708,11710],{"class":120,"line":379},[118,11705,11620],{"class":226},[118,11707,6349],{"class":124},[118,11709,7363],{"class":299},[118,11711,2643],{"class":124},[118,11713,11714],{"class":120,"line":417},[118,11715,6056],{"class":124},[118,11717,11718],{"class":120,"line":466},[118,11719,8140],{"class":124},[118,11721,11722],{"class":120,"line":472},[118,11723,1479],{"class":124},[118,11725,11726],{"class":120,"line":503},[118,11727,136],{"emptyLinePlaceholder":135},[118,11729,11730,11732,11734,11736,11738,11740,11742],{"class":120,"line":537},[118,11731,8025],{"class":226},[118,11733,230],{"class":124},[118,11735,8030],{"class":124},[118,11737,258],{"class":128},[118,11739,25],{"class":124},[118,11741,4929],{"class":128},[118,11743,7406],{"class":124},[118,11745,11746,11748,11750,11752,11754,11756,11758],{"class":120,"line":566},[118,11747,8043],{"class":226},[118,11749,6349],{"class":124},[118,11751,1127],{"class":124},[118,11753,258],{"class":128},[118,11755,25],{"class":124},[118,11757,8054],{"class":128},[118,11759,7406],{"class":124},[118,11761,11762,11764,11766,11768,11770,11772,11774,11776,11778],{"class":120,"line":571},[118,11763,8061],{"class":124},[118,11765,6607],{"class":226},[118,11767,6349],{"class":124},[118,11769,5225],{"class":226},[118,11771,25],{"class":124},[118,11773,6616],{"class":213},[118,11775,255],{"class":124},[118,11777,300],{"class":299},[118,11779,8098],{"class":124},[118,11781,11782,11784,11786,11788,11790,11792,11794,11796,11798],{"class":120,"line":577},[118,11783,8061],{"class":124},[118,11785,6607],{"class":226},[118,11787,6349],{"class":124},[118,11789,5225],{"class":226},[118,11791,25],{"class":124},[118,11793,6616],{"class":213},[118,11795,255],{"class":124},[118,11797,300],{"class":299},[118,11799,8098],{"class":124},[118,11801,11802,11804,11806,11808,11810,11812,11814],{"class":120,"line":602},[118,11803,8061],{"class":124},[118,11805,6607],{"class":226},[118,11807,6349],{"class":124},[118,11809,5225],{"class":226},[118,11811,25],{"class":124},[118,11813,8113],{"class":226},[118,11815,8191],{"class":124},[118,11817,11818,11820,11822,11824,11826,11828,11830,11832,11835,11837],{"class":120,"line":633},[118,11819,8061],{"class":124},[118,11821,6607],{"class":226},[118,11823,6349],{"class":124},[118,11825,5225],{"class":226},[118,11827,25],{"class":124},[118,11829,6095],{"class":213},[118,11831,255],{"class":124},[118,11833,11834],{"class":299},"80",[118,11836,8078],{"class":124},[118,11838,11839],{"class":3981}," // fixed 80pt regardless of page width\n",[118,11841,11842],{"class":120,"line":668},[118,11843,8140],{"class":124},[118,11845,11846,11848,11850,11853],{"class":120,"line":693},[118,11847,8145],{"class":226},[118,11849,6349],{"class":124},[118,11851,11852],{"class":3981}," /* ... */",[118,11854,2643],{"class":124},[118,11856,11857,11859,11861,11864],{"class":120,"line":698},[118,11858,8160],{"class":226},[118,11860,6349],{"class":124},[118,11862,11863],{"class":3981},"   /* ... */",[118,11865,2643],{"class":124},[118,11867,11868,11870,11872,11874,11876,11878,11880,11882,11884],{"class":120,"line":703},[118,11869,8172],{"class":226},[118,11871,6349],{"class":124},[118,11873,1127],{"class":124},[118,11875,258],{"class":128},[118,11877,25],{"class":124},[118,11879,8183],{"class":128},[118,11881,1134],{"class":124},[118,11883,3099],{"class":226},[118,11885,8191],{"class":124},[118,11887,11888],{"class":120,"line":709},[118,11889,1479],{"class":124},[14,11891,11892,11893,11896,11897,8450,11900,3096,11902,3096,11904,3096,11907,3096,11910,3096,11913,11915,11916,11918,11919,11921,11922,11924],{},"A few things to notice. ",[18,11894,11895],{},"TableColumn.Width"," is a ",[18,11898,11899],{},"document.Value",[18,11901,6095],{},[18,11903,294],{},[18,11905,11906],{},"Cm",[18,11908,11909],{},"In",[18,11911,11912],{},"Em",[18,11914,6616],{},", or the special ",[18,11917,8113],{},". You can mix them in one table. ",[18,11920,8113],{}," columns share whatever's left after the fixed and percentage columns are subtracted. This is closer to the CSS ",[18,11923,10038],{}," element than to the builder's percentage-only model.",[14,11926,11927,54,11930,11933],{},[18,11928,11929],{},"TableCell.ColSpan",[18,11931,11932],{},"RowSpan"," are integers, default 1, expand as expected. The example above is the classic invoice footer: three header columns merge to spell \"Total\", and the fourth column holds the sum.",[14,11935,11936,11896,11938,11941],{},[18,11937,8000],{},[18,11939,11940],{},"[]TableRow"," that repeats on every page, same as the header. The builder API doesn't expose it because most short tables don't need one — when you do need one, you've already left the \"common case\" zone.",[14,11943,11944,11945,11947],{},"This is the gpdf pattern in general: the high-level builder covers the 90% case ergonomically, and the document layer is right there for the 10% case. They're not separate libraries. You can mix builder-built rows with manually-built ones in the same document. The builder is just a constructor for the same ",[18,11946,8442],{}," node.",[41,11949,11951],{"id":11950},"borders-and-the-box-model","Borders and the box model",[14,11953,11954],{},"Three border options, three different jobs:",[109,11956,11958],{"className":111,"code":11957,"language":113,"meta":114,"style":114},"template.WithTableBorder(spec)         // outer frame around the whole table\ntemplate.WithTableCellBorder(spec)     // same border around every cell\ntemplate.WithTableBorderCollapse(true) // merge adjacent cell borders\n",[18,11959,11960,11979,11996],{"__ignoreMap":114},[118,11961,11962,11964,11966,11969,11971,11974,11976],{"class":120,"line":121},[118,11963,337],{"class":226},[118,11965,25],{"class":124},[118,11967,11968],{"class":213},"WithTableBorder",[118,11970,255],{"class":124},[118,11972,11973],{"class":226},"spec",[118,11975,345],{"class":124},[118,11977,11978],{"class":3981},"         // outer frame around the whole table\n",[118,11980,11981,11983,11985,11987,11989,11991,11993],{"class":120,"line":132},[118,11982,337],{"class":226},[118,11984,25],{"class":124},[118,11986,11020],{"class":213},[118,11988,255],{"class":124},[118,11990,11973],{"class":226},[118,11992,345],{"class":124},[118,11994,11995],{"class":3981},"     // same border around every cell\n",[118,11997,11998,12000,12002,12005,12007,12011,12013],{"class":120,"line":139},[118,11999,337],{"class":226},[118,12001,25],{"class":124},[118,12003,12004],{"class":213},"WithTableBorderCollapse",[118,12006,255],{"class":124},[118,12008,12010],{"class":12009},"sfNiH","true",[118,12012,345],{"class":124},[118,12014,12015],{"class":3981}," // merge adjacent cell borders\n",[14,12017,12018,12019,12021,12022,12024,12025,12028,12029,25],{},"By default a table has no borders. Add ",[18,12020,11968],{}," for an outer frame. Add ",[18,12023,11020],{}," to draw the same border around every cell — the grid look. Add both for a frame around a grid. The ",[18,12026,12027],{},"BorderSpec"," itself is built with ",[18,12030,12031],{},"template.Border(template.BorderWidth(...), template.BorderColor(...))",[14,12033,12034,12037],{},[18,12035,12036],{},"WithTableBorderCollapse(true)"," is the CSS analog: adjacent cell borders merge into a single line, instead of drawing twice (once for each cell's edge). For a hairline grid where the borders matter visually, collapse looks cleaner. For thick borders where you want the doubling effect on purpose, leave it off. The default is separated.",[14,12039,12040],{},"A useful pairing is hairline cell borders + light stripes:",[109,12042,12044],{"className":111,"code":12043,"language":113,"meta":114,"style":114},"c.Table(header, rows,\n    template.ColumnWidths(40, 20, 15, 25),\n    template.TableHeaderStyle(template.TextColor(pdf.White), template.BgColor(brand)),\n    template.TableStripe(pdf.RGBHex(0xF5F5F5)),\n    template.WithTableCellBorder(template.Border(\n        template.BorderWidth(document.Pt(0.5)),\n        template.BorderColor(pdf.Gray(0.85)),\n    )),\n    template.WithTableBorderCollapse(true),\n)\n",[18,12045,12046,12064,12090,12128,12150,12168,12190,12212,12217,12231],{"__ignoreMap":114},[118,12047,12048,12050,12052,12054,12056,12058,12060,12062],{"class":120,"line":121},[118,12049,401],{"class":226},[118,12051,25],{"class":124},[118,12053,4929],{"class":213},[118,12055,255],{"class":124},[118,12057,3095],{"class":226},[118,12059,395],{"class":124},[118,12061,5118],{"class":226},[118,12063,2643],{"class":124},[118,12065,12066,12068,12070,12072,12074,12076,12078,12080,12082,12084,12086,12088],{"class":120,"line":132},[118,12067,2775],{"class":226},[118,12069,25],{"class":124},[118,12071,7733],{"class":213},[118,12073,255],{"class":124},[118,12075,9910],{"class":299},[118,12077,395],{"class":124},[118,12079,7742],{"class":299},[118,12081,395],{"class":124},[118,12083,9915],{"class":299},[118,12085,395],{"class":124},[118,12087,9924],{"class":299},[118,12089,266],{"class":124},[118,12091,12092,12094,12096,12098,12100,12102,12104,12106,12108,12110,12112,12114,12116,12118,12120,12122,12124,12126],{"class":120,"line":139},[118,12093,2775],{"class":226},[118,12095,25],{"class":124},[118,12097,7762],{"class":213},[118,12099,255],{"class":124},[118,12101,337],{"class":226},[118,12103,25],{"class":124},[118,12105,545],{"class":213},[118,12107,255],{"class":124},[118,12109,550],{"class":226},[118,12111,25],{"class":124},[118,12113,7781],{"class":226},[118,12115,1280],{"class":124},[118,12117,233],{"class":226},[118,12119,25],{"class":124},[118,12121,7792],{"class":213},[118,12123,255],{"class":124},[118,12125,7797],{"class":226},[118,12127,3646],{"class":124},[118,12129,12130,12132,12134,12136,12138,12140,12142,12144,12146,12148],{"class":120,"line":149},[118,12131,2775],{"class":226},[118,12133,25],{"class":124},[118,12135,9973],{"class":213},[118,12137,255],{"class":124},[118,12139,550],{"class":226},[118,12141,25],{"class":124},[118,12143,658],{"class":213},[118,12145,255],{"class":124},[118,12147,9986],{"class":299},[118,12149,3646],{"class":124},[118,12151,12152,12154,12156,12158,12160,12162,12164,12166],{"class":120,"line":161},[118,12153,2775],{"class":226},[118,12155,25],{"class":124},[118,12157,11020],{"class":213},[118,12159,255],{"class":124},[118,12161,337],{"class":226},[118,12163,25],{"class":124},[118,12165,6075],{"class":213},[118,12167,241],{"class":124},[118,12169,12170,12172,12174,12176,12178,12180,12182,12184,12186,12188],{"class":120,"line":166},[118,12171,247],{"class":226},[118,12173,25],{"class":124},[118,12175,6086],{"class":213},[118,12177,255],{"class":124},[118,12179,258],{"class":226},[118,12181,25],{"class":124},[118,12183,6095],{"class":213},[118,12185,255],{"class":124},[118,12187,560],{"class":299},[118,12189,3646],{"class":124},[118,12191,12192,12194,12196,12198,12200,12202,12204,12206,12208,12210],{"class":120,"line":176},[118,12193,247],{"class":226},[118,12195,25],{"class":124},[118,12197,6110],{"class":213},[118,12199,255],{"class":124},[118,12201,550],{"class":226},[118,12203,25],{"class":124},[118,12205,555],{"class":213},[118,12207,255],{"class":124},[118,12209,10500],{"class":299},[118,12211,3646],{"class":124},[118,12213,12214],{"class":120,"line":186},[118,12215,12216],{"class":124},"    )),\n",[118,12218,12219,12221,12223,12225,12227,12229],{"class":120,"line":196},[118,12220,2775],{"class":226},[118,12222,25],{"class":124},[118,12224,12004],{"class":213},[118,12226,255],{"class":124},[118,12228,12010],{"class":12009},[118,12230,266],{"class":124},[118,12232,12233],{"class":120,"line":202},[118,12234,199],{"class":124},[14,12236,12237],{},"That's the look every accountant's spreadsheet print preview lands on, and it's the right default for any finance-adjacent document — invoices, statements, ledgers, expense reports.",[41,12239,12241],{"id":12240},"how-this-compares-to-the-alternatives","How this compares to the alternatives",[14,12243,12244],{},"For context, here's what the same multi-page striped table costs in the libraries gpdf usually replaces:",[1516,12246,12247,12265],{},[1519,12248,12249],{},[1522,12250,12251,12253,12256,12259,12262],{},[1525,12252,1527],{},[1525,12254,12255],{},"Lines for the table",[1525,12257,12258],{},"Page break header repeat",[1525,12260,12261],{},"Stripes",[1525,12263,12264],{},"Notes",[1532,12266,12267,12293,12315,12330,12345,12365],{},[1522,12268,12269,12273,12278,12283,12290],{},[1537,12270,12271],{},[1629,12272,1587],{},[1537,12274,12275],{},[1629,12276,12277],{},"~10",[1537,12279,12280],{},[1629,12281,12282],{},"automatic",[1537,12284,12285],{},[1629,12286,12287],{},[18,12288,12289],{},"TableStripe(...)",[1537,12291,12292],{},"Builder + low-level both available",[1522,12294,12295,12298,12301,12307,12312],{},[1537,12296,12297],{},"jung-kurt/gofpdf (archived 2021)",[1537,12299,12300],{},"40–60",[1537,12302,12303,12304,12306],{},"manual: track Y, call ",[18,12305,1190],{},", re-emit header",[1537,12308,12309,12310],{},"manual row loop with ",[18,12311,11349],{},[1537,12313,12314],{},"Foundational, no longer maintained",[1522,12316,12317,12320,12322,12325,12327],{},[1537,12318,12319],{},"go-pdf/fpdf (archived 2025)",[1537,12321,12300],{},[1537,12323,12324],{},"same as above",[1537,12326,12324],{},[1537,12328,12329],{},"Was a gofpdf fork; same model",[1522,12331,12332,12334,12337,12340,12342],{},[1537,12333,1565],{},[1537,12335,12336],{},"50–80",[1537,12338,12339],{},"manual",[1537,12341,12339],{},[1537,12343,12344],{},"Lower-level still",[1522,12346,12347,12350,12353,12355,12362],{},[1537,12348,12349],{},"johnfercher/maroto v2",[1537,12351,12352],{},"~15",[1537,12354,12282],{},[1537,12356,12357,12358,12361],{},"manual ",[18,12359,12360],{},"WithBackgroundColor"," per row",[1537,12363,12364],{},"Built on gofpdf; nice API but inherits the dependency story",[1522,12366,12367,12370,12373,12375,12378],{},[1537,12368,12369],{},"unidoc/unipdf",[1537,12371,12372],{},"~12",[1537,12374,12282],{},[1537,12376,12377],{},"row-style helper",[1537,12379,12380],{},"Commercial license required",[14,12382,12383,12384,12386,12387,12389],{},"The builder line counts are the tight part. The actual difference shows up at month 6 of using one of these, when the requirements drift — a new column needs a different alignment, the report has to ship in Japanese, the customer wants the row count printed in the footer. With ",[18,12385,1539],{}," or ",[18,12388,11345],{},", every drift requires touching the row loop. With gpdf, the option list grows and the body code stays the same.",[14,12391,12392,12393,12396,12397,12400],{},"For benchmarks — the actual µs-per-table numbers — see ",[3163,12394,12395],{"href":4069},"why gpdf is faster",". For the broader library showdown across more dimensions, ",[3163,12398,12399],{"href":3381},"the 2026 showdown"," goes column by column.",[41,12402,12404],{"id":12403},"cjk-in-tables","CJK in tables",[14,12406,12407],{},"One thing that's invisible in the comparison table above: gpdf renders CJK glyphs natively. There's no \"table mode\" for Japanese — you register a font once and the table uses it for everything.",[109,12409,12411],{"className":111,"code":12410,"language":113,"meta":114,"style":114},"ttf, _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\ndoc := gpdf.NewDocument(\n    gpdf.WithPageSize(gpdf.A4),\n    gpdf.WithFont(\"NotoSansJP\", ttf),\n    gpdf.WithDefaultFont(\"NotoSansJP\"),\n)\n\nc.Table(\n    []string{\"日付\", \"請求書番号\", \"顧客名\", \"金額\"},\n    [][]string{\n        {\"2026-04-01\", \"INV-10001\", \"株式会社サンプル\", \"￥120,000\"},\n        {\"2026-04-02\", \"INV-10002\", \"山田商店\", \"￥38,500\"},\n    },\n    template.ColumnWidths(20, 20, 40, 20),\n)\n",[18,12412,12413,12439,12453,12471,12493,12512,12516,12520,12530,12575,12584,12624,12664,12668,12694],{"__ignoreMap":114},[118,12414,12415,12417,12419,12421,12423,12425,12427,12429,12431,12433,12435,12437],{"class":120,"line":121},[118,12416,9532],{"class":226},[118,12418,395],{"class":124},[118,12420,3999],{"class":226},[118,12422,230],{"class":124},[118,12424,1447],{"class":226},[118,12426,25],{"class":124},[118,12428,9545],{"class":213},[118,12430,255],{"class":124},[118,12432,430],{"class":124},[118,12434,9552],{"class":433},[118,12436,430],{"class":124},[118,12438,199],{"class":124},[118,12440,12441,12443,12445,12447,12449,12451],{"class":120,"line":132},[118,12442,2760],{"class":226},[118,12444,230],{"class":124},[118,12446,3595],{"class":226},[118,12448,25],{"class":124},[118,12450,3600],{"class":213},[118,12452,241],{"class":124},[118,12454,12455,12457,12459,12461,12463,12465,12467,12469],{"class":120,"line":139},[118,12456,4532],{"class":226},[118,12458,25],{"class":124},[118,12460,252],{"class":213},[118,12462,255],{"class":124},[118,12464,1587],{"class":226},[118,12466,25],{"class":124},[118,12468,263],{"class":226},[118,12470,266],{"class":124},[118,12472,12473,12475,12477,12479,12481,12483,12485,12487,12489,12491],{"class":120,"line":149},[118,12474,4532],{"class":226},[118,12476,25],{"class":124},[118,12478,2798],{"class":213},[118,12480,255],{"class":124},[118,12482,430],{"class":124},[118,12484,2805],{"class":433},[118,12486,430],{"class":124},[118,12488,395],{"class":124},[118,12490,9595],{"class":226},[118,12492,266],{"class":124},[118,12494,12495,12497,12499,12502,12504,12506,12508,12510],{"class":120,"line":161},[118,12496,4532],{"class":226},[118,12498,25],{"class":124},[118,12500,12501],{"class":213},"WithDefaultFont",[118,12503,255],{"class":124},[118,12505,430],{"class":124},[118,12507,2805],{"class":433},[118,12509,430],{"class":124},[118,12511,266],{"class":124},[118,12513,12514],{"class":120,"line":166},[118,12515,199],{"class":124},[118,12517,12518],{"class":120,"line":176},[118,12519,136],{"emptyLinePlaceholder":135},[118,12521,12522,12524,12526,12528],{"class":120,"line":186},[118,12523,401],{"class":226},[118,12525,25],{"class":124},[118,12527,4929],{"class":213},[118,12529,241],{"class":124},[118,12531,12532,12535,12537,12539,12541,12544,12546,12548,12550,12553,12555,12557,12559,12562,12564,12566,12568,12571,12573],{"class":120,"line":196},[118,12533,12534],{"class":124},"    []",[118,12536,1131],{"class":1130},[118,12538,1134],{"class":124},[118,12540,430],{"class":124},[118,12542,12543],{"class":433},"日付",[118,12545,430],{"class":124},[118,12547,395],{"class":124},[118,12549,1146],{"class":124},[118,12551,12552],{"class":433},"請求書番号",[118,12554,430],{"class":124},[118,12556,395],{"class":124},[118,12558,1146],{"class":124},[118,12560,12561],{"class":433},"顧客名",[118,12563,430],{"class":124},[118,12565,395],{"class":124},[118,12567,1146],{"class":124},[118,12569,12570],{"class":433},"金額",[118,12572,430],{"class":124},[118,12574,8191],{"class":124},[118,12576,12577,12580,12582],{"class":120,"line":202},[118,12578,12579],{"class":124},"    [][]",[118,12581,1131],{"class":1130},[118,12583,7406],{"class":124},[118,12585,12586,12588,12590,12593,12595,12597,12599,12602,12604,12606,12608,12611,12613,12615,12617,12620,12622],{"class":120,"line":207},[118,12587,8061],{"class":124},[118,12589,430],{"class":124},[118,12591,12592],{"class":433},"2026-04-01",[118,12594,430],{"class":124},[118,12596,395],{"class":124},[118,12598,1146],{"class":124},[118,12600,12601],{"class":433},"INV-10001",[118,12603,430],{"class":124},[118,12605,395],{"class":124},[118,12607,1146],{"class":124},[118,12609,12610],{"class":433},"株式会社サンプル",[118,12612,430],{"class":124},[118,12614,395],{"class":124},[118,12616,1146],{"class":124},[118,12618,12619],{"class":433},"￥120,000",[118,12621,430],{"class":124},[118,12623,8191],{"class":124},[118,12625,12626,12628,12630,12633,12635,12637,12639,12642,12644,12646,12648,12651,12653,12655,12657,12660,12662],{"class":120,"line":223},[118,12627,8061],{"class":124},[118,12629,430],{"class":124},[118,12631,12632],{"class":433},"2026-04-02",[118,12634,430],{"class":124},[118,12636,395],{"class":124},[118,12638,1146],{"class":124},[118,12640,12641],{"class":433},"INV-10002",[118,12643,430],{"class":124},[118,12645,395],{"class":124},[118,12647,1146],{"class":124},[118,12649,12650],{"class":433},"山田商店",[118,12652,430],{"class":124},[118,12654,395],{"class":124},[118,12656,1146],{"class":124},[118,12658,12659],{"class":433},"￥38,500",[118,12661,430],{"class":124},[118,12663,8191],{"class":124},[118,12665,12666],{"class":120,"line":244},[118,12667,8140],{"class":124},[118,12669,12670,12672,12674,12676,12678,12680,12682,12684,12686,12688,12690,12692],{"class":120,"line":269},[118,12671,2775],{"class":226},[118,12673,25],{"class":124},[118,12675,7733],{"class":213},[118,12677,255],{"class":124},[118,12679,300],{"class":299},[118,12681,395],{"class":124},[118,12683,7742],{"class":299},[118,12685,395],{"class":124},[118,12687,7747],{"class":299},[118,12689,395],{"class":124},[118,12691,7742],{"class":299},[118,12693,266],{"class":124},[118,12695,12696],{"class":120,"line":306},[118,12697,199],{"class":124},[14,12699,12700],{},"The header is Japanese, the body is Japanese, the column widths are still percentages, the page-break header repeat still works. The font is subset to only the glyphs the document uses, so the output PDF is small even with full Noto Sans JP available — about 50 KB for a single page versus the 6 MB of the unsubset font file.",[14,12702,12703,12704,12707],{},"For the font setup itself, ",[3163,12705,12706],{"href":9791},"embed a Japanese TrueType font"," is the recipe. The point here is that nothing about the table API changes when the data is CJK.",[41,12709,12711],{"id":12710},"frequently-asked","Frequently asked",[14,12713,12714],{},[1629,12715,12716],{},"Q: Does gpdf support per-row styling?",[14,12718,12719,12720,12723,12724,12727,12728,12730,12731,12733,12734,12737,12738,12740],{},"Not in the builder API. The builder takes ",[18,12721,12722],{},"[][]string"," for body rows, which means every body cell shares the same ",[18,12725,12726],{},"Style"," derived from the column. To style individual rows differently, build the table at the ",[18,12729,8442],{}," layer where each ",[18,12732,11528],{}," carries its own ",[18,12735,12736],{},"CellStyle",". The pattern is straightforward; it just costs you the convenience of the ",[18,12739,12722],{}," shape.",[14,12742,12743],{},[1629,12744,12745],{},"Q: Can I put images or other tables inside a cell?",[14,12747,12748,12749,12751,12752,6589,12755,12758,12759,3096,12762,12765,12766,12769],{},"Yes, at the ",[18,12750,8442],{}," layer. ",[18,12753,12754],{},"TableCell.Content",[18,12756,12757],{},"[]DocumentNode",", which accepts any node — ",[18,12760,12761],{},"*Text",[18,12763,12764],{},"*Image",", even another ",[18,12767,12768],{},"*Table",". The builder's string-based API doesn't expose this because it's a sharper edge than most users want, but the underlying model supports it.",[14,12771,12772],{},[1629,12773,12774],{},"Q: How does gpdf decide where to split the body across pages?",[14,12776,12777],{},"Row by row. The layout engine measures each body row in order and adds it to the current page until the next row would exceed the available height. That row becomes the first row of the overflow table. There's no \"keep these rows together\" annotation yet — every row is split-eligible. For invoice line items where you really need a logical group on one page, you'd need to start a fresh page manually before the group, or fall back to the document layer to insert page break hints.",[14,12779,12780],{},[1629,12781,12782],{},"Q: What's the largest table gpdf can render?",[14,12784,12785,12786,12789,12790,12792,12793,12795],{},"We've tested at 10,000 body rows on A4. It paginates correctly, the header repeats on every page, the resulting PDF is ~150 pages and a few hundred KB. The bottleneck is not the table layout — it's the text shaping for the cell content, which is ",[18,12787,12788],{},"O(rows × columns)",". If you need 100,000+ rows, write to disk in chunks (multiple ",[18,12791,1400],{}," calls per ~10k rows) or feed pre-shaped runs at the ",[18,12794,8442],{}," layer.",[14,12797,12798],{},[1629,12799,12800],{},"Q: Can I get the footer to show only on the last page?",[14,12802,12803,12804,12806],{},"Not built in. ",[18,12805,8000],{}," repeats on every page by design — that's the common case (column totals shown per page). If you need a one-shot summary at the document end, append it as a separate row block after the table, not inside it.",[14,12808,12809],{},[1629,12810,12811,12812,12814],{},"Q: Does ",[18,12813,11020],{}," affect the header too?",[14,12816,12817,12818,12821],{},"Yes. Cell borders apply uniformly to header and body. If you want a different border on the header (say, thicker bottom border under the header row), build the header at the document layer and apply per-cell ",[18,12819,12820],{},"CellStyle.Border"," there.",[41,12823,12825],{"id":12824},"the-shape-of-the-design","The shape of the design",[14,12827,12828,12829,12832],{},"If there's one thing to take away: ",[1629,12830,12831],{},"gpdf's table API is small because most table problems are the same three problems."," Widths, stripes, page breaks. Everything else is a long tail. Putting the common cases in the builder and the long tail in the document layer is the trade — you get five-line tables for the things that come up every day, and you don't pay for the abstraction when you need to do something the builder can't express.",[14,12834,12835,12836,12839],{},"The cost is honest: there's no ",[18,12837,12838],{},"setRowStyle(i, ...)"," shortcut, and there won't be one. If you want to style row 4 differently from row 5, you've crossed a complexity line that the builder isn't trying to handle. Drop a layer. The boundary is clear and stable.",[14,12841,12842],{},"That's the whole article. Twenty minutes of reading for a part of the API that's worth getting right once and then not thinking about again.",[41,12844,4794],{"id":4793},[14,12846,6933],{},[109,12848,12849],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,12850,12851],{"__ignoreMap":114},[118,12852,12853,12855,12857],{"class":120,"line":121},[118,12854,113],{"class":128},[118,12856,3156],{"class":433},[118,12858,3159],{"class":433},[14,12860,12861,3169,12864],{},[3163,12862,3168],{"href":3165,"rel":12863},[3167],[3163,12865,3174],{"href":3172,"rel":12866},[3167],[41,12868,12870],{"id":12869},"related-reading","Related reading",[46,12872,12873,12881,12886,12893,12898],{},[49,12874,12875,8450,12878,12880],{},[3163,12876,12877],{"href":8448},"How do I set column widths in a gpdf table?",[18,12879,7733],{}," corner cases in detail",[49,12882,12883,12885],{},[3163,12884,8459],{"href":8458}," — color choice and dark-theme variants",[49,12887,12888,12892],{},[3163,12889,12891],{"href":12890},"/blog/bootstrap-grid-thinking-for-pdf","Bootstrap thinking for PDF: gpdf's 12-column grid"," — what the parent Col is that table percentages resolve against",[49,12894,12895,12897],{},[3163,12896,6919],{"href":6918}," — a real-world table inside a complete document",[49,12899,12900,12903],{},[3163,12901,12902],{"href":4069},"Why gpdf is faster than gofpdf, gopdf, and Maroto"," — the µs numbers behind the comparison table",[3176,12905,12906],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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);}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":114,"searchDepth":132,"depth":132,"links":12908},[12909,12910,12911,12912,12913,12914,12915,12916,12917,12918,12919,12920,12921,12922,12923],{"id":43,"depth":132,"text":44},{"id":10014,"depth":132,"text":10015},{"id":4952,"depth":132,"text":4953},{"id":10225,"depth":132,"text":10226},{"id":11173,"depth":132,"text":11174},{"id":11299,"depth":132,"text":11300},{"id":11390,"depth":132,"text":11391},{"id":11464,"depth":132,"text":11465},{"id":11950,"depth":132,"text":11951},{"id":12240,"depth":132,"text":12241},{"id":12403,"depth":132,"text":12404},{"id":12710,"depth":132,"text":12711},{"id":12824,"depth":132,"text":12825},{"id":4793,"depth":132,"text":4794},{"id":12869,"depth":132,"text":12870},"2026-05-07","Tables are the hardest part of a Go PDF. gpdf collapses widths, stripes, and multi-page header repeat into one Table call — here is the whole API and what it costs.",{"name":12927,"totalTime":12928,"tools":12929,"steps":12930},"Render a multi-page table in Go with column widths, stripes, and a repeating header using gpdf","PT20M",[3202,3203],[12931,12934,12937,12940,12943,12946],{"name":12932,"text":12933},"Install gpdf","Run go get github.com/gpdf-dev/gpdf in a Go 1.22+ module. The core library has no external dependencies, so this is the only line.",{"name":12935,"text":12936},"Build a single Table call inside a Col","Inside page.AutoRow → r.Col(12, ...), call c.Table(header, rows). Header is []string, rows is [][]string. With no options the columns split equally.",{"name":12938,"text":12939},"Set per-column widths as percentages","Pass template.ColumnWidths(40, 15, 20, 25) — each value is a percentage of the parent Col width. Trailing missing values auto-distribute the remainder.",{"name":12941,"text":12942},"Style the header and add zebra stripes","Add template.TableHeaderStyle(template.TextColor(pdf.White), template.BgColor(brand)) and template.TableStripe(pdf.RGBHex(0xF5F5F5)). Stripes apply to body rows only; the header is excluded.",{"name":12944,"text":12945},"Let gpdf paginate. The header repeats automatically","Feed enough rows to overflow one page. gpdf splits the body across pages and repeats the Header section at the top of every page. No PageBreak option, no row counting.",{"name":12947,"text":12948},"Drop to document.Table for ColSpan, RowSpan, fixed widths, or a footer","Build &document.Table{Columns, Header, Body, Footer} directly when you need spanning cells, fixed-pt columns, or a footer that repeats on every page. The builder API doesn't expose these on purpose.",{},{"title":8438,"description":12925},"blog/022.tables-in-go-pdfs",[3226,6997,4866],"KsEzef-ltEvSCEr9ypu6M8z4h1KXDu5KRVykGBkUcQI",{"id":12955,"title":12956,"author":12957,"body":12958,"date":18049,"description":18050,"draft":3196,"extension":3197,"howTo":18051,"image":3220,"meta":18077,"navigation":135,"path":18078,"seo":18079,"stem":18080,"tags":18081,"updated":3220,"__hash__":18082},"blog/blog/021.signintech-gopdf-migration.md","From signintech/gopdf to gpdf: less coordinate math",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":12959,"toc":18032},[12960,12962,12977,12996,12999,13002,13006,13009,13012,13021,13024,13028,13031,13041,13063,13066,13070,13073,13407,13431,13435,13438,13443,13840,13845,14226,14233,14237,14240,14244,14682,14692,14696,14961,14979,14983,14989,14993,15786,15792,15796,16042,16048,16059,16063,16066,16070,16467,16474,16478,16731,16740,16744,16747,16751,17032,17035,17039,17381,17388,17392,17395,17669,17676,17680,17683,17775,17782,17789,17793,17796,17857,17864,17866,17872,17878,17892,17918,17942,17958,17975,17977,17980,17992,18000,18002,18029],[41,12961,44],{"id":43},[14,12963,12964,12966,12967,12969,12970,3096,12973,12976],{},[1629,12965,1587],{}," is a pure-Go PDF library with a 12-column layout engine. ",[1629,12968,1565],{}," is a low-level binding around the PDF coordinate system. If you've been running gopdf for a while and the codebase is now mostly ",[18,12971,12972],{},"SetXY",[18,12974,12975],{},"Cell",", and width arithmetic, this guide shows what those calls collapse into when there's a layout engine underneath.",[14,12978,12979,12980,12982,12983,12982,12985,12988,12989,12992,12993,12995],{},"Last week I sat with someone refactoring an invoice generator on signintech/gopdf. Five years of accretion. The function rendering the line-items table was 280 lines. About 40 of those did real work: format the amount, format the date, repeat for each row. The other 240 were calculating x-positions, tracking y, calling ",[18,12981,12972],{},", calling ",[18,12984,12975],{},[18,12986,12987],{},"Br",", drawing border lines with ",[18,12990,12991],{},"Line(x1, y1, x2, y2)",", deciding whether the row fit on the page or needed a manual ",[18,12994,1190],{}," and a manual reprint of the header.",[14,12997,12998],{},"That's the gopdf experience in production. It's not a bad library. It's a thin, fast, CGO-free binding around the PDF imaging model — and that's exactly what you get. There's a cursor, there are coordinates, and the engineer is the layout engine.",[14,13000,13001],{},"This article maps the gopdf API to gpdf, function by function. The thesis is in the title: most of the lines disappear because they were doing layout math the runtime can do for you.",[41,13003,13005],{"id":13004},"what-signintechgopdf-is-and-what-it-isnt","What signintech/gopdf is — and what it isn't",[14,13007,13008],{},"Worth getting this straight before any \"migrate away\" framing, because gopdf has real virtues.",[14,13010,13011],{},"It's actively maintained. It's pure Go (no CGO), so cross-compilation and Alpine images just work. It supports TrueType fonts including CJK. Output is fast — gopdf is in the same ballpark as gpdf for the imaging primitives because they're both writing PDF wire format directly without a heavyweight engine in front. The API maps cleanly onto the underlying PDF model: there's a current point, you move it, you draw at it. If you already think in PDF coordinates, gopdf is comfortable.",[14,13013,13014,13015,13017,13018,13020],{},"What it isn't, is a layout system. There's no notion of a row, a column, a flex container, or a grid. There's no automatic page break: when your content runs past the bottom margin, you get content past the bottom margin (or off the page entirely) until you call ",[18,13016,1190],{}," yourself. Tables don't exist as a primitive — they're a pattern you re-implement every project, with cell-by-cell ",[18,13019,12975],{}," calls, manual border lines, and your own page-break logic.",[14,13022,13023],{},"For a one-page certificate or a very controlled fixed-template form, the cursor model is fine. For invoices, reports, statements, anything with variable-length content — the coordinate math grows with the surface area of the document. That's the workload gpdf is built for.",[41,13025,13027],{"id":13026},"the-mental-model-shift","The mental model shift",[14,13029,13030],{},"This is the part that actually changes how the code reads. gpdf has two ideas that gopdf doesn't:",[14,13032,13033,13036,13037,13040],{},[1629,13034,13035],{},"Declarative tree."," You don't tell the renderer where to put things. You describe a tree of pages → rows → columns → content, and the layout engine resolves positions in a single pass. There's no cursor to advance. Two consecutive ",[18,13038,13039],{},"r.Col(...)"," calls don't need to know about each other.",[14,13042,13043,13046,13047,13050,13051,13054,13055,13058,13059,13062],{},[1629,13044,13045],{},"12-column grid."," Every row is implicitly 12 units wide. You spend those 12 units across columns: ",[18,13048,13049],{},"r.Col(8, ...)"," takes two-thirds, ",[18,13052,13053],{},"r.Col(4, ...)"," takes one-third. The grid is the same idea Bootstrap and Tailwind use for HTML, applied to PDF. You stop calculating ",[18,13056,13057],{},"pageWidth - leftMargin - rightMargin"," and dividing by 4. You write ",[18,13060,13061],{},"r.Col(3, ...)"," four times.",[14,13064,13065],{},"These two ideas remove most of the math. The before/after pairs below all collapse the same way: a cursor-advancing imperative loop becomes a small declarative tree.",[41,13067,13069],{"id":13068},"the-api-mapping-table","The API mapping table",[14,13071,13072],{},"Cheat sheet first. Sections after walk through five concrete pairs.",[1516,13074,13075,13086],{},[1519,13076,13077],{},[1522,13078,13079,13082,13084],{},[1525,13080,13081],{},"What you want to do",[1525,13083,1565],{},[1525,13085,1587],{},[1532,13087,13088,13103,13118,13133,13151,13169,13187,13206,13224,13240,13255,13270,13285,13299,13319,13338,13359,13374,13389],{},[1522,13089,13090,13093,13098],{},[1537,13091,13092],{},"Construct",[1537,13094,13095],{},[18,13096,13097],{},"pdf := gopdf.GoPdf{}; pdf.Start(gopdf.Config{...})",[1537,13099,13100],{},[18,13101,13102],{},"doc := gpdf.NewDocument(gpdf.WithPageSize(document.A4), ...)",[1522,13104,13105,13108,13113],{},[1537,13106,13107],{},"Set page size",[1537,13109,13110],{},[18,13111,13112],{},"Config{PageSize: gopdf.PageSizeA4}",[1537,13114,13115],{},[18,13116,13117],{},"gpdf.WithPageSize(document.A4)",[1522,13119,13120,13123,13128],{},[1537,13121,13122],{},"Add page",[1537,13124,13125],{},[18,13126,13127],{},"pdf.AddPage()",[1537,13129,13130],{},[18,13131,13132],{},"page := doc.AddPage()",[1522,13134,13135,13138,13146],{},[1537,13136,13137],{},"Move cursor",[1537,13139,13140,4919,13143],{},[18,13141,13142],{},"pdf.SetX(40); pdf.SetY(80)",[4744,13144,13145],{},"(everywhere)",[1537,13147,13148],{},[4744,13149,13150],{},"(no cursor)",[1522,13152,13153,13156,13161],{},[1537,13154,13155],{},"Single line of text",[1537,13157,13158],{},[18,13159,13160],{},"pdf.SetXY(x, y); pdf.Cell(nil, \"hi\")",[1537,13162,13163,4919,13166],{},[18,13164,13165],{},"c.Text(\"hi\")",[4744,13167,13168],{},"(inside a column)",[1522,13170,13171,13174,13179],{},[1537,13172,13173],{},"Wrapped text",[1537,13175,13176],{},[18,13177,13178],{},"pdf.MultiCell(&gopdf.Rect{W: 200, H: 100}, body)",[1537,13180,13181,4919,13184],{},[18,13182,13183],{},"c.Text(body)",[4744,13185,13186],{},"(wraps automatically)",[1522,13188,13189,13192,13197],{},[1537,13190,13191],{},"Line break",[1537,13193,13194],{},[18,13195,13196],{},"pdf.Br(20)",[1537,13198,13199],{},[4744,13200,13201,13202,13205],{},"(implicit between rows; ",[18,13203,13204],{},"c.Spacer(document.Mm(4))"," if you need one)",[1522,13207,13208,13211,13216],{},[1537,13209,13210],{},"Font registration",[1537,13212,13213],{},[18,13214,13215],{},"pdf.AddTTFFont(\"noto\", \"fonts/Noto.ttf\")",[1537,13217,13218,4919,13221],{},[18,13219,13220],{},"gpdf.WithFont(\"Noto\", ttfBytes)",[4744,13222,13223],{},"(at construction)",[1522,13225,13226,13229,13234],{},[1537,13227,13228],{},"Set active font",[1537,13230,13231],{},[18,13232,13233],{},"pdf.SetFont(\"noto\", \"\", 14)",[1537,13235,13236,13239],{},[18,13237,13238],{},"template.FontFamily(\"Noto\"), template.FontSize(14)"," per-text",[1522,13241,13242,13245,13250],{},[1537,13243,13244],{},"Color",[1537,13246,13247],{},[18,13248,13249],{},"pdf.SetTextColor(26, 35, 126)",[1537,13251,13252],{},[18,13253,13254],{},"template.TextColor(pdf.RGBHex(0x1A237E))",[1522,13256,13257,13260,13265],{},[1537,13258,13259],{},"Horizontal rule",[1537,13261,13262],{},[18,13263,13264],{},"pdf.Line(40, 100, 555, 100)",[1537,13266,13267],{},[18,13268,13269],{},"c.Line(template.LineColor(pdf.Gray(0.7)))",[1522,13271,13272,13275,13280],{},[1537,13273,13274],{},"Rectangle",[1537,13276,13277],{},[18,13278,13279],{},"pdf.RectFromUpperLeftWithStyle(x, y, w, h, \"FD\")",[1537,13281,13282],{},[18,13283,13284],{},"c.Box(template.BgColor(...), template.Border(...))",[1522,13286,13287,13289,13294],{},[1537,13288,1773],{},[1537,13290,13291],{},[18,13292,13293],{},"pdf.Image(\"logo.png\", x, y, &gopdf.Rect{W: 100, H: 50})",[1537,13295,13296],{},[18,13297,13298],{},"c.Image(imgBytes, template.FitWidth(document.Mm(35)))",[1522,13300,13301,13304,13314],{},[1537,13302,13303],{},"Manual table (cell-by-cell)",[1537,13305,13306,13307,9731,13309,9731,13311,13313],{},"dozens of ",[18,13308,12975],{},[18,13310,640],{},[18,13312,12972],{}," calls",[1537,13315,13316],{},[18,13317,13318],{},"c.Table(headers, rows, template.ColumnWidths(...))",[1522,13320,13321,13324,13332],{},[1537,13322,13323],{},"Header / footer",[1537,13325,13326,1592,13329],{},[18,13327,13328],{},"pdf.AddHeader(fn)",[18,13330,13331],{},"pdf.AddFooter(fn)",[1537,13333,13334,1592,13336],{},[18,13335,53],{},[18,13337,57],{},[1522,13339,13340,13343,13350],{},[1537,13341,13342],{},"Page number",[1537,13344,13345,13346,13349],{},"format ",[18,13347,13348],{},"\"Page %d of %d\""," from a counter you maintain",[1537,13351,13352,1592,13354,4919,13356],{},[18,13353,66],{},[18,13355,70],{},[4744,13357,13358],{},"(placeholders)",[1522,13360,13361,13364,13369],{},[1537,13362,13363],{},"Encrypt",[1537,13365,13366],{},[18,13367,13368],{},"Config{Protection: PDFProtectionConfig{...}}",[1537,13370,13371],{},[18,13372,13373],{},"gpdf.WithEncryption(gpdf.AES256, \"user\", \"owner\", perms)",[1522,13375,13376,13379,13384],{},[1537,13377,13378],{},"Output",[1537,13380,13381],{},[18,13382,13383],{},"pdf.WritePdf(\"out.pdf\")",[1537,13385,13386],{},[18,13387,13388],{},"data, _ := doc.Generate(); os.WriteFile(\"out.pdf\", data, 0o644)",[1522,13390,13391,13394,13402],{},[1537,13392,13393],{},"Output to writer",[1537,13395,13396,1592,13399],{},[18,13397,13398],{},"pdf.Write(w)",[18,13400,13401],{},"pdf.ToBuffer()",[1537,13403,13404],{},[18,13405,13406],{},"doc.Render(w)",[14,13408,13409,13410,13413,13414,13416,13417,13419,13420,13422,13423,13426,13427,13430],{},"Two structural shifts. First, ",[1629,13411,13412],{},"the cursor goes away",". Lines marked ",[4744,13415,13145],{}," in the table aren't an exaggeration — in a real gopdf codebase, ",[18,13418,12972],{}," calls outnumber ",[18,13421,12975],{}," calls. They all collapse to nothing in gpdf. Second, ",[1629,13424,13425],{},"percentages replace pixels",". ",[18,13428,13429],{},"Rect{W: 200, H: 100}"," becomes \"this column takes 4 of 12 units of whatever container it's in.\" Drop the same column inside a half-width row and it scales without changes.",[41,13432,13434],{"id":13433},"before-after-1-hello-world","Before / After 1: hello world",[14,13436,13437],{},"The shortest possible diff. Notice what's missing on the right.",[14,13439,13440],{},[1629,13441,13442],{},"Before — signintech/gopdf:",[109,13444,13446],{"className":111,"code":13445,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n\n    \"github.com/signintech/gopdf\"\n)\n\nfunc main() {\n    pdf := gopdf.GoPdf{}\n    pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})\n    pdf.AddPage()\n\n    if err := pdf.AddTTFFont(\"helvetica\", \"fonts/Helvetica.ttf\"); err != nil {\n        log.Fatal(err)\n    }\n    if err := pdf.SetFont(\"helvetica\", \"\", 24); err != nil {\n        log.Fatal(err)\n    }\n\n    pdf.SetX(40)\n    pdf.SetY(80)\n    if err := pdf.Cell(nil, \"Hello, World!\"); err != nil {\n        log.Fatal(err)\n    }\n\n    if err := pdf.WritePdf(\"hello.pdf\"); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,13447,13448,13454,13458,13464,13472,13476,13485,13489,13493,13503,13521,13558,13568,13572,13615,13629,13633,13676,13690,13694,13698,13713,13728,13762,13776,13780,13784,13818,13832,13836],{"__ignoreMap":114},[118,13449,13450,13452],{"class":120,"line":121},[118,13451,125],{"class":124},[118,13453,129],{"class":128},[118,13455,13456],{"class":120,"line":132},[118,13457,136],{"emptyLinePlaceholder":135},[118,13459,13460,13462],{"class":120,"line":139},[118,13461,143],{"class":142},[118,13463,146],{"class":124},[118,13465,13466,13468,13470],{"class":120,"line":149},[118,13467,152],{"class":124},[118,13469,5303],{"class":128},[118,13471,158],{"class":124},[118,13473,13474],{"class":120,"line":161},[118,13475,136],{"emptyLinePlaceholder":135},[118,13477,13478,13480,13483],{"class":120,"line":166},[118,13479,152],{"class":124},[118,13481,13482],{"class":128},"github.com/signintech/gopdf",[118,13484,158],{"class":124},[118,13486,13487],{"class":120,"line":176},[118,13488,199],{"class":124},[118,13490,13491],{"class":120,"line":186},[118,13492,136],{"emptyLinePlaceholder":135},[118,13494,13495,13497,13499,13501],{"class":120,"line":196},[118,13496,210],{"class":124},[118,13498,214],{"class":213},[118,13500,217],{"class":124},[118,13502,220],{"class":124},[118,13504,13505,13508,13510,13513,13515,13518],{"class":120,"line":202},[118,13506,13507],{"class":226},"    pdf ",[118,13509,230],{"class":124},[118,13511,13512],{"class":128}," gopdf",[118,13514,25],{"class":124},[118,13516,13517],{"class":128},"GoPdf",[118,13519,13520],{"class":124},"{}\n",[118,13522,13523,13526,13528,13531,13533,13535,13537,13540,13542,13545,13547,13549,13551,13553,13556],{"class":120,"line":207},[118,13524,13525],{"class":226},"    pdf",[118,13527,25],{"class":124},[118,13529,13530],{"class":213},"Start",[118,13532,255],{"class":124},[118,13534,11345],{"class":128},[118,13536,25],{"class":124},[118,13538,13539],{"class":128},"Config",[118,13541,1134],{"class":124},[118,13543,13544],{"class":226},"PageSize",[118,13546,6349],{"class":124},[118,13548,334],{"class":124},[118,13550,11345],{"class":226},[118,13552,25],{"class":124},[118,13554,13555],{"class":226},"PageSizeA4",[118,13557,1944],{"class":124},[118,13559,13560,13562,13564,13566],{"class":120,"line":223},[118,13561,13525],{"class":226},[118,13563,25],{"class":124},[118,13565,1190],{"class":213},[118,13567,1193],{"class":124},[118,13569,13570],{"class":120,"line":244},[118,13571,136],{"emptyLinePlaceholder":135},[118,13573,13574,13576,13578,13580,13582,13584,13587,13589,13591,13594,13596,13598,13600,13603,13605,13607,13609,13611,13613],{"class":120,"line":269},[118,13575,1408],{"class":142},[118,13577,1391],{"class":226},[118,13579,230],{"class":124},[118,13581,7260],{"class":226},[118,13583,25],{"class":124},[118,13585,13586],{"class":213},"AddTTFFont",[118,13588,255],{"class":124},[118,13590,430],{"class":124},[118,13592,13593],{"class":433},"helvetica",[118,13595,430],{"class":124},[118,13597,395],{"class":124},[118,13599,1146],{"class":124},[118,13601,13602],{"class":433},"fonts/Helvetica.ttf",[118,13604,430],{"class":124},[118,13606,7902],{"class":124},[118,13608,1391],{"class":226},[118,13610,1413],{"class":124},[118,13612,1416],{"class":124},[118,13614,220],{"class":124},[118,13616,13617,13619,13621,13623,13625,13627],{"class":120,"line":306},[118,13618,5818],{"class":226},[118,13620,25],{"class":124},[118,13622,5823],{"class":213},[118,13624,255],{"class":124},[118,13626,1429],{"class":226},[118,13628,199],{"class":124},[118,13630,13631],{"class":120,"line":312},[118,13632,1375],{"class":124},[118,13634,13635,13637,13639,13641,13643,13645,13648,13650,13652,13654,13656,13658,13661,13663,13666,13668,13670,13672,13674],{"class":120,"line":317},[118,13636,1408],{"class":142},[118,13638,1391],{"class":226},[118,13640,230],{"class":124},[118,13642,7260],{"class":226},[118,13644,25],{"class":124},[118,13646,13647],{"class":213},"SetFont",[118,13649,255],{"class":124},[118,13651,430],{"class":124},[118,13653,13593],{"class":433},[118,13655,430],{"class":124},[118,13657,395],{"class":124},[118,13659,13660],{"class":124}," \"\"",[118,13662,395],{"class":124},[118,13664,13665],{"class":299}," 24",[118,13667,7902],{"class":124},[118,13669,1391],{"class":226},[118,13671,1413],{"class":124},[118,13673,1416],{"class":124},[118,13675,220],{"class":124},[118,13677,13678,13680,13682,13684,13686,13688],{"class":120,"line":350},[118,13679,5818],{"class":226},[118,13681,25],{"class":124},[118,13683,5823],{"class":213},[118,13685,255],{"class":124},[118,13687,1429],{"class":226},[118,13689,199],{"class":124},[118,13691,13692],{"class":120,"line":379},[118,13693,1375],{"class":124},[118,13695,13696],{"class":120,"line":417},[118,13697,136],{"emptyLinePlaceholder":135},[118,13699,13700,13702,13704,13707,13709,13711],{"class":120,"line":466},[118,13701,13525],{"class":226},[118,13703,25],{"class":124},[118,13705,13706],{"class":213},"SetX",[118,13708,255],{"class":124},[118,13710,9910],{"class":299},[118,13712,199],{"class":124},[118,13714,13715,13717,13719,13722,13724,13726],{"class":120,"line":472},[118,13716,13525],{"class":226},[118,13718,25],{"class":124},[118,13720,13721],{"class":213},"SetY",[118,13723,255],{"class":124},[118,13725,11834],{"class":299},[118,13727,199],{"class":124},[118,13729,13730,13732,13734,13736,13738,13740,13742,13745,13747,13750,13752,13754,13756,13758,13760],{"class":120,"line":503},[118,13731,1408],{"class":142},[118,13733,1391],{"class":226},[118,13735,230],{"class":124},[118,13737,7260],{"class":226},[118,13739,25],{"class":124},[118,13741,12975],{"class":213},[118,13743,13744],{"class":124},"(nil,",[118,13746,1146],{"class":124},[118,13748,13749],{"class":433},"Hello, World!",[118,13751,430],{"class":124},[118,13753,7902],{"class":124},[118,13755,1391],{"class":226},[118,13757,1413],{"class":124},[118,13759,1416],{"class":124},[118,13761,220],{"class":124},[118,13763,13764,13766,13768,13770,13772,13774],{"class":120,"line":537},[118,13765,5818],{"class":226},[118,13767,25],{"class":124},[118,13769,5823],{"class":213},[118,13771,255],{"class":124},[118,13773,1429],{"class":226},[118,13775,199],{"class":124},[118,13777,13778],{"class":120,"line":566},[118,13779,1375],{"class":124},[118,13781,13782],{"class":120,"line":571},[118,13783,136],{"emptyLinePlaceholder":135},[118,13785,13786,13788,13790,13792,13794,13796,13799,13801,13803,13806,13808,13810,13812,13814,13816],{"class":120,"line":577},[118,13787,1408],{"class":142},[118,13789,1391],{"class":226},[118,13791,230],{"class":124},[118,13793,7260],{"class":226},[118,13795,25],{"class":124},[118,13797,13798],{"class":213},"WritePdf",[118,13800,255],{"class":124},[118,13802,430],{"class":124},[118,13804,13805],{"class":433},"hello.pdf",[118,13807,430],{"class":124},[118,13809,7902],{"class":124},[118,13811,1391],{"class":226},[118,13813,1413],{"class":124},[118,13815,1416],{"class":124},[118,13817,220],{"class":124},[118,13819,13820,13822,13824,13826,13828,13830],{"class":120,"line":602},[118,13821,5818],{"class":226},[118,13823,25],{"class":124},[118,13825,5823],{"class":213},[118,13827,255],{"class":124},[118,13829,1429],{"class":226},[118,13831,199],{"class":124},[118,13833,13834],{"class":120,"line":633},[118,13835,1375],{"class":124},[118,13837,13838],{"class":120,"line":668},[118,13839,1479],{"class":124},[14,13841,13842],{},[1629,13843,13844],{},"After — gpdf:",[109,13846,13848],{"className":111,"code":13847,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Hello, World!\", template.FontSize(24), template.Bold())\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"hello.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,13849,13850,13856,13860,13866,13874,13882,13886,13894,13902,13910,13914,13918,13928,13942,13960,13990,13994,13998,14012,14036,14066,14104,14108,14112,14116,14134,14146,14160,14164,14204,14218,14222],{"__ignoreMap":114},[118,13851,13852,13854],{"class":120,"line":121},[118,13853,125],{"class":124},[118,13855,129],{"class":128},[118,13857,13858],{"class":120,"line":132},[118,13859,136],{"emptyLinePlaceholder":135},[118,13861,13862,13864],{"class":120,"line":139},[118,13863,143],{"class":142},[118,13865,146],{"class":124},[118,13867,13868,13870,13872],{"class":120,"line":149},[118,13869,152],{"class":124},[118,13871,5303],{"class":128},[118,13873,158],{"class":124},[118,13875,13876,13878,13880],{"class":120,"line":161},[118,13877,152],{"class":124},[118,13879,155],{"class":128},[118,13881,158],{"class":124},[118,13883,13884],{"class":120,"line":166},[118,13885,136],{"emptyLinePlaceholder":135},[118,13887,13888,13890,13892],{"class":120,"line":176},[118,13889,152],{"class":124},[118,13891,3203],{"class":128},[118,13893,158],{"class":124},[118,13895,13896,13898,13900],{"class":120,"line":186},[118,13897,152],{"class":124},[118,13899,171],{"class":128},[118,13901,158],{"class":124},[118,13903,13904,13906,13908],{"class":120,"line":196},[118,13905,152],{"class":124},[118,13907,191],{"class":128},[118,13909,158],{"class":124},[118,13911,13912],{"class":120,"line":202},[118,13913,199],{"class":124},[118,13915,13916],{"class":120,"line":207},[118,13917,136],{"emptyLinePlaceholder":135},[118,13919,13920,13922,13924,13926],{"class":120,"line":223},[118,13921,210],{"class":124},[118,13923,214],{"class":213},[118,13925,217],{"class":124},[118,13927,220],{"class":124},[118,13929,13930,13932,13934,13936,13938,13940],{"class":120,"line":244},[118,13931,227],{"class":226},[118,13933,230],{"class":124},[118,13935,3595],{"class":226},[118,13937,25],{"class":124},[118,13939,3600],{"class":213},[118,13941,241],{"class":124},[118,13943,13944,13946,13948,13950,13952,13954,13956,13958],{"class":120,"line":269},[118,13945,3607],{"class":226},[118,13947,25],{"class":124},[118,13949,252],{"class":213},[118,13951,255],{"class":124},[118,13953,258],{"class":226},[118,13955,25],{"class":124},[118,13957,263],{"class":226},[118,13959,266],{"class":124},[118,13961,13962,13964,13966,13968,13970,13972,13974,13976,13978,13980,13982,13984,13986,13988],{"class":120,"line":306},[118,13963,3607],{"class":226},[118,13965,25],{"class":124},[118,13967,276],{"class":213},[118,13969,255],{"class":124},[118,13971,258],{"class":226},[118,13973,25],{"class":124},[118,13975,285],{"class":213},[118,13977,255],{"class":124},[118,13979,258],{"class":226},[118,13981,25],{"class":124},[118,13983,294],{"class":213},[118,13985,255],{"class":124},[118,13987,300],{"class":299},[118,13989,303],{"class":124},[118,13991,13992],{"class":120,"line":312},[118,13993,309],{"class":124},[118,13995,13996],{"class":120,"line":317},[118,13997,136],{"emptyLinePlaceholder":135},[118,13999,14000,14002,14004,14006,14008,14010],{"class":120,"line":350},[118,14001,5431],{"class":226},[118,14003,230],{"class":124},[118,14005,1185],{"class":226},[118,14007,25],{"class":124},[118,14009,1190],{"class":213},[118,14011,1193],{"class":124},[118,14013,14014,14016,14018,14020,14022,14024,14026,14028,14030,14032,14034],{"class":120,"line":379},[118,14015,5494],{"class":226},[118,14017,25],{"class":124},[118,14019,358],{"class":213},[118,14021,328],{"class":124},[118,14023,363],{"class":331},[118,14025,334],{"class":124},[118,14027,337],{"class":128},[118,14029,25],{"class":124},[118,14031,372],{"class":128},[118,14033,345],{"class":124},[118,14035,220],{"class":124},[118,14037,14038,14040,14042,14044,14046,14048,14050,14052,14054,14056,14058,14060,14062,14064],{"class":120,"line":417},[118,14039,1737],{"class":226},[118,14041,25],{"class":124},[118,14043,387],{"class":213},[118,14045,255],{"class":124},[118,14047,20],{"class":299},[118,14049,395],{"class":124},[118,14051,398],{"class":124},[118,14053,401],{"class":331},[118,14055,334],{"class":124},[118,14057,337],{"class":128},[118,14059,25],{"class":124},[118,14061,410],{"class":128},[118,14063,345],{"class":124},[118,14065,220],{"class":124},[118,14067,14068,14070,14072,14074,14076,14078,14080,14082,14084,14086,14088,14090,14092,14094,14096,14098,14100,14102],{"class":120,"line":466},[118,14069,1768],{"class":226},[118,14071,25],{"class":124},[118,14073,425],{"class":213},[118,14075,255],{"class":124},[118,14077,430],{"class":124},[118,14079,13749],{"class":433},[118,14081,430],{"class":124},[118,14083,395],{"class":124},[118,14085,233],{"class":226},[118,14087,25],{"class":124},[118,14089,455],{"class":213},[118,14091,255],{"class":124},[118,14093,3777],{"class":299},[118,14095,1280],{"class":124},[118,14097,233],{"class":226},[118,14099,25],{"class":124},[118,14101,445],{"class":213},[118,14103,1289],{"class":124},[118,14105,14106],{"class":120,"line":472},[118,14107,574],{"class":124},[118,14109,14110],{"class":120,"line":503},[118,14111,706],{"class":124},[118,14113,14114],{"class":120,"line":537},[118,14115,136],{"emptyLinePlaceholder":135},[118,14117,14118,14120,14122,14124,14126,14128,14130,14132],{"class":120,"line":566},[118,14119,5787],{"class":226},[118,14121,395],{"class":124},[118,14123,1391],{"class":226},[118,14125,230],{"class":124},[118,14127,1185],{"class":226},[118,14129,25],{"class":124},[118,14131,1400],{"class":213},[118,14133,1193],{"class":124},[118,14135,14136,14138,14140,14142,14144],{"class":120,"line":571},[118,14137,1408],{"class":142},[118,14139,1391],{"class":226},[118,14141,1413],{"class":124},[118,14143,1416],{"class":124},[118,14145,220],{"class":124},[118,14147,14148,14150,14152,14154,14156,14158],{"class":120,"line":577},[118,14149,5818],{"class":226},[118,14151,25],{"class":124},[118,14153,5823],{"class":213},[118,14155,255],{"class":124},[118,14157,1429],{"class":226},[118,14159,199],{"class":124},[118,14161,14162],{"class":120,"line":602},[118,14163,1375],{"class":124},[118,14165,14166,14168,14170,14172,14174,14176,14178,14180,14182,14184,14186,14188,14190,14192,14194,14196,14198,14200,14202],{"class":120,"line":633},[118,14167,1408],{"class":142},[118,14169,1391],{"class":226},[118,14171,230],{"class":124},[118,14173,1447],{"class":226},[118,14175,25],{"class":124},[118,14177,1452],{"class":213},[118,14179,255],{"class":124},[118,14181,430],{"class":124},[118,14183,13805],{"class":433},[118,14185,430],{"class":124},[118,14187,395],{"class":124},[118,14189,5859],{"class":226},[118,14191,395],{"class":124},[118,14193,1471],{"class":299},[118,14195,7902],{"class":124},[118,14197,1391],{"class":226},[118,14199,1413],{"class":124},[118,14201,1416],{"class":124},[118,14203,220],{"class":124},[118,14205,14206,14208,14210,14212,14214,14216],{"class":120,"line":668},[118,14207,5818],{"class":226},[118,14209,25],{"class":124},[118,14211,5823],{"class":213},[118,14213,255],{"class":124},[118,14215,1429],{"class":226},[118,14217,199],{"class":124},[118,14219,14220],{"class":120,"line":693},[118,14221,1375],{"class":124},[118,14223,14224],{"class":120,"line":698},[118,14225,1479],{"class":124},[14,14227,14228,14229,14232],{},"Two things gone. The TTF file is no longer required at runtime — Helvetica is part of the standard 14 fonts, and gpdf bundles them. The ",[18,14230,14231],{},"SetX(40); SetY(80)"," is gone — the row sits inside the page margins automatically. What's added: a row with one column spanning all 12 units. That row scaffolding looks heavy for \"Hello, World!\", but it's the same scaffolding that handles a 100-page report, which is the whole point.",[41,14234,14236],{"id":14235},"before-after-2-a-4-column-header-row","Before / After 2: a 4-column header row",[14,14238,14239],{},"This is the place coordinate math is most visible. You want a header strip across the page with four equal cells: page width minus margins, divided by four. In gopdf you do that division. In gpdf you spend 12 units four ways.",[14,14241,14242],{},[1629,14243,13442],{},[109,14245,14247],{"className":111,"code":14246,"language":113,"meta":114,"style":114},"const (\n    pageWidth   = 595.28 // A4 in points\n    leftMargin  = 40.0\n    rightMargin = 40.0\n    rowY        = 100.0\n    rowH        = 24.0\n)\n\ncontentWidth := pageWidth - leftMargin - rightMargin // 515.28\ncolW := contentWidth / 4                              // 128.82\n\npdf.SetFont(\"helvetica-bold\", \"\", 11)\npdf.SetFillColor(26, 35, 126)\npdf.SetTextColor(255, 255, 255)\n\nheaders := []string{\"Description\", \"Qty\", \"Unit\", \"Amount\"}\nfor i, h := range headers {\n    x := leftMargin + colW*float64(i)\n    pdf.RectFromUpperLeftWithStyle(x, rowY, colW, rowH, \"F\")\n\n    pdf.SetXY(x+6, rowY+7)\n    if err := pdf.Cell(nil, h); err != nil {\n        log.Fatal(err)\n    }\n}\n\npdf.SetTextColor(0, 0, 0)\n",[18,14248,14249,14256,14269,14279,14288,14298,14308,14312,14316,14339,14357,14361,14389,14414,14439,14443,14491,14512,14536,14575,14579,14605,14634,14648,14652,14656,14660],{"__ignoreMap":114},[118,14250,14251,14254],{"class":120,"line":121},[118,14252,14253],{"class":124},"const",[118,14255,146],{"class":124},[118,14257,14258,14261,14263,14266],{"class":120,"line":132},[118,14259,14260],{"class":226},"    pageWidth   ",[118,14262,1366],{"class":124},[118,14264,14265],{"class":299}," 595.28",[118,14267,14268],{"class":3981}," // A4 in points\n",[118,14270,14271,14274,14276],{"class":120,"line":139},[118,14272,14273],{"class":226},"    leftMargin  ",[118,14275,1366],{"class":124},[118,14277,14278],{"class":299}," 40.0\n",[118,14280,14281,14284,14286],{"class":120,"line":149},[118,14282,14283],{"class":226},"    rightMargin ",[118,14285,1366],{"class":124},[118,14287,14278],{"class":299},[118,14289,14290,14293,14295],{"class":120,"line":161},[118,14291,14292],{"class":226},"    rowY        ",[118,14294,1366],{"class":124},[118,14296,14297],{"class":299}," 100.0\n",[118,14299,14300,14303,14305],{"class":120,"line":166},[118,14301,14302],{"class":226},"    rowH        ",[118,14304,1366],{"class":124},[118,14306,14307],{"class":299}," 24.0\n",[118,14309,14310],{"class":120,"line":176},[118,14311,199],{"class":124},[118,14313,14314],{"class":120,"line":186},[118,14315,136],{"emptyLinePlaceholder":135},[118,14317,14318,14321,14323,14326,14328,14331,14333,14336],{"class":120,"line":196},[118,14319,14320],{"class":226},"contentWidth ",[118,14322,230],{"class":124},[118,14324,14325],{"class":226}," pageWidth ",[118,14327,7430],{"class":124},[118,14329,14330],{"class":226}," leftMargin ",[118,14332,7430],{"class":124},[118,14334,14335],{"class":226}," rightMargin ",[118,14337,14338],{"class":3981},"// 515.28\n",[118,14340,14341,14344,14346,14349,14351,14354],{"class":120,"line":202},[118,14342,14343],{"class":226},"colW ",[118,14345,230],{"class":124},[118,14347,14348],{"class":226}," contentWidth ",[118,14350,1579],{"class":124},[118,14352,14353],{"class":299}," 4",[118,14355,14356],{"class":3981},"                              // 128.82\n",[118,14358,14359],{"class":120,"line":207},[118,14360,136],{"emptyLinePlaceholder":135},[118,14362,14363,14365,14367,14369,14371,14373,14376,14378,14380,14382,14384,14387],{"class":120,"line":223},[118,14364,550],{"class":226},[118,14366,25],{"class":124},[118,14368,13647],{"class":213},[118,14370,255],{"class":124},[118,14372,430],{"class":124},[118,14374,14375],{"class":433},"helvetica-bold",[118,14377,430],{"class":124},[118,14379,395],{"class":124},[118,14381,13660],{"class":124},[118,14383,395],{"class":124},[118,14385,14386],{"class":299}," 11",[118,14388,199],{"class":124},[118,14390,14391,14393,14395,14397,14399,14402,14404,14407,14409,14412],{"class":120,"line":244},[118,14392,550],{"class":226},[118,14394,25],{"class":124},[118,14396,11349],{"class":213},[118,14398,255],{"class":124},[118,14400,14401],{"class":299},"26",[118,14403,395],{"class":124},[118,14405,14406],{"class":299}," 35",[118,14408,395],{"class":124},[118,14410,14411],{"class":299}," 126",[118,14413,199],{"class":124},[118,14415,14416,14418,14420,14423,14425,14428,14430,14433,14435,14437],{"class":120,"line":269},[118,14417,550],{"class":226},[118,14419,25],{"class":124},[118,14421,14422],{"class":213},"SetTextColor",[118,14424,255],{"class":124},[118,14426,14427],{"class":299},"255",[118,14429,395],{"class":124},[118,14431,14432],{"class":299}," 255",[118,14434,395],{"class":124},[118,14436,14432],{"class":299},[118,14438,199],{"class":124},[118,14440,14441],{"class":120,"line":306},[118,14442,136],{"emptyLinePlaceholder":135},[118,14444,14445,14448,14450,14452,14454,14456,14458,14461,14463,14465,14467,14470,14472,14474,14476,14479,14481,14483,14485,14487,14489],{"class":120,"line":312},[118,14446,14447],{"class":226},"headers ",[118,14449,230],{"class":124},[118,14451,1127],{"class":124},[118,14453,1131],{"class":1130},[118,14455,1134],{"class":124},[118,14457,430],{"class":124},[118,14459,14460],{"class":433},"Description",[118,14462,430],{"class":124},[118,14464,395],{"class":124},[118,14466,1146],{"class":124},[118,14468,14469],{"class":433},"Qty",[118,14471,430],{"class":124},[118,14473,395],{"class":124},[118,14475,1146],{"class":124},[118,14477,14478],{"class":433},"Unit",[118,14480,430],{"class":124},[118,14482,395],{"class":124},[118,14484,1146],{"class":124},[118,14486,7320],{"class":433},[118,14488,430],{"class":124},[118,14490,1479],{"class":124},[118,14492,14493,14496,14498,14500,14503,14505,14507,14510],{"class":120,"line":317},[118,14494,14495],{"class":142},"for",[118,14497,1114],{"class":226},[118,14499,395],{"class":124},[118,14501,14502],{"class":226}," h ",[118,14504,230],{"class":124},[118,14506,1124],{"class":142},[118,14508,14509],{"class":226}," headers ",[118,14511,7406],{"class":124},[118,14513,14514,14517,14519,14521,14523,14526,14528,14530,14532,14534],{"class":120,"line":350},[118,14515,14516],{"class":226},"    x ",[118,14518,230],{"class":124},[118,14520,14330],{"class":226},[118,14522,1339],{"class":124},[118,14524,14525],{"class":226}," colW",[118,14527,4981],{"class":124},[118,14529,6621],{"class":1130},[118,14531,255],{"class":124},[118,14533,7441],{"class":226},[118,14535,199],{"class":124},[118,14537,14538,14540,14542,14545,14547,14550,14552,14555,14557,14559,14561,14564,14566,14568,14571,14573],{"class":120,"line":379},[118,14539,13525],{"class":226},[118,14541,25],{"class":124},[118,14543,14544],{"class":213},"RectFromUpperLeftWithStyle",[118,14546,255],{"class":124},[118,14548,14549],{"class":226},"x",[118,14551,395],{"class":124},[118,14553,14554],{"class":226}," rowY",[118,14556,395],{"class":124},[118,14558,14525],{"class":226},[118,14560,395],{"class":124},[118,14562,14563],{"class":226}," rowH",[118,14565,395],{"class":124},[118,14567,1146],{"class":124},[118,14569,14570],{"class":433},"F",[118,14572,430],{"class":124},[118,14574,199],{"class":124},[118,14576,14577],{"class":120,"line":417},[118,14578,136],{"emptyLinePlaceholder":135},[118,14580,14581,14583,14585,14587,14589,14591,14593,14595,14597,14599,14601,14603],{"class":120,"line":466},[118,14582,13525],{"class":226},[118,14584,25],{"class":124},[118,14586,12972],{"class":213},[118,14588,255],{"class":124},[118,14590,14549],{"class":226},[118,14592,1339],{"class":124},[118,14594,392],{"class":299},[118,14596,395],{"class":124},[118,14598,14554],{"class":226},[118,14600,1339],{"class":124},[118,14602,7559],{"class":299},[118,14604,199],{"class":124},[118,14606,14607,14609,14611,14613,14615,14617,14619,14621,14624,14626,14628,14630,14632],{"class":120,"line":472},[118,14608,1408],{"class":142},[118,14610,1391],{"class":226},[118,14612,230],{"class":124},[118,14614,7260],{"class":226},[118,14616,25],{"class":124},[118,14618,12975],{"class":213},[118,14620,13744],{"class":124},[118,14622,14623],{"class":226}," h",[118,14625,7902],{"class":124},[118,14627,1391],{"class":226},[118,14629,1413],{"class":124},[118,14631,1416],{"class":124},[118,14633,220],{"class":124},[118,14635,14636,14638,14640,14642,14644,14646],{"class":120,"line":503},[118,14637,5818],{"class":226},[118,14639,25],{"class":124},[118,14641,5823],{"class":213},[118,14643,255],{"class":124},[118,14645,1429],{"class":226},[118,14647,199],{"class":124},[118,14649,14650],{"class":120,"line":537},[118,14651,1375],{"class":124},[118,14653,14654],{"class":120,"line":566},[118,14655,1479],{"class":124},[118,14657,14658],{"class":120,"line":571},[118,14659,136],{"emptyLinePlaceholder":135},[118,14661,14662,14664,14666,14668,14670,14672,14674,14676,14678,14680],{"class":120,"line":577},[118,14663,550],{"class":226},[118,14665,25],{"class":124},[118,14667,14422],{"class":213},[118,14669,255],{"class":124},[118,14671,4159],{"class":299},[118,14673,395],{"class":124},[118,14675,7344],{"class":299},[118,14677,395],{"class":124},[118,14679,7344],{"class":299},[118,14681,199],{"class":124},[14,14683,14684,14685,14688,14689,14691],{},"There are four constants. There's a width subtraction. There's a division. There's a loop with ",[18,14686,14687],{},"colW*float64(i)"," — and that float cast was only there because Go's ",[18,14690,4981],{}," doesn't promote int to float64. None of those exist in the gpdf version.",[14,14693,14694],{},[1629,14695,13844],{},[109,14697,14699],{"className":111,"code":14698,"language":113,"meta":114,"style":114},"page.AutoRow(func(r *template.RowBuilder) {\n    headers := []string{\"Description\", \"Qty\", \"Unit\", \"Amount\"}\n    for _, h := range headers {\n        r.Col(3, func(c *template.ColBuilder) {\n            c.Box(\n                template.BgColor(pdf.RGBHex(0x1A237E)),\n                template.Padding(document.Mm(2), document.Mm(3)),\n            )\n            c.Text(h,\n                template.Bold(), template.FontSize(11),\n                template.TextColor(pdf.White),\n            )\n        })\n    }\n})\n",[18,14700,14701,14725,14770,14789,14819,14829,14851,14886,14890,14905,14927,14945,14949,14953,14957],{"__ignoreMap":114},[118,14702,14703,14705,14707,14709,14711,14713,14715,14717,14719,14721,14723],{"class":120,"line":121},[118,14704,5910],{"class":226},[118,14706,25],{"class":124},[118,14708,358],{"class":213},[118,14710,328],{"class":124},[118,14712,363],{"class":331},[118,14714,334],{"class":124},[118,14716,337],{"class":128},[118,14718,25],{"class":124},[118,14720,372],{"class":128},[118,14722,345],{"class":124},[118,14724,220],{"class":124},[118,14726,14727,14730,14732,14734,14736,14738,14740,14742,14744,14746,14748,14750,14752,14754,14756,14758,14760,14762,14764,14766,14768],{"class":120,"line":132},[118,14728,14729],{"class":226},"    headers ",[118,14731,230],{"class":124},[118,14733,1127],{"class":124},[118,14735,1131],{"class":1130},[118,14737,1134],{"class":124},[118,14739,430],{"class":124},[118,14741,14460],{"class":433},[118,14743,430],{"class":124},[118,14745,395],{"class":124},[118,14747,1146],{"class":124},[118,14749,14469],{"class":433},[118,14751,430],{"class":124},[118,14753,395],{"class":124},[118,14755,1146],{"class":124},[118,14757,14478],{"class":433},[118,14759,430],{"class":124},[118,14761,395],{"class":124},[118,14763,1146],{"class":124},[118,14765,7320],{"class":433},[118,14767,430],{"class":124},[118,14769,1479],{"class":124},[118,14771,14772,14774,14777,14779,14781,14783,14785,14787],{"class":120,"line":139},[118,14773,1111],{"class":142},[118,14775,14776],{"class":226}," _",[118,14778,395],{"class":124},[118,14780,14502],{"class":226},[118,14782,230],{"class":124},[118,14784,1124],{"class":142},[118,14786,14509],{"class":226},[118,14788,7406],{"class":124},[118,14790,14791,14793,14795,14797,14799,14801,14803,14805,14807,14809,14811,14813,14815,14817],{"class":120,"line":149},[118,14792,1737],{"class":226},[118,14794,25],{"class":124},[118,14796,387],{"class":213},[118,14798,255],{"class":124},[118,14800,688],{"class":299},[118,14802,395],{"class":124},[118,14804,398],{"class":124},[118,14806,401],{"class":331},[118,14808,334],{"class":124},[118,14810,337],{"class":128},[118,14812,25],{"class":124},[118,14814,410],{"class":128},[118,14816,345],{"class":124},[118,14818,220],{"class":124},[118,14820,14821,14823,14825,14827],{"class":120,"line":161},[118,14822,1768],{"class":226},[118,14824,25],{"class":124},[118,14826,4932],{"class":213},[118,14828,241],{"class":124},[118,14830,14831,14833,14835,14837,14839,14841,14843,14845,14847,14849],{"class":120,"line":166},[118,14832,2648],{"class":226},[118,14834,25],{"class":124},[118,14836,7792],{"class":213},[118,14838,255],{"class":124},[118,14840,550],{"class":226},[118,14842,25],{"class":124},[118,14844,658],{"class":213},[118,14846,255],{"class":124},[118,14848,7269],{"class":299},[118,14850,3646],{"class":124},[118,14852,14853,14855,14857,14860,14862,14864,14866,14868,14870,14872,14874,14876,14878,14880,14882,14884],{"class":120,"line":176},[118,14854,2648],{"class":226},[118,14856,25],{"class":124},[118,14858,14859],{"class":213},"Padding",[118,14861,255],{"class":124},[118,14863,258],{"class":226},[118,14865,25],{"class":124},[118,14867,294],{"class":213},[118,14869,255],{"class":124},[118,14871,870],{"class":299},[118,14873,1280],{"class":124},[118,14875,5225],{"class":226},[118,14877,25],{"class":124},[118,14879,294],{"class":213},[118,14881,255],{"class":124},[118,14883,688],{"class":299},[118,14885,3646],{"class":124},[118,14887,14888],{"class":120,"line":186},[118,14889,7809],{"class":124},[118,14891,14892,14894,14896,14898,14900,14903],{"class":120,"line":196},[118,14893,1768],{"class":226},[118,14895,25],{"class":124},[118,14897,425],{"class":213},[118,14899,255],{"class":124},[118,14901,14902],{"class":226},"h",[118,14904,2643],{"class":124},[118,14906,14907,14909,14911,14913,14915,14917,14919,14921,14923,14925],{"class":120,"line":202},[118,14908,2648],{"class":226},[118,14910,25],{"class":124},[118,14912,445],{"class":213},[118,14914,448],{"class":124},[118,14916,233],{"class":226},[118,14918,25],{"class":124},[118,14920,455],{"class":213},[118,14922,255],{"class":124},[118,14924,3893],{"class":299},[118,14926,266],{"class":124},[118,14928,14929,14931,14933,14935,14937,14939,14941,14943],{"class":120,"line":207},[118,14930,2648],{"class":226},[118,14932,25],{"class":124},[118,14934,545],{"class":213},[118,14936,255],{"class":124},[118,14938,550],{"class":226},[118,14940,25],{"class":124},[118,14942,7781],{"class":226},[118,14944,266],{"class":124},[118,14946,14947],{"class":120,"line":223},[118,14948,7809],{"class":124},[118,14950,14951],{"class":120,"line":244},[118,14952,574],{"class":124},[118,14954,14955],{"class":120,"line":269},[118,14956,1375],{"class":124},[118,14958,14959],{"class":120,"line":306},[118,14960,1944],{"class":124},[14,14962,14963,14965,14966,14969,14970,1649,14972,14974,14975,14978],{},[18,14964,13061],{}," four times sums to 12. The grid handles widths. If you change A4 to Letter, or shrink margins, the header still tiles correctly because nothing in this code depends on ",[18,14967,14968],{},"pageWidth"," at all. If you decide column 1 should be twice as wide as the other three, change ",[18,14971,13061],{},[18,14973,11193],{}," for that one and one of the others to ",[18,14976,14977],{},"r.Col(2, ...)",". No arithmetic.",[41,14980,14982],{"id":14981},"before-after-3-an-invoice-table-that-breaks-across-pages","Before / After 3: an invoice table that breaks across pages",[14,14984,14985,14986,14988],{},"The big one. In gopdf, drawing a table that flows over multiple pages is mostly bookkeeping: you track the current y, draw each row, check if the next row will fit, and if not, call ",[18,14987,1190],{}," and reprint the header. The state machine is in your code.",[14,14990,14991],{},[1629,14992,13442],{},[109,14994,14996],{"className":111,"code":14995,"language":113,"meta":114,"style":114},"func drawInvoiceTable(pdf *gopdf.GoPdf, items [][4]string) error {\n    const (\n        pageH       = 841.89 // A4 height\n        bottomLimit = pageH - 40\n        rowH        = 22.0\n        leftX       = 40.0\n        widths      = 4\n    )\n    cols := []float64{260, 80, 80, 95} // Description, Qty, Unit, Amount\n\n    // Header function we'll call on first page and after page breaks.\n    drawHeader := func(y float64) float64 {\n        pdf.SetFont(\"helvetica-bold\", \"\", 11)\n        pdf.SetFillColor(26, 35, 126)\n        pdf.SetTextColor(255, 255, 255)\n        x := leftX\n        for i, h := range []string{\"Description\", \"Qty\", \"Unit\", \"Amount\"} {\n            pdf.RectFromUpperLeftWithStyle(x, y, cols[i], rowH, \"F\")\n            pdf.SetXY(x+6, y+7)\n            if err := pdf.Cell(nil, h); err != nil {\n                log.Println(err)\n            }\n            x += cols[i]\n        }\n        pdf.SetTextColor(0, 0, 0)\n        pdf.SetFont(\"helvetica\", \"\", 11)\n        return y + rowH\n    }\n\n    y := drawHeader(100)\n    for _, row := range items {\n        if y+rowH > bottomLimit {\n            pdf.AddPage()\n            y = drawHeader(60)\n        }\n\n        x := leftX\n        for i, cell := range row {\n            pdf.RectFromUpperLeftWithStyle(x, y, cols[i], rowH, \"D\") // border only\n            pdf.SetXY(x+6, y+7)\n            if err := pdf.Cell(nil, cell); err != nil {\n                return err\n            }\n            x += cols[i]\n        }\n        y += rowH\n    }\n    return nil\n}\n",[18,14997,14998,15039,15046,15059,15074,15084,15093,15103,15107,15142,15146,15151,15171,15198,15220,15242,15252,15307,15350,15376,15405,15421,15426,15443,15448,15470,15496,15509,15513,15517,15534,15554,15574,15584,15599,15603,15607,15615,15634,15676,15702,15731,15739,15743,15757,15761,15770,15774,15782],{"__ignoreMap":114},[118,14999,15000,15002,15005,15007,15009,15011,15013,15015,15017,15019,15022,15025,15027,15030,15032,15034,15037],{"class":120,"line":121},[118,15001,210],{"class":124},[118,15003,15004],{"class":213}," drawInvoiceTable",[118,15006,255],{"class":124},[118,15008,550],{"class":331},[118,15010,334],{"class":124},[118,15012,11345],{"class":128},[118,15014,25],{"class":124},[118,15016,13517],{"class":128},[118,15018,395],{"class":124},[118,15020,15021],{"class":331}," items",[118,15023,15024],{"class":124}," [][",[118,15026,1493],{"class":299},[118,15028,15029],{"class":124},"]",[118,15031,1131],{"class":1130},[118,15033,345],{"class":124},[118,15035,15036],{"class":1130}," error",[118,15038,220],{"class":124},[118,15040,15041,15044],{"class":120,"line":132},[118,15042,15043],{"class":124},"    const",[118,15045,146],{"class":124},[118,15047,15048,15051,15053,15056],{"class":120,"line":139},[118,15049,15050],{"class":226},"        pageH       ",[118,15052,1366],{"class":124},[118,15054,15055],{"class":299}," 841.89",[118,15057,15058],{"class":3981}," // A4 height\n",[118,15060,15061,15064,15066,15069,15071],{"class":120,"line":149},[118,15062,15063],{"class":226},"        bottomLimit ",[118,15065,1366],{"class":124},[118,15067,15068],{"class":226}," pageH ",[118,15070,7430],{"class":124},[118,15072,15073],{"class":299}," 40\n",[118,15075,15076,15079,15081],{"class":120,"line":161},[118,15077,15078],{"class":226},"        rowH        ",[118,15080,1366],{"class":124},[118,15082,15083],{"class":299}," 22.0\n",[118,15085,15086,15089,15091],{"class":120,"line":166},[118,15087,15088],{"class":226},"        leftX       ",[118,15090,1366],{"class":124},[118,15092,14278],{"class":299},[118,15094,15095,15098,15100],{"class":120,"line":176},[118,15096,15097],{"class":226},"        widths      ",[118,15099,1366],{"class":124},[118,15101,15102],{"class":299}," 4\n",[118,15104,15105],{"class":120,"line":186},[118,15106,309],{"class":124},[118,15108,15109,15112,15114,15116,15118,15120,15123,15125,15128,15130,15132,15134,15137,15139],{"class":120,"line":196},[118,15110,15111],{"class":226},"    cols ",[118,15113,230],{"class":124},[118,15115,1127],{"class":124},[118,15117,6621],{"class":1130},[118,15119,1134],{"class":124},[118,15121,15122],{"class":299},"260",[118,15124,395],{"class":124},[118,15126,15127],{"class":299}," 80",[118,15129,395],{"class":124},[118,15131,15127],{"class":299},[118,15133,395],{"class":124},[118,15135,15136],{"class":299}," 95",[118,15138,1172],{"class":124},[118,15140,15141],{"class":3981}," // Description, Qty, Unit, Amount\n",[118,15143,15144],{"class":120,"line":202},[118,15145,136],{"emptyLinePlaceholder":135},[118,15147,15148],{"class":120,"line":207},[118,15149,15150],{"class":3981},"    // Header function we'll call on first page and after page breaks.\n",[118,15152,15153,15156,15158,15160,15163,15165,15167,15169],{"class":120,"line":223},[118,15154,15155],{"class":226},"    drawHeader ",[118,15157,230],{"class":124},[118,15159,398],{"class":124},[118,15161,15162],{"class":331},"y",[118,15164,6638],{"class":1130},[118,15166,345],{"class":124},[118,15168,6638],{"class":1130},[118,15170,220],{"class":124},[118,15172,15173,15176,15178,15180,15182,15184,15186,15188,15190,15192,15194,15196],{"class":120,"line":244},[118,15174,15175],{"class":226},"        pdf",[118,15177,25],{"class":124},[118,15179,13647],{"class":213},[118,15181,255],{"class":124},[118,15183,430],{"class":124},[118,15185,14375],{"class":433},[118,15187,430],{"class":124},[118,15189,395],{"class":124},[118,15191,13660],{"class":124},[118,15193,395],{"class":124},[118,15195,14386],{"class":299},[118,15197,199],{"class":124},[118,15199,15200,15202,15204,15206,15208,15210,15212,15214,15216,15218],{"class":120,"line":269},[118,15201,15175],{"class":226},[118,15203,25],{"class":124},[118,15205,11349],{"class":213},[118,15207,255],{"class":124},[118,15209,14401],{"class":299},[118,15211,395],{"class":124},[118,15213,14406],{"class":299},[118,15215,395],{"class":124},[118,15217,14411],{"class":299},[118,15219,199],{"class":124},[118,15221,15222,15224,15226,15228,15230,15232,15234,15236,15238,15240],{"class":120,"line":306},[118,15223,15175],{"class":226},[118,15225,25],{"class":124},[118,15227,14422],{"class":213},[118,15229,255],{"class":124},[118,15231,14427],{"class":299},[118,15233,395],{"class":124},[118,15235,14432],{"class":299},[118,15237,395],{"class":124},[118,15239,14432],{"class":299},[118,15241,199],{"class":124},[118,15243,15244,15247,15249],{"class":120,"line":312},[118,15245,15246],{"class":226},"        x ",[118,15248,230],{"class":124},[118,15250,15251],{"class":226}," leftX\n",[118,15253,15254,15257,15259,15261,15263,15265,15267,15269,15271,15273,15275,15277,15279,15281,15283,15285,15287,15289,15291,15293,15295,15297,15299,15301,15303,15305],{"class":120,"line":317},[118,15255,15256],{"class":142},"        for",[118,15258,1114],{"class":226},[118,15260,395],{"class":124},[118,15262,14502],{"class":226},[118,15264,230],{"class":124},[118,15266,1124],{"class":142},[118,15268,1127],{"class":124},[118,15270,1131],{"class":1130},[118,15272,1134],{"class":124},[118,15274,430],{"class":124},[118,15276,14460],{"class":433},[118,15278,430],{"class":124},[118,15280,395],{"class":124},[118,15282,1146],{"class":124},[118,15284,14469],{"class":433},[118,15286,430],{"class":124},[118,15288,395],{"class":124},[118,15290,1146],{"class":124},[118,15292,14478],{"class":433},[118,15294,430],{"class":124},[118,15296,395],{"class":124},[118,15298,1146],{"class":124},[118,15300,7320],{"class":433},[118,15302,430],{"class":124},[118,15304,1172],{"class":124},[118,15306,220],{"class":124},[118,15308,15309,15312,15314,15316,15318,15320,15322,15325,15327,15330,15333,15335,15338,15340,15342,15344,15346,15348],{"class":120,"line":350},[118,15310,15311],{"class":226},"            pdf",[118,15313,25],{"class":124},[118,15315,14544],{"class":213},[118,15317,255],{"class":124},[118,15319,14549],{"class":226},[118,15321,395],{"class":124},[118,15323,15324],{"class":226}," y",[118,15326,395],{"class":124},[118,15328,15329],{"class":226}," cols",[118,15331,15332],{"class":124},"[",[118,15334,7441],{"class":226},[118,15336,15337],{"class":124},"],",[118,15339,14563],{"class":226},[118,15341,395],{"class":124},[118,15343,1146],{"class":124},[118,15345,14570],{"class":433},[118,15347,430],{"class":124},[118,15349,199],{"class":124},[118,15351,15352,15354,15356,15358,15360,15362,15364,15366,15368,15370,15372,15374],{"class":120,"line":379},[118,15353,15311],{"class":226},[118,15355,25],{"class":124},[118,15357,12972],{"class":213},[118,15359,255],{"class":124},[118,15361,14549],{"class":226},[118,15363,1339],{"class":124},[118,15365,392],{"class":299},[118,15367,395],{"class":124},[118,15369,15324],{"class":226},[118,15371,1339],{"class":124},[118,15373,7559],{"class":299},[118,15375,199],{"class":124},[118,15377,15378,15381,15383,15385,15387,15389,15391,15393,15395,15397,15399,15401,15403],{"class":120,"line":417},[118,15379,15380],{"class":142},"            if",[118,15382,1391],{"class":226},[118,15384,230],{"class":124},[118,15386,7260],{"class":226},[118,15388,25],{"class":124},[118,15390,12975],{"class":213},[118,15392,13744],{"class":124},[118,15394,14623],{"class":226},[118,15396,7902],{"class":124},[118,15398,1391],{"class":226},[118,15400,1413],{"class":124},[118,15402,1416],{"class":124},[118,15404,220],{"class":124},[118,15406,15407,15410,15412,15415,15417,15419],{"class":120,"line":466},[118,15408,15409],{"class":226},"                log",[118,15411,25],{"class":124},[118,15413,15414],{"class":213},"Println",[118,15416,255],{"class":124},[118,15418,1429],{"class":226},[118,15420,199],{"class":124},[118,15422,15423],{"class":120,"line":472},[118,15424,15425],{"class":124},"            }\n",[118,15427,15428,15431,15434,15436,15438,15440],{"class":120,"line":503},[118,15429,15430],{"class":226},"            x ",[118,15432,15433],{"class":124},"+=",[118,15435,15329],{"class":226},[118,15437,15332],{"class":124},[118,15439,7441],{"class":226},[118,15441,15442],{"class":124},"]\n",[118,15444,15445],{"class":120,"line":537},[118,15446,15447],{"class":124},"        }\n",[118,15449,15450,15452,15454,15456,15458,15460,15462,15464,15466,15468],{"class":120,"line":566},[118,15451,15175],{"class":226},[118,15453,25],{"class":124},[118,15455,14422],{"class":213},[118,15457,255],{"class":124},[118,15459,4159],{"class":299},[118,15461,395],{"class":124},[118,15463,7344],{"class":299},[118,15465,395],{"class":124},[118,15467,7344],{"class":299},[118,15469,199],{"class":124},[118,15471,15472,15474,15476,15478,15480,15482,15484,15486,15488,15490,15492,15494],{"class":120,"line":571},[118,15473,15175],{"class":226},[118,15475,25],{"class":124},[118,15477,13647],{"class":213},[118,15479,255],{"class":124},[118,15481,430],{"class":124},[118,15483,13593],{"class":433},[118,15485,430],{"class":124},[118,15487,395],{"class":124},[118,15489,13660],{"class":124},[118,15491,395],{"class":124},[118,15493,14386],{"class":299},[118,15495,199],{"class":124},[118,15497,15498,15501,15504,15506],{"class":120,"line":577},[118,15499,15500],{"class":142},"        return",[118,15502,15503],{"class":226}," y ",[118,15505,1339],{"class":124},[118,15507,15508],{"class":226}," rowH\n",[118,15510,15511],{"class":120,"line":602},[118,15512,1375],{"class":124},[118,15514,15515],{"class":120,"line":633},[118,15516,136],{"emptyLinePlaceholder":135},[118,15518,15519,15522,15524,15527,15529,15532],{"class":120,"line":668},[118,15520,15521],{"class":226},"    y ",[118,15523,230],{"class":124},[118,15525,15526],{"class":213}," drawHeader",[118,15528,255],{"class":124},[118,15530,15531],{"class":299},"100",[118,15533,199],{"class":124},[118,15535,15536,15538,15540,15542,15545,15547,15549,15552],{"class":120,"line":693},[118,15537,1111],{"class":142},[118,15539,14776],{"class":226},[118,15541,395],{"class":124},[118,15543,15544],{"class":226}," row ",[118,15546,230],{"class":124},[118,15548,1124],{"class":142},[118,15550,15551],{"class":226}," items ",[118,15553,7406],{"class":124},[118,15555,15556,15559,15561,15563,15566,15569,15572],{"class":120,"line":698},[118,15557,15558],{"class":142},"        if",[118,15560,15324],{"class":226},[118,15562,1339],{"class":124},[118,15564,15565],{"class":226},"rowH ",[118,15567,15568],{"class":124},">",[118,15570,15571],{"class":226}," bottomLimit ",[118,15573,7406],{"class":124},[118,15575,15576,15578,15580,15582],{"class":120,"line":703},[118,15577,15311],{"class":226},[118,15579,25],{"class":124},[118,15581,1190],{"class":213},[118,15583,1193],{"class":124},[118,15585,15586,15589,15591,15593,15595,15597],{"class":120,"line":709},[118,15587,15588],{"class":226},"            y ",[118,15590,1366],{"class":124},[118,15592,15526],{"class":213},[118,15594,255],{"class":124},[118,15596,31],{"class":299},[118,15598,199],{"class":124},[118,15600,15601],{"class":120,"line":714},[118,15602,15447],{"class":124},[118,15604,15605],{"class":120,"line":740},[118,15606,136],{"emptyLinePlaceholder":135},[118,15608,15609,15611,15613],{"class":120,"line":765},[118,15610,15246],{"class":226},[118,15612,230],{"class":124},[118,15614,15251],{"class":226},[118,15616,15617,15619,15621,15623,15626,15628,15630,15632],{"class":120,"line":796},[118,15618,15256],{"class":142},[118,15620,1114],{"class":226},[118,15622,395],{"class":124},[118,15624,15625],{"class":226}," cell ",[118,15627,230],{"class":124},[118,15629,1124],{"class":142},[118,15631,15544],{"class":226},[118,15633,7406],{"class":124},[118,15635,15636,15638,15640,15642,15644,15646,15648,15650,15652,15654,15656,15658,15660,15662,15664,15666,15669,15671,15673],{"class":120,"line":819},[118,15637,15311],{"class":226},[118,15639,25],{"class":124},[118,15641,14544],{"class":213},[118,15643,255],{"class":124},[118,15645,14549],{"class":226},[118,15647,395],{"class":124},[118,15649,15324],{"class":226},[118,15651,395],{"class":124},[118,15653,15329],{"class":226},[118,15655,15332],{"class":124},[118,15657,7441],{"class":226},[118,15659,15337],{"class":124},[118,15661,14563],{"class":226},[118,15663,395],{"class":124},[118,15665,1146],{"class":124},[118,15667,15668],{"class":433},"D",[118,15670,430],{"class":124},[118,15672,345],{"class":124},[118,15674,15675],{"class":3981}," // border only\n",[118,15677,15678,15680,15682,15684,15686,15688,15690,15692,15694,15696,15698,15700],{"class":120,"line":851},[118,15679,15311],{"class":226},[118,15681,25],{"class":124},[118,15683,12972],{"class":213},[118,15685,255],{"class":124},[118,15687,14549],{"class":226},[118,15689,1339],{"class":124},[118,15691,392],{"class":299},[118,15693,395],{"class":124},[118,15695,15324],{"class":226},[118,15697,1339],{"class":124},[118,15699,7559],{"class":299},[118,15701,199],{"class":124},[118,15703,15704,15706,15708,15710,15712,15714,15716,15718,15721,15723,15725,15727,15729],{"class":120,"line":875},[118,15705,15380],{"class":142},[118,15707,1391],{"class":226},[118,15709,230],{"class":124},[118,15711,7260],{"class":226},[118,15713,25],{"class":124},[118,15715,12975],{"class":213},[118,15717,13744],{"class":124},[118,15719,15720],{"class":226}," cell",[118,15722,7902],{"class":124},[118,15724,1391],{"class":226},[118,15726,1413],{"class":124},[118,15728,1416],{"class":124},[118,15730,220],{"class":124},[118,15732,15733,15736],{"class":120,"line":880},[118,15734,15735],{"class":142},"                return",[118,15737,15738],{"class":226}," err\n",[118,15740,15741],{"class":120,"line":885},[118,15742,15425],{"class":124},[118,15744,15745,15747,15749,15751,15753,15755],{"class":120,"line":910},[118,15746,15430],{"class":226},[118,15748,15433],{"class":124},[118,15750,15329],{"class":226},[118,15752,15332],{"class":124},[118,15754,7441],{"class":226},[118,15756,15442],{"class":124},[118,15758,15759],{"class":120,"line":941},[118,15760,15447],{"class":124},[118,15762,15763,15766,15768],{"class":120,"line":974},[118,15764,15765],{"class":226},"        y ",[118,15767,15433],{"class":124},[118,15769,15508],{"class":226},[118,15771,15772],{"class":120,"line":997},[118,15773,1375],{"class":124},[118,15775,15776,15779],{"class":120,"line":1002},[118,15777,15778],{"class":142},"    return",[118,15780,15781],{"class":124}," nil\n",[118,15783,15784],{"class":120,"line":1033},[118,15785,1479],{"class":124},[14,15787,15788,15789,15791],{},"The table-drawing function is 30 lines, and only 5 of them are about the data. The rest is layout: hard-coded heights, a hard-coded bottom limit, a closure to redraw the header after page breaks, two ",[18,15790,14495],{}," loops, two cursor advances per cell. This is the median gopdf table.",[14,15793,15794],{},[1629,15795,13844],{},[109,15797,15799],{"className":111,"code":15798,"language":113,"meta":114,"style":114},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Table(\n            []string{\"Description\", \"Qty\", \"Unit\", \"Amount\"},\n            items, // [][]string\n            template.ColumnWidths(55, 15, 15, 15),\n            template.TableHeaderStyle(\n                template.Bold(),\n                template.TextColor(pdf.White),\n                template.BgColor(pdf.RGBHex(0x1A237E)),\n            ),\n            template.TableStripe(pdf.RGBHex(0xF5F5F5)),\n        )\n    })\n})\n",[18,15800,15801,15825,15855,15865,15906,15916,15943,15953,15963,15981,16003,16008,16030,16034,16038],{"__ignoreMap":114},[118,15802,15803,15805,15807,15809,15811,15813,15815,15817,15819,15821,15823],{"class":120,"line":121},[118,15804,5910],{"class":226},[118,15806,25],{"class":124},[118,15808,358],{"class":213},[118,15810,328],{"class":124},[118,15812,363],{"class":331},[118,15814,334],{"class":124},[118,15816,337],{"class":128},[118,15818,25],{"class":124},[118,15820,372],{"class":128},[118,15822,345],{"class":124},[118,15824,220],{"class":124},[118,15826,15827,15829,15831,15833,15835,15837,15839,15841,15843,15845,15847,15849,15851,15853],{"class":120,"line":132},[118,15828,5935],{"class":226},[118,15830,25],{"class":124},[118,15832,387],{"class":213},[118,15834,255],{"class":124},[118,15836,20],{"class":299},[118,15838,395],{"class":124},[118,15840,398],{"class":124},[118,15842,401],{"class":331},[118,15844,334],{"class":124},[118,15846,337],{"class":128},[118,15848,25],{"class":124},[118,15850,410],{"class":128},[118,15852,345],{"class":124},[118,15854,220],{"class":124},[118,15856,15857,15859,15861,15863],{"class":120,"line":139},[118,15858,5966],{"class":226},[118,15860,25],{"class":124},[118,15862,4929],{"class":213},[118,15864,241],{"class":124},[118,15866,15867,15870,15872,15874,15876,15878,15880,15882,15884,15886,15888,15890,15892,15894,15896,15898,15900,15902,15904],{"class":120,"line":149},[118,15868,15869],{"class":124},"            []",[118,15871,1131],{"class":1130},[118,15873,1134],{"class":124},[118,15875,430],{"class":124},[118,15877,14460],{"class":433},[118,15879,430],{"class":124},[118,15881,395],{"class":124},[118,15883,1146],{"class":124},[118,15885,14469],{"class":433},[118,15887,430],{"class":124},[118,15889,395],{"class":124},[118,15891,1146],{"class":124},[118,15893,14478],{"class":433},[118,15895,430],{"class":124},[118,15897,395],{"class":124},[118,15899,1146],{"class":124},[118,15901,7320],{"class":433},[118,15903,430],{"class":124},[118,15905,8191],{"class":124},[118,15907,15908,15911,15913],{"class":120,"line":161},[118,15909,15910],{"class":226},"            items",[118,15912,395],{"class":124},[118,15914,15915],{"class":3981}," // [][]string\n",[118,15917,15918,15920,15922,15924,15926,15929,15931,15933,15935,15937,15939,15941],{"class":120,"line":166},[118,15919,6061],{"class":226},[118,15921,25],{"class":124},[118,15923,7733],{"class":213},[118,15925,255],{"class":124},[118,15927,15928],{"class":299},"55",[118,15930,395],{"class":124},[118,15932,9915],{"class":299},[118,15934,395],{"class":124},[118,15936,9915],{"class":299},[118,15938,395],{"class":124},[118,15940,9915],{"class":299},[118,15942,266],{"class":124},[118,15944,15945,15947,15949,15951],{"class":120,"line":176},[118,15946,6061],{"class":226},[118,15948,25],{"class":124},[118,15950,7762],{"class":213},[118,15952,241],{"class":124},[118,15954,15955,15957,15959,15961],{"class":120,"line":186},[118,15956,2648],{"class":226},[118,15958,25],{"class":124},[118,15960,445],{"class":213},[118,15962,2655],{"class":124},[118,15964,15965,15967,15969,15971,15973,15975,15977,15979],{"class":120,"line":196},[118,15966,2648],{"class":226},[118,15968,25],{"class":124},[118,15970,545],{"class":213},[118,15972,255],{"class":124},[118,15974,550],{"class":226},[118,15976,25],{"class":124},[118,15978,7781],{"class":226},[118,15980,266],{"class":124},[118,15982,15983,15985,15987,15989,15991,15993,15995,15997,15999,16001],{"class":120,"line":202},[118,15984,2648],{"class":226},[118,15986,25],{"class":124},[118,15988,7792],{"class":213},[118,15990,255],{"class":124},[118,15992,550],{"class":226},[118,15994,25],{"class":124},[118,15996,658],{"class":213},[118,15998,255],{"class":124},[118,16000,7269],{"class":299},[118,16002,3646],{"class":124},[118,16004,16005],{"class":120,"line":207},[118,16006,16007],{"class":124},"            ),\n",[118,16009,16010,16012,16014,16016,16018,16020,16022,16024,16026,16028],{"class":120,"line":223},[118,16011,6061],{"class":226},[118,16013,25],{"class":124},[118,16015,9973],{"class":213},[118,16017,255],{"class":124},[118,16019,550],{"class":226},[118,16021,25],{"class":124},[118,16023,658],{"class":213},[118,16025,255],{"class":124},[118,16027,9986],{"class":299},[118,16029,3646],{"class":124},[118,16031,16032],{"class":120,"line":244},[118,16033,6166],{"class":124},[118,16035,16036],{"class":120,"line":269},[118,16037,706],{"class":124},[118,16039,16040],{"class":120,"line":306},[118,16041,1944],{"class":124},[14,16043,16044,16045,16047],{},"That's it. Page breaks are automatic. The header repeats on every page where the body continues. Striped rows take one option. Column widths are percentages of the container, so this same table inside ",[18,16046,11193],{}," would render at half size with the same proportions, no rewrites. The 25-line gopdf bookkeeping function disappears.",[14,16049,16050,16051,16054,16055,16058],{},"A specific number worth seeing. The 100-row invoice render benchmarks at ",[1629,16052,16053],{},"108 µs"," in gpdf and roughly ",[1629,16056,16057],{},"2.4 ms"," in signintech/gopdf — and the gopdf number depends on the cell-by-cell pattern you wrote, so it varies. The factor isn't the headline; the disappearance of the function is.",[41,16060,16062],{"id":16061},"before-after-4-an-image-positioned-next-to-a-paragraph","Before / After 4: an image positioned next to a paragraph",[14,16064,16065],{},"Common pattern: company logo on the left, address block on the right.",[14,16067,16068],{},[1629,16069,13442],{},[109,16071,16073],{"className":111,"code":16072,"language":113,"meta":114,"style":114},"const (\n    leftX  = 40.0\n    rightX = 380.0\n    blockY = 50.0\n)\n\nif err := pdf.Image(\"logo.png\", leftX, blockY, &gopdf.Rect{W: 100, H: 60}); err != nil {\n    log.Fatal(err)\n}\n\npdf.SetFont(\"helvetica-bold\", \"\", 14)\npdf.SetXY(rightX, blockY)\nif err := pdf.Cell(nil, \"ACME Corporation\"); err != nil {\n    log.Fatal(err)\n}\n\npdf.SetFont(\"helvetica\", \"\", 10)\npdf.SetXY(rightX, blockY+20)\npdf.Cell(nil, \"1 Market Street, Suite 400\")\npdf.SetXY(rightX, blockY+34)\npdf.Cell(nil, \"San Francisco, CA 94103\")\npdf.SetXY(rightX, blockY+48)\npdf.Cell(nil, \"billing@acme.example\")\n",[18,16074,16075,16081,16090,16100,16110,16114,16118,16192,16207,16211,16215,16242,16261,16294,16308,16312,16316,16342,16364,16383,16406,16425,16448],{"__ignoreMap":114},[118,16076,16077,16079],{"class":120,"line":121},[118,16078,14253],{"class":124},[118,16080,146],{"class":124},[118,16082,16083,16086,16088],{"class":120,"line":132},[118,16084,16085],{"class":226},"    leftX  ",[118,16087,1366],{"class":124},[118,16089,14278],{"class":299},[118,16091,16092,16095,16097],{"class":120,"line":139},[118,16093,16094],{"class":226},"    rightX ",[118,16096,1366],{"class":124},[118,16098,16099],{"class":299}," 380.0\n",[118,16101,16102,16105,16107],{"class":120,"line":149},[118,16103,16104],{"class":226},"    blockY ",[118,16106,1366],{"class":124},[118,16108,16109],{"class":299}," 50.0\n",[118,16111,16112],{"class":120,"line":161},[118,16113,199],{"class":124},[118,16115,16116],{"class":120,"line":166},[118,16117,136],{"emptyLinePlaceholder":135},[118,16119,16120,16123,16125,16127,16129,16131,16133,16135,16137,16139,16141,16143,16146,16148,16151,16153,16155,16157,16159,16162,16164,16167,16169,16171,16173,16176,16178,16181,16184,16186,16188,16190],{"class":120,"line":176},[118,16121,16122],{"class":142},"if",[118,16124,1391],{"class":226},[118,16126,230],{"class":124},[118,16128,7260],{"class":226},[118,16130,25],{"class":124},[118,16132,1773],{"class":213},[118,16134,255],{"class":124},[118,16136,430],{"class":124},[118,16138,1780],{"class":433},[118,16140,430],{"class":124},[118,16142,395],{"class":124},[118,16144,16145],{"class":226}," leftX",[118,16147,395],{"class":124},[118,16149,16150],{"class":226}," blockY",[118,16152,395],{"class":124},[118,16154,8030],{"class":124},[118,16156,11345],{"class":128},[118,16158,25],{"class":124},[118,16160,16161],{"class":128},"Rect",[118,16163,1134],{"class":124},[118,16165,16166],{"class":226},"W",[118,16168,6349],{"class":124},[118,16170,6650],{"class":299},[118,16172,395],{"class":124},[118,16174,16175],{"class":226}," H",[118,16177,6349],{"class":124},[118,16179,16180],{"class":299}," 60",[118,16182,16183],{"class":124},"});",[118,16185,1391],{"class":226},[118,16187,1413],{"class":124},[118,16189,1416],{"class":124},[118,16191,220],{"class":124},[118,16193,16194,16197,16199,16201,16203,16205],{"class":120,"line":186},[118,16195,16196],{"class":226},"    log",[118,16198,25],{"class":124},[118,16200,5823],{"class":213},[118,16202,255],{"class":124},[118,16204,1429],{"class":226},[118,16206,199],{"class":124},[118,16208,16209],{"class":120,"line":196},[118,16210,1479],{"class":124},[118,16212,16213],{"class":120,"line":202},[118,16214,136],{"emptyLinePlaceholder":135},[118,16216,16217,16219,16221,16223,16225,16227,16229,16231,16233,16235,16237,16240],{"class":120,"line":207},[118,16218,550],{"class":226},[118,16220,25],{"class":124},[118,16222,13647],{"class":213},[118,16224,255],{"class":124},[118,16226,430],{"class":124},[118,16228,14375],{"class":433},[118,16230,430],{"class":124},[118,16232,395],{"class":124},[118,16234,13660],{"class":124},[118,16236,395],{"class":124},[118,16238,16239],{"class":299}," 14",[118,16241,199],{"class":124},[118,16243,16244,16246,16248,16250,16252,16255,16257,16259],{"class":120,"line":223},[118,16245,550],{"class":226},[118,16247,25],{"class":124},[118,16249,12972],{"class":213},[118,16251,255],{"class":124},[118,16253,16254],{"class":226},"rightX",[118,16256,395],{"class":124},[118,16258,16150],{"class":226},[118,16260,199],{"class":124},[118,16262,16263,16265,16267,16269,16271,16273,16275,16277,16279,16282,16284,16286,16288,16290,16292],{"class":120,"line":244},[118,16264,16122],{"class":142},[118,16266,1391],{"class":226},[118,16268,230],{"class":124},[118,16270,7260],{"class":226},[118,16272,25],{"class":124},[118,16274,12975],{"class":213},[118,16276,13744],{"class":124},[118,16278,1146],{"class":124},[118,16280,16281],{"class":433},"ACME Corporation",[118,16283,430],{"class":124},[118,16285,7902],{"class":124},[118,16287,1391],{"class":226},[118,16289,1413],{"class":124},[118,16291,1416],{"class":124},[118,16293,220],{"class":124},[118,16295,16296,16298,16300,16302,16304,16306],{"class":120,"line":269},[118,16297,16196],{"class":226},[118,16299,25],{"class":124},[118,16301,5823],{"class":213},[118,16303,255],{"class":124},[118,16305,1429],{"class":226},[118,16307,199],{"class":124},[118,16309,16310],{"class":120,"line":306},[118,16311,1479],{"class":124},[118,16313,16314],{"class":120,"line":312},[118,16315,136],{"emptyLinePlaceholder":135},[118,16317,16318,16320,16322,16324,16326,16328,16330,16332,16334,16336,16338,16340],{"class":120,"line":317},[118,16319,550],{"class":226},[118,16321,25],{"class":124},[118,16323,13647],{"class":213},[118,16325,255],{"class":124},[118,16327,430],{"class":124},[118,16329,13593],{"class":433},[118,16331,430],{"class":124},[118,16333,395],{"class":124},[118,16335,13660],{"class":124},[118,16337,395],{"class":124},[118,16339,11241],{"class":299},[118,16341,199],{"class":124},[118,16343,16344,16346,16348,16350,16352,16354,16356,16358,16360,16362],{"class":120,"line":350},[118,16345,550],{"class":226},[118,16347,25],{"class":124},[118,16349,12972],{"class":213},[118,16351,255],{"class":124},[118,16353,16254],{"class":226},[118,16355,395],{"class":124},[118,16357,16150],{"class":226},[118,16359,1339],{"class":124},[118,16361,300],{"class":299},[118,16363,199],{"class":124},[118,16365,16366,16368,16370,16372,16374,16376,16379,16381],{"class":120,"line":379},[118,16367,550],{"class":226},[118,16369,25],{"class":124},[118,16371,12975],{"class":213},[118,16373,13744],{"class":124},[118,16375,1146],{"class":124},[118,16377,16378],{"class":433},"1 Market Street, Suite 400",[118,16380,430],{"class":124},[118,16382,199],{"class":124},[118,16384,16385,16387,16389,16391,16393,16395,16397,16399,16401,16404],{"class":120,"line":417},[118,16386,550],{"class":226},[118,16388,25],{"class":124},[118,16390,12972],{"class":213},[118,16392,255],{"class":124},[118,16394,16254],{"class":226},[118,16396,395],{"class":124},[118,16398,16150],{"class":226},[118,16400,1339],{"class":124},[118,16402,16403],{"class":299},"34",[118,16405,199],{"class":124},[118,16407,16408,16410,16412,16414,16416,16418,16421,16423],{"class":120,"line":466},[118,16409,550],{"class":226},[118,16411,25],{"class":124},[118,16413,12975],{"class":213},[118,16415,13744],{"class":124},[118,16417,1146],{"class":124},[118,16419,16420],{"class":433},"San Francisco, CA 94103",[118,16422,430],{"class":124},[118,16424,199],{"class":124},[118,16426,16427,16429,16431,16433,16435,16437,16439,16441,16443,16446],{"class":120,"line":472},[118,16428,550],{"class":226},[118,16430,25],{"class":124},[118,16432,12972],{"class":213},[118,16434,255],{"class":124},[118,16436,16254],{"class":226},[118,16438,395],{"class":124},[118,16440,16150],{"class":226},[118,16442,1339],{"class":124},[118,16444,16445],{"class":299},"48",[118,16447,199],{"class":124},[118,16449,16450,16452,16454,16456,16458,16460,16463,16465],{"class":120,"line":503},[118,16451,550],{"class":226},[118,16453,25],{"class":124},[118,16455,12975],{"class":213},[118,16457,13744],{"class":124},[118,16459,1146],{"class":124},[118,16461,16462],{"class":433},"billing@acme.example",[118,16464,430],{"class":124},[118,16466,199],{"class":124},[14,16468,16469,16470,16473],{},"There are six explicit y-coordinates, and the right column starts at ",[18,16471,16472],{},"rightX = 380"," because someone decided the logo was 100 wide and the right block needed a 240-pixel gap. Move the logo to the right side and every number changes.",[14,16475,16476],{},[1629,16477,13844],{},[109,16479,16481],{"className":111,"code":16480,"language":113,"meta":114,"style":114},"//go:embed logo.png\nvar logoData []byte\n\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Image(logoData, template.FitWidth(document.Mm(35)))\n    })\n    r.Col(8, func(c *template.ColBuilder) {\n        c.Text(\"ACME Corporation\", template.Bold(), template.FontSize(14))\n        c.Text(\"1 Market Street, Suite 400\")\n        c.Text(\"San Francisco, CA 94103\")\n        c.Text(\"billing@acme.example\")\n    })\n})\n",[18,16482,16483,16488,16502,16506,16530,16560,16597,16601,16631,16669,16687,16705,16723,16727],{"__ignoreMap":114},[118,16484,16485],{"class":120,"line":121},[118,16486,16487],{"class":3981},"//go:embed logo.png\n",[118,16489,16490,16493,16496,16499],{"class":120,"line":132},[118,16491,16492],{"class":124},"var",[118,16494,16495],{"class":226}," logoData ",[118,16497,16498],{"class":124},"[]",[118,16500,16501],{"class":1130},"byte\n",[118,16503,16504],{"class":120,"line":139},[118,16505,136],{"emptyLinePlaceholder":135},[118,16507,16508,16510,16512,16514,16516,16518,16520,16522,16524,16526,16528],{"class":120,"line":149},[118,16509,5910],{"class":226},[118,16511,25],{"class":124},[118,16513,358],{"class":213},[118,16515,328],{"class":124},[118,16517,363],{"class":331},[118,16519,334],{"class":124},[118,16521,337],{"class":128},[118,16523,25],{"class":124},[118,16525,372],{"class":128},[118,16527,345],{"class":124},[118,16529,220],{"class":124},[118,16531,16532,16534,16536,16538,16540,16542,16544,16546,16548,16550,16552,16554,16556,16558],{"class":120,"line":161},[118,16533,5935],{"class":226},[118,16535,25],{"class":124},[118,16537,387],{"class":213},[118,16539,255],{"class":124},[118,16541,1493],{"class":299},[118,16543,395],{"class":124},[118,16545,398],{"class":124},[118,16547,401],{"class":331},[118,16549,334],{"class":124},[118,16551,337],{"class":128},[118,16553,25],{"class":124},[118,16555,410],{"class":128},[118,16557,345],{"class":124},[118,16559,220],{"class":124},[118,16561,16562,16564,16566,16568,16570,16573,16575,16577,16579,16582,16584,16586,16588,16590,16592,16595],{"class":120,"line":166},[118,16563,5966],{"class":226},[118,16565,25],{"class":124},[118,16567,1773],{"class":213},[118,16569,255],{"class":124},[118,16571,16572],{"class":226},"logoData",[118,16574,395],{"class":124},[118,16576,233],{"class":226},[118,16578,25],{"class":124},[118,16580,16581],{"class":213},"FitWidth",[118,16583,255],{"class":124},[118,16585,258],{"class":226},[118,16587,25],{"class":124},[118,16589,294],{"class":213},[118,16591,255],{"class":124},[118,16593,16594],{"class":299},"35",[118,16596,563],{"class":124},[118,16598,16599],{"class":120,"line":176},[118,16600,706],{"class":124},[118,16602,16603,16605,16607,16609,16611,16613,16615,16617,16619,16621,16623,16625,16627,16629],{"class":120,"line":186},[118,16604,5935],{"class":226},[118,16606,25],{"class":124},[118,16608,387],{"class":213},[118,16610,255],{"class":124},[118,16612,969],{"class":299},[118,16614,395],{"class":124},[118,16616,398],{"class":124},[118,16618,401],{"class":331},[118,16620,334],{"class":124},[118,16622,337],{"class":128},[118,16624,25],{"class":124},[118,16626,410],{"class":128},[118,16628,345],{"class":124},[118,16630,220],{"class":124},[118,16632,16633,16635,16637,16639,16641,16643,16645,16647,16649,16651,16653,16655,16657,16659,16661,16663,16665,16667],{"class":120,"line":196},[118,16634,5966],{"class":226},[118,16636,25],{"class":124},[118,16638,425],{"class":213},[118,16640,255],{"class":124},[118,16642,430],{"class":124},[118,16644,16281],{"class":433},[118,16646,430],{"class":124},[118,16648,395],{"class":124},[118,16650,233],{"class":226},[118,16652,25],{"class":124},[118,16654,445],{"class":213},[118,16656,448],{"class":124},[118,16658,233],{"class":226},[118,16660,25],{"class":124},[118,16662,455],{"class":213},[118,16664,255],{"class":124},[118,16666,1877],{"class":299},[118,16668,463],{"class":124},[118,16670,16671,16673,16675,16677,16679,16681,16683,16685],{"class":120,"line":202},[118,16672,5966],{"class":226},[118,16674,25],{"class":124},[118,16676,425],{"class":213},[118,16678,255],{"class":124},[118,16680,430],{"class":124},[118,16682,16378],{"class":433},[118,16684,430],{"class":124},[118,16686,199],{"class":124},[118,16688,16689,16691,16693,16695,16697,16699,16701,16703],{"class":120,"line":207},[118,16690,5966],{"class":226},[118,16692,25],{"class":124},[118,16694,425],{"class":213},[118,16696,255],{"class":124},[118,16698,430],{"class":124},[118,16700,16420],{"class":433},[118,16702,430],{"class":124},[118,16704,199],{"class":124},[118,16706,16707,16709,16711,16713,16715,16717,16719,16721],{"class":120,"line":223},[118,16708,5966],{"class":226},[118,16710,25],{"class":124},[118,16712,425],{"class":213},[118,16714,255],{"class":124},[118,16716,430],{"class":124},[118,16718,16462],{"class":433},[118,16720,430],{"class":124},[118,16722,199],{"class":124},[118,16724,16725],{"class":120,"line":244},[118,16726,706],{"class":124},[118,16728,16729],{"class":120,"line":269},[118,16730,1944],{"class":124},[14,16732,16733,16734,16736,16737,16739],{},"Two columns, 4 + 8 = 12. The image fits a fixed width and lets gpdf compute the height from the aspect ratio. Each ",[18,16735,8551],{}," flows below the previous one — no ",[18,16738,12987],{},", no y arithmetic. Swap the column order if you want the logo on the right.",[41,16741,16743],{"id":16742},"before-after-5-page-numbers-in-the-footer","Before / After 5: page numbers in the footer",[14,16745,16746],{},"In gopdf you maintain the count yourself, because the render is single-pass and the total isn't known when you draw the first footer. Most codebases do a two-pass workaround: render once to count pages, render again with the count baked in.",[14,16748,16749],{},[1629,16750,13442],{},[109,16752,16754],{"className":111,"code":16753,"language":113,"meta":114,"style":114},"totalPages := 0\npdf.AddFooter(func() {\n    totalPages++\n})\n\n// First pass: count pages by rendering the whole document.\nbuildContent(&pdf)\nfinalTotal := totalPages\n\n// Second pass: re-render with the known total.\npdf2 := gopdf.GoPdf{}\npdf2.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})\npageNum := 0\npdf2.AddFooter(func() {\n    pageNum++\n    pdf2.SetFont(\"helvetica\", \"\", 8)\n    pdf2.SetXY(40, 800)\n    pdf2.Cell(nil, fmt.Sprintf(\"Page %d of %d\", pageNum, finalTotal))\n})\nbuildContent(&pdf2)\npdf2.WritePdf(\"report.pdf\")\n",[18,16755,16756,16766,16780,16788,16792,16796,16801,16813,16823,16827,16832,16847,16880,16889,16901,16908,16936,16955,17000,17004,17014],{"__ignoreMap":114},[118,16757,16758,16761,16763],{"class":120,"line":121},[118,16759,16760],{"class":226},"totalPages ",[118,16762,230],{"class":124},[118,16764,16765],{"class":299}," 0\n",[118,16767,16768,16770,16772,16775,16778],{"class":120,"line":132},[118,16769,550],{"class":226},[118,16771,25],{"class":124},[118,16773,16774],{"class":213},"AddFooter",[118,16776,16777],{"class":124},"(func()",[118,16779,220],{"class":124},[118,16781,16782,16785],{"class":120,"line":139},[118,16783,16784],{"class":226},"    totalPages",[118,16786,16787],{"class":124},"++\n",[118,16789,16790],{"class":120,"line":149},[118,16791,1944],{"class":124},[118,16793,16794],{"class":120,"line":161},[118,16795,136],{"emptyLinePlaceholder":135},[118,16797,16798],{"class":120,"line":166},[118,16799,16800],{"class":3981},"// First pass: count pages by rendering the whole document.\n",[118,16802,16803,16806,16809,16811],{"class":120,"line":176},[118,16804,16805],{"class":213},"buildContent",[118,16807,16808],{"class":124},"(&",[118,16810,550],{"class":226},[118,16812,199],{"class":124},[118,16814,16815,16818,16820],{"class":120,"line":186},[118,16816,16817],{"class":226},"finalTotal ",[118,16819,230],{"class":124},[118,16821,16822],{"class":226}," totalPages\n",[118,16824,16825],{"class":120,"line":196},[118,16826,136],{"emptyLinePlaceholder":135},[118,16828,16829],{"class":120,"line":202},[118,16830,16831],{"class":3981},"// Second pass: re-render with the known total.\n",[118,16833,16834,16837,16839,16841,16843,16845],{"class":120,"line":207},[118,16835,16836],{"class":226},"pdf2 ",[118,16838,230],{"class":124},[118,16840,13512],{"class":128},[118,16842,25],{"class":124},[118,16844,13517],{"class":128},[118,16846,13520],{"class":124},[118,16848,16849,16852,16854,16856,16858,16860,16862,16864,16866,16868,16870,16872,16874,16876,16878],{"class":120,"line":223},[118,16850,16851],{"class":226},"pdf2",[118,16853,25],{"class":124},[118,16855,13530],{"class":213},[118,16857,255],{"class":124},[118,16859,11345],{"class":128},[118,16861,25],{"class":124},[118,16863,13539],{"class":128},[118,16865,1134],{"class":124},[118,16867,13544],{"class":226},[118,16869,6349],{"class":124},[118,16871,334],{"class":124},[118,16873,11345],{"class":226},[118,16875,25],{"class":124},[118,16877,13555],{"class":226},[118,16879,1944],{"class":124},[118,16881,16882,16885,16887],{"class":120,"line":244},[118,16883,16884],{"class":226},"pageNum ",[118,16886,230],{"class":124},[118,16888,16765],{"class":299},[118,16890,16891,16893,16895,16897,16899],{"class":120,"line":269},[118,16892,16851],{"class":226},[118,16894,25],{"class":124},[118,16896,16774],{"class":213},[118,16898,16777],{"class":124},[118,16900,220],{"class":124},[118,16902,16903,16906],{"class":120,"line":306},[118,16904,16905],{"class":226},"    pageNum",[118,16907,16787],{"class":124},[118,16909,16910,16913,16915,16917,16919,16921,16923,16925,16927,16929,16931,16934],{"class":120,"line":312},[118,16911,16912],{"class":226},"    pdf2",[118,16914,25],{"class":124},[118,16916,13647],{"class":213},[118,16918,255],{"class":124},[118,16920,430],{"class":124},[118,16922,13593],{"class":433},[118,16924,430],{"class":124},[118,16926,395],{"class":124},[118,16928,13660],{"class":124},[118,16930,395],{"class":124},[118,16932,16933],{"class":299}," 8",[118,16935,199],{"class":124},[118,16937,16938,16940,16942,16944,16946,16948,16950,16953],{"class":120,"line":317},[118,16939,16912],{"class":226},[118,16941,25],{"class":124},[118,16943,12972],{"class":213},[118,16945,255],{"class":124},[118,16947,9910],{"class":299},[118,16949,395],{"class":124},[118,16951,16952],{"class":299}," 800",[118,16954,199],{"class":124},[118,16956,16957,16959,16961,16963,16965,16968,16970,16972,16974,16976,16979,16981,16984,16986,16988,16990,16993,16995,16998],{"class":120,"line":350},[118,16958,16912],{"class":226},[118,16960,25],{"class":124},[118,16962,12975],{"class":213},[118,16964,13744],{"class":124},[118,16966,16967],{"class":226}," fmt",[118,16969,25],{"class":124},[118,16971,7416],{"class":213},[118,16973,255],{"class":124},[118,16975,430],{"class":124},[118,16977,16978],{"class":433},"Page ",[118,16980,2323],{"class":7426},[118,16982,16983],{"class":433}," of ",[118,16985,2323],{"class":7426},[118,16987,430],{"class":124},[118,16989,395],{"class":124},[118,16991,16992],{"class":226}," pageNum",[118,16994,395],{"class":124},[118,16996,16997],{"class":226}," finalTotal",[118,16999,463],{"class":124},[118,17001,17002],{"class":120,"line":379},[118,17003,1944],{"class":124},[118,17005,17006,17008,17010,17012],{"class":120,"line":417},[118,17007,16805],{"class":213},[118,17009,16808],{"class":124},[118,17011,16851],{"class":226},[118,17013,199],{"class":124},[118,17015,17016,17018,17020,17022,17024,17026,17028,17030],{"class":120,"line":466},[118,17017,16851],{"class":226},[118,17019,25],{"class":124},[118,17021,13798],{"class":213},[118,17023,255],{"class":124},[118,17025,430],{"class":124},[118,17027,1459],{"class":433},[118,17029,430],{"class":124},[118,17031,199],{"class":124},[14,17033,17034],{},"If you've maintained gopdf code, you've written this. It's not in any FAQ, but it's the only way to get an honest \"Page X of Y\" footer without parsing the output.",[14,17036,17037],{},[1629,17038,13844],{},[109,17040,17042],{"className":111,"code":17041,"language":113,"meta":114,"style":114},"doc.Footer(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"ACME Corporation\",\n                template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Stack(template.AlignRight(), func(c *template.ColBuilder) {\n                c.Text(\"Page \", template.Inline())\n                c.PageNumber(template.Inline())\n                c.Text(\" of \", template.Inline())\n                c.TotalPages(template.Inline())\n            }, template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n    })\n})\n",[18,17043,17044,17068,17092,17122,17140,17174,17178,17208,17243,17270,17288,17314,17332,17369,17373,17377],{"__ignoreMap":114},[118,17045,17046,17048,17050,17052,17054,17056,17058,17060,17062,17064,17066],{"class":120,"line":121},[118,17047,1687],{"class":226},[118,17049,25],{"class":124},[118,17051,721],{"class":213},[118,17053,328],{"class":124},[118,17055,14],{"class":331},[118,17057,334],{"class":124},[118,17059,337],{"class":128},[118,17061,25],{"class":124},[118,17063,342],{"class":128},[118,17065,345],{"class":124},[118,17067,220],{"class":124},[118,17069,17070,17072,17074,17076,17078,17080,17082,17084,17086,17088,17090],{"class":120,"line":132},[118,17071,1712],{"class":226},[118,17073,25],{"class":124},[118,17075,358],{"class":213},[118,17077,328],{"class":124},[118,17079,363],{"class":331},[118,17081,334],{"class":124},[118,17083,337],{"class":128},[118,17085,25],{"class":124},[118,17087,372],{"class":128},[118,17089,345],{"class":124},[118,17091,220],{"class":124},[118,17093,17094,17096,17098,17100,17102,17104,17106,17108,17110,17112,17114,17116,17118,17120],{"class":120,"line":139},[118,17095,1737],{"class":226},[118,17097,25],{"class":124},[118,17099,387],{"class":213},[118,17101,255],{"class":124},[118,17103,392],{"class":299},[118,17105,395],{"class":124},[118,17107,398],{"class":124},[118,17109,401],{"class":331},[118,17111,334],{"class":124},[118,17113,337],{"class":128},[118,17115,25],{"class":124},[118,17117,410],{"class":128},[118,17119,345],{"class":124},[118,17121,220],{"class":124},[118,17123,17124,17126,17128,17130,17132,17134,17136,17138],{"class":120,"line":149},[118,17125,1768],{"class":226},[118,17127,25],{"class":124},[118,17129,425],{"class":213},[118,17131,255],{"class":124},[118,17133,430],{"class":124},[118,17135,16281],{"class":433},[118,17137,430],{"class":124},[118,17139,2643],{"class":124},[118,17141,17142,17144,17146,17148,17150,17152,17154,17156,17158,17160,17162,17164,17166,17168,17170,17172],{"class":120,"line":161},[118,17143,2648],{"class":226},[118,17145,25],{"class":124},[118,17147,455],{"class":213},[118,17149,255],{"class":124},[118,17151,969],{"class":299},[118,17153,1280],{"class":124},[118,17155,233],{"class":226},[118,17157,25],{"class":124},[118,17159,545],{"class":213},[118,17161,255],{"class":124},[118,17163,550],{"class":226},[118,17165,25],{"class":124},[118,17167,555],{"class":213},[118,17169,255],{"class":124},[118,17171,560],{"class":299},[118,17173,563],{"class":124},[118,17175,17176],{"class":120,"line":166},[118,17177,574],{"class":124},[118,17179,17180,17182,17184,17186,17188,17190,17192,17194,17196,17198,17200,17202,17204,17206],{"class":120,"line":176},[118,17181,1737],{"class":226},[118,17183,25],{"class":124},[118,17185,387],{"class":213},[118,17187,255],{"class":124},[118,17189,392],{"class":299},[118,17191,395],{"class":124},[118,17193,398],{"class":124},[118,17195,401],{"class":331},[118,17197,334],{"class":124},[118,17199,337],{"class":128},[118,17201,25],{"class":124},[118,17203,410],{"class":128},[118,17205,345],{"class":124},[118,17207,220],{"class":124},[118,17209,17210,17212,17214,17217,17219,17221,17223,17225,17227,17229,17231,17233,17235,17237,17239,17241],{"class":120,"line":186},[118,17211,1768],{"class":226},[118,17213,25],{"class":124},[118,17215,17216],{"class":213},"Stack",[118,17218,255],{"class":124},[118,17220,337],{"class":226},[118,17222,25],{"class":124},[118,17224,519],{"class":213},[118,17226,448],{"class":124},[118,17228,398],{"class":124},[118,17230,401],{"class":331},[118,17232,334],{"class":124},[118,17234,337],{"class":128},[118,17236,25],{"class":124},[118,17238,410],{"class":128},[118,17240,345],{"class":124},[118,17242,220],{"class":124},[118,17244,17245,17247,17249,17251,17253,17255,17257,17259,17261,17263,17265,17268],{"class":120,"line":196},[118,17246,420],{"class":226},[118,17248,25],{"class":124},[118,17250,425],{"class":213},[118,17252,255],{"class":124},[118,17254,430],{"class":124},[118,17256,16978],{"class":433},[118,17258,430],{"class":124},[118,17260,395],{"class":124},[118,17262,233],{"class":226},[118,17264,25],{"class":124},[118,17266,17267],{"class":213},"Inline",[118,17269,1289],{"class":124},[118,17271,17272,17274,17276,17278,17280,17282,17284,17286],{"class":120,"line":202},[118,17273,420],{"class":226},[118,17275,25],{"class":124},[118,17277,1040],{"class":213},[118,17279,255],{"class":124},[118,17281,337],{"class":226},[118,17283,25],{"class":124},[118,17285,17267],{"class":213},[118,17287,1289],{"class":124},[118,17289,17290,17292,17294,17296,17298,17300,17302,17304,17306,17308,17310,17312],{"class":120,"line":207},[118,17291,420],{"class":226},[118,17293,25],{"class":124},[118,17295,425],{"class":213},[118,17297,255],{"class":124},[118,17299,430],{"class":124},[118,17301,16983],{"class":433},[118,17303,430],{"class":124},[118,17305,395],{"class":124},[118,17307,233],{"class":226},[118,17309,25],{"class":124},[118,17311,17267],{"class":213},[118,17313,1289],{"class":124},[118,17315,17316,17318,17320,17322,17324,17326,17328,17330],{"class":120,"line":223},[118,17317,420],{"class":226},[118,17319,25],{"class":124},[118,17321,510],{"class":213},[118,17323,255],{"class":124},[118,17325,337],{"class":226},[118,17327,25],{"class":124},[118,17329,17267],{"class":213},[118,17331,1289],{"class":124},[118,17333,17334,17337,17339,17341,17343,17345,17347,17349,17351,17353,17355,17357,17359,17361,17363,17365,17367],{"class":120,"line":244},[118,17335,17336],{"class":124},"            },",[118,17338,233],{"class":226},[118,17340,25],{"class":124},[118,17342,455],{"class":213},[118,17344,255],{"class":124},[118,17346,969],{"class":299},[118,17348,1280],{"class":124},[118,17350,233],{"class":226},[118,17352,25],{"class":124},[118,17354,545],{"class":213},[118,17356,255],{"class":124},[118,17358,550],{"class":226},[118,17360,25],{"class":124},[118,17362,555],{"class":213},[118,17364,255],{"class":124},[118,17366,560],{"class":299},[118,17368,563],{"class":124},[118,17370,17371],{"class":120,"line":269},[118,17372,574],{"class":124},[118,17374,17375],{"class":120,"line":306},[118,17376,706],{"class":124},[118,17378,17379],{"class":120,"line":312},[118,17380,1944],{"class":124},[14,17382,17383,54,17385,17387],{},[18,17384,1040],{},[18,17386,510],{}," are placeholders. The layout engine paginates first, resolves the totals, then writes them in. One pass, no manual counting, no double render. The placeholders also work mid-paragraph if you want phrasing like \"5 of 12\" rather than the bare numbers.",[41,17389,17391],{"id":17390},"japanese-text-without-the-manual-subset","Japanese text without the manual subset",[14,17393,17394],{},"signintech/gopdf supports CJK well, but the path is character-set bookkeeping you do by hand. You add the TTF, you set the character map, and if your text contains a glyph outside the subset you registered, you get tofu. gpdf's TrueType subsetter walks the cmap (formats 4, 6, 12) and embeds exactly the glyphs you used — no manual subset list.",[109,17396,17398],{"className":111,"code":17397,"language":113,"meta":114,"style":114},"//go:embed NotoSansJP-Regular.ttf\nvar notoJP []byte\n\ndoc := gpdf.NewDocument(\n    gpdf.WithPageSize(document.A4),\n    gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    gpdf.WithFont(\"NotoSansJP\", notoJP),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 14),\n)\n\npage := doc.AddPage()\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Text(\"こんにちは、世界。\")\n        c.Text(\"吾輩は猫である。名前はまだ無い。\")\n        c.Text(\"東京都渋谷区神宮前1-2-3\")\n    })\n})\n",[18,17399,17400,17405,17416,17420,17434,17452,17482,17505,17527,17531,17535,17550,17574,17604,17623,17642,17661,17665],{"__ignoreMap":114},[118,17401,17402],{"class":120,"line":121},[118,17403,17404],{"class":3981},"//go:embed NotoSansJP-Regular.ttf\n",[118,17406,17407,17409,17412,17414],{"class":120,"line":132},[118,17408,16492],{"class":124},[118,17410,17411],{"class":226}," notoJP ",[118,17413,16498],{"class":124},[118,17415,16501],{"class":1130},[118,17417,17418],{"class":120,"line":139},[118,17419,136],{"emptyLinePlaceholder":135},[118,17421,17422,17424,17426,17428,17430,17432],{"class":120,"line":149},[118,17423,2760],{"class":226},[118,17425,230],{"class":124},[118,17427,3595],{"class":226},[118,17429,25],{"class":124},[118,17431,3600],{"class":213},[118,17433,241],{"class":124},[118,17435,17436,17438,17440,17442,17444,17446,17448,17450],{"class":120,"line":161},[118,17437,4532],{"class":226},[118,17439,25],{"class":124},[118,17441,252],{"class":213},[118,17443,255],{"class":124},[118,17445,258],{"class":226},[118,17447,25],{"class":124},[118,17449,263],{"class":226},[118,17451,266],{"class":124},[118,17453,17454,17456,17458,17460,17462,17464,17466,17468,17470,17472,17474,17476,17478,17480],{"class":120,"line":166},[118,17455,4532],{"class":226},[118,17457,25],{"class":124},[118,17459,276],{"class":213},[118,17461,255],{"class":124},[118,17463,258],{"class":226},[118,17465,25],{"class":124},[118,17467,285],{"class":213},[118,17469,255],{"class":124},[118,17471,258],{"class":226},[118,17473,25],{"class":124},[118,17475,294],{"class":213},[118,17477,255],{"class":124},[118,17479,300],{"class":299},[118,17481,303],{"class":124},[118,17483,17484,17486,17488,17490,17492,17494,17496,17498,17500,17503],{"class":120,"line":176},[118,17485,4532],{"class":226},[118,17487,25],{"class":124},[118,17489,2798],{"class":213},[118,17491,255],{"class":124},[118,17493,430],{"class":124},[118,17495,2805],{"class":433},[118,17497,430],{"class":124},[118,17499,395],{"class":124},[118,17501,17502],{"class":226}," notoJP",[118,17504,266],{"class":124},[118,17506,17507,17509,17511,17513,17515,17517,17519,17521,17523,17525],{"class":120,"line":186},[118,17508,4532],{"class":226},[118,17510,25],{"class":124},[118,17512,12501],{"class":213},[118,17514,255],{"class":124},[118,17516,430],{"class":124},[118,17518,2805],{"class":433},[118,17520,430],{"class":124},[118,17522,395],{"class":124},[118,17524,16239],{"class":299},[118,17526,266],{"class":124},[118,17528,17529],{"class":120,"line":196},[118,17530,199],{"class":124},[118,17532,17533],{"class":120,"line":202},[118,17534,136],{"emptyLinePlaceholder":135},[118,17536,17537,17540,17542,17544,17546,17548],{"class":120,"line":207},[118,17538,17539],{"class":226},"page ",[118,17541,230],{"class":124},[118,17543,1185],{"class":226},[118,17545,25],{"class":124},[118,17547,1190],{"class":213},[118,17549,1193],{"class":124},[118,17551,17552,17554,17556,17558,17560,17562,17564,17566,17568,17570,17572],{"class":120,"line":223},[118,17553,5910],{"class":226},[118,17555,25],{"class":124},[118,17557,358],{"class":213},[118,17559,328],{"class":124},[118,17561,363],{"class":331},[118,17563,334],{"class":124},[118,17565,337],{"class":128},[118,17567,25],{"class":124},[118,17569,372],{"class":128},[118,17571,345],{"class":124},[118,17573,220],{"class":124},[118,17575,17576,17578,17580,17582,17584,17586,17588,17590,17592,17594,17596,17598,17600,17602],{"class":120,"line":244},[118,17577,5935],{"class":226},[118,17579,25],{"class":124},[118,17581,387],{"class":213},[118,17583,255],{"class":124},[118,17585,20],{"class":299},[118,17587,395],{"class":124},[118,17589,398],{"class":124},[118,17591,401],{"class":331},[118,17593,334],{"class":124},[118,17595,337],{"class":128},[118,17597,25],{"class":124},[118,17599,410],{"class":128},[118,17601,345],{"class":124},[118,17603,220],{"class":124},[118,17605,17606,17608,17610,17612,17614,17616,17619,17621],{"class":120,"line":269},[118,17607,5966],{"class":226},[118,17609,25],{"class":124},[118,17611,425],{"class":213},[118,17613,255],{"class":124},[118,17615,430],{"class":124},[118,17617,17618],{"class":433},"こんにちは、世界。",[118,17620,430],{"class":124},[118,17622,199],{"class":124},[118,17624,17625,17627,17629,17631,17633,17635,17638,17640],{"class":120,"line":306},[118,17626,5966],{"class":226},[118,17628,25],{"class":124},[118,17630,425],{"class":213},[118,17632,255],{"class":124},[118,17634,430],{"class":124},[118,17636,17637],{"class":433},"吾輩は猫である。名前はまだ無い。",[118,17639,430],{"class":124},[118,17641,199],{"class":124},[118,17643,17644,17646,17648,17650,17652,17654,17657,17659],{"class":120,"line":312},[118,17645,5966],{"class":226},[118,17647,25],{"class":124},[118,17649,425],{"class":213},[118,17651,255],{"class":124},[118,17653,430],{"class":124},[118,17655,17656],{"class":433},"東京都渋谷区神宮前1-2-3",[118,17658,430],{"class":124},[118,17660,199],{"class":124},[118,17662,17663],{"class":120,"line":317},[118,17664,706],{"class":124},[118,17666,17667],{"class":120,"line":350},[118,17668,1944],{"class":124},[14,17670,17671,17672,17675],{},"A 200-character Japanese invoice produces a ~30 KB font subset rather than a 4 MB full embed. The companion piece on ",[3163,17673,17674],{"href":9791},"embedding Japanese fonts"," covers IPAex Gothic and Source Han Sans fallbacks.",[41,17677,17679],{"id":17678},"benchmarks","Benchmarks",[14,17681,17682],{},"Same hardware, same workloads, Apple M1 and Go 1.25.",[1516,17684,17685,17701],{},[1519,17686,17687],{},[1522,17688,17689,17692,17694,17696,17698],{},[1525,17690,17691],{},"Benchmark",[1525,17693,1587],{},[1525,17695,1565],{},[1525,17697,1539],{},[1525,17699,17700],{},"Maroto v2",[1532,17702,17703,17721,17739,17756],{},[1522,17704,17705,17708,17712,17715,17718],{},[1537,17706,17707],{},"Single page",[1537,17709,17710],{},[1629,17711,3248],{},[1537,17713,17714],{},"423 µs",[1537,17716,17717],{},"132 µs",[1537,17719,17720],{},"237 µs",[1522,17722,17723,17726,17730,17733,17736],{},[1537,17724,17725],{},"4×10 invoice table",[1537,17727,17728],{},[1629,17729,16053],{},[1537,17731,17732],{},"835 µs",[1537,17734,17735],{},"241 µs",[1537,17737,17738],{},"8.6 ms",[1522,17740,17741,17744,17748,17750,17753],{},[1537,17742,17743],{},"100-page report",[1537,17745,17746],{},[1629,17747,4131],{},[1537,17749,17738],{},[1537,17751,17752],{},"11.7 ms",[1537,17754,17755],{},"19.8 ms",[1522,17757,17758,17761,17766,17769,17772],{},[1537,17759,17760],{},"Complex CJK invoice",[1537,17762,17763],{},[1629,17764,17765],{},"133 µs",[1537,17767,17768],{},"997 µs",[1537,17770,17771],{},"254 µs",[1537,17773,17774],{},"10.4 ms",[14,17776,17777,17778,17781],{},"Numbers come from ",[18,17779,17780],{},"gpdf/_benchmark/benchmark_test.go",". signintech/gopdf is faster than gofpdf on simple paths because it does less per call, but the gap widens on tables because the layout work in gpdf is internal C-style imperative code with no allocations in the hot path, while a hand-rolled gopdf table allocates strings and font metrics per cell.",[14,17783,17784,17785,17788],{},"The single-core throughput at ",[1629,17786,17787],{},"108 µs per table-rich page"," is roughly 9,000 invoices per second. For most workloads that means PDF generation can stay on the request path. Cron, queue, and pre-render workarounds you wrote because gopdf was 10× slower can usually be removed.",[41,17790,17792],{"id":17791},"what-gopdf-does-that-gpdf-doesnt","What gopdf does that gpdf doesn't",[14,17794,17795],{},"Honest section. If your gopdf usage relies on these, the migration won't carry you all the way.",[46,17797,17798,17814,17827,17841],{},[49,17799,17800,17805,17806,17809,17810,17813],{},[1629,17801,17802,25],{},[18,17803,17804],{},"ImportPage"," gopdf can import a single page from an existing PDF and stamp content over it. This is the \"PDF template\" workflow — load a vendor-provided invoice template, write your data on top. gpdf's overlay support handles the common case (",[18,17807,17808],{},"gpdf.Overlay","), but it doesn't expose the same ",[18,17811,17812],{},"UseImportedTemplate"," primitive. If your codebase is built around drawing on top of vendor PDFs, evaluate the overlay API first.",[49,17815,17816,17819,17820,54,17823,17826],{},[1629,17817,17818],{},"Polygons and ovals as primitives."," gopdf has ",[18,17821,17822],{},"Oval",[18,17824,17825],{},"Polygon"," calls. gpdf's primitive set is rectangles, lines, images, text, and tables; arbitrary path drawing is not first-class. For data visualization, render with a charting library to PNG/SVG and embed that.",[49,17828,17829,17832,17833,17836,17837,17840],{},[1629,17830,17831],{},"Direct cursor positioning."," If your codebase legitimately wants pixel-perfect placement (a stamp at exactly ",[18,17834,17835],{},"(420, 240)","), ",[18,17838,17839],{},"page.Absolute(x, y, fn)"," exists, but it's the escape hatch — most code shouldn't reach for it.",[49,17842,17843,17851,17852,1592,17854,17856],{},[1629,17844,17845,1592,17848,25],{},[18,17846,17847],{},"PlaceHolderText",[18,17849,17850],{},"FillInPlaceHoldText"," gopdf has a two-pass placeholder pattern for filling in text after the layout is done. gpdf's ",[18,17853,1040],{},[18,17855,510],{}," placeholders handle the common case (page numbering); a general \"fill this slot later\" mechanism doesn't exist yet.",[14,17858,17859,17860,17863],{},"For ",[1629,17861,17862],{},"invoices, statements, reports, certificates, contracts, receipts, shipping labels, packing slips, and CJK documents"," — what most gopdf bills actually generate — the swap is complete.",[41,17865,3054],{"id":3053},[14,17867,17868,17871],{},[1629,17869,17870],{},"Is gpdf a fork of signintech/gopdf?","\nNo. gpdf is a clean reimplementation in pure Go. The PDF wire writer, layout engine, TrueType subsetter, AES encryption, and PKCS#7 signing are all written from scratch. There is no shared code or shared lineage.",[14,17873,17874,17877],{},[1629,17875,17876],{},"Both are pure Go and CGO-free. What's the actual value of switching?","\nThe layout engine. The migration sections above are 80% about removing coordinate math, and that's the lived-day-to-day difference. The benchmarks are a secondary win. The MIT license is identical to gopdf's MIT, so licensing is not a factor.",[14,17879,17880,17883,17884,17887,17888,17891],{},[1629,17881,17882],{},"Can I migrate incrementally?","\nYes — the two libraries don't conflict. They produce independent ",[18,17885,17886],{},"[]byte"," outputs. Render one section with gpdf, another with gopdf, and ",[18,17889,17890],{},"gpdf.Merge(a, b)"," glues them. In practice most teams find it easier to migrate a whole document at a time, because the layout shift is conceptual and partial migrations end up with both mental models in the same file.",[14,17893,17894,17901,17902,17905,17906,17909,17910,17913,17914,17917],{},[1629,17895,17896,17897,17900],{},"My existing code uses ",[18,17898,17899],{},"pdf.Image(path, ...)"," to load logos from disk. Do I have to embed them?","\nYou don't ",[4744,17903,17904],{},"have"," to. ",[18,17907,17908],{},"c.Image(imageBytes, ...)"," takes bytes — read the file with ",[18,17911,17912],{},"os.ReadFile"," if you want runtime loading. But ",[18,17915,17916],{},"//go:embed"," is the better default: the binary stops needing a writable filesystem at the path it expects, container images shrink to one binary, and the asset can't go missing in production.",[14,17919,17920,17927,17928,3096,17931,3096,17934,17937,17938,17941],{},[1629,17921,17922,17923,17926],{},"What about ",[18,17924,17925],{},"gopdf.PageSizeA4"," and the other page size constants?","\ngpdf's ",[18,17929,17930],{},"document.A4",[18,17932,17933],{},"document.Letter",[18,17935,17936],{},"document.Legal",", etc., cover the same set. For a custom size, ",[18,17939,17940],{},"document.PageSize(document.Mm(210), document.Mm(297))"," works.",[14,17943,17944,17951,17953,17954,17957],{},[1629,17945,17946,17947,17950],{},"My invoice generator uses ",[18,17948,17949],{},"pdf.Rotate"," for portrait-on-landscape stamping. Is there an equivalent?",[18,17952,17839],{}," accepts a rotation option; the typical \"watermark across the page diagonally\" pattern is one ",[18,17955,17956],{},"page.Absolute"," call. Per-element rotation on inline content isn't a primitive — if you need rotated cells inside a table, render that cell to an image and embed.",[14,17959,17960,17963,17964,1579,17966,17968,17969,1579,17972,17974],{},[1629,17961,17962],{},"Is there a tool that auto-rewrites my code?","\nNot yet. The mapping is mechanical for the simple parts (",[18,17965,12972],{},[18,17967,12975],{}," → ",[18,17970,17971],{},"r.Col",[18,17973,8551],{},") but the table rewrite is structural — you delete the bookkeeping rather than translating it. Hand-migration of a typical generator takes a few hours per document type.",[41,17976,4794],{"id":4793},[14,17978,17979],{},"gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, native CJK support, 12-column grid layout.",[109,17981,17982],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,17983,17984],{"__ignoreMap":114},[118,17985,17986,17988,17990],{"class":120,"line":121},[118,17987,113],{"class":128},[118,17989,3156],{"class":433},[118,17991,3159],{"class":433},[14,17993,17994,3169,17997],{},[3163,17995,3168],{"href":3165,"rel":17996},[3167],[3163,17998,3174],{"href":3172,"rel":17999},[3167],[41,18001,4821],{"id":4820},[46,18003,18004,18009,18014,18019,18025],{},[49,18005,18006],{},[3163,18007,18008],{"href":6901},"The 12-column grid: bringing Bootstrap thinking to PDF layout",[49,18010,18011],{},[3163,18012,18013],{"href":12890},"Bootstrap-grid thinking for PDF",[49,18015,18016],{},[3163,18017,18018],{"href":4839},"gofpdf is archived. Here's how to migrate to gpdf.",[49,18020,18021],{},[3163,18022,18024],{"href":18023},"/blog/unidoc-migration","unipdf is AGPL or paid. Here's how to migrate to gpdf.",[49,18026,18027],{},[3163,18028,6919],{"href":6918},[3176,18030,18031],{},"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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);}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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":114,"searchDepth":132,"depth":132,"links":18033},[18034,18035,18036,18037,18038,18039,18040,18041,18042,18043,18044,18045,18046,18047,18048],{"id":43,"depth":132,"text":44},{"id":13004,"depth":132,"text":13005},{"id":13026,"depth":132,"text":13027},{"id":13068,"depth":132,"text":13069},{"id":13433,"depth":132,"text":13434},{"id":14235,"depth":132,"text":14236},{"id":14981,"depth":132,"text":14982},{"id":16061,"depth":132,"text":16062},{"id":16742,"depth":132,"text":16743},{"id":17390,"depth":132,"text":17391},{"id":17678,"depth":132,"text":17679},{"id":17791,"depth":132,"text":17792},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"2026-05-06","signintech/gopdf works, but every cell, line, and header is an (x, y) calculation. This guide maps the gopdf API to gpdf — same Go, no coordinate math.",{"name":18052,"totalTime":18053,"tools":18054,"steps":18055},"Migrate a Go project from signintech/gopdf to gpdf","PT40M",[3202],[18056,18059,18062,18065,18068,18071,18074],{"name":18057,"text":18058},"Replace the import path","Swap github.com/signintech/gopdf for github.com/gpdf-dev/gpdf, github.com/gpdf-dev/gpdf/document, and github.com/gpdf-dev/gpdf/template. The old gopdf.GoPdf{} struct goes away — there is no equivalent shared mutable cursor.",{"name":18060,"text":18061},"Replace pdf.Start and pdf.AddPage with gpdf.NewDocument","Construct the document with gpdf.NewDocument(WithPageSize(document.A4), WithMargins(...)). doc.AddPage() returns a PageBuilder, not a cursor. You stop tracking the current y-position by hand.",{"name":18063,"text":18064},"Stop calling SetX, SetY, and SetXY","Delete every cursor call. Wrap content in page.AutoRow(func(r *RowBuilder)) and r.Col(span, func(c *ColBuilder)). The 12-column grid handles horizontal placement; AutoRow stacks vertically with no manual y math.",{"name":18066,"text":18067},"Replace Cell and MultiCell with c.Text","Inside a column, call c.Text(string, options...) instead of computing a Rect and calling Cell or MultiCell. Wrapping is automatic — the column knows its own width.",{"name":18069,"text":18070},"Rewrite manual table loops with c.Table","Replace the SetXY/Cell loops drawing each header and body row with c.Table(headers, rows, template.ColumnWidths(...)). Borders, page breaks, and header repetition are handled by the layout engine.",{"name":18072,"text":18073},"Switch font registration to bytes","Replace pdf.AddTTFFont(name, path) with gpdf.WithFont(name, ttfBytes) at construction. Embed the TTF via //go:embed so the binary stops needing a font path at runtime.",{"name":18075,"text":18076},"Replace WritePdf with doc.Generate plus os.WriteFile","Swap pdf.WritePdf(path) for data, _ := doc.Generate() then os.WriteFile(path, data, 0o644). Use doc.Render(w) when you want to stream straight into an http.ResponseWriter or S3 PutObject.",{},"/blog/signintech-gopdf-migration",{"title":12956,"description":18050},"blog/021.signintech-gopdf-migration",[4868,4866,3226],"Mvbdanhi8qGwsEO9AEjcxx5HIdQu-OeC_rgCAgAClDI",{"id":18084,"title":18085,"author":18086,"body":18087,"date":19234,"description":19235,"draft":3196,"extension":3197,"howTo":19236,"image":3220,"meta":19253,"navigation":135,"path":19254,"seo":19255,"stem":19256,"tags":19257,"updated":3220,"__hash__":19258},"blog/blog/020.scale-image-fit-column.md","How do I scale an image proportionally to fit a column?",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":18088,"toc":19223},[18089,18091,18105,18107,18127,18138,18147,18221,18224,18228,18789,18800,18804,18807,18882,18896,18900,18903,18976,18982,18988,19073,19077,19080,19087,19091,19097,19165,19170,19172,19195,19197,19200,19212,19220],[41,18090,4879],{"id":4878},[14,18092,18093,18094,18097,18098,18101,18102,18104],{},"I have a logo, a chart, or a screenshot — say a 1200×800 PNG — and I want it inside one of my ",[3163,18095,1587],{"href":3165,"rel":18096},[3167]," columns. I do ",[1629,18099,18100],{},"not"," want to do the aspect-ratio math by hand. I do ",[1629,18103,18100],{}," want it stretched into an oval. I do not want it overflowing into the next column. Just shrink it to fit, keep it proportional, done.",[41,18106,44],{"id":43},[109,18108,18110],{"className":111,"code":18109,"language":113,"meta":114,"style":114},"c.Image(imgBytes)\n",[18,18111,18112],{"__ignoreMap":114},[118,18113,18114,18116,18118,18120,18122,18125],{"class":120,"line":121},[118,18115,401],{"class":226},[118,18117,25],{"class":124},[118,18119,1773],{"class":213},[118,18121,255],{"class":124},[118,18123,18124],{"class":226},"imgBytes",[118,18126,199],{"class":124},[14,18128,18129,18130,18133,18134,18137],{},"That is the whole recipe in the most common case. ",[18,18131,18132],{},"c.Image"," defaults to ",[18,18135,18136],{},"FitContain",", which scales the image down to the column width while keeping the original aspect ratio. If the image is already smaller than the column, gpdf draws it at its natural size.",[14,18139,18140,18141,12386,18144,6349],{},"Need a smaller bound than the full column? Add ",[18,18142,18143],{},"template.FitWidth",[18,18145,18146],{},"template.FitHeight",[109,18148,18150],{"className":111,"code":18149,"language":113,"meta":114,"style":114},"c.Image(imgBytes, template.FitWidth(document.Mm(40)))\nc.Image(imgBytes, template.FitHeight(document.Mm(20)))\n",[18,18151,18152,18186],{"__ignoreMap":114},[118,18153,18154,18156,18158,18160,18162,18164,18166,18168,18170,18172,18174,18176,18178,18180,18182,18184],{"class":120,"line":121},[118,18155,401],{"class":226},[118,18157,25],{"class":124},[118,18159,1773],{"class":213},[118,18161,255],{"class":124},[118,18163,18124],{"class":226},[118,18165,395],{"class":124},[118,18167,233],{"class":226},[118,18169,25],{"class":124},[118,18171,16581],{"class":213},[118,18173,255],{"class":124},[118,18175,258],{"class":226},[118,18177,25],{"class":124},[118,18179,294],{"class":213},[118,18181,255],{"class":124},[118,18183,9910],{"class":299},[118,18185,563],{"class":124},[118,18187,18188,18190,18192,18194,18196,18198,18200,18202,18204,18207,18209,18211,18213,18215,18217,18219],{"class":120,"line":132},[118,18189,401],{"class":226},[118,18191,25],{"class":124},[118,18193,1773],{"class":213},[118,18195,255],{"class":124},[118,18197,18124],{"class":226},[118,18199,395],{"class":124},[118,18201,233],{"class":226},[118,18203,25],{"class":124},[118,18205,18206],{"class":213},"FitHeight",[118,18208,255],{"class":124},[118,18210,258],{"class":226},[118,18212,25],{"class":124},[118,18214,294],{"class":213},[118,18216,255],{"class":124},[118,18218,300],{"class":299},[118,18220,563],{"class":124},[14,18222,18223],{},"Both options preserve the source aspect ratio. You only specify one dimension; gpdf computes the other.",[41,18225,18227],{"id":18226},"a-complete-example","A complete example",[109,18229,18231],{"className":111,"code":18230,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    logo, err := os.ReadFile(\"logo.png\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    chart, err := os.ReadFile(\"chart.png\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n\n    page.AutoRow(func(r *template.RowBuilder) {\n        // Narrow column for the logo, bounded to 30mm wide.\n        r.Col(3, func(c *template.ColBuilder) {\n            c.Image(logo, template.FitWidth(document.Mm(30)))\n        })\n        // Wide column for the chart, default fit fills the available width.\n        r.Col(9, func(c *template.ColBuilder) {\n            c.Image(chart)\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"report.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,18232,18233,18239,18243,18249,18257,18265,18269,18277,18285,18293,18297,18301,18311,18338,18350,18364,18368,18396,18408,18422,18426,18430,18444,18462,18492,18496,18500,18514,18518,18542,18547,18577,18613,18617,18622,18652,18667,18671,18675,18679,18697,18709,18723,18727,18767,18781,18785],{"__ignoreMap":114},[118,18234,18235,18237],{"class":120,"line":121},[118,18236,125],{"class":124},[118,18238,129],{"class":128},[118,18240,18241],{"class":120,"line":132},[118,18242,136],{"emptyLinePlaceholder":135},[118,18244,18245,18247],{"class":120,"line":139},[118,18246,143],{"class":142},[118,18248,146],{"class":124},[118,18250,18251,18253,18255],{"class":120,"line":149},[118,18252,152],{"class":124},[118,18254,5303],{"class":128},[118,18256,158],{"class":124},[118,18258,18259,18261,18263],{"class":120,"line":161},[118,18260,152],{"class":124},[118,18262,155],{"class":128},[118,18264,158],{"class":124},[118,18266,18267],{"class":120,"line":166},[118,18268,136],{"emptyLinePlaceholder":135},[118,18270,18271,18273,18275],{"class":120,"line":176},[118,18272,152],{"class":124},[118,18274,3203],{"class":128},[118,18276,158],{"class":124},[118,18278,18279,18281,18283],{"class":120,"line":186},[118,18280,152],{"class":124},[118,18282,171],{"class":128},[118,18284,158],{"class":124},[118,18286,18287,18289,18291],{"class":120,"line":196},[118,18288,152],{"class":124},[118,18290,191],{"class":128},[118,18292,158],{"class":124},[118,18294,18295],{"class":120,"line":202},[118,18296,199],{"class":124},[118,18298,18299],{"class":120,"line":207},[118,18300,136],{"emptyLinePlaceholder":135},[118,18302,18303,18305,18307,18309],{"class":120,"line":223},[118,18304,210],{"class":124},[118,18306,214],{"class":213},[118,18308,217],{"class":124},[118,18310,220],{"class":124},[118,18312,18313,18316,18318,18320,18322,18324,18326,18328,18330,18332,18334,18336],{"class":120,"line":244},[118,18314,18315],{"class":226},"    logo",[118,18317,395],{"class":124},[118,18319,1391],{"class":226},[118,18321,230],{"class":124},[118,18323,1447],{"class":226},[118,18325,25],{"class":124},[118,18327,9545],{"class":213},[118,18329,255],{"class":124},[118,18331,430],{"class":124},[118,18333,1780],{"class":433},[118,18335,430],{"class":124},[118,18337,199],{"class":124},[118,18339,18340,18342,18344,18346,18348],{"class":120,"line":269},[118,18341,1408],{"class":142},[118,18343,1391],{"class":226},[118,18345,1413],{"class":124},[118,18347,1416],{"class":124},[118,18349,220],{"class":124},[118,18351,18352,18354,18356,18358,18360,18362],{"class":120,"line":306},[118,18353,5818],{"class":226},[118,18355,25],{"class":124},[118,18357,5823],{"class":213},[118,18359,255],{"class":124},[118,18361,1429],{"class":226},[118,18363,199],{"class":124},[118,18365,18366],{"class":120,"line":312},[118,18367,1375],{"class":124},[118,18369,18370,18373,18375,18377,18379,18381,18383,18385,18387,18389,18392,18394],{"class":120,"line":317},[118,18371,18372],{"class":226},"    chart",[118,18374,395],{"class":124},[118,18376,1391],{"class":226},[118,18378,230],{"class":124},[118,18380,1447],{"class":226},[118,18382,25],{"class":124},[118,18384,9545],{"class":213},[118,18386,255],{"class":124},[118,18388,430],{"class":124},[118,18390,18391],{"class":433},"chart.png",[118,18393,430],{"class":124},[118,18395,199],{"class":124},[118,18397,18398,18400,18402,18404,18406],{"class":120,"line":350},[118,18399,1408],{"class":142},[118,18401,1391],{"class":226},[118,18403,1413],{"class":124},[118,18405,1416],{"class":124},[118,18407,220],{"class":124},[118,18409,18410,18412,18414,18416,18418,18420],{"class":120,"line":379},[118,18411,5818],{"class":226},[118,18413,25],{"class":124},[118,18415,5823],{"class":213},[118,18417,255],{"class":124},[118,18419,1429],{"class":226},[118,18421,199],{"class":124},[118,18423,18424],{"class":120,"line":417},[118,18425,1375],{"class":124},[118,18427,18428],{"class":120,"line":466},[118,18429,136],{"emptyLinePlaceholder":135},[118,18431,18432,18434,18436,18438,18440,18442],{"class":120,"line":472},[118,18433,227],{"class":226},[118,18435,230],{"class":124},[118,18437,3595],{"class":226},[118,18439,25],{"class":124},[118,18441,3600],{"class":213},[118,18443,241],{"class":124},[118,18445,18446,18448,18450,18452,18454,18456,18458,18460],{"class":120,"line":503},[118,18447,3607],{"class":226},[118,18449,25],{"class":124},[118,18451,252],{"class":213},[118,18453,255],{"class":124},[118,18455,1587],{"class":226},[118,18457,25],{"class":124},[118,18459,263],{"class":226},[118,18461,266],{"class":124},[118,18463,18464,18466,18468,18470,18472,18474,18476,18478,18480,18482,18484,18486,18488,18490],{"class":120,"line":537},[118,18465,3607],{"class":226},[118,18467,25],{"class":124},[118,18469,276],{"class":213},[118,18471,255],{"class":124},[118,18473,258],{"class":226},[118,18475,25],{"class":124},[118,18477,285],{"class":213},[118,18479,255],{"class":124},[118,18481,258],{"class":226},[118,18483,25],{"class":124},[118,18485,294],{"class":213},[118,18487,255],{"class":124},[118,18489,300],{"class":299},[118,18491,303],{"class":124},[118,18493,18494],{"class":120,"line":566},[118,18495,309],{"class":124},[118,18497,18498],{"class":120,"line":571},[118,18499,136],{"emptyLinePlaceholder":135},[118,18501,18502,18504,18506,18508,18510,18512],{"class":120,"line":577},[118,18503,5431],{"class":226},[118,18505,230],{"class":124},[118,18507,1185],{"class":226},[118,18509,25],{"class":124},[118,18511,1190],{"class":213},[118,18513,1193],{"class":124},[118,18515,18516],{"class":120,"line":602},[118,18517,136],{"emptyLinePlaceholder":135},[118,18519,18520,18522,18524,18526,18528,18530,18532,18534,18536,18538,18540],{"class":120,"line":633},[118,18521,5494],{"class":226},[118,18523,25],{"class":124},[118,18525,358],{"class":213},[118,18527,328],{"class":124},[118,18529,363],{"class":331},[118,18531,334],{"class":124},[118,18533,337],{"class":128},[118,18535,25],{"class":124},[118,18537,372],{"class":128},[118,18539,345],{"class":124},[118,18541,220],{"class":124},[118,18543,18544],{"class":120,"line":668},[118,18545,18546],{"class":3981},"        // Narrow column for the logo, bounded to 30mm wide.\n",[118,18548,18549,18551,18553,18555,18557,18559,18561,18563,18565,18567,18569,18571,18573,18575],{"class":120,"line":693},[118,18550,1737],{"class":226},[118,18552,25],{"class":124},[118,18554,387],{"class":213},[118,18556,255],{"class":124},[118,18558,688],{"class":299},[118,18560,395],{"class":124},[118,18562,398],{"class":124},[118,18564,401],{"class":331},[118,18566,334],{"class":124},[118,18568,337],{"class":128},[118,18570,25],{"class":124},[118,18572,410],{"class":128},[118,18574,345],{"class":124},[118,18576,220],{"class":124},[118,18578,18579,18581,18583,18585,18587,18590,18592,18594,18596,18598,18600,18602,18604,18606,18608,18611],{"class":120,"line":698},[118,18580,1768],{"class":226},[118,18582,25],{"class":124},[118,18584,1773],{"class":213},[118,18586,255],{"class":124},[118,18588,18589],{"class":226},"logo",[118,18591,395],{"class":124},[118,18593,233],{"class":226},[118,18595,25],{"class":124},[118,18597,16581],{"class":213},[118,18599,255],{"class":124},[118,18601,258],{"class":226},[118,18603,25],{"class":124},[118,18605,294],{"class":213},[118,18607,255],{"class":124},[118,18609,18610],{"class":299},"30",[118,18612,563],{"class":124},[118,18614,18615],{"class":120,"line":703},[118,18616,574],{"class":124},[118,18618,18619],{"class":120,"line":709},[118,18620,18621],{"class":3981},"        // Wide column for the chart, default fit fills the available width.\n",[118,18623,18624,18626,18628,18630,18632,18634,18636,18638,18640,18642,18644,18646,18648,18650],{"class":120,"line":714},[118,18625,1737],{"class":226},[118,18627,25],{"class":124},[118,18629,387],{"class":213},[118,18631,255],{"class":124},[118,18633,532],{"class":299},[118,18635,395],{"class":124},[118,18637,398],{"class":124},[118,18639,401],{"class":331},[118,18641,334],{"class":124},[118,18643,337],{"class":128},[118,18645,25],{"class":124},[118,18647,410],{"class":128},[118,18649,345],{"class":124},[118,18651,220],{"class":124},[118,18653,18654,18656,18658,18660,18662,18665],{"class":120,"line":740},[118,18655,1768],{"class":226},[118,18657,25],{"class":124},[118,18659,1773],{"class":213},[118,18661,255],{"class":124},[118,18663,18664],{"class":226},"chart",[118,18666,199],{"class":124},[118,18668,18669],{"class":120,"line":765},[118,18670,574],{"class":124},[118,18672,18673],{"class":120,"line":796},[118,18674,706],{"class":124},[118,18676,18677],{"class":120,"line":819},[118,18678,136],{"emptyLinePlaceholder":135},[118,18680,18681,18683,18685,18687,18689,18691,18693,18695],{"class":120,"line":851},[118,18682,5787],{"class":226},[118,18684,395],{"class":124},[118,18686,1391],{"class":226},[118,18688,230],{"class":124},[118,18690,1185],{"class":226},[118,18692,25],{"class":124},[118,18694,1400],{"class":213},[118,18696,1193],{"class":124},[118,18698,18699,18701,18703,18705,18707],{"class":120,"line":875},[118,18700,1408],{"class":142},[118,18702,1391],{"class":226},[118,18704,1413],{"class":124},[118,18706,1416],{"class":124},[118,18708,220],{"class":124},[118,18710,18711,18713,18715,18717,18719,18721],{"class":120,"line":880},[118,18712,5818],{"class":226},[118,18714,25],{"class":124},[118,18716,5823],{"class":213},[118,18718,255],{"class":124},[118,18720,1429],{"class":226},[118,18722,199],{"class":124},[118,18724,18725],{"class":120,"line":885},[118,18726,1375],{"class":124},[118,18728,18729,18731,18733,18735,18737,18739,18741,18743,18745,18747,18749,18751,18753,18755,18757,18759,18761,18763,18765],{"class":120,"line":910},[118,18730,1408],{"class":142},[118,18732,1391],{"class":226},[118,18734,230],{"class":124},[118,18736,1447],{"class":226},[118,18738,25],{"class":124},[118,18740,1452],{"class":213},[118,18742,255],{"class":124},[118,18744,430],{"class":124},[118,18746,1459],{"class":433},[118,18748,430],{"class":124},[118,18750,395],{"class":124},[118,18752,5859],{"class":226},[118,18754,395],{"class":124},[118,18756,1471],{"class":299},[118,18758,7902],{"class":124},[118,18760,1391],{"class":226},[118,18762,1413],{"class":124},[118,18764,1416],{"class":124},[118,18766,220],{"class":124},[118,18768,18769,18771,18773,18775,18777,18779],{"class":120,"line":941},[118,18770,5818],{"class":226},[118,18772,25],{"class":124},[118,18774,5823],{"class":213},[118,18776,255],{"class":124},[118,18778,1429],{"class":226},[118,18780,199],{"class":124},[118,18782,18783],{"class":120,"line":974},[118,18784,1375],{"class":124},[118,18786,18787],{"class":120,"line":997},[118,18788,1479],{"class":124},[14,18790,18791,18792,18795,18796,18799],{},"Two things are happening here. The 3-column logo cell uses ",[18,18793,18794],{},"FitWidth(30mm)"," because we want the logo small and consistent regardless of how much room the column has. The 9-column chart cell takes a bare ",[18,18797,18798],{},"c.Image(chart)"," because we want the chart to use everything the column will give it. Both stay proportional. Neither needs the source pixel dimensions to be known in code.",[41,18801,18803],{"id":18802},"what-proportional-actually-means-in-gpdf","What \"proportional\" actually means in gpdf",[14,18805,18806],{},"Four fit modes exist; one of them is the default and covers maybe 90% of real use:",[1516,18808,18809,18822],{},[1519,18810,18811],{},[1522,18812,18813,18816,18819],{},[1525,18814,18815],{},"Mode",[1525,18817,18818],{},"What it does",[1525,18820,18821],{},"When to use it",[1532,18823,18824,18837,18853,18869],{},[1522,18825,18826,18831,18834],{},[1537,18827,18828,18830],{},[18,18829,18136],{}," (default)",[1537,18832,18833],{},"Scales down to fit inside the box, preserves aspect, may leave empty space",[1537,18835,18836],{},"Logos, charts, screenshots — almost everything",[1522,18838,18839,18844,18850],{},[1537,18840,18841],{},[18,18842,18843],{},"FitCover",[1537,18845,18846,18847],{},"Scales up or down to cover the entire box, preserves aspect, ",[1629,18848,18849],{},"clips overflow",[1537,18851,18852],{},"Hero banners, profile photo crops",[1522,18854,18855,18860,18866],{},[1537,18856,18857],{},[18,18858,18859],{},"FitStretch",[1537,18861,18862,18863],{},"Scales to exactly fill the box, ",[1629,18864,18865],{},"distorts aspect",[1537,18867,18868],{},"Almost never — usually a bug if you reach for this",[1522,18870,18871,18876,18879],{},[1537,18872,18873],{},[18,18874,18875],{},"FitOriginal",[1537,18877,18878],{},"Renders at the source pixel dimensions converted at 72 DPI",[1537,18880,18881],{},"Diagrams that were authored at print resolution and must not be resampled",[14,18883,18884,54,18886,18888,18889,18891,18892,18895],{},[18,18885,16581],{},[18,18887,18206],{}," both pin one dimension and use ",[18,18890,18136],{}," for the other. They are the ergonomic shortcut for \"I care about width\" or \"I care about height\" — you almost never need to call ",[18,18893,18894],{},"WithFitMode"," directly.",[41,18897,18899],{"id":18898},"the-trap-people-fall-into","The trap people fall into",[14,18901,18902],{},"The mistake we see most often is supplying both a width and a height that don't match the source aspect ratio, then complaining the image looks squished. That happens when you do something like this:",[109,18904,18906],{"className":111,"code":18905,"language":113,"meta":114,"style":114},"// Don't do this unless you really mean it.\nc.Image(img,\n    template.FitWidth(document.Mm(40)),\n    template.FitHeight(document.Mm(40)),\n)\n",[18,18907,18908,18913,18928,18950,18972],{"__ignoreMap":114},[118,18909,18910],{"class":120,"line":121},[118,18911,18912],{"class":3981},"// Don't do this unless you really mean it.\n",[118,18914,18915,18917,18919,18921,18923,18926],{"class":120,"line":132},[118,18916,401],{"class":226},[118,18918,25],{"class":124},[118,18920,1773],{"class":213},[118,18922,255],{"class":124},[118,18924,18925],{"class":226},"img",[118,18927,2643],{"class":124},[118,18929,18930,18932,18934,18936,18938,18940,18942,18944,18946,18948],{"class":120,"line":139},[118,18931,2775],{"class":226},[118,18933,25],{"class":124},[118,18935,16581],{"class":213},[118,18937,255],{"class":124},[118,18939,258],{"class":226},[118,18941,25],{"class":124},[118,18943,294],{"class":213},[118,18945,255],{"class":124},[118,18947,9910],{"class":299},[118,18949,3646],{"class":124},[118,18951,18952,18954,18956,18958,18960,18962,18964,18966,18968,18970],{"class":120,"line":149},[118,18953,2775],{"class":226},[118,18955,25],{"class":124},[118,18957,18206],{"class":213},[118,18959,255],{"class":124},[118,18961,258],{"class":226},[118,18963,25],{"class":124},[118,18965,294],{"class":213},[118,18967,255],{"class":124},[118,18969,9910],{"class":299},[118,18971,3646],{"class":124},[118,18973,18974],{"class":120,"line":161},[118,18975,199],{"class":124},[14,18977,18978,18979,18981],{},"If your PNG is 1200×800, forcing it into a 40×40 box means one of two things has to give: the aspect ratio (FitStretch behavior) or part of the image (FitCover behavior). The default fit mode is ",[18,18980,18136],{},", so gpdf will keep the aspect and leave one dimension under-filled — the image will be 40mm wide and ~26mm tall, sitting in a 40mm tall slot with empty space below.",[14,18983,18984,18985,18987],{},"The fix is to pick one dimension and trust the math. If you really do need a square crop of a non-square image, you want ",[18,18986,18843],{},", not two competing dimensions:",[109,18989,18991],{"className":111,"code":18990,"language":113,"meta":114,"style":114},"c.Image(img,\n    template.FitWidth(document.Mm(40)),\n    template.FitHeight(document.Mm(40)),\n    template.WithFitMode(document.FitCover),\n)\n",[18,18992,18993,19007,19029,19051,19069],{"__ignoreMap":114},[118,18994,18995,18997,18999,19001,19003,19005],{"class":120,"line":121},[118,18996,401],{"class":226},[118,18998,25],{"class":124},[118,19000,1773],{"class":213},[118,19002,255],{"class":124},[118,19004,18925],{"class":226},[118,19006,2643],{"class":124},[118,19008,19009,19011,19013,19015,19017,19019,19021,19023,19025,19027],{"class":120,"line":132},[118,19010,2775],{"class":226},[118,19012,25],{"class":124},[118,19014,16581],{"class":213},[118,19016,255],{"class":124},[118,19018,258],{"class":226},[118,19020,25],{"class":124},[118,19022,294],{"class":213},[118,19024,255],{"class":124},[118,19026,9910],{"class":299},[118,19028,3646],{"class":124},[118,19030,19031,19033,19035,19037,19039,19041,19043,19045,19047,19049],{"class":120,"line":139},[118,19032,2775],{"class":226},[118,19034,25],{"class":124},[118,19036,18206],{"class":213},[118,19038,255],{"class":124},[118,19040,258],{"class":226},[118,19042,25],{"class":124},[118,19044,294],{"class":213},[118,19046,255],{"class":124},[118,19048,9910],{"class":299},[118,19050,3646],{"class":124},[118,19052,19053,19055,19057,19059,19061,19063,19065,19067],{"class":120,"line":149},[118,19054,2775],{"class":226},[118,19056,25],{"class":124},[118,19058,18894],{"class":213},[118,19060,255],{"class":124},[118,19062,258],{"class":226},[118,19064,25],{"class":124},[118,19066,18843],{"class":226},[118,19068,266],{"class":124},[118,19070,19071],{"class":120,"line":161},[118,19072,199],{"class":124},[41,19074,19076],{"id":19075},"pixel-size-doesnt-lie","Pixel size doesn't lie",[14,19078,19079],{},"gpdf reads the intrinsic pixel dimensions out of the PNG or JPEG header before any scaling decision. So a 4000×3000 photo dropped into a 60mm column is not \"scaled at the source\" — gpdf embeds the full image bytes, and the PDF reader does the resampling at render time. The output PDF will be the same file size as if you had embedded the photo at any other display dimension.",[14,19081,19082,19083,19086],{},"If file size matters more than maximum print quality, downscale the source image with something like ",[18,19084,19085],{},"image/draw"," before handing it to gpdf. The library will not silently throw away pixels for you. That choice belongs to you.",[41,19088,19090],{"id":19089},"what-about-the-layout-overflow-case","What about the layout overflow case?",[14,19092,19093,19094,19096],{},"If a column ends up too narrow at render time — because the page broke unexpectedly, or a table cell shrank to fit content — the default ",[18,19095,18136],{}," will gladly scale your logo down to a postage stamp. If that bothers you, set a floor:",[109,19098,19100],{"className":111,"code":19099,"language":113,"meta":114,"style":114},"c.Image(logo,\n    template.FitWidth(document.Mm(30)),\n    template.MinDisplayWidth(document.Mm(20)),\n)\n",[18,19101,19102,19116,19138,19161],{"__ignoreMap":114},[118,19103,19104,19106,19108,19110,19112,19114],{"class":120,"line":121},[118,19105,401],{"class":226},[118,19107,25],{"class":124},[118,19109,1773],{"class":213},[118,19111,255],{"class":124},[118,19113,18589],{"class":226},[118,19115,2643],{"class":124},[118,19117,19118,19120,19122,19124,19126,19128,19130,19132,19134,19136],{"class":120,"line":132},[118,19119,2775],{"class":226},[118,19121,25],{"class":124},[118,19123,16581],{"class":213},[118,19125,255],{"class":124},[118,19127,258],{"class":226},[118,19129,25],{"class":124},[118,19131,294],{"class":213},[118,19133,255],{"class":124},[118,19135,18610],{"class":299},[118,19137,3646],{"class":124},[118,19139,19140,19142,19144,19147,19149,19151,19153,19155,19157,19159],{"class":120,"line":139},[118,19141,2775],{"class":226},[118,19143,25],{"class":124},[118,19145,19146],{"class":213},"MinDisplayWidth",[118,19148,255],{"class":124},[118,19150,258],{"class":226},[118,19152,25],{"class":124},[118,19154,294],{"class":213},[118,19156,255],{"class":124},[118,19158,300],{"class":299},[118,19160,3646],{"class":124},[118,19162,19163],{"class":120,"line":149},[118,19164,199],{"class":124},[14,19166,19167,19169],{},[18,19168,19146],{}," tells the layout engine: if you would have to shrink this image below 20mm to make it fit, push it to the next page instead. The image stays legible or it doesn't get drawn — never the worst-of-both middle ground.",[41,19171,6894],{"id":6893},[46,19173,19174,19184,19189],{},[49,19175,19176,19180,19181,19183],{},[3163,19177,19179],{"href":19178},"/blog/embed-png-transparency","How do I embed a PNG with transparency in gpdf?"," — same ",[18,19182,18132],{}," entry point, but with the alpha-channel details",[49,19185,19186,19188],{},[3163,19187,6902],{"href":6901}," — what \"the column width\" actually resolves to",[49,19190,19191,19194],{},[3163,19192,19193],{"href":8448},"How do I set column widths in a table?"," — when the box around your image is a table cell, not a row column",[41,19196,4794],{"id":4793},[14,19198,19199],{},"gpdf is a Go library for generating PDFs. MIT, zero external dependencies, pure-Go image and font handling.",[109,19201,19202],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,19203,19204],{"__ignoreMap":114},[118,19205,19206,19208,19210],{"class":120,"line":121},[118,19207,113],{"class":128},[118,19209,3156],{"class":433},[118,19211,3159],{"class":433},[14,19213,19214,3169,19217],{},[3163,19215,3168],{"href":3165,"rel":19216},[3167],[3163,19218,3174],{"href":3172,"rel":19219},[3167],[3176,19221,19222],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":114,"searchDepth":132,"depth":132,"links":19224},[19225,19226,19227,19228,19229,19230,19231,19232,19233],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":18226,"depth":132,"text":18227},{"id":18802,"depth":132,"text":18803},{"id":18898,"depth":132,"text":18899},{"id":19075,"depth":132,"text":19076},{"id":19089,"depth":132,"text":19090},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-05-05","gpdf already does it. c.Image(bytes) fills the column width and preserves aspect ratio. Use FitWidth or FitHeight for explicit bounds, WithFitMode for the non-default behaviors.",{"name":19237,"totalTime":19238,"tools":19239,"steps":19240},"Scale an image proportionally inside a gpdf column","PT5M",[3202,3203],[19241,19244,19247,19250],{"name":19242,"text":19243},"Pass the image bytes to c.Image with no fit options","Inside a column, call c.Image(imgBytes). The default fit mode is FitContain, so gpdf scales the image to the column width while preserving the aspect ratio. No manual width or height calculation needed.",{"name":19245,"text":19246},"Use FitWidth when you want a specific width smaller than the column","Call c.Image(imgBytes, template.FitWidth(document.Mm(40))) to bound the image to 40mm wide. Height is derived from the source aspect ratio.",{"name":19248,"text":19249},"Use FitHeight when the vertical bound matters more","Call c.Image(imgBytes, template.FitHeight(document.Mm(20))) to lock the image to 20mm tall, with width derived from aspect.",{"name":19251,"text":19252},"Reach for WithFitMode only when you need a non-default behavior","Pass template.WithFitMode(document.FitCover) to fill the box and clip overflow, FitStretch to distort to exact bounds, or FitOriginal to render at the source pixel size converted at 72 DPI.",{},"/blog/scale-image-fit-column",{"title":18085,"description":19235},"blog/020.scale-image-fit-column",[6996,3226],"FRCVgtzUV9n25OrxSN6D1a4ts2M7MbN1W3iGbbkvS-k",{"id":19260,"title":8459,"author":19261,"body":19262,"date":20722,"description":20723,"draft":3196,"extension":3197,"howTo":20724,"image":3220,"meta":20737,"navigation":135,"path":8458,"seo":20738,"stem":20739,"tags":20740,"updated":3220,"__hash__":20741},"blog/blog/019.zebra-striped-table-rows.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":19263,"toc":20711},[19264,19266,19277,19279,19322,19325,19327,20192,20197,20201,20211,20279,20282,20285,20289,20296,20372,20375,20386,20390,20396,20599,20602,20610,20612,20662,20664,20683,20685,20688,20700,20708],[41,19265,4879],{"id":4878},[14,19267,19268,19269,19272,19273,19276],{},"I have a table — invoices, transactions, log lines, anything with more than 5 rows — and I want every other row tinted gray so the eye can track across without losing the line. Bootstrap calls it ",[18,19270,19271],{},".table-striped",". I just want that, in ",[3163,19274,1587],{"href":3165,"rel":19275},[3167],", without writing a row loop.",[41,19278,44],{"id":43},[109,19280,19282],{"className":111,"code":19281,"language":113,"meta":114,"style":114},"c.Table(header, rows, template.TableStripe(pdf.RGBHex(0xF5F5F5)))\n",[18,19283,19284],{"__ignoreMap":114},[118,19285,19286,19288,19290,19292,19294,19296,19298,19300,19302,19304,19306,19308,19310,19312,19314,19316,19318,19320],{"class":120,"line":121},[118,19287,401],{"class":226},[118,19289,25],{"class":124},[118,19291,4929],{"class":213},[118,19293,255],{"class":124},[118,19295,3095],{"class":226},[118,19297,395],{"class":124},[118,19299,5118],{"class":226},[118,19301,395],{"class":124},[118,19303,233],{"class":226},[118,19305,25],{"class":124},[118,19307,9973],{"class":213},[118,19309,255],{"class":124},[118,19311,550],{"class":226},[118,19313,25],{"class":124},[118,19315,658],{"class":213},[118,19317,255],{"class":124},[118,19319,9986],{"class":299},[118,19321,563],{"class":124},[14,19323,19324],{},"That's it. gpdf handles the alternation. The header is excluded — only body rows are striped. The first body row stays plain; the second is tinted; the third plain; the fourth tinted; and so on.",[41,19326,7069],{"id":7068},[109,19328,19330],{"className":111,"code":19329,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\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 := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    brand := pdf.RGBHex(0x1A237E)     // header background\n    stripe := pdf.RGBHex(0xF5F5F5)    // every-other-row tint\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Q1 Sales\", template.FontSize(20), template.Bold())\n            c.Spacer(document.Mm(4))\n\n            c.Table(\n                []string{\"Product\", \"Region\", \"Qty\", \"Revenue\"},\n                [][]string{\n                    {\"Laptop Pro 15\", \"NA\",   \"120\", \"$155,880\"},\n                    {\"Wireless Mouse\", \"EU\",  \"640\", \"$19,193\"},\n                    {\"USB-C Hub\",      \"APAC\",\"410\", \"$20,495\"},\n                    {\"Monitor 27\\\"\",   \"NA\",  \"180\", \"$71,820\"},\n                    {\"Keyboard\",       \"EU\",  \"320\", \"$25,596\"},\n                    {\"Webcam HD\",      \"APAC\",\"260\", \"$23,397\"},\n                },\n                template.ColumnWidths(40, 20, 15, 25),\n                template.TableHeaderStyle(\n                    template.TextColor(pdf.White),\n                    template.BgColor(brand),\n                ),\n                template.TableStripe(stripe),\n            )\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"sales.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,19331,19332,19338,19342,19348,19356,19364,19368,19376,19384,19392,19400,19404,19408,19418,19432,19450,19480,19484,19488,19509,19530,19534,19548,19572,19602,19641,19663,19667,19677,19721,19730,19772,19813,19854,19896,19936,19974,19979,20005,20015,20033,20047,20051,20065,20069,20073,20077,20081,20099,20111,20125,20129,20170,20184,20188],{"__ignoreMap":114},[118,19333,19334,19336],{"class":120,"line":121},[118,19335,125],{"class":124},[118,19337,129],{"class":128},[118,19339,19340],{"class":120,"line":132},[118,19341,136],{"emptyLinePlaceholder":135},[118,19343,19344,19346],{"class":120,"line":139},[118,19345,143],{"class":142},[118,19347,146],{"class":124},[118,19349,19350,19352,19354],{"class":120,"line":149},[118,19351,152],{"class":124},[118,19353,5303],{"class":128},[118,19355,158],{"class":124},[118,19357,19358,19360,19362],{"class":120,"line":161},[118,19359,152],{"class":124},[118,19361,155],{"class":128},[118,19363,158],{"class":124},[118,19365,19366],{"class":120,"line":166},[118,19367,136],{"emptyLinePlaceholder":135},[118,19369,19370,19372,19374],{"class":120,"line":176},[118,19371,152],{"class":124},[118,19373,3203],{"class":128},[118,19375,158],{"class":124},[118,19377,19378,19380,19382],{"class":120,"line":186},[118,19379,152],{"class":124},[118,19381,171],{"class":128},[118,19383,158],{"class":124},[118,19385,19386,19388,19390],{"class":120,"line":196},[118,19387,152],{"class":124},[118,19389,181],{"class":128},[118,19391,158],{"class":124},[118,19393,19394,19396,19398],{"class":120,"line":202},[118,19395,152],{"class":124},[118,19397,191],{"class":128},[118,19399,158],{"class":124},[118,19401,19402],{"class":120,"line":207},[118,19403,199],{"class":124},[118,19405,19406],{"class":120,"line":223},[118,19407,136],{"emptyLinePlaceholder":135},[118,19409,19410,19412,19414,19416],{"class":120,"line":244},[118,19411,210],{"class":124},[118,19413,214],{"class":213},[118,19415,217],{"class":124},[118,19417,220],{"class":124},[118,19419,19420,19422,19424,19426,19428,19430],{"class":120,"line":269},[118,19421,227],{"class":226},[118,19423,230],{"class":124},[118,19425,3595],{"class":226},[118,19427,25],{"class":124},[118,19429,3600],{"class":213},[118,19431,241],{"class":124},[118,19433,19434,19436,19438,19440,19442,19444,19446,19448],{"class":120,"line":306},[118,19435,3607],{"class":226},[118,19437,25],{"class":124},[118,19439,252],{"class":213},[118,19441,255],{"class":124},[118,19443,1587],{"class":226},[118,19445,25],{"class":124},[118,19447,263],{"class":226},[118,19449,266],{"class":124},[118,19451,19452,19454,19456,19458,19460,19462,19464,19466,19468,19470,19472,19474,19476,19478],{"class":120,"line":312},[118,19453,3607],{"class":226},[118,19455,25],{"class":124},[118,19457,276],{"class":213},[118,19459,255],{"class":124},[118,19461,258],{"class":226},[118,19463,25],{"class":124},[118,19465,285],{"class":213},[118,19467,255],{"class":124},[118,19469,258],{"class":226},[118,19471,25],{"class":124},[118,19473,294],{"class":213},[118,19475,255],{"class":124},[118,19477,300],{"class":299},[118,19479,303],{"class":124},[118,19481,19482],{"class":120,"line":317},[118,19483,309],{"class":124},[118,19485,19486],{"class":120,"line":350},[118,19487,136],{"emptyLinePlaceholder":135},[118,19489,19490,19492,19494,19496,19498,19500,19502,19504,19506],{"class":120,"line":379},[118,19491,7255],{"class":226},[118,19493,230],{"class":124},[118,19495,7260],{"class":226},[118,19497,25],{"class":124},[118,19499,658],{"class":213},[118,19501,255],{"class":124},[118,19503,7269],{"class":299},[118,19505,345],{"class":124},[118,19507,19508],{"class":3981},"     // header background\n",[118,19510,19511,19513,19515,19517,19519,19521,19523,19525,19527],{"class":120,"line":417},[118,19512,10428],{"class":226},[118,19514,230],{"class":124},[118,19516,7260],{"class":226},[118,19518,25],{"class":124},[118,19520,658],{"class":213},[118,19522,255],{"class":124},[118,19524,9986],{"class":299},[118,19526,345],{"class":124},[118,19528,19529],{"class":3981},"    // every-other-row tint\n",[118,19531,19532],{"class":120,"line":466},[118,19533,136],{"emptyLinePlaceholder":135},[118,19535,19536,19538,19540,19542,19544,19546],{"class":120,"line":472},[118,19537,5431],{"class":226},[118,19539,230],{"class":124},[118,19541,1185],{"class":226},[118,19543,25],{"class":124},[118,19545,1190],{"class":213},[118,19547,1193],{"class":124},[118,19549,19550,19552,19554,19556,19558,19560,19562,19564,19566,19568,19570],{"class":120,"line":503},[118,19551,5494],{"class":226},[118,19553,25],{"class":124},[118,19555,358],{"class":213},[118,19557,328],{"class":124},[118,19559,363],{"class":331},[118,19561,334],{"class":124},[118,19563,337],{"class":128},[118,19565,25],{"class":124},[118,19567,372],{"class":128},[118,19569,345],{"class":124},[118,19571,220],{"class":124},[118,19573,19574,19576,19578,19580,19582,19584,19586,19588,19590,19592,19594,19596,19598,19600],{"class":120,"line":537},[118,19575,1737],{"class":226},[118,19577,25],{"class":124},[118,19579,387],{"class":213},[118,19581,255],{"class":124},[118,19583,20],{"class":299},[118,19585,395],{"class":124},[118,19587,398],{"class":124},[118,19589,401],{"class":331},[118,19591,334],{"class":124},[118,19593,337],{"class":128},[118,19595,25],{"class":124},[118,19597,410],{"class":128},[118,19599,345],{"class":124},[118,19601,220],{"class":124},[118,19603,19604,19606,19608,19610,19612,19614,19617,19619,19621,19623,19625,19627,19629,19631,19633,19635,19637,19639],{"class":120,"line":566},[118,19605,1768],{"class":226},[118,19607,25],{"class":124},[118,19609,425],{"class":213},[118,19611,255],{"class":124},[118,19613,430],{"class":124},[118,19615,19616],{"class":433},"Q1 Sales",[118,19618,430],{"class":124},[118,19620,395],{"class":124},[118,19622,233],{"class":226},[118,19624,25],{"class":124},[118,19626,455],{"class":213},[118,19628,255],{"class":124},[118,19630,300],{"class":299},[118,19632,1280],{"class":124},[118,19634,233],{"class":226},[118,19636,25],{"class":124},[118,19638,445],{"class":213},[118,19640,1289],{"class":124},[118,19642,19643,19645,19647,19649,19651,19653,19655,19657,19659,19661],{"class":120,"line":571},[118,19644,1768],{"class":226},[118,19646,25],{"class":124},[118,19648,675],{"class":213},[118,19650,255],{"class":124},[118,19652,258],{"class":226},[118,19654,25],{"class":124},[118,19656,294],{"class":213},[118,19658,255],{"class":124},[118,19660,1493],{"class":299},[118,19662,463],{"class":124},[118,19664,19665],{"class":120,"line":577},[118,19666,136],{"emptyLinePlaceholder":135},[118,19668,19669,19671,19673,19675],{"class":120,"line":602},[118,19670,1768],{"class":226},[118,19672,25],{"class":124},[118,19674,4929],{"class":213},[118,19676,241],{"class":124},[118,19678,19679,19682,19684,19686,19688,19691,19693,19695,19697,19700,19702,19704,19706,19708,19710,19712,19714,19717,19719],{"class":120,"line":633},[118,19680,19681],{"class":124},"                []",[118,19683,1131],{"class":1130},[118,19685,1134],{"class":124},[118,19687,430],{"class":124},[118,19689,19690],{"class":433},"Product",[118,19692,430],{"class":124},[118,19694,395],{"class":124},[118,19696,1146],{"class":124},[118,19698,19699],{"class":433},"Region",[118,19701,430],{"class":124},[118,19703,395],{"class":124},[118,19705,1146],{"class":124},[118,19707,14469],{"class":433},[118,19709,430],{"class":124},[118,19711,395],{"class":124},[118,19713,1146],{"class":124},[118,19715,19716],{"class":433},"Revenue",[118,19718,430],{"class":124},[118,19720,8191],{"class":124},[118,19722,19723,19726,19728],{"class":120,"line":668},[118,19724,19725],{"class":124},"                [][]",[118,19727,1131],{"class":1130},[118,19729,7406],{"class":124},[118,19731,19732,19735,19737,19740,19742,19744,19746,19749,19751,19753,19756,19759,19761,19763,19765,19768,19770],{"class":120,"line":693},[118,19733,19734],{"class":124},"                    {",[118,19736,430],{"class":124},[118,19738,19739],{"class":433},"Laptop Pro 15",[118,19741,430],{"class":124},[118,19743,395],{"class":124},[118,19745,1146],{"class":124},[118,19747,19748],{"class":433},"NA",[118,19750,430],{"class":124},[118,19752,395],{"class":124},[118,19754,19755],{"class":124},"   \"",[118,19757,19758],{"class":433},"120",[118,19760,430],{"class":124},[118,19762,395],{"class":124},[118,19764,1146],{"class":124},[118,19766,19767],{"class":433},"$155,880",[118,19769,430],{"class":124},[118,19771,8191],{"class":124},[118,19773,19774,19776,19778,19781,19783,19785,19787,19790,19792,19794,19797,19800,19802,19804,19806,19809,19811],{"class":120,"line":698},[118,19775,19734],{"class":124},[118,19777,430],{"class":124},[118,19779,19780],{"class":433},"Wireless Mouse",[118,19782,430],{"class":124},[118,19784,395],{"class":124},[118,19786,1146],{"class":124},[118,19788,19789],{"class":433},"EU",[118,19791,430],{"class":124},[118,19793,395],{"class":124},[118,19795,19796],{"class":124},"  \"",[118,19798,19799],{"class":433},"640",[118,19801,430],{"class":124},[118,19803,395],{"class":124},[118,19805,1146],{"class":124},[118,19807,19808],{"class":433},"$19,193",[118,19810,430],{"class":124},[118,19812,8191],{"class":124},[118,19814,19815,19817,19819,19822,19824,19826,19829,19832,19834,19836,19838,19841,19843,19845,19847,19850,19852],{"class":120,"line":703},[118,19816,19734],{"class":124},[118,19818,430],{"class":124},[118,19820,19821],{"class":433},"USB-C Hub",[118,19823,430],{"class":124},[118,19825,395],{"class":124},[118,19827,19828],{"class":124},"      \"",[118,19830,19831],{"class":433},"APAC",[118,19833,430],{"class":124},[118,19835,395],{"class":124},[118,19837,430],{"class":124},[118,19839,19840],{"class":433},"410",[118,19842,430],{"class":124},[118,19844,395],{"class":124},[118,19846,1146],{"class":124},[118,19848,19849],{"class":433},"$20,495",[118,19851,430],{"class":124},[118,19853,8191],{"class":124},[118,19855,19856,19858,19860,19863,19866,19868,19870,19872,19874,19876,19878,19880,19883,19885,19887,19889,19892,19894],{"class":120,"line":709},[118,19857,19734],{"class":124},[118,19859,430],{"class":124},[118,19861,19862],{"class":433},"Monitor 27",[118,19864,19865],{"class":226},"\\\"",[118,19867,430],{"class":124},[118,19869,395],{"class":124},[118,19871,19755],{"class":124},[118,19873,19748],{"class":433},[118,19875,430],{"class":124},[118,19877,395],{"class":124},[118,19879,19796],{"class":124},[118,19881,19882],{"class":433},"180",[118,19884,430],{"class":124},[118,19886,395],{"class":124},[118,19888,1146],{"class":124},[118,19890,19891],{"class":433},"$71,820",[118,19893,430],{"class":124},[118,19895,8191],{"class":124},[118,19897,19898,19900,19902,19905,19907,19909,19912,19914,19916,19918,19920,19923,19925,19927,19929,19932,19934],{"class":120,"line":714},[118,19899,19734],{"class":124},[118,19901,430],{"class":124},[118,19903,19904],{"class":433},"Keyboard",[118,19906,430],{"class":124},[118,19908,395],{"class":124},[118,19910,19911],{"class":124},"       \"",[118,19913,19789],{"class":433},[118,19915,430],{"class":124},[118,19917,395],{"class":124},[118,19919,19796],{"class":124},[118,19921,19922],{"class":433},"320",[118,19924,430],{"class":124},[118,19926,395],{"class":124},[118,19928,1146],{"class":124},[118,19930,19931],{"class":433},"$25,596",[118,19933,430],{"class":124},[118,19935,8191],{"class":124},[118,19937,19938,19940,19942,19945,19947,19949,19951,19953,19955,19957,19959,19961,19963,19965,19967,19970,19972],{"class":120,"line":740},[118,19939,19734],{"class":124},[118,19941,430],{"class":124},[118,19943,19944],{"class":433},"Webcam HD",[118,19946,430],{"class":124},[118,19948,395],{"class":124},[118,19950,19828],{"class":124},[118,19952,19831],{"class":433},[118,19954,430],{"class":124},[118,19956,395],{"class":124},[118,19958,430],{"class":124},[118,19960,15122],{"class":433},[118,19962,430],{"class":124},[118,19964,395],{"class":124},[118,19966,1146],{"class":124},[118,19968,19969],{"class":433},"$23,397",[118,19971,430],{"class":124},[118,19973,8191],{"class":124},[118,19975,19976],{"class":120,"line":765},[118,19977,19978],{"class":124},"                },\n",[118,19980,19981,19983,19985,19987,19989,19991,19993,19995,19997,19999,20001,20003],{"class":120,"line":796},[118,19982,2648],{"class":226},[118,19984,25],{"class":124},[118,19986,7733],{"class":213},[118,19988,255],{"class":124},[118,19990,9910],{"class":299},[118,19992,395],{"class":124},[118,19994,7742],{"class":299},[118,19996,395],{"class":124},[118,19998,9915],{"class":299},[118,20000,395],{"class":124},[118,20002,9924],{"class":299},[118,20004,266],{"class":124},[118,20006,20007,20009,20011,20013],{"class":120,"line":819},[118,20008,2648],{"class":226},[118,20010,25],{"class":124},[118,20012,7762],{"class":213},[118,20014,241],{"class":124},[118,20016,20017,20019,20021,20023,20025,20027,20029,20031],{"class":120,"line":851},[118,20018,540],{"class":226},[118,20020,25],{"class":124},[118,20022,545],{"class":213},[118,20024,255],{"class":124},[118,20026,550],{"class":226},[118,20028,25],{"class":124},[118,20030,7781],{"class":226},[118,20032,266],{"class":124},[118,20034,20035,20037,20039,20041,20043,20045],{"class":120,"line":875},[118,20036,540],{"class":226},[118,20038,25],{"class":124},[118,20040,7792],{"class":213},[118,20042,255],{"class":124},[118,20044,7797],{"class":226},[118,20046,266],{"class":124},[118,20048,20049],{"class":120,"line":880},[118,20050,7804],{"class":124},[118,20052,20053,20055,20057,20059,20061,20063],{"class":120,"line":885},[118,20054,2648],{"class":226},[118,20056,25],{"class":124},[118,20058,9973],{"class":213},[118,20060,255],{"class":124},[118,20062,11009],{"class":226},[118,20064,266],{"class":124},[118,20066,20067],{"class":120,"line":910},[118,20068,7809],{"class":124},[118,20070,20071],{"class":120,"line":941},[118,20072,574],{"class":124},[118,20074,20075],{"class":120,"line":974},[118,20076,706],{"class":124},[118,20078,20079],{"class":120,"line":997},[118,20080,136],{"emptyLinePlaceholder":135},[118,20082,20083,20085,20087,20089,20091,20093,20095,20097],{"class":120,"line":1002},[118,20084,5787],{"class":226},[118,20086,395],{"class":124},[118,20088,1391],{"class":226},[118,20090,230],{"class":124},[118,20092,1185],{"class":226},[118,20094,25],{"class":124},[118,20096,1400],{"class":213},[118,20098,1193],{"class":124},[118,20100,20101,20103,20105,20107,20109],{"class":120,"line":1033},[118,20102,1408],{"class":142},[118,20104,1391],{"class":226},[118,20106,1413],{"class":124},[118,20108,1416],{"class":124},[118,20110,220],{"class":124},[118,20112,20113,20115,20117,20119,20121,20123],{"class":120,"line":1065},[118,20114,5818],{"class":226},[118,20116,25],{"class":124},[118,20118,5823],{"class":213},[118,20120,255],{"class":124},[118,20122,1429],{"class":226},[118,20124,199],{"class":124},[118,20126,20127],{"class":120,"line":1088},[118,20128,1375],{"class":124},[118,20130,20131,20133,20135,20137,20139,20141,20143,20145,20147,20150,20152,20154,20156,20158,20160,20162,20164,20166,20168],{"class":120,"line":1093},[118,20132,1408],{"class":142},[118,20134,1391],{"class":226},[118,20136,230],{"class":124},[118,20138,1447],{"class":226},[118,20140,25],{"class":124},[118,20142,1452],{"class":213},[118,20144,255],{"class":124},[118,20146,430],{"class":124},[118,20148,20149],{"class":433},"sales.pdf",[118,20151,430],{"class":124},[118,20153,395],{"class":124},[118,20155,5859],{"class":226},[118,20157,395],{"class":124},[118,20159,1471],{"class":299},[118,20161,7902],{"class":124},[118,20163,1391],{"class":226},[118,20165,1413],{"class":124},[118,20167,1416],{"class":124},[118,20169,220],{"class":124},[118,20171,20172,20174,20176,20178,20180,20182],{"class":120,"line":1098},[118,20173,5818],{"class":226},[118,20175,25],{"class":124},[118,20177,5823],{"class":213},[118,20179,255],{"class":124},[118,20181,1429],{"class":226},[118,20183,199],{"class":124},[118,20185,20186],{"class":120,"line":1103},[118,20187,1375],{"class":124},[118,20189,20190],{"class":120,"line":1108},[118,20191,1479],{"class":124},[14,20193,20194,20196],{},[18,20195,106],{},". Six body rows, three of them tinted, header in dark blue with white text. The kind of report that goes in a Monday email.",[41,20198,20200],{"id":20199},"how-the-alternation-works","How the alternation works",[14,20202,20203,20204,20206,20207,20210],{},"Internally, gpdf walks the body rows with an index ",[18,20205,7441],{}," starting at 0 and applies the stripe to rows where ",[18,20208,20209],{},"i%2 == 1",". The header row is its own slice and isn't counted. So:",[1516,20212,20213,20226],{},[1519,20214,20215],{},[1522,20216,20217,20220,20223],{},[1525,20218,20219],{},"Body row index (0-based)",[1525,20221,20222],{},"Visually",[1525,20224,20225],{},"Striped?",[1532,20227,20228,20238,20250,20259,20270],{},[1522,20229,20230,20232,20235],{},[1537,20231,4159],{},[1537,20233,20234],{},"1st",[1537,20236,20237],{},"no",[1522,20239,20240,20242,20245],{},[1537,20241,2402],{},[1537,20243,20244],{},"2nd",[1537,20246,20247],{},[1629,20248,20249],{},"yes",[1522,20251,20252,20254,20257],{},[1537,20253,870],{},[1537,20255,20256],{},"3rd",[1537,20258,20237],{},[1522,20260,20261,20263,20266],{},[1537,20262,688],{},[1537,20264,20265],{},"4th",[1537,20267,20268],{},[1629,20269,20249],{},[1522,20271,20272,20275,20277],{},[1537,20273,20274],{},"...",[1537,20276,20274],{},[1537,20278,20274],{},[14,20280,20281],{},"That parity matches the Bootstrap convention. The first row of data sits clean, and the stripe is the visual \"rest\" — your eye travels across the white row, the next is shaded, repeat.",[14,20283,20284],{},"There's no option to flip the parity (stripe odd-indexed rows instead). If you really want it inverted, prepend an empty row to the body — but you don't, because nobody actually wants that.",[41,20286,20288],{"id":20287},"pick-the-color","Pick the color",[14,20290,20291,20292,20295],{},"The whole point is ",[1629,20293,20294],{},"subtle",". A stripe loud enough to compete with the text defeats itself.",[109,20297,20299],{"className":111,"code":20298,"language":113,"meta":114,"style":114},"pdf.RGBHex(0xF5F5F5) // gentle warm gray — Bootstrap default territory\npdf.RGBHex(0xFAFAFA) // even softer, almost imperceptible at small sizes\npdf.RGBHex(0xEEF2FF) // pale brand tint (works if header is the brand color)\npdf.Gray(0.96)       // grayscale equivalent — saves a few bytes in PDF/A workflows\n",[18,20300,20301,20318,20336,20354],{"__ignoreMap":114},[118,20302,20303,20305,20307,20309,20311,20313,20315],{"class":120,"line":121},[118,20304,550],{"class":226},[118,20306,25],{"class":124},[118,20308,658],{"class":213},[118,20310,255],{"class":124},[118,20312,9986],{"class":299},[118,20314,345],{"class":124},[118,20316,20317],{"class":3981}," // gentle warm gray — Bootstrap default territory\n",[118,20319,20320,20322,20324,20326,20328,20331,20333],{"class":120,"line":132},[118,20321,550],{"class":226},[118,20323,25],{"class":124},[118,20325,658],{"class":213},[118,20327,255],{"class":124},[118,20329,20330],{"class":299},"0xFAFAFA",[118,20332,345],{"class":124},[118,20334,20335],{"class":3981}," // even softer, almost imperceptible at small sizes\n",[118,20337,20338,20340,20342,20344,20346,20349,20351],{"class":120,"line":139},[118,20339,550],{"class":226},[118,20341,25],{"class":124},[118,20343,658],{"class":213},[118,20345,255],{"class":124},[118,20347,20348],{"class":299},"0xEEF2FF",[118,20350,345],{"class":124},[118,20352,20353],{"class":3981}," // pale brand tint (works if header is the brand color)\n",[118,20355,20356,20358,20360,20362,20364,20367,20369],{"class":120,"line":149},[118,20357,550],{"class":226},[118,20359,25],{"class":124},[118,20361,555],{"class":213},[118,20363,255],{"class":124},[118,20365,20366],{"class":299},"0.96",[118,20368,345],{"class":124},[118,20370,20371],{"class":3981},"       // grayscale equivalent — saves a few bytes in PDF/A workflows\n",[14,20373,20374],{},"Avoid saturated colors. A blue stripe at 60% saturation reads \"this row is selected/important\" and breaks the across-row scan that zebra striping is supposed to fix.",[14,20376,20377,20378,20381,20382,20385],{},"For dark themes (rare in PDFs but they exist for slide-style reports), ",[18,20379,20380],{},"pdf.RGBHex(0x202020)"," over a ",[18,20383,20384],{},"0x1A1A1A"," page works. Keep the contrast ratio low.",[41,20387,20389],{"id":20388},"combine-with-cell-borders","Combine with cell borders",[14,20391,20392,20393,20395],{},"Stripes alone are enough for short tables. For dense, finance-style tables, pair stripes with ",[18,20394,11020],{}," to draw a hairline between every cell:",[109,20397,20399],{"className":111,"code":20398,"language":113,"meta":114,"style":114},"hairline := template.Border(\n    template.BorderWidth(document.Pt(0.5)),\n    template.BorderColor(pdf.Gray(0.85)),\n)\n\nc.Table(header, rows,\n    template.ColumnWidths(40, 20, 15, 25),\n    template.TableHeaderStyle(\n        template.TextColor(pdf.White),\n        template.BgColor(brand),\n    ),\n    template.TableStripe(pdf.RGBHex(0xF5F5F5)),\n    template.WithTableCellBorder(hairline),\n)\n",[18,20400,20401,20416,20438,20460,20464,20468,20486,20512,20522,20540,20554,20559,20581,20595],{"__ignoreMap":114},[118,20402,20403,20406,20408,20410,20412,20414],{"class":120,"line":121},[118,20404,20405],{"class":226},"hairline ",[118,20407,230],{"class":124},[118,20409,233],{"class":226},[118,20411,25],{"class":124},[118,20413,6075],{"class":213},[118,20415,241],{"class":124},[118,20417,20418,20420,20422,20424,20426,20428,20430,20432,20434,20436],{"class":120,"line":132},[118,20419,2775],{"class":226},[118,20421,25],{"class":124},[118,20423,6086],{"class":213},[118,20425,255],{"class":124},[118,20427,258],{"class":226},[118,20429,25],{"class":124},[118,20431,6095],{"class":213},[118,20433,255],{"class":124},[118,20435,560],{"class":299},[118,20437,3646],{"class":124},[118,20439,20440,20442,20444,20446,20448,20450,20452,20454,20456,20458],{"class":120,"line":139},[118,20441,2775],{"class":226},[118,20443,25],{"class":124},[118,20445,6110],{"class":213},[118,20447,255],{"class":124},[118,20449,550],{"class":226},[118,20451,25],{"class":124},[118,20453,555],{"class":213},[118,20455,255],{"class":124},[118,20457,10500],{"class":299},[118,20459,3646],{"class":124},[118,20461,20462],{"class":120,"line":149},[118,20463,199],{"class":124},[118,20465,20466],{"class":120,"line":161},[118,20467,136],{"emptyLinePlaceholder":135},[118,20469,20470,20472,20474,20476,20478,20480,20482,20484],{"class":120,"line":166},[118,20471,401],{"class":226},[118,20473,25],{"class":124},[118,20475,4929],{"class":213},[118,20477,255],{"class":124},[118,20479,3095],{"class":226},[118,20481,395],{"class":124},[118,20483,5118],{"class":226},[118,20485,2643],{"class":124},[118,20487,20488,20490,20492,20494,20496,20498,20500,20502,20504,20506,20508,20510],{"class":120,"line":176},[118,20489,2775],{"class":226},[118,20491,25],{"class":124},[118,20493,7733],{"class":213},[118,20495,255],{"class":124},[118,20497,9910],{"class":299},[118,20499,395],{"class":124},[118,20501,7742],{"class":299},[118,20503,395],{"class":124},[118,20505,9915],{"class":299},[118,20507,395],{"class":124},[118,20509,9924],{"class":299},[118,20511,266],{"class":124},[118,20513,20514,20516,20518,20520],{"class":120,"line":186},[118,20515,2775],{"class":226},[118,20517,25],{"class":124},[118,20519,7762],{"class":213},[118,20521,241],{"class":124},[118,20523,20524,20526,20528,20530,20532,20534,20536,20538],{"class":120,"line":196},[118,20525,247],{"class":226},[118,20527,25],{"class":124},[118,20529,545],{"class":213},[118,20531,255],{"class":124},[118,20533,550],{"class":226},[118,20535,25],{"class":124},[118,20537,7781],{"class":226},[118,20539,266],{"class":124},[118,20541,20542,20544,20546,20548,20550,20552],{"class":120,"line":202},[118,20543,247],{"class":226},[118,20545,25],{"class":124},[118,20547,7792],{"class":213},[118,20549,255],{"class":124},[118,20551,7797],{"class":226},[118,20553,266],{"class":124},[118,20555,20556],{"class":120,"line":207},[118,20557,20558],{"class":124},"    ),\n",[118,20560,20561,20563,20565,20567,20569,20571,20573,20575,20577,20579],{"class":120,"line":223},[118,20562,2775],{"class":226},[118,20564,25],{"class":124},[118,20566,9973],{"class":213},[118,20568,255],{"class":124},[118,20570,550],{"class":226},[118,20572,25],{"class":124},[118,20574,658],{"class":213},[118,20576,255],{"class":124},[118,20578,9986],{"class":299},[118,20580,3646],{"class":124},[118,20582,20583,20585,20587,20589,20591,20593],{"class":120,"line":244},[118,20584,2775],{"class":226},[118,20586,25],{"class":124},[118,20588,11020],{"class":213},[118,20590,255],{"class":124},[118,20592,11025],{"class":226},[118,20594,266],{"class":124},[118,20596,20597],{"class":120,"line":269},[118,20598,199],{"class":124},[14,20600,20601],{},"Hairline borders + light stripe = the look of every accountant's spreadsheet print preview, deliberately. Keep the border lighter than the stripe so the stripe stays the dominant signal.",[14,20603,20604,20605,20607,20608,25],{},"If you only want an outer frame (no inner grid), swap ",[18,20606,11020],{}," for ",[18,20609,11968],{},[41,20611,8388],{"id":8387},[46,20613,20614,20627,20639,20656],{},[49,20615,20616,4919,20619,20622,20623,20626],{},[1629,20617,20618],{},"Color in the wrong unit range.",[18,20620,20621],{},"pdf.RGB(245, 245, 245)"," produces a black box. The constructor expects 0.0–1.0, not 0–255. Use ",[18,20624,20625],{},"pdf.RGBHex(0xF5F5F5)"," if you're thinking in CSS values.",[49,20628,20629,4919,20632,20634,20635,20638],{},[1629,20630,20631],{},"Striping the header.",[18,20633,9973],{}," does not touch the header. If you want a tinted header, that's ",[18,20636,20637],{},"TableHeaderStyle(template.BgColor(...))"," — a different option. Confusing the two and then wondering why the header isn't tinted is the classic first-time bug.",[49,20640,20641,20644,20645,20648,20649,20651,20652,20655],{},[1629,20642,20643],{},"Two-color alternation."," gpdf supports one stripe color, not two. If you set both ",[18,20646,20647],{},"pdf.White"," (row 0) and ",[18,20650,9986],{}," (row 1), you don't actually need to set white — the page is already white. Asking for ",[18,20653,20654],{},"[white, gray, blue]"," 3-cycle is not a feature; it would also be hostile to the reader.",[49,20657,20658,20661],{},[1629,20659,20660],{},"Stripes on a 3-row table."," A stripe needs at least 4–5 body rows to do its job. On 2–3 rows, it just looks like one cell got selected. Skip it; let the table breathe.",[41,20663,6894],{"id":6893},[46,20665,20666,20673,20678],{},[49,20667,20668,8450,20670,20672],{},[3163,20669,8449],{"href":8448},[18,20671,7733],{}," in detail.",[49,20674,20675,20677],{},[3163,20676,9785],{"href":9784}," — make the table render in your brand font.",[49,20679,20680,20682],{},[3163,20681,6919],{"href":6918}," — a real-world table with header style, stripe, and totals.",[41,20684,4794],{"id":4793},[14,20686,20687],{},"gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, pure-Go TrueType handling.",[109,20689,20690],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,20691,20692],{"__ignoreMap":114},[118,20693,20694,20696,20698],{"class":120,"line":121},[118,20695,113],{"class":128},[118,20697,3156],{"class":433},[118,20699,3159],{"class":433},[14,20701,20702,3169,20705],{},[3163,20703,3168],{"href":3165,"rel":20704},[3167],[3163,20706,3174],{"href":3172,"rel":20707},[3167],[3176,20709,20710],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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);}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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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}",{"title":114,"searchDepth":132,"depth":132,"links":20712},[20713,20714,20715,20716,20717,20718,20719,20720,20721],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":7068,"depth":132,"text":7069},{"id":20199,"depth":132,"text":20200},{"id":20287,"depth":132,"text":20288},{"id":20388,"depth":132,"text":20389},{"id":8387,"depth":132,"text":8388},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-05-04","Pass template.TableStripe to a table call. gpdf paints the alternate body rows with the color you give it. No row-loop, no manual cell styling.",{"name":20725,"totalTime":19238,"tools":20726,"steps":20727},"Add zebra-striped rows to a gpdf table",[3202,3203],[20728,20731,20734],{"name":20729,"text":20730},"Pick a stripe color","Build a pdf.Color with pdf.RGBHex(0xF5F5F5) for a soft gray, or pdf.RGB(r, g, b) with values in 0.0–1.0. Anything readable behind body text works — keep it light.",{"name":20732,"text":20733},"Pass template.TableStripe to the Table call","Inside a column, call c.Table(header, rows, template.TableStripe(stripeColor)). gpdf walks the body rows and applies the color to every other one.",{"name":20735,"text":20736},"Pair it with TableHeaderStyle for contrast","Add template.TableHeaderStyle(template.TextColor(pdf.White), template.BgColor(brand)) so the header sits visually separate from the striped body. Stripes work but feel half-finished without a styled header.",{},{"title":8459,"description":20723},"blog/019.zebra-striped-table-rows",[6996,3226,6997],"GyhMZhHkOb4zcruYd1xI34nFFChKzqGoeJfT4ZOFIf8",{"id":20743,"title":9785,"author":20744,"body":20745,"date":22074,"description":22075,"draft":3196,"extension":3197,"howTo":22076,"image":3220,"meta":22093,"navigation":135,"path":9784,"seo":22094,"stem":22095,"tags":22096,"updated":3220,"__hash__":22097},"blog/blog/018.add-custom-truetype-font.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":20746,"toc":22062},[20747,20749,20763,20765,20785,20799,20801,21301,21318,21322,21353,21359,21363,21366,21430,21439,21442,21696,21706,21710,21715,21798,21804,21808,21811,21970,21973,21977,22013,22015,22035,22037,22039,22051,22059],[41,20748,4879],{"id":4878},[14,20750,20751,20752,20755,20756,20759,20760,20762],{},"I have a ",[18,20753,20754],{},".ttf"," file — Inter for the brand, JetBrains Mono for code blocks, an icon font for glyphs. How do I get it into a ",[3163,20757,1587],{"href":3165,"rel":20758},[3167]," document and reference it from a ",[18,20761,2741],{}," call?",[41,20764,44],{"id":43},[14,20766,20767,20768,1649,20771,20773,20774,20777,20778,20781,20782,25],{},"Load the TTF bytes. Pass ",[18,20769,20770],{},"gpdf.WithFont(\"YourFamily\", bytes)",[18,20772,3600],{},". Then reference ",[18,20775,20776],{},"\"YourFamily\""," from ",[18,20779,20780],{},"template.FontFamily(...)"," or set it as the default with ",[18,20783,20784],{},"gpdf.WithDefaultFont",[14,20786,20787,20788,20791,20792,20795,20796,20798],{},"The family name is ",[1629,20789,20790],{},"arbitrary",". It has nothing to do with the font's internal ",[18,20793,20794],{},"name"," table — it's just the lookup key gpdf uses when resolving a ",[18,20797,2926],{}," option. Pick something short.",[41,20800,7069],{"id":7068},[109,20802,20804],{"className":111,"code":20803,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    regular, err := os.ReadFile(\"Inter-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"Inter\", regular),\n        gpdf.WithDefaultFont(\"Inter\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Quarterly Report\", template.FontSize(28))\n            c.Text(\"Generated with gpdf and Inter.\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"report.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,20805,20806,20812,20816,20822,20830,20838,20842,20850,20858,20866,20870,20874,20884,20912,20924,20938,20942,20946,20960,20978,21008,21032,21054,21058,21062,21076,21100,21130,21160,21179,21183,21187,21191,21209,21221,21235,21239,21279,21293,21297],{"__ignoreMap":114},[118,20807,20808,20810],{"class":120,"line":121},[118,20809,125],{"class":124},[118,20811,129],{"class":128},[118,20813,20814],{"class":120,"line":132},[118,20815,136],{"emptyLinePlaceholder":135},[118,20817,20818,20820],{"class":120,"line":139},[118,20819,143],{"class":142},[118,20821,146],{"class":124},[118,20823,20824,20826,20828],{"class":120,"line":149},[118,20825,152],{"class":124},[118,20827,5303],{"class":128},[118,20829,158],{"class":124},[118,20831,20832,20834,20836],{"class":120,"line":161},[118,20833,152],{"class":124},[118,20835,155],{"class":128},[118,20837,158],{"class":124},[118,20839,20840],{"class":120,"line":166},[118,20841,136],{"emptyLinePlaceholder":135},[118,20843,20844,20846,20848],{"class":120,"line":176},[118,20845,152],{"class":124},[118,20847,3203],{"class":128},[118,20849,158],{"class":124},[118,20851,20852,20854,20856],{"class":120,"line":186},[118,20853,152],{"class":124},[118,20855,171],{"class":128},[118,20857,158],{"class":124},[118,20859,20860,20862,20864],{"class":120,"line":196},[118,20861,152],{"class":124},[118,20863,191],{"class":128},[118,20865,158],{"class":124},[118,20867,20868],{"class":120,"line":202},[118,20869,199],{"class":124},[118,20871,20872],{"class":120,"line":207},[118,20873,136],{"emptyLinePlaceholder":135},[118,20875,20876,20878,20880,20882],{"class":120,"line":223},[118,20877,210],{"class":124},[118,20879,214],{"class":213},[118,20881,217],{"class":124},[118,20883,220],{"class":124},[118,20885,20886,20889,20891,20893,20895,20897,20899,20901,20903,20905,20908,20910],{"class":120,"line":244},[118,20887,20888],{"class":226},"    regular",[118,20890,395],{"class":124},[118,20892,1391],{"class":226},[118,20894,230],{"class":124},[118,20896,1447],{"class":226},[118,20898,25],{"class":124},[118,20900,9545],{"class":213},[118,20902,255],{"class":124},[118,20904,430],{"class":124},[118,20906,20907],{"class":433},"Inter-Regular.ttf",[118,20909,430],{"class":124},[118,20911,199],{"class":124},[118,20913,20914,20916,20918,20920,20922],{"class":120,"line":269},[118,20915,1408],{"class":142},[118,20917,1391],{"class":226},[118,20919,1413],{"class":124},[118,20921,1416],{"class":124},[118,20923,220],{"class":124},[118,20925,20926,20928,20930,20932,20934,20936],{"class":120,"line":306},[118,20927,5818],{"class":226},[118,20929,25],{"class":124},[118,20931,5823],{"class":213},[118,20933,255],{"class":124},[118,20935,1429],{"class":226},[118,20937,199],{"class":124},[118,20939,20940],{"class":120,"line":312},[118,20941,1375],{"class":124},[118,20943,20944],{"class":120,"line":317},[118,20945,136],{"emptyLinePlaceholder":135},[118,20947,20948,20950,20952,20954,20956,20958],{"class":120,"line":350},[118,20949,227],{"class":226},[118,20951,230],{"class":124},[118,20953,3595],{"class":226},[118,20955,25],{"class":124},[118,20957,3600],{"class":213},[118,20959,241],{"class":124},[118,20961,20962,20964,20966,20968,20970,20972,20974,20976],{"class":120,"line":379},[118,20963,3607],{"class":226},[118,20965,25],{"class":124},[118,20967,252],{"class":213},[118,20969,255],{"class":124},[118,20971,1587],{"class":226},[118,20973,25],{"class":124},[118,20975,263],{"class":226},[118,20977,266],{"class":124},[118,20979,20980,20982,20984,20986,20988,20990,20992,20994,20996,20998,21000,21002,21004,21006],{"class":120,"line":417},[118,20981,3607],{"class":226},[118,20983,25],{"class":124},[118,20985,276],{"class":213},[118,20987,255],{"class":124},[118,20989,258],{"class":226},[118,20991,25],{"class":124},[118,20993,285],{"class":213},[118,20995,255],{"class":124},[118,20997,258],{"class":226},[118,20999,25],{"class":124},[118,21001,294],{"class":213},[118,21003,255],{"class":124},[118,21005,300],{"class":299},[118,21007,303],{"class":124},[118,21009,21010,21012,21014,21016,21018,21020,21023,21025,21027,21030],{"class":120,"line":466},[118,21011,3607],{"class":226},[118,21013,25],{"class":124},[118,21015,2798],{"class":213},[118,21017,255],{"class":124},[118,21019,430],{"class":124},[118,21021,21022],{"class":433},"Inter",[118,21024,430],{"class":124},[118,21026,395],{"class":124},[118,21028,21029],{"class":226}," regular",[118,21031,266],{"class":124},[118,21033,21034,21036,21038,21040,21042,21044,21046,21048,21050,21052],{"class":120,"line":472},[118,21035,3607],{"class":226},[118,21037,25],{"class":124},[118,21039,12501],{"class":213},[118,21041,255],{"class":124},[118,21043,430],{"class":124},[118,21045,21022],{"class":433},[118,21047,430],{"class":124},[118,21049,395],{"class":124},[118,21051,14386],{"class":299},[118,21053,266],{"class":124},[118,21055,21056],{"class":120,"line":503},[118,21057,309],{"class":124},[118,21059,21060],{"class":120,"line":537},[118,21061,136],{"emptyLinePlaceholder":135},[118,21063,21064,21066,21068,21070,21072,21074],{"class":120,"line":566},[118,21065,5431],{"class":226},[118,21067,230],{"class":124},[118,21069,1185],{"class":226},[118,21071,25],{"class":124},[118,21073,1190],{"class":213},[118,21075,1193],{"class":124},[118,21077,21078,21080,21082,21084,21086,21088,21090,21092,21094,21096,21098],{"class":120,"line":571},[118,21079,5494],{"class":226},[118,21081,25],{"class":124},[118,21083,358],{"class":213},[118,21085,328],{"class":124},[118,21087,363],{"class":331},[118,21089,334],{"class":124},[118,21091,337],{"class":128},[118,21093,25],{"class":124},[118,21095,372],{"class":128},[118,21097,345],{"class":124},[118,21099,220],{"class":124},[118,21101,21102,21104,21106,21108,21110,21112,21114,21116,21118,21120,21122,21124,21126,21128],{"class":120,"line":577},[118,21103,1737],{"class":226},[118,21105,25],{"class":124},[118,21107,387],{"class":213},[118,21109,255],{"class":124},[118,21111,20],{"class":299},[118,21113,395],{"class":124},[118,21115,398],{"class":124},[118,21117,401],{"class":331},[118,21119,334],{"class":124},[118,21121,337],{"class":128},[118,21123,25],{"class":124},[118,21125,410],{"class":128},[118,21127,345],{"class":124},[118,21129,220],{"class":124},[118,21131,21132,21134,21136,21138,21140,21142,21144,21146,21148,21150,21152,21154,21156,21158],{"class":120,"line":602},[118,21133,1768],{"class":226},[118,21135,25],{"class":124},[118,21137,425],{"class":213},[118,21139,255],{"class":124},[118,21141,430],{"class":124},[118,21143,434],{"class":433},[118,21145,430],{"class":124},[118,21147,395],{"class":124},[118,21149,233],{"class":226},[118,21151,25],{"class":124},[118,21153,455],{"class":213},[118,21155,255],{"class":124},[118,21157,7462],{"class":299},[118,21159,463],{"class":124},[118,21161,21162,21164,21166,21168,21170,21172,21175,21177],{"class":120,"line":633},[118,21163,1768],{"class":226},[118,21165,25],{"class":124},[118,21167,425],{"class":213},[118,21169,255],{"class":124},[118,21171,430],{"class":124},[118,21173,21174],{"class":433},"Generated with gpdf and Inter.",[118,21176,430],{"class":124},[118,21178,199],{"class":124},[118,21180,21181],{"class":120,"line":668},[118,21182,574],{"class":124},[118,21184,21185],{"class":120,"line":693},[118,21186,706],{"class":124},[118,21188,21189],{"class":120,"line":698},[118,21190,136],{"emptyLinePlaceholder":135},[118,21192,21193,21195,21197,21199,21201,21203,21205,21207],{"class":120,"line":703},[118,21194,5787],{"class":226},[118,21196,395],{"class":124},[118,21198,1391],{"class":226},[118,21200,230],{"class":124},[118,21202,1185],{"class":226},[118,21204,25],{"class":124},[118,21206,1400],{"class":213},[118,21208,1193],{"class":124},[118,21210,21211,21213,21215,21217,21219],{"class":120,"line":709},[118,21212,1408],{"class":142},[118,21214,1391],{"class":226},[118,21216,1413],{"class":124},[118,21218,1416],{"class":124},[118,21220,220],{"class":124},[118,21222,21223,21225,21227,21229,21231,21233],{"class":120,"line":714},[118,21224,5818],{"class":226},[118,21226,25],{"class":124},[118,21228,5823],{"class":213},[118,21230,255],{"class":124},[118,21232,1429],{"class":226},[118,21234,199],{"class":124},[118,21236,21237],{"class":120,"line":740},[118,21238,1375],{"class":124},[118,21240,21241,21243,21245,21247,21249,21251,21253,21255,21257,21259,21261,21263,21265,21267,21269,21271,21273,21275,21277],{"class":120,"line":765},[118,21242,1408],{"class":142},[118,21244,1391],{"class":226},[118,21246,230],{"class":124},[118,21248,1447],{"class":226},[118,21250,25],{"class":124},[118,21252,1452],{"class":213},[118,21254,255],{"class":124},[118,21256,430],{"class":124},[118,21258,1459],{"class":433},[118,21260,430],{"class":124},[118,21262,395],{"class":124},[118,21264,5859],{"class":226},[118,21266,395],{"class":124},[118,21268,1471],{"class":299},[118,21270,7902],{"class":124},[118,21272,1391],{"class":226},[118,21274,1413],{"class":124},[118,21276,1416],{"class":124},[118,21278,220],{"class":124},[118,21280,21281,21283,21285,21287,21289,21291],{"class":120,"line":796},[118,21282,5818],{"class":226},[118,21284,25],{"class":124},[118,21286,5823],{"class":213},[118,21288,255],{"class":124},[118,21290,1429],{"class":226},[118,21292,199],{"class":124},[118,21294,21295],{"class":120,"line":819},[118,21296,1375],{"class":124},[118,21298,21299],{"class":120,"line":851},[118,21300,1479],{"class":124},[14,21302,21303,21304,21306,21307,21309,21310,21315,21316,2712],{},"Drop ",[18,21305,20907],{}," next to ",[18,21308,102],{}," (download from ",[3163,21311,21314],{"href":21312,"rel":21313},"https://rsms.me/inter/",[3167],"rsms.me/inter","). ",[18,21317,106],{},[41,21319,21321],{"id":21320},"what-gpdf-does-with-the-bytes","What gpdf does with the bytes",[14,21323,21324,21325,21328,21329,3096,21332,3096,21335,3096,21338,21341,21342,21345,21346,1592,21349,21352],{},"When ",[18,21326,21327],{},"Generate()"," runs, gpdf parses the TrueType tables (",[18,21330,21331],{},"cmap",[18,21333,21334],{},"glyf",[18,21336,21337],{},"loca",[18,21339,21340],{},"hmtx",", …) in pure Go — no FreeType, no CGO. It walks the rendered text, collects the code points actually used, and ",[1629,21343,21344],{},"subsets the glyph table"," to that set. The PDF embeds a ",[18,21347,21348],{},"Type0",[18,21350,21351],{},"CIDFontType2"," font carrying only the glyphs you needed.",[14,21354,21355,21356,21358],{},"Practical effect: a 600 KB ",[18,21357,20907],{}," becomes roughly a 12 KB font subset inside the PDF if your document used a couple of paragraphs. The brand font lands without bloating the file.",[41,21360,21362],{"id":21361},"bold-and-italic-need-their-own-files","Bold and italic need their own files",[14,21364,21365],{},"This is the part that bites people. gpdf does not synthesize bold or italic — there is no algorithmic \"make it bolder\" step. It looks up a variant ID built from the style flags:",[1516,21367,21368,21385],{},[1519,21369,21370],{},[1522,21371,21372,21377,21382],{},[1525,21373,21374],{},[18,21375,21376],{},"Bold()",[1525,21378,21379],{},[18,21380,21381],{},"Italic()",[1525,21383,21384],{},"Lookup key",[1532,21386,21387,21397,21408,21419],{},[1522,21388,21389,21391,21393],{},[1537,21390,20237],{},[1537,21392,20237],{},[1537,21394,21395],{},[18,21396,21022],{},[1522,21398,21399,21401,21403],{},[1537,21400,20249],{},[1537,21402,20237],{},[1537,21404,21405],{},[18,21406,21407],{},"Inter-Bold",[1522,21409,21410,21412,21414],{},[1537,21411,20237],{},[1537,21413,20249],{},[1537,21415,21416],{},[18,21417,21418],{},"Inter-Italic",[1522,21420,21421,21423,21425],{},[1537,21422,20249],{},[1537,21424,20249],{},[1537,21426,21427],{},[18,21428,21429],{},"Inter-BoldItalic",[14,21431,21432,21433,21435,21436,21438],{},"If you didn't register ",[18,21434,21407],{},", the lookup falls back to plain ",[18,21437,21022],{}," — silently. The PDF renders, but everything stays regular weight. There's no warning.",[14,21440,21441],{},"Register all four:",[109,21443,21445],{"className":111,"code":21444,"language":113,"meta":114,"style":114},"regular, _    := os.ReadFile(\"Inter-Regular.ttf\")\nbold, _       := os.ReadFile(\"Inter-Bold.ttf\")\nitalic, _     := os.ReadFile(\"Inter-Italic.ttf\")\nboldItalic, _ := os.ReadFile(\"Inter-BoldItalic.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"Inter\", regular),\n    gpdf.WithFont(\"Inter-Bold\", bold),\n    gpdf.WithFont(\"Inter-Italic\", italic),\n    gpdf.WithFont(\"Inter-BoldItalic\", boldItalic),\n    gpdf.WithDefaultFont(\"Inter\", 11),\n)\n",[18,21446,21447,21475,21504,21533,21561,21565,21579,21601,21624,21647,21670,21692],{"__ignoreMap":114},[118,21448,21449,21452,21454,21457,21459,21461,21463,21465,21467,21469,21471,21473],{"class":120,"line":121},[118,21450,21451],{"class":226},"regular",[118,21453,395],{"class":124},[118,21455,21456],{"class":226}," _    ",[118,21458,230],{"class":124},[118,21460,1447],{"class":226},[118,21462,25],{"class":124},[118,21464,9545],{"class":213},[118,21466,255],{"class":124},[118,21468,430],{"class":124},[118,21470,20907],{"class":433},[118,21472,430],{"class":124},[118,21474,199],{"class":124},[118,21476,21477,21480,21482,21485,21487,21489,21491,21493,21495,21497,21500,21502],{"class":120,"line":132},[118,21478,21479],{"class":226},"bold",[118,21481,395],{"class":124},[118,21483,21484],{"class":226}," _       ",[118,21486,230],{"class":124},[118,21488,1447],{"class":226},[118,21490,25],{"class":124},[118,21492,9545],{"class":213},[118,21494,255],{"class":124},[118,21496,430],{"class":124},[118,21498,21499],{"class":433},"Inter-Bold.ttf",[118,21501,430],{"class":124},[118,21503,199],{"class":124},[118,21505,21506,21509,21511,21514,21516,21518,21520,21522,21524,21526,21529,21531],{"class":120,"line":139},[118,21507,21508],{"class":226},"italic",[118,21510,395],{"class":124},[118,21512,21513],{"class":226}," _     ",[118,21515,230],{"class":124},[118,21517,1447],{"class":226},[118,21519,25],{"class":124},[118,21521,9545],{"class":213},[118,21523,255],{"class":124},[118,21525,430],{"class":124},[118,21527,21528],{"class":433},"Inter-Italic.ttf",[118,21530,430],{"class":124},[118,21532,199],{"class":124},[118,21534,21535,21538,21540,21542,21544,21546,21548,21550,21552,21554,21557,21559],{"class":120,"line":149},[118,21536,21537],{"class":226},"boldItalic",[118,21539,395],{"class":124},[118,21541,3999],{"class":226},[118,21543,230],{"class":124},[118,21545,1447],{"class":226},[118,21547,25],{"class":124},[118,21549,9545],{"class":213},[118,21551,255],{"class":124},[118,21553,430],{"class":124},[118,21555,21556],{"class":433},"Inter-BoldItalic.ttf",[118,21558,430],{"class":124},[118,21560,199],{"class":124},[118,21562,21563],{"class":120,"line":161},[118,21564,136],{"emptyLinePlaceholder":135},[118,21566,21567,21569,21571,21573,21575,21577],{"class":120,"line":166},[118,21568,2760],{"class":226},[118,21570,230],{"class":124},[118,21572,3595],{"class":226},[118,21574,25],{"class":124},[118,21576,3600],{"class":213},[118,21578,241],{"class":124},[118,21580,21581,21583,21585,21587,21589,21591,21593,21595,21597,21599],{"class":120,"line":176},[118,21582,4532],{"class":226},[118,21584,25],{"class":124},[118,21586,2798],{"class":213},[118,21588,255],{"class":124},[118,21590,430],{"class":124},[118,21592,21022],{"class":433},[118,21594,430],{"class":124},[118,21596,395],{"class":124},[118,21598,21029],{"class":226},[118,21600,266],{"class":124},[118,21602,21603,21605,21607,21609,21611,21613,21615,21617,21619,21622],{"class":120,"line":186},[118,21604,4532],{"class":226},[118,21606,25],{"class":124},[118,21608,2798],{"class":213},[118,21610,255],{"class":124},[118,21612,430],{"class":124},[118,21614,21407],{"class":433},[118,21616,430],{"class":124},[118,21618,395],{"class":124},[118,21620,21621],{"class":226}," bold",[118,21623,266],{"class":124},[118,21625,21626,21628,21630,21632,21634,21636,21638,21640,21642,21645],{"class":120,"line":196},[118,21627,4532],{"class":226},[118,21629,25],{"class":124},[118,21631,2798],{"class":213},[118,21633,255],{"class":124},[118,21635,430],{"class":124},[118,21637,21418],{"class":433},[118,21639,430],{"class":124},[118,21641,395],{"class":124},[118,21643,21644],{"class":226}," italic",[118,21646,266],{"class":124},[118,21648,21649,21651,21653,21655,21657,21659,21661,21663,21665,21668],{"class":120,"line":202},[118,21650,4532],{"class":226},[118,21652,25],{"class":124},[118,21654,2798],{"class":213},[118,21656,255],{"class":124},[118,21658,430],{"class":124},[118,21660,21429],{"class":433},[118,21662,430],{"class":124},[118,21664,395],{"class":124},[118,21666,21667],{"class":226}," boldItalic",[118,21669,266],{"class":124},[118,21671,21672,21674,21676,21678,21680,21682,21684,21686,21688,21690],{"class":120,"line":207},[118,21673,4532],{"class":226},[118,21675,25],{"class":124},[118,21677,12501],{"class":213},[118,21679,255],{"class":124},[118,21681,430],{"class":124},[118,21683,21022],{"class":433},[118,21685,430],{"class":124},[118,21687,395],{"class":124},[118,21689,14386],{"class":299},[118,21691,266],{"class":124},[118,21693,21694],{"class":120,"line":223},[118,21695,199],{"class":124},[14,21697,21698,21699,12386,21702,21705],{},"If a font ships only one weight (lots of icon and display fonts do), don't call ",[18,21700,21701],{},"template.Bold()",[18,21703,21704],{},"template.Italic()"," for those at all. Skipping a variant is fine. Falling back to the wrong variant is what produces \"why is the bold not bold\" bug reports.",[41,21707,21709],{"id":21708},"embed-the-font-in-the-binary","Embed the font in the binary",[14,21711,21712,21714],{},[18,21713,17912],{}," at startup works in development. In production the font is part of the program — it should travel inside the binary:",[109,21716,21718],{"className":111,"code":21717,"language":113,"meta":114,"style":114},"import _ \"embed\"\n\n//go:embed fonts/Inter-Regular.ttf\nvar interRegular []byte\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"Inter\", interRegular),\n)\n",[18,21719,21720,21733,21737,21742,21753,21757,21771,21794],{"__ignoreMap":114},[118,21721,21722,21724,21726,21728,21731],{"class":120,"line":121},[118,21723,143],{"class":142},[118,21725,3999],{"class":226},[118,21727,430],{"class":124},[118,21729,21730],{"class":128},"embed",[118,21732,158],{"class":124},[118,21734,21735],{"class":120,"line":132},[118,21736,136],{"emptyLinePlaceholder":135},[118,21738,21739],{"class":120,"line":139},[118,21740,21741],{"class":3981},"//go:embed fonts/Inter-Regular.ttf\n",[118,21743,21744,21746,21749,21751],{"class":120,"line":149},[118,21745,16492],{"class":124},[118,21747,21748],{"class":226}," interRegular ",[118,21750,16498],{"class":124},[118,21752,16501],{"class":1130},[118,21754,21755],{"class":120,"line":161},[118,21756,136],{"emptyLinePlaceholder":135},[118,21758,21759,21761,21763,21765,21767,21769],{"class":120,"line":166},[118,21760,2760],{"class":226},[118,21762,230],{"class":124},[118,21764,3595],{"class":226},[118,21766,25],{"class":124},[118,21768,3600],{"class":213},[118,21770,241],{"class":124},[118,21772,21773,21775,21777,21779,21781,21783,21785,21787,21789,21792],{"class":120,"line":176},[118,21774,4532],{"class":226},[118,21776,25],{"class":124},[118,21778,2798],{"class":213},[118,21780,255],{"class":124},[118,21782,430],{"class":124},[118,21784,21022],{"class":433},[118,21786,430],{"class":124},[118,21788,395],{"class":124},[118,21790,21791],{"class":226}," interRegular",[118,21793,266],{"class":124},[118,21795,21796],{"class":120,"line":186},[118,21797,199],{"class":124},[14,21799,21800,21803],{},[18,21801,21802],{},"go build"," bakes the bytes in. No more \"where is the .ttf in the deploy image\" debugging on a Friday afternoon.",[41,21805,21807],{"id":21806},"icon-fonts-work-the-same-way","Icon fonts work the same way",[14,21809,21810],{},"Font Awesome, Material Symbols exported as TTF, IcoMoon, custom brand glyph sets — they're all just TrueType files. Register them the same way:",[109,21812,21814],{"className":111,"code":21813,"language":113,"meta":114,"style":114},"icons, _ := os.ReadFile(\"MaterialSymbols-Regular.ttf\")\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"Icons\", icons),\n    gpdf.WithDefaultFont(\"Inter\", 11), // body text default\n)\n\n// In a column:\nc.Text(\"\", template.FontFamily(\"Icons\"), template.FontSize(20)) // \"home\" icon\n",[18,21815,21816,21844,21858,21882,21907,21911,21915,21920],{"__ignoreMap":114},[118,21817,21818,21821,21823,21825,21827,21829,21831,21833,21835,21837,21840,21842],{"class":120,"line":121},[118,21819,21820],{"class":226},"icons",[118,21822,395],{"class":124},[118,21824,3999],{"class":226},[118,21826,230],{"class":124},[118,21828,1447],{"class":226},[118,21830,25],{"class":124},[118,21832,9545],{"class":213},[118,21834,255],{"class":124},[118,21836,430],{"class":124},[118,21838,21839],{"class":433},"MaterialSymbols-Regular.ttf",[118,21841,430],{"class":124},[118,21843,199],{"class":124},[118,21845,21846,21848,21850,21852,21854,21856],{"class":120,"line":132},[118,21847,2760],{"class":226},[118,21849,230],{"class":124},[118,21851,3595],{"class":226},[118,21853,25],{"class":124},[118,21855,3600],{"class":213},[118,21857,241],{"class":124},[118,21859,21860,21862,21864,21866,21868,21870,21873,21875,21877,21880],{"class":120,"line":139},[118,21861,4532],{"class":226},[118,21863,25],{"class":124},[118,21865,2798],{"class":213},[118,21867,255],{"class":124},[118,21869,430],{"class":124},[118,21871,21872],{"class":433},"Icons",[118,21874,430],{"class":124},[118,21876,395],{"class":124},[118,21878,21879],{"class":226}," icons",[118,21881,266],{"class":124},[118,21883,21884,21886,21888,21890,21892,21894,21896,21898,21900,21902,21904],{"class":120,"line":149},[118,21885,4532],{"class":226},[118,21887,25],{"class":124},[118,21889,12501],{"class":213},[118,21891,255],{"class":124},[118,21893,430],{"class":124},[118,21895,21022],{"class":433},[118,21897,430],{"class":124},[118,21899,395],{"class":124},[118,21901,14386],{"class":299},[118,21903,1280],{"class":124},[118,21905,21906],{"class":3981}," // body text default\n",[118,21908,21909],{"class":120,"line":161},[118,21910,199],{"class":124},[118,21912,21913],{"class":120,"line":166},[118,21914,136],{"emptyLinePlaceholder":135},[118,21916,21917],{"class":120,"line":176},[118,21918,21919],{"class":3981},"// In a column:\n",[118,21921,21922,21924,21926,21928,21930,21932,21935,21937,21939,21941,21943,21945,21947,21949,21951,21953,21955,21957,21959,21961,21963,21965,21967],{"class":120,"line":186},[118,21923,401],{"class":226},[118,21925,25],{"class":124},[118,21927,425],{"class":213},[118,21929,255],{"class":124},[118,21931,430],{"class":124},[118,21933,21934],{"class":433},"",[118,21936,430],{"class":124},[118,21938,395],{"class":124},[118,21940,233],{"class":226},[118,21942,25],{"class":124},[118,21944,2926],{"class":213},[118,21946,255],{"class":124},[118,21948,430],{"class":124},[118,21950,21872],{"class":433},[118,21952,430],{"class":124},[118,21954,1280],{"class":124},[118,21956,233],{"class":226},[118,21958,25],{"class":124},[118,21960,455],{"class":213},[118,21962,255],{"class":124},[118,21964,300],{"class":299},[118,21966,9688],{"class":124},[118,21968,21969],{"class":3981}," // \"home\" icon\n",[14,21971,21972],{},"The Unicode escape is whatever the font's documentation says it is. gpdf doesn't care that the glyph is an icon — to it, it's a code point, and it subsets the same way it does for letters.",[41,21974,21976],{"id":21975},"common-mistakes","Common mistakes",[46,21978,21979,21988,22000],{},[49,21980,21981,4919,21984,21987],{},[1629,21982,21983],{},"Family name typo at the call site.",[18,21985,21986],{},"template.FontFamily(\"Intr\")"," falls back to the document default. No error, no warning. If text suddenly looks like Helvetica, this is the first place to look.",[49,21989,21990,21996,21997,21999],{},[1629,21991,21992,21993,21995],{},"Not embedding via ",[18,21994,17916],{}," in containers."," A trimmed Docker context drops the ",[18,21998,20754],{},", the runtime fallback kicks in, and you find out from a customer email. Embed.",[49,22001,22002,22005,22006,22008,22009,22012],{},[1629,22003,22004],{},"Using the font's PostScript name as the family."," \"Inter-Regular\" is the PostScript name. Pass that to ",[18,22007,2798],{}," and the bold lookup tries to find \"Inter-Regular-Bold\" — which doesn't exist. Pick a clean family root (",[18,22010,22011],{},"\"Inter\"",") and let the variant suffix handle the styles.",[41,22014,6894],{"id":6893},[46,22016,22017,22022,22028],{},[49,22018,22019,22021],{},[3163,22020,9792],{"href":9791}," — same mechanism, with CJK-specific notes.",[49,22023,22024,22027],{},[3163,22025,22026],{"href":9774},"How do I use bold and italic together?"," — the variant resolver in detail.",[49,22029,22030,22034],{},[3163,22031,22033],{"href":22032},"/blog/tofu-boxes-japanese","Why does my PDF show tofu boxes?"," — what happens when the font doesn't cover the code points.",[41,22036,4794],{"id":4793},[14,22038,20687],{},[109,22040,22041],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,22042,22043],{"__ignoreMap":114},[118,22044,22045,22047,22049],{"class":120,"line":121},[118,22046,113],{"class":128},[118,22048,3156],{"class":433},[118,22050,3159],{"class":433},[14,22052,22053,3169,22056],{},[3163,22054,3168],{"href":3165,"rel":22055},[3167],[3163,22057,3174],{"href":3172,"rel":22058},[3167],[3176,22060,22061],{},"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":114,"searchDepth":132,"depth":132,"links":22063},[22064,22065,22066,22067,22068,22069,22070,22071,22072,22073],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":7068,"depth":132,"text":7069},{"id":21320,"depth":132,"text":21321},{"id":21361,"depth":132,"text":21362},{"id":21708,"depth":132,"text":21709},{"id":21806,"depth":132,"text":21807},{"id":21975,"depth":132,"text":21976},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-04-30","Load TTF bytes, register with gpdf.WithFont, then reference the family name. Works for any TrueType — Inter, Roboto, icon fonts, brand fonts.",{"name":22077,"totalTime":6973,"tools":22078,"steps":22080},"Register a custom TrueType font in a gpdf document",[3202,22079],"Any TrueType .ttf file (Inter, Roboto, JetBrains Mono, an icon font, etc.)",[22081,22084,22087,22090],{"name":22082,"text":22083},"Load the TTF bytes","Read the .ttf file with os.ReadFile into a []byte. For deployable builds, prefer //go:embed so the font travels inside the binary.",{"name":22085,"text":22086},"Register the family at document construction","Pass gpdf.WithFont(\"Inter\", fontBytes) to gpdf.NewDocument. The family name is whatever string you choose — it has no relation to the font's internal name.",{"name":22088,"text":22089},"Set the family as the default, or pass it per call","Add gpdf.WithDefaultFont(\"Inter\", 11) so every c.Text uses it. Otherwise pass template.FontFamily(\"Inter\") on each Text call that needs it.",{"name":22091,"text":22092},"Register Bold, Italic, and BoldItalic variants separately","Each style is its own TTF. Register them as \"Inter-Bold\", \"Inter-Italic\", \"Inter-BoldItalic\". gpdf builds the variant ID from the style flags and looks up that exact string.",{},{"title":9785,"description":22075},"blog/018.add-custom-truetype-font",[6996,3226],"j12k87D6aG8KPMN43KnArhS-YHkbVgYkzJjeBWKn2Xk",{"id":22099,"title":18024,"author":22100,"body":22101,"date":27376,"description":27377,"draft":3196,"extension":3197,"howTo":27378,"image":3220,"meta":27404,"navigation":135,"path":18023,"seo":27405,"stem":27406,"tags":27407,"updated":3220,"__hash__":27408},"blog/blog/016.unidoc-migration.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":22102,"toc":27359},[22103,22105,22124,22139,22150,22153,22157,22160,22163,22183,22186,22190,22193,22317,22324,22328,22331,22434,22546,22563,22570,22572,22583,22858,22888,22892,22895,22900,23237,23241,23621,23630,23634,23651,23655,24240,24243,24247,24613,24625,24634,24638,24641,24645,24904,24910,24914,25353,25367,25370,25374,25388,25392,25671,25674,25678,26315,26322,26326,26332,26336,26790,26794,27080,27091,27095,27102,27181,27184,27187,27191,27194,27232,27238,27240,27246,27258,27277,27283,27300,27306,27308,27311,27323,27331,27333,27356],[41,22104,44],{"id":43},[14,22106,22107,22109,22110,2527,22113,22116,22117,22119,22120,22123],{},[1629,22108,1587],{}," is a pure-Go PDF library under the ",[1629,22111,22112],{},"MIT license",[1629,22114,22115],{},"zero external dependencies"," and no license-key registration step. If you're on ",[18,22118,12369],{}," because nothing else handled CJK or AcroForm flattening, but the AGPL clause has your legal team blocking distribution and the commercial tier is hard to justify, this guide maps the unipdf ",[18,22121,22122],{},"creator"," API to gpdf one piece at a time.",[14,22125,22126,22127,22130,22131,22134,22135,22138],{},"Last quarter a friend at a fintech ran the OSS approval flow on ",[18,22128,22129],{},"github.com/unidoc/unipdf/v3",". The compliance ticket came back the next day with a red X next to ",[1629,22132,22133],{},"AGPL-3.0"," and a note from legal: ",[4744,22136,22137],{},"\"Cannot be linked into closed-source distributable products. Acquire commercial license or remove.\""," The commercial quote arrived with a per-developer annual fee that, for a team of twelve, made everyone reopen the search results.",[14,22140,22141,22142,22145,22146,22149],{},"This is the part of the unipdf story that doesn't show up in the README. unipdf is technically excellent — mature, deeply featured, well-maintained. It's also dual-licensed: ",[1629,22143,22144],{},"AGPL v3"," for open use, ",[1629,22147,22148],{},"paid commercial"," for everything else. AGPL v3 is the strongest copyleft in common use. If you link unipdf into a service that users interact with over a network, AGPL §13 says you have to publish your full corresponding source. Most companies' lawyers say no.",[14,22151,22152],{},"If you're sitting on a unipdf codebase and the license either bit you in audit or is about to renew, this is the migration map. If you're new and reflexively grabbed unipdf because the docs were the most polished, this is the alternative that doesn't ship with a billing relationship.",[41,22154,22156],{"id":22155},"what-agpl-or-paid-actually-means-in-practice","What \"AGPL or paid\" actually means in practice",[14,22158,22159],{},"A lot of Go libraries are casually labeled \"AGPL\" without the team really thinking about what that means. unipdf is not casual about it. The repository's license file is plain AGPL v3, the README is explicit that commercial use requires a key, and the binary itself enforces it — call any unipdf API without registering a license at startup and you get an error or a watermark on every output page.",[14,22161,22162],{},"There are roughly three modes you can be in:",[1624,22164,22165,22171,22177],{},[49,22166,22167,22170],{},[1629,22168,22169],{},"AGPL mode."," You're publishing your code under AGPL v3. Every byte of your service that touches unipdf, plus everything that links to it, has to be available to anyone who interacts with the service over the network. For most internal tooling and SaaS products, this is a non-starter.",[49,22172,22173,22176],{},[1629,22174,22175],{},"Commercial mode."," You pay UniDoc per developer per year. Pricing varies — last public quotes hovered around four figures per seat per year — and includes a metered or license-key registration call that every binary has to make at startup. The key is treated as a secret, which means it lives in your secret manager and gets injected into every container.",[49,22178,22179,22182],{},[1629,22180,22181],{},"Trial / evaluation mode."," Free for a limited time. Outputs include a watermark. Not viable for production.",[14,22184,22185],{},"None of those modes are intrinsically wrong. UniDoc is a real company with real engineers and the price reflects what it costs to build and maintain a comprehensive PDF library. The point is just that the licensing decision sits at every layer: legal review, secret rotation, finance renewal, and the deployment surface (every container needs the key). gpdf removes that whole column from your spreadsheet by being MIT.",[41,22187,22189],{"id":22188},"what-youre-losing-and-what-youre-keeping","What you're losing and what you're keeping",[14,22191,22192],{},"Worth being honest before getting into the API. unipdf does things gpdf does not:",[1516,22194,22195,22207],{},[1519,22196,22197],{},[1522,22198,22199,22202,22205],{},[1525,22200,22201],{},"Capability",[1525,22203,22204],{},"unipdf",[1525,22206,1587],{},[1532,22208,22209,22219,22229,22239,22249,22258,22268,22278,22288,22297,22306],{},[1522,22210,22211,22214,22217],{},[1537,22212,22213],{},"PDF generation",[1537,22215,22216],{},"✅",[1537,22218,22216],{},[1522,22220,22221,22224,22226],{},[1537,22222,22223],{},"TrueType / CJK fonts",[1537,22225,22216],{},[1537,22227,22228],{},"✅ (CGO-free, automatic subsetting)",[1522,22230,22231,22234,22236],{},[1537,22232,22233],{},"AES-128/256 encryption",[1537,22235,22216],{},[1537,22237,22238],{},"✅ (ISO 32000-2 Rev 6, pure Go)",[1522,22240,22241,22244,22246],{},[1537,22242,22243],{},"PKCS#7 / PAdES signing",[1537,22245,22216],{},[1537,22247,22248],{},"✅ (RFC 3161 TSA support)",[1522,22250,22251,22254,22256],{},[1537,22252,22253],{},"PDF/A-1b/2b",[1537,22255,22216],{},[1537,22257,22216],{},[1522,22259,22260,22263,22265],{},[1537,22261,22262],{},"AcroForm — fill existing",[1537,22264,22216],{},[1537,22266,22267],{},"✅ (flatten only — no new field creation yet)",[1522,22269,22270,22273,22275],{},[1537,22271,22272],{},"AcroForm — author new fields",[1537,22274,22216],{},[1537,22276,22277],{},"❌",[1522,22279,22280,22283,22285],{},[1537,22281,22282],{},"PDF parsing / text extraction",[1537,22284,22216],{},[1537,22286,22287],{},"❌ (gpdf is generation-focused)",[1522,22289,22290,22293,22295],{},[1537,22291,22292],{},"OCR",[1537,22294,22216],{},[1537,22296,22277],{},[1522,22298,22299,22302,22304],{},[1537,22300,22301],{},"PDF redaction",[1537,22303,22216],{},[1537,22305,22277],{},[1522,22307,22308,22311,22314],{},[1537,22309,22310],{},"HTML rendering",[1537,22312,22313],{},"partial",[1537,22315,22316],{},"❌ (use a separate renderer, then merge)",[14,22318,22319,22320,22323],{},"If you need PDF parsing, OCR, or redaction, this migration won't carry you all the way. Either keep unipdf in those code paths only (you'll still owe the commercial license for those binaries) or pick a parsing-focused library for the read side. For the ",[1629,22321,22322],{},"generation, encryption, signing, fonts, and CJK"," path — which is what most unipdf bills are actually for — gpdf is a complete swap.",[41,22325,22327],{"id":22326},"removing-the-license-registration-code","Removing the license registration code",[14,22329,22330],{},"This is the smallest diff in the whole migration and the one that makes the rest feel real. unipdf binaries have to register a key at startup. There are a few variants:",[109,22332,22334],{"className":111,"code":22333,"language":113,"meta":114,"style":114},"// API key (metered)\nimport \"github.com/unidoc/unipdf/v3/common/license\"\n\nfunc init() {\n    if err := license.SetMeteredKey(os.Getenv(\"UNIDOC_API_KEY\")); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,22335,22336,22341,22352,22356,22367,22412,22426,22430],{"__ignoreMap":114},[118,22337,22338],{"class":120,"line":121},[118,22339,22340],{"class":3981},"// API key (metered)\n",[118,22342,22343,22345,22347,22350],{"class":120,"line":132},[118,22344,143],{"class":142},[118,22346,1146],{"class":124},[118,22348,22349],{"class":128},"github.com/unidoc/unipdf/v3/common/license",[118,22351,158],{"class":124},[118,22353,22354],{"class":120,"line":139},[118,22355,136],{"emptyLinePlaceholder":135},[118,22357,22358,22360,22363,22365],{"class":120,"line":149},[118,22359,210],{"class":124},[118,22361,22362],{"class":213}," init",[118,22364,217],{"class":124},[118,22366,220],{"class":124},[118,22368,22369,22371,22373,22375,22378,22380,22383,22385,22387,22389,22392,22394,22396,22399,22401,22404,22406,22408,22410],{"class":120,"line":161},[118,22370,1408],{"class":142},[118,22372,1391],{"class":226},[118,22374,230],{"class":124},[118,22376,22377],{"class":226}," license",[118,22379,25],{"class":124},[118,22381,22382],{"class":213},"SetMeteredKey",[118,22384,255],{"class":124},[118,22386,155],{"class":226},[118,22388,25],{"class":124},[118,22390,22391],{"class":213},"Getenv",[118,22393,255],{"class":124},[118,22395,430],{"class":124},[118,22397,22398],{"class":433},"UNIDOC_API_KEY",[118,22400,430],{"class":124},[118,22402,22403],{"class":124},"));",[118,22405,1391],{"class":226},[118,22407,1413],{"class":124},[118,22409,1416],{"class":124},[118,22411,220],{"class":124},[118,22413,22414,22416,22418,22420,22422,22424],{"class":120,"line":166},[118,22415,5818],{"class":226},[118,22417,25],{"class":124},[118,22419,5823],{"class":213},[118,22421,255],{"class":124},[118,22423,1429],{"class":226},[118,22425,199],{"class":124},[118,22427,22428],{"class":120,"line":176},[118,22429,1375],{"class":124},[118,22431,22432],{"class":120,"line":186},[118,22433,1479],{"class":124},[109,22435,22437],{"className":111,"code":22436,"language":113,"meta":114,"style":114},"// Offline license file\nfunc init() {\n    licenseKey, _ := os.ReadFile(\"/etc/unidoc/license.txt\")\n    if err := license.SetLicenseKey(string(licenseKey), \"Acme Corp\"); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,22438,22439,22444,22454,22482,22524,22538,22542],{"__ignoreMap":114},[118,22440,22441],{"class":120,"line":121},[118,22442,22443],{"class":3981},"// Offline license file\n",[118,22445,22446,22448,22450,22452],{"class":120,"line":132},[118,22447,210],{"class":124},[118,22449,22362],{"class":213},[118,22451,217],{"class":124},[118,22453,220],{"class":124},[118,22455,22456,22459,22461,22463,22465,22467,22469,22471,22473,22475,22478,22480],{"class":120,"line":139},[118,22457,22458],{"class":226},"    licenseKey",[118,22460,395],{"class":124},[118,22462,3999],{"class":226},[118,22464,230],{"class":124},[118,22466,1447],{"class":226},[118,22468,25],{"class":124},[118,22470,9545],{"class":213},[118,22472,255],{"class":124},[118,22474,430],{"class":124},[118,22476,22477],{"class":433},"/etc/unidoc/license.txt",[118,22479,430],{"class":124},[118,22481,199],{"class":124},[118,22483,22484,22486,22488,22490,22492,22494,22497,22499,22501,22503,22506,22508,22510,22512,22514,22516,22518,22520,22522],{"class":120,"line":149},[118,22485,1408],{"class":142},[118,22487,1391],{"class":226},[118,22489,230],{"class":124},[118,22491,22377],{"class":226},[118,22493,25],{"class":124},[118,22495,22496],{"class":213},"SetLicenseKey",[118,22498,255],{"class":124},[118,22500,1131],{"class":1130},[118,22502,255],{"class":124},[118,22504,22505],{"class":226},"licenseKey",[118,22507,1280],{"class":124},[118,22509,1146],{"class":124},[118,22511,3878],{"class":433},[118,22513,430],{"class":124},[118,22515,7902],{"class":124},[118,22517,1391],{"class":226},[118,22519,1413],{"class":124},[118,22521,1416],{"class":124},[118,22523,220],{"class":124},[118,22525,22526,22528,22530,22532,22534,22536],{"class":120,"line":161},[118,22527,5818],{"class":226},[118,22529,25],{"class":124},[118,22531,5823],{"class":213},[118,22533,255],{"class":124},[118,22535,1429],{"class":226},[118,22537,199],{"class":124},[118,22539,22540],{"class":120,"line":166},[118,22541,1375],{"class":124},[118,22543,22544],{"class":120,"line":176},[118,22545,1479],{"class":124},[14,22547,22548,22549,22552,22553,22555,22556,22558,22559,22562],{},"In gpdf there is no equivalent. Delete the entire ",[18,22550,22551],{},"init()"," block. Pull ",[18,22554,22398],{}," out of your secret manager, your CI variables, and your container manifests. Remove the license file from your image. The only thing you import is ",[18,22557,3203],{},", and the only thing it requires is that you call ",[18,22560,22561],{},"gpdf.NewDocument"," somewhere.",[14,22564,22565,22566,22569],{},"That's the whole thing. This is also the test for whether your migration has actually landed: ",[18,22567,22568],{},"grep -r unidoc ."," should return zero matches when you're done.",[41,22571,13069],{"id":13068},[14,22573,22574,22575,22578,22579,22582],{},"The table is the cheat sheet. Sections after it walk five concrete pairs. unipdf calls the high-level builder a ",[18,22576,22577],{},"Creator","; gpdf calls it a ",[18,22580,22581],{},"Document",". The shapes are similar enough that most code translates by inspection.",[1516,22584,22585,22598],{},[1519,22586,22587],{},[1522,22588,22589,22591,22596],{},[1525,22590,13081],{},[1525,22592,22593,22594,345],{},"unipdf (",[18,22595,22122],{},[1525,22597,1587],{},[1532,22599,22600,22615,22630,22644,22659,22674,22690,22705,22718,22731,22745,22763,22781,22799,22814,22832,22845],{},[1522,22601,22602,22605,22610],{},[1537,22603,22604],{},"Create a builder",[1537,22606,22607],{},[18,22608,22609],{},"c := creator.New(); c.SetPageSize(creator.PageSizeA4)",[1537,22611,22612],{},[18,22613,22614],{},"doc := gpdf.NewDocument(gpdf.WithPageSize(document.A4))",[1522,22616,22617,22620,22625],{},[1537,22618,22619],{},"Set margins",[1537,22621,22622],{},[18,22623,22624],{},"c.SetPageMargins(L, R, T, B)",[1537,22626,22627],{},[18,22628,22629],{},"gpdf.WithMargins(document.UniformEdges(document.Mm(20)))",[1522,22631,22632,22635,22640],{},[1537,22633,22634],{},"New page",[1537,22636,22637],{},[18,22638,22639],{},"c.NewPage()",[1537,22641,22642],{},[18,22643,13132],{},[1522,22645,22646,22648,22653],{},[1537,22647,13155],{},[1537,22649,22650],{},[18,22651,22652],{},"p := c.NewParagraph(\"hi\"); c.Draw(p)",[1537,22654,22655,4919,22657],{},[18,22656,13165],{},[4744,22658,13168],{},[1522,22660,22661,22663,22668],{},[1537,22662,13173],{},[1537,22664,22665],{},[18,22666,22667],{},"p := c.NewStyledParagraph(); p.SetText(...); c.Draw(p)",[1537,22669,22670,4919,22672],{},[18,22671,13183],{},[4744,22673,13186],{},[1522,22675,22676,22678,22683],{},[1537,22677,13210],{},[1537,22679,22680],{},[18,22681,22682],{},"model.NewCompositePdfFontFromTTFFile(path)",[1537,22684,22685,4919,22688],{},[18,22686,22687],{},"gpdf.WithFont(\"Name\", ttfBytes)",[4744,22689,13223],{},[1522,22691,22692,22695,22700],{},[1537,22693,22694],{},"Set font on text",[1537,22696,22697],{},[18,22698,22699],{},"style.Font = font; style.FontSize = 12",[1537,22701,22702,13239],{},[18,22703,22704],{},"template.FontFamily(\"Name\"), template.FontSize(12)",[1522,22706,22707,22709,22714],{},[1537,22708,13244],{},[1537,22710,22711],{},[18,22712,22713],{},"style.Color = creator.ColorRGBFromHex(\"#1A237E\")",[1537,22715,22716],{},[18,22717,13254],{},[1522,22719,22720,22722,22727],{},[1537,22721,4929],{},[1537,22723,22724],{},[18,22725,22726],{},"t := c.NewTable(4); t.SetColumnWidths(...); c.Draw(t)",[1537,22728,22729],{},[18,22730,13318],{},[1522,22732,22733,22735,22740],{},[1537,22734,1773],{},[1537,22736,22737],{},[18,22738,22739],{},"img, _ := c.NewImageFromFile(path); img.ScaleToWidth(w); c.Draw(img)",[1537,22741,22742],{},[18,22743,22744],{},"c.Image(imgBytes, template.FitWidth(document.Mm(50)))",[1522,22746,22747,22749,22757],{},[1537,22748,13323],{},[1537,22750,22751,1592,22754],{},[18,22752,22753],{},"c.DrawHeader(fn)",[18,22755,22756],{},"c.DrawFooter(fn)",[1537,22758,22759,1592,22761],{},[18,22760,53],{},[18,22762,57],{},[1522,22764,22765,22767,22773],{},[1537,22766,13342],{},[1537,22768,22769,22770,13313],{},"manual count tracked across ",[18,22771,22772],{},"DrawFooter",[1537,22774,22775,1592,22777,4919,22779],{},[18,22776,66],{},[18,22778,70],{},[4744,22780,13358],{},[1522,22782,22783,22785,22795],{},[1537,22784,13363],{},[1537,22786,22787,22790,22791,22794],{},[18,22788,22789],{},"c.SetOptimizer(...)",", then ",[18,22792,22793],{},"model.PdfWriter"," + AES options",[1537,22796,22797],{},[18,22798,13373],{},[1522,22800,22801,22804,22809],{},[1537,22802,22803],{},"Sign",[1537,22805,22806],{},[18,22807,22808],{},"model.NewPdfAppender(...).Sign(...)",[1537,22810,22811],{},[18,22812,22813],{},"gpdf.SignDocument(pdfBytes, signer, opts)",[1522,22815,22816,22819,22827],{},[1537,22817,22818],{},"License registration",[1537,22820,22821,22824,22825],{},[18,22822,22823],{},"license.SetMeteredKey(...)"," in ",[18,22826,22551],{},[1537,22828,22829],{},[4744,22830,22831],{},"(none — delete it)",[1522,22833,22834,22836,22841],{},[1537,22835,13378],{},[1537,22837,22838],{},[18,22839,22840],{},"c.WriteToFile(\"out.pdf\")",[1537,22842,22843],{},[18,22844,13388],{},[1522,22846,22847,22849,22854],{},[1537,22848,13393],{},[1537,22850,22851],{},[18,22852,22853],{},"c.Write(w)",[1537,22855,22856],{},[18,22857,13406],{},[14,22859,22860,22861,22864,22865,22868,22869,22871,22872,22875,22876,22879,22880,22883,22884,22887],{},"Two structural shifts to keep in mind. unipdf's creator is ",[1629,22862,22863],{},"stateful",": you build a ",[18,22866,22867],{},"Paragraph"," or a ",[18,22870,4929],{},", then call ",[18,22873,22874],{},"c.Draw(thing)"," to commit it. gpdf is ",[1629,22877,22878],{},"declarative",": you describe a tree of rows and columns and let the layout engine place things. The second shift is that gpdf has a ",[1629,22881,22882],{},"12-column grid"," the way Bootstrap does. Every row is implicitly 12 units wide; you spend them with ",[18,22885,22886],{},"r.Col(n, fn)",". Most layouts collapse to two or three lines once you stop tracking widths in millimeters.",[41,22889,22891],{"id":22890},"before-after-1-the-smallest-possible-pdf","Before / After 1: the smallest possible PDF",[14,22893,22894],{},"The \"hello world\" pair. unipdf's version isn't long; it just has more setup ceremony because of the license call.",[14,22896,22897],{},[1629,22898,22899],{},"Before — unipdf:",[109,22901,22903],{"className":111,"code":22902,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/unidoc/unipdf/v3/common/license\"\n    \"github.com/unidoc/unipdf/v3/creator\"\n)\n\nfunc init() {\n    if err := license.SetMeteredKey(os.Getenv(\"UNIDOC_API_KEY\")); err != nil {\n        log.Fatal(err)\n    }\n}\n\nfunc main() {\n    c := creator.New()\n    c.SetPageSize(creator.PageSizeA4)\n\n    p := c.NewParagraph(\"Hello, World!\")\n    p.SetFontSize(24)\n    if err := c.Draw(p); err != nil {\n        log.Fatal(err)\n    }\n\n    if err := c.WriteToFile(\"hello.pdf\"); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,22904,22905,22911,22915,22921,22929,22937,22941,22949,22958,22962,22966,22976,23016,23030,23034,23038,23042,23052,23068,23087,23091,23116,23131,23160,23174,23178,23182,23215,23229,23233],{"__ignoreMap":114},[118,22906,22907,22909],{"class":120,"line":121},[118,22908,125],{"class":124},[118,22910,129],{"class":128},[118,22912,22913],{"class":120,"line":132},[118,22914,136],{"emptyLinePlaceholder":135},[118,22916,22917,22919],{"class":120,"line":139},[118,22918,143],{"class":142},[118,22920,146],{"class":124},[118,22922,22923,22925,22927],{"class":120,"line":149},[118,22924,152],{"class":124},[118,22926,5303],{"class":128},[118,22928,158],{"class":124},[118,22930,22931,22933,22935],{"class":120,"line":161},[118,22932,152],{"class":124},[118,22934,155],{"class":128},[118,22936,158],{"class":124},[118,22938,22939],{"class":120,"line":166},[118,22940,136],{"emptyLinePlaceholder":135},[118,22942,22943,22945,22947],{"class":120,"line":176},[118,22944,152],{"class":124},[118,22946,22349],{"class":128},[118,22948,158],{"class":124},[118,22950,22951,22953,22956],{"class":120,"line":186},[118,22952,152],{"class":124},[118,22954,22955],{"class":128},"github.com/unidoc/unipdf/v3/creator",[118,22957,158],{"class":124},[118,22959,22960],{"class":120,"line":196},[118,22961,199],{"class":124},[118,22963,22964],{"class":120,"line":202},[118,22965,136],{"emptyLinePlaceholder":135},[118,22967,22968,22970,22972,22974],{"class":120,"line":207},[118,22969,210],{"class":124},[118,22971,22362],{"class":213},[118,22973,217],{"class":124},[118,22975,220],{"class":124},[118,22977,22978,22980,22982,22984,22986,22988,22990,22992,22994,22996,22998,23000,23002,23004,23006,23008,23010,23012,23014],{"class":120,"line":223},[118,22979,1408],{"class":142},[118,22981,1391],{"class":226},[118,22983,230],{"class":124},[118,22985,22377],{"class":226},[118,22987,25],{"class":124},[118,22989,22382],{"class":213},[118,22991,255],{"class":124},[118,22993,155],{"class":226},[118,22995,25],{"class":124},[118,22997,22391],{"class":213},[118,22999,255],{"class":124},[118,23001,430],{"class":124},[118,23003,22398],{"class":433},[118,23005,430],{"class":124},[118,23007,22403],{"class":124},[118,23009,1391],{"class":226},[118,23011,1413],{"class":124},[118,23013,1416],{"class":124},[118,23015,220],{"class":124},[118,23017,23018,23020,23022,23024,23026,23028],{"class":120,"line":244},[118,23019,5818],{"class":226},[118,23021,25],{"class":124},[118,23023,5823],{"class":213},[118,23025,255],{"class":124},[118,23027,1429],{"class":226},[118,23029,199],{"class":124},[118,23031,23032],{"class":120,"line":269},[118,23033,1375],{"class":124},[118,23035,23036],{"class":120,"line":306},[118,23037,1479],{"class":124},[118,23039,23040],{"class":120,"line":312},[118,23041,136],{"emptyLinePlaceholder":135},[118,23043,23044,23046,23048,23050],{"class":120,"line":317},[118,23045,210],{"class":124},[118,23047,214],{"class":213},[118,23049,217],{"class":124},[118,23051,220],{"class":124},[118,23053,23054,23057,23059,23062,23064,23066],{"class":120,"line":350},[118,23055,23056],{"class":226},"    c ",[118,23058,230],{"class":124},[118,23060,23061],{"class":226}," creator",[118,23063,25],{"class":124},[118,23065,238],{"class":213},[118,23067,1193],{"class":124},[118,23069,23070,23072,23074,23077,23079,23081,23083,23085],{"class":120,"line":379},[118,23071,2022],{"class":226},[118,23073,25],{"class":124},[118,23075,23076],{"class":213},"SetPageSize",[118,23078,255],{"class":124},[118,23080,22122],{"class":226},[118,23082,25],{"class":124},[118,23084,13555],{"class":226},[118,23086,199],{"class":124},[118,23088,23089],{"class":120,"line":417},[118,23090,136],{"emptyLinePlaceholder":135},[118,23092,23093,23096,23098,23101,23103,23106,23108,23110,23112,23114],{"class":120,"line":466},[118,23094,23095],{"class":226},"    p ",[118,23097,230],{"class":124},[118,23099,23100],{"class":226}," c",[118,23102,25],{"class":124},[118,23104,23105],{"class":213},"NewParagraph",[118,23107,255],{"class":124},[118,23109,430],{"class":124},[118,23111,13749],{"class":433},[118,23113,430],{"class":124},[118,23115,199],{"class":124},[118,23117,23118,23120,23122,23125,23127,23129],{"class":120,"line":472},[118,23119,1712],{"class":226},[118,23121,25],{"class":124},[118,23123,23124],{"class":213},"SetFontSize",[118,23126,255],{"class":124},[118,23128,3777],{"class":299},[118,23130,199],{"class":124},[118,23132,23133,23135,23137,23139,23141,23143,23146,23148,23150,23152,23154,23156,23158],{"class":120,"line":503},[118,23134,1408],{"class":142},[118,23136,1391],{"class":226},[118,23138,230],{"class":124},[118,23140,23100],{"class":226},[118,23142,25],{"class":124},[118,23144,23145],{"class":213},"Draw",[118,23147,255],{"class":124},[118,23149,14],{"class":226},[118,23151,7902],{"class":124},[118,23153,1391],{"class":226},[118,23155,1413],{"class":124},[118,23157,1416],{"class":124},[118,23159,220],{"class":124},[118,23161,23162,23164,23166,23168,23170,23172],{"class":120,"line":537},[118,23163,5818],{"class":226},[118,23165,25],{"class":124},[118,23167,5823],{"class":213},[118,23169,255],{"class":124},[118,23171,1429],{"class":226},[118,23173,199],{"class":124},[118,23175,23176],{"class":120,"line":566},[118,23177,1375],{"class":124},[118,23179,23180],{"class":120,"line":571},[118,23181,136],{"emptyLinePlaceholder":135},[118,23183,23184,23186,23188,23190,23192,23194,23197,23199,23201,23203,23205,23207,23209,23211,23213],{"class":120,"line":577},[118,23185,1408],{"class":142},[118,23187,1391],{"class":226},[118,23189,230],{"class":124},[118,23191,23100],{"class":226},[118,23193,25],{"class":124},[118,23195,23196],{"class":213},"WriteToFile",[118,23198,255],{"class":124},[118,23200,430],{"class":124},[118,23202,13805],{"class":433},[118,23204,430],{"class":124},[118,23206,7902],{"class":124},[118,23208,1391],{"class":226},[118,23210,1413],{"class":124},[118,23212,1416],{"class":124},[118,23214,220],{"class":124},[118,23216,23217,23219,23221,23223,23225,23227],{"class":120,"line":602},[118,23218,5818],{"class":226},[118,23220,25],{"class":124},[118,23222,5823],{"class":213},[118,23224,255],{"class":124},[118,23226,1429],{"class":226},[118,23228,199],{"class":124},[118,23230,23231],{"class":120,"line":633},[118,23232,1375],{"class":124},[118,23234,23235],{"class":120,"line":668},[118,23236,1479],{"class":124},[14,23238,23239],{},[1629,23240,13844],{},[109,23242,23243],{"className":111,"code":13847,"language":113,"meta":114,"style":114},[18,23244,23245,23251,23255,23261,23269,23277,23281,23289,23297,23305,23309,23313,23323,23337,23355,23385,23389,23393,23407,23431,23461,23499,23503,23507,23511,23529,23541,23555,23559,23599,23613,23617],{"__ignoreMap":114},[118,23246,23247,23249],{"class":120,"line":121},[118,23248,125],{"class":124},[118,23250,129],{"class":128},[118,23252,23253],{"class":120,"line":132},[118,23254,136],{"emptyLinePlaceholder":135},[118,23256,23257,23259],{"class":120,"line":139},[118,23258,143],{"class":142},[118,23260,146],{"class":124},[118,23262,23263,23265,23267],{"class":120,"line":149},[118,23264,152],{"class":124},[118,23266,5303],{"class":128},[118,23268,158],{"class":124},[118,23270,23271,23273,23275],{"class":120,"line":161},[118,23272,152],{"class":124},[118,23274,155],{"class":128},[118,23276,158],{"class":124},[118,23278,23279],{"class":120,"line":166},[118,23280,136],{"emptyLinePlaceholder":135},[118,23282,23283,23285,23287],{"class":120,"line":176},[118,23284,152],{"class":124},[118,23286,3203],{"class":128},[118,23288,158],{"class":124},[118,23290,23291,23293,23295],{"class":120,"line":186},[118,23292,152],{"class":124},[118,23294,171],{"class":128},[118,23296,158],{"class":124},[118,23298,23299,23301,23303],{"class":120,"line":196},[118,23300,152],{"class":124},[118,23302,191],{"class":128},[118,23304,158],{"class":124},[118,23306,23307],{"class":120,"line":202},[118,23308,199],{"class":124},[118,23310,23311],{"class":120,"line":207},[118,23312,136],{"emptyLinePlaceholder":135},[118,23314,23315,23317,23319,23321],{"class":120,"line":223},[118,23316,210],{"class":124},[118,23318,214],{"class":213},[118,23320,217],{"class":124},[118,23322,220],{"class":124},[118,23324,23325,23327,23329,23331,23333,23335],{"class":120,"line":244},[118,23326,227],{"class":226},[118,23328,230],{"class":124},[118,23330,3595],{"class":226},[118,23332,25],{"class":124},[118,23334,3600],{"class":213},[118,23336,241],{"class":124},[118,23338,23339,23341,23343,23345,23347,23349,23351,23353],{"class":120,"line":269},[118,23340,3607],{"class":226},[118,23342,25],{"class":124},[118,23344,252],{"class":213},[118,23346,255],{"class":124},[118,23348,258],{"class":226},[118,23350,25],{"class":124},[118,23352,263],{"class":226},[118,23354,266],{"class":124},[118,23356,23357,23359,23361,23363,23365,23367,23369,23371,23373,23375,23377,23379,23381,23383],{"class":120,"line":306},[118,23358,3607],{"class":226},[118,23360,25],{"class":124},[118,23362,276],{"class":213},[118,23364,255],{"class":124},[118,23366,258],{"class":226},[118,23368,25],{"class":124},[118,23370,285],{"class":213},[118,23372,255],{"class":124},[118,23374,258],{"class":226},[118,23376,25],{"class":124},[118,23378,294],{"class":213},[118,23380,255],{"class":124},[118,23382,300],{"class":299},[118,23384,303],{"class":124},[118,23386,23387],{"class":120,"line":312},[118,23388,309],{"class":124},[118,23390,23391],{"class":120,"line":317},[118,23392,136],{"emptyLinePlaceholder":135},[118,23394,23395,23397,23399,23401,23403,23405],{"class":120,"line":350},[118,23396,5431],{"class":226},[118,23398,230],{"class":124},[118,23400,1185],{"class":226},[118,23402,25],{"class":124},[118,23404,1190],{"class":213},[118,23406,1193],{"class":124},[118,23408,23409,23411,23413,23415,23417,23419,23421,23423,23425,23427,23429],{"class":120,"line":379},[118,23410,5494],{"class":226},[118,23412,25],{"class":124},[118,23414,358],{"class":213},[118,23416,328],{"class":124},[118,23418,363],{"class":331},[118,23420,334],{"class":124},[118,23422,337],{"class":128},[118,23424,25],{"class":124},[118,23426,372],{"class":128},[118,23428,345],{"class":124},[118,23430,220],{"class":124},[118,23432,23433,23435,23437,23439,23441,23443,23445,23447,23449,23451,23453,23455,23457,23459],{"class":120,"line":417},[118,23434,1737],{"class":226},[118,23436,25],{"class":124},[118,23438,387],{"class":213},[118,23440,255],{"class":124},[118,23442,20],{"class":299},[118,23444,395],{"class":124},[118,23446,398],{"class":124},[118,23448,401],{"class":331},[118,23450,334],{"class":124},[118,23452,337],{"class":128},[118,23454,25],{"class":124},[118,23456,410],{"class":128},[118,23458,345],{"class":124},[118,23460,220],{"class":124},[118,23462,23463,23465,23467,23469,23471,23473,23475,23477,23479,23481,23483,23485,23487,23489,23491,23493,23495,23497],{"class":120,"line":466},[118,23464,1768],{"class":226},[118,23466,25],{"class":124},[118,23468,425],{"class":213},[118,23470,255],{"class":124},[118,23472,430],{"class":124},[118,23474,13749],{"class":433},[118,23476,430],{"class":124},[118,23478,395],{"class":124},[118,23480,233],{"class":226},[118,23482,25],{"class":124},[118,23484,455],{"class":213},[118,23486,255],{"class":124},[118,23488,3777],{"class":299},[118,23490,1280],{"class":124},[118,23492,233],{"class":226},[118,23494,25],{"class":124},[118,23496,445],{"class":213},[118,23498,1289],{"class":124},[118,23500,23501],{"class":120,"line":472},[118,23502,574],{"class":124},[118,23504,23505],{"class":120,"line":503},[118,23506,706],{"class":124},[118,23508,23509],{"class":120,"line":537},[118,23510,136],{"emptyLinePlaceholder":135},[118,23512,23513,23515,23517,23519,23521,23523,23525,23527],{"class":120,"line":566},[118,23514,5787],{"class":226},[118,23516,395],{"class":124},[118,23518,1391],{"class":226},[118,23520,230],{"class":124},[118,23522,1185],{"class":226},[118,23524,25],{"class":124},[118,23526,1400],{"class":213},[118,23528,1193],{"class":124},[118,23530,23531,23533,23535,23537,23539],{"class":120,"line":571},[118,23532,1408],{"class":142},[118,23534,1391],{"class":226},[118,23536,1413],{"class":124},[118,23538,1416],{"class":124},[118,23540,220],{"class":124},[118,23542,23543,23545,23547,23549,23551,23553],{"class":120,"line":577},[118,23544,5818],{"class":226},[118,23546,25],{"class":124},[118,23548,5823],{"class":213},[118,23550,255],{"class":124},[118,23552,1429],{"class":226},[118,23554,199],{"class":124},[118,23556,23557],{"class":120,"line":602},[118,23558,1375],{"class":124},[118,23560,23561,23563,23565,23567,23569,23571,23573,23575,23577,23579,23581,23583,23585,23587,23589,23591,23593,23595,23597],{"class":120,"line":633},[118,23562,1408],{"class":142},[118,23564,1391],{"class":226},[118,23566,230],{"class":124},[118,23568,1447],{"class":226},[118,23570,25],{"class":124},[118,23572,1452],{"class":213},[118,23574,255],{"class":124},[118,23576,430],{"class":124},[118,23578,13805],{"class":433},[118,23580,430],{"class":124},[118,23582,395],{"class":124},[118,23584,5859],{"class":226},[118,23586,395],{"class":124},[118,23588,1471],{"class":299},[118,23590,7902],{"class":124},[118,23592,1391],{"class":226},[118,23594,1413],{"class":124},[118,23596,1416],{"class":124},[118,23598,220],{"class":124},[118,23600,23601,23603,23605,23607,23609,23611],{"class":120,"line":668},[118,23602,5818],{"class":226},[118,23604,25],{"class":124},[118,23606,5823],{"class":213},[118,23608,255],{"class":124},[118,23610,1429],{"class":226},[118,23612,199],{"class":124},[118,23614,23615],{"class":120,"line":693},[118,23616,1375],{"class":124},[118,23618,23619],{"class":120,"line":698},[118,23620,1479],{"class":124},[14,23622,23623,23624,23626,23627,23629],{},"Three things to notice. The ",[18,23625,22551],{}," block is gone — no key, no env var. Construction takes options instead of mutating the builder. The text lives inside a row and column instead of being a free ",[18,23628,22867],{}," you draw later. The grid is doing the placement; you don't pick coordinates.",[41,23631,23633],{"id":23632},"before-after-2-a-styled-invoice-line-items-table","Before / After 2: a styled invoice line-items table",[14,23635,23636,23637,23639,23640,23643,23644,1592,23647,23650],{},"Tables are where the unipdf creator API gets long. You construct a ",[18,23638,4929],{},", call ",[18,23641,23642],{},"SetColumnWidths"," with absolute fractions, build cells one by one with ",[18,23645,23646],{},"NewCell",[18,23648,23649],{},"SetContent",", and configure each cell's borders and alignment by hand.",[14,23652,23653],{},[1629,23654,22899],{},[109,23656,23658],{"className":111,"code":23657,"language":113,"meta":114,"style":114},"table := c.NewTable(4)\ntable.SetColumnWidths(0.5, 0.15, 0.15, 0.2)\n\nheaderStyle := c.NewTextStyle()\nheaderStyle.Font, _ = model.NewStandard14Font(\"Helvetica-Bold\")\nheaderStyle.FontSize = 11\nheaderStyle.Color = creator.ColorWhite\n\ndrawHeaderCell := func(text string) {\n    cell := table.NewCell()\n    cell.SetBackgroundColor(creator.ColorRGBFromHex(\"#1A237E\"))\n    cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 0.5)\n\n    p := c.NewStyledParagraph()\n    chunk := p.Append(text)\n    chunk.Style = headerStyle\n    cell.SetContent(p)\n}\n\nfor _, h := range []string{\"Description\", \"Qty\", \"Unit\", \"Amount\"} {\n    drawHeaderCell(h)\n}\n\nfor _, row := range items {\n    for _, cellText := range row {\n        cell := table.NewCell()\n        cell.SetBorder(creator.CellBorderSideAll, creator.CellBorderStyleSingle, 0.3)\n\n        p := c.NewParagraph(cellText)\n        p.SetFontSize(11)\n        cell.SetContent(p)\n    }\n}\n\nif err := c.Draw(table); err != nil {\n    log.Fatal(err)\n}\n",[18,23659,23660,23680,23708,23712,23728,23763,23777,23795,23799,23816,23832,23862,23896,23900,23915,23936,23951,23965,23969,23973,24027,24038,24042,24046,24064,24083,24098,24130,24134,24154,24168,24182,24186,24190,24194,24222,24236],{"__ignoreMap":114},[118,23661,23662,23665,23667,23669,23671,23674,23676,23678],{"class":120,"line":121},[118,23663,23664],{"class":226},"table ",[118,23666,230],{"class":124},[118,23668,23100],{"class":226},[118,23670,25],{"class":124},[118,23672,23673],{"class":213},"NewTable",[118,23675,255],{"class":124},[118,23677,1493],{"class":299},[118,23679,199],{"class":124},[118,23681,23682,23684,23686,23688,23690,23692,23694,23697,23699,23701,23703,23706],{"class":120,"line":132},[118,23683,1516],{"class":226},[118,23685,25],{"class":124},[118,23687,23642],{"class":213},[118,23689,255],{"class":124},[118,23691,560],{"class":299},[118,23693,395],{"class":124},[118,23695,23696],{"class":299}," 0.15",[118,23698,395],{"class":124},[118,23700,23696],{"class":299},[118,23702,395],{"class":124},[118,23704,23705],{"class":299}," 0.2",[118,23707,199],{"class":124},[118,23709,23710],{"class":120,"line":139},[118,23711,136],{"emptyLinePlaceholder":135},[118,23713,23714,23717,23719,23721,23723,23726],{"class":120,"line":149},[118,23715,23716],{"class":226},"headerStyle ",[118,23718,230],{"class":124},[118,23720,23100],{"class":226},[118,23722,25],{"class":124},[118,23724,23725],{"class":213},"NewTextStyle",[118,23727,1193],{"class":124},[118,23729,23730,23733,23735,23738,23740,23742,23744,23747,23749,23752,23754,23756,23759,23761],{"class":120,"line":161},[118,23731,23732],{"class":226},"headerStyle",[118,23734,25],{"class":124},[118,23736,23737],{"class":226},"Font",[118,23739,395],{"class":124},[118,23741,3999],{"class":226},[118,23743,1366],{"class":124},[118,23745,23746],{"class":226}," model",[118,23748,25],{"class":124},[118,23750,23751],{"class":213},"NewStandard14Font",[118,23753,255],{"class":124},[118,23755,430],{"class":124},[118,23757,23758],{"class":433},"Helvetica-Bold",[118,23760,430],{"class":124},[118,23762,199],{"class":124},[118,23764,23765,23767,23769,23772,23774],{"class":120,"line":166},[118,23766,23732],{"class":226},[118,23768,25],{"class":124},[118,23770,23771],{"class":226},"FontSize ",[118,23773,1366],{"class":124},[118,23775,23776],{"class":299}," 11\n",[118,23778,23779,23781,23783,23786,23788,23790,23792],{"class":120,"line":176},[118,23780,23732],{"class":226},[118,23782,25],{"class":124},[118,23784,23785],{"class":226},"Color ",[118,23787,1366],{"class":124},[118,23789,23061],{"class":226},[118,23791,25],{"class":124},[118,23793,23794],{"class":226},"ColorWhite\n",[118,23796,23797],{"class":120,"line":186},[118,23798,136],{"emptyLinePlaceholder":135},[118,23800,23801,23804,23806,23808,23810,23812,23814],{"class":120,"line":196},[118,23802,23803],{"class":226},"drawHeaderCell ",[118,23805,230],{"class":124},[118,23807,398],{"class":124},[118,23809,4993],{"class":331},[118,23811,4996],{"class":1130},[118,23813,345],{"class":124},[118,23815,220],{"class":124},[118,23817,23818,23821,23823,23826,23828,23830],{"class":120,"line":202},[118,23819,23820],{"class":226},"    cell ",[118,23822,230],{"class":124},[118,23824,23825],{"class":226}," table",[118,23827,25],{"class":124},[118,23829,23646],{"class":213},[118,23831,1193],{"class":124},[118,23833,23834,23837,23839,23842,23844,23846,23848,23851,23853,23855,23858,23860],{"class":120,"line":207},[118,23835,23836],{"class":226},"    cell",[118,23838,25],{"class":124},[118,23840,23841],{"class":213},"SetBackgroundColor",[118,23843,255],{"class":124},[118,23845,22122],{"class":226},[118,23847,25],{"class":124},[118,23849,23850],{"class":213},"ColorRGBFromHex",[118,23852,255],{"class":124},[118,23854,430],{"class":124},[118,23856,23857],{"class":433},"#1A237E",[118,23859,430],{"class":124},[118,23861,463],{"class":124},[118,23863,23864,23866,23868,23871,23873,23875,23877,23880,23882,23884,23886,23889,23891,23894],{"class":120,"line":223},[118,23865,23836],{"class":226},[118,23867,25],{"class":124},[118,23869,23870],{"class":213},"SetBorder",[118,23872,255],{"class":124},[118,23874,22122],{"class":226},[118,23876,25],{"class":124},[118,23878,23879],{"class":226},"CellBorderSideAll",[118,23881,395],{"class":124},[118,23883,23061],{"class":226},[118,23885,25],{"class":124},[118,23887,23888],{"class":226},"CellBorderStyleSingle",[118,23890,395],{"class":124},[118,23892,23893],{"class":299}," 0.5",[118,23895,199],{"class":124},[118,23897,23898],{"class":120,"line":244},[118,23899,136],{"emptyLinePlaceholder":135},[118,23901,23902,23904,23906,23908,23910,23913],{"class":120,"line":269},[118,23903,23095],{"class":226},[118,23905,230],{"class":124},[118,23907,23100],{"class":226},[118,23909,25],{"class":124},[118,23911,23912],{"class":213},"NewStyledParagraph",[118,23914,1193],{"class":124},[118,23916,23917,23920,23922,23925,23927,23930,23932,23934],{"class":120,"line":306},[118,23918,23919],{"class":226},"    chunk ",[118,23921,230],{"class":124},[118,23923,23924],{"class":226}," p",[118,23926,25],{"class":124},[118,23928,23929],{"class":213},"Append",[118,23931,255],{"class":124},[118,23933,4993],{"class":226},[118,23935,199],{"class":124},[118,23937,23938,23941,23943,23946,23948],{"class":120,"line":312},[118,23939,23940],{"class":226},"    chunk",[118,23942,25],{"class":124},[118,23944,23945],{"class":226},"Style ",[118,23947,1366],{"class":124},[118,23949,23950],{"class":226}," headerStyle\n",[118,23952,23953,23955,23957,23959,23961,23963],{"class":120,"line":317},[118,23954,23836],{"class":226},[118,23956,25],{"class":124},[118,23958,23649],{"class":213},[118,23960,255],{"class":124},[118,23962,14],{"class":226},[118,23964,199],{"class":124},[118,23966,23967],{"class":120,"line":350},[118,23968,1479],{"class":124},[118,23970,23971],{"class":120,"line":379},[118,23972,136],{"emptyLinePlaceholder":135},[118,23974,23975,23977,23979,23981,23983,23985,23987,23989,23991,23993,23995,23997,23999,24001,24003,24005,24007,24009,24011,24013,24015,24017,24019,24021,24023,24025],{"class":120,"line":417},[118,23976,14495],{"class":142},[118,23978,14776],{"class":226},[118,23980,395],{"class":124},[118,23982,14502],{"class":226},[118,23984,230],{"class":124},[118,23986,1124],{"class":142},[118,23988,1127],{"class":124},[118,23990,1131],{"class":1130},[118,23992,1134],{"class":124},[118,23994,430],{"class":124},[118,23996,14460],{"class":433},[118,23998,430],{"class":124},[118,24000,395],{"class":124},[118,24002,1146],{"class":124},[118,24004,14469],{"class":433},[118,24006,430],{"class":124},[118,24008,395],{"class":124},[118,24010,1146],{"class":124},[118,24012,14478],{"class":433},[118,24014,430],{"class":124},[118,24016,395],{"class":124},[118,24018,1146],{"class":124},[118,24020,7320],{"class":433},[118,24022,430],{"class":124},[118,24024,1172],{"class":124},[118,24026,220],{"class":124},[118,24028,24029,24032,24034,24036],{"class":120,"line":466},[118,24030,24031],{"class":213},"    drawHeaderCell",[118,24033,255],{"class":124},[118,24035,14902],{"class":226},[118,24037,199],{"class":124},[118,24039,24040],{"class":120,"line":472},[118,24041,1479],{"class":124},[118,24043,24044],{"class":120,"line":503},[118,24045,136],{"emptyLinePlaceholder":135},[118,24047,24048,24050,24052,24054,24056,24058,24060,24062],{"class":120,"line":537},[118,24049,14495],{"class":142},[118,24051,14776],{"class":226},[118,24053,395],{"class":124},[118,24055,15544],{"class":226},[118,24057,230],{"class":124},[118,24059,1124],{"class":142},[118,24061,15551],{"class":226},[118,24063,7406],{"class":124},[118,24065,24066,24068,24070,24072,24075,24077,24079,24081],{"class":120,"line":566},[118,24067,1111],{"class":142},[118,24069,14776],{"class":226},[118,24071,395],{"class":124},[118,24073,24074],{"class":226}," cellText ",[118,24076,230],{"class":124},[118,24078,1124],{"class":142},[118,24080,15544],{"class":226},[118,24082,7406],{"class":124},[118,24084,24085,24088,24090,24092,24094,24096],{"class":120,"line":571},[118,24086,24087],{"class":226},"        cell ",[118,24089,230],{"class":124},[118,24091,23825],{"class":226},[118,24093,25],{"class":124},[118,24095,23646],{"class":213},[118,24097,1193],{"class":124},[118,24099,24100,24103,24105,24107,24109,24111,24113,24115,24117,24119,24121,24123,24125,24128],{"class":120,"line":577},[118,24101,24102],{"class":226},"        cell",[118,24104,25],{"class":124},[118,24106,23870],{"class":213},[118,24108,255],{"class":124},[118,24110,22122],{"class":226},[118,24112,25],{"class":124},[118,24114,23879],{"class":226},[118,24116,395],{"class":124},[118,24118,23061],{"class":226},[118,24120,25],{"class":124},[118,24122,23888],{"class":226},[118,24124,395],{"class":124},[118,24126,24127],{"class":299}," 0.3",[118,24129,199],{"class":124},[118,24131,24132],{"class":120,"line":602},[118,24133,136],{"emptyLinePlaceholder":135},[118,24135,24136,24139,24141,24143,24145,24147,24149,24152],{"class":120,"line":633},[118,24137,24138],{"class":226},"        p ",[118,24140,230],{"class":124},[118,24142,23100],{"class":226},[118,24144,25],{"class":124},[118,24146,23105],{"class":213},[118,24148,255],{"class":124},[118,24150,24151],{"class":226},"cellText",[118,24153,199],{"class":124},[118,24155,24156,24158,24160,24162,24164,24166],{"class":120,"line":668},[118,24157,353],{"class":226},[118,24159,25],{"class":124},[118,24161,23124],{"class":213},[118,24163,255],{"class":124},[118,24165,3893],{"class":299},[118,24167,199],{"class":124},[118,24169,24170,24172,24174,24176,24178,24180],{"class":120,"line":693},[118,24171,24102],{"class":226},[118,24173,25],{"class":124},[118,24175,23649],{"class":213},[118,24177,255],{"class":124},[118,24179,14],{"class":226},[118,24181,199],{"class":124},[118,24183,24184],{"class":120,"line":698},[118,24185,1375],{"class":124},[118,24187,24188],{"class":120,"line":703},[118,24189,1479],{"class":124},[118,24191,24192],{"class":120,"line":709},[118,24193,136],{"emptyLinePlaceholder":135},[118,24195,24196,24198,24200,24202,24204,24206,24208,24210,24212,24214,24216,24218,24220],{"class":120,"line":714},[118,24197,16122],{"class":142},[118,24199,1391],{"class":226},[118,24201,230],{"class":124},[118,24203,23100],{"class":226},[118,24205,25],{"class":124},[118,24207,23145],{"class":213},[118,24209,255],{"class":124},[118,24211,1516],{"class":226},[118,24213,7902],{"class":124},[118,24215,1391],{"class":226},[118,24217,1413],{"class":124},[118,24219,1416],{"class":124},[118,24221,220],{"class":124},[118,24223,24224,24226,24228,24230,24232,24234],{"class":120,"line":740},[118,24225,16196],{"class":226},[118,24227,25],{"class":124},[118,24229,5823],{"class":213},[118,24231,255],{"class":124},[118,24233,1429],{"class":226},[118,24235,199],{"class":124},[118,24237,24238],{"class":120,"line":765},[118,24239,1479],{"class":124},[14,24241,24242],{},"The borders, the per-cell content, the loop that draws the header — all of it is mechanical.",[14,24244,24245],{},[1629,24246,13844],{},[109,24248,24250],{"className":111,"code":24249,"language":113,"meta":114,"style":114},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Table(\n            []string{\"Description\", \"Qty\", \"Unit\", \"Amount\"},\n            [][]string{\n                {\"Frontend dev\", \"40 hrs\", \"$150.00\", \"$6,000.00\"},\n                {\"Backend dev\",  \"60 hrs\", \"$150.00\", \"$9,000.00\"},\n                {\"UI design\",    \"20 hrs\", \"$120.00\", \"$2,400.00\"},\n            },\n            template.ColumnWidths(50, 15, 15, 20),\n            template.TableHeaderStyle(\n                template.Bold(),\n                template.TextColor(pdf.White),\n                template.BgColor(pdf.RGBHex(0x1A237E)),\n            ),\n            template.TableStripe(pdf.RGBHex(0xF5F5F5)),\n        )\n    })\n})\n",[18,24251,24252,24276,24306,24316,24356,24365,24406,24445,24485,24489,24515,24525,24535,24553,24575,24579,24601,24605,24609],{"__ignoreMap":114},[118,24253,24254,24256,24258,24260,24262,24264,24266,24268,24270,24272,24274],{"class":120,"line":121},[118,24255,5910],{"class":226},[118,24257,25],{"class":124},[118,24259,358],{"class":213},[118,24261,328],{"class":124},[118,24263,363],{"class":331},[118,24265,334],{"class":124},[118,24267,337],{"class":128},[118,24269,25],{"class":124},[118,24271,372],{"class":128},[118,24273,345],{"class":124},[118,24275,220],{"class":124},[118,24277,24278,24280,24282,24284,24286,24288,24290,24292,24294,24296,24298,24300,24302,24304],{"class":120,"line":132},[118,24279,5935],{"class":226},[118,24281,25],{"class":124},[118,24283,387],{"class":213},[118,24285,255],{"class":124},[118,24287,20],{"class":299},[118,24289,395],{"class":124},[118,24291,398],{"class":124},[118,24293,401],{"class":331},[118,24295,334],{"class":124},[118,24297,337],{"class":128},[118,24299,25],{"class":124},[118,24301,410],{"class":128},[118,24303,345],{"class":124},[118,24305,220],{"class":124},[118,24307,24308,24310,24312,24314],{"class":120,"line":139},[118,24309,5966],{"class":226},[118,24311,25],{"class":124},[118,24313,4929],{"class":213},[118,24315,241],{"class":124},[118,24317,24318,24320,24322,24324,24326,24328,24330,24332,24334,24336,24338,24340,24342,24344,24346,24348,24350,24352,24354],{"class":120,"line":149},[118,24319,15869],{"class":124},[118,24321,1131],{"class":1130},[118,24323,1134],{"class":124},[118,24325,430],{"class":124},[118,24327,14460],{"class":433},[118,24329,430],{"class":124},[118,24331,395],{"class":124},[118,24333,1146],{"class":124},[118,24335,14469],{"class":433},[118,24337,430],{"class":124},[118,24339,395],{"class":124},[118,24341,1146],{"class":124},[118,24343,14478],{"class":433},[118,24345,430],{"class":124},[118,24347,395],{"class":124},[118,24349,1146],{"class":124},[118,24351,7320],{"class":433},[118,24353,430],{"class":124},[118,24355,8191],{"class":124},[118,24357,24358,24361,24363],{"class":120,"line":161},[118,24359,24360],{"class":124},"            [][]",[118,24362,1131],{"class":1130},[118,24364,7406],{"class":124},[118,24366,24367,24370,24372,24375,24377,24379,24381,24384,24386,24388,24390,24393,24395,24397,24399,24402,24404],{"class":120,"line":166},[118,24368,24369],{"class":124},"                {",[118,24371,430],{"class":124},[118,24373,24374],{"class":433},"Frontend dev",[118,24376,430],{"class":124},[118,24378,395],{"class":124},[118,24380,1146],{"class":124},[118,24382,24383],{"class":433},"40 hrs",[118,24385,430],{"class":124},[118,24387,395],{"class":124},[118,24389,1146],{"class":124},[118,24391,24392],{"class":433},"$150.00",[118,24394,430],{"class":124},[118,24396,395],{"class":124},[118,24398,1146],{"class":124},[118,24400,24401],{"class":433},"$6,000.00",[118,24403,430],{"class":124},[118,24405,8191],{"class":124},[118,24407,24408,24410,24412,24415,24417,24419,24421,24424,24426,24428,24430,24432,24434,24436,24438,24441,24443],{"class":120,"line":176},[118,24409,24369],{"class":124},[118,24411,430],{"class":124},[118,24413,24414],{"class":433},"Backend dev",[118,24416,430],{"class":124},[118,24418,395],{"class":124},[118,24420,19796],{"class":124},[118,24422,24423],{"class":433},"60 hrs",[118,24425,430],{"class":124},[118,24427,395],{"class":124},[118,24429,1146],{"class":124},[118,24431,24392],{"class":433},[118,24433,430],{"class":124},[118,24435,395],{"class":124},[118,24437,1146],{"class":124},[118,24439,24440],{"class":433},"$9,000.00",[118,24442,430],{"class":124},[118,24444,8191],{"class":124},[118,24446,24447,24449,24451,24454,24456,24458,24460,24463,24465,24467,24469,24472,24474,24476,24478,24481,24483],{"class":120,"line":186},[118,24448,24369],{"class":124},[118,24450,430],{"class":124},[118,24452,24453],{"class":433},"UI design",[118,24455,430],{"class":124},[118,24457,395],{"class":124},[118,24459,152],{"class":124},[118,24461,24462],{"class":433},"20 hrs",[118,24464,430],{"class":124},[118,24466,395],{"class":124},[118,24468,1146],{"class":124},[118,24470,24471],{"class":433},"$120.00",[118,24473,430],{"class":124},[118,24475,395],{"class":124},[118,24477,1146],{"class":124},[118,24479,24480],{"class":433},"$2,400.00",[118,24482,430],{"class":124},[118,24484,8191],{"class":124},[118,24486,24487],{"class":120,"line":196},[118,24488,11600],{"class":124},[118,24490,24491,24493,24495,24497,24499,24501,24503,24505,24507,24509,24511,24513],{"class":120,"line":202},[118,24492,6061],{"class":226},[118,24494,25],{"class":124},[118,24496,7733],{"class":213},[118,24498,255],{"class":124},[118,24500,1510],{"class":299},[118,24502,395],{"class":124},[118,24504,9915],{"class":299},[118,24506,395],{"class":124},[118,24508,9915],{"class":299},[118,24510,395],{"class":124},[118,24512,7742],{"class":299},[118,24514,266],{"class":124},[118,24516,24517,24519,24521,24523],{"class":120,"line":207},[118,24518,6061],{"class":226},[118,24520,25],{"class":124},[118,24522,7762],{"class":213},[118,24524,241],{"class":124},[118,24526,24527,24529,24531,24533],{"class":120,"line":223},[118,24528,2648],{"class":226},[118,24530,25],{"class":124},[118,24532,445],{"class":213},[118,24534,2655],{"class":124},[118,24536,24537,24539,24541,24543,24545,24547,24549,24551],{"class":120,"line":244},[118,24538,2648],{"class":226},[118,24540,25],{"class":124},[118,24542,545],{"class":213},[118,24544,255],{"class":124},[118,24546,550],{"class":226},[118,24548,25],{"class":124},[118,24550,7781],{"class":226},[118,24552,266],{"class":124},[118,24554,24555,24557,24559,24561,24563,24565,24567,24569,24571,24573],{"class":120,"line":269},[118,24556,2648],{"class":226},[118,24558,25],{"class":124},[118,24560,7792],{"class":213},[118,24562,255],{"class":124},[118,24564,550],{"class":226},[118,24566,25],{"class":124},[118,24568,658],{"class":213},[118,24570,255],{"class":124},[118,24572,7269],{"class":299},[118,24574,3646],{"class":124},[118,24576,24577],{"class":120,"line":306},[118,24578,16007],{"class":124},[118,24580,24581,24583,24585,24587,24589,24591,24593,24595,24597,24599],{"class":120,"line":312},[118,24582,6061],{"class":226},[118,24584,25],{"class":124},[118,24586,9973],{"class":213},[118,24588,255],{"class":124},[118,24590,550],{"class":226},[118,24592,25],{"class":124},[118,24594,658],{"class":213},[118,24596,255],{"class":124},[118,24598,9986],{"class":299},[118,24600,3646],{"class":124},[118,24602,24603],{"class":120,"line":317},[118,24604,6166],{"class":124},[118,24606,24607],{"class":120,"line":350},[118,24608,706],{"class":124},[118,24610,24611],{"class":120,"line":379},[118,24612,1944],{"class":124},[14,24614,24615,24617,24618,24621,24622,24624],{},[18,24616,7733],{}," are ",[1629,24619,24620],{},"percentages of the column the table lives in",", not absolute fractions of the page. Drop the same table inside ",[18,24623,11193],{}," and the percentages still hold — the table now occupies half the row and the columns redistribute in proportion. Page breaks are handled automatically; if the body runs past the bottom margin, the header repeats on the next page without you wiring anything.",[14,24626,24627,24628,24630,24631,24633],{},"A specific detail worth calling out. unipdf's Table on a 100-row invoice run benchmarks at around ",[1629,24629,17738],{}," per render in our suite. gpdf's table runs the same workload in ",[1629,24632,16053],{}," — about 80× faster — because the layout engine measures each row once and writes pages in a single pass, instead of materializing a cell-by-cell DOM. For a single invoice this difference is invisible. For a batch report run on a cron, it changes whether you need a queue.",[41,24635,24637],{"id":24636},"before-after-3-japanese-text-without-the-composite-font-dance","Before / After 3: Japanese text without the composite-font dance",[14,24639,24640],{},"unipdf supports CJK well, but the path is verbose. You construct a composite font from a TTF on disk, set it as the style font, and pass it through every paragraph. If you want fallbacks you wire them yourself.",[14,24642,24643],{},[1629,24644,22899],{},[109,24646,24648],{"className":111,"code":24647,"language":113,"meta":114,"style":114},"font, err := model.NewCompositePdfFontFromTTFFile(\"NotoSansJP-Regular.ttf\")\nif err != nil {\n    log.Fatal(err)\n}\n\nc := creator.New()\nc.SetPageSize(creator.PageSizeA4)\n\nstyle := c.NewTextStyle()\nstyle.Font = font\nstyle.FontSize = 14\n\np := c.NewStyledParagraph()\np.Append(\"こんにちは、世界。\").Style = style\nif err := c.Draw(p); err != nil {\n    log.Fatal(err)\n}\n\nc.WriteToFile(\"ja.pdf\")\n",[18,24649,24650,24678,24690,24704,24708,24712,24726,24744,24748,24763,24777,24790,24794,24809,24835,24863,24877,24881,24885],{"__ignoreMap":114},[118,24651,24652,24655,24657,24659,24661,24663,24665,24668,24670,24672,24674,24676],{"class":120,"line":121},[118,24653,24654],{"class":226},"font",[118,24656,395],{"class":124},[118,24658,1391],{"class":226},[118,24660,230],{"class":124},[118,24662,23746],{"class":226},[118,24664,25],{"class":124},[118,24666,24667],{"class":213},"NewCompositePdfFontFromTTFFile",[118,24669,255],{"class":124},[118,24671,430],{"class":124},[118,24673,9552],{"class":433},[118,24675,430],{"class":124},[118,24677,199],{"class":124},[118,24679,24680,24682,24684,24686,24688],{"class":120,"line":132},[118,24681,16122],{"class":142},[118,24683,1391],{"class":226},[118,24685,1413],{"class":124},[118,24687,1416],{"class":124},[118,24689,220],{"class":124},[118,24691,24692,24694,24696,24698,24700,24702],{"class":120,"line":139},[118,24693,16196],{"class":226},[118,24695,25],{"class":124},[118,24697,5823],{"class":213},[118,24699,255],{"class":124},[118,24701,1429],{"class":226},[118,24703,199],{"class":124},[118,24705,24706],{"class":120,"line":149},[118,24707,1479],{"class":124},[118,24709,24710],{"class":120,"line":161},[118,24711,136],{"emptyLinePlaceholder":135},[118,24713,24714,24716,24718,24720,24722,24724],{"class":120,"line":166},[118,24715,4978],{"class":226},[118,24717,230],{"class":124},[118,24719,23061],{"class":226},[118,24721,25],{"class":124},[118,24723,238],{"class":213},[118,24725,1193],{"class":124},[118,24727,24728,24730,24732,24734,24736,24738,24740,24742],{"class":120,"line":176},[118,24729,401],{"class":226},[118,24731,25],{"class":124},[118,24733,23076],{"class":213},[118,24735,255],{"class":124},[118,24737,22122],{"class":226},[118,24739,25],{"class":124},[118,24741,13555],{"class":226},[118,24743,199],{"class":124},[118,24745,24746],{"class":120,"line":186},[118,24747,136],{"emptyLinePlaceholder":135},[118,24749,24750,24753,24755,24757,24759,24761],{"class":120,"line":196},[118,24751,24752],{"class":226},"style ",[118,24754,230],{"class":124},[118,24756,23100],{"class":226},[118,24758,25],{"class":124},[118,24760,23725],{"class":213},[118,24762,1193],{"class":124},[118,24764,24765,24767,24769,24772,24774],{"class":120,"line":202},[118,24766,3176],{"class":226},[118,24768,25],{"class":124},[118,24770,24771],{"class":226},"Font ",[118,24773,1366],{"class":124},[118,24775,24776],{"class":226}," font\n",[118,24778,24779,24781,24783,24785,24787],{"class":120,"line":207},[118,24780,3176],{"class":226},[118,24782,25],{"class":124},[118,24784,23771],{"class":226},[118,24786,1366],{"class":124},[118,24788,24789],{"class":299}," 14\n",[118,24791,24792],{"class":120,"line":223},[118,24793,136],{"emptyLinePlaceholder":135},[118,24795,24796,24799,24801,24803,24805,24807],{"class":120,"line":244},[118,24797,24798],{"class":226},"p ",[118,24800,230],{"class":124},[118,24802,23100],{"class":226},[118,24804,25],{"class":124},[118,24806,23912],{"class":213},[118,24808,1193],{"class":124},[118,24810,24811,24813,24815,24817,24819,24821,24823,24825,24828,24830,24832],{"class":120,"line":269},[118,24812,14],{"class":226},[118,24814,25],{"class":124},[118,24816,23929],{"class":213},[118,24818,255],{"class":124},[118,24820,430],{"class":124},[118,24822,17618],{"class":433},[118,24824,430],{"class":124},[118,24826,24827],{"class":124},").",[118,24829,23945],{"class":226},[118,24831,1366],{"class":124},[118,24833,24834],{"class":226}," style\n",[118,24836,24837,24839,24841,24843,24845,24847,24849,24851,24853,24855,24857,24859,24861],{"class":120,"line":306},[118,24838,16122],{"class":142},[118,24840,1391],{"class":226},[118,24842,230],{"class":124},[118,24844,23100],{"class":226},[118,24846,25],{"class":124},[118,24848,23145],{"class":213},[118,24850,255],{"class":124},[118,24852,14],{"class":226},[118,24854,7902],{"class":124},[118,24856,1391],{"class":226},[118,24858,1413],{"class":124},[118,24860,1416],{"class":124},[118,24862,220],{"class":124},[118,24864,24865,24867,24869,24871,24873,24875],{"class":120,"line":312},[118,24866,16196],{"class":226},[118,24868,25],{"class":124},[118,24870,5823],{"class":213},[118,24872,255],{"class":124},[118,24874,1429],{"class":226},[118,24876,199],{"class":124},[118,24878,24879],{"class":120,"line":317},[118,24880,1479],{"class":124},[118,24882,24883],{"class":120,"line":350},[118,24884,136],{"emptyLinePlaceholder":135},[118,24886,24887,24889,24891,24893,24895,24897,24900,24902],{"class":120,"line":379},[118,24888,401],{"class":226},[118,24890,25],{"class":124},[118,24892,23196],{"class":213},[118,24894,255],{"class":124},[118,24896,430],{"class":124},[118,24898,24899],{"class":433},"ja.pdf",[118,24901,430],{"class":124},[118,24903,199],{"class":124},[14,24905,24906,24907,24909],{},"The font has to exist at the path you give, at runtime, on the host running the binary. Container images need to ship the TTF. The ",[18,24908,24667],{}," step has to happen before any drawing call that uses the font, which means it lives somewhere global or gets passed around as a dependency.",[14,24911,24912],{},[1629,24913,13844],{},[109,24915,24917],{"className":111,"code":24916,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    _ \"embed\"\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\n//go:embed NotoSansJP-Regular.ttf\nvar notoJP []byte\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", notoJP),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 14),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"こんにちは、世界。\")\n            c.Text(\"吾輩は猫である。名前はまだ無い。\")\n            c.Text(\"東京都渋谷区神宮前1-2-3\")\n        })\n    })\n\n    data, _ := doc.Generate()\n    if err := os.WriteFile(\"ja.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,24918,24919,24925,24929,24935,24945,24953,24961,24965,24973,24981,24989,24993,24997,25001,25011,25015,25025,25039,25057,25087,25109,25131,25135,25139,25153,25177,25207,25225,25243,25261,25265,25269,25273,25291,25331,25345,25349],{"__ignoreMap":114},[118,24920,24921,24923],{"class":120,"line":121},[118,24922,125],{"class":124},[118,24924,129],{"class":128},[118,24926,24927],{"class":120,"line":132},[118,24928,136],{"emptyLinePlaceholder":135},[118,24930,24931,24933],{"class":120,"line":139},[118,24932,143],{"class":142},[118,24934,146],{"class":124},[118,24936,24937,24939,24941,24943],{"class":120,"line":149},[118,24938,1442],{"class":226},[118,24940,430],{"class":124},[118,24942,21730],{"class":128},[118,24944,158],{"class":124},[118,24946,24947,24949,24951],{"class":120,"line":161},[118,24948,152],{"class":124},[118,24950,5303],{"class":128},[118,24952,158],{"class":124},[118,24954,24955,24957,24959],{"class":120,"line":166},[118,24956,152],{"class":124},[118,24958,155],{"class":128},[118,24960,158],{"class":124},[118,24962,24963],{"class":120,"line":176},[118,24964,136],{"emptyLinePlaceholder":135},[118,24966,24967,24969,24971],{"class":120,"line":186},[118,24968,152],{"class":124},[118,24970,3203],{"class":128},[118,24972,158],{"class":124},[118,24974,24975,24977,24979],{"class":120,"line":196},[118,24976,152],{"class":124},[118,24978,171],{"class":128},[118,24980,158],{"class":124},[118,24982,24983,24985,24987],{"class":120,"line":202},[118,24984,152],{"class":124},[118,24986,191],{"class":128},[118,24988,158],{"class":124},[118,24990,24991],{"class":120,"line":207},[118,24992,199],{"class":124},[118,24994,24995],{"class":120,"line":223},[118,24996,136],{"emptyLinePlaceholder":135},[118,24998,24999],{"class":120,"line":244},[118,25000,17404],{"class":3981},[118,25002,25003,25005,25007,25009],{"class":120,"line":269},[118,25004,16492],{"class":124},[118,25006,17411],{"class":226},[118,25008,16498],{"class":124},[118,25010,16501],{"class":1130},[118,25012,25013],{"class":120,"line":306},[118,25014,136],{"emptyLinePlaceholder":135},[118,25016,25017,25019,25021,25023],{"class":120,"line":312},[118,25018,210],{"class":124},[118,25020,214],{"class":213},[118,25022,217],{"class":124},[118,25024,220],{"class":124},[118,25026,25027,25029,25031,25033,25035,25037],{"class":120,"line":317},[118,25028,227],{"class":226},[118,25030,230],{"class":124},[118,25032,3595],{"class":226},[118,25034,25],{"class":124},[118,25036,3600],{"class":213},[118,25038,241],{"class":124},[118,25040,25041,25043,25045,25047,25049,25051,25053,25055],{"class":120,"line":350},[118,25042,3607],{"class":226},[118,25044,25],{"class":124},[118,25046,252],{"class":213},[118,25048,255],{"class":124},[118,25050,258],{"class":226},[118,25052,25],{"class":124},[118,25054,263],{"class":226},[118,25056,266],{"class":124},[118,25058,25059,25061,25063,25065,25067,25069,25071,25073,25075,25077,25079,25081,25083,25085],{"class":120,"line":379},[118,25060,3607],{"class":226},[118,25062,25],{"class":124},[118,25064,276],{"class":213},[118,25066,255],{"class":124},[118,25068,258],{"class":226},[118,25070,25],{"class":124},[118,25072,285],{"class":213},[118,25074,255],{"class":124},[118,25076,258],{"class":226},[118,25078,25],{"class":124},[118,25080,294],{"class":213},[118,25082,255],{"class":124},[118,25084,300],{"class":299},[118,25086,303],{"class":124},[118,25088,25089,25091,25093,25095,25097,25099,25101,25103,25105,25107],{"class":120,"line":417},[118,25090,3607],{"class":226},[118,25092,25],{"class":124},[118,25094,2798],{"class":213},[118,25096,255],{"class":124},[118,25098,430],{"class":124},[118,25100,2805],{"class":433},[118,25102,430],{"class":124},[118,25104,395],{"class":124},[118,25106,17502],{"class":226},[118,25108,266],{"class":124},[118,25110,25111,25113,25115,25117,25119,25121,25123,25125,25127,25129],{"class":120,"line":466},[118,25112,3607],{"class":226},[118,25114,25],{"class":124},[118,25116,12501],{"class":213},[118,25118,255],{"class":124},[118,25120,430],{"class":124},[118,25122,2805],{"class":433},[118,25124,430],{"class":124},[118,25126,395],{"class":124},[118,25128,16239],{"class":299},[118,25130,266],{"class":124},[118,25132,25133],{"class":120,"line":472},[118,25134,309],{"class":124},[118,25136,25137],{"class":120,"line":503},[118,25138,136],{"emptyLinePlaceholder":135},[118,25140,25141,25143,25145,25147,25149,25151],{"class":120,"line":537},[118,25142,5431],{"class":226},[118,25144,230],{"class":124},[118,25146,1185],{"class":226},[118,25148,25],{"class":124},[118,25150,1190],{"class":213},[118,25152,1193],{"class":124},[118,25154,25155,25157,25159,25161,25163,25165,25167,25169,25171,25173,25175],{"class":120,"line":566},[118,25156,5494],{"class":226},[118,25158,25],{"class":124},[118,25160,358],{"class":213},[118,25162,328],{"class":124},[118,25164,363],{"class":331},[118,25166,334],{"class":124},[118,25168,337],{"class":128},[118,25170,25],{"class":124},[118,25172,372],{"class":128},[118,25174,345],{"class":124},[118,25176,220],{"class":124},[118,25178,25179,25181,25183,25185,25187,25189,25191,25193,25195,25197,25199,25201,25203,25205],{"class":120,"line":571},[118,25180,1737],{"class":226},[118,25182,25],{"class":124},[118,25184,387],{"class":213},[118,25186,255],{"class":124},[118,25188,20],{"class":299},[118,25190,395],{"class":124},[118,25192,398],{"class":124},[118,25194,401],{"class":331},[118,25196,334],{"class":124},[118,25198,337],{"class":128},[118,25200,25],{"class":124},[118,25202,410],{"class":128},[118,25204,345],{"class":124},[118,25206,220],{"class":124},[118,25208,25209,25211,25213,25215,25217,25219,25221,25223],{"class":120,"line":577},[118,25210,1768],{"class":226},[118,25212,25],{"class":124},[118,25214,425],{"class":213},[118,25216,255],{"class":124},[118,25218,430],{"class":124},[118,25220,17618],{"class":433},[118,25222,430],{"class":124},[118,25224,199],{"class":124},[118,25226,25227,25229,25231,25233,25235,25237,25239,25241],{"class":120,"line":602},[118,25228,1768],{"class":226},[118,25230,25],{"class":124},[118,25232,425],{"class":213},[118,25234,255],{"class":124},[118,25236,430],{"class":124},[118,25238,17637],{"class":433},[118,25240,430],{"class":124},[118,25242,199],{"class":124},[118,25244,25245,25247,25249,25251,25253,25255,25257,25259],{"class":120,"line":633},[118,25246,1768],{"class":226},[118,25248,25],{"class":124},[118,25250,425],{"class":213},[118,25252,255],{"class":124},[118,25254,430],{"class":124},[118,25256,17656],{"class":433},[118,25258,430],{"class":124},[118,25260,199],{"class":124},[118,25262,25263],{"class":120,"line":668},[118,25264,574],{"class":124},[118,25266,25267],{"class":120,"line":693},[118,25268,706],{"class":124},[118,25270,25271],{"class":120,"line":698},[118,25272,136],{"emptyLinePlaceholder":135},[118,25274,25275,25277,25279,25281,25283,25285,25287,25289],{"class":120,"line":703},[118,25276,5787],{"class":226},[118,25278,395],{"class":124},[118,25280,3999],{"class":226},[118,25282,230],{"class":124},[118,25284,1185],{"class":226},[118,25286,25],{"class":124},[118,25288,1400],{"class":213},[118,25290,1193],{"class":124},[118,25292,25293,25295,25297,25299,25301,25303,25305,25307,25309,25311,25313,25315,25317,25319,25321,25323,25325,25327,25329],{"class":120,"line":709},[118,25294,1408],{"class":142},[118,25296,1391],{"class":226},[118,25298,230],{"class":124},[118,25300,1447],{"class":226},[118,25302,25],{"class":124},[118,25304,1452],{"class":213},[118,25306,255],{"class":124},[118,25308,430],{"class":124},[118,25310,24899],{"class":433},[118,25312,430],{"class":124},[118,25314,395],{"class":124},[118,25316,5859],{"class":226},[118,25318,395],{"class":124},[118,25320,1471],{"class":299},[118,25322,7902],{"class":124},[118,25324,1391],{"class":226},[118,25326,1413],{"class":124},[118,25328,1416],{"class":124},[118,25330,220],{"class":124},[118,25332,25333,25335,25337,25339,25341,25343],{"class":120,"line":714},[118,25334,5818],{"class":226},[118,25336,25],{"class":124},[118,25338,5823],{"class":213},[118,25340,255],{"class":124},[118,25342,1429],{"class":226},[118,25344,199],{"class":124},[118,25346,25347],{"class":120,"line":740},[118,25348,1375],{"class":124},[118,25350,25351],{"class":120,"line":765},[118,25352,1479],{"class":124},[14,25354,25355,25356,25359,25360,25362,25363,25366],{},"Three differences worth seeing. The font is ",[1629,25357,25358],{},"bytes",", not a path — ",[18,25361,17916],{}," compiles it into the binary so the runtime image stops needing a font directory. The font is registered ",[1629,25364,25365],{},"once at construction","; no per-paragraph style threading. And gpdf's TrueType subsetter understands CJK cmap formats (4, 6, 12) and Identity-H encoding, so the output PDF embeds only the glyphs you used. A 200-character Japanese invoice produces a ~30 KB font subset rather than a 4 MB full embed.",[14,25368,25369],{},"The companion piece on Japanese fonts walks IPAex Gothic, Source Han Sans, and fallback chains in more depth.",[41,25371,25373],{"id":25372},"before-after-4-header-on-every-page-page-numbers-in-the-footer","Before / After 4: header on every page, page numbers in the footer",[14,25375,25376,25377,54,25379,25381,25382,54,25385,25387],{},"unipdf's pattern is ",[18,25378,22753],{},[18,25380,22756],{},", both of which receive a context with the current block and the page number. Page numbers come from the context's ",[18,25383,25384],{},"PageNum",[18,25386,510],{}," fields.",[14,25389,25390],{},[1629,25391,22899],{},[109,25393,25395],{"className":111,"code":25394,"language":113,"meta":114,"style":114},"c.DrawHeader(func(block *creator.Block, args creator.HeaderFunctionArgs) {\n    p := c.NewParagraph(\"ACME Corporation\")\n    p.SetFontSize(12)\n    p.SetPos(40, 30)\n    block.Draw(p)\n})\n\nc.DrawFooter(func(block *creator.Block, args creator.FooterFunctionArgs) {\n    p := c.NewParagraph(fmt.Sprintf(\"Page %d of %d\", args.PageNum, args.TotalPages))\n    p.SetFontSize(8)\n    p.SetPos(0, 20)\n    p.SetTextAlignment(creator.TextAlignmentCenter)\n    block.Draw(p)\n})\n",[18,25396,25397,25436,25458,25472,25491,25506,25510,25514,25549,25601,25615,25633,25653,25667],{"__ignoreMap":114},[118,25398,25399,25401,25403,25406,25408,25411,25413,25415,25417,25420,25422,25425,25427,25429,25432,25434],{"class":120,"line":121},[118,25400,401],{"class":226},[118,25402,25],{"class":124},[118,25404,25405],{"class":213},"DrawHeader",[118,25407,328],{"class":124},[118,25409,25410],{"class":331},"block",[118,25412,334],{"class":124},[118,25414,22122],{"class":128},[118,25416,25],{"class":124},[118,25418,25419],{"class":128},"Block",[118,25421,395],{"class":124},[118,25423,25424],{"class":331}," args",[118,25426,23061],{"class":128},[118,25428,25],{"class":124},[118,25430,25431],{"class":128},"HeaderFunctionArgs",[118,25433,345],{"class":124},[118,25435,220],{"class":124},[118,25437,25438,25440,25442,25444,25446,25448,25450,25452,25454,25456],{"class":120,"line":132},[118,25439,23095],{"class":226},[118,25441,230],{"class":124},[118,25443,23100],{"class":226},[118,25445,25],{"class":124},[118,25447,23105],{"class":213},[118,25449,255],{"class":124},[118,25451,430],{"class":124},[118,25453,16281],{"class":433},[118,25455,430],{"class":124},[118,25457,199],{"class":124},[118,25459,25460,25462,25464,25466,25468,25470],{"class":120,"line":139},[118,25461,1712],{"class":226},[118,25463,25],{"class":124},[118,25465,23124],{"class":213},[118,25467,255],{"class":124},[118,25469,20],{"class":299},[118,25471,199],{"class":124},[118,25473,25474,25476,25478,25481,25483,25485,25487,25489],{"class":120,"line":149},[118,25475,1712],{"class":226},[118,25477,25],{"class":124},[118,25479,25480],{"class":213},"SetPos",[118,25482,255],{"class":124},[118,25484,9910],{"class":299},[118,25486,395],{"class":124},[118,25488,11280],{"class":299},[118,25490,199],{"class":124},[118,25492,25493,25496,25498,25500,25502,25504],{"class":120,"line":161},[118,25494,25495],{"class":226},"    block",[118,25497,25],{"class":124},[118,25499,23145],{"class":213},[118,25501,255],{"class":124},[118,25503,14],{"class":226},[118,25505,199],{"class":124},[118,25507,25508],{"class":120,"line":166},[118,25509,1944],{"class":124},[118,25511,25512],{"class":120,"line":176},[118,25513,136],{"emptyLinePlaceholder":135},[118,25515,25516,25518,25520,25522,25524,25526,25528,25530,25532,25534,25536,25538,25540,25542,25545,25547],{"class":120,"line":186},[118,25517,401],{"class":226},[118,25519,25],{"class":124},[118,25521,22772],{"class":213},[118,25523,328],{"class":124},[118,25525,25410],{"class":331},[118,25527,334],{"class":124},[118,25529,22122],{"class":128},[118,25531,25],{"class":124},[118,25533,25419],{"class":128},[118,25535,395],{"class":124},[118,25537,25424],{"class":331},[118,25539,23061],{"class":128},[118,25541,25],{"class":124},[118,25543,25544],{"class":128},"FooterFunctionArgs",[118,25546,345],{"class":124},[118,25548,220],{"class":124},[118,25550,25551,25553,25555,25557,25559,25561,25563,25565,25567,25569,25571,25573,25575,25577,25579,25581,25583,25585,25587,25589,25591,25593,25595,25597,25599],{"class":120,"line":196},[118,25552,23095],{"class":226},[118,25554,230],{"class":124},[118,25556,23100],{"class":226},[118,25558,25],{"class":124},[118,25560,23105],{"class":213},[118,25562,255],{"class":124},[118,25564,7108],{"class":226},[118,25566,25],{"class":124},[118,25568,7416],{"class":213},[118,25570,255],{"class":124},[118,25572,430],{"class":124},[118,25574,16978],{"class":433},[118,25576,2323],{"class":7426},[118,25578,16983],{"class":433},[118,25580,2323],{"class":7426},[118,25582,430],{"class":124},[118,25584,395],{"class":124},[118,25586,25424],{"class":226},[118,25588,25],{"class":124},[118,25590,25384],{"class":226},[118,25592,395],{"class":124},[118,25594,25424],{"class":226},[118,25596,25],{"class":124},[118,25598,510],{"class":226},[118,25600,463],{"class":124},[118,25602,25603,25605,25607,25609,25611,25613],{"class":120,"line":202},[118,25604,1712],{"class":226},[118,25606,25],{"class":124},[118,25608,23124],{"class":213},[118,25610,255],{"class":124},[118,25612,969],{"class":299},[118,25614,199],{"class":124},[118,25616,25617,25619,25621,25623,25625,25627,25629,25631],{"class":120,"line":207},[118,25618,1712],{"class":226},[118,25620,25],{"class":124},[118,25622,25480],{"class":213},[118,25624,255],{"class":124},[118,25626,4159],{"class":299},[118,25628,395],{"class":124},[118,25630,7742],{"class":299},[118,25632,199],{"class":124},[118,25634,25635,25637,25639,25642,25644,25646,25648,25651],{"class":120,"line":223},[118,25636,1712],{"class":226},[118,25638,25],{"class":124},[118,25640,25641],{"class":213},"SetTextAlignment",[118,25643,255],{"class":124},[118,25645,22122],{"class":226},[118,25647,25],{"class":124},[118,25649,25650],{"class":226},"TextAlignmentCenter",[118,25652,199],{"class":124},[118,25654,25655,25657,25659,25661,25663,25665],{"class":120,"line":244},[118,25656,25495],{"class":226},[118,25658,25],{"class":124},[118,25660,23145],{"class":213},[118,25662,255],{"class":124},[118,25664,14],{"class":226},[118,25666,199],{"class":124},[118,25668,25669],{"class":120,"line":269},[118,25670,1944],{"class":124},[14,25672,25673],{},"The header / footer are blocks you draw into with absolute positions. Wrong y-coordinate, wrong margin — pixel work each time you change the page size.",[14,25675,25676],{},[1629,25677,13844],{},[109,25679,25681],{"className":111,"code":25680,"language":113,"meta":114,"style":114},"doc := gpdf.NewDocument(\n    gpdf.WithPageSize(document.A4),\n    gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n)\n\ndoc.Header(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"ACME Corporation\", template.Bold(), template.FontSize(12))\n            c.Line(template.LineColor(pdf.Gray(0.7)))\n            c.Spacer(document.Mm(4))\n        })\n    })\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(\"ACME Corporation\",\n                template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.PageNumber(template.AlignRight(),\n                template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n    })\n})\n\nfor i := 0; i \u003C 10; i++ {\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(fmt.Sprintf(\"Body content for page %d.\", i+1))\n        })\n    })\n}\n",[18,25682,25683,25697,25715,25745,25749,25753,25777,25801,25831,25869,25899,25921,25925,25929,25933,25937,25961,25985,26015,26033,26067,26071,26101,26119,26153,26157,26161,26165,26169,26196,26210,26234,26264,26303,26307,26311],{"__ignoreMap":114},[118,25684,25685,25687,25689,25691,25693,25695],{"class":120,"line":121},[118,25686,2760],{"class":226},[118,25688,230],{"class":124},[118,25690,3595],{"class":226},[118,25692,25],{"class":124},[118,25694,3600],{"class":213},[118,25696,241],{"class":124},[118,25698,25699,25701,25703,25705,25707,25709,25711,25713],{"class":120,"line":132},[118,25700,4532],{"class":226},[118,25702,25],{"class":124},[118,25704,252],{"class":213},[118,25706,255],{"class":124},[118,25708,258],{"class":226},[118,25710,25],{"class":124},[118,25712,263],{"class":226},[118,25714,266],{"class":124},[118,25716,25717,25719,25721,25723,25725,25727,25729,25731,25733,25735,25737,25739,25741,25743],{"class":120,"line":139},[118,25718,4532],{"class":226},[118,25720,25],{"class":124},[118,25722,276],{"class":213},[118,25724,255],{"class":124},[118,25726,258],{"class":226},[118,25728,25],{"class":124},[118,25730,285],{"class":213},[118,25732,255],{"class":124},[118,25734,258],{"class":226},[118,25736,25],{"class":124},[118,25738,294],{"class":213},[118,25740,255],{"class":124},[118,25742,300],{"class":299},[118,25744,303],{"class":124},[118,25746,25747],{"class":120,"line":149},[118,25748,199],{"class":124},[118,25750,25751],{"class":120,"line":161},[118,25752,136],{"emptyLinePlaceholder":135},[118,25754,25755,25757,25759,25761,25763,25765,25767,25769,25771,25773,25775],{"class":120,"line":166},[118,25756,1687],{"class":226},[118,25758,25],{"class":124},[118,25760,325],{"class":213},[118,25762,328],{"class":124},[118,25764,14],{"class":331},[118,25766,334],{"class":124},[118,25768,337],{"class":128},[118,25770,25],{"class":124},[118,25772,342],{"class":128},[118,25774,345],{"class":124},[118,25776,220],{"class":124},[118,25778,25779,25781,25783,25785,25787,25789,25791,25793,25795,25797,25799],{"class":120,"line":176},[118,25780,1712],{"class":226},[118,25782,25],{"class":124},[118,25784,358],{"class":213},[118,25786,328],{"class":124},[118,25788,363],{"class":331},[118,25790,334],{"class":124},[118,25792,337],{"class":128},[118,25794,25],{"class":124},[118,25796,372],{"class":128},[118,25798,345],{"class":124},[118,25800,220],{"class":124},[118,25802,25803,25805,25807,25809,25811,25813,25815,25817,25819,25821,25823,25825,25827,25829],{"class":120,"line":186},[118,25804,1737],{"class":226},[118,25806,25],{"class":124},[118,25808,387],{"class":213},[118,25810,255],{"class":124},[118,25812,20],{"class":299},[118,25814,395],{"class":124},[118,25816,398],{"class":124},[118,25818,401],{"class":331},[118,25820,334],{"class":124},[118,25822,337],{"class":128},[118,25824,25],{"class":124},[118,25826,410],{"class":128},[118,25828,345],{"class":124},[118,25830,220],{"class":124},[118,25832,25833,25835,25837,25839,25841,25843,25845,25847,25849,25851,25853,25855,25857,25859,25861,25863,25865,25867],{"class":120,"line":196},[118,25834,1768],{"class":226},[118,25836,25],{"class":124},[118,25838,425],{"class":213},[118,25840,255],{"class":124},[118,25842,430],{"class":124},[118,25844,16281],{"class":433},[118,25846,430],{"class":124},[118,25848,395],{"class":124},[118,25850,233],{"class":226},[118,25852,25],{"class":124},[118,25854,445],{"class":213},[118,25856,448],{"class":124},[118,25858,233],{"class":226},[118,25860,25],{"class":124},[118,25862,455],{"class":213},[118,25864,255],{"class":124},[118,25866,20],{"class":299},[118,25868,463],{"class":124},[118,25870,25871,25873,25875,25877,25879,25881,25883,25885,25887,25889,25891,25893,25895,25897],{"class":120,"line":202},[118,25872,1768],{"class":226},[118,25874,25],{"class":124},[118,25876,640],{"class":213},[118,25878,255],{"class":124},[118,25880,337],{"class":226},[118,25882,25],{"class":124},[118,25884,649],{"class":213},[118,25886,255],{"class":124},[118,25888,550],{"class":226},[118,25890,25],{"class":124},[118,25892,555],{"class":213},[118,25894,255],{"class":124},[118,25896,846],{"class":299},[118,25898,563],{"class":124},[118,25900,25901,25903,25905,25907,25909,25911,25913,25915,25917,25919],{"class":120,"line":207},[118,25902,1768],{"class":226},[118,25904,25],{"class":124},[118,25906,675],{"class":213},[118,25908,255],{"class":124},[118,25910,258],{"class":226},[118,25912,25],{"class":124},[118,25914,294],{"class":213},[118,25916,255],{"class":124},[118,25918,1493],{"class":299},[118,25920,463],{"class":124},[118,25922,25923],{"class":120,"line":223},[118,25924,574],{"class":124},[118,25926,25927],{"class":120,"line":244},[118,25928,706],{"class":124},[118,25930,25931],{"class":120,"line":269},[118,25932,1944],{"class":124},[118,25934,25935],{"class":120,"line":306},[118,25936,136],{"emptyLinePlaceholder":135},[118,25938,25939,25941,25943,25945,25947,25949,25951,25953,25955,25957,25959],{"class":120,"line":312},[118,25940,1687],{"class":226},[118,25942,25],{"class":124},[118,25944,721],{"class":213},[118,25946,328],{"class":124},[118,25948,14],{"class":331},[118,25950,334],{"class":124},[118,25952,337],{"class":128},[118,25954,25],{"class":124},[118,25956,342],{"class":128},[118,25958,345],{"class":124},[118,25960,220],{"class":124},[118,25962,25963,25965,25967,25969,25971,25973,25975,25977,25979,25981,25983],{"class":120,"line":317},[118,25964,1712],{"class":226},[118,25966,25],{"class":124},[118,25968,358],{"class":213},[118,25970,328],{"class":124},[118,25972,363],{"class":331},[118,25974,334],{"class":124},[118,25976,337],{"class":128},[118,25978,25],{"class":124},[118,25980,372],{"class":128},[118,25982,345],{"class":124},[118,25984,220],{"class":124},[118,25986,25987,25989,25991,25993,25995,25997,25999,26001,26003,26005,26007,26009,26011,26013],{"class":120,"line":350},[118,25988,1737],{"class":226},[118,25990,25],{"class":124},[118,25992,387],{"class":213},[118,25994,255],{"class":124},[118,25996,392],{"class":299},[118,25998,395],{"class":124},[118,26000,398],{"class":124},[118,26002,401],{"class":331},[118,26004,334],{"class":124},[118,26006,337],{"class":128},[118,26008,25],{"class":124},[118,26010,410],{"class":128},[118,26012,345],{"class":124},[118,26014,220],{"class":124},[118,26016,26017,26019,26021,26023,26025,26027,26029,26031],{"class":120,"line":379},[118,26018,1768],{"class":226},[118,26020,25],{"class":124},[118,26022,425],{"class":213},[118,26024,255],{"class":124},[118,26026,430],{"class":124},[118,26028,16281],{"class":433},[118,26030,430],{"class":124},[118,26032,2643],{"class":124},[118,26034,26035,26037,26039,26041,26043,26045,26047,26049,26051,26053,26055,26057,26059,26061,26063,26065],{"class":120,"line":417},[118,26036,2648],{"class":226},[118,26038,25],{"class":124},[118,26040,455],{"class":213},[118,26042,255],{"class":124},[118,26044,969],{"class":299},[118,26046,1280],{"class":124},[118,26048,233],{"class":226},[118,26050,25],{"class":124},[118,26052,545],{"class":213},[118,26054,255],{"class":124},[118,26056,550],{"class":226},[118,26058,25],{"class":124},[118,26060,555],{"class":213},[118,26062,255],{"class":124},[118,26064,560],{"class":299},[118,26066,563],{"class":124},[118,26068,26069],{"class":120,"line":466},[118,26070,574],{"class":124},[118,26072,26073,26075,26077,26079,26081,26083,26085,26087,26089,26091,26093,26095,26097,26099],{"class":120,"line":472},[118,26074,1737],{"class":226},[118,26076,25],{"class":124},[118,26078,387],{"class":213},[118,26080,255],{"class":124},[118,26082,392],{"class":299},[118,26084,395],{"class":124},[118,26086,398],{"class":124},[118,26088,401],{"class":331},[118,26090,334],{"class":124},[118,26092,337],{"class":128},[118,26094,25],{"class":124},[118,26096,410],{"class":128},[118,26098,345],{"class":124},[118,26100,220],{"class":124},[118,26102,26103,26105,26107,26109,26111,26113,26115,26117],{"class":120,"line":503},[118,26104,1768],{"class":226},[118,26106,25],{"class":124},[118,26108,1040],{"class":213},[118,26110,255],{"class":124},[118,26112,337],{"class":226},[118,26114,25],{"class":124},[118,26116,519],{"class":213},[118,26118,2655],{"class":124},[118,26120,26121,26123,26125,26127,26129,26131,26133,26135,26137,26139,26141,26143,26145,26147,26149,26151],{"class":120,"line":537},[118,26122,2648],{"class":226},[118,26124,25],{"class":124},[118,26126,455],{"class":213},[118,26128,255],{"class":124},[118,26130,969],{"class":299},[118,26132,1280],{"class":124},[118,26134,233],{"class":226},[118,26136,25],{"class":124},[118,26138,545],{"class":213},[118,26140,255],{"class":124},[118,26142,550],{"class":226},[118,26144,25],{"class":124},[118,26146,555],{"class":213},[118,26148,255],{"class":124},[118,26150,560],{"class":299},[118,26152,563],{"class":124},[118,26154,26155],{"class":120,"line":566},[118,26156,574],{"class":124},[118,26158,26159],{"class":120,"line":571},[118,26160,706],{"class":124},[118,26162,26163],{"class":120,"line":577},[118,26164,1944],{"class":124},[118,26166,26167],{"class":120,"line":602},[118,26168,136],{"emptyLinePlaceholder":135},[118,26170,26171,26173,26175,26177,26179,26181,26183,26186,26188,26190,26192,26194],{"class":120,"line":633},[118,26172,14495],{"class":142},[118,26174,7358],{"class":226},[118,26176,230],{"class":124},[118,26178,7344],{"class":299},[118,26180,7366],{"class":124},[118,26182,7358],{"class":226},[118,26184,26185],{"class":124},"\u003C",[118,26187,11241],{"class":299},[118,26189,7366],{"class":124},[118,26191,1114],{"class":226},[118,26193,7380],{"class":124},[118,26195,220],{"class":124},[118,26197,26198,26200,26202,26204,26206,26208],{"class":120,"line":668},[118,26199,5431],{"class":226},[118,26201,230],{"class":124},[118,26203,1185],{"class":226},[118,26205,25],{"class":124},[118,26207,1190],{"class":213},[118,26209,1193],{"class":124},[118,26211,26212,26214,26216,26218,26220,26222,26224,26226,26228,26230,26232],{"class":120,"line":693},[118,26213,5494],{"class":226},[118,26215,25],{"class":124},[118,26217,358],{"class":213},[118,26219,328],{"class":124},[118,26221,363],{"class":331},[118,26223,334],{"class":124},[118,26225,337],{"class":128},[118,26227,25],{"class":124},[118,26229,372],{"class":128},[118,26231,345],{"class":124},[118,26233,220],{"class":124},[118,26235,26236,26238,26240,26242,26244,26246,26248,26250,26252,26254,26256,26258,26260,26262],{"class":120,"line":698},[118,26237,1737],{"class":226},[118,26239,25],{"class":124},[118,26241,387],{"class":213},[118,26243,255],{"class":124},[118,26245,20],{"class":299},[118,26247,395],{"class":124},[118,26249,398],{"class":124},[118,26251,401],{"class":331},[118,26253,334],{"class":124},[118,26255,337],{"class":128},[118,26257,25],{"class":124},[118,26259,410],{"class":128},[118,26261,345],{"class":124},[118,26263,220],{"class":124},[118,26265,26266,26268,26270,26272,26274,26276,26278,26280,26282,26284,26287,26289,26291,26293,26295,26297,26299,26301],{"class":120,"line":703},[118,26267,1768],{"class":226},[118,26269,25],{"class":124},[118,26271,425],{"class":213},[118,26273,255],{"class":124},[118,26275,7108],{"class":226},[118,26277,25],{"class":124},[118,26279,7416],{"class":213},[118,26281,255],{"class":124},[118,26283,430],{"class":124},[118,26285,26286],{"class":433},"Body content for page ",[118,26288,2323],{"class":7426},[118,26290,25],{"class":433},[118,26292,430],{"class":124},[118,26294,395],{"class":124},[118,26296,1114],{"class":226},[118,26298,1339],{"class":124},[118,26300,2402],{"class":299},[118,26302,463],{"class":124},[118,26304,26305],{"class":120,"line":709},[118,26306,574],{"class":124},[118,26308,26309],{"class":120,"line":714},[118,26310,706],{"class":124},[118,26312,26313],{"class":120,"line":740},[118,26314,1479],{"class":124},[14,26316,26317,54,26319,26321],{},[18,26318,1040],{},[18,26320,510],{}," are placeholders that the layout engine resolves after pagination. Header and footer are themselves trees, not blocks you position by hand. The engine reserves space for them on every page automatically; if you change the page size from A4 to Letter, nothing else has to move.",[41,26323,26325],{"id":26324},"before-after-5-encryption-with-aes-256","Before / After 5: encryption with AES-256",[14,26327,26328,26329,26331],{},"This is the pair where the license picture is most stark. unipdf's encryption goes through ",[18,26330,22793],{},", which counts as commercial usage and triggers the license registration path. gpdf's lives behind a single functional option, and the AES-256 (ISO 32000-2 Rev 6) implementation is in the open-source MIT core.",[14,26333,26334],{},[1629,26335,22899],{},[109,26337,26339],{"className":111,"code":26338,"language":113,"meta":114,"style":114},"// Render content via creator first, then re-encode with model.PdfWriter\n// to attach encryption. The license check fires here.\nc := creator.New()\n// ... draw content ...\n\nvar buf bytes.Buffer\nif err := c.Write(&buf); err != nil {\n    log.Fatal(err)\n}\n\nreader, err := model.NewPdfReader(bytes.NewReader(buf.Bytes()))\nif err != nil {\n    log.Fatal(err)\n}\n\nwriter := model.NewPdfWriter()\nencryptOpts := &model.EncryptOptions{Algorithm: model.RC4_128bit, Permissions: model.PermPrinting}\nif err := writer.Encrypt([]byte(\"user-pwd\"), []byte(\"owner-pwd\"), encryptOpts); err != nil {\n    log.Fatal(err)\n}\n\nfor i := 1; i \u003C= reader.NumPage; i++ {\n    page, _ := reader.GetPage(i)\n    writer.AddPage(page)\n}\n\nf, _ := os.Create(\"encrypted.pdf\")\ndefer f.Close()\nwriter.Write(f)\n",[18,26340,26341,26346,26351,26365,26370,26374,26388,26417,26431,26435,26439,26478,26490,26504,26508,26512,26528,26575,26634,26648,26652,26656,26688,26711,26726,26730,26734,26762,26775],{"__ignoreMap":114},[118,26342,26343],{"class":120,"line":121},[118,26344,26345],{"class":3981},"// Render content via creator first, then re-encode with model.PdfWriter\n",[118,26347,26348],{"class":120,"line":132},[118,26349,26350],{"class":3981},"// to attach encryption. The license check fires here.\n",[118,26352,26353,26355,26357,26359,26361,26363],{"class":120,"line":139},[118,26354,4978],{"class":226},[118,26356,230],{"class":124},[118,26358,23061],{"class":226},[118,26360,25],{"class":124},[118,26362,238],{"class":213},[118,26364,1193],{"class":124},[118,26366,26367],{"class":120,"line":149},[118,26368,26369],{"class":3981},"// ... draw content ...\n",[118,26371,26372],{"class":120,"line":161},[118,26373,136],{"emptyLinePlaceholder":135},[118,26375,26376,26378,26381,26383,26385],{"class":120,"line":166},[118,26377,16492],{"class":124},[118,26379,26380],{"class":226}," buf ",[118,26382,25358],{"class":128},[118,26384,25],{"class":124},[118,26386,26387],{"class":128},"Buffer\n",[118,26389,26390,26392,26394,26396,26398,26400,26402,26404,26407,26409,26411,26413,26415],{"class":120,"line":176},[118,26391,16122],{"class":142},[118,26393,1391],{"class":226},[118,26395,230],{"class":124},[118,26397,23100],{"class":226},[118,26399,25],{"class":124},[118,26401,4042],{"class":213},[118,26403,16808],{"class":124},[118,26405,26406],{"class":226},"buf",[118,26408,7902],{"class":124},[118,26410,1391],{"class":226},[118,26412,1413],{"class":124},[118,26414,1416],{"class":124},[118,26416,220],{"class":124},[118,26418,26419,26421,26423,26425,26427,26429],{"class":120,"line":186},[118,26420,16196],{"class":226},[118,26422,25],{"class":124},[118,26424,5823],{"class":213},[118,26426,255],{"class":124},[118,26428,1429],{"class":226},[118,26430,199],{"class":124},[118,26432,26433],{"class":120,"line":196},[118,26434,1479],{"class":124},[118,26436,26437],{"class":120,"line":202},[118,26438,136],{"emptyLinePlaceholder":135},[118,26440,26441,26444,26446,26448,26450,26452,26454,26457,26459,26461,26463,26466,26468,26470,26472,26475],{"class":120,"line":207},[118,26442,26443],{"class":226},"reader",[118,26445,395],{"class":124},[118,26447,1391],{"class":226},[118,26449,230],{"class":124},[118,26451,23746],{"class":226},[118,26453,25],{"class":124},[118,26455,26456],{"class":213},"NewPdfReader",[118,26458,255],{"class":124},[118,26460,25358],{"class":226},[118,26462,25],{"class":124},[118,26464,26465],{"class":213},"NewReader",[118,26467,255],{"class":124},[118,26469,26406],{"class":226},[118,26471,25],{"class":124},[118,26473,26474],{"class":213},"Bytes",[118,26476,26477],{"class":124},"()))\n",[118,26479,26480,26482,26484,26486,26488],{"class":120,"line":223},[118,26481,16122],{"class":142},[118,26483,1391],{"class":226},[118,26485,1413],{"class":124},[118,26487,1416],{"class":124},[118,26489,220],{"class":124},[118,26491,26492,26494,26496,26498,26500,26502],{"class":120,"line":244},[118,26493,16196],{"class":226},[118,26495,25],{"class":124},[118,26497,5823],{"class":213},[118,26499,255],{"class":124},[118,26501,1429],{"class":226},[118,26503,199],{"class":124},[118,26505,26506],{"class":120,"line":269},[118,26507,1479],{"class":124},[118,26509,26510],{"class":120,"line":306},[118,26511,136],{"emptyLinePlaceholder":135},[118,26513,26514,26517,26519,26521,26523,26526],{"class":120,"line":312},[118,26515,26516],{"class":226},"writer ",[118,26518,230],{"class":124},[118,26520,23746],{"class":226},[118,26522,25],{"class":124},[118,26524,26525],{"class":213},"NewPdfWriter",[118,26527,1193],{"class":124},[118,26529,26530,26533,26535,26537,26540,26542,26545,26547,26550,26552,26554,26556,26559,26561,26564,26566,26568,26570,26573],{"class":120,"line":317},[118,26531,26532],{"class":226},"encryptOpts ",[118,26534,230],{"class":124},[118,26536,8030],{"class":124},[118,26538,26539],{"class":128},"model",[118,26541,25],{"class":124},[118,26543,26544],{"class":128},"EncryptOptions",[118,26546,1134],{"class":124},[118,26548,26549],{"class":226},"Algorithm",[118,26551,6349],{"class":124},[118,26553,23746],{"class":226},[118,26555,25],{"class":124},[118,26557,26558],{"class":226},"RC4_128bit",[118,26560,395],{"class":124},[118,26562,26563],{"class":226}," Permissions",[118,26565,6349],{"class":124},[118,26567,23746],{"class":226},[118,26569,25],{"class":124},[118,26571,26572],{"class":226},"PermPrinting",[118,26574,1479],{"class":124},[118,26576,26577,26579,26581,26583,26586,26588,26590,26593,26595,26597,26599,26602,26604,26606,26608,26610,26612,26614,26617,26619,26621,26624,26626,26628,26630,26632],{"class":120,"line":350},[118,26578,16122],{"class":142},[118,26580,1391],{"class":226},[118,26582,230],{"class":124},[118,26584,26585],{"class":226}," writer",[118,26587,25],{"class":124},[118,26589,13363],{"class":213},[118,26591,26592],{"class":124},"([]",[118,26594,5036],{"class":1130},[118,26596,255],{"class":124},[118,26598,430],{"class":124},[118,26600,26601],{"class":433},"user-pwd",[118,26603,430],{"class":124},[118,26605,1280],{"class":124},[118,26607,1127],{"class":124},[118,26609,5036],{"class":1130},[118,26611,255],{"class":124},[118,26613,430],{"class":124},[118,26615,26616],{"class":433},"owner-pwd",[118,26618,430],{"class":124},[118,26620,1280],{"class":124},[118,26622,26623],{"class":226}," encryptOpts",[118,26625,7902],{"class":124},[118,26627,1391],{"class":226},[118,26629,1413],{"class":124},[118,26631,1416],{"class":124},[118,26633,220],{"class":124},[118,26635,26636,26638,26640,26642,26644,26646],{"class":120,"line":379},[118,26637,16196],{"class":226},[118,26639,25],{"class":124},[118,26641,5823],{"class":213},[118,26643,255],{"class":124},[118,26645,1429],{"class":226},[118,26647,199],{"class":124},[118,26649,26650],{"class":120,"line":417},[118,26651,1479],{"class":124},[118,26653,26654],{"class":120,"line":466},[118,26655,136],{"emptyLinePlaceholder":135},[118,26657,26658,26660,26662,26664,26666,26668,26670,26672,26675,26677,26680,26682,26684,26686],{"class":120,"line":472},[118,26659,14495],{"class":142},[118,26661,7358],{"class":226},[118,26663,230],{"class":124},[118,26665,7363],{"class":299},[118,26667,7366],{"class":124},[118,26669,7358],{"class":226},[118,26671,7371],{"class":124},[118,26673,26674],{"class":226}," reader",[118,26676,25],{"class":124},[118,26678,26679],{"class":226},"NumPage",[118,26681,7366],{"class":124},[118,26683,1114],{"class":226},[118,26685,7380],{"class":124},[118,26687,220],{"class":124},[118,26689,26690,26692,26694,26696,26698,26700,26702,26705,26707,26709],{"class":120,"line":503},[118,26691,5494],{"class":226},[118,26693,395],{"class":124},[118,26695,3999],{"class":226},[118,26697,230],{"class":124},[118,26699,26674],{"class":226},[118,26701,25],{"class":124},[118,26703,26704],{"class":213},"GetPage",[118,26706,255],{"class":124},[118,26708,7441],{"class":226},[118,26710,199],{"class":124},[118,26712,26713,26716,26718,26720,26722,26724],{"class":120,"line":537},[118,26714,26715],{"class":226},"    writer",[118,26717,25],{"class":124},[118,26719,1190],{"class":213},[118,26721,255],{"class":124},[118,26723,5910],{"class":226},[118,26725,199],{"class":124},[118,26727,26728],{"class":120,"line":566},[118,26729,1479],{"class":124},[118,26731,26732],{"class":120,"line":571},[118,26733,136],{"emptyLinePlaceholder":135},[118,26735,26736,26739,26741,26743,26745,26747,26749,26751,26753,26755,26758,26760],{"class":120,"line":577},[118,26737,26738],{"class":226},"f",[118,26740,395],{"class":124},[118,26742,3999],{"class":226},[118,26744,230],{"class":124},[118,26746,1447],{"class":226},[118,26748,25],{"class":124},[118,26750,4008],{"class":213},[118,26752,255],{"class":124},[118,26754,430],{"class":124},[118,26756,26757],{"class":433},"encrypted.pdf",[118,26759,430],{"class":124},[118,26761,199],{"class":124},[118,26763,26764,26766,26769,26771,26773],{"class":120,"line":602},[118,26765,4700],{"class":142},[118,26767,26768],{"class":226}," f",[118,26770,25],{"class":124},[118,26772,4031],{"class":213},[118,26774,1193],{"class":124},[118,26776,26777,26780,26782,26784,26786,26788],{"class":120,"line":633},[118,26778,26779],{"class":226},"writer",[118,26781,25],{"class":124},[118,26783,4042],{"class":213},[118,26785,255],{"class":124},[118,26787,26738],{"class":226},[118,26789,199],{"class":124},[14,26791,26792],{},[1629,26793,13844],{},[109,26795,26797],{"className":111,"code":26796,"language":113,"meta":114,"style":114},"doc := gpdf.NewDocument(\n    gpdf.WithPageSize(document.A4),\n    gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    gpdf.WithEncryption(\n        gpdf.AES256,\n        \"user-pwd\",\n        \"owner-pwd\",\n        gpdf.PermPrinting|gpdf.PermCopyContent,\n    ),\n)\n\npage := doc.AddPage()\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Text(\"Confidential.\")\n    })\n})\n\ndata, _ := doc.Generate()\nos.WriteFile(\"encrypted.pdf\", data, 0o644)\n",[18,26798,26799,26813,26831,26861,26872,26883,26894,26904,26924,26928,26932,26936,26950,26974,27004,27023,27027,27031,27035,27054],{"__ignoreMap":114},[118,26800,26801,26803,26805,26807,26809,26811],{"class":120,"line":121},[118,26802,2760],{"class":226},[118,26804,230],{"class":124},[118,26806,3595],{"class":226},[118,26808,25],{"class":124},[118,26810,3600],{"class":213},[118,26812,241],{"class":124},[118,26814,26815,26817,26819,26821,26823,26825,26827,26829],{"class":120,"line":132},[118,26816,4532],{"class":226},[118,26818,25],{"class":124},[118,26820,252],{"class":213},[118,26822,255],{"class":124},[118,26824,258],{"class":226},[118,26826,25],{"class":124},[118,26828,263],{"class":226},[118,26830,266],{"class":124},[118,26832,26833,26835,26837,26839,26841,26843,26845,26847,26849,26851,26853,26855,26857,26859],{"class":120,"line":139},[118,26834,4532],{"class":226},[118,26836,25],{"class":124},[118,26838,276],{"class":213},[118,26840,255],{"class":124},[118,26842,258],{"class":226},[118,26844,25],{"class":124},[118,26846,285],{"class":213},[118,26848,255],{"class":124},[118,26850,258],{"class":226},[118,26852,25],{"class":124},[118,26854,294],{"class":213},[118,26856,255],{"class":124},[118,26858,300],{"class":299},[118,26860,303],{"class":124},[118,26862,26863,26865,26867,26870],{"class":120,"line":149},[118,26864,4532],{"class":226},[118,26866,25],{"class":124},[118,26868,26869],{"class":213},"WithEncryption",[118,26871,241],{"class":124},[118,26873,26874,26876,26878,26881],{"class":120,"line":161},[118,26875,3607],{"class":226},[118,26877,25],{"class":124},[118,26879,26880],{"class":226},"AES256",[118,26882,2643],{"class":124},[118,26884,26885,26888,26890,26892],{"class":120,"line":166},[118,26886,26887],{"class":124},"        \"",[118,26889,26601],{"class":433},[118,26891,430],{"class":124},[118,26893,2643],{"class":124},[118,26895,26896,26898,26900,26902],{"class":120,"line":176},[118,26897,26887],{"class":124},[118,26899,26616],{"class":433},[118,26901,430],{"class":124},[118,26903,2643],{"class":124},[118,26905,26906,26908,26910,26912,26915,26917,26919,26922],{"class":120,"line":186},[118,26907,3607],{"class":226},[118,26909,25],{"class":124},[118,26911,26572],{"class":226},[118,26913,26914],{"class":124},"|",[118,26916,1587],{"class":226},[118,26918,25],{"class":124},[118,26920,26921],{"class":226},"PermCopyContent",[118,26923,2643],{"class":124},[118,26925,26926],{"class":120,"line":196},[118,26927,20558],{"class":124},[118,26929,26930],{"class":120,"line":202},[118,26931,199],{"class":124},[118,26933,26934],{"class":120,"line":207},[118,26935,136],{"emptyLinePlaceholder":135},[118,26937,26938,26940,26942,26944,26946,26948],{"class":120,"line":223},[118,26939,17539],{"class":226},[118,26941,230],{"class":124},[118,26943,1185],{"class":226},[118,26945,25],{"class":124},[118,26947,1190],{"class":213},[118,26949,1193],{"class":124},[118,26951,26952,26954,26956,26958,26960,26962,26964,26966,26968,26970,26972],{"class":120,"line":244},[118,26953,5910],{"class":226},[118,26955,25],{"class":124},[118,26957,358],{"class":213},[118,26959,328],{"class":124},[118,26961,363],{"class":331},[118,26963,334],{"class":124},[118,26965,337],{"class":128},[118,26967,25],{"class":124},[118,26969,372],{"class":128},[118,26971,345],{"class":124},[118,26973,220],{"class":124},[118,26975,26976,26978,26980,26982,26984,26986,26988,26990,26992,26994,26996,26998,27000,27002],{"class":120,"line":269},[118,26977,5935],{"class":226},[118,26979,25],{"class":124},[118,26981,387],{"class":213},[118,26983,255],{"class":124},[118,26985,20],{"class":299},[118,26987,395],{"class":124},[118,26989,398],{"class":124},[118,26991,401],{"class":331},[118,26993,334],{"class":124},[118,26995,337],{"class":128},[118,26997,25],{"class":124},[118,26999,410],{"class":128},[118,27001,345],{"class":124},[118,27003,220],{"class":124},[118,27005,27006,27008,27010,27012,27014,27016,27019,27021],{"class":120,"line":306},[118,27007,5966],{"class":226},[118,27009,25],{"class":124},[118,27011,425],{"class":213},[118,27013,255],{"class":124},[118,27015,430],{"class":124},[118,27017,27018],{"class":433},"Confidential.",[118,27020,430],{"class":124},[118,27022,199],{"class":124},[118,27024,27025],{"class":120,"line":312},[118,27026,706],{"class":124},[118,27028,27029],{"class":120,"line":317},[118,27030,1944],{"class":124},[118,27032,27033],{"class":120,"line":350},[118,27034,136],{"emptyLinePlaceholder":135},[118,27036,27037,27040,27042,27044,27046,27048,27050,27052],{"class":120,"line":379},[118,27038,27039],{"class":226},"data",[118,27041,395],{"class":124},[118,27043,3999],{"class":226},[118,27045,230],{"class":124},[118,27047,1185],{"class":226},[118,27049,25],{"class":124},[118,27051,1400],{"class":213},[118,27053,1193],{"class":124},[118,27055,27056,27058,27060,27062,27064,27066,27068,27070,27072,27074,27076,27078],{"class":120,"line":417},[118,27057,155],{"class":226},[118,27059,25],{"class":124},[118,27061,1452],{"class":213},[118,27063,255],{"class":124},[118,27065,430],{"class":124},[118,27067,26757],{"class":433},[118,27069,430],{"class":124},[118,27071,395],{"class":124},[118,27073,5859],{"class":226},[118,27075,395],{"class":124},[118,27077,1471],{"class":299},[118,27079,199],{"class":124},[14,27081,27082,27083,27086,27087,27090],{},"One option, AES-256 by default, no separate writer pass. The whole encryption path lives inside the MIT core — same module, same ",[18,27084,27085],{},"go get",". Same story for digital signing: ",[18,27088,27089],{},"gpdf.SignDocument(pdfBytes, signer, gpdf.WithTSA(\"http://timestamp.digicert.com\"))"," post-processes the bytes with a PKCS#7 + RFC 3161 timestamp, no extra package, no key registration.",[41,27092,27094],{"id":27093},"how-fast-is-the-result","How fast is the result?",[14,27096,27097,27098,27101],{},"Benchmarks from ",[18,27099,27100],{},"_benchmark/benchmark_test.go"," on an Apple M1 with Go 1.25. unipdf isn't in our suite directly because its license terms made distributing the comparison code awkward; the numbers below are what we collected on the same hardware against the same workloads.",[1516,27103,27104,27119],{},[1519,27105,27106],{},[1522,27107,27108,27110,27112,27115,27117],{},[1525,27109,17691],{},[1525,27111,1587],{},[1525,27113,27114],{},"unipdf*",[1525,27116,1539],{},[1525,27118,17700],{},[1532,27120,27121,27136,27151,27166],{},[1522,27122,27123,27125,27129,27132,27134],{},[1537,27124,17707],{},[1537,27126,27127],{},[1629,27128,3248],{},[1537,27130,27131],{},"~180 µs",[1537,27133,17717],{},[1537,27135,17720],{},[1522,27137,27138,27140,27144,27147,27149],{},[1537,27139,17725],{},[1537,27141,27142],{},[1629,27143,16053],{},[1537,27145,27146],{},"~8.6 ms",[1537,27148,17735],{},[1537,27150,17738],{},[1522,27152,27153,27155,27159,27162,27164],{},[1537,27154,17743],{},[1537,27156,27157],{},[1629,27158,4131],{},[1537,27160,27161],{},"~95 ms",[1537,27163,17752],{},[1537,27165,17755],{},[1522,27167,27168,27170,27174,27177,27179],{},[1537,27169,17760],{},[1537,27171,27172],{},[1629,27173,17765],{},[1537,27175,27176],{},"~12 ms",[1537,27178,17771],{},[1537,27180,17774],{},[14,27182,27183],{},"* unipdf numbers are from a separate run on the same Apple M1 / Go 1.25, captured by us against unipdf v3 latest at time of writing. Treat them as approximate; they aren't part of our committed suite.",[14,27185,27186],{},"The shape is the same as the gofpdf comparison: gpdf is roughly 10–80× faster across the workloads people actually run. At 108 µs per table-rich page, a single core can produce ~9,000 invoices per second. The point isn't bragging rights — it's that you can stop thinking about whether to cache or async-queue PDF generation. Generating on the request path is fine for nearly everything.",[41,27188,27190],{"id":27189},"what-about-the-parts-gpdf-doesnt-have","What about the parts gpdf doesn't have?",[14,27192,27193],{},"If your unipdf bill is paying for OCR, redaction, or PDF parsing, this migration won't carry you all the way. The honest options:",[46,27195,27196,27208,27220,27226],{},[49,27197,27198,27201,27202,27207],{},[1629,27199,27200],{},"OCR."," gpdf doesn't do OCR and is unlikely to. Use ",[3163,27203,27206],{"href":27204,"rel":27205},"https://github.com/tesseract-ocr/tesseract",[3167],"Tesseract"," via gosseract, or a hosted OCR API. Generation stays on gpdf, parsing stays on whatever you pick.",[49,27209,27210,27213,27214,27219],{},[1629,27211,27212],{},"PDF parsing / text extraction."," gpdf is generation-only by design. For read-side workloads, ",[3163,27215,27218],{"href":27216,"rel":27217},"https://github.com/pdfcpu/pdfcpu",[3167],"pdfcpu"," handles a lot of common cases (apache 2.0). Keep unipdf for parsing only and you may be able to reduce your seat count.",[49,27221,27222,27225],{},[1629,27223,27224],{},"AcroForm field authoring."," gpdf can flatten existing AcroForm fields; it can't yet author new ones. If you produce fillable forms for users to complete in a viewer, this is the gap you'll feel. A tracked roadmap item.",[49,27227,27228,27231],{},[1629,27229,27230],{},"Redaction."," Not on the gpdf roadmap. Redaction needs a real renderer to know what to black out, which is a different architecture than generation.",[14,27233,27234,27235,27237],{},"For the ",[1629,27236,22322],{}," path — what most unipdf bills actually go to — the swap is complete.",[41,27239,3054],{"id":3053},[14,27241,27242,27245],{},[1629,27243,27244],{},"Is gpdf a fork of unipdf?","\nNo. gpdf is a clean reimplementation in pure Go. Wire format, layout engine, TrueType subsetter, AES, PKCS#7 — all written from scratch. There's no shared code, no shared lineage, and no possibility of a license-clean argument going wrong because nothing was copied.",[14,27247,27248,27251,27252,27257],{},[1629,27249,27250],{},"Is gpdf really MIT? No \"AGPL upon some condition\"?","\nYes. The repository ",[3163,27253,27256],{"href":27254,"rel":27255},"https://github.com/gpdf-dev/gpdf/blob/main/LICENSE",[3167],"LICENSE"," is the MIT license verbatim, no addenda, no field-of-use clauses, no commercial-tier carveouts. Use it in closed-source distributable products, embed it in commercial SaaS, ship it inside on-prem appliances. The only obligation is the license-and-copyright notice in your distribution.",[14,27259,27260,27263,27264,4919,27267,27270,27271,27274,27275,25],{},[1629,27261,27262],{},"What about transitive dependencies — is anything copyleft hiding underneath?","\nThe gpdf core's ",[18,27265,27266],{},"go.mod",[18,27268,27269],{},"require"," block is empty. No transitive AGPL, no transitive GPL, no transitive anything. You can verify with ",[18,27272,27273],{},"go mod graph | grep gpdf"," after ",[18,27276,27085],{},[14,27278,27279,27282],{},[1629,27280,27281],{},"Does removing the license key really matter that much?","\nFor some teams it's the whole game. The license key has to live in your secret manager, get rotated, get audited, get included in every container image, and not leak in logs. For a multi-tenant SaaS with hundreds of pods, that's a real operational surface. Deleting the requirement removes a class of incidents.",[14,27284,27285,27292,27293,27295,27296,27299],{},[1629,27286,27287,27288,27291],{},"My existing unipdf code uses absolute positioning via ",[18,27289,27290],{},"creator.Block.SetPos",". Does gpdf have an equivalent?","\nYes — ",[18,27294,17839],{}," lets you drop a sub-tree at an explicit coordinate. But if your code is mostly absolute positioning, the layout-engine model is a mental shift, not a syntactic one. Read the ",[3163,27297,27298],{"href":6901},"12-column grid post"," before estimating; rewritten code is usually shorter than the original.",[14,27301,27302,27305],{},[1629,27303,27304],{},"What if UniDoc relicenses unipdf to MIT one day?","\nThen you have one more option. The bet behind gpdf isn't that unipdf will stay AGPL forever; it's that a license that requires a registration call at startup, and a per-developer renewal at the finance level, is a tax that doesn't have to exist for most workloads. Even if unipdf relicensed tomorrow, the operational surface of the license key would still be there until they removed it.",[41,27307,4794],{"id":4793},[14,27309,27310],{},"gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, no license key, native CJK support.",[109,27312,27313],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,27314,27315],{"__ignoreMap":114},[118,27316,27317,27319,27321],{"class":120,"line":121},[118,27318,113],{"class":128},[118,27320,3156],{"class":433},[118,27322,3159],{"class":433},[14,27324,27325,3169,27328],{},[3163,27326,3168],{"href":3165,"rel":27327},[3167],[3163,27329,3174],{"href":3172,"rel":27330},[3167],[41,27332,4821],{"id":4820},[46,27334,27335,27339,27343,27347],{},[49,27336,27337],{},[3163,27338,9792],{"href":9791},[49,27340,27341],{},[3163,27342,18008],{"href":6901},[49,27344,27345],{},[3163,27346,18018],{"href":4839},[49,27348,27349,27353,27354],{},[3163,27350,27352],{"href":3172,"rel":27351},[3167],"Quickstart"," — five-minute setup, including ",[18,27355,27266],{},[3176,27357,27358],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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 .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 .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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":114,"searchDepth":132,"depth":132,"links":27360},[27361,27362,27363,27364,27365,27366,27367,27368,27369,27370,27371,27372,27373,27374,27375],{"id":43,"depth":132,"text":44},{"id":22155,"depth":132,"text":22156},{"id":22188,"depth":132,"text":22189},{"id":22326,"depth":132,"text":22327},{"id":13068,"depth":132,"text":13069},{"id":22890,"depth":132,"text":22891},{"id":23632,"depth":132,"text":23633},{"id":24636,"depth":132,"text":24637},{"id":25372,"depth":132,"text":25373},{"id":26324,"depth":132,"text":26325},{"id":27093,"depth":132,"text":27094},{"id":27189,"depth":132,"text":27190},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"2026-04-29","UniDoc's unipdf forces AGPL v3 or a per-developer commercial license. This guide maps the unipdf creator API to gpdf — MIT, zero deps, no license key.",{"name":27379,"totalTime":27380,"tools":27381,"steps":27382},"Migrate a Go project from unidoc/unipdf to gpdf","PT45M",[3202],[27383,27386,27389,27392,27395,27398,27401],{"name":27384,"text":27385},"Delete the license registration code","Remove the unipdf license.SetMeteredKey or license.SetLicenseKey call from main and any init() blocks. gpdf has no license key, no metering API, and no startup registration.",{"name":27387,"text":27388},"Replace the import paths","Swap github.com/unidoc/unipdf/v3/creator and github.com/unidoc/unipdf/v3/model for github.com/gpdf-dev/gpdf, github.com/gpdf-dev/gpdf/document, and github.com/gpdf-dev/gpdf/template.",{"name":27390,"text":27391},"Replace creator.New with gpdf.NewDocument","Construct the document with gpdf.NewDocument(WithPageSize(document.A4), WithMargins(...)). Pages come from doc.AddPage() and return a PageBuilder, not a free cursor.",{"name":27393,"text":27394},"Convert creator.NewParagraph and creator.NewStyledParagraph to c.Text","Inside a column, call c.Text(string, options...) instead of building a Paragraph and calling c.Draw on it. Font, size, and color move from struct fields onto per-text options.",{"name":27396,"text":27397},"Rewrite tables with the 12-column grid","Replace creator.NewTable(cols).SetColumnWidths and SetCellSpan with row.Col(span, fn) inside an AutoRow. The 12-column grid handles widths as percentages and breaks tables across pages automatically.",{"name":27399,"text":27400},"Re-register fonts as bytes, not file paths","Replace model.NewCompositePdfFontFromTTFFile with gpdf.WithFont(name, ttfBytes) at construction. Embed the TTF via //go:embed so the binary stops needing a font path at runtime.",{"name":27402,"text":27403},"Switch the output call","Replace c.WriteToFile(path) with doc.Generate() plus os.WriteFile(path, data, 0o644), or doc.Render(w) to stream straight into an io.Writer.",{},{"title":18024,"description":27377},"blog/016.unidoc-migration",[4868,4866,3226],"h0EasuCrRUnpBwnJJZI5t3XcUhEE1ANtVwQDYAEbEcw",{"id":27410,"title":12891,"author":27411,"body":27412,"date":27376,"description":29309,"draft":3196,"extension":3197,"howTo":3220,"image":3220,"meta":29310,"navigation":135,"path":12890,"seo":29311,"stem":29312,"tags":29313,"updated":3220,"__hash__":29314},"blog/blog/017.bootstrap-grid-thinking-for-pdf.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":27413,"toc":29286},[27414,27416,27422,27429,27431,27441,27452,27459,27473,27477,27480,27504,27511,27514,27518,27521,27553,27562,27576,27579,27583,27586,27609,27612,27616,27620,27631,27654,27658,27661,27671,27675,27686,27690,27697,27706,27788,27791,27795,27802,27939,27964,27970,27974,27990,27994,28135,28152,28156,28178,28191,28198,28200,28203,29116,29126,29130,29133,29158,29161,29165,29174,29192,29201,29214,29224,29228,29235,29237,29240,29252,29260,29264,29283],[41,27415,44],{"id":43},[14,27417,27418,27421],{},[1629,27419,27420],{},"gpdf uses the 12-column grid from Bootstrap. Twelve, because it divides cleanly into 1, 2, 3, 4, 6 — the splits you actually want. We kept the integer span model and threw out everything else: no breakpoints, no gutters, no order, no auto-fill."," A page is a stack of rows; each row is one horizontal box; columns inside it are widths in twelfths.",[14,27423,27424,27425,27428],{},"That's the whole grid. The implementation is around 30 lines of Go. The interesting part is what we ",[4744,27426,27427],{},"didn't"," port.",[41,27430,10015],{"id":10014},[14,27432,27433,27434,27437,27438,27440],{},"gpdf is a Go library for generating PDFs. The high-level layout API is a builder: ",[18,27435,27436],{},"page.AutoRow → r.Col(span, fn) → c.Text/Image/Table",". New users see ",[18,27439,13053],{}," and ask three questions:",[1624,27442,27443,27446,27449],{},[49,27444,27445],{},"Why 12? Why not 16, 24, or \"as many as you want\"?",[49,27447,27448],{},"Is this CSS Grid? Bootstrap? Something else?",[49,27450,27451],{},"What happens if my spans don't add up to 12?",[14,27453,27454,27455,27458],{},"This post answers those by walking through the design choices behind the API. They mostly come down to one principle: ",[1629,27456,27457],{},"PDF rendering needs to be predictable, not adaptive."," A web page reflows on resize. A PDF doesn't. That single difference removes most of what makes web grid systems hard, and lets us ship a much smaller idea.",[14,27460,27461,27462,27465,27466,27468,27469,27472],{},"If you only want to know ",[4744,27463,27464],{},"how"," to use the grid, the recipe at ",[3163,27467,6901],{"href":6901}," is more direct. This post is about ",[4744,27470,27471],{},"why"," it looks the way it does.",[41,27474,27476],{"id":27475},"the-three-options-for-laying-out-a-pdf","The three options for laying out a PDF",[14,27478,27479],{},"When we started the high-level API, we had three real choices:",[1624,27481,27482,27488,27494],{},[49,27483,27484,27487],{},[1629,27485,27486],{},"Absolute positioning."," \"Draw text at (72, 540) in points.\" This is what most low-level Go PDF libraries give you. Maximum power, terrible ergonomics. You compute every coordinate yourself.",[49,27489,27490,27493],{},[1629,27491,27492],{},"Flow + flexbox."," Stack content top-to-bottom; rows distribute children horizontally with grow/shrink ratios. Powerful, but the layout pass is non-trivial — you need a constraint solver, and rounding errors compound.",[49,27495,27496,27499,27500,27503],{},[1629,27497,27498],{},"Fixed-grid + ratios."," A page is a stack of rows. A row is divided into N equal slots. Each column claims some integer number of slots. Width = ",[18,27501,27502],{},"slots / N * row_width",". No constraint solver. No grow/shrink.",[14,27505,27506,27507,27510],{},"We picked option 3. Bootstrap got there over a decade ago for the same reason: ",[1629,27508,27509],{},"most layouts you actually need are integer-fraction layouts."," Two columns of equal width. A 1/3 + 2/3 split. Four cards across. A 25-50-25 row. None of those need a constraint solver.",[14,27512,27513],{},"The remaining question was: how many slots?",[41,27515,27517],{"id":27516},"why-12","Why 12",[14,27519,27520],{},"12 isn't magic, but it isn't arbitrary either. Think about which integer divisions you ever actually want in a document:",[46,27522,27523,27529,27535,27541,27547],{},[49,27524,27525,27528],{},[1629,27526,27527],{},"2 columns"," — left/right halves",[49,27530,27531,27534],{},[1629,27532,27533],{},"3 columns"," — thirds (gallery, three-card row)",[49,27536,27537,27540],{},[1629,27538,27539],{},"4 columns"," — quarters (KPI strip)",[49,27542,27543,27546],{},[1629,27544,27545],{},"6 columns"," — sixths (rare, but useful for narrow side panels)",[49,27548,27549,27552],{},[1629,27550,27551],{},"12 columns"," — twelfths (rare; thin separators)",[14,27554,27555,27556,27558,27559,27561],{},"Notice the divisors of 12: 1, 2, 3, 4, 6, 12. That's ",[4744,27557,8369],{}," useful integer split up to a sixth. 10 doesn't give you thirds. 16 doesn't give you thirds either. 24 gives you everything but doubles the cognitive load — you'd write ",[18,27560,13049],{}," and have to remember whether that means a third (24/3) or two-thirds (8/12). 12 is the smallest number that covers the splits people actually use.",[14,27563,27564,27565,27568,27569,27571,27572,27575],{},"Bootstrap landed on 12 in 2011 for exactly this reason. CSS Grid later went further and let you write ",[18,27566,27567],{},"1fr 2fr 1fr"," directly, removing the magic number. But fractions are not a free lunch — they push more work onto whoever reads your layout. ",[18,27570,13053],{}," is concretely \"one third of the row.\" ",[18,27573,27574],{},"r.Col(2fr, ...)"," requires you to look at every sibling before you know what it means.",[14,27577,27578],{},"For PDFs, where layouts are stable and inspected by hand, the integer model wins.",[41,27580,27582],{"id":27581},"what-we-kept-from-bootstrap","What we kept from Bootstrap",[14,27584,27585],{},"Three things, and only three:",[1624,27587,27588,27594,27603],{},[49,27589,27590,27593],{},[1629,27591,27592],{},"Twelve."," The denominator. The only number on the dial.",[49,27595,27596,27599,27600,27602],{},[1629,27597,27598],{},"Span as an integer 1–12."," Not a fraction, not a CSS unit. ",[18,27601,13053],{}," claims four-twelfths of the row.",[49,27604,27605,27608],{},[1629,27606,27607],{},"The mental model."," A page is a stack of rows. A row is divided into columns. Same shape as the grid you've been writing in HTML for ten years.",[14,27610,27611],{},"That's it. Now the actually interesting part.",[41,27613,27615],{"id":27614},"what-we-threw-out","What we threw out",[1613,27617,27619],{"id":27618},"breakpoints","Breakpoints",[14,27621,27622,27623,27626,27627,27630],{},"Bootstrap's ",[18,27624,27625],{},"col-md-6 col-lg-4"," makes a column claim half the row on tablets and a third on desktops. Useful on the web. ",[1629,27628,27629],{},"Meaningless in a PDF."," A PDF page is a fixed canvas. There's no viewport to query, no resize event, no media query. We deleted breakpoints entirely.",[14,27632,27633,27634,3096,27637,3096,27640,3096,27643,3096,27646,27649,27650,27653],{},"The savings are larger than they look. Breakpoints are the reason CSS frameworks ship ",[18,27635,27636],{},"col-xs-*",[18,27638,27639],{},"col-sm-*",[18,27641,27642],{},"col-md-*",[18,27644,27645],{},"col-lg-*",[18,27647,27648],{},"col-xl-*"," variants — five copies of the same column class. None of them exist in gpdf. The API is ",[18,27651,27652],{},"r.Col(span int, fn func(*ColBuilder))",". One signature. One mental slot.",[1613,27655,27657],{"id":27656},"gutters","Gutters",[14,27659,27660],{},"Bootstrap rows have horizontal padding between columns by default. PDFs don't need a default, because the right margin between columns depends entirely on what you're rendering — a tightly packed table has no gutter, a hero section has 24pt of breathing room, an invoice line might want 0.5pt for a separator. We chose to make spacing explicit.",[14,27662,27663,27664,27667,27668],{},"If you want a gutter, you put it in: drop a ",[18,27665,27666],{},"c.Spacer(...)"," between columns, or wrap the inner content in a Box with padding. The grid itself never inserts pixels you didn't ask for. ",[1629,27669,27670],{},"No gutter is the right default for a print medium where every point counts.",[1613,27672,27674],{"id":27673},"order","Order",[14,27676,27677,27678,27681,27682,27685],{},"CSS lets you reorder columns visually with ",[18,27679,27680],{},"order: 2",". Useful for responsive design, where the same DOM should produce a different visual order on small screens. ",[1629,27683,27684],{},"Useless for PDFs."," The order columns appear in the file is the order they appear on the page. We never even considered adding it.",[1613,27687,27689],{"id":27688},"auto-fill-auto-fit","Auto-fill / auto-fit",[14,27691,27692,27693,27696],{},"CSS Grid has ",[18,27694,27695],{},"repeat(auto-fit, minmax(200px, 1fr))"," — fill the row with as many 200px-minimum columns as fit. Beautiful for image galleries on the web. For a PDF, you know the page width at build time. You don't need the layout engine to figure it out.",[14,27698,27699,27700,27702,27703,27705],{},"If you want a 4-card row, write ",[18,27701,13061],{}," four times. If you want a 6-card row, write ",[18,27704,14977],{}," six times. The \"auto\" version is one for-loop in your own code:",[109,27707,27709],{"className":111,"code":27708,"language":113,"meta":114,"style":114},"for _, item := range items {\n    r.Col(3, func(c *template.ColBuilder) {\n        c.Text(item.Name)\n    })\n}\n",[18,27710,27711,27730,27760,27780,27784],{"__ignoreMap":114},[118,27712,27713,27715,27717,27719,27722,27724,27726,27728],{"class":120,"line":121},[118,27714,14495],{"class":142},[118,27716,14776],{"class":226},[118,27718,395],{"class":124},[118,27720,27721],{"class":226}," item ",[118,27723,230],{"class":124},[118,27725,1124],{"class":142},[118,27727,15551],{"class":226},[118,27729,7406],{"class":124},[118,27731,27732,27734,27736,27738,27740,27742,27744,27746,27748,27750,27752,27754,27756,27758],{"class":120,"line":132},[118,27733,5935],{"class":226},[118,27735,25],{"class":124},[118,27737,387],{"class":213},[118,27739,255],{"class":124},[118,27741,688],{"class":299},[118,27743,395],{"class":124},[118,27745,398],{"class":124},[118,27747,401],{"class":331},[118,27749,334],{"class":124},[118,27751,337],{"class":128},[118,27753,25],{"class":124},[118,27755,410],{"class":128},[118,27757,345],{"class":124},[118,27759,220],{"class":124},[118,27761,27762,27764,27766,27768,27770,27773,27775,27778],{"class":120,"line":139},[118,27763,5966],{"class":226},[118,27765,25],{"class":124},[118,27767,425],{"class":213},[118,27769,255],{"class":124},[118,27771,27772],{"class":226},"item",[118,27774,25],{"class":124},[118,27776,27777],{"class":226},"Name",[118,27779,199],{"class":124},[118,27781,27782],{"class":120,"line":149},[118,27783,706],{"class":124},[118,27785,27786],{"class":120,"line":161},[118,27787,1479],{"class":124},[14,27789,27790],{},"Three lines. We didn't need to bake them into the framework.",[1613,27792,27794],{"id":27793},"span-sum-enforcement","Span-sum enforcement",[14,27796,27797,27798,27801],{},"Here's the surprising one: ",[1629,27799,27800],{},"gpdf does not require column spans to sum to 12."," This is on purpose.",[109,27803,27805],{"className":111,"code":27804,"language":113,"meta":114,"style":114},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"Left third\") })\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"Middle third\") })\n    // sum = 8. The right third is just empty.\n})\n",[18,27806,27807,27831,27881,27930,27935],{"__ignoreMap":114},[118,27808,27809,27811,27813,27815,27817,27819,27821,27823,27825,27827,27829],{"class":120,"line":121},[118,27810,5910],{"class":226},[118,27812,25],{"class":124},[118,27814,358],{"class":213},[118,27816,328],{"class":124},[118,27818,363],{"class":331},[118,27820,334],{"class":124},[118,27822,337],{"class":128},[118,27824,25],{"class":124},[118,27826,372],{"class":128},[118,27828,345],{"class":124},[118,27830,220],{"class":124},[118,27832,27833,27835,27837,27839,27841,27843,27845,27847,27849,27851,27853,27855,27857,27859,27861,27863,27865,27867,27869,27871,27874,27876,27878],{"class":120,"line":132},[118,27834,5935],{"class":226},[118,27836,25],{"class":124},[118,27838,387],{"class":213},[118,27840,255],{"class":124},[118,27842,1493],{"class":299},[118,27844,395],{"class":124},[118,27846,398],{"class":124},[118,27848,401],{"class":331},[118,27850,334],{"class":124},[118,27852,337],{"class":128},[118,27854,25],{"class":124},[118,27856,410],{"class":128},[118,27858,345],{"class":124},[118,27860,8081],{"class":124},[118,27862,23100],{"class":226},[118,27864,25],{"class":124},[118,27866,425],{"class":213},[118,27868,255],{"class":124},[118,27870,430],{"class":124},[118,27872,27873],{"class":433},"Left third",[118,27875,430],{"class":124},[118,27877,345],{"class":124},[118,27879,27880],{"class":124}," })\n",[118,27882,27883,27885,27887,27889,27891,27893,27895,27897,27899,27901,27903,27905,27907,27909,27911,27913,27915,27917,27919,27921,27924,27926,27928],{"class":120,"line":139},[118,27884,5935],{"class":226},[118,27886,25],{"class":124},[118,27888,387],{"class":213},[118,27890,255],{"class":124},[118,27892,1493],{"class":299},[118,27894,395],{"class":124},[118,27896,398],{"class":124},[118,27898,401],{"class":331},[118,27900,334],{"class":124},[118,27902,337],{"class":128},[118,27904,25],{"class":124},[118,27906,410],{"class":128},[118,27908,345],{"class":124},[118,27910,8081],{"class":124},[118,27912,23100],{"class":226},[118,27914,25],{"class":124},[118,27916,425],{"class":213},[118,27918,255],{"class":124},[118,27920,430],{"class":124},[118,27922,27923],{"class":433},"Middle third",[118,27925,430],{"class":124},[118,27927,345],{"class":124},[118,27929,27880],{"class":124},[118,27931,27932],{"class":120,"line":149},[118,27933,27934],{"class":3981},"    // sum = 8. The right third is just empty.\n",[118,27936,27937],{"class":120,"line":161},[118,27938,1944],{"class":124},[14,27940,27941,27942,27945,27946,27949,27950,54,27953,27949,27956,27959,27960,27963],{},"The library treats each column as ",[18,27943,27944],{},"span/12 * row_width",", period. If you put 4 + 4 in a row, the third \"slot\" of width is empty. If you put 7 + 8, the second column overflows past the row boundary — also intentional, because sometimes you want overflow (e.g., aligning to a layout grid that's wider than the page itself). Spans clamp to 1–12 (so ",[18,27947,27948],{},"Col(0, ...)"," becomes ",[18,27951,27952],{},"Col(1, ...)",[18,27954,27955],{},"Col(99, ...)",[18,27957,27958],{},"Col(12, ...)",", see ",[18,27961,27962],{},"gpdf/template/grid.go:120","), but no auto-wrapping, no auto-balancing.",[14,27965,27966,27967],{},"Bootstrap's old \"wrap into next row when columns sum past 12\" behaviour solved a real responsive problem. PDFs don't have that problem. We replaced it with a simpler contract: ",[1629,27968,27969],{},"what you wrote is what you get.",[1613,27971,27973],{"id":27972},"containers-fluid-mode-no-gutters-mode-offsets-pushpull","Containers, fluid mode, no-gutters mode, offsets, push/pull",[14,27975,27976,27977,3096,27980,3096,27983,27986,27987,27989],{},"None of it. We don't ship ",[18,27978,27979],{},"container-fluid",[18,27981,27982],{},"col-md-offset-3",[18,27984,27985],{},"col-md-push-2",", or any other Bootstrap utility-class equivalent. If you want to push a column right, wrap it: put an ",[18,27988,13061],{}," of empty content before it. Eight more characters, zero new concepts.",[41,27991,27993],{"id":27992},"gpdf-vs-bootstrap-vs-css-grid","gpdf vs Bootstrap vs CSS Grid",[1516,27995,27996,28012],{},[1519,27997,27998],{},[1522,27999,28000,28003,28006,28009],{},[1525,28001,28002],{},"Feature",[1525,28004,28005],{},"Bootstrap (CSS)",[1525,28007,28008],{},"CSS Grid (CSS)",[1525,28010,28011],{},"gpdf (Go)",[1532,28013,28014,28029,28046,28059,28075,28092,28105,28119],{},[1522,28015,28016,28019,28021,28027],{},[1537,28017,28018],{},"Grid size",[1537,28020,27551],{},[1537,28022,28023,28024,345],{},"Arbitrary (",[18,28025,28026],{},"grid-template-columns",[1537,28028,27551],{},[1522,28030,28031,28033,28036,28043],{},[1537,28032,14478],{},[1537,28034,28035],{},"Class names",[1537,28037,28038,28039,28042],{},"Fractions (",[18,28040,28041],{},"fr","), pixels, %",[1537,28044,28045],{},"Integer span 1–12",[1522,28047,28048,28050,28053,28056],{},[1537,28049,27619],{},[1537,28051,28052],{},"5 (xs/sm/md/lg/xl)",[1537,28054,28055],{},"Via media queries",[1537,28057,28058],{},"None",[1522,28060,28061,28064,28071,28073],{},[1537,28062,28063],{},"Default gutter",[1537,28065,28066,28067,28070],{},"Yes (",[18,28068,28069],{},"gx-*"," controls it)",[1537,28072,28058],{},[1537,28074,28058],{},[1522,28076,28077,28080,28085,28090],{},[1537,28078,28079],{},"Visual reorder",[1537,28081,28082],{},[18,28083,28084],{},"order-*",[1537,28086,28087,28089],{},[18,28088,27673],{}," property",[1537,28091,28058],{},[1522,28093,28094,28097,28100,28103],{},[1537,28095,28096],{},"Auto-fill",[1537,28098,28099],{},"No",[1537,28101,28102],{},"Yes",[1537,28104,28099],{},[1522,28106,28107,28110,28113,28116],{},[1537,28108,28109],{},"Wrap when sum > 12",[1537,28111,28112],{},"Yes (legacy) / No (flex)",[1537,28114,28115],{},"N/A",[1537,28117,28118],{},"No (overflow allowed)",[1522,28120,28121,28124,28127,28130],{},[1537,28122,28123],{},"Implementation size",[1537,28125,28126],{},"~3,000 LoC SCSS",[1537,28128,28129],{},"Inside the browser",[1537,28131,28132],{},[1629,28133,28134],{},"~30 LoC Go",[14,28136,28137,28138,28140,28141,28144,28145,28147,28148,28151],{},"The \"30 LoC\" number is real. Open ",[18,28139,4962],{}," and count: a constant (",[18,28142,28143],{},"gridColumns = 12","), a builder method that clamps integers, and a build pass that emits one ",[18,28146,4932],{}," per row with horizontal direction and ",[18,28149,28150],{},"Pct(span/12*100)"," widths per child. There's no measurement pass, no flex algorithm, no rebalancing. The width arithmetic is the algorithm.",[41,28153,28155],{"id":28154},"how-gpdf-renders-this-internally","How gpdf renders this internally",[14,28157,28158,28159,28162,28163,28166,28167,2527,28170,28173,28174,28177],{},"When you call ",[18,28160,28161],{},"r.Col(4, fn)",", gpdf appends a ",[18,28164,28165],{},"colEntry{span: 4, fn: fn}"," to the row. When the document builds, each entry becomes a ",[18,28168,28169],{},"document.Box",[18,28171,28172],{},"Width: document.Pct(33.333…)"," and the column's content nested inside. The row itself is a Box with ",[18,28175,28176],{},"Direction: DirectionHorizontal",". The PDF writer (Layer 1) walks Boxes in document order and emits content streams; the layout engine (Layer 2) does width and height resolution; the grid (Layer 3) does the integer-to-percentage conversion.",[14,28179,28180,28181,28184,28185,28187,28188,28190],{},"The reason this stays at 30 lines is that ",[1629,28182,28183],{},"percentages and integers compose"," without rounding errors at the layout boundary. A column inside a column inside a column still ends up as a stack of ",[18,28186,6616],{}," multiplies, all done in ",[18,28189,6621],{},". The error budget is well below a typographic point even for deeply nested layouts.",[14,28192,28193,28194,28197],{},"If you want to see the chain end-to-end, ",[3163,28195,28196],{"href":4069},"why gpdf is 10× faster than alternatives"," covers the rendering pipeline. The grid is one of the cheapest layers in it — single-page render time on an M1 is around 13 µs, and the grid contributes a few hundred nanoseconds of that.",[41,28199,18227],{"id":18226},[14,28201,28202],{},"Two-column header (4/8 split), then a full-width table row, then a 3/3/3/3 KPI strip:",[109,28204,28206],{"className":111,"code":28205,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := template.NewDocument(document.PageSize(document.A4))\n\n    doc.Page(func(p *template.PageBuilder) {\n        // 4/8 split: logo block on the left, address on the right.\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(4, func(c *template.ColBuilder) {\n                c.Text(\"ACME, Inc.\", template.FontSize(18), template.Bold())\n            })\n            r.Col(8, func(c *template.ColBuilder) {\n                c.Text(\"123 Industrial Way\", template.AlignRight())\n                c.Text(\"Tokyo, Japan 100-0001\", template.AlignRight())\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // Full-width row (one 12-span column) for a table.\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Table([]string{\"Item\", \"Qty\", \"Price\"}, [][]string{\n                    {\"Widget A\", \"2\", \"¥1,000\"},\n                    {\"Widget B\", \"1\", \"¥2,500\"},\n                })\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // KPI strip: four equal columns of 3-span each.\n        kpis := []struct{ label, value string }{\n            {\"Subtotal\", \"¥4,500\"},\n            {\"Tax (10%)\", \"¥450\"},\n            {\"Shipping\", \"¥0\"},\n            {\"Total\", \"¥4,950\"},\n        }\n        p.AutoRow(func(r *template.RowBuilder) {\n            for _, k := range kpis {\n                k := k\n                r.Col(3, func(c *template.ColBuilder) {\n                    c.Text(k.label, template.FontSize(8))\n                    c.Text(k.value, template.FontSize(14), template.Bold())\n                })\n            }\n        })\n    })\n\n    f, _ := os.Create(\"invoice.pdf\")\n    defer f.Close()\n    doc.Render(f)\n}\n",[18,28207,28208,28214,28218,28224,28232,28236,28244,28252,28256,28260,28270,28300,28304,28328,28333,28357,28387,28426,28430,28460,28487,28514,28518,28522,28526,28548,28552,28557,28581,28611,28657,28687,28717,28722,28726,28730,28734,28756,28760,28765,28788,28811,28833,28855,28876,28880,28904,28925,28935,28966,28999,29038,29042,29046,29050,29054,29058,29085,29097,29112],{"__ignoreMap":114},[118,28209,28210,28212],{"class":120,"line":121},[118,28211,125],{"class":124},[118,28213,129],{"class":128},[118,28215,28216],{"class":120,"line":132},[118,28217,136],{"emptyLinePlaceholder":135},[118,28219,28220,28222],{"class":120,"line":139},[118,28221,143],{"class":142},[118,28223,146],{"class":124},[118,28225,28226,28228,28230],{"class":120,"line":149},[118,28227,152],{"class":124},[118,28229,155],{"class":128},[118,28231,158],{"class":124},[118,28233,28234],{"class":120,"line":161},[118,28235,136],{"emptyLinePlaceholder":135},[118,28237,28238,28240,28242],{"class":120,"line":166},[118,28239,152],{"class":124},[118,28241,171],{"class":128},[118,28243,158],{"class":124},[118,28245,28246,28248,28250],{"class":120,"line":176},[118,28247,152],{"class":124},[118,28249,191],{"class":128},[118,28251,158],{"class":124},[118,28253,28254],{"class":120,"line":186},[118,28255,199],{"class":124},[118,28257,28258],{"class":120,"line":196},[118,28259,136],{"emptyLinePlaceholder":135},[118,28261,28262,28264,28266,28268],{"class":120,"line":202},[118,28263,210],{"class":124},[118,28265,214],{"class":213},[118,28267,217],{"class":124},[118,28269,220],{"class":124},[118,28271,28272,28274,28276,28278,28280,28282,28284,28286,28288,28290,28292,28294,28296,28298],{"class":120,"line":207},[118,28273,227],{"class":226},[118,28275,230],{"class":124},[118,28277,233],{"class":226},[118,28279,25],{"class":124},[118,28281,3600],{"class":213},[118,28283,255],{"class":124},[118,28285,258],{"class":226},[118,28287,25],{"class":124},[118,28289,13544],{"class":213},[118,28291,255],{"class":124},[118,28293,258],{"class":226},[118,28295,25],{"class":124},[118,28297,263],{"class":226},[118,28299,463],{"class":124},[118,28301,28302],{"class":120,"line":223},[118,28303,136],{"emptyLinePlaceholder":135},[118,28305,28306,28308,28310,28312,28314,28316,28318,28320,28322,28324,28326],{"class":120,"line":244},[118,28307,320],{"class":226},[118,28309,25],{"class":124},[118,28311,2087],{"class":213},[118,28313,328],{"class":124},[118,28315,14],{"class":331},[118,28317,334],{"class":124},[118,28319,337],{"class":128},[118,28321,25],{"class":124},[118,28323,342],{"class":128},[118,28325,345],{"class":124},[118,28327,220],{"class":124},[118,28329,28330],{"class":120,"line":269},[118,28331,28332],{"class":3981},"        // 4/8 split: logo block on the left, address on the right.\n",[118,28334,28335,28337,28339,28341,28343,28345,28347,28349,28351,28353,28355],{"class":120,"line":306},[118,28336,353],{"class":226},[118,28338,25],{"class":124},[118,28340,358],{"class":213},[118,28342,328],{"class":124},[118,28344,363],{"class":331},[118,28346,334],{"class":124},[118,28348,337],{"class":128},[118,28350,25],{"class":124},[118,28352,372],{"class":128},[118,28354,345],{"class":124},[118,28356,220],{"class":124},[118,28358,28359,28361,28363,28365,28367,28369,28371,28373,28375,28377,28379,28381,28383,28385],{"class":120,"line":312},[118,28360,382],{"class":226},[118,28362,25],{"class":124},[118,28364,387],{"class":213},[118,28366,255],{"class":124},[118,28368,1493],{"class":299},[118,28370,395],{"class":124},[118,28372,398],{"class":124},[118,28374,401],{"class":331},[118,28376,334],{"class":124},[118,28378,337],{"class":128},[118,28380,25],{"class":124},[118,28382,410],{"class":128},[118,28384,345],{"class":124},[118,28386,220],{"class":124},[118,28388,28389,28391,28393,28395,28397,28399,28402,28404,28406,28408,28410,28412,28414,28416,28418,28420,28422,28424],{"class":120,"line":317},[118,28390,420],{"class":226},[118,28392,25],{"class":124},[118,28394,425],{"class":213},[118,28396,255],{"class":124},[118,28398,430],{"class":124},[118,28400,28401],{"class":433},"ACME, Inc.",[118,28403,430],{"class":124},[118,28405,395],{"class":124},[118,28407,233],{"class":226},[118,28409,25],{"class":124},[118,28411,455],{"class":213},[118,28413,255],{"class":124},[118,28415,1277],{"class":299},[118,28417,1280],{"class":124},[118,28419,233],{"class":226},[118,28421,25],{"class":124},[118,28423,445],{"class":213},[118,28425,1289],{"class":124},[118,28427,28428],{"class":120,"line":350},[118,28429,469],{"class":124},[118,28431,28432,28434,28436,28438,28440,28442,28444,28446,28448,28450,28452,28454,28456,28458],{"class":120,"line":379},[118,28433,382],{"class":226},[118,28435,25],{"class":124},[118,28437,387],{"class":213},[118,28439,255],{"class":124},[118,28441,969],{"class":299},[118,28443,395],{"class":124},[118,28445,398],{"class":124},[118,28447,401],{"class":331},[118,28449,334],{"class":124},[118,28451,337],{"class":128},[118,28453,25],{"class":124},[118,28455,410],{"class":128},[118,28457,345],{"class":124},[118,28459,220],{"class":124},[118,28461,28462,28464,28466,28468,28470,28472,28475,28477,28479,28481,28483,28485],{"class":120,"line":417},[118,28463,420],{"class":226},[118,28465,25],{"class":124},[118,28467,425],{"class":213},[118,28469,255],{"class":124},[118,28471,430],{"class":124},[118,28473,28474],{"class":433},"123 Industrial Way",[118,28476,430],{"class":124},[118,28478,395],{"class":124},[118,28480,233],{"class":226},[118,28482,25],{"class":124},[118,28484,519],{"class":213},[118,28486,1289],{"class":124},[118,28488,28489,28491,28493,28495,28497,28499,28502,28504,28506,28508,28510,28512],{"class":120,"line":466},[118,28490,420],{"class":226},[118,28492,25],{"class":124},[118,28494,425],{"class":213},[118,28496,255],{"class":124},[118,28498,430],{"class":124},[118,28500,28501],{"class":433},"Tokyo, Japan 100-0001",[118,28503,430],{"class":124},[118,28505,395],{"class":124},[118,28507,233],{"class":226},[118,28509,25],{"class":124},[118,28511,519],{"class":213},[118,28513,1289],{"class":124},[118,28515,28516],{"class":120,"line":472},[118,28517,469],{"class":124},[118,28519,28520],{"class":120,"line":503},[118,28521,574],{"class":124},[118,28523,28524],{"class":120,"line":537},[118,28525,136],{"emptyLinePlaceholder":135},[118,28527,28528,28530,28532,28534,28536,28538,28540,28542,28544,28546],{"class":120,"line":566},[118,28529,353],{"class":226},[118,28531,25],{"class":124},[118,28533,675],{"class":213},[118,28535,255],{"class":124},[118,28537,258],{"class":226},[118,28539,25],{"class":124},[118,28541,294],{"class":213},[118,28543,255],{"class":124},[118,28545,460],{"class":299},[118,28547,463],{"class":124},[118,28549,28550],{"class":120,"line":571},[118,28551,136],{"emptyLinePlaceholder":135},[118,28553,28554],{"class":120,"line":577},[118,28555,28556],{"class":3981},"        // Full-width row (one 12-span column) for a table.\n",[118,28558,28559,28561,28563,28565,28567,28569,28571,28573,28575,28577,28579],{"class":120,"line":602},[118,28560,353],{"class":226},[118,28562,25],{"class":124},[118,28564,358],{"class":213},[118,28566,328],{"class":124},[118,28568,363],{"class":331},[118,28570,334],{"class":124},[118,28572,337],{"class":128},[118,28574,25],{"class":124},[118,28576,372],{"class":128},[118,28578,345],{"class":124},[118,28580,220],{"class":124},[118,28582,28583,28585,28587,28589,28591,28593,28595,28597,28599,28601,28603,28605,28607,28609],{"class":120,"line":633},[118,28584,382],{"class":226},[118,28586,25],{"class":124},[118,28588,387],{"class":213},[118,28590,255],{"class":124},[118,28592,20],{"class":299},[118,28594,395],{"class":124},[118,28596,398],{"class":124},[118,28598,401],{"class":331},[118,28600,334],{"class":124},[118,28602,337],{"class":128},[118,28604,25],{"class":124},[118,28606,410],{"class":128},[118,28608,345],{"class":124},[118,28610,220],{"class":124},[118,28612,28613,28615,28617,28619,28621,28623,28625,28627,28630,28632,28634,28636,28638,28640,28642,28644,28647,28649,28651,28653,28655],{"class":120,"line":668},[118,28614,420],{"class":226},[118,28616,25],{"class":124},[118,28618,4929],{"class":213},[118,28620,26592],{"class":124},[118,28622,1131],{"class":1130},[118,28624,1134],{"class":124},[118,28626,430],{"class":124},[118,28628,28629],{"class":433},"Item",[118,28631,430],{"class":124},[118,28633,395],{"class":124},[118,28635,1146],{"class":124},[118,28637,14469],{"class":433},[118,28639,430],{"class":124},[118,28641,395],{"class":124},[118,28643,1146],{"class":124},[118,28645,28646],{"class":433},"Price",[118,28648,430],{"class":124},[118,28650,8116],{"class":124},[118,28652,5121],{"class":124},[118,28654,1131],{"class":1130},[118,28656,7406],{"class":124},[118,28658,28659,28661,28663,28666,28668,28670,28672,28674,28676,28678,28680,28683,28685],{"class":120,"line":693},[118,28660,19734],{"class":124},[118,28662,430],{"class":124},[118,28664,28665],{"class":433},"Widget A",[118,28667,430],{"class":124},[118,28669,395],{"class":124},[118,28671,1146],{"class":124},[118,28673,870],{"class":433},[118,28675,430],{"class":124},[118,28677,395],{"class":124},[118,28679,1146],{"class":124},[118,28681,28682],{"class":433},"¥1,000",[118,28684,430],{"class":124},[118,28686,8191],{"class":124},[118,28688,28689,28691,28693,28696,28698,28700,28702,28704,28706,28708,28710,28713,28715],{"class":120,"line":698},[118,28690,19734],{"class":124},[118,28692,430],{"class":124},[118,28694,28695],{"class":433},"Widget B",[118,28697,430],{"class":124},[118,28699,395],{"class":124},[118,28701,1146],{"class":124},[118,28703,2402],{"class":433},[118,28705,430],{"class":124},[118,28707,395],{"class":124},[118,28709,1146],{"class":124},[118,28711,28712],{"class":433},"¥2,500",[118,28714,430],{"class":124},[118,28716,8191],{"class":124},[118,28718,28719],{"class":120,"line":703},[118,28720,28721],{"class":124},"                })\n",[118,28723,28724],{"class":120,"line":709},[118,28725,469],{"class":124},[118,28727,28728],{"class":120,"line":714},[118,28729,574],{"class":124},[118,28731,28732],{"class":120,"line":740},[118,28733,136],{"emptyLinePlaceholder":135},[118,28735,28736,28738,28740,28742,28744,28746,28748,28750,28752,28754],{"class":120,"line":765},[118,28737,353],{"class":226},[118,28739,25],{"class":124},[118,28741,675],{"class":213},[118,28743,255],{"class":124},[118,28745,258],{"class":226},[118,28747,25],{"class":124},[118,28749,294],{"class":213},[118,28751,255],{"class":124},[118,28753,460],{"class":299},[118,28755,463],{"class":124},[118,28757,28758],{"class":120,"line":796},[118,28759,136],{"emptyLinePlaceholder":135},[118,28761,28762],{"class":120,"line":819},[118,28763,28764],{"class":3981},"        // KPI strip: four equal columns of 3-span each.\n",[118,28766,28767,28770,28772,28775,28778,28780,28783,28785],{"class":120,"line":851},[118,28768,28769],{"class":226},"        kpis ",[118,28771,230],{"class":124},[118,28773,28774],{"class":124}," []struct{",[118,28776,28777],{"class":226}," label",[118,28779,395],{"class":124},[118,28781,28782],{"class":226}," value ",[118,28784,1131],{"class":1130},[118,28786,28787],{"class":124}," }{\n",[118,28789,28790,28793,28795,28798,28800,28802,28804,28807,28809],{"class":120,"line":875},[118,28791,28792],{"class":124},"            {",[118,28794,430],{"class":124},[118,28796,28797],{"class":433},"Subtotal",[118,28799,430],{"class":124},[118,28801,395],{"class":124},[118,28803,1146],{"class":124},[118,28805,28806],{"class":433},"¥4,500",[118,28808,430],{"class":124},[118,28810,8191],{"class":124},[118,28812,28813,28815,28817,28820,28822,28824,28826,28829,28831],{"class":120,"line":880},[118,28814,28792],{"class":124},[118,28816,430],{"class":124},[118,28818,28819],{"class":433},"Tax (10%)",[118,28821,430],{"class":124},[118,28823,395],{"class":124},[118,28825,1146],{"class":124},[118,28827,28828],{"class":433},"¥450",[118,28830,430],{"class":124},[118,28832,8191],{"class":124},[118,28834,28835,28837,28839,28842,28844,28846,28848,28851,28853],{"class":120,"line":885},[118,28836,28792],{"class":124},[118,28838,430],{"class":124},[118,28840,28841],{"class":433},"Shipping",[118,28843,430],{"class":124},[118,28845,395],{"class":124},[118,28847,1146],{"class":124},[118,28849,28850],{"class":433},"¥0",[118,28852,430],{"class":124},[118,28854,8191],{"class":124},[118,28856,28857,28859,28861,28863,28865,28867,28869,28872,28874],{"class":120,"line":910},[118,28858,28792],{"class":124},[118,28860,430],{"class":124},[118,28862,11576],{"class":433},[118,28864,430],{"class":124},[118,28866,395],{"class":124},[118,28868,1146],{"class":124},[118,28870,28871],{"class":433},"¥4,950",[118,28873,430],{"class":124},[118,28875,8191],{"class":124},[118,28877,28878],{"class":120,"line":941},[118,28879,15447],{"class":124},[118,28881,28882,28884,28886,28888,28890,28892,28894,28896,28898,28900,28902],{"class":120,"line":974},[118,28883,353],{"class":226},[118,28885,25],{"class":124},[118,28887,358],{"class":213},[118,28889,328],{"class":124},[118,28891,363],{"class":331},[118,28893,334],{"class":124},[118,28895,337],{"class":128},[118,28897,25],{"class":124},[118,28899,372],{"class":128},[118,28901,345],{"class":124},[118,28903,220],{"class":124},[118,28905,28906,28909,28911,28913,28916,28918,28920,28923],{"class":120,"line":997},[118,28907,28908],{"class":142},"            for",[118,28910,14776],{"class":226},[118,28912,395],{"class":124},[118,28914,28915],{"class":226}," k ",[118,28917,230],{"class":124},[118,28919,1124],{"class":142},[118,28921,28922],{"class":226}," kpis ",[118,28924,7406],{"class":124},[118,28926,28927,28930,28932],{"class":120,"line":1002},[118,28928,28929],{"class":226},"                k ",[118,28931,230],{"class":124},[118,28933,28934],{"class":226}," k\n",[118,28936,28937,28940,28942,28944,28946,28948,28950,28952,28954,28956,28958,28960,28962,28964],{"class":120,"line":1033},[118,28938,28939],{"class":226},"                r",[118,28941,25],{"class":124},[118,28943,387],{"class":213},[118,28945,255],{"class":124},[118,28947,688],{"class":299},[118,28949,395],{"class":124},[118,28951,398],{"class":124},[118,28953,401],{"class":331},[118,28955,334],{"class":124},[118,28957,337],{"class":128},[118,28959,25],{"class":124},[118,28961,410],{"class":128},[118,28963,345],{"class":124},[118,28965,220],{"class":124},[118,28967,28968,28971,28973,28975,28977,28980,28982,28985,28987,28989,28991,28993,28995,28997],{"class":120,"line":1065},[118,28969,28970],{"class":226},"                    c",[118,28972,25],{"class":124},[118,28974,425],{"class":213},[118,28976,255],{"class":124},[118,28978,28979],{"class":226},"k",[118,28981,25],{"class":124},[118,28983,28984],{"class":226},"label",[118,28986,395],{"class":124},[118,28988,233],{"class":226},[118,28990,25],{"class":124},[118,28992,455],{"class":213},[118,28994,255],{"class":124},[118,28996,969],{"class":299},[118,28998,463],{"class":124},[118,29000,29001,29003,29005,29007,29009,29011,29013,29016,29018,29020,29022,29024,29026,29028,29030,29032,29034,29036],{"class":120,"line":1088},[118,29002,28970],{"class":226},[118,29004,25],{"class":124},[118,29006,425],{"class":213},[118,29008,255],{"class":124},[118,29010,28979],{"class":226},[118,29012,25],{"class":124},[118,29014,29015],{"class":226},"value",[118,29017,395],{"class":124},[118,29019,233],{"class":226},[118,29021,25],{"class":124},[118,29023,455],{"class":213},[118,29025,255],{"class":124},[118,29027,1877],{"class":299},[118,29029,1280],{"class":124},[118,29031,233],{"class":226},[118,29033,25],{"class":124},[118,29035,445],{"class":213},[118,29037,1289],{"class":124},[118,29039,29040],{"class":120,"line":1093},[118,29041,28721],{"class":124},[118,29043,29044],{"class":120,"line":1098},[118,29045,15425],{"class":124},[118,29047,29048],{"class":120,"line":1103},[118,29049,574],{"class":124},[118,29051,29052],{"class":120,"line":1108},[118,29053,706],{"class":124},[118,29055,29056],{"class":120,"line":1177},[118,29057,136],{"emptyLinePlaceholder":135},[118,29059,29060,29063,29065,29067,29069,29071,29073,29075,29077,29079,29081,29083],{"class":120,"line":1196},[118,29061,29062],{"class":226},"    f",[118,29064,395],{"class":124},[118,29066,3999],{"class":226},[118,29068,230],{"class":124},[118,29070,1447],{"class":226},[118,29072,25],{"class":124},[118,29074,4008],{"class":213},[118,29076,255],{"class":124},[118,29078,430],{"class":124},[118,29080,4015],{"class":433},[118,29082,430],{"class":124},[118,29084,199],{"class":124},[118,29086,29087,29089,29091,29093,29095],{"class":120,"line":1222},[118,29088,4024],{"class":142},[118,29090,26768],{"class":226},[118,29092,25],{"class":124},[118,29094,4031],{"class":213},[118,29096,1193],{"class":124},[118,29098,29099,29101,29103,29106,29108,29110],{"class":120,"line":1253},[118,29100,320],{"class":226},[118,29102,25],{"class":124},[118,29104,29105],{"class":213},"Render",[118,29107,255],{"class":124},[118,29109,26738],{"class":226},[118,29111,199],{"class":124},[118,29113,29114],{"class":120,"line":1292},[118,29115,1479],{"class":124},[14,29117,29118,29119,29122,29123,29125],{},"That's a real, working program. ",[18,29120,29121],{},"go get github.com/gpdf-dev/gpdf"," and run it; ",[18,29124,4015],{}," lands in your working directory. Render time on an M1: about 130 µs.",[41,29127,29129],{"id":29128},"when-the-integer-model-is-wrong","When the integer model is wrong",[14,29131,29132],{},"The integer-twelfths model is genuinely the wrong choice in two cases. Honest list, since you'll hit at least one eventually:",[1624,29134,29135,29152],{},[49,29136,29137,29140,29141,29143,29144,29147,29148,29151],{},[1629,29138,29139],{},"You need exact pixel-perfect widths."," \"This column must be exactly 73.5pt wide.\" ",[18,29142,6616],{}," won't get you there because ",[18,29145,29146],{},"73.5/total*12"," is rarely an integer. Use ",[18,29149,29150],{},"page.Absolute(...)"," for the few elements that need fixed coordinates and let the grid handle everything else. Mixing both is fine; they live on the same page.",[49,29153,29154,29157],{},[1629,29155,29156],{},"You need newspaper-style column flow."," A paragraph that fills one column and continues into the next. The grid does not do this. We don't have a column-flow text engine yet. If you need it, file an issue — we know it's missing.",[14,29159,29160],{},"For everything else — invoices, reports, contracts, brochures, decks — the 12-grid is a tighter fit than CSS, not a looser one.",[41,29162,29164],{"id":29163},"frequently-asked-questions","Frequently asked questions",[14,29166,29167,29170,29171,29173],{},[1629,29168,29169],{},"Q: Can I change the 12 to something else, like 24?","\nNo. ",[18,29172,6643],{}," is a constant. Changing it would invalidate every existing template. We picked 12 once and committed.",[14,29175,29176,29179,29180,29183,29184,29187,29188,29191],{},[1629,29177,29178],{},"Q: What if I want to nest a row inside a column?","\nYou can. ",[18,29181,29182],{},"c.AutoRow(...)"," creates a sub-row inside the column. Spans inside the sub-row are 1–12 of the ",[4744,29185,29186],{},"parent column's"," width, not the page width. Nesting composes cleanly because every level is just ",[18,29189,29190],{},"Pct(span/12 * 100)"," of its parent.",[14,29193,29194,29197,29198,29200],{},[1629,29195,29196],{},"Q: Does this work for landscape pages?","\nYes. The grid is page-size agnostic. ",[18,29199,11193],{}," is half the row whether the row is 210mm wide (A4 portrait) or 297mm wide (A4 landscape).",[14,29202,29203,29210,29211,29213],{},[1629,29204,29205,29206,29209],{},"Q: Why is there no ",[18,29207,29208],{},"r.Col2(span, span, fn1, fn2)"," shortcut for two-column rows?","\nBecause saving one line by adding API surface is a bad trade. If you find yourself repeating a row pattern, write a Go function that takes a ",[18,29212,1676],{}," and adds it. The grid stays minimal so user-level patterns can grow without conflict.",[14,29215,29216,29223],{},[1629,29217,29218,29219,29222],{},"Q: What about CSS Grid features like ",[18,29220,29221],{},"grid-area"," and named lines?","\nNot in gpdf, and not on the roadmap. The cost-benefit doesn't pencil out for PDFs.",[41,29225,29227],{"id":29226},"recap","Recap",[14,29229,29230,29231,29234],{},"The 12-column grid is the smallest layout primitive that handles the splits real documents need. We borrowed the number from Bootstrap, kept the integer model, and dropped breakpoints, gutters, order, auto-fill, span-sum enforcement, and the rest of the responsive-web baggage. What's left is one constant, one builder method, and one width formula — about 30 lines of Go. It composes through nesting, plays nicely with ",[18,29232,29233],{},"Absolute"," for the few cases the grid can't express, and never silently rebalances what you wrote.",[41,29236,4794],{"id":4793},[14,29238,29239],{},"gpdf is a Go PDF library — MIT, zero dependencies, CJK out of the box.",[109,29241,29242],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,29243,29244],{"__ignoreMap":114},[118,29245,29246,29248,29250],{"class":120,"line":121},[118,29247,113],{"class":128},[118,29249,3156],{"class":433},[118,29251,3159],{"class":433},[14,29253,29254,3169,29257],{},[3163,29255,3168],{"href":3165,"rel":29256},[3167],[3163,29258,3174],{"href":3172,"rel":29259},[3167],[41,29261,29263],{"id":29262},"what-to-read-next","What to read next",[46,29265,29266,29271,29277],{},[49,29267,29268,29270],{},[3163,29269,6902],{"href":6901}," — the recipe version, with more code patterns",[49,29272,29273,29276],{},[3163,29274,29275],{"href":4069},"Why gpdf is 10× faster than alternatives"," — internals of the rendering pipeline",[49,29278,29279,29282],{},[3163,29280,27352],{"href":29281},"/docs/quickstart"," — generate your first PDF in five minutes",[3176,29284,29285],{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":114,"searchDepth":132,"depth":132,"links":29287},[29288,29289,29290,29291,29292,29293,29301,29302,29303,29304,29305,29306,29307,29308],{"id":43,"depth":132,"text":44},{"id":10014,"depth":132,"text":10015},{"id":27475,"depth":132,"text":27476},{"id":27516,"depth":132,"text":27517},{"id":27581,"depth":132,"text":27582},{"id":27614,"depth":132,"text":27615,"children":29294},[29295,29296,29297,29298,29299,29300],{"id":27618,"depth":139,"text":27619},{"id":27656,"depth":139,"text":27657},{"id":27673,"depth":139,"text":27674},{"id":27688,"depth":139,"text":27689},{"id":27793,"depth":139,"text":27794},{"id":27972,"depth":139,"text":27973},{"id":27992,"depth":132,"text":27993},{"id":28154,"depth":132,"text":28155},{"id":18226,"depth":132,"text":18227},{"id":29128,"depth":132,"text":29129},{"id":29163,"depth":132,"text":29164},{"id":29226,"depth":132,"text":29227},{"id":4793,"depth":132,"text":4794},{"id":29262,"depth":132,"text":29263},"gpdf borrows the 12-column grid from Bootstrap, but PDFs aren't web pages. Here's why 12 still works, and what we threw out: breakpoints, gutters, order.",{},{"title":12891,"description":29309},"blog/017.bootstrap-grid-thinking-for-pdf",[3227,6997,4866],"4cIC9au31OgjbmXd0g_cJuPcut1Bt4nbixIgadPfl4Q",{"id":29316,"title":12877,"author":29317,"body":29318,"date":30471,"description":30472,"draft":3196,"extension":3197,"howTo":30473,"image":3220,"meta":30489,"navigation":135,"path":8448,"seo":30490,"stem":30491,"tags":30492,"updated":3220,"__hash__":30493},"blog/blog/014.table-column-widths.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":29319,"toc":30459},[29320,29322,29335,29337,29346,29393,29400,29403,29407,30017,30026,30032,30043,30056,30062,30066,30069,30083,30086,30096,30147,30154,30205,30215,30219,30226,30229,30233,30236,30391,30409,30416,30418,30432,30434,30436,30448,30456],[41,29321,4879],{"id":4878},[14,29323,29324,29325,29328,29329,29331,29332,29334],{},"I have a table with four columns. By default ",[18,29326,29327],{},"c.Table(header, rows)"," makes them all the same width, but for an invoice the ",[1629,29330,14460],{}," column should be wide and ",[1629,29333,14469],{}," should be narrow. How do I tell gpdf the per-column widths, and what unit am I actually setting?",[41,29336,8546],{"id":8545},[14,29338,29339,29340,29343,29344,6349],{},"Pass ",[18,29341,29342],{},"template.ColumnWidths(...)"," as a ",[18,29345,5132],{},[109,29347,29349],{"className":111,"code":29348,"language":113,"meta":114,"style":114},"c.Table(header, rows, template.ColumnWidths(40, 15, 20, 25))\n",[18,29350,29351],{"__ignoreMap":114},[118,29352,29353,29355,29357,29359,29361,29363,29365,29367,29369,29371,29373,29375,29377,29379,29381,29383,29385,29387,29389,29391],{"class":120,"line":121},[118,29354,401],{"class":226},[118,29356,25],{"class":124},[118,29358,4929],{"class":213},[118,29360,255],{"class":124},[118,29362,3095],{"class":226},[118,29364,395],{"class":124},[118,29366,5118],{"class":226},[118,29368,395],{"class":124},[118,29370,233],{"class":226},[118,29372,25],{"class":124},[118,29374,7733],{"class":213},[118,29376,255],{"class":124},[118,29378,9910],{"class":299},[118,29380,395],{"class":124},[118,29382,9915],{"class":299},[118,29384,395],{"class":124},[118,29386,7742],{"class":299},[118,29388,395],{"class":124},[118,29390,9924],{"class":299},[118,29392,463],{"class":124},[14,29394,29395,29396,29399],{},"The values are ",[1629,29397,29398],{},"percentages of the parent column's content width",", not points. They don't have to sum to 100, but they usually should — anything missing leaves empty space on the right, anything extra overflows the cell.",[14,29401,29402],{},"That's it for the common case. The interesting parts are what happens when the percentages don't sum to 100, when you pass fewer values than there are columns, and what the parent width actually is.",[41,29404,29406],{"id":29405},"working-code-a-four-column-invoice-table","Working code (a four-column invoice table)",[109,29408,29410],{"className":111,"code":29409,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\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 := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    header := []string{\"Description\", \"Qty\", \"Unit price\", \"Total\"}\n    rows := [][]string{\n        {\"Annual support contract\", \"1\", \"$1,200.00\", \"$1,200.00\"},\n        {\"On-site training (per day)\", \"3\", \"$800.00\", \"$2,400.00\"},\n        {\"Custom template development\", \"12\", \"$95.00\", \"$1,140.00\"},\n    }\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Table(header, rows,\n                template.ColumnWidths(40, 15, 20, 25),\n                template.TableHeaderStyle(template.BgColor(pdf.Gray(0.92))),\n            )\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,29411,29412,29418,29422,29428,29436,29444,29448,29456,29464,29472,29480,29484,29488,29498,29512,29530,29560,29564,29568,29613,29625,29663,29701,29740,29744,29748,29762,29786,29816,29834,29860,29891,29895,29899,29903,29907,29925,29937,29951,29955,29995,30009,30013],{"__ignoreMap":114},[118,29413,29414,29416],{"class":120,"line":121},[118,29415,125],{"class":124},[118,29417,129],{"class":128},[118,29419,29420],{"class":120,"line":132},[118,29421,136],{"emptyLinePlaceholder":135},[118,29423,29424,29426],{"class":120,"line":139},[118,29425,143],{"class":142},[118,29427,146],{"class":124},[118,29429,29430,29432,29434],{"class":120,"line":149},[118,29431,152],{"class":124},[118,29433,5303],{"class":128},[118,29435,158],{"class":124},[118,29437,29438,29440,29442],{"class":120,"line":161},[118,29439,152],{"class":124},[118,29441,155],{"class":128},[118,29443,158],{"class":124},[118,29445,29446],{"class":120,"line":166},[118,29447,136],{"emptyLinePlaceholder":135},[118,29449,29450,29452,29454],{"class":120,"line":176},[118,29451,152],{"class":124},[118,29453,3203],{"class":128},[118,29455,158],{"class":124},[118,29457,29458,29460,29462],{"class":120,"line":186},[118,29459,152],{"class":124},[118,29461,171],{"class":128},[118,29463,158],{"class":124},[118,29465,29466,29468,29470],{"class":120,"line":196},[118,29467,152],{"class":124},[118,29469,181],{"class":128},[118,29471,158],{"class":124},[118,29473,29474,29476,29478],{"class":120,"line":202},[118,29475,152],{"class":124},[118,29477,191],{"class":128},[118,29479,158],{"class":124},[118,29481,29482],{"class":120,"line":207},[118,29483,199],{"class":124},[118,29485,29486],{"class":120,"line":223},[118,29487,136],{"emptyLinePlaceholder":135},[118,29489,29490,29492,29494,29496],{"class":120,"line":244},[118,29491,210],{"class":124},[118,29493,214],{"class":213},[118,29495,217],{"class":124},[118,29497,220],{"class":124},[118,29499,29500,29502,29504,29506,29508,29510],{"class":120,"line":269},[118,29501,227],{"class":226},[118,29503,230],{"class":124},[118,29505,3595],{"class":226},[118,29507,25],{"class":124},[118,29509,3600],{"class":213},[118,29511,241],{"class":124},[118,29513,29514,29516,29518,29520,29522,29524,29526,29528],{"class":120,"line":306},[118,29515,3607],{"class":226},[118,29517,25],{"class":124},[118,29519,252],{"class":213},[118,29521,255],{"class":124},[118,29523,1587],{"class":226},[118,29525,25],{"class":124},[118,29527,263],{"class":226},[118,29529,266],{"class":124},[118,29531,29532,29534,29536,29538,29540,29542,29544,29546,29548,29550,29552,29554,29556,29558],{"class":120,"line":312},[118,29533,3607],{"class":226},[118,29535,25],{"class":124},[118,29537,276],{"class":213},[118,29539,255],{"class":124},[118,29541,258],{"class":226},[118,29543,25],{"class":124},[118,29545,285],{"class":213},[118,29547,255],{"class":124},[118,29549,258],{"class":226},[118,29551,25],{"class":124},[118,29553,294],{"class":213},[118,29555,255],{"class":124},[118,29557,300],{"class":299},[118,29559,303],{"class":124},[118,29561,29562],{"class":120,"line":317},[118,29563,309],{"class":124},[118,29565,29566],{"class":120,"line":350},[118,29567,136],{"emptyLinePlaceholder":135},[118,29569,29570,29572,29574,29576,29578,29580,29582,29584,29586,29588,29590,29592,29594,29596,29598,29601,29603,29605,29607,29609,29611],{"class":120,"line":379},[118,29571,7280],{"class":226},[118,29573,230],{"class":124},[118,29575,1127],{"class":124},[118,29577,1131],{"class":1130},[118,29579,1134],{"class":124},[118,29581,430],{"class":124},[118,29583,14460],{"class":433},[118,29585,430],{"class":124},[118,29587,395],{"class":124},[118,29589,1146],{"class":124},[118,29591,14469],{"class":433},[118,29593,430],{"class":124},[118,29595,395],{"class":124},[118,29597,1146],{"class":124},[118,29599,29600],{"class":433},"Unit price",[118,29602,430],{"class":124},[118,29604,395],{"class":124},[118,29606,1146],{"class":124},[118,29608,11576],{"class":433},[118,29610,430],{"class":124},[118,29612,1479],{"class":124},[118,29614,29615,29617,29619,29621,29623],{"class":120,"line":417},[118,29616,7329],{"class":226},[118,29618,230],{"class":124},[118,29620,5121],{"class":124},[118,29622,1131],{"class":1130},[118,29624,7406],{"class":124},[118,29626,29627,29629,29631,29634,29636,29638,29640,29642,29644,29646,29648,29651,29653,29655,29657,29659,29661],{"class":120,"line":466},[118,29628,8061],{"class":124},[118,29630,430],{"class":124},[118,29632,29633],{"class":433},"Annual support contract",[118,29635,430],{"class":124},[118,29637,395],{"class":124},[118,29639,1146],{"class":124},[118,29641,2402],{"class":433},[118,29643,430],{"class":124},[118,29645,395],{"class":124},[118,29647,1146],{"class":124},[118,29649,29650],{"class":433},"$1,200.00",[118,29652,430],{"class":124},[118,29654,395],{"class":124},[118,29656,1146],{"class":124},[118,29658,29650],{"class":433},[118,29660,430],{"class":124},[118,29662,8191],{"class":124},[118,29664,29665,29667,29669,29672,29674,29676,29678,29680,29682,29684,29686,29689,29691,29693,29695,29697,29699],{"class":120,"line":472},[118,29666,8061],{"class":124},[118,29668,430],{"class":124},[118,29670,29671],{"class":433},"On-site training (per day)",[118,29673,430],{"class":124},[118,29675,395],{"class":124},[118,29677,1146],{"class":124},[118,29679,688],{"class":433},[118,29681,430],{"class":124},[118,29683,395],{"class":124},[118,29685,1146],{"class":124},[118,29687,29688],{"class":433},"$800.00",[118,29690,430],{"class":124},[118,29692,395],{"class":124},[118,29694,1146],{"class":124},[118,29696,24480],{"class":433},[118,29698,430],{"class":124},[118,29700,8191],{"class":124},[118,29702,29703,29705,29707,29710,29712,29714,29716,29718,29720,29722,29724,29727,29729,29731,29733,29736,29738],{"class":120,"line":503},[118,29704,8061],{"class":124},[118,29706,430],{"class":124},[118,29708,29709],{"class":433},"Custom template development",[118,29711,430],{"class":124},[118,29713,395],{"class":124},[118,29715,1146],{"class":124},[118,29717,20],{"class":433},[118,29719,430],{"class":124},[118,29721,395],{"class":124},[118,29723,1146],{"class":124},[118,29725,29726],{"class":433},"$95.00",[118,29728,430],{"class":124},[118,29730,395],{"class":124},[118,29732,1146],{"class":124},[118,29734,29735],{"class":433},"$1,140.00",[118,29737,430],{"class":124},[118,29739,8191],{"class":124},[118,29741,29742],{"class":120,"line":537},[118,29743,1375],{"class":124},[118,29745,29746],{"class":120,"line":566},[118,29747,136],{"emptyLinePlaceholder":135},[118,29749,29750,29752,29754,29756,29758,29760],{"class":120,"line":571},[118,29751,5431],{"class":226},[118,29753,230],{"class":124},[118,29755,1185],{"class":226},[118,29757,25],{"class":124},[118,29759,1190],{"class":213},[118,29761,1193],{"class":124},[118,29763,29764,29766,29768,29770,29772,29774,29776,29778,29780,29782,29784],{"class":120,"line":577},[118,29765,5494],{"class":226},[118,29767,25],{"class":124},[118,29769,358],{"class":213},[118,29771,328],{"class":124},[118,29773,363],{"class":331},[118,29775,334],{"class":124},[118,29777,337],{"class":128},[118,29779,25],{"class":124},[118,29781,372],{"class":128},[118,29783,345],{"class":124},[118,29785,220],{"class":124},[118,29787,29788,29790,29792,29794,29796,29798,29800,29802,29804,29806,29808,29810,29812,29814],{"class":120,"line":602},[118,29789,1737],{"class":226},[118,29791,25],{"class":124},[118,29793,387],{"class":213},[118,29795,255],{"class":124},[118,29797,20],{"class":299},[118,29799,395],{"class":124},[118,29801,398],{"class":124},[118,29803,401],{"class":331},[118,29805,334],{"class":124},[118,29807,337],{"class":128},[118,29809,25],{"class":124},[118,29811,410],{"class":128},[118,29813,345],{"class":124},[118,29815,220],{"class":124},[118,29817,29818,29820,29822,29824,29826,29828,29830,29832],{"class":120,"line":633},[118,29819,1768],{"class":226},[118,29821,25],{"class":124},[118,29823,4929],{"class":213},[118,29825,255],{"class":124},[118,29827,3095],{"class":226},[118,29829,395],{"class":124},[118,29831,5118],{"class":226},[118,29833,2643],{"class":124},[118,29835,29836,29838,29840,29842,29844,29846,29848,29850,29852,29854,29856,29858],{"class":120,"line":668},[118,29837,2648],{"class":226},[118,29839,25],{"class":124},[118,29841,7733],{"class":213},[118,29843,255],{"class":124},[118,29845,9910],{"class":299},[118,29847,395],{"class":124},[118,29849,9915],{"class":299},[118,29851,395],{"class":124},[118,29853,7742],{"class":299},[118,29855,395],{"class":124},[118,29857,9924],{"class":299},[118,29859,266],{"class":124},[118,29861,29862,29864,29866,29868,29870,29872,29874,29876,29878,29880,29882,29884,29886,29889],{"class":120,"line":693},[118,29863,2648],{"class":226},[118,29865,25],{"class":124},[118,29867,7762],{"class":213},[118,29869,255],{"class":124},[118,29871,337],{"class":226},[118,29873,25],{"class":124},[118,29875,7792],{"class":213},[118,29877,255],{"class":124},[118,29879,550],{"class":226},[118,29881,25],{"class":124},[118,29883,555],{"class":213},[118,29885,255],{"class":124},[118,29887,29888],{"class":299},"0.92",[118,29890,303],{"class":124},[118,29892,29893],{"class":120,"line":698},[118,29894,7809],{"class":124},[118,29896,29897],{"class":120,"line":703},[118,29898,574],{"class":124},[118,29900,29901],{"class":120,"line":709},[118,29902,706],{"class":124},[118,29904,29905],{"class":120,"line":714},[118,29906,136],{"emptyLinePlaceholder":135},[118,29908,29909,29911,29913,29915,29917,29919,29921,29923],{"class":120,"line":740},[118,29910,5787],{"class":226},[118,29912,395],{"class":124},[118,29914,1391],{"class":226},[118,29916,230],{"class":124},[118,29918,1185],{"class":226},[118,29920,25],{"class":124},[118,29922,1400],{"class":213},[118,29924,1193],{"class":124},[118,29926,29927,29929,29931,29933,29935],{"class":120,"line":765},[118,29928,1408],{"class":142},[118,29930,1391],{"class":226},[118,29932,1413],{"class":124},[118,29934,1416],{"class":124},[118,29936,220],{"class":124},[118,29938,29939,29941,29943,29945,29947,29949],{"class":120,"line":796},[118,29940,5818],{"class":226},[118,29942,25],{"class":124},[118,29944,5823],{"class":213},[118,29946,255],{"class":124},[118,29948,1429],{"class":226},[118,29950,199],{"class":124},[118,29952,29953],{"class":120,"line":819},[118,29954,1375],{"class":124},[118,29956,29957,29959,29961,29963,29965,29967,29969,29971,29973,29975,29977,29979,29981,29983,29985,29987,29989,29991,29993],{"class":120,"line":851},[118,29958,1408],{"class":142},[118,29960,1391],{"class":226},[118,29962,230],{"class":124},[118,29964,1447],{"class":226},[118,29966,25],{"class":124},[118,29968,1452],{"class":213},[118,29970,255],{"class":124},[118,29972,430],{"class":124},[118,29974,4015],{"class":433},[118,29976,430],{"class":124},[118,29978,395],{"class":124},[118,29980,5859],{"class":226},[118,29982,395],{"class":124},[118,29984,1471],{"class":299},[118,29986,7902],{"class":124},[118,29988,1391],{"class":226},[118,29990,1413],{"class":124},[118,29992,1416],{"class":124},[118,29994,220],{"class":124},[118,29996,29997,29999,30001,30003,30005,30007],{"class":120,"line":875},[118,29998,5818],{"class":226},[118,30000,25],{"class":124},[118,30002,5823],{"class":213},[118,30004,255],{"class":124},[118,30006,1429],{"class":226},[118,30008,199],{"class":124},[118,30010,30011],{"class":120,"line":880},[118,30012,1375],{"class":124},[118,30014,30015],{"class":120,"line":885},[118,30016,1479],{"class":124},[14,30018,30019,30022,30023,30025],{},[18,30020,30021],{},"r.Col(12, ...)"," claims the full row width. The table sits inside that column, so 100% of the table = 100% of the grid Col content area. With ",[18,30024,11179],{}," summing to 100, every PDF point of horizontal space is used.",[41,30027,30029,30030],{"id":30028},"what-the-percentages-are-a-percentage-of","What the percentages are a percentage ",[4744,30031,2201],{},[14,30033,30034,30035,30038,30039,30042],{},"The number you pass is forwarded to ",[18,30036,30037],{},"document.Pct(w)"," and resolved against the ",[1629,30040,30041],{},"table's content width",". That's the width of the grid Col the table lives in, minus any margin, padding, and border on the table itself (in practice, just the Col width — table styling defaults to none).",[14,30044,30045,30046,30048,30049,30051,30052,30055],{},"So with ",[18,30047,11193],{}," (half the row) and ",[18,30050,11197],{},", each table column is ",[1629,30053,30054],{},"25% of the row width",", not 50%. The percentages are local to the table, not to the page.",[14,30057,30058,30059,30061],{},"This matters if you ever swap a table from a full-width row into a side-by-side layout. The ",[18,30060,7733],{}," call doesn't need to change — it scales.",[41,30063,30065],{"id":30064},"what-gpdf-does-when-the-math-doesnt-work-out","What gpdf does when the math doesn't work out",[14,30067,30068],{},"Two cases come up constantly. Both are handled by the layout engine in a specific way that's worth knowing.",[14,30070,30071,30074,30075,30078,30079,30082],{},[1629,30072,30073],{},"Case 1: percentages don't sum to 100."," Each value is taken at face value. ",[18,30076,30077],{},"ColumnWidths(40, 30, 20)"," on a three-column table produces columns at 40%, 30%, and 20% of the parent — leaving 10% empty on the right. ",[18,30080,30081],{},"ColumnWidths(50, 50, 50)"," overflows; the third column extends past the parent edge and may bleed into the next page column or off the page.",[14,30084,30085],{},"There is no normalization step. gpdf trusts you to do the arithmetic.",[14,30087,30088,30091,30092,30095],{},[1629,30089,30090],{},"Case 2: fewer widths than there are columns."," This is the more interesting one. The trailing columns become ",[1629,30093,30094],{},"auto-width"," and share the remainder equally:",[109,30097,30099],{"className":111,"code":30098,"language":113,"meta":114,"style":114},"// Three-column table, only two widths given.\nc.Table(header3, rows3, template.ColumnWidths(40, 30))\n// → 40% / 30% / 30%   (the third column gets 100 - 40 - 30 = 30%)\n",[18,30100,30101,30106,30142],{"__ignoreMap":114},[118,30102,30103],{"class":120,"line":121},[118,30104,30105],{"class":3981},"// Three-column table, only two widths given.\n",[118,30107,30108,30110,30112,30114,30116,30119,30121,30124,30126,30128,30130,30132,30134,30136,30138,30140],{"class":120,"line":132},[118,30109,401],{"class":226},[118,30111,25],{"class":124},[118,30113,4929],{"class":213},[118,30115,255],{"class":124},[118,30117,30118],{"class":226},"header3",[118,30120,395],{"class":124},[118,30122,30123],{"class":226}," rows3",[118,30125,395],{"class":124},[118,30127,233],{"class":226},[118,30129,25],{"class":124},[118,30131,7733],{"class":213},[118,30133,255],{"class":124},[118,30135,9910],{"class":299},[118,30137,395],{"class":124},[118,30139,11280],{"class":299},[118,30141,463],{"class":124},[118,30143,30144],{"class":120,"line":139},[118,30145,30146],{"class":3981},"// → 40% / 30% / 30%   (the third column gets 100 - 40 - 30 = 30%)\n",[14,30148,30149,30150,30153],{},"If the explicit widths already sum to 100 or more, the auto columns get ",[1629,30151,30152],{},"zero width"," and effectively disappear. If they sum to less than 100, the leftover is divided equally among the auto columns:",[109,30155,30157],{"className":111,"code":30156,"language":113,"meta":114,"style":114},"// Five-column table, two widths given.\nc.Table(header5, rows5, template.ColumnWidths(50, 10))\n// → 50% / 10% / 13.33% / 13.33% / 13.33%   (40% split three ways)\n",[18,30158,30159,30164,30200],{"__ignoreMap":114},[118,30160,30161],{"class":120,"line":121},[118,30162,30163],{"class":3981},"// Five-column table, two widths given.\n",[118,30165,30166,30168,30170,30172,30174,30177,30179,30182,30184,30186,30188,30190,30192,30194,30196,30198],{"class":120,"line":132},[118,30167,401],{"class":226},[118,30169,25],{"class":124},[118,30171,4929],{"class":213},[118,30173,255],{"class":124},[118,30175,30176],{"class":226},"header5",[118,30178,395],{"class":124},[118,30180,30181],{"class":226}," rows5",[118,30183,395],{"class":124},[118,30185,233],{"class":226},[118,30187,25],{"class":124},[118,30189,7733],{"class":213},[118,30191,255],{"class":124},[118,30193,1510],{"class":299},[118,30195,395],{"class":124},[118,30197,11241],{"class":299},[118,30199,463],{"class":124},[118,30201,30202],{"class":120,"line":139},[118,30203,30204],{"class":3981},"// → 50% / 10% / 13.33% / 13.33% / 13.33%   (40% split three ways)\n",[14,30206,30207,30208,30210,30211,30214],{},"There's a useful trick hiding in this rule: passing ",[18,30209,4159],{}," for a column also makes it auto. So ",[18,30212,30213],{},"ColumnWidths(0, 30, 30)"," on a three-column table fixes the last two at 30% each and gives the first column the remaining 40%. This is how you say \"I care about these specific widths; let gpdf handle the rest.\"",[41,30216,30218],{"id":30217},"the-other-direction-too-many-widths","The other direction: too many widths",[14,30220,30221,30222,30225],{},"Extra values beyond the column count are silently ignored. ",[18,30223,30224],{},"ColumnWidths(40, 30, 20, 10)"," on a two-column table just uses the first two. This is forgiving but it's also a bug magnet — if you delete a column from your header but forget to drop the matching width, gpdf won't tell you. There's no warning log.",[14,30227,30228],{},"The column count itself comes from the header row's length (or the first body row, if there's no header). Add a header cell and you've added a column; gpdf will discover this and re-balance whatever ColumnWidths you passed.",[41,30230,30232],{"id":30231},"when-percentages-arent-what-you-want","When percentages aren't what you want",[14,30234,30235],{},"The builder API only exposes percentages. If you genuinely need a fixed-width column — say, a 50pt \"Qty\" column that doesn't scale with the page — you have to drop one layer down to the document tree:",[109,30237,30239],{"className":111,"code":30238,"language":113,"meta":114,"style":114},"import \"github.com/gpdf-dev/gpdf/document\"\n\ntbl := &document.Table{\n    Columns: []document.TableColumn{\n        {Width: document.Auto},\n        {Width: document.Pt(50)},\n        {Width: document.Pt(80)},\n        {Width: document.Pt(80)},\n    },\n    Header: /* ... */,\n    Body:   /* ... */,\n}\n",[18,30240,30241,30251,30255,30271,30287,30303,30323,30343,30363,30367,30377,30387],{"__ignoreMap":114},[118,30242,30243,30245,30247,30249],{"class":120,"line":121},[118,30244,143],{"class":142},[118,30246,1146],{"class":124},[118,30248,171],{"class":128},[118,30250,158],{"class":124},[118,30252,30253],{"class":120,"line":132},[118,30254,136],{"emptyLinePlaceholder":135},[118,30256,30257,30259,30261,30263,30265,30267,30269],{"class":120,"line":139},[118,30258,8025],{"class":226},[118,30260,230],{"class":124},[118,30262,8030],{"class":124},[118,30264,258],{"class":128},[118,30266,25],{"class":124},[118,30268,4929],{"class":128},[118,30270,7406],{"class":124},[118,30272,30273,30275,30277,30279,30281,30283,30285],{"class":120,"line":149},[118,30274,8043],{"class":226},[118,30276,6349],{"class":124},[118,30278,1127],{"class":124},[118,30280,258],{"class":128},[118,30282,25],{"class":124},[118,30284,8054],{"class":128},[118,30286,7406],{"class":124},[118,30288,30289,30291,30293,30295,30297,30299,30301],{"class":120,"line":161},[118,30290,8061],{"class":124},[118,30292,6607],{"class":226},[118,30294,6349],{"class":124},[118,30296,5225],{"class":226},[118,30298,25],{"class":124},[118,30300,8113],{"class":226},[118,30302,8191],{"class":124},[118,30304,30305,30307,30309,30311,30313,30315,30317,30319,30321],{"class":120,"line":166},[118,30306,8061],{"class":124},[118,30308,6607],{"class":226},[118,30310,6349],{"class":124},[118,30312,5225],{"class":226},[118,30314,25],{"class":124},[118,30316,6095],{"class":213},[118,30318,255],{"class":124},[118,30320,1510],{"class":299},[118,30322,8098],{"class":124},[118,30324,30325,30327,30329,30331,30333,30335,30337,30339,30341],{"class":120,"line":176},[118,30326,8061],{"class":124},[118,30328,6607],{"class":226},[118,30330,6349],{"class":124},[118,30332,5225],{"class":226},[118,30334,25],{"class":124},[118,30336,6095],{"class":213},[118,30338,255],{"class":124},[118,30340,11834],{"class":299},[118,30342,8098],{"class":124},[118,30344,30345,30347,30349,30351,30353,30355,30357,30359,30361],{"class":120,"line":186},[118,30346,8061],{"class":124},[118,30348,6607],{"class":226},[118,30350,6349],{"class":124},[118,30352,5225],{"class":226},[118,30354,25],{"class":124},[118,30356,6095],{"class":213},[118,30358,255],{"class":124},[118,30360,11834],{"class":299},[118,30362,8098],{"class":124},[118,30364,30365],{"class":120,"line":196},[118,30366,8140],{"class":124},[118,30368,30369,30371,30373,30375],{"class":120,"line":202},[118,30370,8145],{"class":226},[118,30372,6349],{"class":124},[118,30374,11852],{"class":3981},[118,30376,2643],{"class":124},[118,30378,30379,30381,30383,30385],{"class":120,"line":207},[118,30380,8160],{"class":226},[118,30382,6349],{"class":124},[118,30384,11863],{"class":3981},[118,30386,2643],{"class":124},[118,30388,30389],{"class":120,"line":223},[118,30390,1479],{"class":124},[14,30392,30393,30394,3096,30396,3096,30398,3096,30400,30402,30403,30405,30406,30408],{},"Mix and match: ",[18,30395,8113],{},[18,30397,6095],{},[18,30399,294],{},[18,30401,6616],{}," all work at the document layer. The first column with ",[18,30404,8113],{}," gets whatever's left after the three fixed columns are subtracted. This is closer to the CSS ",[18,30407,10038],{}," element than to a percentage system.",[14,30410,30411,30412,30415],{},"You give up the convenience of ",[18,30413,30414],{},"c.Table(header, rows, ...)"," doing the cell construction for you, but for invoices that need to print on physical letterhead with stable column positions, the trade is worth it.",[41,30417,6894],{"id":6893},[46,30419,30420,30425],{},[49,30421,30422,30424],{},[3163,30423,6902],{"href":6901}," — how the row's 12 columns become the parent width that table percentages resolve against",[49,30426,30427,8450,30429,30431],{},[3163,30428,6919],{"href":6918},[18,30430,11179],{}," in context with the rest of an invoice document",[41,30433,4794],{"id":4793},[14,30435,6933],{},[109,30437,30438],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,30439,30440],{"__ignoreMap":114},[118,30441,30442,30444,30446],{"class":120,"line":121},[118,30443,113],{"class":128},[118,30445,3156],{"class":433},[118,30447,3159],{"class":433},[14,30449,30450,3169,30453],{},[3163,30451,3168],{"href":3165,"rel":30452},[3167],[3163,30454,3174],{"href":3172,"rel":30455},[3167],[3176,30457,30458],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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);}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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":114,"searchDepth":132,"depth":132,"links":30460},[30461,30462,30463,30464,30466,30467,30468,30469,30470],{"id":4878,"depth":132,"text":4879},{"id":8545,"depth":132,"text":8546},{"id":29405,"depth":132,"text":29406},{"id":30028,"depth":132,"text":30465},"What the percentages are a percentage of",{"id":30064,"depth":132,"text":30065},{"id":30217,"depth":132,"text":30218},{"id":30231,"depth":132,"text":30232},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-04-28","Pass template.ColumnWidths(...) to c.Table. Values are percentages of the parent column's width. Sum to 100; trailing missing values auto-distribute.",{"name":30474,"totalTime":6973,"tools":30475,"steps":30476},"Set per-column widths on a table built with gpdf",[3202],[30477,30480,30483,30486],{"name":30478,"text":30479},"Build the table without widths first to confirm column count","Call c.Table(header, rows). With no ColumnWidths option, gpdf splits the parent column equally across the columns inferred from the header row.",{"name":30481,"text":30482},"Pass ColumnWidths as percentages summing to 100","Add template.ColumnWidths(40, 15, 20, 25) as the third argument to c.Table. Each value is a percentage of the table's parent width — the grid Col it lives in.",{"name":30484,"text":30485},"Use 0 to mark a column as auto-distributed","ColumnWidths(0, 30, 30) for a three-column table fixes the last two at 30% each and gives the remaining 40% to the first column.",{"name":30487,"text":30488},"Pass fewer widths than columns to let trailing columns share what's left","ColumnWidths(40, 30) on a three-column table assigns 40 / 30 / 30 — the third column gets the leftover 30 because it had no explicit entry.",{},{"title":12877,"description":30472},"blog/014.table-column-widths",[6996,3226],"KMX8jEZ0qSbUFiZDPJ7LmNlG5aBuLxxN8rJrKALvKXA",{"id":30495,"title":19179,"author":30496,"body":30497,"date":30471,"description":31372,"draft":3196,"extension":3197,"howTo":31373,"image":3220,"meta":31390,"navigation":135,"path":19178,"seo":31391,"stem":31392,"tags":31393,"updated":3220,"__hash__":31394},"blog/blog/015.embed-png-transparency.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":30498,"toc":31361},[30499,30501,30507,30509,30515,30580,30587,30591,30597,31177,31183,31187,31194,31201,31216,31223,31242,31246,31249,31255,31261,31265,31268,31281,31290,31293,31297,31304,31314,31316,31333,31335,31338,31350,31358],[41,30500,4879],{"id":4878},[14,30502,30503,30504,30506],{},"I have a logo or a stamp saved as a PNG with a transparent background — ",[18,30505,1780],{},", RGBA, the kind Photoshop or Figma exports. When I embed it in a gpdf PDF, will the transparent area stay transparent so my page color shows through? Or am I going to get a white box around the logo?",[41,30508,8546],{"id":8545},[14,30510,30511,30512,30514],{},"Pass the PNG bytes to ",[18,30513,18132],{}," and nothing else. gpdf decodes the alpha channel and writes a PDF SMask (soft mask) object alongside the image. Transparent pixels render as transparent.",[109,30516,30518],{"className":111,"code":30517,"language":113,"meta":114,"style":114},"logo, _ := os.ReadFile(\"logo.png\")\nc.Image(logo, template.FitWidth(document.Mm(40)))\n",[18,30519,30520,30546],{"__ignoreMap":114},[118,30521,30522,30524,30526,30528,30530,30532,30534,30536,30538,30540,30542,30544],{"class":120,"line":121},[118,30523,18589],{"class":226},[118,30525,395],{"class":124},[118,30527,3999],{"class":226},[118,30529,230],{"class":124},[118,30531,1447],{"class":226},[118,30533,25],{"class":124},[118,30535,9545],{"class":213},[118,30537,255],{"class":124},[118,30539,430],{"class":124},[118,30541,1780],{"class":433},[118,30543,430],{"class":124},[118,30545,199],{"class":124},[118,30547,30548,30550,30552,30554,30556,30558,30560,30562,30564,30566,30568,30570,30572,30574,30576,30578],{"class":120,"line":132},[118,30549,401],{"class":226},[118,30551,25],{"class":124},[118,30553,1773],{"class":213},[118,30555,255],{"class":124},[118,30557,18589],{"class":226},[118,30559,395],{"class":124},[118,30561,233],{"class":226},[118,30563,25],{"class":124},[118,30565,16581],{"class":213},[118,30567,255],{"class":124},[118,30569,258],{"class":226},[118,30571,25],{"class":124},[118,30573,294],{"class":213},[118,30575,255],{"class":124},[118,30577,9910],{"class":299},[118,30579,563],{"class":124},[14,30581,30582,30583,30586],{},"That is the entire recipe. ",[1629,30584,30585],{},"You do not flatten the alpha onto a white background. You do not convert RGBA to RGB. You do not pass an option to \"enable transparency\"."," The PNG stays a PNG, all the way to the PDF.",[41,30588,30590],{"id":30589},"a-complete-example-you-can-run","A complete example you can run",[14,30592,30593,30594,30596],{},"To actually see the alpha working, the PNG needs something underneath to show through. A watermark on top of body text is the canonical case — ",[18,30595,17956],{}," puts the logo at fixed coordinates while normal flow content fills the page below it.",[109,30598,30600],{"className":111,"code":30599,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    stamp, err := os.ReadFile(\"draft-stamp.png\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Quarterly report — Q1 2026\", template.FontSize(20), template.Bold())\n            c.Text(\"Revenue grew 38% year over year, driven by enterprise renewals and three new logos in financial services. Operating margin expanded to 24% as infrastructure spend flattened.\")\n            c.Text(\"Headcount ended the quarter at 142, up from 128 at the close of Q4. Engineering hires accounted for 9 of the 14 net adds.\")\n        })\n    })\n\n    page.Absolute(document.Mm(60), document.Mm(120), func(c *template.ColBuilder) {\n        c.Image(stamp, template.FitWidth(document.Mm(80)))\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"report-draft.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,30601,30602,30608,30612,30618,30626,30634,30638,30646,30654,30662,30666,30670,30680,30708,30720,30734,30738,30742,30756,30774,30804,30808,30812,30826,30830,30854,30884,30923,30942,30961,30965,30969,30973,31023,31058,31062,31066,31084,31096,31110,31114,31155,31169,31173],{"__ignoreMap":114},[118,30603,30604,30606],{"class":120,"line":121},[118,30605,125],{"class":124},[118,30607,129],{"class":128},[118,30609,30610],{"class":120,"line":132},[118,30611,136],{"emptyLinePlaceholder":135},[118,30613,30614,30616],{"class":120,"line":139},[118,30615,143],{"class":142},[118,30617,146],{"class":124},[118,30619,30620,30622,30624],{"class":120,"line":149},[118,30621,152],{"class":124},[118,30623,5303],{"class":128},[118,30625,158],{"class":124},[118,30627,30628,30630,30632],{"class":120,"line":161},[118,30629,152],{"class":124},[118,30631,155],{"class":128},[118,30633,158],{"class":124},[118,30635,30636],{"class":120,"line":166},[118,30637,136],{"emptyLinePlaceholder":135},[118,30639,30640,30642,30644],{"class":120,"line":176},[118,30641,152],{"class":124},[118,30643,3203],{"class":128},[118,30645,158],{"class":124},[118,30647,30648,30650,30652],{"class":120,"line":186},[118,30649,152],{"class":124},[118,30651,171],{"class":128},[118,30653,158],{"class":124},[118,30655,30656,30658,30660],{"class":120,"line":196},[118,30657,152],{"class":124},[118,30659,191],{"class":128},[118,30661,158],{"class":124},[118,30663,30664],{"class":120,"line":202},[118,30665,199],{"class":124},[118,30667,30668],{"class":120,"line":207},[118,30669,136],{"emptyLinePlaceholder":135},[118,30671,30672,30674,30676,30678],{"class":120,"line":223},[118,30673,210],{"class":124},[118,30675,214],{"class":213},[118,30677,217],{"class":124},[118,30679,220],{"class":124},[118,30681,30682,30685,30687,30689,30691,30693,30695,30697,30699,30701,30704,30706],{"class":120,"line":244},[118,30683,30684],{"class":226},"    stamp",[118,30686,395],{"class":124},[118,30688,1391],{"class":226},[118,30690,230],{"class":124},[118,30692,1447],{"class":226},[118,30694,25],{"class":124},[118,30696,9545],{"class":213},[118,30698,255],{"class":124},[118,30700,430],{"class":124},[118,30702,30703],{"class":433},"draft-stamp.png",[118,30705,430],{"class":124},[118,30707,199],{"class":124},[118,30709,30710,30712,30714,30716,30718],{"class":120,"line":269},[118,30711,1408],{"class":142},[118,30713,1391],{"class":226},[118,30715,1413],{"class":124},[118,30717,1416],{"class":124},[118,30719,220],{"class":124},[118,30721,30722,30724,30726,30728,30730,30732],{"class":120,"line":306},[118,30723,5818],{"class":226},[118,30725,25],{"class":124},[118,30727,5823],{"class":213},[118,30729,255],{"class":124},[118,30731,1429],{"class":226},[118,30733,199],{"class":124},[118,30735,30736],{"class":120,"line":312},[118,30737,1375],{"class":124},[118,30739,30740],{"class":120,"line":317},[118,30741,136],{"emptyLinePlaceholder":135},[118,30743,30744,30746,30748,30750,30752,30754],{"class":120,"line":350},[118,30745,227],{"class":226},[118,30747,230],{"class":124},[118,30749,3595],{"class":226},[118,30751,25],{"class":124},[118,30753,3600],{"class":213},[118,30755,241],{"class":124},[118,30757,30758,30760,30762,30764,30766,30768,30770,30772],{"class":120,"line":379},[118,30759,3607],{"class":226},[118,30761,25],{"class":124},[118,30763,252],{"class":213},[118,30765,255],{"class":124},[118,30767,1587],{"class":226},[118,30769,25],{"class":124},[118,30771,263],{"class":226},[118,30773,266],{"class":124},[118,30775,30776,30778,30780,30782,30784,30786,30788,30790,30792,30794,30796,30798,30800,30802],{"class":120,"line":417},[118,30777,3607],{"class":226},[118,30779,25],{"class":124},[118,30781,276],{"class":213},[118,30783,255],{"class":124},[118,30785,258],{"class":226},[118,30787,25],{"class":124},[118,30789,285],{"class":213},[118,30791,255],{"class":124},[118,30793,258],{"class":226},[118,30795,25],{"class":124},[118,30797,294],{"class":213},[118,30799,255],{"class":124},[118,30801,300],{"class":299},[118,30803,303],{"class":124},[118,30805,30806],{"class":120,"line":466},[118,30807,309],{"class":124},[118,30809,30810],{"class":120,"line":472},[118,30811,136],{"emptyLinePlaceholder":135},[118,30813,30814,30816,30818,30820,30822,30824],{"class":120,"line":503},[118,30815,5431],{"class":226},[118,30817,230],{"class":124},[118,30819,1185],{"class":226},[118,30821,25],{"class":124},[118,30823,1190],{"class":213},[118,30825,1193],{"class":124},[118,30827,30828],{"class":120,"line":537},[118,30829,136],{"emptyLinePlaceholder":135},[118,30831,30832,30834,30836,30838,30840,30842,30844,30846,30848,30850,30852],{"class":120,"line":566},[118,30833,5494],{"class":226},[118,30835,25],{"class":124},[118,30837,358],{"class":213},[118,30839,328],{"class":124},[118,30841,363],{"class":331},[118,30843,334],{"class":124},[118,30845,337],{"class":128},[118,30847,25],{"class":124},[118,30849,372],{"class":128},[118,30851,345],{"class":124},[118,30853,220],{"class":124},[118,30855,30856,30858,30860,30862,30864,30866,30868,30870,30872,30874,30876,30878,30880,30882],{"class":120,"line":571},[118,30857,1737],{"class":226},[118,30859,25],{"class":124},[118,30861,387],{"class":213},[118,30863,255],{"class":124},[118,30865,20],{"class":299},[118,30867,395],{"class":124},[118,30869,398],{"class":124},[118,30871,401],{"class":331},[118,30873,334],{"class":124},[118,30875,337],{"class":128},[118,30877,25],{"class":124},[118,30879,410],{"class":128},[118,30881,345],{"class":124},[118,30883,220],{"class":124},[118,30885,30886,30888,30890,30892,30894,30896,30899,30901,30903,30905,30907,30909,30911,30913,30915,30917,30919,30921],{"class":120,"line":577},[118,30887,1768],{"class":226},[118,30889,25],{"class":124},[118,30891,425],{"class":213},[118,30893,255],{"class":124},[118,30895,430],{"class":124},[118,30897,30898],{"class":433},"Quarterly report — Q1 2026",[118,30900,430],{"class":124},[118,30902,395],{"class":124},[118,30904,233],{"class":226},[118,30906,25],{"class":124},[118,30908,455],{"class":213},[118,30910,255],{"class":124},[118,30912,300],{"class":299},[118,30914,1280],{"class":124},[118,30916,233],{"class":226},[118,30918,25],{"class":124},[118,30920,445],{"class":213},[118,30922,1289],{"class":124},[118,30924,30925,30927,30929,30931,30933,30935,30938,30940],{"class":120,"line":602},[118,30926,1768],{"class":226},[118,30928,25],{"class":124},[118,30930,425],{"class":213},[118,30932,255],{"class":124},[118,30934,430],{"class":124},[118,30936,30937],{"class":433},"Revenue grew 38% year over year, driven by enterprise renewals and three new logos in financial services. Operating margin expanded to 24% as infrastructure spend flattened.",[118,30939,430],{"class":124},[118,30941,199],{"class":124},[118,30943,30944,30946,30948,30950,30952,30954,30957,30959],{"class":120,"line":633},[118,30945,1768],{"class":226},[118,30947,25],{"class":124},[118,30949,425],{"class":213},[118,30951,255],{"class":124},[118,30953,430],{"class":124},[118,30955,30956],{"class":433},"Headcount ended the quarter at 142, up from 128 at the close of Q4. Engineering hires accounted for 9 of the 14 net adds.",[118,30958,430],{"class":124},[118,30960,199],{"class":124},[118,30962,30963],{"class":120,"line":668},[118,30964,574],{"class":124},[118,30966,30967],{"class":120,"line":693},[118,30968,706],{"class":124},[118,30970,30971],{"class":120,"line":698},[118,30972,136],{"emptyLinePlaceholder":135},[118,30974,30975,30977,30979,30981,30983,30985,30987,30989,30991,30993,30995,30997,30999,31001,31003,31005,31007,31009,31011,31013,31015,31017,31019,31021],{"class":120,"line":703},[118,30976,5494],{"class":226},[118,30978,25],{"class":124},[118,30980,29233],{"class":213},[118,30982,255],{"class":124},[118,30984,258],{"class":226},[118,30986,25],{"class":124},[118,30988,294],{"class":213},[118,30990,255],{"class":124},[118,30992,31],{"class":299},[118,30994,1280],{"class":124},[118,30996,5225],{"class":226},[118,30998,25],{"class":124},[118,31000,294],{"class":213},[118,31002,255],{"class":124},[118,31004,19758],{"class":299},[118,31006,1280],{"class":124},[118,31008,398],{"class":124},[118,31010,401],{"class":331},[118,31012,334],{"class":124},[118,31014,337],{"class":128},[118,31016,25],{"class":124},[118,31018,410],{"class":128},[118,31020,345],{"class":124},[118,31022,220],{"class":124},[118,31024,31025,31027,31029,31031,31033,31036,31038,31040,31042,31044,31046,31048,31050,31052,31054,31056],{"class":120,"line":709},[118,31026,5966],{"class":226},[118,31028,25],{"class":124},[118,31030,1773],{"class":213},[118,31032,255],{"class":124},[118,31034,31035],{"class":226},"stamp",[118,31037,395],{"class":124},[118,31039,233],{"class":226},[118,31041,25],{"class":124},[118,31043,16581],{"class":213},[118,31045,255],{"class":124},[118,31047,258],{"class":226},[118,31049,25],{"class":124},[118,31051,294],{"class":213},[118,31053,255],{"class":124},[118,31055,11834],{"class":299},[118,31057,563],{"class":124},[118,31059,31060],{"class":120,"line":714},[118,31061,706],{"class":124},[118,31063,31064],{"class":120,"line":740},[118,31065,136],{"emptyLinePlaceholder":135},[118,31067,31068,31070,31072,31074,31076,31078,31080,31082],{"class":120,"line":765},[118,31069,5787],{"class":226},[118,31071,395],{"class":124},[118,31073,1391],{"class":226},[118,31075,230],{"class":124},[118,31077,1185],{"class":226},[118,31079,25],{"class":124},[118,31081,1400],{"class":213},[118,31083,1193],{"class":124},[118,31085,31086,31088,31090,31092,31094],{"class":120,"line":796},[118,31087,1408],{"class":142},[118,31089,1391],{"class":226},[118,31091,1413],{"class":124},[118,31093,1416],{"class":124},[118,31095,220],{"class":124},[118,31097,31098,31100,31102,31104,31106,31108],{"class":120,"line":819},[118,31099,5818],{"class":226},[118,31101,25],{"class":124},[118,31103,5823],{"class":213},[118,31105,255],{"class":124},[118,31107,1429],{"class":226},[118,31109,199],{"class":124},[118,31111,31112],{"class":120,"line":851},[118,31113,1375],{"class":124},[118,31115,31116,31118,31120,31122,31124,31126,31128,31130,31132,31135,31137,31139,31141,31143,31145,31147,31149,31151,31153],{"class":120,"line":875},[118,31117,1408],{"class":142},[118,31119,1391],{"class":226},[118,31121,230],{"class":124},[118,31123,1447],{"class":226},[118,31125,25],{"class":124},[118,31127,1452],{"class":213},[118,31129,255],{"class":124},[118,31131,430],{"class":124},[118,31133,31134],{"class":433},"report-draft.pdf",[118,31136,430],{"class":124},[118,31138,395],{"class":124},[118,31140,5859],{"class":226},[118,31142,395],{"class":124},[118,31144,1471],{"class":299},[118,31146,7902],{"class":124},[118,31148,1391],{"class":226},[118,31150,1413],{"class":124},[118,31152,1416],{"class":124},[118,31154,220],{"class":124},[118,31156,31157,31159,31161,31163,31165,31167],{"class":120,"line":880},[118,31158,5818],{"class":226},[118,31160,25],{"class":124},[118,31162,5823],{"class":213},[118,31164,255],{"class":124},[118,31166,1429],{"class":226},[118,31168,199],{"class":124},[118,31170,31171],{"class":120,"line":885},[118,31172,1375],{"class":124},[118,31174,31175],{"class":120,"line":910},[118,31176,1479],{"class":124},[14,31178,31179,31180,31182],{},"The \"DRAFT\" stamp is an RGBA PNG with bold red letters and a fully transparent background. When it lands on top of the body text, every transparent pixel reveals the paragraph underneath. Replace ",[18,31181,30703],{}," with any logo, seal, or signature image — same code path, same SMask handling.",[41,31184,31186],{"id":31185},"what-gpdf-actually-does-with-the-png","What gpdf actually does with the PNG",[14,31188,31189,31190,31193],{},"The interesting part is on the writer side. PDF doesn't have a single \"RGBA image\" object. It has an RGB image object plus an optional grayscale ",[1629,31191,31192],{},"SMask"," (soft mask) image, where each pixel of the mask is the alpha value for the corresponding pixel of the main image. The PDF reader composites them at render time.",[14,31195,31196,31197,31200],{},"When you hand gpdf a PNG, the renderer (",[18,31198,31199],{},"document/render/pdftarget.go",") walks the pixel grid once:",[46,31202,31203,31206,31209],{},[49,31204,31205],{},"24 bits of RGB go into the main image stream, FlateDecode compressed.",[49,31207,31208],{},"8 bits of alpha go into a separate SMask stream, also FlateDecode compressed.",[49,31210,31211,31212,31215],{},"The image dictionary gets ",[18,31213,31214],{},"/SMask \u003Cref>"," pointing at the alpha stream.",[14,31217,31218,31219,31222],{},"If every alpha sample turns out to be ",[18,31220,31221],{},"0xFF"," (fully opaque), gpdf throws the alpha buffer away and skips the SMask write. So a JPEG-style opaque PNG costs you nothing extra in the output. The cost only kicks in when the alpha is doing real work.",[14,31224,31225,31226,31229,31230,31233,31234,31237,31238,31241],{},"This whole path is pure Go — ",[18,31227,31228],{},"image/png"," from the standard library does the decode, ",[18,31231,31232],{},"compress/flate"," does the compression. ",[1629,31235,31236],{},"No CGO, no libpng dependency."," Cross-compiling from macOS to ",[18,31239,31240],{},"linux/arm64"," for a Lambda still produces a static binary.",[41,31243,31245],{"id":31244},"the-jpeg-trap","The JPEG trap",[14,31247,31248],{},"If your \"transparent\" logo came out of a tool that exported it as JPEG, transparency is already gone before gpdf sees the file. JPEG cannot carry an alpha channel. Whatever tool exported it has flattened the alpha onto whatever background color it felt like — usually white.",[14,31250,31251,31254],{},[18,31252,31253],{},"c.Image(jpegBytes)"," works fine, but the embedded image will have an opaque white (or black, or pink) rectangle where the transparent pixels used to be. The fix is upstream: re-export as PNG. There is no flag in gpdf that brings transparency back from a JPEG.",[14,31256,31257,31258,31260],{},"The same applies to \"PNG-8\" with palette transparency. gpdf's decoder uses Go's standard ",[18,31259,31228],{},", which handles palette PNGs correctly, so that case works. But if your asset pipeline accidentally went through a JPEG step, the data is lost.",[41,31262,31264],{"id":31263},"sizing-and-watermarks","Sizing and watermarks",[14,31266,31267],{},"Two practical extensions cover most real use cases.",[14,31269,31270,31273,31274,12386,31277,31280],{},[1629,31271,31272],{},"Scaling the logo",": pass ",[18,31275,31276],{},"template.FitWidth(document.Mm(40))",[18,31278,31279],{},"template.FitHeight(document.Mm(20))",". The PNG is decoded at full resolution, then scaled at render time using PDF's coordinate transform — no resampling step on the alpha. Crisp edges either way.",[14,31282,31283,31286,31287,31289],{},[1629,31284,31285],{},"Diagonal \"DRAFT\" watermarks",": bake the watermark as a PNG with a faint alpha (around 25–40%) and drop it on the page with ",[18,31288,17956],{},", the same way the example above places the stamp. Because the alpha is per-pixel, you can vary the opacity across the watermark — gradient fades, semi-transparent fills around solid logo lines. The PDF reader composites it correctly with the text underneath.",[14,31291,31292],{},"If you need a pixel-perfect 30%-opacity overlay, that's an alpha pre-bake decision in your image editor. gpdf reproduces whatever alpha values it finds; it does not provide a per-image opacity multiplier in the builder API.",[41,31294,31296],{"id":31295},"file-size-sanity-check","File size sanity check",[14,31298,31299,31300,31303],{},"PNG with alpha → RGB stream + grayscale SMask stream means ",[1629,31301,31302],{},"roughly 33% more bytes"," than the same image without alpha. A 100 KB opaque-PNG embed becomes ~133 KB with the alpha channel attached. For one logo this is invisible. For a 50-page report with a watermark on every page, it's still invisible — the SMask is registered once and referenced on each page, not duplicated.",[14,31305,31306,31307,12386,31310,31313],{},"If a single image suddenly costs you megabytes, it's the source PNG, not gpdf's encoding. Run it through ",[18,31308,31309],{},"pngquant",[18,31311,31312],{},"oxipng"," before embedding. The alpha channel survives both.",[41,31315,6894],{"id":6893},[46,31317,31318,31323,31328],{},[49,31319,31320,31322],{},[3163,31321,9792],{"href":9791}," — same \"just pass the bytes\" pattern, but for TrueType",[49,31324,31325,31327],{},[3163,31326,6919],{"href":6918}," — where a transparent company logo usually ends up in a real document",[49,31329,31330,31332],{},[3163,31331,4828],{"href":4069}," — what the pure-Go decode path costs (and saves) in microseconds",[41,31334,4794],{"id":4793},[14,31336,31337],{},"gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, pure-Go PNG and TrueType handling.",[109,31339,31340],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,31341,31342],{"__ignoreMap":114},[118,31343,31344,31346,31348],{"class":120,"line":121},[118,31345,113],{"class":128},[118,31347,3156],{"class":433},[118,31349,3159],{"class":433},[14,31351,31352,3169,31355],{},[3163,31353,3168],{"href":3165,"rel":31354},[3167],[3163,31356,3174],{"href":3172,"rel":31357},[3167],[3176,31359,31360],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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);}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 .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}",{"title":114,"searchDepth":132,"depth":132,"links":31362},[31363,31364,31365,31366,31367,31368,31369,31370,31371],{"id":4878,"depth":132,"text":4879},{"id":8545,"depth":132,"text":8546},{"id":30589,"depth":132,"text":30590},{"id":31185,"depth":132,"text":31186},{"id":31244,"depth":132,"text":31245},{"id":31263,"depth":132,"text":31264},{"id":31295,"depth":132,"text":31296},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"Pass the PNG bytes directly to c.Image. gpdf decodes the alpha channel into a PDF SMask so transparent backgrounds render correctly.",{"name":31374,"totalTime":19238,"tools":31375,"steps":31377},"Embed a transparent PNG (RGBA with alpha) in a gpdf document",[3202,31376],"An RGBA PNG file with an alpha channel (logo.png, stamp.png, etc.)",[31378,31381,31384,31387],{"name":31379,"text":31380},"Read the PNG bytes","Load the PNG with os.ReadFile into a []byte. Embedding via //go:embed works too if you want the asset compiled into the binary.",{"name":31382,"text":31383},"Pass the bytes straight to c.Image","Inside a column, call c.Image(pngBytes). Do not convert the PNG to RGB first — gpdf needs the alpha channel intact to reproduce transparency.",{"name":31385,"text":31386},"Place the image over existing content with page.Absolute for a watermark","Use page.Absolute(x, y, fn) to drop the PNG on top of body text. Transparent pixels let the text underneath show through, which is the whole point of a watermark.",{"name":31388,"text":31389},"Use FitWidth or FitHeight if the PNG is larger than your column","Pass template.FitWidth(document.Mm(40)) to scale the logo proportionally. Aspect ratio is preserved; alpha survives the resample.",{},{"title":19179,"description":31372},"blog/015.embed-png-transparency",[6996,3226],"cqpmFXcQpkyZJG-0J8YMVXHVxdtxNP6ruNALevZYDzk",{"id":31396,"title":9775,"author":31397,"body":31398,"date":33044,"description":33045,"draft":3196,"extension":3197,"howTo":33046,"image":3220,"meta":33062,"navigation":135,"path":9774,"seo":33063,"stem":33064,"tags":33065,"updated":3220,"__hash__":33066},"blog/blog/013.bold-italic-together.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":31399,"toc":33034},[31400,31402,31409,31411,31417,31457,31476,31479,31483,31935,31951,31955,31958,32024,32054,32058,32064,32204,32214,32217,32806,32818,32822,32829,32934,32953,32978,32980,33007,33009,33011,33023,33031],[41,31401,4879],{"id":4878},[14,31403,31404,31405,31408],{},"I want a single word — or a whole line — to be both ",[1629,31406,31407],{},"bold and italic"," in the same PDF. How do I set both at once, and why does it sometimes come out looking like neither?",[41,31410,8546],{"id":8545},[14,31412,31413,31414,31416],{},"Pass both options on the same ",[18,31415,8551],{}," call:",[109,31418,31420],{"className":111,"code":31419,"language":113,"meta":114,"style":114},"c.Text(\"WARNING\", template.Bold(), template.Italic())\n",[18,31421,31422],{"__ignoreMap":114},[118,31423,31424,31426,31428,31430,31432,31434,31437,31439,31441,31443,31445,31447,31449,31451,31453,31455],{"class":120,"line":121},[118,31425,401],{"class":226},[118,31427,25],{"class":124},[118,31429,425],{"class":213},[118,31431,255],{"class":124},[118,31433,430],{"class":124},[118,31435,31436],{"class":433},"WARNING",[118,31438,430],{"class":124},[118,31440,395],{"class":124},[118,31442,233],{"class":226},[118,31444,25],{"class":124},[118,31446,445],{"class":213},[118,31448,448],{"class":124},[118,31450,233],{"class":226},[118,31452,25],{"class":124},[118,31454,6483],{"class":213},[118,31456,1289],{"class":124},[14,31458,31459,31460,31463,31464,31467,31468,31471,31472,31475],{},"gpdf builds the variant ID ",[18,31461,31462],{},"Family-BoldItalic"," and looks it up in the registered fonts. For the Adobe Standard 14 families (Helvetica, Courier, Times) this just works — gpdf aliases ",[18,31465,31466],{},"-BoldItalic"," to the canonical ",[18,31469,31470],{},"-BoldOblique"," internally and uses the built-in AFM metrics. For a TrueType font you register yourself, ",[1629,31473,31474],{},"you have to register all four variants"," or the lookup silently falls back to the base family.",[14,31477,31478],{},"That second point is where most of the bugs live.",[41,31480,31482],{"id":31481},"working-code-helvetica-no-font-registration","Working code (Helvetica, no font registration)",[109,31484,31486],{"className":111,"code":31485,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Regular Helvetica.\")\n            c.Text(\"Bold only.\", template.Bold())\n            c.Text(\"Italic only.\", template.Italic())\n            c.Text(\"Bold and italic.\", template.Bold(), template.Italic())\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"emphasis.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,31487,31488,31494,31498,31504,31512,31520,31524,31532,31540,31548,31552,31556,31566,31580,31598,31628,31632,31636,31650,31674,31704,31723,31750,31777,31812,31816,31820,31824,31842,31854,31868,31872,31913,31927,31931],{"__ignoreMap":114},[118,31489,31490,31492],{"class":120,"line":121},[118,31491,125],{"class":124},[118,31493,129],{"class":128},[118,31495,31496],{"class":120,"line":132},[118,31497,136],{"emptyLinePlaceholder":135},[118,31499,31500,31502],{"class":120,"line":139},[118,31501,143],{"class":142},[118,31503,146],{"class":124},[118,31505,31506,31508,31510],{"class":120,"line":149},[118,31507,152],{"class":124},[118,31509,5303],{"class":128},[118,31511,158],{"class":124},[118,31513,31514,31516,31518],{"class":120,"line":161},[118,31515,152],{"class":124},[118,31517,155],{"class":128},[118,31519,158],{"class":124},[118,31521,31522],{"class":120,"line":166},[118,31523,136],{"emptyLinePlaceholder":135},[118,31525,31526,31528,31530],{"class":120,"line":176},[118,31527,152],{"class":124},[118,31529,3203],{"class":128},[118,31531,158],{"class":124},[118,31533,31534,31536,31538],{"class":120,"line":186},[118,31535,152],{"class":124},[118,31537,171],{"class":128},[118,31539,158],{"class":124},[118,31541,31542,31544,31546],{"class":120,"line":196},[118,31543,152],{"class":124},[118,31545,191],{"class":128},[118,31547,158],{"class":124},[118,31549,31550],{"class":120,"line":202},[118,31551,199],{"class":124},[118,31553,31554],{"class":120,"line":207},[118,31555,136],{"emptyLinePlaceholder":135},[118,31557,31558,31560,31562,31564],{"class":120,"line":223},[118,31559,210],{"class":124},[118,31561,214],{"class":213},[118,31563,217],{"class":124},[118,31565,220],{"class":124},[118,31567,31568,31570,31572,31574,31576,31578],{"class":120,"line":244},[118,31569,227],{"class":226},[118,31571,230],{"class":124},[118,31573,3595],{"class":226},[118,31575,25],{"class":124},[118,31577,3600],{"class":213},[118,31579,241],{"class":124},[118,31581,31582,31584,31586,31588,31590,31592,31594,31596],{"class":120,"line":269},[118,31583,3607],{"class":226},[118,31585,25],{"class":124},[118,31587,252],{"class":213},[118,31589,255],{"class":124},[118,31591,1587],{"class":226},[118,31593,25],{"class":124},[118,31595,263],{"class":226},[118,31597,266],{"class":124},[118,31599,31600,31602,31604,31606,31608,31610,31612,31614,31616,31618,31620,31622,31624,31626],{"class":120,"line":306},[118,31601,3607],{"class":226},[118,31603,25],{"class":124},[118,31605,276],{"class":213},[118,31607,255],{"class":124},[118,31609,258],{"class":226},[118,31611,25],{"class":124},[118,31613,285],{"class":213},[118,31615,255],{"class":124},[118,31617,258],{"class":226},[118,31619,25],{"class":124},[118,31621,294],{"class":213},[118,31623,255],{"class":124},[118,31625,300],{"class":299},[118,31627,303],{"class":124},[118,31629,31630],{"class":120,"line":312},[118,31631,309],{"class":124},[118,31633,31634],{"class":120,"line":317},[118,31635,136],{"emptyLinePlaceholder":135},[118,31637,31638,31640,31642,31644,31646,31648],{"class":120,"line":350},[118,31639,5431],{"class":226},[118,31641,230],{"class":124},[118,31643,1185],{"class":226},[118,31645,25],{"class":124},[118,31647,1190],{"class":213},[118,31649,1193],{"class":124},[118,31651,31652,31654,31656,31658,31660,31662,31664,31666,31668,31670,31672],{"class":120,"line":379},[118,31653,5494],{"class":226},[118,31655,25],{"class":124},[118,31657,358],{"class":213},[118,31659,328],{"class":124},[118,31661,363],{"class":331},[118,31663,334],{"class":124},[118,31665,337],{"class":128},[118,31667,25],{"class":124},[118,31669,372],{"class":128},[118,31671,345],{"class":124},[118,31673,220],{"class":124},[118,31675,31676,31678,31680,31682,31684,31686,31688,31690,31692,31694,31696,31698,31700,31702],{"class":120,"line":417},[118,31677,1737],{"class":226},[118,31679,25],{"class":124},[118,31681,387],{"class":213},[118,31683,255],{"class":124},[118,31685,20],{"class":299},[118,31687,395],{"class":124},[118,31689,398],{"class":124},[118,31691,401],{"class":331},[118,31693,334],{"class":124},[118,31695,337],{"class":128},[118,31697,25],{"class":124},[118,31699,410],{"class":128},[118,31701,345],{"class":124},[118,31703,220],{"class":124},[118,31705,31706,31708,31710,31712,31714,31716,31719,31721],{"class":120,"line":466},[118,31707,1768],{"class":226},[118,31709,25],{"class":124},[118,31711,425],{"class":213},[118,31713,255],{"class":124},[118,31715,430],{"class":124},[118,31717,31718],{"class":433},"Regular Helvetica.",[118,31720,430],{"class":124},[118,31722,199],{"class":124},[118,31724,31725,31727,31729,31731,31733,31735,31738,31740,31742,31744,31746,31748],{"class":120,"line":472},[118,31726,1768],{"class":226},[118,31728,25],{"class":124},[118,31730,425],{"class":213},[118,31732,255],{"class":124},[118,31734,430],{"class":124},[118,31736,31737],{"class":433},"Bold only.",[118,31739,430],{"class":124},[118,31741,395],{"class":124},[118,31743,233],{"class":226},[118,31745,25],{"class":124},[118,31747,445],{"class":213},[118,31749,1289],{"class":124},[118,31751,31752,31754,31756,31758,31760,31762,31765,31767,31769,31771,31773,31775],{"class":120,"line":503},[118,31753,1768],{"class":226},[118,31755,25],{"class":124},[118,31757,425],{"class":213},[118,31759,255],{"class":124},[118,31761,430],{"class":124},[118,31763,31764],{"class":433},"Italic only.",[118,31766,430],{"class":124},[118,31768,395],{"class":124},[118,31770,233],{"class":226},[118,31772,25],{"class":124},[118,31774,6483],{"class":213},[118,31776,1289],{"class":124},[118,31778,31779,31781,31783,31785,31787,31789,31792,31794,31796,31798,31800,31802,31804,31806,31808,31810],{"class":120,"line":537},[118,31780,1768],{"class":226},[118,31782,25],{"class":124},[118,31784,425],{"class":213},[118,31786,255],{"class":124},[118,31788,430],{"class":124},[118,31790,31791],{"class":433},"Bold and italic.",[118,31793,430],{"class":124},[118,31795,395],{"class":124},[118,31797,233],{"class":226},[118,31799,25],{"class":124},[118,31801,445],{"class":213},[118,31803,448],{"class":124},[118,31805,233],{"class":226},[118,31807,25],{"class":124},[118,31809,6483],{"class":213},[118,31811,1289],{"class":124},[118,31813,31814],{"class":120,"line":566},[118,31815,574],{"class":124},[118,31817,31818],{"class":120,"line":571},[118,31819,706],{"class":124},[118,31821,31822],{"class":120,"line":577},[118,31823,136],{"emptyLinePlaceholder":135},[118,31825,31826,31828,31830,31832,31834,31836,31838,31840],{"class":120,"line":602},[118,31827,5787],{"class":226},[118,31829,395],{"class":124},[118,31831,1391],{"class":226},[118,31833,230],{"class":124},[118,31835,1185],{"class":226},[118,31837,25],{"class":124},[118,31839,1400],{"class":213},[118,31841,1193],{"class":124},[118,31843,31844,31846,31848,31850,31852],{"class":120,"line":633},[118,31845,1408],{"class":142},[118,31847,1391],{"class":226},[118,31849,1413],{"class":124},[118,31851,1416],{"class":124},[118,31853,220],{"class":124},[118,31855,31856,31858,31860,31862,31864,31866],{"class":120,"line":668},[118,31857,5818],{"class":226},[118,31859,25],{"class":124},[118,31861,5823],{"class":213},[118,31863,255],{"class":124},[118,31865,1429],{"class":226},[118,31867,199],{"class":124},[118,31869,31870],{"class":120,"line":693},[118,31871,1375],{"class":124},[118,31873,31874,31876,31878,31880,31882,31884,31886,31888,31890,31893,31895,31897,31899,31901,31903,31905,31907,31909,31911],{"class":120,"line":698},[118,31875,1408],{"class":142},[118,31877,1391],{"class":226},[118,31879,230],{"class":124},[118,31881,1447],{"class":226},[118,31883,25],{"class":124},[118,31885,1452],{"class":213},[118,31887,255],{"class":124},[118,31889,430],{"class":124},[118,31891,31892],{"class":433},"emphasis.pdf",[118,31894,430],{"class":124},[118,31896,395],{"class":124},[118,31898,5859],{"class":226},[118,31900,395],{"class":124},[118,31902,1471],{"class":299},[118,31904,7902],{"class":124},[118,31906,1391],{"class":226},[118,31908,1413],{"class":124},[118,31910,1416],{"class":124},[118,31912,220],{"class":124},[118,31914,31915,31917,31919,31921,31923,31925],{"class":120,"line":703},[118,31916,5818],{"class":226},[118,31918,25],{"class":124},[118,31920,5823],{"class":213},[118,31922,255],{"class":124},[118,31924,1429],{"class":226},[118,31926,199],{"class":124},[118,31928,31929],{"class":120,"line":709},[118,31930,1375],{"class":124},[118,31932,31933],{"class":120,"line":714},[118,31934,1479],{"class":124},[14,31936,31937,31938,31940,31941,3096,31943,3096,31945,9386,31948,31950],{},"Four lines, four visible styles. No ",[18,31939,2798],{}," calls at all. The PDF ends up referencing ",[18,31942,8686],{},[18,31944,23758],{},[18,31946,31947],{},"Helvetica-Oblique",[18,31949,9385],{}," as non-embedded Type 1 entries, which every PDF viewer already has.",[41,31952,31954],{"id":31953},"what-gpdf-actually-does","What gpdf actually does",[14,31956,31957],{},"The resolver builds a variant ID from the style flags:",[1516,31959,31960,31975],{},[1519,31961,31962],{},[1522,31963,31964,31968,31972],{},[1525,31965,31966],{},[18,31967,21376],{},[1525,31969,31970],{},[18,31971,21381],{},[1525,31973,31974],{},"Variant ID looked up",[1532,31976,31977,31987,31997,32011],{},[1522,31978,31979,31981,31983],{},[1537,31980,20237],{},[1537,31982,20237],{},[1537,31984,31985],{},[18,31986,8686],{},[1522,31988,31989,31991,31993],{},[1537,31990,20249],{},[1537,31992,20237],{},[1537,31994,31995],{},[18,31996,23758],{},[1522,31998,31999,32001,32003],{},[1537,32000,20237],{},[1537,32002,20249],{},[1537,32004,32005,32008,32009],{},[18,32006,32007],{},"Helvetica-Italic"," → aliased to ",[18,32010,31947],{},[1522,32012,32013,32015,32017],{},[1537,32014,20249],{},[1537,32016,20249],{},[1537,32018,32019,32008,32022],{},[18,32020,32021],{},"Helvetica-BoldItalic",[18,32023,9385],{},[14,32025,32026,32027,32030,32031,1592,32034,32036,32037,32039,32040,54,32042,32039,32044,32046,32047,1592,32050,32053],{},"The alias step is the only thing that makes Helvetica special. ",[18,32028,32029],{},"buildFontVariantID"," always emits the generic ",[18,32032,32033],{},"-Italic",[18,32035,31466],{}," suffixes regardless of family; the Standard 14 init hook then points ",[18,32038,32007],{}," at ",[18,32041,31947],{},[18,32043,32021],{},[18,32045,9385],{}," so the metrics match what the viewer draws. Courier gets the same treatment. Times doesn't need the alias because its upright form is already called ",[18,32048,32049],{},"Times-Italic",[18,32051,32052],{},"Times-BoldItalic"," in the canonical Adobe names.",[41,32055,32057],{"id":32056},"the-trap-truetype-fonts-need-all-four-registrations","The trap: TrueType fonts need all four registrations",[14,32059,32060,32061,32063],{},"This is where CJK documents silently break. If you register Noto Sans JP but forget one of the variants, the missing slot does ",[1629,32062,18100],{}," fall through to Bold or Italic — it falls through straight to the base family.",[109,32065,32067],{"className":111,"code":32066,"language":113,"meta":114,"style":114},"// Looks fine. Isn't.\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", regular),\n    gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n)\n\n// This renders in plain NotoSansJP — not bold, not italic.\nc.Text(\"強調したい\", template.Bold(), template.Italic())\n",[18,32068,32069,32074,32088,32110,32133,32156,32160,32164,32169],{"__ignoreMap":114},[118,32070,32071],{"class":120,"line":121},[118,32072,32073],{"class":3981},"// Looks fine. Isn't.\n",[118,32075,32076,32078,32080,32082,32084,32086],{"class":120,"line":132},[118,32077,2760],{"class":226},[118,32079,230],{"class":124},[118,32081,3595],{"class":226},[118,32083,25],{"class":124},[118,32085,3600],{"class":213},[118,32087,241],{"class":124},[118,32089,32090,32092,32094,32096,32098,32100,32102,32104,32106,32108],{"class":120,"line":139},[118,32091,4532],{"class":226},[118,32093,25],{"class":124},[118,32095,2798],{"class":213},[118,32097,255],{"class":124},[118,32099,430],{"class":124},[118,32101,2805],{"class":433},[118,32103,430],{"class":124},[118,32105,395],{"class":124},[118,32107,21029],{"class":226},[118,32109,266],{"class":124},[118,32111,32112,32114,32116,32118,32120,32122,32125,32127,32129,32131],{"class":120,"line":149},[118,32113,4532],{"class":226},[118,32115,25],{"class":124},[118,32117,2798],{"class":213},[118,32119,255],{"class":124},[118,32121,430],{"class":124},[118,32123,32124],{"class":433},"NotoSansJP-Bold",[118,32126,430],{"class":124},[118,32128,395],{"class":124},[118,32130,21621],{"class":226},[118,32132,266],{"class":124},[118,32134,32135,32137,32139,32141,32143,32145,32147,32149,32151,32154],{"class":120,"line":161},[118,32136,4532],{"class":226},[118,32138,25],{"class":124},[118,32140,12501],{"class":213},[118,32142,255],{"class":124},[118,32144,430],{"class":124},[118,32146,2805],{"class":433},[118,32148,430],{"class":124},[118,32150,395],{"class":124},[118,32152,32153],{"class":299}," 12",[118,32155,266],{"class":124},[118,32157,32158],{"class":120,"line":166},[118,32159,199],{"class":124},[118,32161,32162],{"class":120,"line":176},[118,32163,136],{"emptyLinePlaceholder":135},[118,32165,32166],{"class":120,"line":186},[118,32167,32168],{"class":3981},"// This renders in plain NotoSansJP — not bold, not italic.\n",[118,32170,32171,32173,32175,32177,32179,32181,32184,32186,32188,32190,32192,32194,32196,32198,32200,32202],{"class":120,"line":196},[118,32172,401],{"class":226},[118,32174,25],{"class":124},[118,32176,425],{"class":213},[118,32178,255],{"class":124},[118,32180,430],{"class":124},[118,32182,32183],{"class":433},"強調したい",[118,32185,430],{"class":124},[118,32187,395],{"class":124},[118,32189,233],{"class":226},[118,32191,25],{"class":124},[118,32193,445],{"class":213},[118,32195,448],{"class":124},[118,32197,233],{"class":226},[118,32199,25],{"class":124},[118,32201,6483],{"class":213},[118,32203,1289],{"class":124},[14,32205,32206,32207,32210,32211,32213],{},"The reason is in the resolver. It tries ",[18,32208,32209],{},"NotoSansJP-BoldItalic"," first, misses, then falls back to exactly one thing: the base family ",[18,32212,2805],{},". There's no intermediate step that tries the bold version as a consolation prize. You asked for bold-italic, you got regular.",[14,32215,32216],{},"The fix is to register every variant you intend to use:",[109,32218,32220],{"className":111,"code":32219,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    regular := mustRead(\"NotoSansJP-Regular.ttf\")\n    bold := mustRead(\"NotoSansJP-Bold.ttf\")\n    italic := mustRead(\"NotoSansJP-Italic.ttf\")\n    boldItalic := mustRead(\"NotoSansJP-BoldItalic.ttf\")\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithFont(\"NotoSansJP\", regular),\n        gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n        gpdf.WithFont(\"NotoSansJP-Italic\", italic),\n        gpdf.WithFont(\"NotoSansJP-BoldItalic\", boldItalic),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"通常のテキスト\")\n            c.Text(\"強調\", template.Bold(), template.Italic())\n        })\n    })\n\n    data, _ := doc.Generate()\n    os.WriteFile(\"jp-emphasis.pdf\", data, 0o644)\n}\n\nfunc mustRead(path string) []byte {\n    b, err := os.ReadFile(path)\n    if err != nil { log.Fatal(err) }\n    return b\n}\n",[18,32221,32222,32228,32232,32238,32246,32254,32258,32266,32274,32282,32286,32290,32300,32320,32340,32360,32380,32384,32398,32416,32438,32460,32483,32505,32527,32531,32535,32549,32573,32603,32622,32657,32661,32665,32669,32687,32715,32719,32723,32744,32767,32795,32802],{"__ignoreMap":114},[118,32223,32224,32226],{"class":120,"line":121},[118,32225,125],{"class":124},[118,32227,129],{"class":128},[118,32229,32230],{"class":120,"line":132},[118,32231,136],{"emptyLinePlaceholder":135},[118,32233,32234,32236],{"class":120,"line":139},[118,32235,143],{"class":142},[118,32237,146],{"class":124},[118,32239,32240,32242,32244],{"class":120,"line":149},[118,32241,152],{"class":124},[118,32243,5303],{"class":128},[118,32245,158],{"class":124},[118,32247,32248,32250,32252],{"class":120,"line":161},[118,32249,152],{"class":124},[118,32251,155],{"class":128},[118,32253,158],{"class":124},[118,32255,32256],{"class":120,"line":166},[118,32257,136],{"emptyLinePlaceholder":135},[118,32259,32260,32262,32264],{"class":120,"line":176},[118,32261,152],{"class":124},[118,32263,3203],{"class":128},[118,32265,158],{"class":124},[118,32267,32268,32270,32272],{"class":120,"line":186},[118,32269,152],{"class":124},[118,32271,171],{"class":128},[118,32273,158],{"class":124},[118,32275,32276,32278,32280],{"class":120,"line":196},[118,32277,152],{"class":124},[118,32279,191],{"class":128},[118,32281,158],{"class":124},[118,32283,32284],{"class":120,"line":202},[118,32285,199],{"class":124},[118,32287,32288],{"class":120,"line":207},[118,32289,136],{"emptyLinePlaceholder":135},[118,32291,32292,32294,32296,32298],{"class":120,"line":223},[118,32293,210],{"class":124},[118,32295,214],{"class":213},[118,32297,217],{"class":124},[118,32299,220],{"class":124},[118,32301,32302,32305,32307,32310,32312,32314,32316,32318],{"class":120,"line":244},[118,32303,32304],{"class":226},"    regular ",[118,32306,230],{"class":124},[118,32308,32309],{"class":213}," mustRead",[118,32311,255],{"class":124},[118,32313,430],{"class":124},[118,32315,9552],{"class":433},[118,32317,430],{"class":124},[118,32319,199],{"class":124},[118,32321,32322,32325,32327,32329,32331,32333,32336,32338],{"class":120,"line":269},[118,32323,32324],{"class":226},"    bold ",[118,32326,230],{"class":124},[118,32328,32309],{"class":213},[118,32330,255],{"class":124},[118,32332,430],{"class":124},[118,32334,32335],{"class":433},"NotoSansJP-Bold.ttf",[118,32337,430],{"class":124},[118,32339,199],{"class":124},[118,32341,32342,32345,32347,32349,32351,32353,32356,32358],{"class":120,"line":306},[118,32343,32344],{"class":226},"    italic ",[118,32346,230],{"class":124},[118,32348,32309],{"class":213},[118,32350,255],{"class":124},[118,32352,430],{"class":124},[118,32354,32355],{"class":433},"NotoSansJP-Italic.ttf",[118,32357,430],{"class":124},[118,32359,199],{"class":124},[118,32361,32362,32365,32367,32369,32371,32373,32376,32378],{"class":120,"line":312},[118,32363,32364],{"class":226},"    boldItalic ",[118,32366,230],{"class":124},[118,32368,32309],{"class":213},[118,32370,255],{"class":124},[118,32372,430],{"class":124},[118,32374,32375],{"class":433},"NotoSansJP-BoldItalic.ttf",[118,32377,430],{"class":124},[118,32379,199],{"class":124},[118,32381,32382],{"class":120,"line":317},[118,32383,136],{"emptyLinePlaceholder":135},[118,32385,32386,32388,32390,32392,32394,32396],{"class":120,"line":350},[118,32387,227],{"class":226},[118,32389,230],{"class":124},[118,32391,3595],{"class":226},[118,32393,25],{"class":124},[118,32395,3600],{"class":213},[118,32397,241],{"class":124},[118,32399,32400,32402,32404,32406,32408,32410,32412,32414],{"class":120,"line":379},[118,32401,3607],{"class":226},[118,32403,25],{"class":124},[118,32405,252],{"class":213},[118,32407,255],{"class":124},[118,32409,1587],{"class":226},[118,32411,25],{"class":124},[118,32413,263],{"class":226},[118,32415,266],{"class":124},[118,32417,32418,32420,32422,32424,32426,32428,32430,32432,32434,32436],{"class":120,"line":417},[118,32419,3607],{"class":226},[118,32421,25],{"class":124},[118,32423,2798],{"class":213},[118,32425,255],{"class":124},[118,32427,430],{"class":124},[118,32429,2805],{"class":433},[118,32431,430],{"class":124},[118,32433,395],{"class":124},[118,32435,21029],{"class":226},[118,32437,266],{"class":124},[118,32439,32440,32442,32444,32446,32448,32450,32452,32454,32456,32458],{"class":120,"line":466},[118,32441,3607],{"class":226},[118,32443,25],{"class":124},[118,32445,2798],{"class":213},[118,32447,255],{"class":124},[118,32449,430],{"class":124},[118,32451,32124],{"class":433},[118,32453,430],{"class":124},[118,32455,395],{"class":124},[118,32457,21621],{"class":226},[118,32459,266],{"class":124},[118,32461,32462,32464,32466,32468,32470,32472,32475,32477,32479,32481],{"class":120,"line":472},[118,32463,3607],{"class":226},[118,32465,25],{"class":124},[118,32467,2798],{"class":213},[118,32469,255],{"class":124},[118,32471,430],{"class":124},[118,32473,32474],{"class":433},"NotoSansJP-Italic",[118,32476,430],{"class":124},[118,32478,395],{"class":124},[118,32480,21644],{"class":226},[118,32482,266],{"class":124},[118,32484,32485,32487,32489,32491,32493,32495,32497,32499,32501,32503],{"class":120,"line":503},[118,32486,3607],{"class":226},[118,32488,25],{"class":124},[118,32490,2798],{"class":213},[118,32492,255],{"class":124},[118,32494,430],{"class":124},[118,32496,32209],{"class":433},[118,32498,430],{"class":124},[118,32500,395],{"class":124},[118,32502,21667],{"class":226},[118,32504,266],{"class":124},[118,32506,32507,32509,32511,32513,32515,32517,32519,32521,32523,32525],{"class":120,"line":537},[118,32508,3607],{"class":226},[118,32510,25],{"class":124},[118,32512,12501],{"class":213},[118,32514,255],{"class":124},[118,32516,430],{"class":124},[118,32518,2805],{"class":433},[118,32520,430],{"class":124},[118,32522,395],{"class":124},[118,32524,32153],{"class":299},[118,32526,266],{"class":124},[118,32528,32529],{"class":120,"line":566},[118,32530,309],{"class":124},[118,32532,32533],{"class":120,"line":571},[118,32534,136],{"emptyLinePlaceholder":135},[118,32536,32537,32539,32541,32543,32545,32547],{"class":120,"line":577},[118,32538,5431],{"class":226},[118,32540,230],{"class":124},[118,32542,1185],{"class":226},[118,32544,25],{"class":124},[118,32546,1190],{"class":213},[118,32548,1193],{"class":124},[118,32550,32551,32553,32555,32557,32559,32561,32563,32565,32567,32569,32571],{"class":120,"line":602},[118,32552,5494],{"class":226},[118,32554,25],{"class":124},[118,32556,358],{"class":213},[118,32558,328],{"class":124},[118,32560,363],{"class":331},[118,32562,334],{"class":124},[118,32564,337],{"class":128},[118,32566,25],{"class":124},[118,32568,372],{"class":128},[118,32570,345],{"class":124},[118,32572,220],{"class":124},[118,32574,32575,32577,32579,32581,32583,32585,32587,32589,32591,32593,32595,32597,32599,32601],{"class":120,"line":633},[118,32576,1737],{"class":226},[118,32578,25],{"class":124},[118,32580,387],{"class":213},[118,32582,255],{"class":124},[118,32584,20],{"class":299},[118,32586,395],{"class":124},[118,32588,398],{"class":124},[118,32590,401],{"class":331},[118,32592,334],{"class":124},[118,32594,337],{"class":128},[118,32596,25],{"class":124},[118,32598,410],{"class":128},[118,32600,345],{"class":124},[118,32602,220],{"class":124},[118,32604,32605,32607,32609,32611,32613,32615,32618,32620],{"class":120,"line":668},[118,32606,1768],{"class":226},[118,32608,25],{"class":124},[118,32610,425],{"class":213},[118,32612,255],{"class":124},[118,32614,430],{"class":124},[118,32616,32617],{"class":433},"通常のテキスト",[118,32619,430],{"class":124},[118,32621,199],{"class":124},[118,32623,32624,32626,32628,32630,32632,32634,32637,32639,32641,32643,32645,32647,32649,32651,32653,32655],{"class":120,"line":693},[118,32625,1768],{"class":226},[118,32627,25],{"class":124},[118,32629,425],{"class":213},[118,32631,255],{"class":124},[118,32633,430],{"class":124},[118,32635,32636],{"class":433},"強調",[118,32638,430],{"class":124},[118,32640,395],{"class":124},[118,32642,233],{"class":226},[118,32644,25],{"class":124},[118,32646,445],{"class":213},[118,32648,448],{"class":124},[118,32650,233],{"class":226},[118,32652,25],{"class":124},[118,32654,6483],{"class":213},[118,32656,1289],{"class":124},[118,32658,32659],{"class":120,"line":698},[118,32660,574],{"class":124},[118,32662,32663],{"class":120,"line":703},[118,32664,706],{"class":124},[118,32666,32667],{"class":120,"line":709},[118,32668,136],{"emptyLinePlaceholder":135},[118,32670,32671,32673,32675,32677,32679,32681,32683,32685],{"class":120,"line":714},[118,32672,5787],{"class":226},[118,32674,395],{"class":124},[118,32676,3999],{"class":226},[118,32678,230],{"class":124},[118,32680,1185],{"class":226},[118,32682,25],{"class":124},[118,32684,1400],{"class":213},[118,32686,1193],{"class":124},[118,32688,32689,32692,32694,32696,32698,32700,32703,32705,32707,32709,32711,32713],{"class":120,"line":740},[118,32690,32691],{"class":226},"    os",[118,32693,25],{"class":124},[118,32695,1452],{"class":213},[118,32697,255],{"class":124},[118,32699,430],{"class":124},[118,32701,32702],{"class":433},"jp-emphasis.pdf",[118,32704,430],{"class":124},[118,32706,395],{"class":124},[118,32708,5859],{"class":226},[118,32710,395],{"class":124},[118,32712,1471],{"class":299},[118,32714,199],{"class":124},[118,32716,32717],{"class":120,"line":765},[118,32718,1479],{"class":124},[118,32720,32721],{"class":120,"line":796},[118,32722,136],{"emptyLinePlaceholder":135},[118,32724,32725,32727,32729,32731,32734,32736,32738,32740,32742],{"class":120,"line":819},[118,32726,210],{"class":124},[118,32728,32309],{"class":213},[118,32730,255],{"class":124},[118,32732,32733],{"class":331},"path",[118,32735,4996],{"class":1130},[118,32737,345],{"class":124},[118,32739,1127],{"class":124},[118,32741,5036],{"class":1130},[118,32743,220],{"class":124},[118,32745,32746,32749,32751,32753,32755,32757,32759,32761,32763,32765],{"class":120,"line":851},[118,32747,32748],{"class":226},"    b",[118,32750,395],{"class":124},[118,32752,1391],{"class":226},[118,32754,230],{"class":124},[118,32756,1447],{"class":226},[118,32758,25],{"class":124},[118,32760,9545],{"class":213},[118,32762,255],{"class":124},[118,32764,32733],{"class":226},[118,32766,199],{"class":124},[118,32768,32769,32771,32773,32775,32777,32779,32782,32784,32786,32788,32790,32792],{"class":120,"line":875},[118,32770,1408],{"class":142},[118,32772,1391],{"class":226},[118,32774,1413],{"class":124},[118,32776,1416],{"class":124},[118,32778,8081],{"class":124},[118,32780,32781],{"class":226}," log",[118,32783,25],{"class":124},[118,32785,5823],{"class":213},[118,32787,255],{"class":124},[118,32789,1429],{"class":226},[118,32791,345],{"class":124},[118,32793,32794],{"class":124}," }\n",[118,32796,32797,32799],{"class":120,"line":880},[118,32798,15778],{"class":142},[118,32800,32801],{"class":226}," b\n",[118,32803,32804],{"class":120,"line":885},[118,32805,1479],{"class":124},[14,32807,32808,32809,32811,32812,32814,32815,32817],{},"Noto Sans JP's official distribution doesn't actually ship a slanted italic cut — italic Japanese typography is unusual — so in practice most Japanese documents register only regular and bold and simply don't use ",[18,32810,21704],{}," on Japanese spans. That's fine. The rule is: if you never call ",[18,32813,21381],{}," on a family, you don't need its italic variant. The trap is only when you call ",[18,32816,21381],{}," and haven't registered the file.",[41,32819,32821],{"id":32820},"mixing-bold-and-italic-in-the-same-paragraph","Mixing bold and italic in the same paragraph",[14,32823,32824,32826,32827,6349],{},[18,32825,8551],{}," applies one style to the whole string. For mid-sentence emphasis use ",[18,32828,8559],{},[109,32830,32832],{"className":111,"code":32831,"language":113,"meta":114,"style":114},"c.RichText(func(rt *template.RichTextBuilder) {\n    rt.Span(\"The \")\n    rt.Span(\"quick brown fox\", template.Bold(), template.Italic())\n    rt.Span(\" jumps over the lazy dog.\")\n})\n",[18,32833,32834,32858,32876,32911,32930],{"__ignoreMap":114},[118,32835,32836,32838,32840,32842,32844,32846,32848,32850,32852,32854,32856],{"class":120,"line":121},[118,32837,401],{"class":226},[118,32839,25],{"class":124},[118,32841,8574],{"class":213},[118,32843,328],{"class":124},[118,32845,8579],{"class":331},[118,32847,334],{"class":124},[118,32849,337],{"class":128},[118,32851,25],{"class":124},[118,32853,8588],{"class":128},[118,32855,345],{"class":124},[118,32857,220],{"class":124},[118,32859,32860,32862,32864,32866,32868,32870,32872,32874],{"class":120,"line":132},[118,32861,8597],{"class":226},[118,32863,25],{"class":124},[118,32865,8602],{"class":213},[118,32867,255],{"class":124},[118,32869,430],{"class":124},[118,32871,8198],{"class":433},[118,32873,430],{"class":124},[118,32875,199],{"class":124},[118,32877,32878,32880,32882,32884,32886,32888,32891,32893,32895,32897,32899,32901,32903,32905,32907,32909],{"class":120,"line":139},[118,32879,8597],{"class":226},[118,32881,25],{"class":124},[118,32883,8602],{"class":213},[118,32885,255],{"class":124},[118,32887,430],{"class":124},[118,32889,32890],{"class":433},"quick brown fox",[118,32892,430],{"class":124},[118,32894,395],{"class":124},[118,32896,233],{"class":226},[118,32898,25],{"class":124},[118,32900,445],{"class":213},[118,32902,448],{"class":124},[118,32904,233],{"class":226},[118,32906,25],{"class":124},[118,32908,6483],{"class":213},[118,32910,1289],{"class":124},[118,32912,32913,32915,32917,32919,32921,32923,32926,32928],{"class":120,"line":149},[118,32914,8597],{"class":226},[118,32916,25],{"class":124},[118,32918,8602],{"class":213},[118,32920,255],{"class":124},[118,32922,430],{"class":124},[118,32924,32925],{"class":433}," jumps over the lazy dog.",[118,32927,430],{"class":124},[118,32929,199],{"class":124},[118,32931,32932],{"class":120,"line":161},[118,32933,1944],{"class":124},[14,32935,9396,32936,32938,32939,9731,32941,32943,32944,32946,32947,32949,32950,32952],{},[18,32937,9399],{}," gets its own style flags, and the layout engine line-breaks across spans the way a word processor would. ",[18,32940,21376],{},[18,32942,21381],{}," on a single ",[18,32945,8602],{}," hits the same ",[18,32948,31466],{}," variant lookup as ",[18,32951,8551],{}," — it's the same code path.",[14,32954,32955,32956,54,32958,32960,32961,54,32964,32967,32968,54,32971,32974,32975,32977],{},"One thing worth naming: ",[18,32957,21376],{},[18,32959,21381],{}," are commutative. ",[18,32962,32963],{},"template.Italic(), template.Bold()",[18,32965,32966],{},"template.Bold(), template.Italic()"," produce identical output. They're setting two different fields (",[18,32969,32970],{},"FontWeight",[18,32972,32973],{},"FontStyle",") on the same ",[18,32976,8555],{},", so ordering doesn't matter.",[41,32979,6894],{"id":6893},[46,32981,32982,32990,32996],{},[49,32983,32984,32986,32987,32989],{},[3163,32985,9792],{"href":9791}," — full ",[18,32988,2798],{}," walkthrough including the four-variant pattern",[49,32991,32992,32995],{},[3163,32993,32994],{"href":22032},"Why does my PDF show tofu boxes for Japanese?"," — what \"silent fallback\" looks like when the base family also isn't registered",[49,32997,32998,33002,33003,33006],{},[3163,32999,33001],{"href":33000},"/blog/noto-sans-jp-with-gpdf","How do I use Noto Sans JP with gpdf?"," — which Noto files to pick and how ",[18,33004,33005],{},"go:embed"," simplifies distribution",[41,33008,4794],{"id":4793},[14,33010,6933],{},[109,33012,33013],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,33014,33015],{"__ignoreMap":114},[118,33016,33017,33019,33021],{"class":120,"line":121},[118,33018,113],{"class":128},[118,33020,3156],{"class":433},[118,33022,3159],{"class":433},[14,33024,33025,3169,33028],{},[3163,33026,3168],{"href":3165,"rel":33027},[3167],[3163,33029,3174],{"href":3172,"rel":33030},[3167],[3176,33032,33033],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}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 .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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":114,"searchDepth":132,"depth":132,"links":33035},[33036,33037,33038,33039,33040,33041,33042,33043],{"id":4878,"depth":132,"text":4879},{"id":8545,"depth":132,"text":8546},{"id":31481,"depth":132,"text":31482},{"id":31953,"depth":132,"text":31954},{"id":32056,"depth":132,"text":32057},{"id":32820,"depth":132,"text":32821},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-04-23","Bold and italic on the same span work via template.Bold() and template.Italic(). For TrueType fonts you must register all four variants or the BoldItalic lookup silently falls back to the base family.",{"name":33047,"totalTime":6973,"tools":33048,"steps":33049},"Apply combined bold and italic to text in gpdf",[3202],[33050,33053,33056,33059],{"name":33051,"text":33052},"Pass both Bold() and Italic() on the same c.Text call","Call c.Text(\"caution\", template.Bold(), template.Italic()). gpdf builds the variant ID Family-BoldItalic and looks that up in the registered fonts.",{"name":33054,"text":33055},"Rely on the built-in alias for Helvetica and Courier","For Helvetica-BoldItalic and Courier-BoldItalic, gpdf aliases to the Adobe -BoldOblique metrics automatically. No WithFont call is needed for these Standard 14 families.",{"name":33057,"text":33058},"Register all four variants for a TrueType family","For NotoSansJP or any other TTF, call WithFont four times: NotoSansJP, NotoSansJP-Bold, NotoSansJP-Italic, and NotoSansJP-BoldItalic. Missing variants fall back to the base family, not to the bold one.",{"name":33060,"text":33061},"Mix bold and italic inside one paragraph with RichText","For emphasis inside a sentence, use c.RichText(func(rt) { rt.Span(\"plain\") ; rt.Span(\"bold-italic\", template.Bold(), template.Italic()) }).",{},{"title":9775,"description":33045},"blog/013.bold-italic-together",[6996,3226],"cOdahIAusF814yYx6jo1B3H78ZH1qyl_dByGYNVo9gA",{"id":33068,"title":6919,"author":33069,"body":33070,"date":35450,"description":35451,"draft":3196,"extension":3197,"howTo":35452,"image":3220,"meta":35470,"navigation":135,"path":6918,"seo":35471,"stem":35472,"tags":35473,"updated":3220,"__hash__":35474},"blog/blog/012.invoice-pdf-go-under-50-lines.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":33071,"toc":35431},[33072,33074,33087,33090,33102,33108,33112,33115,33118,33132,33135,33140,33143,33147,34005,34013,34017,34021,34024,34086,34095,34102,34109,34119,34123,34158,34180,34187,34191,34292,34304,34313,34319,34330,34334,34532,34535,34551,34569,34572,34583,34587,34638,34652,34656,34760,34773,34782,34786,34789,34802,34926,34929,34938,35045,35048,35056,35136,35148,35152,35218,35224,35237,35241,35244,35253,35270,35289,35299,35302,35304,35310,35328,35337,35356,35370,35384,35386,35389,35401,35409,35411,35428],[41,33073,44],{"id":43},[14,33075,33076,33077,33080,33081,33083,33084,33086],{},"A working invoice PDF in Go, end-to-end, in ",[1629,33078,33079],{},"50 lines",". One ",[18,33082,102],{},", one ",[18,33085,27085],{},", no Chromium, no CGO, no templating language, no HTML. Table, striped rows, right-aligned totals. It runs. The code is below, and the rest of this post is what each block does and where the pattern stops scaling.",[14,33088,33089],{},"If you just want to read the code first:",[109,33091,33092],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,33093,33094],{"__ignoreMap":114},[118,33095,33096,33098,33100],{"class":120,"line":121},[118,33097,113],{"class":128},[118,33099,3156],{"class":433},[118,33101,3159],{"class":433},[14,33103,33104,33105,33107],{},"Then paste ",[18,33106,102],{}," from the next section.",[41,33109,33111],{"id":33110},"why-under-50-lines-is-the-threshold-we-care-about","Why \"under 50 lines\" is the threshold we care about",[14,33113,33114],{},"The honest reason this post exists: most people Googling \"generate invoice pdf in go\" find blog posts that either (a) recommend spawning headless Chromium, or (b) show 400 lines of low-level PDF operators to render a single table. Both answers are technically correct. Neither is what the task is.",[14,33116,33117],{},"A reasonable invoice has:",[46,33119,33120,33123,33126,33129],{},[49,33121,33122],{},"A header with your company and the customer's",[49,33124,33125],{},"An invoice number and a due date",[49,33127,33128],{},"A line-item table",[49,33130,33131],{},"A total",[14,33133,33134],{},"That's four things. It should be four blocks of code. If it takes more than one screen, the library is wrong.",[14,33136,33137,33139],{},[1629,33138,33079],{}," is roughly the limit where the code still fits on one screen in a normal editor. It's also the threshold where a reviewer will read it end-to-end instead of skipping to the tests. Hitting it means you can paste the result into a Slack message and someone can learn the library from that message alone. That's the bar.",[14,33141,33142],{},"The code below is gofmt'd, all imports expanded, all error paths honored. No clever tricks, no helper package hidden elsewhere. What you see is what compiles.",[41,33144,33146],{"id":33145},"the-50-lines","The 50 lines",[109,33148,33150],{"className":111,"code":33149,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\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 := gpdf.NewDocument(template.WithPageSize(document.A4))\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"ACME Corp\", template.FontSize(22), template.Bold())\n            c.Text(\"123 Business St, San Francisco\")\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"INVOICE #INV-2026-001\", template.Bold(), template.AlignRight())\n            c.Text(\"Due: 2026-03-31\", template.AlignRight())\n        })\n    })\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Spacer(document.Mm(6))\n            c.Table(\n                []string{\"Description\", \"Qty\", \"Unit Price\", \"Amount\"},\n                [][]string{\n                    {\"Frontend dev\", \"40 hrs\", \"$150.00\", \"$6,000.00\"},\n                    {\"Backend dev\", \"60 hrs\", \"$150.00\", \"$9,000.00\"},\n                    {\"UI/UX design\", \"20 hrs\", \"$120.00\", \"$2,400.00\"},\n                },\n                template.ColumnWidths(40, 15, 20, 25),\n                template.TableHeaderStyle(template.Bold(), template.BgColor(pdf.RGBHex(0xF0F0F0))),\n                template.TableStripe(pdf.RGBHex(0xFAFAFA)),\n            )\n            c.Text(\"Total: $17,400.00\", template.AlignRight(), template.Bold(), template.FontSize(14))\n        })\n    })\n    b, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", b, 0644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,33151,33152,33158,33162,33168,33176,33184,33188,33196,33204,33212,33220,33224,33228,33238,33268,33282,33306,33336,33376,33395,33399,33429,33464,33491,33495,33499,33523,33553,33575,33585,33626,33634,33670,33706,33743,33747,33773,33812,33834,33838,33885,33889,33893,33911,33923,33937,33941,33983,33997,34001],{"__ignoreMap":114},[118,33153,33154,33156],{"class":120,"line":121},[118,33155,125],{"class":124},[118,33157,129],{"class":128},[118,33159,33160],{"class":120,"line":132},[118,33161,136],{"emptyLinePlaceholder":135},[118,33163,33164,33166],{"class":120,"line":139},[118,33165,143],{"class":142},[118,33167,146],{"class":124},[118,33169,33170,33172,33174],{"class":120,"line":149},[118,33171,152],{"class":124},[118,33173,5303],{"class":128},[118,33175,158],{"class":124},[118,33177,33178,33180,33182],{"class":120,"line":161},[118,33179,152],{"class":124},[118,33181,155],{"class":128},[118,33183,158],{"class":124},[118,33185,33186],{"class":120,"line":166},[118,33187,136],{"emptyLinePlaceholder":135},[118,33189,33190,33192,33194],{"class":120,"line":176},[118,33191,152],{"class":124},[118,33193,3203],{"class":128},[118,33195,158],{"class":124},[118,33197,33198,33200,33202],{"class":120,"line":186},[118,33199,152],{"class":124},[118,33201,171],{"class":128},[118,33203,158],{"class":124},[118,33205,33206,33208,33210],{"class":120,"line":196},[118,33207,152],{"class":124},[118,33209,181],{"class":128},[118,33211,158],{"class":124},[118,33213,33214,33216,33218],{"class":120,"line":202},[118,33215,152],{"class":124},[118,33217,191],{"class":128},[118,33219,158],{"class":124},[118,33221,33222],{"class":120,"line":207},[118,33223,199],{"class":124},[118,33225,33226],{"class":120,"line":223},[118,33227,136],{"emptyLinePlaceholder":135},[118,33229,33230,33232,33234,33236],{"class":120,"line":244},[118,33231,210],{"class":124},[118,33233,214],{"class":213},[118,33235,217],{"class":124},[118,33237,220],{"class":124},[118,33239,33240,33242,33244,33246,33248,33250,33252,33254,33256,33258,33260,33262,33264,33266],{"class":120,"line":269},[118,33241,227],{"class":226},[118,33243,230],{"class":124},[118,33245,3595],{"class":226},[118,33247,25],{"class":124},[118,33249,3600],{"class":213},[118,33251,255],{"class":124},[118,33253,337],{"class":226},[118,33255,25],{"class":124},[118,33257,252],{"class":213},[118,33259,255],{"class":124},[118,33261,258],{"class":226},[118,33263,25],{"class":124},[118,33265,263],{"class":226},[118,33267,463],{"class":124},[118,33269,33270,33272,33274,33276,33278,33280],{"class":120,"line":306},[118,33271,5431],{"class":226},[118,33273,230],{"class":124},[118,33275,1185],{"class":226},[118,33277,25],{"class":124},[118,33279,1190],{"class":213},[118,33281,1193],{"class":124},[118,33283,33284,33286,33288,33290,33292,33294,33296,33298,33300,33302,33304],{"class":120,"line":312},[118,33285,5494],{"class":226},[118,33287,25],{"class":124},[118,33289,358],{"class":213},[118,33291,328],{"class":124},[118,33293,363],{"class":331},[118,33295,334],{"class":124},[118,33297,337],{"class":128},[118,33299,25],{"class":124},[118,33301,372],{"class":128},[118,33303,345],{"class":124},[118,33305,220],{"class":124},[118,33307,33308,33310,33312,33314,33316,33318,33320,33322,33324,33326,33328,33330,33332,33334],{"class":120,"line":317},[118,33309,1737],{"class":226},[118,33311,25],{"class":124},[118,33313,387],{"class":213},[118,33315,255],{"class":124},[118,33317,392],{"class":299},[118,33319,395],{"class":124},[118,33321,398],{"class":124},[118,33323,401],{"class":331},[118,33325,334],{"class":124},[118,33327,337],{"class":128},[118,33329,25],{"class":124},[118,33331,410],{"class":128},[118,33333,345],{"class":124},[118,33335,220],{"class":124},[118,33337,33338,33340,33342,33344,33346,33348,33351,33353,33355,33357,33359,33361,33363,33366,33368,33370,33372,33374],{"class":120,"line":350},[118,33339,1768],{"class":226},[118,33341,25],{"class":124},[118,33343,425],{"class":213},[118,33345,255],{"class":124},[118,33347,430],{"class":124},[118,33349,33350],{"class":433},"ACME Corp",[118,33352,430],{"class":124},[118,33354,395],{"class":124},[118,33356,233],{"class":226},[118,33358,25],{"class":124},[118,33360,455],{"class":213},[118,33362,255],{"class":124},[118,33364,33365],{"class":299},"22",[118,33367,1280],{"class":124},[118,33369,233],{"class":226},[118,33371,25],{"class":124},[118,33373,445],{"class":213},[118,33375,1289],{"class":124},[118,33377,33378,33380,33382,33384,33386,33388,33391,33393],{"class":120,"line":379},[118,33379,1768],{"class":226},[118,33381,25],{"class":124},[118,33383,425],{"class":213},[118,33385,255],{"class":124},[118,33387,430],{"class":124},[118,33389,33390],{"class":433},"123 Business St, San Francisco",[118,33392,430],{"class":124},[118,33394,199],{"class":124},[118,33396,33397],{"class":120,"line":417},[118,33398,574],{"class":124},[118,33400,33401,33403,33405,33407,33409,33411,33413,33415,33417,33419,33421,33423,33425,33427],{"class":120,"line":466},[118,33402,1737],{"class":226},[118,33404,25],{"class":124},[118,33406,387],{"class":213},[118,33408,255],{"class":124},[118,33410,392],{"class":299},[118,33412,395],{"class":124},[118,33414,398],{"class":124},[118,33416,401],{"class":331},[118,33418,334],{"class":124},[118,33420,337],{"class":128},[118,33422,25],{"class":124},[118,33424,410],{"class":128},[118,33426,345],{"class":124},[118,33428,220],{"class":124},[118,33430,33431,33433,33435,33437,33439,33441,33444,33446,33448,33450,33452,33454,33456,33458,33460,33462],{"class":120,"line":472},[118,33432,1768],{"class":226},[118,33434,25],{"class":124},[118,33436,425],{"class":213},[118,33438,255],{"class":124},[118,33440,430],{"class":124},[118,33442,33443],{"class":433},"INVOICE #INV-2026-001",[118,33445,430],{"class":124},[118,33447,395],{"class":124},[118,33449,233],{"class":226},[118,33451,25],{"class":124},[118,33453,445],{"class":213},[118,33455,448],{"class":124},[118,33457,233],{"class":226},[118,33459,25],{"class":124},[118,33461,519],{"class":213},[118,33463,1289],{"class":124},[118,33465,33466,33468,33470,33472,33474,33476,33479,33481,33483,33485,33487,33489],{"class":120,"line":503},[118,33467,1768],{"class":226},[118,33469,25],{"class":124},[118,33471,425],{"class":213},[118,33473,255],{"class":124},[118,33475,430],{"class":124},[118,33477,33478],{"class":433},"Due: 2026-03-31",[118,33480,430],{"class":124},[118,33482,395],{"class":124},[118,33484,233],{"class":226},[118,33486,25],{"class":124},[118,33488,519],{"class":213},[118,33490,1289],{"class":124},[118,33492,33493],{"class":120,"line":537},[118,33494,574],{"class":124},[118,33496,33497],{"class":120,"line":566},[118,33498,706],{"class":124},[118,33500,33501,33503,33505,33507,33509,33511,33513,33515,33517,33519,33521],{"class":120,"line":571},[118,33502,5494],{"class":226},[118,33504,25],{"class":124},[118,33506,358],{"class":213},[118,33508,328],{"class":124},[118,33510,363],{"class":331},[118,33512,334],{"class":124},[118,33514,337],{"class":128},[118,33516,25],{"class":124},[118,33518,372],{"class":128},[118,33520,345],{"class":124},[118,33522,220],{"class":124},[118,33524,33525,33527,33529,33531,33533,33535,33537,33539,33541,33543,33545,33547,33549,33551],{"class":120,"line":577},[118,33526,1737],{"class":226},[118,33528,25],{"class":124},[118,33530,387],{"class":213},[118,33532,255],{"class":124},[118,33534,20],{"class":299},[118,33536,395],{"class":124},[118,33538,398],{"class":124},[118,33540,401],{"class":331},[118,33542,334],{"class":124},[118,33544,337],{"class":128},[118,33546,25],{"class":124},[118,33548,410],{"class":128},[118,33550,345],{"class":124},[118,33552,220],{"class":124},[118,33554,33555,33557,33559,33561,33563,33565,33567,33569,33571,33573],{"class":120,"line":602},[118,33556,1768],{"class":226},[118,33558,25],{"class":124},[118,33560,675],{"class":213},[118,33562,255],{"class":124},[118,33564,258],{"class":226},[118,33566,25],{"class":124},[118,33568,294],{"class":213},[118,33570,255],{"class":124},[118,33572,392],{"class":299},[118,33574,463],{"class":124},[118,33576,33577,33579,33581,33583],{"class":120,"line":633},[118,33578,1768],{"class":226},[118,33580,25],{"class":124},[118,33582,4929],{"class":213},[118,33584,241],{"class":124},[118,33586,33587,33589,33591,33593,33595,33597,33599,33601,33603,33605,33607,33609,33611,33614,33616,33618,33620,33622,33624],{"class":120,"line":668},[118,33588,19681],{"class":124},[118,33590,1131],{"class":1130},[118,33592,1134],{"class":124},[118,33594,430],{"class":124},[118,33596,14460],{"class":433},[118,33598,430],{"class":124},[118,33600,395],{"class":124},[118,33602,1146],{"class":124},[118,33604,14469],{"class":433},[118,33606,430],{"class":124},[118,33608,395],{"class":124},[118,33610,1146],{"class":124},[118,33612,33613],{"class":433},"Unit Price",[118,33615,430],{"class":124},[118,33617,395],{"class":124},[118,33619,1146],{"class":124},[118,33621,7320],{"class":433},[118,33623,430],{"class":124},[118,33625,8191],{"class":124},[118,33627,33628,33630,33632],{"class":120,"line":693},[118,33629,19725],{"class":124},[118,33631,1131],{"class":1130},[118,33633,7406],{"class":124},[118,33635,33636,33638,33640,33642,33644,33646,33648,33650,33652,33654,33656,33658,33660,33662,33664,33666,33668],{"class":120,"line":698},[118,33637,19734],{"class":124},[118,33639,430],{"class":124},[118,33641,24374],{"class":433},[118,33643,430],{"class":124},[118,33645,395],{"class":124},[118,33647,1146],{"class":124},[118,33649,24383],{"class":433},[118,33651,430],{"class":124},[118,33653,395],{"class":124},[118,33655,1146],{"class":124},[118,33657,24392],{"class":433},[118,33659,430],{"class":124},[118,33661,395],{"class":124},[118,33663,1146],{"class":124},[118,33665,24401],{"class":433},[118,33667,430],{"class":124},[118,33669,8191],{"class":124},[118,33671,33672,33674,33676,33678,33680,33682,33684,33686,33688,33690,33692,33694,33696,33698,33700,33702,33704],{"class":120,"line":703},[118,33673,19734],{"class":124},[118,33675,430],{"class":124},[118,33677,24414],{"class":433},[118,33679,430],{"class":124},[118,33681,395],{"class":124},[118,33683,1146],{"class":124},[118,33685,24423],{"class":433},[118,33687,430],{"class":124},[118,33689,395],{"class":124},[118,33691,1146],{"class":124},[118,33693,24392],{"class":433},[118,33695,430],{"class":124},[118,33697,395],{"class":124},[118,33699,1146],{"class":124},[118,33701,24440],{"class":433},[118,33703,430],{"class":124},[118,33705,8191],{"class":124},[118,33707,33708,33710,33712,33715,33717,33719,33721,33723,33725,33727,33729,33731,33733,33735,33737,33739,33741],{"class":120,"line":709},[118,33709,19734],{"class":124},[118,33711,430],{"class":124},[118,33713,33714],{"class":433},"UI/UX design",[118,33716,430],{"class":124},[118,33718,395],{"class":124},[118,33720,1146],{"class":124},[118,33722,24462],{"class":433},[118,33724,430],{"class":124},[118,33726,395],{"class":124},[118,33728,1146],{"class":124},[118,33730,24471],{"class":433},[118,33732,430],{"class":124},[118,33734,395],{"class":124},[118,33736,1146],{"class":124},[118,33738,24480],{"class":433},[118,33740,430],{"class":124},[118,33742,8191],{"class":124},[118,33744,33745],{"class":120,"line":714},[118,33746,19978],{"class":124},[118,33748,33749,33751,33753,33755,33757,33759,33761,33763,33765,33767,33769,33771],{"class":120,"line":740},[118,33750,2648],{"class":226},[118,33752,25],{"class":124},[118,33754,7733],{"class":213},[118,33756,255],{"class":124},[118,33758,9910],{"class":299},[118,33760,395],{"class":124},[118,33762,9915],{"class":299},[118,33764,395],{"class":124},[118,33766,7742],{"class":299},[118,33768,395],{"class":124},[118,33770,9924],{"class":299},[118,33772,266],{"class":124},[118,33774,33775,33777,33779,33781,33783,33785,33787,33789,33791,33793,33795,33797,33799,33801,33803,33805,33807,33810],{"class":120,"line":765},[118,33776,2648],{"class":226},[118,33778,25],{"class":124},[118,33780,7762],{"class":213},[118,33782,255],{"class":124},[118,33784,337],{"class":226},[118,33786,25],{"class":124},[118,33788,445],{"class":213},[118,33790,448],{"class":124},[118,33792,233],{"class":226},[118,33794,25],{"class":124},[118,33796,7792],{"class":213},[118,33798,255],{"class":124},[118,33800,550],{"class":226},[118,33802,25],{"class":124},[118,33804,658],{"class":213},[118,33806,255],{"class":124},[118,33808,33809],{"class":299},"0xF0F0F0",[118,33811,303],{"class":124},[118,33813,33814,33816,33818,33820,33822,33824,33826,33828,33830,33832],{"class":120,"line":796},[118,33815,2648],{"class":226},[118,33817,25],{"class":124},[118,33819,9973],{"class":213},[118,33821,255],{"class":124},[118,33823,550],{"class":226},[118,33825,25],{"class":124},[118,33827,658],{"class":213},[118,33829,255],{"class":124},[118,33831,20330],{"class":299},[118,33833,3646],{"class":124},[118,33835,33836],{"class":120,"line":819},[118,33837,7809],{"class":124},[118,33839,33840,33842,33844,33846,33848,33850,33853,33855,33857,33859,33861,33863,33865,33867,33869,33871,33873,33875,33877,33879,33881,33883],{"class":120,"line":851},[118,33841,1768],{"class":226},[118,33843,25],{"class":124},[118,33845,425],{"class":213},[118,33847,255],{"class":124},[118,33849,430],{"class":124},[118,33851,33852],{"class":433},"Total: $17,400.00",[118,33854,430],{"class":124},[118,33856,395],{"class":124},[118,33858,233],{"class":226},[118,33860,25],{"class":124},[118,33862,519],{"class":213},[118,33864,448],{"class":124},[118,33866,233],{"class":226},[118,33868,25],{"class":124},[118,33870,445],{"class":213},[118,33872,448],{"class":124},[118,33874,233],{"class":226},[118,33876,25],{"class":124},[118,33878,455],{"class":213},[118,33880,255],{"class":124},[118,33882,1877],{"class":299},[118,33884,463],{"class":124},[118,33886,33887],{"class":120,"line":875},[118,33888,574],{"class":124},[118,33890,33891],{"class":120,"line":880},[118,33892,706],{"class":124},[118,33894,33895,33897,33899,33901,33903,33905,33907,33909],{"class":120,"line":885},[118,33896,32748],{"class":226},[118,33898,395],{"class":124},[118,33900,1391],{"class":226},[118,33902,230],{"class":124},[118,33904,1185],{"class":226},[118,33906,25],{"class":124},[118,33908,1400],{"class":213},[118,33910,1193],{"class":124},[118,33912,33913,33915,33917,33919,33921],{"class":120,"line":910},[118,33914,1408],{"class":142},[118,33916,1391],{"class":226},[118,33918,1413],{"class":124},[118,33920,1416],{"class":124},[118,33922,220],{"class":124},[118,33924,33925,33927,33929,33931,33933,33935],{"class":120,"line":941},[118,33926,5818],{"class":226},[118,33928,25],{"class":124},[118,33930,5823],{"class":213},[118,33932,255],{"class":124},[118,33934,1429],{"class":226},[118,33936,199],{"class":124},[118,33938,33939],{"class":120,"line":974},[118,33940,1375],{"class":124},[118,33942,33943,33945,33947,33949,33951,33953,33955,33957,33959,33961,33963,33965,33968,33970,33973,33975,33977,33979,33981],{"class":120,"line":997},[118,33944,1408],{"class":142},[118,33946,1391],{"class":226},[118,33948,230],{"class":124},[118,33950,1447],{"class":226},[118,33952,25],{"class":124},[118,33954,1452],{"class":213},[118,33956,255],{"class":124},[118,33958,430],{"class":124},[118,33960,4015],{"class":433},[118,33962,430],{"class":124},[118,33964,395],{"class":124},[118,33966,33967],{"class":226}," b",[118,33969,395],{"class":124},[118,33971,33972],{"class":299}," 0644",[118,33974,7902],{"class":124},[118,33976,1391],{"class":226},[118,33978,1413],{"class":124},[118,33980,1416],{"class":124},[118,33982,220],{"class":124},[118,33984,33985,33987,33989,33991,33993,33995],{"class":120,"line":1002},[118,33986,5818],{"class":226},[118,33988,25],{"class":124},[118,33990,5823],{"class":213},[118,33992,255],{"class":124},[118,33994,1429],{"class":226},[118,33996,199],{"class":124},[118,33998,33999],{"class":120,"line":1033},[118,34000,1375],{"class":124},[118,34002,34003],{"class":120,"line":1065},[118,34004,1479],{"class":124},[14,34006,34007,34009,34010,34012],{},[18,34008,7077],{}," produces ",[18,34011,4015],{}," in the current directory. On an M1 the whole program finishes in a few milliseconds — the actual PDF generation is under 150 µs, the rest is process startup.",[41,34014,34016],{"id":34015},"what-each-block-is-doing","What each block is doing",[1613,34018,34020],{"id":34019},"imports","Imports",[14,34022,34023],{},"Four packages from gpdf:",[46,34025,34026,34038,34065,34077],{},[49,34027,34028,34030,34031,34033,34034,34037],{},[18,34029,3203],{}," — the facade. The only thing we use from it is ",[18,34032,22561],{},", which is a thin wrapper over ",[18,34035,34036],{},"template.New"," with sensible defaults.",[49,34039,34040,34042,34043,3096,34045,3096,34047,3096,34049,3096,34051,3096,34053,34055,34056,3096,34058,3096,34061,34064],{},[18,34041,171],{}," — units (",[18,34044,294],{},[18,34046,6095],{},[18,34048,11906],{},[18,34050,11909],{},[18,34052,11912],{},[18,34054,6616],{},"), page sizes (",[18,34057,263],{},[18,34059,34060],{},"Letter",[18,34062,34063],{},"Legal","), margins.",[49,34066,34067,34069,34070,3096,34072,34074,34075,24827],{},[18,34068,181],{}," — color primitives (",[18,34071,658],{},[18,34073,555],{},", named constants like ",[18,34076,20647],{},[49,34078,34079,34081,34082,34085],{},[18,34080,191],{}," — the builder API. Everything that starts with ",[18,34083,34084],{},"template."," (options, layout functions, style modifiers) comes from here.",[14,34087,34088,34089,34091,34092,34094],{},"If the four-package split feels like a lot, that's by design. The ",[18,34090,550],{}," package is the low-level writer — you almost never touch it directly — but it exposes color types because colors are shared between text and tables and lines, and shoving them into ",[18,34093,337],{}," would make that package enormous. The other three you'll import in every file.",[14,34096,34097,34098,27274,34100,6349],{},"No external dependencies. ",[18,34099,27266],{},[18,34101,29121],{},[109,34103,34107],{"className":34104,"code":34106,"language":4993},[34105],"language-text","require github.com/gpdf-dev/gpdf v1.x.x\n",[18,34108,34106],{"__ignoreMap":114},[14,34110,34111,34112,34114,34115,34118],{},"That's the whole ",[18,34113,27269],{}," block. No ",[18,34116,34117],{},"indirect"," explosion.",[1613,34120,34122],{"id":34121},"document-construction","Document construction",[109,34124,34126],{"className":111,"code":34125,"language":113,"meta":114,"style":114},"doc := gpdf.NewDocument(template.WithPageSize(document.A4))\n",[18,34127,34128],{"__ignoreMap":114},[118,34129,34130,34132,34134,34136,34138,34140,34142,34144,34146,34148,34150,34152,34154,34156],{"class":120,"line":121},[118,34131,2760],{"class":226},[118,34133,230],{"class":124},[118,34135,3595],{"class":226},[118,34137,25],{"class":124},[118,34139,3600],{"class":213},[118,34141,255],{"class":124},[118,34143,337],{"class":226},[118,34145,25],{"class":124},[118,34147,252],{"class":213},[118,34149,255],{"class":124},[118,34151,258],{"class":226},[118,34153,25],{"class":124},[118,34155,263],{"class":226},[118,34157,463],{"class":124},[14,34159,34160,34162,34163,34166,34167,34170,34171,20607,34173,34175,34176,34179],{},[18,34161,22561],{}," takes a variadic ",[18,34164,34165],{},"...template.Option",". All configuration — page size, margins, default font, metadata, custom fonts — is a ",[18,34168,34169],{},"WithXxx"," option. If you want US Letter, swap ",[18,34172,17930],{},[18,34174,17933],{},". Default margins are 20 mm; override with ",[18,34177,34178],{},"template.WithMargins(document.UniformEdges(document.Mm(15)))"," if you want tighter.",[14,34181,34182,34183,34186],{},"The invoice above ships with whatever the library's default font is (Helvetica equivalent, built-in, no TTF required). For Japanese, Chinese, or Korean invoices you register a TTF with ",[18,34184,34185],{},"template.WithFont"," — that's a different post and it's the main reason people come to gpdf, but for a dollar-USD invoice you don't need it.",[1613,34188,34190],{"id":34189},"the-header-row","The header row",[109,34192,34194],{"className":111,"code":34193,"language":113,"meta":114,"style":114},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(6, func(c *template.ColBuilder) { ... })\n    r.Col(6, func(c *template.ColBuilder) { ... })\n})\n",[18,34195,34196,34220,34254,34288],{"__ignoreMap":114},[118,34197,34198,34200,34202,34204,34206,34208,34210,34212,34214,34216,34218],{"class":120,"line":121},[118,34199,5910],{"class":226},[118,34201,25],{"class":124},[118,34203,358],{"class":213},[118,34205,328],{"class":124},[118,34207,363],{"class":331},[118,34209,334],{"class":124},[118,34211,337],{"class":128},[118,34213,25],{"class":124},[118,34215,372],{"class":128},[118,34217,345],{"class":124},[118,34219,220],{"class":124},[118,34221,34222,34224,34226,34228,34230,34232,34234,34236,34238,34240,34242,34244,34246,34248,34250,34252],{"class":120,"line":132},[118,34223,5935],{"class":226},[118,34225,25],{"class":124},[118,34227,387],{"class":213},[118,34229,255],{"class":124},[118,34231,392],{"class":299},[118,34233,395],{"class":124},[118,34235,398],{"class":124},[118,34237,401],{"class":331},[118,34239,334],{"class":124},[118,34241,337],{"class":128},[118,34243,25],{"class":124},[118,34245,410],{"class":128},[118,34247,345],{"class":124},[118,34249,8081],{"class":124},[118,34251,5004],{"class":124},[118,34253,27880],{"class":124},[118,34255,34256,34258,34260,34262,34264,34266,34268,34270,34272,34274,34276,34278,34280,34282,34284,34286],{"class":120,"line":139},[118,34257,5935],{"class":226},[118,34259,25],{"class":124},[118,34261,387],{"class":213},[118,34263,255],{"class":124},[118,34265,392],{"class":299},[118,34267,395],{"class":124},[118,34269,398],{"class":124},[118,34271,401],{"class":331},[118,34273,334],{"class":124},[118,34275,337],{"class":128},[118,34277,25],{"class":124},[118,34279,410],{"class":128},[118,34281,345],{"class":124},[118,34283,8081],{"class":124},[118,34285,5004],{"class":124},[118,34287,27880],{"class":124},[118,34289,34290],{"class":120,"line":149},[118,34291,1944],{"class":124},[14,34293,34294,34295,34297,34298,34300,34301,34303],{},"This is the part that surprises people coming from gofpdf or gopdf: gpdf uses a ",[1629,34296,22882],{},", same mental model as Bootstrap. A row has 12 units of horizontal space. ",[18,34299,11193],{}," takes half. Two ",[18,34302,6658],{}," calls add up to 12, which fills the row exactly.",[14,34305,34306,34308,34309,34312],{},[18,34307,358],{}," means the row's height is whatever its tallest column needs. If you want a fixed-height row — useful for card layouts — there's ",[18,34310,34311],{},"FixedRow(height, fn)",". For an invoice, auto is what you want.",[14,34314,34315,34316,34318],{},"Inside each column, you stack ",[18,34317,2741],{}," calls top to bottom. No explicit positioning. The builder tracks a cursor internally and advances it by the rendered height of each element.",[14,34320,34321,34322,34325,34326,34329],{},"The right-side column uses ",[18,34323,34324],{},"template.AlignRight()",". Text options are composable — ",[18,34327,34328],{},"c.Text(\"INVOICE\", template.Bold(), template.AlignRight(), template.FontSize(20))"," just layers three modifiers on one call. The order doesn't matter.",[1613,34331,34333],{"id":34332},"the-items-table","The items table",[109,34335,34337],{"className":111,"code":34336,"language":113,"meta":114,"style":114},"c.Table(\n    []string{\"Description\", \"Qty\", \"Unit Price\", \"Amount\"},\n    [][]string{\n        {\"Frontend dev\", \"40 hrs\", \"$150.00\", \"$6,000.00\"},\n        ...\n    },\n    template.ColumnWidths(40, 15, 20, 25),\n    template.TableHeaderStyle(template.Bold(), template.BgColor(pdf.RGBHex(0xF0F0F0))),\n    template.TableStripe(pdf.RGBHex(0xFAFAFA)),\n)\n",[18,34338,34339,34349,34389,34397,34433,34438,34442,34468,34506,34528],{"__ignoreMap":114},[118,34340,34341,34343,34345,34347],{"class":120,"line":121},[118,34342,401],{"class":226},[118,34344,25],{"class":124},[118,34346,4929],{"class":213},[118,34348,241],{"class":124},[118,34350,34351,34353,34355,34357,34359,34361,34363,34365,34367,34369,34371,34373,34375,34377,34379,34381,34383,34385,34387],{"class":120,"line":132},[118,34352,12534],{"class":124},[118,34354,1131],{"class":1130},[118,34356,1134],{"class":124},[118,34358,430],{"class":124},[118,34360,14460],{"class":433},[118,34362,430],{"class":124},[118,34364,395],{"class":124},[118,34366,1146],{"class":124},[118,34368,14469],{"class":433},[118,34370,430],{"class":124},[118,34372,395],{"class":124},[118,34374,1146],{"class":124},[118,34376,33613],{"class":433},[118,34378,430],{"class":124},[118,34380,395],{"class":124},[118,34382,1146],{"class":124},[118,34384,7320],{"class":433},[118,34386,430],{"class":124},[118,34388,8191],{"class":124},[118,34390,34391,34393,34395],{"class":120,"line":139},[118,34392,12579],{"class":124},[118,34394,1131],{"class":1130},[118,34396,7406],{"class":124},[118,34398,34399,34401,34403,34405,34407,34409,34411,34413,34415,34417,34419,34421,34423,34425,34427,34429,34431],{"class":120,"line":149},[118,34400,8061],{"class":124},[118,34402,430],{"class":124},[118,34404,24374],{"class":433},[118,34406,430],{"class":124},[118,34408,395],{"class":124},[118,34410,1146],{"class":124},[118,34412,24383],{"class":433},[118,34414,430],{"class":124},[118,34416,395],{"class":124},[118,34418,1146],{"class":124},[118,34420,24392],{"class":433},[118,34422,430],{"class":124},[118,34424,395],{"class":124},[118,34426,1146],{"class":124},[118,34428,24401],{"class":433},[118,34430,430],{"class":124},[118,34432,8191],{"class":124},[118,34434,34435],{"class":120,"line":161},[118,34436,34437],{"class":124},"        ...\n",[118,34439,34440],{"class":120,"line":166},[118,34441,8140],{"class":124},[118,34443,34444,34446,34448,34450,34452,34454,34456,34458,34460,34462,34464,34466],{"class":120,"line":176},[118,34445,2775],{"class":226},[118,34447,25],{"class":124},[118,34449,7733],{"class":213},[118,34451,255],{"class":124},[118,34453,9910],{"class":299},[118,34455,395],{"class":124},[118,34457,9915],{"class":299},[118,34459,395],{"class":124},[118,34461,7742],{"class":299},[118,34463,395],{"class":124},[118,34465,9924],{"class":299},[118,34467,266],{"class":124},[118,34469,34470,34472,34474,34476,34478,34480,34482,34484,34486,34488,34490,34492,34494,34496,34498,34500,34502,34504],{"class":120,"line":186},[118,34471,2775],{"class":226},[118,34473,25],{"class":124},[118,34475,7762],{"class":213},[118,34477,255],{"class":124},[118,34479,337],{"class":226},[118,34481,25],{"class":124},[118,34483,445],{"class":213},[118,34485,448],{"class":124},[118,34487,233],{"class":226},[118,34489,25],{"class":124},[118,34491,7792],{"class":213},[118,34493,255],{"class":124},[118,34495,550],{"class":226},[118,34497,25],{"class":124},[118,34499,658],{"class":213},[118,34501,255],{"class":124},[118,34503,33809],{"class":299},[118,34505,303],{"class":124},[118,34507,34508,34510,34512,34514,34516,34518,34520,34522,34524,34526],{"class":120,"line":196},[118,34509,2775],{"class":226},[118,34511,25],{"class":124},[118,34513,9973],{"class":213},[118,34515,255],{"class":124},[118,34517,550],{"class":226},[118,34519,25],{"class":124},[118,34521,658],{"class":213},[118,34523,255],{"class":124},[118,34525,20330],{"class":299},[118,34527,3646],{"class":124},[118,34529,34530],{"class":120,"line":202},[118,34531,199],{"class":124},[14,34533,34534],{},"Three arguments, three shape-defining options. Nothing more.",[14,34536,34537,6589,34539,34542,34543,34546,34547,34550],{},[18,34538,11179],{},[1629,34540,34541],{},"percentages of the containing column",", not absolute points. The four widths sum to 100. If you hand it ",[18,34544,34545],{},"40, 20, 20, 20"," (sum 100), that works; if you hand it ",[18,34548,34549],{},"40, 15, 20, 30"," (sum 105), the table still renders but the last column overflows — that's the one gotcha. Sum-to-100 or you get ugly output. We considered failing loudly on sum mismatch and decided against it; some layouts genuinely want a 90% total with 10% whitespace on the right, and forcing a check would break that.",[14,34552,34553,34555,34556,3096,34558,3096,34560,3096,34562,34564,34565,34568],{},[18,34554,7762],{}," takes the same text options everything else takes (",[18,34557,445],{},[18,34559,545],{},[18,34561,7792],{},[18,34563,2150],{},"). If you want an opinionated dark-on-light header, this is one line. ",[18,34566,34567],{},"TableStripe(color)"," alternates the row background — the zebra effect. If you don't want stripes, omit it; rows render with transparent backgrounds.",[14,34570,34571],{},"What you don't do here:",[46,34573,34574,34577,34580],{},[49,34575,34576],{},"You don't specify row heights. The table measures each cell and picks the row height from the tallest.",[49,34578,34579],{},"You don't specify fonts. The table inherits from the column's default, which inherits from the document.",[49,34581,34582],{},"You don't handle pagination. If the table overflows the page, gpdf breaks it across pages and re-draws the header on each continuation page. The 50-line version above has three rows and clearly fits; if you push it to 100 rows, pagination is automatic.",[1613,34584,34586],{"id":34585},"the-total","The total",[109,34588,34590],{"className":111,"code":34589,"language":113,"meta":114,"style":114},"c.Text(\"Total: $17,400.00\", template.AlignRight(), template.Bold(), template.FontSize(14))\n",[18,34591,34592],{"__ignoreMap":114},[118,34593,34594,34596,34598,34600,34602,34604,34606,34608,34610,34612,34614,34616,34618,34620,34622,34624,34626,34628,34630,34632,34634,34636],{"class":120,"line":121},[118,34595,401],{"class":226},[118,34597,25],{"class":124},[118,34599,425],{"class":213},[118,34601,255],{"class":124},[118,34603,430],{"class":124},[118,34605,33852],{"class":433},[118,34607,430],{"class":124},[118,34609,395],{"class":124},[118,34611,233],{"class":226},[118,34613,25],{"class":124},[118,34615,519],{"class":213},[118,34617,448],{"class":124},[118,34619,233],{"class":226},[118,34621,25],{"class":124},[118,34623,445],{"class":213},[118,34625,448],{"class":124},[118,34627,233],{"class":226},[118,34629,25],{"class":124},[118,34631,455],{"class":213},[118,34633,255],{"class":124},[118,34635,1877],{"class":299},[118,34637,463],{"class":124},[14,34639,34640,34641,34643,34644,34647,34648,54,34650,25],{},"Nothing clever. It's another ",[18,34642,425],{}," call outside the table, right-aligned, slightly bigger. The visual distance from the table comes from the row's natural cursor advance — there's no explicit spacer above. If you want breathing room, add ",[18,34645,34646],{},"c.Spacer(document.Mm(3))"," between ",[18,34649,8204],{},[18,34651,2741],{},[1613,34653,34655],{"id":34654},"generate-and-write","Generate and write",[109,34657,34659],{"className":111,"code":34658,"language":113,"meta":114,"style":114},"b, err := doc.Generate()\nif err != nil { log.Fatal(err) }\nif err := os.WriteFile(\"invoice.pdf\", b, 0644); err != nil { log.Fatal(err) }\n",[18,34660,34661,34680,34706],{"__ignoreMap":114},[118,34662,34663,34666,34668,34670,34672,34674,34676,34678],{"class":120,"line":121},[118,34664,34665],{"class":226},"b",[118,34667,395],{"class":124},[118,34669,1391],{"class":226},[118,34671,230],{"class":124},[118,34673,1185],{"class":226},[118,34675,25],{"class":124},[118,34677,1400],{"class":213},[118,34679,1193],{"class":124},[118,34681,34682,34684,34686,34688,34690,34692,34694,34696,34698,34700,34702,34704],{"class":120,"line":132},[118,34683,16122],{"class":142},[118,34685,1391],{"class":226},[118,34687,1413],{"class":124},[118,34689,1416],{"class":124},[118,34691,8081],{"class":124},[118,34693,32781],{"class":226},[118,34695,25],{"class":124},[118,34697,5823],{"class":213},[118,34699,255],{"class":124},[118,34701,1429],{"class":226},[118,34703,345],{"class":124},[118,34705,32794],{"class":124},[118,34707,34708,34710,34712,34714,34716,34718,34720,34722,34724,34726,34728,34730,34732,34734,34736,34738,34740,34742,34744,34746,34748,34750,34752,34754,34756,34758],{"class":120,"line":139},[118,34709,16122],{"class":142},[118,34711,1391],{"class":226},[118,34713,230],{"class":124},[118,34715,1447],{"class":226},[118,34717,25],{"class":124},[118,34719,1452],{"class":213},[118,34721,255],{"class":124},[118,34723,430],{"class":124},[118,34725,4015],{"class":433},[118,34727,430],{"class":124},[118,34729,395],{"class":124},[118,34731,33967],{"class":226},[118,34733,395],{"class":124},[118,34735,33972],{"class":299},[118,34737,7902],{"class":124},[118,34739,1391],{"class":226},[118,34741,1413],{"class":124},[118,34743,1416],{"class":124},[118,34745,8081],{"class":124},[118,34747,32781],{"class":226},[118,34749,25],{"class":124},[118,34751,5823],{"class":213},[118,34753,255],{"class":124},[118,34755,1429],{"class":226},[118,34757,345],{"class":124},[118,34759,32794],{"class":124},[14,34761,34762,34764,34765,34768,34769,34772],{},[18,34763,1657],{}," returns ",[18,34766,34767],{},"([]byte, error)",". It doesn't touch the filesystem. The byte slice is a complete PDF — you can write it to disk, upload it to S3, stream it as an HTTP response with ",[18,34770,34771],{},"w.Write(b)",", or embed it in an email attachment. No temp files, no cleanup.",[14,34774,34775,34776,34779,34780,25],{},"If you want streaming instead of a buffered slice, ",[18,34777,34778],{},"doc.Render(w io.Writer)"," exists and writes directly. For an invoice, which is kilobytes, the difference is noise. For a 10,000-page report you'd use ",[18,34781,29105],{},[41,34783,34785],{"id":34784},"making-it-prettier-without-breaking-50-lines","Making it prettier (without breaking 50 lines)",[14,34787,34788],{},"The version above is functional but plain. A few single-line additions change the look significantly.",[14,34790,34791,34794,34795,34797,34798,34801],{},[1629,34792,34793],{},"Brand color."," Pick a hex (navy blue at ",[18,34796,7269],{},", a teal at ",[18,34799,34800],{},"0x00796B",", whatever) and thread it through the two places readers notice most — the company name and the table header:",[109,34803,34805],{"className":111,"code":34804,"language":113,"meta":114,"style":114},"brand := pdf.RGBHex(0x1A237E)\nc.Text(\"ACME Corp\", template.FontSize(22), template.Bold(), template.TextColor(brand))\n// ...\ntemplate.TableHeaderStyle(template.Bold(), template.TextColor(pdf.White), template.BgColor(brand)),\n",[18,34806,34807,34826,34876,34880],{"__ignoreMap":114},[118,34808,34809,34812,34814,34816,34818,34820,34822,34824],{"class":120,"line":121},[118,34810,34811],{"class":226},"brand ",[118,34813,230],{"class":124},[118,34815,7260],{"class":226},[118,34817,25],{"class":124},[118,34819,658],{"class":213},[118,34821,255],{"class":124},[118,34823,7269],{"class":299},[118,34825,199],{"class":124},[118,34827,34828,34830,34832,34834,34836,34838,34840,34842,34844,34846,34848,34850,34852,34854,34856,34858,34860,34862,34864,34866,34868,34870,34872,34874],{"class":120,"line":132},[118,34829,401],{"class":226},[118,34831,25],{"class":124},[118,34833,425],{"class":213},[118,34835,255],{"class":124},[118,34837,430],{"class":124},[118,34839,33350],{"class":433},[118,34841,430],{"class":124},[118,34843,395],{"class":124},[118,34845,233],{"class":226},[118,34847,25],{"class":124},[118,34849,455],{"class":213},[118,34851,255],{"class":124},[118,34853,33365],{"class":299},[118,34855,1280],{"class":124},[118,34857,233],{"class":226},[118,34859,25],{"class":124},[118,34861,445],{"class":213},[118,34863,448],{"class":124},[118,34865,233],{"class":226},[118,34867,25],{"class":124},[118,34869,545],{"class":213},[118,34871,255],{"class":124},[118,34873,7797],{"class":226},[118,34875,463],{"class":124},[118,34877,34878],{"class":120,"line":139},[118,34879,9606],{"class":3981},[118,34881,34882,34884,34886,34888,34890,34892,34894,34896,34898,34900,34902,34904,34906,34908,34910,34912,34914,34916,34918,34920,34922,34924],{"class":120,"line":149},[118,34883,337],{"class":226},[118,34885,25],{"class":124},[118,34887,7762],{"class":213},[118,34889,255],{"class":124},[118,34891,337],{"class":226},[118,34893,25],{"class":124},[118,34895,445],{"class":213},[118,34897,448],{"class":124},[118,34899,233],{"class":226},[118,34901,25],{"class":124},[118,34903,545],{"class":213},[118,34905,255],{"class":124},[118,34907,550],{"class":226},[118,34909,25],{"class":124},[118,34911,7781],{"class":226},[118,34913,1280],{"class":124},[118,34915,233],{"class":226},[118,34917,25],{"class":124},[118,34919,7792],{"class":213},[118,34921,255],{"class":124},[118,34923,7797],{"class":226},[118,34925,3646],{"class":124},[14,34927,34928],{},"Two new lines, one changed line. Still under 50.",[14,34930,34931,34934,34935,34937],{},[1629,34932,34933],{},"Tax and subtotal."," If the line above the total needs to break out subtotal and tax, stack three ",[18,34936,8551],{}," calls:",[109,34939,34941],{"className":111,"code":34940,"language":113,"meta":114,"style":114},"c.Text(\"Subtotal: $17,400.00\", template.AlignRight())\nc.Text(\"Tax (10%): $1,740.00\",  template.AlignRight())\nc.Text(\"Total:    $19,140.00\",  template.AlignRight(), template.Bold(), template.FontSize(14))\n",[18,34942,34943,34970,34998],{"__ignoreMap":114},[118,34944,34945,34947,34949,34951,34953,34955,34958,34960,34962,34964,34966,34968],{"class":120,"line":121},[118,34946,401],{"class":226},[118,34948,25],{"class":124},[118,34950,425],{"class":213},[118,34952,255],{"class":124},[118,34954,430],{"class":124},[118,34956,34957],{"class":433},"Subtotal: $17,400.00",[118,34959,430],{"class":124},[118,34961,395],{"class":124},[118,34963,233],{"class":226},[118,34965,25],{"class":124},[118,34967,519],{"class":213},[118,34969,1289],{"class":124},[118,34971,34972,34974,34976,34978,34980,34982,34985,34987,34989,34992,34994,34996],{"class":120,"line":132},[118,34973,401],{"class":226},[118,34975,25],{"class":124},[118,34977,425],{"class":213},[118,34979,255],{"class":124},[118,34981,430],{"class":124},[118,34983,34984],{"class":433},"Tax (10%): $1,740.00",[118,34986,430],{"class":124},[118,34988,395],{"class":124},[118,34990,34991],{"class":226},"  template",[118,34993,25],{"class":124},[118,34995,519],{"class":213},[118,34997,1289],{"class":124},[118,34999,35000,35002,35004,35006,35008,35010,35013,35015,35017,35019,35021,35023,35025,35027,35029,35031,35033,35035,35037,35039,35041,35043],{"class":120,"line":139},[118,35001,401],{"class":226},[118,35003,25],{"class":124},[118,35005,425],{"class":213},[118,35007,255],{"class":124},[118,35009,430],{"class":124},[118,35011,35012],{"class":433},"Total:    $19,140.00",[118,35014,430],{"class":124},[118,35016,395],{"class":124},[118,35018,34991],{"class":226},[118,35020,25],{"class":124},[118,35022,519],{"class":213},[118,35024,448],{"class":124},[118,35026,233],{"class":226},[118,35028,25],{"class":124},[118,35030,445],{"class":213},[118,35032,448],{"class":124},[118,35034,233],{"class":226},[118,35036,25],{"class":124},[118,35038,455],{"class":213},[118,35040,255],{"class":124},[118,35042,1877],{"class":299},[118,35044,463],{"class":124},[14,35046,35047],{},"You've just blown the 50-line budget by two lines. Whether that's a real cost depends on whether you're selling the number \"50\" or the ability to add a tax line without rewriting the layout. We'd pick the tax line.",[14,35049,35050,35053,35054,6349],{},[1629,35051,35052],{},"A rule above the total."," Between the subtotal block and the total, drop a ",[18,35055,2530],{},[109,35057,35059],{"className":111,"code":35058,"language":113,"meta":114,"style":114},"c.Spacer(document.Mm(2))\nc.Line(template.LineThickness(document.Pt(0.5)))\nc.Spacer(document.Mm(2))\n",[18,35060,35061,35083,35114],{"__ignoreMap":114},[118,35062,35063,35065,35067,35069,35071,35073,35075,35077,35079,35081],{"class":120,"line":121},[118,35064,401],{"class":226},[118,35066,25],{"class":124},[118,35068,675],{"class":213},[118,35070,255],{"class":124},[118,35072,258],{"class":226},[118,35074,25],{"class":124},[118,35076,294],{"class":213},[118,35078,255],{"class":124},[118,35080,870],{"class":299},[118,35082,463],{"class":124},[118,35084,35085,35087,35089,35091,35093,35095,35097,35100,35102,35104,35106,35108,35110,35112],{"class":120,"line":132},[118,35086,401],{"class":226},[118,35088,25],{"class":124},[118,35090,640],{"class":213},[118,35092,255],{"class":124},[118,35094,337],{"class":226},[118,35096,25],{"class":124},[118,35098,35099],{"class":213},"LineThickness",[118,35101,255],{"class":124},[118,35103,258],{"class":226},[118,35105,25],{"class":124},[118,35107,6095],{"class":213},[118,35109,255],{"class":124},[118,35111,560],{"class":299},[118,35113,563],{"class":124},[118,35115,35116,35118,35120,35122,35124,35126,35128,35130,35132,35134],{"class":120,"line":139},[118,35117,401],{"class":226},[118,35119,25],{"class":124},[118,35121,675],{"class":213},[118,35123,255],{"class":124},[118,35125,258],{"class":226},[118,35127,25],{"class":124},[118,35129,294],{"class":213},[118,35131,255],{"class":124},[118,35133,870],{"class":299},[118,35135,463],{"class":124},[14,35137,35138,35141,35142,35144,35145,25],{},[1629,35139,35140],{},"A payment-info column under the header."," Duplicate the header row structure with \"Bill To\" and \"Payment Info\" inside two new ",[18,35143,6658],{}," cells. This is the only pattern that starts pushing the code toward a function extraction — two near-identical column layouts is fine, three is the point where you pull out ",[18,35146,35147],{},"func billTo(c *template.ColBuilder, ...)",[41,35149,35151],{"id":35150},"running-it","Running it",[109,35153,35155],{"className":3145,"code":35154,"language":3147,"meta":114,"style":114},"mkdir invoice-demo\ncd invoice-demo\ngo mod init example.com/invoice-demo\ngo get github.com/gpdf-dev/gpdf\n# paste main.go\ngo run .\nopen invoice.pdf    # macOS; xdg-open on Linux, start on Windows\n",[18,35156,35157,35165,35172,35184,35192,35197,35207],{"__ignoreMap":114},[118,35158,35159,35162],{"class":120,"line":121},[118,35160,35161],{"class":128},"mkdir",[118,35163,35164],{"class":433}," invoice-demo\n",[118,35166,35167,35170],{"class":120,"line":132},[118,35168,35169],{"class":213},"cd",[118,35171,35164],{"class":433},[118,35173,35174,35176,35179,35181],{"class":120,"line":139},[118,35175,113],{"class":128},[118,35177,35178],{"class":433}," mod",[118,35180,22362],{"class":433},[118,35182,35183],{"class":433}," example.com/invoice-demo\n",[118,35185,35186,35188,35190],{"class":120,"line":149},[118,35187,113],{"class":128},[118,35189,3156],{"class":433},[118,35191,3159],{"class":433},[118,35193,35194],{"class":120,"line":161},[118,35195,35196],{"class":3981},"# paste main.go\n",[118,35198,35199,35201,35204],{"class":120,"line":166},[118,35200,113],{"class":128},[118,35202,35203],{"class":433}," run",[118,35205,35206],{"class":433}," .\n",[118,35208,35209,35212,35215],{"class":120,"line":176},[118,35210,35211],{"class":128},"open",[118,35213,35214],{"class":433}," invoice.pdf",[118,35216,35217],{"class":3981},"    # macOS; xdg-open on Linux, start on Windows\n",[14,35219,35220,35221,35223],{},"The first ",[18,35222,27085],{}," pulls about 3 MB of source (no compiled binaries). Subsequent runs are instant because the module is cached.",[14,35225,35226,35227,35229,35230,20607,35233,35236],{},"If ",[18,35228,7077],{}," produces nothing and exits cleanly, check whether the file was written somewhere unexpected — the program uses the current working directory as the output path. On a Docker build with a read-only filesystem, swap ",[18,35231,35232],{},"os.WriteFile",[18,35234,35235],{},"io.Copy(w, bytes.NewReader(b))"," into an HTTP response.",[41,35238,35240],{"id":35239},"where-this-pattern-breaks","Where this pattern breaks",[14,35242,35243],{},"The 50-line version scales up gracefully until one of four things happens.",[14,35245,35246,35249,35250,35252],{},[1629,35247,35248],{},"The items list becomes data."," If the line items come from a database query or a JSON payload instead of a hardcoded slice, the table stays identical — you just construct ",[18,35251,12722],{}," from your data. That's not a break; it's the expected shape.",[14,35254,35255,35258,35259,35262,35263,35266,35267,35269],{},[1629,35256,35257],{},"You want to reuse the layout."," The moment you're generating invoices in a loop, stop inlining the body into ",[18,35260,35261],{},"main",". Extract ",[18,35264,35265],{},"func renderInvoice(doc *template.Document, inv Invoice)",". The 50-line template stays recognizable; you just pass the ",[18,35268,1687],{}," and the data through.",[14,35271,35272,35275,35276,4975,35280,35283,35284,35288],{},[1629,35273,35274],{},"The layout branches."," Some invoices have a purchase-order column, some don't. Some customers get a tax line, some don't. Once you have conditional sections, the Builder API starts feeling verbose and the ",[3163,35277,35279],{"href":35278},"/docs/guide/json-templates","JSON schema entrypoint",[18,35281,35282],{},"gpdf.NewDocumentFromJSON",") or ",[3163,35285,35287],{"href":35286},"/docs/guide/go-templates","Go templates entrypoint"," becomes a better fit — you declare the structure in a template file and feed it data.",[14,35290,35291,35294,35295,54,35297,25],{},[1629,35292,35293],{},"You need CJK text."," Japanese, Chinese, or Korean characters in the above code render as tofu boxes because the default font is Latin-only. You need one extra call at document construction to register a TTF. That's covered in ",[3163,35296,32994],{"href":22032},[3163,35298,9792],{"href":9791},[14,35300,35301],{},"None of these are \"rewrite from scratch\" moments. They're incremental. The 50-line version is the starting shape you carry forward.",[41,35303,3054],{"id":3053},[14,35305,35306,35309],{},[1629,35307,35308],{},"Can I use this for commercial invoices without attribution?","\nYes. gpdf is MIT-licensed. Build whatever you want on top, including closed-source commercial products. Attribution isn't required, though a GitHub star is nice.",[14,35311,35312,27292,35319,3107,35322,35324,35325,35327],{},[1629,35313,35314,35315,35318],{},"Does it support writing to ",[18,35316,35317],{},"io.Writer"," directly, without the byte slice?",[18,35320,35321],{},"doc.Render(w io.Writer) error",[18,35323,1657],{}," version above is a convenience for the common case where you want ",[18,35326,17886],{}," to attach to something else.",[14,35329,35330,35333,35334,35336],{},[1629,35331,35332],{},"How fast is this actually?","\nThe 50-line program above generates its PDF in about 100 µs on an M1, dominated by the three-row table and the text layout. A single-page hello-world lands at ",[1629,35335,3248],{},". For batch workloads — nightly statement runs, bulk invoicing — gpdf's per-document cost is low enough that the bottleneck becomes whatever is feeding it data.",[14,35338,35339,35342,35343,35346,35347,35349,35350,35353,35354,25],{},[1629,35340,35341],{},"Can I generate an invoice PDF in Go without gpdf?","\nSure. ",[18,35344,35345],{},"jung-kurt/gofpdf"," works (it's archived but stable), ",[18,35348,1565],{}," works at a lower level, and ",[18,35351,35352],{},"johnfercher/maroto"," gives you a different layout abstraction. All of them end up more verbose than the 50 lines above for the same invoice. We wrote more about that in ",[3163,35355,12399],{"href":3381},[14,35357,35358,35365,35366,35369],{},[1629,35359,35360,35361,35364],{},"Why isn't there a first-party ",[18,35362,35363],{},"gpdf.Invoice"," helper?","\nBecause \"invoice\" means different things in different countries and every simplification picks a side. We'd rather give you a 50-line starting point you can adapt than a ",[18,35367,35368],{},"NewInvoice(companyName, lineItems)"," constructor that breaks the moment you need a Japanese 適格請求書 with a 登録番号 or a Brazilian NFe with DANFE metadata. The Builder API is the helper.",[14,35371,35372,35375,35376,35379,35380,25],{},[1629,35373,35374],{},"Does the PDF validate against PDF/A or any archival standard?","\nThe default output is standard PDF 1.7. For PDF/A-2b (archival compliance) you add ",[18,35377,35378],{},"gpdf.WithPDFA(pdfa.Level2B)"," at document construction. That's a separate topic, covered in ",[3163,35381,35383],{"href":35382},"/docs/guide/pdf-a","Building PDF/A-2b in pure Go",[41,35385,4794],{"id":4793},[14,35387,35388],{},"gpdf is a Go library for generating PDFs. MIT, zero dependencies, native CJK, 10–30× faster than alternatives on the workloads we benchmark.",[109,35390,35391],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,35392,35393],{"__ignoreMap":114},[118,35394,35395,35397,35399],{"class":120,"line":121},[118,35396,113],{"class":128},[118,35398,3156],{"class":433},[118,35400,3159],{"class":433},[14,35402,35403,3169,35406],{},[3163,35404,3168],{"href":3165,"rel":35405},[3167],[3163,35407,3174],{"href":3172,"rel":35408},[3167],[41,35410,4821],{"id":4820},[46,35412,35413,35418,35423],{},[49,35414,35415,35417],{},[3163,35416,4828],{"href":4069}," — the benchmark numbers behind the \"few hundred microseconds\" claim.",[49,35419,35420,35422],{},[3163,35421,4790],{"href":3381}," — how this 50 lines compares to the same invoice in gofpdf, gopdf, and Maroto.",[49,35424,35425,35427],{},[3163,35426,6902],{"href":6901}," — the layout model used in the header row above, in more depth.",[3176,35429,35430],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":114,"searchDepth":132,"depth":132,"links":35432},[35433,35434,35435,35436,35444,35445,35446,35447,35448,35449],{"id":43,"depth":132,"text":44},{"id":33110,"depth":132,"text":33111},{"id":33145,"depth":132,"text":33146},{"id":34015,"depth":132,"text":34016,"children":35437},[35438,35439,35440,35441,35442,35443],{"id":34019,"depth":139,"text":34020},{"id":34121,"depth":139,"text":34122},{"id":34189,"depth":139,"text":34190},{"id":34332,"depth":139,"text":34333},{"id":34585,"depth":139,"text":34586},{"id":34654,"depth":139,"text":34655},{"id":34784,"depth":132,"text":34785},{"id":35150,"depth":132,"text":35151},{"id":35239,"depth":132,"text":35240},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"2026-04-21","A complete, runnable invoice PDF in Go — 50 lines with gpdf, zero dependencies, no Chromium, no CGO. Here's the code and what every block does.",{"name":35453,"totalTime":6973,"tools":35454,"steps":35455},"Generate an invoice PDF in Go with gpdf",[3202],[35456,35458,35461,35464,35467],{"name":12932,"text":35457},"Run go get github.com/gpdf-dev/gpdf in your module. gpdf has no transitive dependencies, so go.sum gets one line and your binary gains no CGO surface.",{"name":35459,"text":35460},"Construct the document","Call gpdf.NewDocument with template.WithPageSize(document.A4). Then doc.AddPage() returns a PageBuilder you can stack rows onto.",{"name":35462,"text":35463},"Lay out the header with two 6-column cells","Use page.AutoRow with r.Col(6, ...) twice. Put the company block on the left and the INVOICE label plus number on the right with template.AlignRight().",{"name":35465,"text":35466},"Render the items table","Inside a 12-column cell, call c.Table with a header slice and a rows slice. Pass template.ColumnWidths and template.TableHeaderStyle to style it.",{"name":35468,"text":35469},"Generate and write the bytes","Call doc.Generate() for []byte, then os.WriteFile(\"invoice.pdf\", b, 0644). One file on disk, no temp directory, no external binary.",{},{"title":6919,"description":35451},"blog/012.invoice-pdf-go-under-50-lines",[3226,6997],"h3puhmykSl8uTg-uXFzpwk9s5_QRIebe9A2-nL3WweQ",{"id":35476,"title":35477,"author":35478,"body":35479,"date":36554,"description":36555,"draft":3196,"extension":3197,"howTo":36556,"image":3220,"meta":36576,"navigation":135,"path":36577,"seo":36578,"stem":36579,"tags":36580,"updated":3220,"__hash__":36581},"blog/blog/010.source-han-sans-jp-with-gpdf.md","How do I use Source Han Sans JP with gpdf?",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":35480,"toc":36542},[35481,35483,35494,35496,35513,35517,36025,36035,36039,36049,36052,36130,36137,36141,36171,36178,36198,36207,36211,36214,36220,36223,36373,36386,36389,36393,36396,36432,36446,36450,36453,36468,36471,36486,36489,36491,36515,36517,36519,36531,36539],[41,35482,4879],{"id":4878},[14,35484,35485,35486,35489,35490,35493],{},"You want to use ",[1629,35487,35488],{},"Source Han Sans JP"," — Adobe's open-source pan-CJK sans-serif, released in 2014 as the product of the Adobe–Google partnership — in a ",[3163,35491,1587],{"href":3165,"rel":35492},[3167]," document. Maybe your team pins fonts to GitHub release tags for reproducibility, maybe you inherited a design system that standardized on Source Han years ago, maybe you just prefer Adobe's release cadence. Whatever the reason, three things are worth getting straight before you download anything: which file to grab, what the actual relationship to Noto Sans JP is, and which format gpdf can read.",[41,35495,44],{"id":43},[14,35497,35498,35499,35502,35503,35508,35509,35512],{},"Download ",[18,35500,35501],{},"SourceHanSansJP-Regular.ttf"," from the ",[3163,35504,35507],{"href":35505,"rel":35506},"https://github.com/adobe-fonts/source-han-sans/releases",[3167],"adobe-fonts/source-han-sans"," release page (the TTF bundle, not the default OTF), pass it to ",[18,35510,35511],{},"gpdf.WithFont(\"SourceHanSansJP\", bytes)",", and set it as the default. Source Han Sans JP and Noto Sans JP share the same glyph outlines — if none of Adobe's tooling story matters to you, Noto is a friendlier download.",[41,35514,35516],{"id":35515},"the-complete-example","The complete example",[109,35518,35520],{"className":111,"code":35519,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"SourceHanSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"SourceHanSansJP\", font),\n        gpdf.WithDefaultFont(\"SourceHanSansJP\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"報告書\", template.FontSize(24), template.Bold())\n            c.Text(\"Source Han Sans JP — Adobe 配布の無料 CJK フォント。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"report.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,35521,35522,35528,35532,35538,35546,35554,35558,35566,35574,35582,35586,35590,35600,35627,35639,35653,35657,35661,35675,35693,35723,35747,35769,35773,35777,35791,35815,35845,35884,35903,35907,35911,35915,35933,35945,35959,35963,36003,36017,36021],{"__ignoreMap":114},[118,35523,35524,35526],{"class":120,"line":121},[118,35525,125],{"class":124},[118,35527,129],{"class":128},[118,35529,35530],{"class":120,"line":132},[118,35531,136],{"emptyLinePlaceholder":135},[118,35533,35534,35536],{"class":120,"line":139},[118,35535,143],{"class":142},[118,35537,146],{"class":124},[118,35539,35540,35542,35544],{"class":120,"line":149},[118,35541,152],{"class":124},[118,35543,5303],{"class":128},[118,35545,158],{"class":124},[118,35547,35548,35550,35552],{"class":120,"line":161},[118,35549,152],{"class":124},[118,35551,155],{"class":128},[118,35553,158],{"class":124},[118,35555,35556],{"class":120,"line":166},[118,35557,136],{"emptyLinePlaceholder":135},[118,35559,35560,35562,35564],{"class":120,"line":176},[118,35561,152],{"class":124},[118,35563,3203],{"class":128},[118,35565,158],{"class":124},[118,35567,35568,35570,35572],{"class":120,"line":186},[118,35569,152],{"class":124},[118,35571,171],{"class":128},[118,35573,158],{"class":124},[118,35575,35576,35578,35580],{"class":120,"line":196},[118,35577,152],{"class":124},[118,35579,191],{"class":128},[118,35581,158],{"class":124},[118,35583,35584],{"class":120,"line":202},[118,35585,199],{"class":124},[118,35587,35588],{"class":120,"line":207},[118,35589,136],{"emptyLinePlaceholder":135},[118,35591,35592,35594,35596,35598],{"class":120,"line":223},[118,35593,210],{"class":124},[118,35595,214],{"class":213},[118,35597,217],{"class":124},[118,35599,220],{"class":124},[118,35601,35602,35605,35607,35609,35611,35613,35615,35617,35619,35621,35623,35625],{"class":120,"line":244},[118,35603,35604],{"class":226},"    font",[118,35606,395],{"class":124},[118,35608,1391],{"class":226},[118,35610,230],{"class":124},[118,35612,1447],{"class":226},[118,35614,25],{"class":124},[118,35616,9545],{"class":213},[118,35618,255],{"class":124},[118,35620,430],{"class":124},[118,35622,35501],{"class":433},[118,35624,430],{"class":124},[118,35626,199],{"class":124},[118,35628,35629,35631,35633,35635,35637],{"class":120,"line":269},[118,35630,1408],{"class":142},[118,35632,1391],{"class":226},[118,35634,1413],{"class":124},[118,35636,1416],{"class":124},[118,35638,220],{"class":124},[118,35640,35641,35643,35645,35647,35649,35651],{"class":120,"line":306},[118,35642,5818],{"class":226},[118,35644,25],{"class":124},[118,35646,5823],{"class":213},[118,35648,255],{"class":124},[118,35650,1429],{"class":226},[118,35652,199],{"class":124},[118,35654,35655],{"class":120,"line":312},[118,35656,1375],{"class":124},[118,35658,35659],{"class":120,"line":317},[118,35660,136],{"emptyLinePlaceholder":135},[118,35662,35663,35665,35667,35669,35671,35673],{"class":120,"line":350},[118,35664,227],{"class":226},[118,35666,230],{"class":124},[118,35668,3595],{"class":226},[118,35670,25],{"class":124},[118,35672,3600],{"class":213},[118,35674,241],{"class":124},[118,35676,35677,35679,35681,35683,35685,35687,35689,35691],{"class":120,"line":379},[118,35678,3607],{"class":226},[118,35680,25],{"class":124},[118,35682,252],{"class":213},[118,35684,255],{"class":124},[118,35686,1587],{"class":226},[118,35688,25],{"class":124},[118,35690,263],{"class":226},[118,35692,266],{"class":124},[118,35694,35695,35697,35699,35701,35703,35705,35707,35709,35711,35713,35715,35717,35719,35721],{"class":120,"line":417},[118,35696,3607],{"class":226},[118,35698,25],{"class":124},[118,35700,276],{"class":213},[118,35702,255],{"class":124},[118,35704,258],{"class":226},[118,35706,25],{"class":124},[118,35708,285],{"class":213},[118,35710,255],{"class":124},[118,35712,258],{"class":226},[118,35714,25],{"class":124},[118,35716,294],{"class":213},[118,35718,255],{"class":124},[118,35720,300],{"class":299},[118,35722,303],{"class":124},[118,35724,35725,35727,35729,35731,35733,35735,35738,35740,35742,35745],{"class":120,"line":466},[118,35726,3607],{"class":226},[118,35728,25],{"class":124},[118,35730,2798],{"class":213},[118,35732,255],{"class":124},[118,35734,430],{"class":124},[118,35736,35737],{"class":433},"SourceHanSansJP",[118,35739,430],{"class":124},[118,35741,395],{"class":124},[118,35743,35744],{"class":226}," font",[118,35746,266],{"class":124},[118,35748,35749,35751,35753,35755,35757,35759,35761,35763,35765,35767],{"class":120,"line":472},[118,35750,3607],{"class":226},[118,35752,25],{"class":124},[118,35754,12501],{"class":213},[118,35756,255],{"class":124},[118,35758,430],{"class":124},[118,35760,35737],{"class":433},[118,35762,430],{"class":124},[118,35764,395],{"class":124},[118,35766,14386],{"class":299},[118,35768,266],{"class":124},[118,35770,35771],{"class":120,"line":503},[118,35772,309],{"class":124},[118,35774,35775],{"class":120,"line":537},[118,35776,136],{"emptyLinePlaceholder":135},[118,35778,35779,35781,35783,35785,35787,35789],{"class":120,"line":566},[118,35780,5431],{"class":226},[118,35782,230],{"class":124},[118,35784,1185],{"class":226},[118,35786,25],{"class":124},[118,35788,1190],{"class":213},[118,35790,1193],{"class":124},[118,35792,35793,35795,35797,35799,35801,35803,35805,35807,35809,35811,35813],{"class":120,"line":571},[118,35794,5494],{"class":226},[118,35796,25],{"class":124},[118,35798,358],{"class":213},[118,35800,328],{"class":124},[118,35802,363],{"class":331},[118,35804,334],{"class":124},[118,35806,337],{"class":128},[118,35808,25],{"class":124},[118,35810,372],{"class":128},[118,35812,345],{"class":124},[118,35814,220],{"class":124},[118,35816,35817,35819,35821,35823,35825,35827,35829,35831,35833,35835,35837,35839,35841,35843],{"class":120,"line":577},[118,35818,1737],{"class":226},[118,35820,25],{"class":124},[118,35822,387],{"class":213},[118,35824,255],{"class":124},[118,35826,20],{"class":299},[118,35828,395],{"class":124},[118,35830,398],{"class":124},[118,35832,401],{"class":331},[118,35834,334],{"class":124},[118,35836,337],{"class":128},[118,35838,25],{"class":124},[118,35840,410],{"class":128},[118,35842,345],{"class":124},[118,35844,220],{"class":124},[118,35846,35847,35849,35851,35853,35855,35857,35860,35862,35864,35866,35868,35870,35872,35874,35876,35878,35880,35882],{"class":120,"line":602},[118,35848,1768],{"class":226},[118,35850,25],{"class":124},[118,35852,425],{"class":213},[118,35854,255],{"class":124},[118,35856,430],{"class":124},[118,35858,35859],{"class":433},"報告書",[118,35861,430],{"class":124},[118,35863,395],{"class":124},[118,35865,233],{"class":226},[118,35867,25],{"class":124},[118,35869,455],{"class":213},[118,35871,255],{"class":124},[118,35873,3777],{"class":299},[118,35875,1280],{"class":124},[118,35877,233],{"class":226},[118,35879,25],{"class":124},[118,35881,445],{"class":213},[118,35883,1289],{"class":124},[118,35885,35886,35888,35890,35892,35894,35896,35899,35901],{"class":120,"line":633},[118,35887,1768],{"class":226},[118,35889,25],{"class":124},[118,35891,425],{"class":213},[118,35893,255],{"class":124},[118,35895,430],{"class":124},[118,35897,35898],{"class":433},"Source Han Sans JP — Adobe 配布の無料 CJK フォント。",[118,35900,430],{"class":124},[118,35902,199],{"class":124},[118,35904,35905],{"class":120,"line":668},[118,35906,574],{"class":124},[118,35908,35909],{"class":120,"line":693},[118,35910,706],{"class":124},[118,35912,35913],{"class":120,"line":698},[118,35914,136],{"emptyLinePlaceholder":135},[118,35916,35917,35919,35921,35923,35925,35927,35929,35931],{"class":120,"line":703},[118,35918,5787],{"class":226},[118,35920,395],{"class":124},[118,35922,1391],{"class":226},[118,35924,230],{"class":124},[118,35926,1185],{"class":226},[118,35928,25],{"class":124},[118,35930,1400],{"class":213},[118,35932,1193],{"class":124},[118,35934,35935,35937,35939,35941,35943],{"class":120,"line":709},[118,35936,1408],{"class":142},[118,35938,1391],{"class":226},[118,35940,1413],{"class":124},[118,35942,1416],{"class":124},[118,35944,220],{"class":124},[118,35946,35947,35949,35951,35953,35955,35957],{"class":120,"line":714},[118,35948,5818],{"class":226},[118,35950,25],{"class":124},[118,35952,5823],{"class":213},[118,35954,255],{"class":124},[118,35956,1429],{"class":226},[118,35958,199],{"class":124},[118,35960,35961],{"class":120,"line":740},[118,35962,1375],{"class":124},[118,35964,35965,35967,35969,35971,35973,35975,35977,35979,35981,35983,35985,35987,35989,35991,35993,35995,35997,35999,36001],{"class":120,"line":765},[118,35966,1408],{"class":142},[118,35968,1391],{"class":226},[118,35970,230],{"class":124},[118,35972,1447],{"class":226},[118,35974,25],{"class":124},[118,35976,1452],{"class":213},[118,35978,255],{"class":124},[118,35980,430],{"class":124},[118,35982,1459],{"class":433},[118,35984,430],{"class":124},[118,35986,395],{"class":124},[118,35988,5859],{"class":226},[118,35990,395],{"class":124},[118,35992,1471],{"class":299},[118,35994,7902],{"class":124},[118,35996,1391],{"class":226},[118,35998,1413],{"class":124},[118,36000,1416],{"class":124},[118,36002,220],{"class":124},[118,36004,36005,36007,36009,36011,36013,36015],{"class":120,"line":796},[118,36006,5818],{"class":226},[118,36008,25],{"class":124},[118,36010,5823],{"class":213},[118,36012,255],{"class":124},[118,36014,1429],{"class":226},[118,36016,199],{"class":124},[118,36018,36019],{"class":120,"line":819},[118,36020,1375],{"class":124},[118,36022,36023],{"class":120,"line":851},[118,36024,1479],{"class":124},[14,36026,36027,36028,3096,36030,36032,36033,25],{},"Drop the TTF next to ",[18,36029,102],{},[18,36031,106],{},". A one-page PDF with Japanese lands in ",[18,36034,1459],{},[41,36036,36038],{"id":36037},"source-han-sans-jp-is-noto-sans-cjk-jp","Source Han Sans JP is Noto Sans CJK JP",[14,36040,36041,36042,36045,36046,36048],{},"The one fact that saves you hours of reading: ",[1629,36043,36044],{},"Source Han Sans and Noto Sans CJK are the same fonts",". Adobe did the glyph design, metrics, and coverage work. Google handled a parallel distribution channel under the Noto umbrella. Both launched on 2014-07-15. The outlines, ",[18,36047,21340],{},", and JIS X 0213 / Adobe-Japan1-6 coverage are identical, bit for bit. When Adobe ships a version bump, the glyph changes propagate to Noto within weeks.",[14,36050,36051],{},"What actually differs is branding and packaging:",[1516,36053,36054,36065],{},[1519,36055,36056],{},[1522,36057,36058,36060,36062],{},[1525,36059],{},[1525,36061,35488],{},[1525,36063,36064],{},"Noto Sans JP",[1532,36066,36067,36078,36097,36108,36119],{},[1522,36068,36069,36072,36075],{},[1537,36070,36071],{},"Publisher",[1537,36073,36074],{},"Adobe",[1537,36076,36077],{},"Google",[1522,36079,36080,36083,36089],{},[1537,36081,36082],{},"Canonical source",[1537,36084,36085],{},[3163,36086,35507],{"href":36087,"rel":36088},"https://github.com/adobe-fonts/source-han-sans",[3167],[1537,36090,36091,36096],{},[3163,36092,36095],{"href":36093,"rel":36094},"https://notofonts.github.io",[3167],"notofonts.github.io"," + Google Fonts",[1522,36098,36099,36102,36105],{},[1537,36100,36101],{},"Default format",[1537,36103,36104],{},"OTF (CFF outlines)",[1537,36106,36107],{},"TTF (static) + variable",[1522,36109,36110,36113,36116],{},[1537,36111,36112],{},"Release model",[1537,36114,36115],{},"GitHub release tags, versioned manually",[1537,36117,36118],{},"Google Fonts CDN + git repos",[1522,36120,36121,36124,36127],{},[1537,36122,36123],{},"Language bundling",[1537,36125,36126],{},"Per-language TTF + pan-CJK OTC",[1537,36128,36129],{},"JP-only",[14,36131,36132,36133,36136],{},"Pick Source Han Sans JP when your team pins fonts to Adobe's GitHub tags, mirrors Adobe internally, or wants the pan-CJK OTC bundle for other pipelines. Pick Noto Sans JP when you want the shortest path to a TTF file. See the ",[3163,36134,36135],{"href":33000},"Noto Sans JP recipe"," for that path.",[41,36138,36140],{"id":36139},"why-ttf-and-not-otf","Why TTF, and not OTF",[14,36142,36143,36144,36147,36148,36151,36152,3096,36154,3096,36156,3096,36158,36160,36161,12386,36164,36167,36168,36170],{},"Adobe's default asset for Source Han Sans is ",[18,36145,36146],{},".otf"," — specifically CFF-based OpenType. gpdf's font parser lives in one file (",[18,36149,36150],{},"pdf/font/truetype.go",") and it reads ",[18,36153,21334],{},[18,36155,21337],{},[18,36157,21331],{},[18,36159,21340],{},", and composite glyphs. It does not read ",[18,36162,36163],{},"CFF ",[18,36165,36166],{},"CFF2"," outlines. Hand it a CFF-flavored ",[18,36169,36146],{}," and the parser rejects the file at document construction, long before you generate anything.",[14,36172,36173,36174,36177],{},"The Adobe release page publishes both OTF and TTF variants. Grab the ",[1629,36175,36176],{},"TTF bundle",". If your release archive happens to contain only OTF (occasionally true for point releases), two clean alternatives:",[1624,36179,36180,36186],{},[49,36181,36182,36185],{},[1629,36183,36184],{},"Switch to Noto Sans JP."," Google Fonts serves static TTFs directly; the glyph data is identical. Zero conversion, same output.",[49,36187,36188,4919,36191,4975,36194,36197],{},[1629,36189,36190],{},"Convert once, commit the result.",[18,36192,36193],{},"fonttools",[18,36195,36196],{},"otf2ttf",") produces a TTF in a minute. Check it into your repo or an internal artifact server so the conversion is never in your build path.",[14,36199,36200,36201,36203,36204,25],{},"Avoid anything that does the conversion at build time. Font conversion tools change behavior across versions, and a silently different ",[18,36202,21340],{}," table will shift your line breaks after a ",[18,36205,36206],{},"pip install -U",[41,36208,36210],{"id":36209},"the-seven-weights","The seven weights",[14,36212,36213],{},"Source Han Sans JP publishes ExtraLight → Heavy, one file per weight:",[109,36215,36218],{"className":36216,"code":36217,"language":4993},[34105],"SourceHanSansJP-ExtraLight.ttf\nSourceHanSansJP-Light.ttf\nSourceHanSansJP-Normal.ttf\nSourceHanSansJP-Regular.ttf\nSourceHanSansJP-Medium.ttf\nSourceHanSansJP-Bold.ttf\nSourceHanSansJP-Heavy.ttf\n",[18,36219,36217],{"__ignoreMap":114},[14,36221,36222],{},"For most business documents, Regular and Bold cover what you need:",[109,36224,36226],{"className":111,"code":36225,"language":113,"meta":114,"style":114},"reg,  _ := os.ReadFile(\"SourceHanSansJP-Regular.ttf\")\nbold, _ := os.ReadFile(\"SourceHanSansJP-Bold.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"SourceHanSansJP\", reg),\n    gpdf.WithFont(\"SourceHanSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"SourceHanSansJP\", 11),\n)\n",[18,36227,36228,36256,36283,36287,36301,36324,36347,36369],{"__ignoreMap":114},[118,36229,36230,36233,36235,36238,36240,36242,36244,36246,36248,36250,36252,36254],{"class":120,"line":121},[118,36231,36232],{"class":226},"reg",[118,36234,395],{"class":124},[118,36236,36237],{"class":226},"  _ ",[118,36239,230],{"class":124},[118,36241,1447],{"class":226},[118,36243,25],{"class":124},[118,36245,9545],{"class":213},[118,36247,255],{"class":124},[118,36249,430],{"class":124},[118,36251,35501],{"class":433},[118,36253,430],{"class":124},[118,36255,199],{"class":124},[118,36257,36258,36260,36262,36264,36266,36268,36270,36272,36274,36276,36279,36281],{"class":120,"line":132},[118,36259,21479],{"class":226},[118,36261,395],{"class":124},[118,36263,3999],{"class":226},[118,36265,230],{"class":124},[118,36267,1447],{"class":226},[118,36269,25],{"class":124},[118,36271,9545],{"class":213},[118,36273,255],{"class":124},[118,36275,430],{"class":124},[118,36277,36278],{"class":433},"SourceHanSansJP-Bold.ttf",[118,36280,430],{"class":124},[118,36282,199],{"class":124},[118,36284,36285],{"class":120,"line":139},[118,36286,136],{"emptyLinePlaceholder":135},[118,36288,36289,36291,36293,36295,36297,36299],{"class":120,"line":149},[118,36290,2760],{"class":226},[118,36292,230],{"class":124},[118,36294,3595],{"class":226},[118,36296,25],{"class":124},[118,36298,3600],{"class":213},[118,36300,241],{"class":124},[118,36302,36303,36305,36307,36309,36311,36313,36315,36317,36319,36322],{"class":120,"line":161},[118,36304,4532],{"class":226},[118,36306,25],{"class":124},[118,36308,2798],{"class":213},[118,36310,255],{"class":124},[118,36312,430],{"class":124},[118,36314,35737],{"class":433},[118,36316,430],{"class":124},[118,36318,395],{"class":124},[118,36320,36321],{"class":226}," reg",[118,36323,266],{"class":124},[118,36325,36326,36328,36330,36332,36334,36336,36339,36341,36343,36345],{"class":120,"line":166},[118,36327,4532],{"class":226},[118,36329,25],{"class":124},[118,36331,2798],{"class":213},[118,36333,255],{"class":124},[118,36335,430],{"class":124},[118,36337,36338],{"class":433},"SourceHanSansJP-Bold",[118,36340,430],{"class":124},[118,36342,395],{"class":124},[118,36344,21621],{"class":226},[118,36346,266],{"class":124},[118,36348,36349,36351,36353,36355,36357,36359,36361,36363,36365,36367],{"class":120,"line":176},[118,36350,4532],{"class":226},[118,36352,25],{"class":124},[118,36354,12501],{"class":213},[118,36356,255],{"class":124},[118,36358,430],{"class":124},[118,36360,35737],{"class":433},[118,36362,430],{"class":124},[118,36364,395],{"class":124},[118,36366,14386],{"class":299},[118,36368,266],{"class":124},[118,36370,36371],{"class":120,"line":186},[118,36372,199],{"class":124},[14,36374,36375,36376,36379,36380,36382,36383,36385],{},"The suffix ",[18,36377,36378],{},"-Bold"," is the contract that wires ",[18,36381,21701],{}," to the Bold TTF. Skip that registration and ",[18,36384,21701],{}," falls back to a synthesized bold — a stroke overlay on the Regular glyphs. Legible for table headings, but visibly thinner than the real Bold outlines at display sizes.",[14,36387,36388],{},"CJK fonts don't ship italic variants as a rule, and Source Han Sans JP is no exception. If your layout needs italic emphasis on a Japanese run, reach for a heavier weight or color — an oblique transform on CJK glyphs looks broken rather than emphatic.",[41,36390,36392],{"id":36391},"pan-cjk-jp-only-or-the-super-otc","Pan-CJK, JP-only, or the Super OTC",[14,36394,36395],{},"Adobe publishes Source Han Sans in several granularities. They are not interchangeable for a Go PDF generator:",[46,36397,36398,36411,36421],{},[49,36399,36400,36403,36404,36407,36408,36410],{},[1629,36401,36402],{},"SourceHanSans.ttc"," (Super OTC) — every CJK language in one 20 MB+ TrueType Collection. gpdf does not select a face index inside a ",[18,36405,36406],{},".ttc","; you'd need to slice out the JP face first with ",[18,36409,36193],{}," and register the result. Skip the Super OTC.",[49,36412,36413,36416,36417,36420],{},[1629,36414,36415],{},"Region-specific OTF"," (e.g. ",[18,36418,36419],{},"SourceHanSans-Regular.otf",") — all CJK scripts unified, CFF outlines. Not readable by gpdf.",[49,36422,36423,4975,36426,36428,36429,36431],{},[1629,36424,36425],{},"Language-specific TTF",[18,36427,35501],{},") — JP-only, ",[18,36430,21334],{}," outlines. This is the one to grab.",[14,36433,36434,36435,36437,36438,36441,36442,36445],{},"If your document mixes Japanese with Korean or Chinese in the same page, register language-specific families side by side: ",[18,36436,35737],{}," plus ",[18,36439,36440],{},"SourceHanSansKR",". Switch explicitly with ",[18,36443,36444],{},"template.FontFamily"," where the text changes script. Reaching for the pan-CJK unified OTF would have fixed this with Han unification, but that introduces its own surprises for readers who expect JP-shaped kanji in JP text.",[41,36447,36449],{"id":36448},"when-to-pick-source-han-over-noto","When to pick Source Han over Noto",[14,36451,36452],{},"Same outlines, different distribution. Source Han Sans JP makes sense when:",[46,36454,36455,36458,36465],{},[49,36456,36457],{},"Your ops team prefers pinning to Adobe's GitHub release tags for reproducibility",[49,36459,36460,36461,36464],{},"You already mirror ",[18,36462,36463],{},"github.com/adobe-fonts"," internally (common at enterprises with locked-down artifact policies)",[49,36466,36467],{},"The pan-CJK OTC bundle is useful elsewhere in your pipeline — a desktop publishing step, a DTP handoff, a brand system that standardizes on the Adobe name",[14,36469,36470],{},"Noto Sans JP is the better pick when:",[46,36472,36473,36480,36483],{},[49,36474,36475,36476,36479],{},"You want the shortest route to a TTF (",[18,36477,36478],{},"https://fonts.google.com/noto/specimen/Noto+Sans+JP"," → zip → done)",[49,36481,36482],{},"Converting OTF to TTF isn't something you want in your build",[49,36484,36485],{},"Your project already pulls other Google Fonts via an existing workflow",[14,36487,36488],{},"The rendered PDF looks the same either way. The decision is operational — where the file lives, how you version it, how ops feels about it — not aesthetic.",[41,36490,12870],{"id":12869},[46,36492,36493,36498,36503,36510],{},[49,36494,36495,36497],{},[3163,36496,33001],{"href":33000}," — the same glyphs, published as TTF out of the box",[49,36499,36500,36502],{},[3163,36501,9792],{"href":9791}," — the general CJK embedding recipe",[49,36504,36505,36509],{},[3163,36506,36508],{"href":36507},"/blog/ipaex-gothic-gpdf","How do I use IPAex Gothic in gpdf?"," — the IPA-licensed alternative for Japanese institutional submissions",[49,36511,36512,36514],{},[3163,36513,32994],{"href":22032}," — troubleshooting missing glyphs",[41,36516,4794],{"id":4793},[14,36518,6933],{},[109,36520,36521],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,36522,36523],{"__ignoreMap":114},[118,36524,36525,36527,36529],{"class":120,"line":121},[118,36526,113],{"class":128},[118,36528,3156],{"class":433},[118,36530,3159],{"class":433},[14,36532,36533,3169,36536],{},[3163,36534,3168],{"href":3165,"rel":36535},[3167],[3163,36537,3174],{"href":3172,"rel":36538},[3167],[3176,36540,36541],{},"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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":114,"searchDepth":132,"depth":132,"links":36543},[36544,36545,36546,36547,36548,36549,36550,36551,36552,36553],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":35515,"depth":132,"text":35516},{"id":36037,"depth":132,"text":36038},{"id":36139,"depth":132,"text":36140},{"id":36209,"depth":132,"text":36210},{"id":36391,"depth":132,"text":36392},{"id":36448,"depth":132,"text":36449},{"id":12869,"depth":132,"text":12870},{"id":4793,"depth":132,"text":4794},"2026-04-19","Register the TTF variant of Source Han Sans JP from Adobe's GitHub release with gpdf.WithFont. Seven weights, SIL OFL, same glyphs as Noto Sans JP.",{"name":36557,"totalTime":3200,"tools":36558,"steps":36560},"Use Source Han Sans JP as the default font in a gpdf document",[3202,36559],"SourceHanSansJP-Regular.ttf from adobe-fonts/source-han-sans",[36561,36564,36567,36570,36573],{"name":36562,"text":36563},"Grab the TTF variant from Adobe's GitHub release","Open github.com/adobe-fonts/source-han-sans/releases. From the latest release, download the TTF bundle (not the OTF or SuperOTC) and extract SourceHanSansJP-Regular.ttf. gpdf parses TrueType, not CFF-flavored OpenType.",{"name":36565,"text":36566},"Load the bytes at startup","Use os.ReadFile(\"SourceHanSansJP-Regular.ttf\") or //go:embed. Pin a specific Adobe release tag in your build so CI reproduces the same glyph table next month.",{"name":36568,"text":36569},"Register the font at document construction","Pass gpdf.WithFont(\"SourceHanSansJP\", fontBytes) and gpdf.WithDefaultFont(\"SourceHanSansJP\", 11) to gpdf.NewDocument. No AddUTF8Font, no filesystem path.",{"name":36571,"text":36572},"Register extra weights if you need them","Source Han Sans JP ships seven weights from ExtraLight to Heavy. Register the Bold TTF under SourceHanSansJP-Bold and template.Bold() picks up the real bold outlines instead of a synthesized stroke.",{"name":36574,"text":36575},"Keep OFL.txt with your distribution","SIL OFL 1.1 requires the license text to ship alongside the font binary. If you //go:embed the TTF, embed OFL.txt into LICENSES/ and reference it from NOTICE.",{},"/blog/source-han-sans-jp-with-gpdf",{"title":35477,"description":36555},"blog/010.source-han-sans-jp-with-gpdf",[6996,9860,3226],"hHfZefmlhM63qDcrmPeL-P-NG1L-T07msl5f7px5a7g",{"id":36583,"title":4828,"author":36584,"body":36585,"date":36554,"description":38078,"draft":3196,"extension":3197,"howTo":3220,"image":3220,"meta":38079,"navigation":135,"path":4069,"seo":38080,"stem":38081,"tags":38082,"updated":3220,"__hash__":38083},"blog/blog/011.why-gpdf-is-faster.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":36586,"toc":38056},[36587,36589,36607,36631,36634,36643,36646,36650,36657,36750,36760,36763,36770,36774,36777,36783,36794,36797,36803,37082,37116,37134,37137,37141,37144,37161,37164,37174,37177,37184,37187,37196,37289,37316,37319,37322,37464,37470,37480,37484,37490,37496,37502,37505,37511,37514,37518,37528,37531,37535,37538,37541,37544,37570,37577,37580,37587,37713,37716,37720,37731,37734,37738,37750,37757,37761,37764,37841,37844,37847,37851,37854,37860,37870,37884,37893,37896,37900,37903,37942,37951,37954,37965,37967,37976,37982,37994,38000,38006,38008,38011,38023,38031,38033,38053],[41,36588,44],{"id":43},[14,36590,36591,36592,36594,36595,36597,36598,36600,36601,36603,36604,36606],{},"gpdf renders a single page in ",[1629,36593,3248],{},", a 4×10 invoice table in ",[1629,36596,16053],{},", and a 100-page paginated report in ",[1629,36599,4131],{},". The next-fastest maintained Go PDF library — ",[18,36602,35345],{}," — does the same 100-page job in ",[1629,36605,17752],{},", roughly 17× slower. It's not a tuning difference. It's three design choices that stack:",[1624,36608,36609,36615,36625],{},[49,36610,36611,36614],{},[1629,36612,36613],{},"Single-pass layout."," No intermediate AST between the builder API and the PDF content stream.",[49,36616,36617,36620,36621,36624],{},[1629,36618,36619],{},"Concrete types on the hot path."," No reflection, no ",[18,36622,36623],{},"interface{}",", no virtual dispatch inside the layout loop.",[49,36626,36627,36630],{},[1629,36628,36629],{},"A TrueType subsetter that resolves the cmap once."," Not once per glyph. Not once per page. Once.",[14,36632,36633],{},"Any one of these gets you 2–3×. Stacked, you're at an order of magnitude.",[14,36635,36636,36637,36642],{},"This post walks the code path that produces those numbers. The benchmark source is public — ",[3163,36638,36640],{"href":4171,"rel":36639},[3167],[18,36641,27100],{}," — clone it, re-run on your hardware, open an issue if the numbers disagree.",[14,36644,36645],{},"Bias disclosure up front: we ship gpdf. The honest version of \"we're faster\" is \"we made a different set of trade-offs,\" and the interesting question is what we gave up to get here. That's the second half of the post.",[41,36647,36649],{"id":36648},"what-fast-actually-means-here","What \"fast\" actually means here",[14,36651,36652,36653,36656],{},"Before the architecture, the scoreboard we're explaining (Apple M1, Go 1.25, no CGO, ",[18,36654,36655],{},"-benchmem"," enabled):",[1516,36658,36659,36675],{},[1519,36660,36661],{},[1522,36662,36663,36665,36667,36669,36671,36673],{},[1525,36664,4093],{},[1525,36666,1587],{},[1525,36668,1539],{},[1525,36670,1557],{},[1525,36672,1565],{},[1525,36674,17700],{},[1532,36676,36677,36695,36713,36732],{},[1522,36678,36679,36682,36686,36688,36691,36693],{},[1537,36680,36681],{},"Single page hello world",[1537,36683,36684],{},[1629,36685,3248],{},[1537,36687,17717],{},[1537,36689,36690],{},"135 µs",[1537,36692,17714],{},[1537,36694,17720],{},[1522,36696,36697,36699,36703,36705,36708,36710],{},[1537,36698,17725],{},[1537,36700,36701],{},[1629,36702,16053],{},[1537,36704,17735],{},[1537,36706,36707],{},"243 µs",[1537,36709,17732],{},[1537,36711,36712],{},"8,600 µs",[1522,36714,36715,36717,36721,36724,36727,36729],{},[1537,36716,4126],{},[1537,36718,36719],{},[1629,36720,4131],{},[1537,36722,36723],{},"11,700 µs",[1537,36725,36726],{},"11,900 µs",[1537,36728,36712],{},[1537,36730,36731],{},"19,800 µs",[1522,36733,36734,36736,36740,36742,36745,36747],{},[1537,36735,17760],{},[1537,36737,36738],{},[1629,36739,17765],{},[1537,36741,17771],{},[1537,36743,36744],{},"n/a",[1537,36746,17768],{},[1537,36748,36749],{},"10,400 µs",[14,36751,36752,36753,36756,36757,36759],{},"Two things you'll notice before we explain them. The gap ",[1629,36754,36755],{},"widens"," with page count — 10× on a hello world, 17× on 100 pages. And the gap ",[1629,36758,36755],{}," with complexity — 108 µs for a table versus 8.6 ms for the same table through Maroto's gofpdf backend.",[14,36761,36762],{},"Both of those shapes come from the same root cause: the cost per element in gpdf is nearly flat, because the layout loop doesn't allocate on the common path. We'll get to why.",[14,36764,36765,36766,36769],{},"Quick disclaimer nobody wants to read but we're writing anyway: ",[1629,36767,36768],{},"absolute speed matters less than people think for most PDF workloads",". If your biggest document is a one-page receipt, every maintained library in that table is fast enough to generate on the request path. The threshold that matters is \"can I generate 100 of these synchronously in a batch without pushing to a queue,\" and that's where the gap starts mattering.",[41,36771,36773],{"id":36772},"decision-1-no-intermediate-ast","Decision 1: No intermediate AST",[14,36775,36776],{},"Most PDF builder libraries work like this:",[109,36778,36781],{"className":36779,"code":36780,"language":4993},[34105],"builder API → document tree (AST) → layout pass → serializer → bytes\n",[18,36782,36780],{"__ignoreMap":114},[14,36784,36785,36786,36789,36790,36793],{},"The document-tree step is the problem. Every ",[18,36787,36788],{},".Text()"," call allocates a node. Every ",[18,36791,36792],{},".Row()"," allocates a container. The layout pass walks the tree to compute positions. Then the serializer walks it again to emit bytes. Three passes, three sets of allocations, three trips over the same data through the CPU cache.",[14,36795,36796],{},"gpdf doesn't have step 2. The builder writes directly into a layout context that writes directly into the content stream. One pass.",[14,36798,36799,36800,6349],{},"Here's the concrete code path for a text element, lightly edited for length. The real version is in ",[18,36801,36802],{},"template/col_builder.go",[109,36804,36806],{"className":111,"code":36805,"language":113,"meta":114,"style":114},"func (c *ColBuilder) Text(s string, opts ...TextOption) {\n    opt := c.resolveOptions(opts)\n    box := c.currentBox()\n    w := c.measureText(s, opt)\n    h := opt.FontSize.Pt() * opt.LineHeight\n    c.writer.BeginText()\n    c.writer.SetFont(opt.Font, opt.FontSize)\n    c.writer.MoveTo(box.X, box.Y-opt.FontSize.Pt())\n    c.writer.ShowString(s)\n    c.writer.EndText()\n    c.advance(w, h)\n}\n",[18,36807,36808,36843,36863,36879,36904,36932,36947,36978,37024,37043,37058,37078],{"__ignoreMap":114},[118,36809,36810,36812,36814,36816,36818,36820,36822,36824,36826,36829,36831,36833,36835,36837,36839,36841],{"class":120,"line":121},[118,36811,210],{"class":124},[118,36813,4975],{"class":124},[118,36815,4978],{"class":331},[118,36817,4981],{"class":124},[118,36819,410],{"class":128},[118,36821,345],{"class":124},[118,36823,4988],{"class":213},[118,36825,255],{"class":124},[118,36827,36828],{"class":331},"s",[118,36830,4996],{"class":1130},[118,36832,395],{"class":124},[118,36834,5001],{"class":331},[118,36836,5004],{"class":124},[118,36838,5007],{"class":128},[118,36840,345],{"class":124},[118,36842,220],{"class":124},[118,36844,36845,36848,36850,36852,36854,36857,36859,36861],{"class":120,"line":132},[118,36846,36847],{"class":226},"    opt ",[118,36849,230],{"class":124},[118,36851,23100],{"class":226},[118,36853,25],{"class":124},[118,36855,36856],{"class":213},"resolveOptions",[118,36858,255],{"class":124},[118,36860,5156],{"class":226},[118,36862,199],{"class":124},[118,36864,36865,36868,36870,36872,36874,36877],{"class":120,"line":139},[118,36866,36867],{"class":226},"    box ",[118,36869,230],{"class":124},[118,36871,23100],{"class":226},[118,36873,25],{"class":124},[118,36875,36876],{"class":213},"currentBox",[118,36878,1193],{"class":124},[118,36880,36881,36884,36886,36888,36890,36893,36895,36897,36899,36902],{"class":120,"line":149},[118,36882,36883],{"class":226},"    w ",[118,36885,230],{"class":124},[118,36887,23100],{"class":226},[118,36889,25],{"class":124},[118,36891,36892],{"class":213},"measureText",[118,36894,255],{"class":124},[118,36896,36828],{"class":226},[118,36898,395],{"class":124},[118,36900,36901],{"class":226}," opt",[118,36903,199],{"class":124},[118,36905,36906,36909,36911,36913,36915,36917,36919,36921,36923,36925,36927,36929],{"class":120,"line":161},[118,36907,36908],{"class":226},"    h ",[118,36910,230],{"class":124},[118,36912,36901],{"class":226},[118,36914,25],{"class":124},[118,36916,455],{"class":226},[118,36918,25],{"class":124},[118,36920,6095],{"class":213},[118,36922,217],{"class":124},[118,36924,334],{"class":124},[118,36926,36901],{"class":226},[118,36928,25],{"class":124},[118,36930,36931],{"class":226},"LineHeight\n",[118,36933,36934,36936,36938,36940,36942,36945],{"class":120,"line":166},[118,36935,2022],{"class":226},[118,36937,25],{"class":124},[118,36939,26779],{"class":226},[118,36941,25],{"class":124},[118,36943,36944],{"class":213},"BeginText",[118,36946,1193],{"class":124},[118,36948,36949,36951,36953,36955,36957,36959,36961,36964,36966,36968,36970,36972,36974,36976],{"class":120,"line":176},[118,36950,2022],{"class":226},[118,36952,25],{"class":124},[118,36954,26779],{"class":226},[118,36956,25],{"class":124},[118,36958,13647],{"class":213},[118,36960,255],{"class":124},[118,36962,36963],{"class":226},"opt",[118,36965,25],{"class":124},[118,36967,23737],{"class":226},[118,36969,395],{"class":124},[118,36971,36901],{"class":226},[118,36973,25],{"class":124},[118,36975,455],{"class":226},[118,36977,199],{"class":124},[118,36979,36980,36982,36984,36986,36988,36991,36993,36996,36998,37001,37003,37006,37008,37010,37012,37014,37016,37018,37020,37022],{"class":120,"line":186},[118,36981,2022],{"class":226},[118,36983,25],{"class":124},[118,36985,26779],{"class":226},[118,36987,25],{"class":124},[118,36989,36990],{"class":213},"MoveTo",[118,36992,255],{"class":124},[118,36994,36995],{"class":226},"box",[118,36997,25],{"class":124},[118,36999,37000],{"class":226},"X",[118,37002,395],{"class":124},[118,37004,37005],{"class":226}," box",[118,37007,25],{"class":124},[118,37009,1506],{"class":226},[118,37011,7430],{"class":124},[118,37013,36963],{"class":226},[118,37015,25],{"class":124},[118,37017,455],{"class":226},[118,37019,25],{"class":124},[118,37021,6095],{"class":213},[118,37023,1289],{"class":124},[118,37025,37026,37028,37030,37032,37034,37037,37039,37041],{"class":120,"line":196},[118,37027,2022],{"class":226},[118,37029,25],{"class":124},[118,37031,26779],{"class":226},[118,37033,25],{"class":124},[118,37035,37036],{"class":213},"ShowString",[118,37038,255],{"class":124},[118,37040,36828],{"class":226},[118,37042,199],{"class":124},[118,37044,37045,37047,37049,37051,37053,37056],{"class":120,"line":202},[118,37046,2022],{"class":226},[118,37048,25],{"class":124},[118,37050,26779],{"class":226},[118,37052,25],{"class":124},[118,37054,37055],{"class":213},"EndText",[118,37057,1193],{"class":124},[118,37059,37060,37062,37064,37067,37069,37072,37074,37076],{"class":120,"line":207},[118,37061,2022],{"class":226},[118,37063,25],{"class":124},[118,37065,37066],{"class":213},"advance",[118,37068,255],{"class":124},[118,37070,37071],{"class":226},"w",[118,37073,395],{"class":124},[118,37075,14623],{"class":226},[118,37077,199],{"class":124},[118,37079,37080],{"class":120,"line":223},[118,37081,1479],{"class":124},[14,37083,37084,37085,37088,37089,37091,37092,37095,37096,1592,37098,1592,37100,37102,37103,3096,37106,3096,37109,3096,37112,37115],{},"No node gets pushed to a tree. No positions get deferred. The writer is a ",[18,37086,37087],{},"*pdf.Writer"," that holds an ",[18,37090,35317],{}," (typically a ",[18,37093,37094],{},"bytes.Buffer","), and ",[18,37097,36944],{},[18,37099,36990],{},[18,37101,37036],{}," write the PDF operators (",[18,37104,37105],{},"BT",[18,37107,37108],{},"Td",[18,37110,37111],{},"Tj",[18,37113,37114],{},"ET",") to that buffer immediately.",[14,37117,37118,37119,37121,37122,9731,37124,37126,37127,37129,37130,37133],{},"Compare to how gofpdf does the same logical operation. gofpdf maintains a ",[18,37120,5910],{}," object with a slice of operations. Each ",[18,37123,12972],{},[18,37125,12975],{}," call appends to that slice. ",[18,37128,13378],{}," (or ",[18,37131,37132],{},"OutputFileAndClose",") walks the slice at the end and emits the bytes. That's two allocations per cell — one for the operation struct, one for the string copy — and one extra pass over the data.",[14,37135,37136],{},"For a 100-page report with ~40 lines per page, that's 4,000 extra allocations gpdf doesn't make.",[1613,37138,37140],{"id":37139},"where-single-pass-hurts","Where single-pass hurts",[14,37142,37143],{},"The obvious question: how do you do anything that needs to know the final page layout before you start emitting bytes? Headers with page numbers. Tables that span pages. Footers anchored to the bottom of the last line of body text.",[14,37145,37146,37147,37149,37150,37153,37154,3096,37157,37160],{},"Two answers. One, we buffer the current ",[1629,37148,5910],{},", not the document. A page is a bounded unit — tens of kilobytes, not megabytes. When ",[18,37151,37152],{},"AddPage()"," runs for the next page, the current page's content stream is finalized (",[18,37155,37156],{},"Length",[18,37158,37159],{},"Filter",", offsets), its xref entry is written, and the page buffer is reset. Memory high-water mark stays O(one page).",[14,37162,37163],{},"Two, for genuinely global elements (page count \"Page 3 of 27\"), we defer those specific spans to a fix-up pass. The rest of the content is already in the stream. The fix-up walks a short list of deferred-reference markers and patches them. This is the one place in the codebase where we pay something like an AST cost, and we pay it only for the content that actually needs it.",[14,37165,37166,37167,37169,37170,37173],{},"The trade we made: you can't do arbitrary post-processing on a node tree, because there's no node tree. You can't write a plugin that reorders \"all ",[18,37168,425],{}," nodes with ",[18,37171,37172],{},"bold: true",".\" If you need that shape of API, Maroto v2 does it; gpdf does not.",[14,37175,37176],{},"We think that's the right trade for the use cases gpdf targets. Most PDFs are produced left-to-right, top-to-bottom, in a known-at-construction-time layout. The cost of keeping an AST around for the minority of use cases that need it is paid on every page of the majority.",[41,37178,37180,37181,37183],{"id":37179},"decision-2-no-reflection-no-interface-on-the-hot-path","Decision 2: No reflection, no ",[18,37182,36623],{}," on the hot path",[14,37185,37186],{},"This one is less interesting to write about than to profile. But it's where half the remaining speed came from.",[14,37188,37189,37190,37192,37193,37195],{},"Look at ",[18,37191,1539],{},"'s ",[18,37194,11353],{}," signature:",[109,37197,37199],{"className":111,"code":37198,"language":113,"meta":114,"style":114},"func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string,\n    ln int, alignStr string, fill bool, link int, linkStr string) { ... }\n",[18,37200,37201,37244],{"__ignoreMap":114},[118,37202,37203,37205,37207,37210,37212,37215,37217,37220,37222,37224,37226,37228,37230,37232,37235,37237,37240,37242],{"class":120,"line":121},[118,37204,210],{"class":124},[118,37206,4975],{"class":124},[118,37208,37209],{"class":331},"f ",[118,37211,4981],{"class":124},[118,37213,37214],{"class":128},"Fpdf",[118,37216,345],{"class":124},[118,37218,37219],{"class":213}," CellFormat",[118,37221,255],{"class":124},[118,37223,37071],{"class":331},[118,37225,395],{"class":124},[118,37227,14623],{"class":331},[118,37229,6638],{"class":1130},[118,37231,395],{"class":124},[118,37233,37234],{"class":331}," txtStr",[118,37236,395],{"class":124},[118,37238,37239],{"class":331}," borderStr",[118,37241,4996],{"class":1130},[118,37243,2643],{"class":124},[118,37245,37246,37249,37252,37254,37257,37259,37261,37264,37267,37269,37272,37274,37276,37279,37281,37283,37285,37287],{"class":120,"line":132},[118,37247,37248],{"class":331},"    ln",[118,37250,37251],{"class":1130}," int",[118,37253,395],{"class":124},[118,37255,37256],{"class":331}," alignStr",[118,37258,4996],{"class":1130},[118,37260,395],{"class":124},[118,37262,37263],{"class":331}," fill",[118,37265,37266],{"class":1130}," bool",[118,37268,395],{"class":124},[118,37270,37271],{"class":331}," link",[118,37273,37251],{"class":1130},[118,37275,395],{"class":124},[118,37277,37278],{"class":331}," linkStr",[118,37280,4996],{"class":1130},[118,37282,345],{"class":124},[118,37284,8081],{"class":124},[118,37286,5004],{"class":124},[118,37288,32794],{"class":124},[14,37290,37291,37292,37294,37295,37298,37299,37302,37303,37306,37307,37309,37310,37312,37313,37315],{},"Fine. Now look at Maroto's component tree. A ",[18,37293,3687],{}," holds ",[18,37296,37297],{},"[]Component",". A ",[18,37300,37301],{},"Component"," is an interface. Every layout operation is a virtual dispatch: ",[18,37304,37305],{},"component.Render(ctx)",". For a single ",[18,37308,387],{}," with a ",[18,37311,425],{}," and a ",[18,37314,675],{},", that's three interface dispatches. On a 100-page report with ~30 rows per page and ~3 components per row, that's ~9,000 dispatches.",[14,37317,37318],{},"Individually, an interface dispatch in Go is ~2-3 ns. Not a crime. But the dispatch also forces the compiler to keep the boxed value on the heap — you can't stack-allocate through an interface without a devirtualization pass the Go compiler doesn't always do. So the cost isn't just the dispatch; it's the allocation that feeds it.",[14,37320,37321],{},"gpdf's layout engine uses concrete structs:",[109,37323,37325],{"className":111,"code":37324,"language":113,"meta":114,"style":114},"type RowBuilder struct {\n    doc    *Document\n    parent *pageState\n    spans  [12]int\n    cols   [12]ColBuilder  // value, not pointer, not interface\n    n      uint8\n}\n\ntype ColBuilder struct {\n    row    *RowBuilder\n    span   int\n    cursor document.Point\n    writer *pdf.Writer\n}\n",[18,37326,37327,37340,37350,37360,37374,37390,37398,37402,37406,37417,37427,37434,37446,37460],{"__ignoreMap":114},[118,37328,37329,37332,37335,37338],{"class":120,"line":121},[118,37330,37331],{"class":124},"type",[118,37333,37334],{"class":128}," RowBuilder",[118,37336,37337],{"class":124}," struct",[118,37339,220],{"class":124},[118,37341,37342,37345,37347],{"class":120,"line":132},[118,37343,37344],{"class":226},"    doc    ",[118,37346,4981],{"class":124},[118,37348,37349],{"class":128},"Document\n",[118,37351,37352,37355,37357],{"class":120,"line":139},[118,37353,37354],{"class":226},"    parent ",[118,37356,4981],{"class":124},[118,37358,37359],{"class":128},"pageState\n",[118,37361,37362,37365,37367,37369,37371],{"class":120,"line":149},[118,37363,37364],{"class":226},"    spans  ",[118,37366,15332],{"class":124},[118,37368,20],{"class":299},[118,37370,15029],{"class":124},[118,37372,37373],{"class":1130},"int\n",[118,37375,37376,37379,37381,37383,37385,37387],{"class":120,"line":161},[118,37377,37378],{"class":226},"    cols   ",[118,37380,15332],{"class":124},[118,37382,20],{"class":299},[118,37384,15029],{"class":124},[118,37386,410],{"class":128},[118,37388,37389],{"class":3981},"  // value, not pointer, not interface\n",[118,37391,37392,37395],{"class":120,"line":166},[118,37393,37394],{"class":226},"    n      ",[118,37396,37397],{"class":1130},"uint8\n",[118,37399,37400],{"class":120,"line":176},[118,37401,1479],{"class":124},[118,37403,37404],{"class":120,"line":186},[118,37405,136],{"emptyLinePlaceholder":135},[118,37407,37408,37410,37413,37415],{"class":120,"line":196},[118,37409,37331],{"class":124},[118,37411,37412],{"class":128}," ColBuilder",[118,37414,37337],{"class":124},[118,37416,220],{"class":124},[118,37418,37419,37422,37424],{"class":120,"line":202},[118,37420,37421],{"class":226},"    row    ",[118,37423,4981],{"class":124},[118,37425,37426],{"class":128},"RowBuilder\n",[118,37428,37429,37432],{"class":120,"line":207},[118,37430,37431],{"class":226},"    span   ",[118,37433,37373],{"class":1130},[118,37435,37436,37439,37441,37443],{"class":120,"line":223},[118,37437,37438],{"class":226},"    cursor ",[118,37440,258],{"class":128},[118,37442,25],{"class":124},[118,37444,37445],{"class":128},"Point\n",[118,37447,37448,37451,37453,37455,37457],{"class":120,"line":244},[118,37449,37450],{"class":226},"    writer ",[118,37452,4981],{"class":124},[118,37454,550],{"class":128},[118,37456,25],{"class":124},[118,37458,37459],{"class":128},"Writer\n",[118,37461,37462],{"class":120,"line":269},[118,37463,1479],{"class":124},[14,37465,37466,37469],{},[18,37467,37468],{},"cols"," is a value array, sized to the maximum column count (12, from the grid system). No heap allocation. No interface dispatch when the row iterates its columns. The builder holds a pointer to the writer, not the other way around — the writer has no knowledge of the builder tree.",[14,37471,37472,37473,37476,37477,37479],{},"The callback pattern (",[18,37474,37475],{},"r.Col(4, func(c *ColBuilder) { ... })",") is not an accident. Every other shape we prototyped — a chainable struct-returning API, a tree of boxed Component interfaces — was slower. The closure has zero allocations because the ",[18,37478,410],{}," is a value the caller holds by pointer via the parameter; the closure itself is escape-analyzed to the stack in the common case.",[1613,37481,37483],{"id":37482},"how-we-know-this-worked","How we know this worked",[14,37485,37486,37489],{},[18,37487,37488],{},"go test -run=XXX -bench=BenchmarkSinglePage -memprofile=mem.out"," on gpdf gives one number we're proud of:",[109,37491,37494],{"className":37492,"code":37493,"language":4993},[34105],"BenchmarkSinglePage-8   91270   13120 ns/op   8321 B/op   52 allocs/op\n",[18,37495,37493],{"__ignoreMap":114},[14,37497,37498,37499,37501],{},"Fifty-two allocations for an entire PDF page. Almost all of them are the initial page buffer, the font metrics lookup (once per font, not once per glyph), and the final ",[18,37500,37094],{}," growth. The layout loop allocates zero — look at the profile.",[14,37503,37504],{},"gofpdf on the same page:",[109,37506,37509],{"className":37507,"code":37508,"language":4993},[34105],"BenchmarkGofpdfSinglePage-8   7500   132400 ns/op   71200 B/op   430 allocs/op\n",[18,37510,37508],{"__ignoreMap":114},[14,37512,37513],{},"430 allocations. Most of them are the operation slice and the string copies feeding it. Move that factor of ~8 difference in allocations through the GC, and the runtime gap of ~10× follows mechanically.",[1613,37515,37517],{"id":37516},"what-we-gave-up","What we gave up",[14,37519,37520,37521,37523,37524,37527],{},"Zero ergonomics on the hot path means fewer extension points. If you want to write a custom element type that plugs into gpdf's layout — the equivalent of implementing ",[18,37522,37301],{}," in Maroto — you can't. There's no interface to satisfy. What we offer instead is ",[18,37525,37526],{},"template.WithWriterSetup()",", which gives you a hook into the PDF writer for things like custom annotations, PDF/A metadata, or encryption. For layout extension, you write it as a helper that calls the same builder methods a user would.",[14,37529,37530],{},"Fewer extension points is a real cost. We've decided it's worth it. If the project's shape changes in a direction where it isn't, we'll revisit.",[41,37532,37534],{"id":37533},"decision-3-truetype-subsetting-without-re-walks","Decision 3: TrueType subsetting without re-walks",[14,37536,37537],{},"This is the one where the CJK benchmark (133 µs vs 254 µs for gofpdf) gets most of its gap.",[14,37539,37540],{},"A quick summary of what TrueType subsetting does. When you embed a Japanese font in a PDF, you don't want to embed all 20,000+ glyphs — that's 15 MB of font data in a 100 KB document. You want to embed only the glyphs your document actually uses, packaged as a valid subset TTF that a PDF reader can decode.",[14,37542,37543],{},"To do that, you:",[1624,37545,37546,37561,37564,37567],{},[49,37547,37548,37549,37551,37552,37554,37555,37557,37558,37560],{},"Parse the full TTF tables: ",[18,37550,21331],{}," (character-to-glyph mapping), ",[18,37553,21334],{}," (outlines), ",[18,37556,21337],{}," (offsets into glyf), ",[18,37559,21340],{}," (horizontal metrics), etc.",[49,37562,37563],{},"For each character the document uses, look up its glyph ID via the cmap.",[49,37565,37566],{},"Transitively collect the glyphs that compound glyphs reference.",[49,37568,37569],{},"Emit a new TTF with only those glyphs, renumbered.",[14,37571,37572,37573,37576],{},"Step 2 — the cmap lookup — is the hot path. gofpdf's implementation walks the cmap table from the top on ",[1629,37574,37575],{},"every glyph lookup",". For a Latin-only page, that's fine; the cmap is small and the cache behaves. For a CJK page with 150 unique glyphs, it's 150 full table walks.",[14,37578,37579],{},"The cmap format 12 (used by most modern CJK fonts) is a sorted array of (start, end, startGlyphID) triples. A single walk is O(n) in the number of ranges, ~200-500 for NotoSansJP. 150 glyph lookups × 400 ranges × per-range comparison = way more work than necessary.",[14,37581,37582,37583,37586],{},"gpdf resolves the entire cmap into a ",[18,37584,37585],{},"map[rune]uint16"," on first font load. After that, every lookup is O(1). For NotoSansJP, the one-time cost is ~150 µs; after that, 10 ns per character.",[109,37588,37590],{"className":111,"code":37589,"language":113,"meta":114,"style":114},"// Simplified from pdf/font/ttf.go\ntype Font struct {\n    runeToGID map[rune]uint16  // resolved once at load\n    glyphs    []glyph          // indexed by GID\n    metrics   []glyphMetric\n}\n\nfunc (f *Font) GlyphFor(r rune) uint16 {\n    return f.runeToGID[r]  // O(1), cache-friendly, no table walk\n}\n",[18,37591,37592,37597,37608,37627,37640,37650,37654,37658,37689,37709],{"__ignoreMap":114},[118,37593,37594],{"class":120,"line":121},[118,37595,37596],{"class":3981},"// Simplified from pdf/font/ttf.go\n",[118,37598,37599,37601,37604,37606],{"class":120,"line":132},[118,37600,37331],{"class":124},[118,37602,37603],{"class":128}," Font",[118,37605,37337],{"class":124},[118,37607,220],{"class":124},[118,37609,37610,37613,37616,37619,37621,37624],{"class":120,"line":139},[118,37611,37612],{"class":226},"    runeToGID ",[118,37614,37615],{"class":124},"map[",[118,37617,37618],{"class":1130},"rune",[118,37620,15029],{"class":124},[118,37622,37623],{"class":1130},"uint16",[118,37625,37626],{"class":3981},"  // resolved once at load\n",[118,37628,37629,37632,37634,37637],{"class":120,"line":149},[118,37630,37631],{"class":226},"    glyphs    ",[118,37633,16498],{"class":124},[118,37635,37636],{"class":128},"glyph",[118,37638,37639],{"class":3981},"          // indexed by GID\n",[118,37641,37642,37645,37647],{"class":120,"line":161},[118,37643,37644],{"class":226},"    metrics   ",[118,37646,16498],{"class":124},[118,37648,37649],{"class":128},"glyphMetric\n",[118,37651,37652],{"class":120,"line":166},[118,37653,1479],{"class":124},[118,37655,37656],{"class":120,"line":176},[118,37657,136],{"emptyLinePlaceholder":135},[118,37659,37660,37662,37664,37666,37668,37670,37672,37675,37677,37679,37682,37684,37687],{"class":120,"line":186},[118,37661,210],{"class":124},[118,37663,4975],{"class":124},[118,37665,37209],{"class":331},[118,37667,4981],{"class":124},[118,37669,23737],{"class":128},[118,37671,345],{"class":124},[118,37673,37674],{"class":213}," GlyphFor",[118,37676,255],{"class":124},[118,37678,363],{"class":331},[118,37680,37681],{"class":1130}," rune",[118,37683,345],{"class":124},[118,37685,37686],{"class":1130}," uint16",[118,37688,220],{"class":124},[118,37690,37691,37693,37695,37697,37700,37702,37704,37706],{"class":120,"line":196},[118,37692,15778],{"class":142},[118,37694,26768],{"class":226},[118,37696,25],{"class":124},[118,37698,37699],{"class":226},"runeToGID",[118,37701,15332],{"class":124},[118,37703,363],{"class":226},[118,37705,15029],{"class":124},[118,37707,37708],{"class":3981},"  // O(1), cache-friendly, no table walk\n",[118,37710,37711],{"class":120,"line":202},[118,37712,1479],{"class":124},[14,37714,37715],{},"One map, indexed by rune, populated by a single linear scan of the cmap table. For a document that uses the same font on multiple pages (all of them), this moves glyph lookup from \"quadratic-ish in pages × glyphs\" to \"linear in total glyphs plus a fixed constant.\"",[1613,37717,37719],{"id":37718},"why-format-12-is-the-detail-that-matters","Why \"format 12\" is the detail that matters",[14,37721,37722,37723,37192,37725,4919,37727,37730],{},"Most older Go PDF libraries were written when Latin text was the only text anyone cared about, and they implemented cmap format 4 — a segmented range for the Basic Multilingual Plane (U+0000–U+FFFF). Japanese text outside the BMP (less common, but some Kanji variants) needs format 12. ",[18,37724,1557],{},[18,37726,2745],{},[1629,37728,37729],{},"panics"," on NotoSansJP-Regular.ttf because its format-12 parser was never finished.",[14,37732,37733],{},"This is not a roast. It's an artifact: gofpdf was a great library for what Latin-heavy web apps needed in 2015, and the fork inherited its scope. The world shifted; CJK went from \"someone else's problem\" to \"most of the Japanese and Chinese Go ecosystems.\" gpdf implemented the full cmap spec because the alternative was an invoice that shows tofu boxes for 品目 — a real bug report we got in the first week of public release.",[1613,37735,37737],{"id":37736},"caching-that-scales-with-font-count-not-document-size","Caching that scales with font count, not document size",[14,37739,37740,37741,37743,37744,37746,37747,25],{},"The font cache is per-",[18,37742,22581],{},", not global. If you generate 10,000 PDFs with the same font, you pay the 150 µs resolve cost 10,000 times — unless you share a ",[18,37745,23737],{}," instance across documents, which the API allows via ",[18,37748,37749],{},"gpdf.WithSharedFont(preloadedFont)",[14,37751,37752,37753,37756],{},"For high-volume batch generation (the ",[18,37754,37755],{},"gpdf-api"," SaaS runs this way), the shared-font pattern is what makes P95 latency predictable. We publish it in the docs; most OSS users don't need it.",[41,37758,37760],{"id":37759},"the-combined-effect","The combined effect",[14,37762,37763],{},"Let's put the three decisions side by side on the 100-page benchmark (683 µs for gpdf, 11.7 ms for gofpdf):",[1516,37765,37766,37779],{},[1519,37767,37768],{},[1522,37769,37770,37773,37776],{},[1525,37771,37772],{},"Source of time",[1525,37774,37775],{},"gofpdf (per-page, approx)",[1525,37777,37778],{},"gpdf (per-page, approx)",[1532,37780,37781,37792,37803,37814,37825],{},[1522,37782,37783,37786,37789],{},[1537,37784,37785],{},"Operation slice build-up",[1537,37787,37788],{},"~60 µs",[1537,37790,37791],{},"0 (streams direct)",[1522,37793,37794,37797,37800],{},[1537,37795,37796],{},"Operation serialization",[1537,37798,37799],{},"~35 µs",[1537,37801,37802],{},"0 (already written)",[1522,37804,37805,37808,37811],{},[1537,37806,37807],{},"Glyph lookups (40 chars)",[1537,37809,37810],{},"~6 µs",[1537,37812,37813],{},"~0.4 µs",[1522,37815,37816,37819,37822],{},[1537,37817,37818],{},"Allocation / GC pressure",[1537,37820,37821],{},"~20 µs",[1537,37823,37824],{},"~2 µs",[1522,37826,37827,37831,37836],{},[1537,37828,37829],{},[1629,37830,11576],{},[1537,37832,37833],{},[1629,37834,37835],{},"~120 µs",[1537,37837,37838],{},[1629,37839,37840],{},"~7 µs",[14,37842,37843],{},"The numbers there are estimates from profiling; the real breakdown depends on content. But the shape is right. None of the three designs wins 10× alone. They compound.",[14,37845,37846],{},"The corollary: if you copy just one design into an existing library, you'll get a 2–3× gain. If you want the 10×, you need all three, and you can't retrofit the first one onto an AST-based library without rewriting it.",[41,37848,37850],{"id":37849},"what-we-gave-up-the-honest-section","What we gave up (the honest section)",[14,37852,37853],{},"We've been dancing around this. Here's the list in full:",[14,37855,37856,37859],{},[1629,37857,37858],{},"AST-based post-processing."," No plugin architecture. No \"walk the node tree and apply this transform.\" If you want to edit text styles globally across a document before rendering, you do it before you call the builder, not after.",[14,37861,37862,37865,37866,37869],{},[1629,37863,37864],{},"Introspection."," There's no ",[18,37867,37868],{},"doc.Components()"," that returns everything you put in. The document is a stream of operators by the time any meaningful method can run on it. For most users this never comes up; for the minority writing document-manipulation tools, it does.",[14,37871,37872,37875,37876,37879,37880,37883],{},[1629,37873,37874],{},"Reflection-based serialization."," We don't have a ",[18,37877,37878],{},"json.Unmarshal","-style API that turns arbitrary structs into PDF. The JSON Schema entry point (",[18,37881,37882],{},"template.FromJSON",") is explicit about its supported shapes, by design. If you want to point a library at a generic Go struct and get a PDF, that's unidoc's territory.",[14,37885,37886,37889,37890,37892],{},[1629,37887,37888],{},"The extensibility of an interface."," You can't implement ",[18,37891,37301],{}," and register a custom element. You can write a helper function that wraps the builder calls, and in practice that covers 95% of what people ask for, but it's a different model.",[14,37894,37895],{},"These are deliberate. Each of them, individually, would kill the speed. We picked the bucket of users whose work benefits from \"fast and opinionated\" over the bucket who need \"flexible and plugin-rich.\" If you're in the second bucket, Maroto v2 or unidoc is probably a better fit.",[41,37897,37899],{"id":37898},"can-i-re-run-the-benchmark","Can I re-run the benchmark?",[14,37901,37902],{},"Yes. That's the whole point of publishing the code.",[109,37904,37906],{"className":3145,"code":37905,"language":3147,"meta":114,"style":114},"git clone https://github.com/gpdf-dev/gpdf\ncd gpdf/_benchmark\ngo test -bench=. -benchmem -benchtime=5s\n",[18,37907,37908,37919,37926],{"__ignoreMap":114},[118,37909,37910,37913,37916],{"class":120,"line":121},[118,37911,37912],{"class":128},"git",[118,37914,37915],{"class":433}," clone",[118,37917,37918],{"class":433}," https://github.com/gpdf-dev/gpdf\n",[118,37920,37921,37923],{"class":120,"line":132},[118,37922,35169],{"class":213},[118,37924,37925],{"class":433}," gpdf/_benchmark\n",[118,37927,37928,37930,37933,37936,37939],{"class":120,"line":139},[118,37929,113],{"class":128},[118,37931,37932],{"class":433}," test",[118,37934,37935],{"class":433}," -bench=.",[118,37937,37938],{"class":433}," -benchmem",[118,37940,37941],{"class":433}," -benchtime=5s\n",[14,37943,37944,37945,37950],{},"The README in that directory documents the four workloads and what they measure. If your numbers differ materially (>20%) on the same CPU architecture and Go version, ",[3163,37946,37949],{"href":37947,"rel":37948},"https://github.com/gpdf-dev/gpdf/issues",[3167],"open an issue"," — drift is real and we want to know.",[14,37952,37953],{},"Two caveats worth naming:",[46,37955,37956,37962],{},[49,37957,37958,37959,37961],{},"The benchmark runs with ",[18,37960,36655],{},". If you disable it, numbers improve by ~5% across the board, which we don't count in public claims because it's not how anyone runs real code.",[49,37963,37964],{},"CGO is off. A few readers have asked whether a CGO-linked FreeType backend would be faster for font operations; we tested it, and the marshaling cost across the FFI boundary dominated any gain. The pure-Go subsetter wins for the access patterns a PDF generator has.",[41,37966,3054],{"id":3053},[14,37968,37969,37972,37973,25],{},[1629,37970,37971],{},"Why compare against gofpdf if it's archived?","\nBecause it's still the number-one result in GitHub search for \"go pdf,\" and most teams landing on gpdf are migrating from it. The benchmark needs to answer \"is the migration worth the effort\" for that audience. Short version: yes, and we wrote a ",[3163,37974,37975],{"href":4839},"migration guide",[14,37977,37978,37981],{},[1629,37979,37980],{},"Is 10× faster actually meaningful for PDF generation?","\nDepends on your workload. For one document per user request, not really — both libraries clear the \"generate on request\" bar. For batch operations (nightly statements, bulk invoices, report generation from a DB query), the gap translates directly into fewer machines. We've heard \"10× fewer workers\" from the first team that migrated their batch pipeline; we didn't audit their math but it tracks with the benchmark.",[14,37983,37984,37987,37988,37990,37991,37993],{},[1629,37985,37986],{},"What's the catch in the CJK number?","\nYou still need to ship the font file. gpdf subsets it for you, but a 3 MB NotoSansJP TTF is 3 MB you either embed in your Go binary or ",[18,37989,17912],{}," at startup. For distroless images this matters. The ",[18,37992,37755],{}," SaaS solves it by shipping the common fonts in the image; OSS users handle it themselves.",[14,37995,37996,37999],{},[1629,37997,37998],{},"Will gpdf slow down as features are added?","\nThis is the question we care most about. The answer is: we benchmark every release against the previous one, and a regression greater than 5% on any of the four workloads blocks the release. The benchmarks live in the same repo as the library for exactly this reason.",[14,38001,38002,38005],{},[1629,38003,38004],{},"Where does the name come from?","\ngpdf = Go + PDF. Not clever. Intentional.",[41,38007,4794],{"id":4793},[14,38009,38010],{},"gpdf is a Go library for generating PDFs. MIT, zero dependencies, native CJK.",[109,38012,38013],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,38014,38015],{"__ignoreMap":114},[118,38016,38017,38019,38021],{"class":120,"line":121},[118,38018,113],{"class":128},[118,38020,3156],{"class":433},[118,38022,3159],{"class":433},[14,38024,38025,3169,38028],{},[3163,38026,3168],{"href":3165,"rel":38027},[3167],[3163,38029,3174],{"href":3172,"rel":38030},[3167],[41,38032,4821],{"id":4820},[46,38034,38035,38040,38045],{},[49,38036,38037,38039],{},[3163,38038,4790],{"href":3381}," — the full library comparison with license and dependency details.",[49,38041,38042,38044],{},[3163,38043,18018],{"href":4839}," — five before/after API pairs, runnable.",[49,38046,38047,38048,25],{},"The benchmark code: ",[3163,38049,38051],{"href":4171,"rel":38050},[3167],[18,38052,27100],{},[3176,38054,38055],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":114,"searchDepth":132,"depth":132,"links":38057},[38058,38059,38060,38063,38068,38072,38073,38074,38075,38076,38077],{"id":43,"depth":132,"text":44},{"id":36648,"depth":132,"text":36649},{"id":36772,"depth":132,"text":36773,"children":38061},[38062],{"id":37139,"depth":139,"text":37140},{"id":37179,"depth":132,"text":38064,"children":38065},"Decision 2: No reflection, no interface{} on the hot path",[38066,38067],{"id":37482,"depth":139,"text":37483},{"id":37516,"depth":139,"text":37517},{"id":37533,"depth":132,"text":37534,"children":38069},[38070,38071],{"id":37718,"depth":139,"text":37719},{"id":37736,"depth":139,"text":37737},{"id":37759,"depth":132,"text":37760},{"id":37849,"depth":132,"text":37850},{"id":37898,"depth":132,"text":37899},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"gpdf generates a single PDF page in 13 µs and a 100-page report in 683 µs. Not a tuning trick — three architectural choices that compound. Here's the code path.",{},{"title":4828,"description":38078},"blog/011.why-gpdf-is-faster",[4867,3227,4866],"pI-SV5JMuNMvORIBFnC9IrrPp_fqVFkTDVWK7NqHhL8",{"id":38085,"title":32994,"author":38086,"body":38087,"date":39125,"description":39126,"draft":3196,"extension":3197,"howTo":39127,"image":3220,"meta":39147,"navigation":135,"path":22032,"seo":39148,"stem":39149,"tags":39150,"updated":3220,"__hash__":39152},"blog/blog/008.tofu-boxes-japanese.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":38088,"toc":39113},[38089,38091,38094,38096,38099,38102,38153,38157,38160,38625,38638,38647,38651,38654,38673,38690,38696,38700,38707,38845,38873,38877,38884,38887,38890,38918,38921,38925,38931,38993,39007,39010,39041,39045,39062,39064,39086,39088,39090,39102,39110],[41,38090,4879],{"id":4878},[14,38092,38093],{},"I wrote Japanese text with gpdf and the output PDF shows empty rectangles where the characters should be. What is that, and how do I get real Japanese glyphs into the file?",[41,38095,8546],{"id":8545},[14,38097,38098],{},"That is tofu — the PDF viewer draws a placeholder rectangle because the font embedded in the PDF has no glyph for the Unicode codepoint you asked for. Four things cause it, and one is far more common than the rest.",[14,38100,38101],{},"In order of frequency:",[1624,38103,38104,38115,38130,38143],{},[49,38105,38106,4919,38109,38111,38112,38114],{},[1629,38107,38108],{},"No CJK font registered.",[18,38110,22561],{}," has no ",[18,38113,2798],{}," call, so the document falls back to the PDF Base-14 fonts (Helvetica, Times, Courier). None of those cover U+3040–U+9FFF.",[49,38116,38117,4919,38122,38125,38126,38129],{},[1629,38118,38119,38120,25],{},"CJK font registered, but wrong family on ",[18,38121,8551],{},[18,38123,38124],{},"WithFont(\"NotoSansJP\", ...)"," is set, but ",[18,38127,38128],{},"template.FontFamily(\"Arial\")"," on the text forces gpdf to look up Japanese in a Latin font.",[49,38131,38132,38135,38136,38139,38140,38142],{},[1629,38133,38134],{},"Font file doesn't actually contain CJK glyphs."," The TTF on disk is a Latin subset (",[18,38137,38138],{},"NotoSans-Regular.ttf"," rather than ",[18,38141,9552],{},"). Name looks right, coverage is empty.",[49,38144,38145,38148,38149,38152],{},[1629,38146,38147],{},"Bytes corrupted before gpdf saw them."," The string was decoded as Shift-JIS or Latin-1 upstream and the runes you are trying to render are no longer Japanese. If you see ",[18,38150,38151],{},"縺ゅ→縺"," instead of rectangles, this is the one.",[41,38154,38156],{"id":38155},"the-canonical-fix-for-cause-1","The canonical fix for cause #1",[14,38158,38159],{},"Nine times out of ten, it is this:",[109,38161,38163],{"className":111,"code":38162,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", font),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"こんにちは、世界。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"hello.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,38164,38165,38171,38175,38181,38189,38197,38201,38209,38217,38225,38229,38233,38243,38269,38281,38295,38299,38303,38317,38335,38365,38387,38409,38413,38417,38431,38455,38485,38503,38507,38511,38515,38533,38545,38559,38563,38603,38617,38621],{"__ignoreMap":114},[118,38166,38167,38169],{"class":120,"line":121},[118,38168,125],{"class":124},[118,38170,129],{"class":128},[118,38172,38173],{"class":120,"line":132},[118,38174,136],{"emptyLinePlaceholder":135},[118,38176,38177,38179],{"class":120,"line":139},[118,38178,143],{"class":142},[118,38180,146],{"class":124},[118,38182,38183,38185,38187],{"class":120,"line":149},[118,38184,152],{"class":124},[118,38186,5303],{"class":128},[118,38188,158],{"class":124},[118,38190,38191,38193,38195],{"class":120,"line":161},[118,38192,152],{"class":124},[118,38194,155],{"class":128},[118,38196,158],{"class":124},[118,38198,38199],{"class":120,"line":166},[118,38200,136],{"emptyLinePlaceholder":135},[118,38202,38203,38205,38207],{"class":120,"line":176},[118,38204,152],{"class":124},[118,38206,3203],{"class":128},[118,38208,158],{"class":124},[118,38210,38211,38213,38215],{"class":120,"line":186},[118,38212,152],{"class":124},[118,38214,171],{"class":128},[118,38216,158],{"class":124},[118,38218,38219,38221,38223],{"class":120,"line":196},[118,38220,152],{"class":124},[118,38222,191],{"class":128},[118,38224,158],{"class":124},[118,38226,38227],{"class":120,"line":202},[118,38228,199],{"class":124},[118,38230,38231],{"class":120,"line":207},[118,38232,136],{"emptyLinePlaceholder":135},[118,38234,38235,38237,38239,38241],{"class":120,"line":223},[118,38236,210],{"class":124},[118,38238,214],{"class":213},[118,38240,217],{"class":124},[118,38242,220],{"class":124},[118,38244,38245,38247,38249,38251,38253,38255,38257,38259,38261,38263,38265,38267],{"class":120,"line":244},[118,38246,35604],{"class":226},[118,38248,395],{"class":124},[118,38250,1391],{"class":226},[118,38252,230],{"class":124},[118,38254,1447],{"class":226},[118,38256,25],{"class":124},[118,38258,9545],{"class":213},[118,38260,255],{"class":124},[118,38262,430],{"class":124},[118,38264,9552],{"class":433},[118,38266,430],{"class":124},[118,38268,199],{"class":124},[118,38270,38271,38273,38275,38277,38279],{"class":120,"line":269},[118,38272,1408],{"class":142},[118,38274,1391],{"class":226},[118,38276,1413],{"class":124},[118,38278,1416],{"class":124},[118,38280,220],{"class":124},[118,38282,38283,38285,38287,38289,38291,38293],{"class":120,"line":306},[118,38284,5818],{"class":226},[118,38286,25],{"class":124},[118,38288,5823],{"class":213},[118,38290,255],{"class":124},[118,38292,1429],{"class":226},[118,38294,199],{"class":124},[118,38296,38297],{"class":120,"line":312},[118,38298,1375],{"class":124},[118,38300,38301],{"class":120,"line":317},[118,38302,136],{"emptyLinePlaceholder":135},[118,38304,38305,38307,38309,38311,38313,38315],{"class":120,"line":350},[118,38306,227],{"class":226},[118,38308,230],{"class":124},[118,38310,3595],{"class":226},[118,38312,25],{"class":124},[118,38314,3600],{"class":213},[118,38316,241],{"class":124},[118,38318,38319,38321,38323,38325,38327,38329,38331,38333],{"class":120,"line":379},[118,38320,3607],{"class":226},[118,38322,25],{"class":124},[118,38324,252],{"class":213},[118,38326,255],{"class":124},[118,38328,1587],{"class":226},[118,38330,25],{"class":124},[118,38332,263],{"class":226},[118,38334,266],{"class":124},[118,38336,38337,38339,38341,38343,38345,38347,38349,38351,38353,38355,38357,38359,38361,38363],{"class":120,"line":417},[118,38338,3607],{"class":226},[118,38340,25],{"class":124},[118,38342,276],{"class":213},[118,38344,255],{"class":124},[118,38346,258],{"class":226},[118,38348,25],{"class":124},[118,38350,285],{"class":213},[118,38352,255],{"class":124},[118,38354,258],{"class":226},[118,38356,25],{"class":124},[118,38358,294],{"class":213},[118,38360,255],{"class":124},[118,38362,300],{"class":299},[118,38364,303],{"class":124},[118,38366,38367,38369,38371,38373,38375,38377,38379,38381,38383,38385],{"class":120,"line":466},[118,38368,3607],{"class":226},[118,38370,25],{"class":124},[118,38372,2798],{"class":213},[118,38374,255],{"class":124},[118,38376,430],{"class":124},[118,38378,2805],{"class":433},[118,38380,430],{"class":124},[118,38382,395],{"class":124},[118,38384,35744],{"class":226},[118,38386,266],{"class":124},[118,38388,38389,38391,38393,38395,38397,38399,38401,38403,38405,38407],{"class":120,"line":472},[118,38390,3607],{"class":226},[118,38392,25],{"class":124},[118,38394,12501],{"class":213},[118,38396,255],{"class":124},[118,38398,430],{"class":124},[118,38400,2805],{"class":433},[118,38402,430],{"class":124},[118,38404,395],{"class":124},[118,38406,32153],{"class":299},[118,38408,266],{"class":124},[118,38410,38411],{"class":120,"line":503},[118,38412,309],{"class":124},[118,38414,38415],{"class":120,"line":537},[118,38416,136],{"emptyLinePlaceholder":135},[118,38418,38419,38421,38423,38425,38427,38429],{"class":120,"line":566},[118,38420,5431],{"class":226},[118,38422,230],{"class":124},[118,38424,1185],{"class":226},[118,38426,25],{"class":124},[118,38428,1190],{"class":213},[118,38430,1193],{"class":124},[118,38432,38433,38435,38437,38439,38441,38443,38445,38447,38449,38451,38453],{"class":120,"line":571},[118,38434,5494],{"class":226},[118,38436,25],{"class":124},[118,38438,358],{"class":213},[118,38440,328],{"class":124},[118,38442,363],{"class":331},[118,38444,334],{"class":124},[118,38446,337],{"class":128},[118,38448,25],{"class":124},[118,38450,372],{"class":128},[118,38452,345],{"class":124},[118,38454,220],{"class":124},[118,38456,38457,38459,38461,38463,38465,38467,38469,38471,38473,38475,38477,38479,38481,38483],{"class":120,"line":577},[118,38458,1737],{"class":226},[118,38460,25],{"class":124},[118,38462,387],{"class":213},[118,38464,255],{"class":124},[118,38466,20],{"class":299},[118,38468,395],{"class":124},[118,38470,398],{"class":124},[118,38472,401],{"class":331},[118,38474,334],{"class":124},[118,38476,337],{"class":128},[118,38478,25],{"class":124},[118,38480,410],{"class":128},[118,38482,345],{"class":124},[118,38484,220],{"class":124},[118,38486,38487,38489,38491,38493,38495,38497,38499,38501],{"class":120,"line":602},[118,38488,1768],{"class":226},[118,38490,25],{"class":124},[118,38492,425],{"class":213},[118,38494,255],{"class":124},[118,38496,430],{"class":124},[118,38498,17618],{"class":433},[118,38500,430],{"class":124},[118,38502,199],{"class":124},[118,38504,38505],{"class":120,"line":633},[118,38506,574],{"class":124},[118,38508,38509],{"class":120,"line":668},[118,38510,706],{"class":124},[118,38512,38513],{"class":120,"line":693},[118,38514,136],{"emptyLinePlaceholder":135},[118,38516,38517,38519,38521,38523,38525,38527,38529,38531],{"class":120,"line":698},[118,38518,5787],{"class":226},[118,38520,395],{"class":124},[118,38522,1391],{"class":226},[118,38524,230],{"class":124},[118,38526,1185],{"class":226},[118,38528,25],{"class":124},[118,38530,1400],{"class":213},[118,38532,1193],{"class":124},[118,38534,38535,38537,38539,38541,38543],{"class":120,"line":703},[118,38536,1408],{"class":142},[118,38538,1391],{"class":226},[118,38540,1413],{"class":124},[118,38542,1416],{"class":124},[118,38544,220],{"class":124},[118,38546,38547,38549,38551,38553,38555,38557],{"class":120,"line":709},[118,38548,5818],{"class":226},[118,38550,25],{"class":124},[118,38552,5823],{"class":213},[118,38554,255],{"class":124},[118,38556,1429],{"class":226},[118,38558,199],{"class":124},[118,38560,38561],{"class":120,"line":714},[118,38562,1375],{"class":124},[118,38564,38565,38567,38569,38571,38573,38575,38577,38579,38581,38583,38585,38587,38589,38591,38593,38595,38597,38599,38601],{"class":120,"line":740},[118,38566,1408],{"class":142},[118,38568,1391],{"class":226},[118,38570,230],{"class":124},[118,38572,1447],{"class":226},[118,38574,25],{"class":124},[118,38576,1452],{"class":213},[118,38578,255],{"class":124},[118,38580,430],{"class":124},[118,38582,13805],{"class":433},[118,38584,430],{"class":124},[118,38586,395],{"class":124},[118,38588,5859],{"class":226},[118,38590,395],{"class":124},[118,38592,1471],{"class":299},[118,38594,7902],{"class":124},[118,38596,1391],{"class":226},[118,38598,1413],{"class":124},[118,38600,1416],{"class":124},[118,38602,220],{"class":124},[118,38604,38605,38607,38609,38611,38613,38615],{"class":120,"line":765},[118,38606,5818],{"class":226},[118,38608,25],{"class":124},[118,38610,5823],{"class":213},[118,38612,255],{"class":124},[118,38614,1429],{"class":226},[118,38616,199],{"class":124},[118,38618,38619],{"class":120,"line":796},[118,38620,1375],{"class":124},[118,38622,38623],{"class":120,"line":819},[118,38624,1479],{"class":124},[14,38626,38627,38628,38630,38631,38634,38635,38637],{},"Two lines register and default the font. No CGO. No ",[18,38629,2745],{}," bookkeeping. If you were seeing ",[18,38632,38633],{},"□□□□□、□□。"," before and run this program with a real ",[18,38636,9552],{}," next to it, you get real glyphs.",[14,38639,38640,38641,20777,38643,25],{},"Grab ",[18,38642,9552],{},[3163,38644,38646],{"href":36478,"rel":38645},[3167],"Google Fonts",[41,38648,38650],{"id":38649},"how-to-tell-which-cause-you-are-hitting","How to tell which cause you are hitting",[14,38652,38653],{},"Most of this is staring at three places: where you build the document, where you write the text, and the TTF file itself.",[14,38655,38656,38662,38663,38665,38666,38669,38670,38672],{},[1629,38657,38658,38659],{},"If your output is ",[18,38660,38661],{},"□□□"," (identical rectangles), it is cause 1, 2, or 3. The PDF embedded ",[4744,38664,3163],{}," font but it has no glyphs for those codepoints. Open the PDF in Acrobat, go to ",[18,38667,38668],{},"File → Properties → Fonts",", and look at which fonts were actually embedded. If the list is only Helvetica / Times / Courier, cause 1. If ",[18,38671,2805],{}," is listed and rectangles are still there, cause 2 or 3.",[14,38674,38675,38682,38683,38685,38686,38689],{},[1629,38676,38658,38677,12386,38679],{},[18,38678,38151],{},[18,38680,38681],{},"ã\"ã‚\"ã«ã¡ã¯"," (garbled Latin-ish characters), it is cause 4. Your Japanese string was re-encoded somewhere before gpdf got it. Most common culprit: a CSV saved as Shift-JIS by Excel and read with ",[18,38684,17912],{}," as if it were UTF-8, or an HTTP endpoint that didn't declare ",[18,38687,38688],{},"charset=utf-8",". Fix the decoder, not the PDF.",[14,38691,38692,38695],{},[1629,38693,38694],{},"Mixed output"," — some characters render, some are boxes — means the font has partial coverage. A font labelled \"Japanese\" might include hiragana and katakana but skip uncommon kanji like 鬱 or 龠. Switch to Noto Sans JP (covers JIS X 0213) or Source Han Sans JP if you hit this.",[41,38697,38699],{"id":38698},"cause-2-in-detail-right-font-wrong-family-name","Cause 2 in detail: right font, wrong family name",[14,38701,38702,38703,38706],{},"This one is sneaky because the font ",[4744,38704,38705],{},"is"," embedded — it just isn't used. A minimal repro:",[109,38708,38710],{"className":111,"code":38709,"language":113,"meta":114,"style":114},"doc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", font),\n    // No WithDefaultFont.\n)\n\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Text(\"こんにちは\") // Uses the default font: Helvetica.\n    })\n})\n",[18,38711,38712,38726,38748,38753,38757,38761,38785,38815,38837,38841],{"__ignoreMap":114},[118,38713,38714,38716,38718,38720,38722,38724],{"class":120,"line":121},[118,38715,2760],{"class":226},[118,38717,230],{"class":124},[118,38719,3595],{"class":226},[118,38721,25],{"class":124},[118,38723,3600],{"class":213},[118,38725,241],{"class":124},[118,38727,38728,38730,38732,38734,38736,38738,38740,38742,38744,38746],{"class":120,"line":132},[118,38729,4532],{"class":226},[118,38731,25],{"class":124},[118,38733,2798],{"class":213},[118,38735,255],{"class":124},[118,38737,430],{"class":124},[118,38739,2805],{"class":433},[118,38741,430],{"class":124},[118,38743,395],{"class":124},[118,38745,35744],{"class":226},[118,38747,266],{"class":124},[118,38749,38750],{"class":120,"line":139},[118,38751,38752],{"class":3981},"    // No WithDefaultFont.\n",[118,38754,38755],{"class":120,"line":149},[118,38756,199],{"class":124},[118,38758,38759],{"class":120,"line":161},[118,38760,136],{"emptyLinePlaceholder":135},[118,38762,38763,38765,38767,38769,38771,38773,38775,38777,38779,38781,38783],{"class":120,"line":166},[118,38764,5910],{"class":226},[118,38766,25],{"class":124},[118,38768,358],{"class":213},[118,38770,328],{"class":124},[118,38772,363],{"class":331},[118,38774,334],{"class":124},[118,38776,337],{"class":128},[118,38778,25],{"class":124},[118,38780,372],{"class":128},[118,38782,345],{"class":124},[118,38784,220],{"class":124},[118,38786,38787,38789,38791,38793,38795,38797,38799,38801,38803,38805,38807,38809,38811,38813],{"class":120,"line":176},[118,38788,5935],{"class":226},[118,38790,25],{"class":124},[118,38792,387],{"class":213},[118,38794,255],{"class":124},[118,38796,20],{"class":299},[118,38798,395],{"class":124},[118,38800,398],{"class":124},[118,38802,401],{"class":331},[118,38804,334],{"class":124},[118,38806,337],{"class":128},[118,38808,25],{"class":124},[118,38810,410],{"class":128},[118,38812,345],{"class":124},[118,38814,220],{"class":124},[118,38816,38817,38819,38821,38823,38825,38827,38830,38832,38834],{"class":120,"line":186},[118,38818,5966],{"class":226},[118,38820,25],{"class":124},[118,38822,425],{"class":213},[118,38824,255],{"class":124},[118,38826,430],{"class":124},[118,38828,38829],{"class":433},"こんにちは",[118,38831,430],{"class":124},[118,38833,345],{"class":124},[118,38835,38836],{"class":3981}," // Uses the default font: Helvetica.\n",[118,38838,38839],{"class":120,"line":196},[118,38840,706],{"class":124},[118,38842,38843],{"class":120,"line":202},[118,38844,1944],{"class":124},[14,38846,38847,38848,1649,38851,38853,38854,38857,38858,38860,38861,38863,38864,38866,38867,54,38869,38872],{},"Fix: add ",[18,38849,38850],{},"gpdf.WithDefaultFont(\"NotoSansJP\", 12)",[18,38852,3600],{},", or pass ",[18,38855,38856],{},"template.FontFamily(\"NotoSansJP\")"," on every ",[18,38859,8551],{}," that needs Japanese. The family name in ",[18,38862,2798],{}," and the one on ",[18,38865,8551],{}," must match exactly, including case. ",[18,38868,2805],{},[18,38870,38871],{},"notosansjp"," are two different fonts to gpdf.",[41,38874,38876],{"id":38875},"cause-3-in-detail-the-wrong-ttf-file","Cause 3 in detail: the wrong TTF file",[14,38878,38879,54,38881,38883],{},[18,38880,38138],{},[18,38882,9552],{}," are two different files. The first is a Latin font with zero CJK coverage. The second is the Japanese cut, around 17,000 glyphs. They look almost identical in a directory listing. Autocomplete will happily hand you the wrong one.",[14,38885,38886],{},"gpdf does not validate glyph coverage at registration. If you hand it bytes, it trusts you. The failure shows up as tofu at render time.",[14,38888,38889],{},"Quickest way to check:",[46,38891,38892,38899,38906],{},[49,38893,38894,38895,38898],{},"macOS: ",[18,38896,38897],{},"Font Book → double-click the file → preview"," shows a glyph grid",[49,38900,38901,38902,38905],{},"Linux: ",[18,38903,38904],{},"otfinfo -u NotoSans-Regular.ttf"," dumps the Unicode coverage",[49,38907,38908,38909,8450,38914,38917],{},"Cross-platform: ",[3163,38910,38913],{"href":38911,"rel":38912},"https://github.com/fonttools/fonttools",[3167],"fontTools",[18,38915,38916],{},"ttx -t cmap NotoSans-Regular.ttf"," dumps the cmap table as XML",[14,38919,38920],{},"If U+3042 (あ) is not in the list, you have the Latin subset.",[41,38922,38924],{"id":38923},"cause-4-in-detail-encoding-corruption","Cause 4 in detail: encoding corruption",[14,38926,38927,38928,38930],{},"This one does not actually involve gpdf. The string handed to ",[18,38929,8551],{}," already had the wrong bytes. Print it before rendering:",[109,38932,38934],{"className":111,"code":38933,"language":113,"meta":114,"style":114},"text := loadLabelFromSomewhere()\nfmt.Printf(\"%q\\n\", text) // Shows actual runes\nc.Text(text)\n",[18,38935,38936,38948,38979],{"__ignoreMap":114},[118,38937,38938,38941,38943,38946],{"class":120,"line":121},[118,38939,38940],{"class":226},"text ",[118,38942,230],{"class":124},[118,38944,38945],{"class":213}," loadLabelFromSomewhere",[118,38947,1193],{"class":124},[118,38949,38950,38952,38954,38957,38959,38961,38964,38967,38969,38971,38974,38976],{"class":120,"line":132},[118,38951,7108],{"class":226},[118,38953,25],{"class":124},[118,38955,38956],{"class":213},"Printf",[118,38958,255],{"class":124},[118,38960,430],{"class":124},[118,38962,38963],{"class":7426},"%q",[118,38965,38966],{"class":226},"\\n",[118,38968,430],{"class":124},[118,38970,395],{"class":124},[118,38972,38973],{"class":226}," text",[118,38975,345],{"class":124},[118,38977,38978],{"class":3981}," // Shows actual runes\n",[118,38980,38981,38983,38985,38987,38989,38991],{"class":120,"line":139},[118,38982,401],{"class":226},[118,38984,25],{"class":124},[118,38986,425],{"class":213},[118,38988,255],{"class":124},[118,38990,4993],{"class":226},[118,38992,199],{"class":124},[14,38994,35226,38995,38998,38999,39002,39003,39006],{},[18,38996,38997],{},"fmt.Printf(\"%q\\n\", text)"," prints ",[18,39000,39001],{},"\"縺ゅ→縺\""," instead of ",[18,39004,39005],{},"\"あいうえ\"",", the corruption happened upstream. gpdf cannot fix it — find the place where UTF-8 was mis-decoded.",[14,39008,39009],{},"Common upstream culprits:",[46,39011,39012,39020,39034],{},[49,39013,39014,39015,39017,39018],{},"Reading a CSV exported from Excel (Windows Shift-JIS) with ",[18,39016,17912],{}," and casting straight to ",[18,39019,1131],{},[49,39021,39022,39023,12386,39026,39029,39030,39033],{},"A database column declared ",[18,39024,39025],{},"latin1",[18,39027,39028],{},"utf8mb3"," (not ",[18,39031,39032],{},"utf8mb4",") already storing mojibake",[49,39035,39036,39037,39040],{},"An HTTP response missing ",[18,39038,39039],{},"Content-Type: application/json; charset=utf-8",", and a client that guessed Latin-1",[41,39042,39044],{"id":39043},"one-edge-case-worth-naming","One edge case worth naming",[14,39046,39047,39048,39050,39051,39054,39055,39058,39059,39061],{},"gpdf silently subsets. If you register NotoSansJP at document construction, render ",[18,39049,38829],{},", and then later in the same document render ",[18,39052,39053],{},"鬱陶しい",", the second render will work — gpdf adds the new glyphs to the subset on the fly. But if you generate a PDF, hand it to a viewer, and ",[4744,39056,39057],{},"then"," try to edit the PDF in Acrobat and type a new kanji that wasn't in the original text, you will get tofu there. The subset is frozen at ",[18,39060,21327],{}," time. Re-run your program; don't patch the PDF.",[41,39063,6894],{"id":6893},[46,39065,39066,39073,39080],{},[49,39067,39068,32986,39070,39072],{},[3163,39069,9792],{"href":9791},[18,39071,2798],{}," walkthrough with bold/italic variants and multi-CJK documents",[49,39074,39075,39077,39078,33006],{},[3163,39076,33001],{"href":33000}," — which Noto file to pick and how ",[18,39079,33005],{},[49,39081,39082,39085],{},[3163,39083,39084],{"href":4327},"The definitive guide to Japanese PDFs in Go (2026)"," — long-form guide covering fonts, vertical text, ruby, and JP-specific layout",[41,39087,4794],{"id":4793},[14,39089,6933],{},[109,39091,39092],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,39093,39094],{"__ignoreMap":114},[118,39095,39096,39098,39100],{"class":120,"line":121},[118,39097,113],{"class":128},[118,39099,3156],{"class":433},[118,39101,3159],{"class":433},[14,39103,39104,3169,39107],{},[3163,39105,3168],{"href":3165,"rel":39106},[3167],[3163,39108,3174],{"href":3172,"rel":39109},[3167],[3176,39111,39112],{},"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":114,"searchDepth":132,"depth":132,"links":39114},[39115,39116,39117,39118,39119,39120,39121,39122,39123,39124],{"id":4878,"depth":132,"text":4879},{"id":8545,"depth":132,"text":8546},{"id":38155,"depth":132,"text":38156},{"id":38649,"depth":132,"text":38650},{"id":38698,"depth":132,"text":38699},{"id":38875,"depth":132,"text":38876},{"id":38923,"depth":132,"text":38924},{"id":39043,"depth":132,"text":39044},{"id":6893,"depth":132,"text":6894},{"id":4793,"depth":132,"text":4794},"2026-04-17","Empty rectangles instead of Japanese characters mean your PDF couldn't find glyphs for those codepoints. Here are the four causes and the fixes.",{"name":39128,"totalTime":3200,"tools":39129,"steps":39131},"Diagnose and fix tofu boxes in a gpdf document",[3202,39130],"A CJK-capable TTF such as NotoSansJP-Regular.ttf",[39132,39135,39138,39141,39144],{"name":39133,"text":39134},"Confirm the symptom is tofu, not mojibake","Open the PDF. Japanese characters as empty rectangles (□) mean a font lookup miss. Garbled Latin like 縺ゅ→縺 means UTF-8 was decoded wrong upstream of gpdf.",{"name":39136,"text":39137},"Check that a CJK-capable font is registered","Search your document construction for gpdf.WithFont. With no CJK TTF registered, gpdf falls back to the Base-14 PDF fonts, none of which cover CJK codepoints.",{"name":39139,"text":39140},"Verify the font family on each c.Text call","If WithDefaultFont is not set, every c.Text that renders Japanese needs template.FontFamily(\"NotoSansJP\") explicitly. A mismatched family silently falls back to the default.",{"name":39142,"text":39143},"Confirm the TTF file actually contains CJK glyphs","A file called NotoSans-Regular.ttf (Latin subset) looks right at a glance but has no CJK table. gpdf does not validate coverage at registration time.",{"name":39145,"text":39146},"Regenerate and verify in two viewers","Open the PDF in Adobe Acrobat and in Chrome. Both should render Japanese. If one works and one does not, glyphs are embedded but not subset-registered for that codepoint.",{},{"title":32994,"description":39126},"blog/008.tofu-boxes-japanese",[6996,39151,9860],"troubleshooting","V6WDPupq4c4UqeCWQJG5_ECrHbPU_zHdXQYO8iYXMvc",{"id":39154,"title":36508,"author":39155,"body":39156,"date":39125,"description":40349,"draft":3196,"extension":3197,"howTo":40350,"image":3220,"meta":40368,"navigation":135,"path":36507,"seo":40369,"stem":40370,"tags":40371,"updated":3220,"__hash__":40372},"blog/blog/009.ipaex-gothic-gpdf.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":39157,"toc":40338},[39158,39160,39173,39175,39188,39190,39717,39735,39739,39742,39798,39801,39807,39811,39814,39817,39825,39857,39863,40144,40147,40151,40160,40184,40190,40193,40197,40280,40283,40285,40312,40314,40316,40328,40336],[41,39159,4879],{"id":4878},[14,39161,39162,39163,39168,39169,39172],{},"You want to use IPAex Gothic — the proportional Gothic font the Japanese ",[3163,39164,39167],{"href":39165,"rel":39166},"https://moji.or.jp/ipafont/",[3167],"Information-technology Promotion Agency"," (IPA) maintains — in a ",[3163,39170,1587],{"href":3165,"rel":39171},[3167]," document. The typical reasons: e-Tax PDF submissions, government-facing paperwork, or just a house style that's been on IPAex since the early 2010s. Three things trip people up: which file to grab, how to deal with there being no Bold, and what the IPA Font License actually asks of you.",[41,39174,44],{"id":43},[14,39176,39177,39178,2527,39181,39184,39185,39187],{},"Register ",[18,39179,39180],{},"ipaexg.ttf",[18,39182,39183],{},"gpdf.WithFont(\"IPAexGothic\", bytes)",". Set it as the default. Bold emphasis has to be synthesized with ",[18,39186,21701],{}," or paired with IPAex Mincho since IPAex ships only Regular. Keep the license text next to the binary.",[41,39189,35516],{"id":35515},[109,39191,39193],{"className":111,"code":39192,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"ipaexg.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(25))),\n        gpdf.WithFont(\"IPAexGothic\", font),\n        gpdf.WithDefaultFont(\"IPAexGothic\", 10.5),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"請求書\", template.FontSize(24), template.Bold())\n            c.Text(\"令和8年4月17日発行\")\n            c.Text(\"金額: ¥100,000 (税込)\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,39194,39195,39201,39205,39211,39219,39227,39231,39239,39247,39255,39259,39263,39273,39299,39311,39325,39329,39333,39347,39365,39396,39419,39442,39446,39450,39464,39488,39518,39557,39576,39595,39599,39603,39607,39625,39637,39651,39655,39695,39709,39713],{"__ignoreMap":114},[118,39196,39197,39199],{"class":120,"line":121},[118,39198,125],{"class":124},[118,39200,129],{"class":128},[118,39202,39203],{"class":120,"line":132},[118,39204,136],{"emptyLinePlaceholder":135},[118,39206,39207,39209],{"class":120,"line":139},[118,39208,143],{"class":142},[118,39210,146],{"class":124},[118,39212,39213,39215,39217],{"class":120,"line":149},[118,39214,152],{"class":124},[118,39216,5303],{"class":128},[118,39218,158],{"class":124},[118,39220,39221,39223,39225],{"class":120,"line":161},[118,39222,152],{"class":124},[118,39224,155],{"class":128},[118,39226,158],{"class":124},[118,39228,39229],{"class":120,"line":166},[118,39230,136],{"emptyLinePlaceholder":135},[118,39232,39233,39235,39237],{"class":120,"line":176},[118,39234,152],{"class":124},[118,39236,3203],{"class":128},[118,39238,158],{"class":124},[118,39240,39241,39243,39245],{"class":120,"line":186},[118,39242,152],{"class":124},[118,39244,171],{"class":128},[118,39246,158],{"class":124},[118,39248,39249,39251,39253],{"class":120,"line":196},[118,39250,152],{"class":124},[118,39252,191],{"class":128},[118,39254,158],{"class":124},[118,39256,39257],{"class":120,"line":202},[118,39258,199],{"class":124},[118,39260,39261],{"class":120,"line":207},[118,39262,136],{"emptyLinePlaceholder":135},[118,39264,39265,39267,39269,39271],{"class":120,"line":223},[118,39266,210],{"class":124},[118,39268,214],{"class":213},[118,39270,217],{"class":124},[118,39272,220],{"class":124},[118,39274,39275,39277,39279,39281,39283,39285,39287,39289,39291,39293,39295,39297],{"class":120,"line":244},[118,39276,35604],{"class":226},[118,39278,395],{"class":124},[118,39280,1391],{"class":226},[118,39282,230],{"class":124},[118,39284,1447],{"class":226},[118,39286,25],{"class":124},[118,39288,9545],{"class":213},[118,39290,255],{"class":124},[118,39292,430],{"class":124},[118,39294,39180],{"class":433},[118,39296,430],{"class":124},[118,39298,199],{"class":124},[118,39300,39301,39303,39305,39307,39309],{"class":120,"line":269},[118,39302,1408],{"class":142},[118,39304,1391],{"class":226},[118,39306,1413],{"class":124},[118,39308,1416],{"class":124},[118,39310,220],{"class":124},[118,39312,39313,39315,39317,39319,39321,39323],{"class":120,"line":306},[118,39314,5818],{"class":226},[118,39316,25],{"class":124},[118,39318,5823],{"class":213},[118,39320,255],{"class":124},[118,39322,1429],{"class":226},[118,39324,199],{"class":124},[118,39326,39327],{"class":120,"line":312},[118,39328,1375],{"class":124},[118,39330,39331],{"class":120,"line":317},[118,39332,136],{"emptyLinePlaceholder":135},[118,39334,39335,39337,39339,39341,39343,39345],{"class":120,"line":350},[118,39336,227],{"class":226},[118,39338,230],{"class":124},[118,39340,3595],{"class":226},[118,39342,25],{"class":124},[118,39344,3600],{"class":213},[118,39346,241],{"class":124},[118,39348,39349,39351,39353,39355,39357,39359,39361,39363],{"class":120,"line":379},[118,39350,3607],{"class":226},[118,39352,25],{"class":124},[118,39354,252],{"class":213},[118,39356,255],{"class":124},[118,39358,1587],{"class":226},[118,39360,25],{"class":124},[118,39362,263],{"class":226},[118,39364,266],{"class":124},[118,39366,39367,39369,39371,39373,39375,39377,39379,39381,39383,39385,39387,39389,39391,39394],{"class":120,"line":417},[118,39368,3607],{"class":226},[118,39370,25],{"class":124},[118,39372,276],{"class":213},[118,39374,255],{"class":124},[118,39376,258],{"class":226},[118,39378,25],{"class":124},[118,39380,285],{"class":213},[118,39382,255],{"class":124},[118,39384,258],{"class":226},[118,39386,25],{"class":124},[118,39388,294],{"class":213},[118,39390,255],{"class":124},[118,39392,39393],{"class":299},"25",[118,39395,303],{"class":124},[118,39397,39398,39400,39402,39404,39406,39408,39411,39413,39415,39417],{"class":120,"line":466},[118,39399,3607],{"class":226},[118,39401,25],{"class":124},[118,39403,2798],{"class":213},[118,39405,255],{"class":124},[118,39407,430],{"class":124},[118,39409,39410],{"class":433},"IPAexGothic",[118,39412,430],{"class":124},[118,39414,395],{"class":124},[118,39416,35744],{"class":226},[118,39418,266],{"class":124},[118,39420,39421,39423,39425,39427,39429,39431,39433,39435,39437,39440],{"class":120,"line":472},[118,39422,3607],{"class":226},[118,39424,25],{"class":124},[118,39426,12501],{"class":213},[118,39428,255],{"class":124},[118,39430,430],{"class":124},[118,39432,39410],{"class":433},[118,39434,430],{"class":124},[118,39436,395],{"class":124},[118,39438,39439],{"class":299}," 10.5",[118,39441,266],{"class":124},[118,39443,39444],{"class":120,"line":503},[118,39445,309],{"class":124},[118,39447,39448],{"class":120,"line":537},[118,39449,136],{"emptyLinePlaceholder":135},[118,39451,39452,39454,39456,39458,39460,39462],{"class":120,"line":566},[118,39453,5431],{"class":226},[118,39455,230],{"class":124},[118,39457,1185],{"class":226},[118,39459,25],{"class":124},[118,39461,1190],{"class":213},[118,39463,1193],{"class":124},[118,39465,39466,39468,39470,39472,39474,39476,39478,39480,39482,39484,39486],{"class":120,"line":571},[118,39467,5494],{"class":226},[118,39469,25],{"class":124},[118,39471,358],{"class":213},[118,39473,328],{"class":124},[118,39475,363],{"class":331},[118,39477,334],{"class":124},[118,39479,337],{"class":128},[118,39481,25],{"class":124},[118,39483,372],{"class":128},[118,39485,345],{"class":124},[118,39487,220],{"class":124},[118,39489,39490,39492,39494,39496,39498,39500,39502,39504,39506,39508,39510,39512,39514,39516],{"class":120,"line":577},[118,39491,1737],{"class":226},[118,39493,25],{"class":124},[118,39495,387],{"class":213},[118,39497,255],{"class":124},[118,39499,20],{"class":299},[118,39501,395],{"class":124},[118,39503,398],{"class":124},[118,39505,401],{"class":331},[118,39507,334],{"class":124},[118,39509,337],{"class":128},[118,39511,25],{"class":124},[118,39513,410],{"class":128},[118,39515,345],{"class":124},[118,39517,220],{"class":124},[118,39519,39520,39522,39524,39526,39528,39530,39533,39535,39537,39539,39541,39543,39545,39547,39549,39551,39553,39555],{"class":120,"line":602},[118,39521,1768],{"class":226},[118,39523,25],{"class":124},[118,39525,425],{"class":213},[118,39527,255],{"class":124},[118,39529,430],{"class":124},[118,39531,39532],{"class":433},"請求書",[118,39534,430],{"class":124},[118,39536,395],{"class":124},[118,39538,233],{"class":226},[118,39540,25],{"class":124},[118,39542,455],{"class":213},[118,39544,255],{"class":124},[118,39546,3777],{"class":299},[118,39548,1280],{"class":124},[118,39550,233],{"class":226},[118,39552,25],{"class":124},[118,39554,445],{"class":213},[118,39556,1289],{"class":124},[118,39558,39559,39561,39563,39565,39567,39569,39572,39574],{"class":120,"line":633},[118,39560,1768],{"class":226},[118,39562,25],{"class":124},[118,39564,425],{"class":213},[118,39566,255],{"class":124},[118,39568,430],{"class":124},[118,39570,39571],{"class":433},"令和8年4月17日発行",[118,39573,430],{"class":124},[118,39575,199],{"class":124},[118,39577,39578,39580,39582,39584,39586,39588,39591,39593],{"class":120,"line":668},[118,39579,1768],{"class":226},[118,39581,25],{"class":124},[118,39583,425],{"class":213},[118,39585,255],{"class":124},[118,39587,430],{"class":124},[118,39589,39590],{"class":433},"金額: ¥100,000 (税込)",[118,39592,430],{"class":124},[118,39594,199],{"class":124},[118,39596,39597],{"class":120,"line":693},[118,39598,574],{"class":124},[118,39600,39601],{"class":120,"line":698},[118,39602,706],{"class":124},[118,39604,39605],{"class":120,"line":703},[118,39606,136],{"emptyLinePlaceholder":135},[118,39608,39609,39611,39613,39615,39617,39619,39621,39623],{"class":120,"line":709},[118,39610,5787],{"class":226},[118,39612,395],{"class":124},[118,39614,1391],{"class":226},[118,39616,230],{"class":124},[118,39618,1185],{"class":226},[118,39620,25],{"class":124},[118,39622,1400],{"class":213},[118,39624,1193],{"class":124},[118,39626,39627,39629,39631,39633,39635],{"class":120,"line":714},[118,39628,1408],{"class":142},[118,39630,1391],{"class":226},[118,39632,1413],{"class":124},[118,39634,1416],{"class":124},[118,39636,220],{"class":124},[118,39638,39639,39641,39643,39645,39647,39649],{"class":120,"line":740},[118,39640,5818],{"class":226},[118,39642,25],{"class":124},[118,39644,5823],{"class":213},[118,39646,255],{"class":124},[118,39648,1429],{"class":226},[118,39650,199],{"class":124},[118,39652,39653],{"class":120,"line":765},[118,39654,1375],{"class":124},[118,39656,39657,39659,39661,39663,39665,39667,39669,39671,39673,39675,39677,39679,39681,39683,39685,39687,39689,39691,39693],{"class":120,"line":796},[118,39658,1408],{"class":142},[118,39660,1391],{"class":226},[118,39662,230],{"class":124},[118,39664,1447],{"class":226},[118,39666,25],{"class":124},[118,39668,1452],{"class":213},[118,39670,255],{"class":124},[118,39672,430],{"class":124},[118,39674,4015],{"class":433},[118,39676,430],{"class":124},[118,39678,395],{"class":124},[118,39680,5859],{"class":226},[118,39682,395],{"class":124},[118,39684,1471],{"class":299},[118,39686,7902],{"class":124},[118,39688,1391],{"class":226},[118,39690,1413],{"class":124},[118,39692,1416],{"class":124},[118,39694,220],{"class":124},[118,39696,39697,39699,39701,39703,39705,39707],{"class":120,"line":819},[118,39698,5818],{"class":226},[118,39700,25],{"class":124},[118,39702,5823],{"class":213},[118,39704,255],{"class":124},[118,39706,1429],{"class":226},[118,39708,199],{"class":124},[118,39710,39711],{"class":120,"line":851},[118,39712,1375],{"class":124},[118,39714,39715],{"class":120,"line":875},[118,39716,1479],{"class":124},[14,39718,39719,39720,20777,39723,39727,39728,39730,39731,9386,39733,25],{},"Download the ",[18,39721,39722],{},"IPAex00401.zip",[3163,39724,39726],{"href":39165,"rel":39725},[3167],"moji.or.jp/ipafont",", extract ",[18,39729,39180],{},", drop it next to ",[18,39732,102],{},[18,39734,106],{},[41,39736,39738],{"id":39737},"which-ipa-file-is-the-right-one","Which IPA file is the right one",[14,39740,39741],{},"Open the zip and you get three TTFs plus a license. People mix these up constantly:",[1516,39743,39744,39754],{},[1519,39745,39746],{},[1522,39747,39748,39751],{},[1525,39749,39750],{},"File",[1525,39752,39753],{},"What it is",[1532,39755,39756,39768,39781],{},[1522,39757,39758,39762],{},[1537,39759,39760],{},[18,39761,39180],{},[1537,39763,39764,39767],{},[1629,39765,39766],{},"IPAex Gothic"," — sans-serif, proportional Latin. Use this for most documents.",[1522,39769,39770,39775],{},[1537,39771,39772],{},[18,39773,39774],{},"ipaexm.ttf",[1537,39776,39777,39780],{},[1629,39778,39779],{},"IPAex Mincho"," — serif, proportional Latin. Use for body text in long-form documents or to pair with Gothic for emphasis.",[1522,39782,39783,39788],{},[1537,39784,39785],{},[18,39786,39787],{},"ipag.ttf",[1537,39789,39790,39793,39794,39797],{},[1629,39791,39792],{},"IPA Gothic"," (no \"ex\") — sans-serif, ",[1629,39795,39796],{},"monospace Latin",". Rarely what you want today.",[14,39799,39800],{},"The \"ex\" in IPAex stands for \"extended proportional.\" The original IPA fonts put Latin characters on fixed CJK-width grids, which made mixed J/E text look stretched. IPAex fixes that by making Latin characters proportional while keeping CJK characters on the normal grid. For any document with English loan words, URLs, or numbers — which is basically every business document in Japan — you want IPAex.",[14,39802,39803,39804,39806],{},"If you inherited a project using ",[18,39805,39787],{}," because the engineer who picked the font did it before IPAex existed (original IPA Gothic: 2003, IPAex: 2010), the switch is one file swap. Same family name, same everything else.",[41,39808,39810],{"id":39809},"no-bold-file-now-what","No Bold file — now what",[14,39812,39813],{},"IPAex ships exactly one weight per family: Regular. That's unusual compared to Noto Sans JP (nine weights) and it's the single biggest reason people look at IPAex and decide it won't work for their design.",[14,39815,39816],{},"Two ways to handle it in gpdf:",[14,39818,39819,4919,39822,39824],{},[1629,39820,39821],{},"Synthesized bold.",[18,39823,21701],{}," applies a stroke overlay on Regular glyphs. Typographically it's a cheat — real bold weights have redrawn outlines with thicker strokes, not Regular traced twice. But for invoice headings and table labels at 10 pt or larger, synthesized bold is indistinguishable to most readers:",[109,39826,39828],{"className":111,"code":39827,"language":113,"meta":114,"style":114},"c.Text(\"合計金額\", template.Bold())\n",[18,39829,39830],{"__ignoreMap":114},[118,39831,39832,39834,39836,39838,39840,39842,39845,39847,39849,39851,39853,39855],{"class":120,"line":121},[118,39833,401],{"class":226},[118,39835,25],{"class":124},[118,39837,425],{"class":213},[118,39839,255],{"class":124},[118,39841,430],{"class":124},[118,39843,39844],{"class":433},"合計金額",[118,39846,430],{"class":124},[118,39848,395],{"class":124},[118,39850,233],{"class":226},[118,39852,25],{"class":124},[118,39854,445],{"class":213},[118,39856,1289],{"class":124},[14,39858,39859,39862],{},[1629,39860,39861],{},"Pair with IPAex Mincho."," The classic Japanese typography move for emphasis isn't bold — it's a serif/sans switch. Register both families:",[109,39864,39866],{"className":111,"code":39865,"language":113,"meta":114,"style":114},"gothic,  _ := os.ReadFile(\"ipaexg.ttf\")\nmincho, _ := os.ReadFile(\"ipaexm.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"IPAexGothic\", gothic),\n    gpdf.WithFont(\"IPAexMincho\", mincho),\n    gpdf.WithDefaultFont(\"IPAexGothic\", 10.5),\n)\n\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Text(\"請求書\", template.FontFamily(\"IPAexMincho\"), template.FontSize(24))\n        c.Text(\"ご請求内容は下記の通りです。\")\n    })\n})\n",[18,39867,39868,39895,39922,39926,39940,39963,39987,40009,40013,40017,40041,40071,40117,40136,40140],{"__ignoreMap":114},[118,39869,39870,39873,39875,39877,39879,39881,39883,39885,39887,39889,39891,39893],{"class":120,"line":121},[118,39871,39872],{"class":226},"gothic",[118,39874,395],{"class":124},[118,39876,36237],{"class":226},[118,39878,230],{"class":124},[118,39880,1447],{"class":226},[118,39882,25],{"class":124},[118,39884,9545],{"class":213},[118,39886,255],{"class":124},[118,39888,430],{"class":124},[118,39890,39180],{"class":433},[118,39892,430],{"class":124},[118,39894,199],{"class":124},[118,39896,39897,39900,39902,39904,39906,39908,39910,39912,39914,39916,39918,39920],{"class":120,"line":132},[118,39898,39899],{"class":226},"mincho",[118,39901,395],{"class":124},[118,39903,3999],{"class":226},[118,39905,230],{"class":124},[118,39907,1447],{"class":226},[118,39909,25],{"class":124},[118,39911,9545],{"class":213},[118,39913,255],{"class":124},[118,39915,430],{"class":124},[118,39917,39774],{"class":433},[118,39919,430],{"class":124},[118,39921,199],{"class":124},[118,39923,39924],{"class":120,"line":139},[118,39925,136],{"emptyLinePlaceholder":135},[118,39927,39928,39930,39932,39934,39936,39938],{"class":120,"line":149},[118,39929,2760],{"class":226},[118,39931,230],{"class":124},[118,39933,3595],{"class":226},[118,39935,25],{"class":124},[118,39937,3600],{"class":213},[118,39939,241],{"class":124},[118,39941,39942,39944,39946,39948,39950,39952,39954,39956,39958,39961],{"class":120,"line":161},[118,39943,4532],{"class":226},[118,39945,25],{"class":124},[118,39947,2798],{"class":213},[118,39949,255],{"class":124},[118,39951,430],{"class":124},[118,39953,39410],{"class":433},[118,39955,430],{"class":124},[118,39957,395],{"class":124},[118,39959,39960],{"class":226}," gothic",[118,39962,266],{"class":124},[118,39964,39965,39967,39969,39971,39973,39975,39978,39980,39982,39985],{"class":120,"line":166},[118,39966,4532],{"class":226},[118,39968,25],{"class":124},[118,39970,2798],{"class":213},[118,39972,255],{"class":124},[118,39974,430],{"class":124},[118,39976,39977],{"class":433},"IPAexMincho",[118,39979,430],{"class":124},[118,39981,395],{"class":124},[118,39983,39984],{"class":226}," mincho",[118,39986,266],{"class":124},[118,39988,39989,39991,39993,39995,39997,39999,40001,40003,40005,40007],{"class":120,"line":176},[118,39990,4532],{"class":226},[118,39992,25],{"class":124},[118,39994,12501],{"class":213},[118,39996,255],{"class":124},[118,39998,430],{"class":124},[118,40000,39410],{"class":433},[118,40002,430],{"class":124},[118,40004,395],{"class":124},[118,40006,39439],{"class":299},[118,40008,266],{"class":124},[118,40010,40011],{"class":120,"line":186},[118,40012,199],{"class":124},[118,40014,40015],{"class":120,"line":196},[118,40016,136],{"emptyLinePlaceholder":135},[118,40018,40019,40021,40023,40025,40027,40029,40031,40033,40035,40037,40039],{"class":120,"line":202},[118,40020,5910],{"class":226},[118,40022,25],{"class":124},[118,40024,358],{"class":213},[118,40026,328],{"class":124},[118,40028,363],{"class":331},[118,40030,334],{"class":124},[118,40032,337],{"class":128},[118,40034,25],{"class":124},[118,40036,372],{"class":128},[118,40038,345],{"class":124},[118,40040,220],{"class":124},[118,40042,40043,40045,40047,40049,40051,40053,40055,40057,40059,40061,40063,40065,40067,40069],{"class":120,"line":207},[118,40044,5935],{"class":226},[118,40046,25],{"class":124},[118,40048,387],{"class":213},[118,40050,255],{"class":124},[118,40052,20],{"class":299},[118,40054,395],{"class":124},[118,40056,398],{"class":124},[118,40058,401],{"class":331},[118,40060,334],{"class":124},[118,40062,337],{"class":128},[118,40064,25],{"class":124},[118,40066,410],{"class":128},[118,40068,345],{"class":124},[118,40070,220],{"class":124},[118,40072,40073,40075,40077,40079,40081,40083,40085,40087,40089,40091,40093,40095,40097,40099,40101,40103,40105,40107,40109,40111,40113,40115],{"class":120,"line":223},[118,40074,5966],{"class":226},[118,40076,25],{"class":124},[118,40078,425],{"class":213},[118,40080,255],{"class":124},[118,40082,430],{"class":124},[118,40084,39532],{"class":433},[118,40086,430],{"class":124},[118,40088,395],{"class":124},[118,40090,233],{"class":226},[118,40092,25],{"class":124},[118,40094,2926],{"class":213},[118,40096,255],{"class":124},[118,40098,430],{"class":124},[118,40100,39977],{"class":433},[118,40102,430],{"class":124},[118,40104,1280],{"class":124},[118,40106,233],{"class":226},[118,40108,25],{"class":124},[118,40110,455],{"class":213},[118,40112,255],{"class":124},[118,40114,3777],{"class":299},[118,40116,463],{"class":124},[118,40118,40119,40121,40123,40125,40127,40129,40132,40134],{"class":120,"line":244},[118,40120,5966],{"class":226},[118,40122,25],{"class":124},[118,40124,425],{"class":213},[118,40126,255],{"class":124},[118,40128,430],{"class":124},[118,40130,40131],{"class":433},"ご請求内容は下記の通りです。",[118,40133,430],{"class":124},[118,40135,199],{"class":124},[118,40137,40138],{"class":120,"line":269},[118,40139,706],{"class":124},[118,40141,40142],{"class":120,"line":306},[118,40143,1944],{"class":124},[14,40145,40146],{},"That's the look you see on Japanese wedding invitations and formal reports — Mincho for titles, Gothic for body. If your document is going to a government office, this is probably the expected combination anyway.",[41,40148,40150],{"id":40149},"the-ipa-font-license-briefly","The IPA Font License, briefly",[14,40152,40153,40154,40159],{},"IPAex is not SIL OFL. It's the ",[3163,40155,40158],{"href":40156,"rel":40157},"https://opensource.org/licenses/IPA",[3167],"IPA Font License Agreement v1.0",", which is OSI-approved and mostly permissive but has two asks worth naming:",[1624,40161,40162,40175],{},[49,40163,40164,40167,40168,40170,40171,40174],{},[1629,40165,40166],{},"Preserve the license text"," wherever you ship the font binary. If you ",[18,40169,17916],{}," the TTF, also embed the license file. A project-root ",[18,40172,40173],{},"LICENSES/IPA-FONT-1.0.txt"," satisfies this for most distributions.",[49,40176,40177,40180,40181,40183],{},[1629,40178,40179],{},"Don't rename the font."," If you modify the TTF itself and redistribute it, you must give the derivative a different name (not containing \"IPA\" or \"IPAex\"). Note: this restriction does ",[1629,40182,18100],{}," apply to glyph subsetting performed at render time. Article 3.4 of the license explicitly exempts output documents created using the font from the naming restriction.",[14,40185,40186,40187,40189],{},"Meaning: gpdf's subsetting at ",[18,40188,1657],{}," is fine. The subset font embedded in your PDF doesn't need a different name and doesn't trigger the \"Derivative Font Program\" clauses. You are creating a document, not redistributing a font.",[14,40191,40192],{},"One thing to flag for contributors building gpdf itself: we avoid shipping IPAex in the gpdf repo (golden file tests use SIL OFL fonts like Noto) precisely so downstream users never have to think about license compatibility with their repo's top-level LICENSE. If you use IPAex in your application, that's your project's choice, not ours.",[41,40194,40196],{"id":40195},"when-to-pick-ipaex-over-noto-sans-jp","When to pick IPAex over Noto Sans JP",[1516,40198,40199,40210],{},[1519,40200,40201],{},[1522,40202,40203,40206,40208],{},[1525,40204,40205],{},"Dimension",[1525,40207,39766],{},[1525,40209,36064],{},[1532,40211,40212,40223,40233,40244,40258,40269],{},[1522,40213,40214,40217,40220],{},[1537,40215,40216],{},"Weights shipped",[1537,40218,40219],{},"1 (Regular)",[1537,40221,40222],{},"9 (Thin → Black)",[1522,40224,40225,40227,40230],{},[1537,40226,3286],{},[1537,40228,40229],{},"IPA Font License v1.0",[1537,40231,40232],{},"SIL OFL 1.1",[1522,40234,40235,40238,40241],{},[1537,40236,40237],{},"Latin handling",[1537,40239,40240],{},"Proportional (IPAex) or monospace (IPA)",[1537,40242,40243],{},"Proportional",[1522,40245,40246,40249,40255],{},[1537,40247,40248],{},"Pre-installed on",[1537,40250,40251,40252],{},"Some JP Linux distros, TeX Live ",[18,40253,40254],{},"ptex-fonts",[1537,40256,40257],{},"Android, ChromeOS",[1522,40259,40260,40263,40266],{},[1537,40261,40262],{},"Typical audience",[1537,40264,40265],{},"Japanese government, legal, academic",[1537,40267,40268],{},"Consumer web, international",[1522,40270,40271,40274,40277],{},[1537,40272,40273],{},"File size",[1537,40275,40276],{},"7.5 MB (Gothic)",[1537,40278,40279],{},"5 MB (Regular only)",[14,40281,40282],{},"Pick IPAex when your output crosses a Japanese institutional boundary — e-Tax PDF submissions, court filings, academic paper submissions to a Japanese journal — because graders, reviewers, and OCR tools in those ecosystems are calibrated against IPA. Pick Noto Sans JP for everything else. They render very similarly; the choice is about ecosystem fit, not aesthetics.",[41,40284,12870],{"id":12869},[46,40286,40287,40292,40297,40302],{},[49,40288,40289,40291],{},[3163,40290,9792],{"href":9791}," — the general recipe, works for any CJK TTF",[49,40293,40294,40296],{},[3163,40295,33001],{"href":33000}," — the SIL OFL alternative with nine weights",[49,40298,40299,40301],{},[3163,40300,32994],{"href":22032}," — troubleshooting when glyphs aren't showing up",[49,40303,40304,32986,40309,40311],{},[3163,40305,40308],{"href":40306,"rel":40307},"https://gpdf.dev/docs/guide/fonts",[3167],"Fonts guide",[18,40310,2798],{}," reference including variant naming",[41,40313,4794],{"id":4793},[14,40315,6933],{},[109,40317,40318],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,40319,40320],{"__ignoreMap":114},[118,40321,40322,40324,40326],{"class":120,"line":121},[118,40323,113],{"class":128},[118,40325,3156],{"class":433},[118,40327,3159],{"class":433},[14,40329,40330,3169,40333],{},[3163,40331,3168],{"href":3165,"rel":40332},[3167],[3163,40334,3174],{"href":3172,"rel":40335},[3167],[3176,40337,36541],{},{"title":114,"searchDepth":132,"depth":132,"links":40339},[40340,40341,40342,40343,40344,40345,40346,40347,40348],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":35515,"depth":132,"text":35516},{"id":39737,"depth":132,"text":39738},{"id":39809,"depth":132,"text":39810},{"id":40149,"depth":132,"text":40150},{"id":40195,"depth":132,"text":40196},{"id":12869,"depth":132,"text":12870},{"id":4793,"depth":132,"text":4794},"Register ipaexg.ttf with gpdf.WithFont. IPAex Gothic ships a single Regular weight under the IPA Font License — bold emphasis has to be synthesized or paired.",{"name":40351,"totalTime":6973,"tools":40352,"steps":40354},"Use IPAex Gothic as the default font in a gpdf document",[3202,40353],"ipaexg.ttf (IPAex Gothic v4.01 from moji.or.jp)",[40355,40358,40360,40362,40365],{"name":40356,"text":40357},"Download the IPAex font archive from moji.or.jp","Grab the IPAex00401.zip from moji.or.jp/ipafont. Unzip and keep ipaexg.ttf (Gothic) and the IPA Font License Agreement v1.0 text file that travels with it.",{"name":22082,"text":40359},"Use os.ReadFile(\"ipaexg.ttf\") to load the font into a []byte at program start. For container deployments, //go:embed keeps the font inside the Go binary.",{"name":36568,"text":40361},"Pass gpdf.WithFont(\"IPAexGothic\", fontBytes) and gpdf.WithDefaultFont(\"IPAexGothic\", 10.5) to gpdf.NewDocument. 10.5 pt matches the default size Word uses for Japanese documents.",{"name":40363,"text":40364},"Handle bold emphasis without a Bold file","IPAex Gothic has no Bold variant. Either synthesize bold with template.Bold() (gpdf applies a 0.4 pt stroke overlay) or register IPAex Mincho as a separate family for emphasis.",{"name":40366,"text":40367},"Keep the license notice with your distribution","The IPA Font License v1.0 requires preserving the license text wherever the font binary ships. If you //go:embed the TTF, embed the license alongside it and reference it from LICENSES/NOTICE.",{},{"title":36508,"description":40349},"blog/009.ipaex-gothic-gpdf",[6996,9860,3226],"fZkVfns8BXQz3n-N5ZBoss3xE0Kppv8jV3Npfms1DS4",{"id":40374,"title":6902,"author":40375,"body":40376,"date":40852,"description":41641,"draft":3196,"extension":3197,"howTo":41642,"image":3220,"meta":41661,"navigation":135,"path":6901,"seo":41662,"stem":41663,"tags":41664,"updated":3220,"__hash__":41665},"blog/blog/005.12-column-grid.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":40377,"toc":41629},[40378,40380,40389,40393,40404,40408,41316,41321,41323,41326,41330,41344,41354,41358,41361,41375,41388,41392,41398,41404,41556,41560,41566,41576,41579,41582,41584,41602,41604,41606,41618,41626],[41,40379,4879],{"id":4878},[14,40381,40382,40383,3096,40385,40388],{},"You've seen the gpdf API — page builder, row builder, column builder — and the column constructor takes a number: ",[18,40384,28161],{},[18,40386,40387],{},"r.Col(8, fn)",". What's the number, what happens if the spans don't add up to 12, and how does this compare to the grid you already know from CSS?",[41,40390,40392],{"id":40391},"the-short-version","The short version",[14,40394,40395,40397,40398,40400,40401],{},[18,40396,4905],{}," takes an integer from 1 to 12. That integer is the column's share of the row — ",[18,40399,6592],{}," of the available width. Spans under 1 are clamped to 1, spans over 12 are clamped to 12, and the library does not force spans to sum to 12 per row. ",[1629,40402,40403],{},"The grid is fixed at 12 divisions. Everything else is you choosing how to carve up a row.",[41,40405,40407],{"id":40406},"a-working-example","A working example",[109,40409,40411],{"className":111,"code":40410,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(15))),\n    )\n\n    page := doc.AddPage()\n\n    // Full width\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Invoice #2026-0416\", template.FontSize(18), template.Bold())\n        })\n    })\n\n    // 2-column header (6 + 6)\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"Billed to\")\n            c.Text(\"Acme GmbH\")\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"Issue date\")\n            c.Text(\"2026-04-16\")\n        })\n    })\n\n    // 3-column summary (4 + 4 + 4)\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"Subtotal\")\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"Tax\")\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"Total\")\n        })\n    })\n\n    // Asymmetric (8 + 4) — article area + side panel\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(8, func(c *template.ColBuilder) {\n            c.Text(\"Line items appear here.\")\n        })\n        r.Col(4, func(c *template.ColBuilder) {\n            c.Text(\"Notes\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"layout.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,40412,40413,40419,40423,40429,40437,40445,40449,40457,40465,40473,40477,40481,40491,40505,40523,40553,40557,40561,40575,40579,40584,40608,40638,40677,40681,40685,40689,40694,40718,40748,40767,40785,40789,40819,40838,40857,40861,40865,40869,40874,40898,40928,40946,40950,40980,40999,41003,41033,41051,41055,41059,41063,41068,41092,41122,41141,41145,41175,41193,41197,41201,41205,41223,41235,41249,41253,41294,41308,41312],{"__ignoreMap":114},[118,40414,40415,40417],{"class":120,"line":121},[118,40416,125],{"class":124},[118,40418,129],{"class":128},[118,40420,40421],{"class":120,"line":132},[118,40422,136],{"emptyLinePlaceholder":135},[118,40424,40425,40427],{"class":120,"line":139},[118,40426,143],{"class":142},[118,40428,146],{"class":124},[118,40430,40431,40433,40435],{"class":120,"line":149},[118,40432,152],{"class":124},[118,40434,5303],{"class":128},[118,40436,158],{"class":124},[118,40438,40439,40441,40443],{"class":120,"line":161},[118,40440,152],{"class":124},[118,40442,155],{"class":128},[118,40444,158],{"class":124},[118,40446,40447],{"class":120,"line":166},[118,40448,136],{"emptyLinePlaceholder":135},[118,40450,40451,40453,40455],{"class":120,"line":176},[118,40452,152],{"class":124},[118,40454,3203],{"class":128},[118,40456,158],{"class":124},[118,40458,40459,40461,40463],{"class":120,"line":186},[118,40460,152],{"class":124},[118,40462,171],{"class":128},[118,40464,158],{"class":124},[118,40466,40467,40469,40471],{"class":120,"line":196},[118,40468,152],{"class":124},[118,40470,191],{"class":128},[118,40472,158],{"class":124},[118,40474,40475],{"class":120,"line":202},[118,40476,199],{"class":124},[118,40478,40479],{"class":120,"line":207},[118,40480,136],{"emptyLinePlaceholder":135},[118,40482,40483,40485,40487,40489],{"class":120,"line":223},[118,40484,210],{"class":124},[118,40486,214],{"class":213},[118,40488,217],{"class":124},[118,40490,220],{"class":124},[118,40492,40493,40495,40497,40499,40501,40503],{"class":120,"line":244},[118,40494,227],{"class":226},[118,40496,230],{"class":124},[118,40498,3595],{"class":226},[118,40500,25],{"class":124},[118,40502,3600],{"class":213},[118,40504,241],{"class":124},[118,40506,40507,40509,40511,40513,40515,40517,40519,40521],{"class":120,"line":269},[118,40508,3607],{"class":226},[118,40510,25],{"class":124},[118,40512,252],{"class":213},[118,40514,255],{"class":124},[118,40516,258],{"class":226},[118,40518,25],{"class":124},[118,40520,263],{"class":226},[118,40522,266],{"class":124},[118,40524,40525,40527,40529,40531,40533,40535,40537,40539,40541,40543,40545,40547,40549,40551],{"class":120,"line":306},[118,40526,3607],{"class":226},[118,40528,25],{"class":124},[118,40530,276],{"class":213},[118,40532,255],{"class":124},[118,40534,258],{"class":226},[118,40536,25],{"class":124},[118,40538,285],{"class":213},[118,40540,255],{"class":124},[118,40542,258],{"class":226},[118,40544,25],{"class":124},[118,40546,294],{"class":213},[118,40548,255],{"class":124},[118,40550,5420],{"class":299},[118,40552,303],{"class":124},[118,40554,40555],{"class":120,"line":312},[118,40556,309],{"class":124},[118,40558,40559],{"class":120,"line":317},[118,40560,136],{"emptyLinePlaceholder":135},[118,40562,40563,40565,40567,40569,40571,40573],{"class":120,"line":350},[118,40564,5431],{"class":226},[118,40566,230],{"class":124},[118,40568,1185],{"class":226},[118,40570,25],{"class":124},[118,40572,1190],{"class":213},[118,40574,1193],{"class":124},[118,40576,40577],{"class":120,"line":379},[118,40578,136],{"emptyLinePlaceholder":135},[118,40580,40581],{"class":120,"line":417},[118,40582,40583],{"class":3981},"    // Full width\n",[118,40585,40586,40588,40590,40592,40594,40596,40598,40600,40602,40604,40606],{"class":120,"line":466},[118,40587,5494],{"class":226},[118,40589,25],{"class":124},[118,40591,358],{"class":213},[118,40593,328],{"class":124},[118,40595,363],{"class":331},[118,40597,334],{"class":124},[118,40599,337],{"class":128},[118,40601,25],{"class":124},[118,40603,372],{"class":128},[118,40605,345],{"class":124},[118,40607,220],{"class":124},[118,40609,40610,40612,40614,40616,40618,40620,40622,40624,40626,40628,40630,40632,40634,40636],{"class":120,"line":472},[118,40611,1737],{"class":226},[118,40613,25],{"class":124},[118,40615,387],{"class":213},[118,40617,255],{"class":124},[118,40619,20],{"class":299},[118,40621,395],{"class":124},[118,40623,398],{"class":124},[118,40625,401],{"class":331},[118,40627,334],{"class":124},[118,40629,337],{"class":128},[118,40631,25],{"class":124},[118,40633,410],{"class":128},[118,40635,345],{"class":124},[118,40637,220],{"class":124},[118,40639,40640,40642,40644,40646,40648,40650,40653,40655,40657,40659,40661,40663,40665,40667,40669,40671,40673,40675],{"class":120,"line":503},[118,40641,1768],{"class":226},[118,40643,25],{"class":124},[118,40645,425],{"class":213},[118,40647,255],{"class":124},[118,40649,430],{"class":124},[118,40651,40652],{"class":433},"Invoice #2026-0416",[118,40654,430],{"class":124},[118,40656,395],{"class":124},[118,40658,233],{"class":226},[118,40660,25],{"class":124},[118,40662,455],{"class":213},[118,40664,255],{"class":124},[118,40666,1277],{"class":299},[118,40668,1280],{"class":124},[118,40670,233],{"class":226},[118,40672,25],{"class":124},[118,40674,445],{"class":213},[118,40676,1289],{"class":124},[118,40678,40679],{"class":120,"line":537},[118,40680,574],{"class":124},[118,40682,40683],{"class":120,"line":566},[118,40684,706],{"class":124},[118,40686,40687],{"class":120,"line":571},[118,40688,136],{"emptyLinePlaceholder":135},[118,40690,40691],{"class":120,"line":577},[118,40692,40693],{"class":3981},"    // 2-column header (6 + 6)\n",[118,40695,40696,40698,40700,40702,40704,40706,40708,40710,40712,40714,40716],{"class":120,"line":602},[118,40697,5494],{"class":226},[118,40699,25],{"class":124},[118,40701,358],{"class":213},[118,40703,328],{"class":124},[118,40705,363],{"class":331},[118,40707,334],{"class":124},[118,40709,337],{"class":128},[118,40711,25],{"class":124},[118,40713,372],{"class":128},[118,40715,345],{"class":124},[118,40717,220],{"class":124},[118,40719,40720,40722,40724,40726,40728,40730,40732,40734,40736,40738,40740,40742,40744,40746],{"class":120,"line":633},[118,40721,1737],{"class":226},[118,40723,25],{"class":124},[118,40725,387],{"class":213},[118,40727,255],{"class":124},[118,40729,392],{"class":299},[118,40731,395],{"class":124},[118,40733,398],{"class":124},[118,40735,401],{"class":331},[118,40737,334],{"class":124},[118,40739,337],{"class":128},[118,40741,25],{"class":124},[118,40743,410],{"class":128},[118,40745,345],{"class":124},[118,40747,220],{"class":124},[118,40749,40750,40752,40754,40756,40758,40760,40763,40765],{"class":120,"line":668},[118,40751,1768],{"class":226},[118,40753,25],{"class":124},[118,40755,425],{"class":213},[118,40757,255],{"class":124},[118,40759,430],{"class":124},[118,40761,40762],{"class":433},"Billed to",[118,40764,430],{"class":124},[118,40766,199],{"class":124},[118,40768,40769,40771,40773,40775,40777,40779,40781,40783],{"class":120,"line":693},[118,40770,1768],{"class":226},[118,40772,25],{"class":124},[118,40774,425],{"class":213},[118,40776,255],{"class":124},[118,40778,430],{"class":124},[118,40780,6028],{"class":433},[118,40782,430],{"class":124},[118,40784,199],{"class":124},[118,40786,40787],{"class":120,"line":698},[118,40788,574],{"class":124},[118,40790,40791,40793,40795,40797,40799,40801,40803,40805,40807,40809,40811,40813,40815,40817],{"class":120,"line":703},[118,40792,1737],{"class":226},[118,40794,25],{"class":124},[118,40796,387],{"class":213},[118,40798,255],{"class":124},[118,40800,392],{"class":299},[118,40802,395],{"class":124},[118,40804,398],{"class":124},[118,40806,401],{"class":331},[118,40808,334],{"class":124},[118,40810,337],{"class":128},[118,40812,25],{"class":124},[118,40814,410],{"class":128},[118,40816,345],{"class":124},[118,40818,220],{"class":124},[118,40820,40821,40823,40825,40827,40829,40831,40834,40836],{"class":120,"line":709},[118,40822,1768],{"class":226},[118,40824,25],{"class":124},[118,40826,425],{"class":213},[118,40828,255],{"class":124},[118,40830,430],{"class":124},[118,40832,40833],{"class":433},"Issue date",[118,40835,430],{"class":124},[118,40837,199],{"class":124},[118,40839,40840,40842,40844,40846,40848,40850,40853,40855],{"class":120,"line":714},[118,40841,1768],{"class":226},[118,40843,25],{"class":124},[118,40845,425],{"class":213},[118,40847,255],{"class":124},[118,40849,430],{"class":124},[118,40851,40852],{"class":433},"2026-04-16",[118,40854,430],{"class":124},[118,40856,199],{"class":124},[118,40858,40859],{"class":120,"line":740},[118,40860,574],{"class":124},[118,40862,40863],{"class":120,"line":765},[118,40864,706],{"class":124},[118,40866,40867],{"class":120,"line":796},[118,40868,136],{"emptyLinePlaceholder":135},[118,40870,40871],{"class":120,"line":819},[118,40872,40873],{"class":3981},"    // 3-column summary (4 + 4 + 4)\n",[118,40875,40876,40878,40880,40882,40884,40886,40888,40890,40892,40894,40896],{"class":120,"line":851},[118,40877,5494],{"class":226},[118,40879,25],{"class":124},[118,40881,358],{"class":213},[118,40883,328],{"class":124},[118,40885,363],{"class":331},[118,40887,334],{"class":124},[118,40889,337],{"class":128},[118,40891,25],{"class":124},[118,40893,372],{"class":128},[118,40895,345],{"class":124},[118,40897,220],{"class":124},[118,40899,40900,40902,40904,40906,40908,40910,40912,40914,40916,40918,40920,40922,40924,40926],{"class":120,"line":875},[118,40901,1737],{"class":226},[118,40903,25],{"class":124},[118,40905,387],{"class":213},[118,40907,255],{"class":124},[118,40909,1493],{"class":299},[118,40911,395],{"class":124},[118,40913,398],{"class":124},[118,40915,401],{"class":331},[118,40917,334],{"class":124},[118,40919,337],{"class":128},[118,40921,25],{"class":124},[118,40923,410],{"class":128},[118,40925,345],{"class":124},[118,40927,220],{"class":124},[118,40929,40930,40932,40934,40936,40938,40940,40942,40944],{"class":120,"line":880},[118,40931,1768],{"class":226},[118,40933,25],{"class":124},[118,40935,425],{"class":213},[118,40937,255],{"class":124},[118,40939,430],{"class":124},[118,40941,28797],{"class":433},[118,40943,430],{"class":124},[118,40945,199],{"class":124},[118,40947,40948],{"class":120,"line":885},[118,40949,574],{"class":124},[118,40951,40952,40954,40956,40958,40960,40962,40964,40966,40968,40970,40972,40974,40976,40978],{"class":120,"line":910},[118,40953,1737],{"class":226},[118,40955,25],{"class":124},[118,40957,387],{"class":213},[118,40959,255],{"class":124},[118,40961,1493],{"class":299},[118,40963,395],{"class":124},[118,40965,398],{"class":124},[118,40967,401],{"class":331},[118,40969,334],{"class":124},[118,40971,337],{"class":128},[118,40973,25],{"class":124},[118,40975,410],{"class":128},[118,40977,345],{"class":124},[118,40979,220],{"class":124},[118,40981,40982,40984,40986,40988,40990,40992,40995,40997],{"class":120,"line":941},[118,40983,1768],{"class":226},[118,40985,25],{"class":124},[118,40987,425],{"class":213},[118,40989,255],{"class":124},[118,40991,430],{"class":124},[118,40993,40994],{"class":433},"Tax",[118,40996,430],{"class":124},[118,40998,199],{"class":124},[118,41000,41001],{"class":120,"line":974},[118,41002,574],{"class":124},[118,41004,41005,41007,41009,41011,41013,41015,41017,41019,41021,41023,41025,41027,41029,41031],{"class":120,"line":997},[118,41006,1737],{"class":226},[118,41008,25],{"class":124},[118,41010,387],{"class":213},[118,41012,255],{"class":124},[118,41014,1493],{"class":299},[118,41016,395],{"class":124},[118,41018,398],{"class":124},[118,41020,401],{"class":331},[118,41022,334],{"class":124},[118,41024,337],{"class":128},[118,41026,25],{"class":124},[118,41028,410],{"class":128},[118,41030,345],{"class":124},[118,41032,220],{"class":124},[118,41034,41035,41037,41039,41041,41043,41045,41047,41049],{"class":120,"line":1002},[118,41036,1768],{"class":226},[118,41038,25],{"class":124},[118,41040,425],{"class":213},[118,41042,255],{"class":124},[118,41044,430],{"class":124},[118,41046,11576],{"class":433},[118,41048,430],{"class":124},[118,41050,199],{"class":124},[118,41052,41053],{"class":120,"line":1033},[118,41054,574],{"class":124},[118,41056,41057],{"class":120,"line":1065},[118,41058,706],{"class":124},[118,41060,41061],{"class":120,"line":1088},[118,41062,136],{"emptyLinePlaceholder":135},[118,41064,41065],{"class":120,"line":1093},[118,41066,41067],{"class":3981},"    // Asymmetric (8 + 4) — article area + side panel\n",[118,41069,41070,41072,41074,41076,41078,41080,41082,41084,41086,41088,41090],{"class":120,"line":1098},[118,41071,5494],{"class":226},[118,41073,25],{"class":124},[118,41075,358],{"class":213},[118,41077,328],{"class":124},[118,41079,363],{"class":331},[118,41081,334],{"class":124},[118,41083,337],{"class":128},[118,41085,25],{"class":124},[118,41087,372],{"class":128},[118,41089,345],{"class":124},[118,41091,220],{"class":124},[118,41093,41094,41096,41098,41100,41102,41104,41106,41108,41110,41112,41114,41116,41118,41120],{"class":120,"line":1103},[118,41095,1737],{"class":226},[118,41097,25],{"class":124},[118,41099,387],{"class":213},[118,41101,255],{"class":124},[118,41103,969],{"class":299},[118,41105,395],{"class":124},[118,41107,398],{"class":124},[118,41109,401],{"class":331},[118,41111,334],{"class":124},[118,41113,337],{"class":128},[118,41115,25],{"class":124},[118,41117,410],{"class":128},[118,41119,345],{"class":124},[118,41121,220],{"class":124},[118,41123,41124,41126,41128,41130,41132,41134,41137,41139],{"class":120,"line":1108},[118,41125,1768],{"class":226},[118,41127,25],{"class":124},[118,41129,425],{"class":213},[118,41131,255],{"class":124},[118,41133,430],{"class":124},[118,41135,41136],{"class":433},"Line items appear here.",[118,41138,430],{"class":124},[118,41140,199],{"class":124},[118,41142,41143],{"class":120,"line":1177},[118,41144,574],{"class":124},[118,41146,41147,41149,41151,41153,41155,41157,41159,41161,41163,41165,41167,41169,41171,41173],{"class":120,"line":1196},[118,41148,1737],{"class":226},[118,41150,25],{"class":124},[118,41152,387],{"class":213},[118,41154,255],{"class":124},[118,41156,1493],{"class":299},[118,41158,395],{"class":124},[118,41160,398],{"class":124},[118,41162,401],{"class":331},[118,41164,334],{"class":124},[118,41166,337],{"class":128},[118,41168,25],{"class":124},[118,41170,410],{"class":128},[118,41172,345],{"class":124},[118,41174,220],{"class":124},[118,41176,41177,41179,41181,41183,41185,41187,41189,41191],{"class":120,"line":1222},[118,41178,1768],{"class":226},[118,41180,25],{"class":124},[118,41182,425],{"class":213},[118,41184,255],{"class":124},[118,41186,430],{"class":124},[118,41188,12264],{"class":433},[118,41190,430],{"class":124},[118,41192,199],{"class":124},[118,41194,41195],{"class":120,"line":1253},[118,41196,574],{"class":124},[118,41198,41199],{"class":120,"line":1292},[118,41200,706],{"class":124},[118,41202,41203],{"class":120,"line":1316},[118,41204,136],{"emptyLinePlaceholder":135},[118,41206,41207,41209,41211,41213,41215,41217,41219,41221],{"class":120,"line":1350},[118,41208,5787],{"class":226},[118,41210,395],{"class":124},[118,41212,1391],{"class":226},[118,41214,230],{"class":124},[118,41216,1185],{"class":226},[118,41218,25],{"class":124},[118,41220,1400],{"class":213},[118,41222,1193],{"class":124},[118,41224,41225,41227,41229,41231,41233],{"class":120,"line":1355},[118,41226,1408],{"class":142},[118,41228,1391],{"class":226},[118,41230,1413],{"class":124},[118,41232,1416],{"class":124},[118,41234,220],{"class":124},[118,41236,41237,41239,41241,41243,41245,41247],{"class":120,"line":1360},[118,41238,5818],{"class":226},[118,41240,25],{"class":124},[118,41242,5823],{"class":213},[118,41244,255],{"class":124},[118,41246,1429],{"class":226},[118,41248,199],{"class":124},[118,41250,41251],{"class":120,"line":1372},[118,41252,1375],{"class":124},[118,41254,41255,41257,41259,41261,41263,41265,41267,41269,41271,41274,41276,41278,41280,41282,41284,41286,41288,41290,41292],{"class":120,"line":1378},[118,41256,1408],{"class":142},[118,41258,1391],{"class":226},[118,41260,230],{"class":124},[118,41262,1447],{"class":226},[118,41264,25],{"class":124},[118,41266,1452],{"class":213},[118,41268,255],{"class":124},[118,41270,430],{"class":124},[118,41272,41273],{"class":433},"layout.pdf",[118,41275,430],{"class":124},[118,41277,395],{"class":124},[118,41279,5859],{"class":226},[118,41281,395],{"class":124},[118,41283,1471],{"class":299},[118,41285,7902],{"class":124},[118,41287,1391],{"class":226},[118,41289,1413],{"class":124},[118,41291,1416],{"class":124},[118,41293,220],{"class":124},[118,41295,41296,41298,41300,41302,41304,41306],{"class":120,"line":1383},[118,41297,5818],{"class":226},[118,41299,25],{"class":124},[118,41301,5823],{"class":213},[118,41303,255],{"class":124},[118,41305,1429],{"class":226},[118,41307,199],{"class":124},[118,41309,41310],{"class":120,"line":1405},[118,41311,1375],{"class":124},[118,41313,41314],{"class":120,"line":1421},[118,41315,1479],{"class":124},[14,41317,8609,41318,41320],{},[18,41319,106],{},". You get one page with four rows, each divided differently.",[41,41322,27517],{"id":27516},[14,41324,41325],{},"Twelve factors cleanly into 2, 3, 4, and 6. That covers almost every real layout: halves (6+6), thirds (4+4+4), quarters (3+3+3+3), a sidebar + body (3+9 or 4+8), and a body + rail (8+4). Pick a grid with a smaller factor count and you lose one of those cheaply. Bootstrap settled on twelve back in 2011, and by now \"12-column grid\" is the lingua franca that every designer and frontend engineer already speaks. gpdf borrows the idiom on purpose — a PDF layout isn't a different mental model than a web layout, even though the rendering target happens to be fixed-width paper.",[41,41327,41329],{"id":41328},"the-math-spelled-out","The math, spelled out",[14,41331,41332,41333,41336,41337,41339,41340,41343],{},"With A4 portrait and 15 mm uniform margins, the usable width is 180 mm. A ",[18,41334,41335],{},"Col(4)"," inside a row takes 4/12 of that — 60 mm. ",[18,41338,6661],{}," takes 120 mm. There is no gutter between columns by default. If you want breathing room, either add a ",[18,41341,41342],{},"c.Spacer"," inside the shorter column or leave a one-unit span empty.",[14,41345,41346,41347,41349,41350,41353],{},"The width is computed as a percentage at build time (the relevant line is in ",[18,41348,4962],{},"), and the layout engine resolves it to points using the current page width minus margins. That means the same ",[18,41351,41352],{},"r.Col(6, fn)"," means different physical widths on A4 vs Letter, but the same proportion of the row.",[41,41355,41357],{"id":41356},"sums-that-dont-hit-12","Sums that don't hit 12",[14,41359,41360],{},"gpdf does not validate that your spans add up. This is deliberate.",[46,41362,41363,41369],{},[49,41364,41365,41368],{},[1629,41366,41367],{},"Sum \u003C 12",": the right side of the row is blank. Useful when you want a column anchored to the left edge and the rest of the line to stay empty.",[49,41370,41371,41374],{},[1629,41372,41373],{},"Sum > 12",": the last column overflows past the right margin. Usually a bug. The generated PDF looks wrong; nothing crashes.",[14,41376,41377,41378,41381,41382,41384,41385,41387],{},"Most layouts land on exactly 12 per row because that's what fills the page. But when you want a 6-width block centered on a line, the cheap move is ",[18,41379,41380],{},"Col(3)"," empty, ",[18,41383,6658],{}," content, ",[18,41386,41380],{}," empty — the grid was designed for this kind of shorthand.",[41,41389,41391],{"id":41390},"autorow-vs-row","AutoRow vs Row",[14,41393,41394,41397],{},[18,41395,41396],{},"page.AutoRow(fn)"," grows vertically to fit the tallest column. Most rows should use this.",[14,41399,41400,41403],{},[18,41401,41402],{},"page.Row(height, fn)"," pins the height. Content past that height gets clipped. Use it for invoice headers that must stay exactly 30 mm tall so downstream stapling lines up, and for anything else where visual consistency beats content freedom.",[109,41405,41407],{"className":111,"code":41406,"language":113,"meta":114,"style":114},"page.Row(document.Mm(30), func(r *template.RowBuilder) {\n    r.Col(8, func(c *template.ColBuilder) {\n        c.Text(\"Logo\")\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"Invoice #\")\n    })\n})\n",[18,41408,41409,41447,41477,41496,41500,41530,41548,41552],{"__ignoreMap":114},[118,41410,41411,41413,41415,41417,41419,41421,41423,41425,41427,41429,41431,41433,41435,41437,41439,41441,41443,41445],{"class":120,"line":121},[118,41412,5910],{"class":226},[118,41414,25],{"class":124},[118,41416,3687],{"class":213},[118,41418,255],{"class":124},[118,41420,258],{"class":226},[118,41422,25],{"class":124},[118,41424,294],{"class":213},[118,41426,255],{"class":124},[118,41428,18610],{"class":299},[118,41430,1280],{"class":124},[118,41432,398],{"class":124},[118,41434,363],{"class":331},[118,41436,334],{"class":124},[118,41438,337],{"class":128},[118,41440,25],{"class":124},[118,41442,372],{"class":128},[118,41444,345],{"class":124},[118,41446,220],{"class":124},[118,41448,41449,41451,41453,41455,41457,41459,41461,41463,41465,41467,41469,41471,41473,41475],{"class":120,"line":132},[118,41450,5935],{"class":226},[118,41452,25],{"class":124},[118,41454,387],{"class":213},[118,41456,255],{"class":124},[118,41458,969],{"class":299},[118,41460,395],{"class":124},[118,41462,398],{"class":124},[118,41464,401],{"class":331},[118,41466,334],{"class":124},[118,41468,337],{"class":128},[118,41470,25],{"class":124},[118,41472,410],{"class":128},[118,41474,345],{"class":124},[118,41476,220],{"class":124},[118,41478,41479,41481,41483,41485,41487,41489,41492,41494],{"class":120,"line":139},[118,41480,5966],{"class":226},[118,41482,25],{"class":124},[118,41484,425],{"class":213},[118,41486,255],{"class":124},[118,41488,430],{"class":124},[118,41490,41491],{"class":433},"Logo",[118,41493,430],{"class":124},[118,41495,199],{"class":124},[118,41497,41498],{"class":120,"line":149},[118,41499,706],{"class":124},[118,41501,41502,41504,41506,41508,41510,41512,41514,41516,41518,41520,41522,41524,41526,41528],{"class":120,"line":161},[118,41503,5935],{"class":226},[118,41505,25],{"class":124},[118,41507,387],{"class":213},[118,41509,255],{"class":124},[118,41511,1493],{"class":299},[118,41513,395],{"class":124},[118,41515,398],{"class":124},[118,41517,401],{"class":331},[118,41519,334],{"class":124},[118,41521,337],{"class":128},[118,41523,25],{"class":124},[118,41525,410],{"class":128},[118,41527,345],{"class":124},[118,41529,220],{"class":124},[118,41531,41532,41534,41536,41538,41540,41542,41544,41546],{"class":120,"line":166},[118,41533,5966],{"class":226},[118,41535,25],{"class":124},[118,41537,425],{"class":213},[118,41539,255],{"class":124},[118,41541,430],{"class":124},[118,41543,7302],{"class":433},[118,41545,430],{"class":124},[118,41547,199],{"class":124},[118,41549,41550],{"class":120,"line":176},[118,41551,706],{"class":124},[118,41553,41554],{"class":120,"line":186},[118,41555,1944],{"class":124},[41,41557,41559],{"id":41558},"what-the-grid-does-not-do","What the grid does not do",[14,41561,41562,41563,41565],{},"No nesting. You can't put a sub-row inside a column — ",[18,41564,410],{}," accepts content (Text, Image, Table, List, Spacer), not another row. Layouts that need that pattern are usually better expressed as two sibling rows at the page level.",[14,41567,41568,41569,41572,41573,41575],{},"No offset columns. Bootstrap has ",[18,41570,41571],{},".offset-2","; gpdf does not. Leave an empty ",[18,41574,6912],{}," to push content right.",[14,41577,41578],{},"No breakpoints. PDF pages don't resize. The grid produces the same layout on every device because the output is a raster of fixed coordinates, not a DOM that re-flows.",[14,41580,41581],{},"These omissions are the point. Every feature the grid doesn't have is a class of ambiguity the PDF output doesn't have to reason about.",[41,41583,12870],{"id":12869},[46,41585,41586,41591,41596],{},[49,41587,41588,41590],{},[3163,41589,9792],{"href":9791}," — CJK inside grid columns, same API",[49,41592,41593,41595],{},[3163,41594,4790],{"href":3381}," — how the Builder API compares to gofpdf, gopdf, and Maroto",[49,41597,41598,41601],{},[3163,41599,6927],{"href":6925,"rel":41600},[3167]," — full reference for rows, columns, and spacing",[41,41603,4794],{"id":4793},[14,41605,6933],{},[109,41607,41608],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,41609,41610],{"__ignoreMap":114},[118,41611,41612,41614,41616],{"class":120,"line":121},[118,41613,113],{"class":128},[118,41615,3156],{"class":433},[118,41617,3159],{"class":433},[14,41619,41620,3169,41623],{},[3163,41621,3168],{"href":3165,"rel":41622},[3167],[3163,41624,3174],{"href":3172,"rel":41625},[3167],[3176,41627,41628],{},"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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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 .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":114,"searchDepth":132,"depth":132,"links":41630},[41631,41632,41633,41634,41635,41636,41637,41638,41639,41640],{"id":4878,"depth":132,"text":4879},{"id":40391,"depth":132,"text":40392},{"id":40406,"depth":132,"text":40407},{"id":27516,"depth":132,"text":27517},{"id":41328,"depth":132,"text":41329},{"id":41356,"depth":132,"text":41357},{"id":41390,"depth":132,"text":41391},{"id":41558,"depth":132,"text":41559},{"id":12869,"depth":132,"text":12870},{"id":4793,"depth":132,"text":4794},"gpdf's 12-column grid uses r.Col(span, fn) where span is 1–12. Column width is (span/12) of the row. No breakpoints, no gutters, predictable by design.",{"name":41643,"totalTime":6973,"tools":41644,"steps":41645},"Lay out a page with gpdf's 12-column grid",[3202,3203],[41646,41649,41652,41655,41658],{"name":41647,"text":41648},"Open a row on the page","Call page.AutoRow(fn) for a row whose height grows to fit the tallest column, or page.Row(height, fn) when you want to pin the height.",{"name":41650,"text":41651},"Declare columns with r.Col(span, fn)","Inside the row, call r.Col(span, fn) once per column. span is an integer from 1 to 12 — the fraction of the row width this column takes.",{"name":41653,"text":41654},"Keep the spans per row summing to 12 or less","If the spans sum to less than 12 the remainder is left empty. If they sum to more than 12 the last column overflows the row — usually not what you want.",{"name":41656,"text":41657},"Fill the column with content","Inside the ColBuilder callback, call c.Text, c.Image, c.Table, or c.Spacer. Columns stack content vertically in the order you add it.",{"name":41659,"text":41660},"Start the next row","Call page.AutoRow again for the next visual row. Rows are independent — a 4+8 row can sit directly above a 3+3+3+3 row.",{},{"title":6902,"description":41641},"blog/005.12-column-grid",[6996,3226,6997],"GukJTMTbyAS4HLC-TWk6QK2Mlobm6-IaHafDd8Ql5Ac",{"id":41667,"title":41668,"author":41669,"body":41670,"date":40852,"description":43350,"draft":3196,"extension":3197,"howTo":3220,"image":3220,"meta":43351,"navigation":135,"path":43352,"seo":43353,"stem":43354,"tags":43355,"updated":3220,"__hash__":43356},"blog/blog/006.go-pdf-fpdf-archived.md","go-pdf/fpdf is archived too. Here's the modern Go PDF stack.",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":41671,"toc":43334},[41672,41674,41698,41702,41720,41723,41735,41745,41749,41752,41785,41788,41792,41798,41813,41820,41825,41831,41835,41838,42008,42011,42022,42033,42039,42045,42048,42052,42058,42061,42098,42101,42280,42293,42297,42313,42316,42321,42629,42633,43011,43027,43036,43040,43046,43122,43129,43135,43138,43145,43151,43199,43202,43206,43213,43219,43225,43227,43238,43244,43250,43266,43274,43280,43282,43284,43296,43304,43306,43331],[41,41673,44],{"id":43},[14,41675,41676,41677,41680,41681,41683,41684,41687,41688,41690,41691,41694,41695,41697],{},"Both maintained forks of the ",[18,41678,41679],{},"fpdf"," lineage are now read-only. ",[18,41682,35345],{}," was archived in ",[1629,41685,41686],{},"September 2021","; the community fork ",[18,41689,1557],{}," followed in ",[1629,41692,41693],{},"2025",". There is no \"next maintainer\" coming. For new Go projects, ",[1629,41696,1587],{}," is the modern default: pure Go, zero external dependencies, native CJK, 10–30× faster on common workloads. This is a map of the 2026 landscape and an honest account of when gpdf is the right pick and when it isn't.",[41,41699,41701],{"id":41700},"the-situation","The situation",[14,41703,41704,41705,41708,41709,41712,41713,41716,41717,41719],{},"A teammate typed ",[18,41706,41707],{},"go get github.com/go-pdf/fpdf"," last week and paused at the GitHub banner: ",[4744,41710,41711],{},"\"This repository has been archived by the owner. It is now read-only.\""," This was supposed to be the ",[4744,41714,41715],{},"fixed"," version — the community fork of ",[18,41718,35345],{}," that was meant to keep the lineage alive after the original was archived in 2021.",[14,41721,41722],{},"It's archived too. The README now recommends looking elsewhere.",[14,41724,41725,41726,41728,41729,41731,41732,41734],{},"If you've been building Go services that emit PDFs — invoices, reports, shipping labels, compliance documents — the library at the bottom of your ",[18,41727,27266],{}," has almost certainly been one of these two for the past five years. Stack Overflow answers point to ",[18,41730,35345],{},". Newer tutorials point to ",[18,41733,1557],{},". Both are now supply-chain liabilities: no CVE triage, no Go version compatibility work, no performance fixes, no spec updates.",[14,41736,41737,41738,41741,41742],{},"This post isn't another \"here's how to migrate line-by-line\" guide — ",[3163,41739,41740],{"href":4839},"we already wrote that one",". This is the longer version of the question the migration guide doesn't answer: ",[1629,41743,41744],{},"what actually works for Go PDF generation in 2026, and how did the ecosystem end up here?",[41,41746,41748],{"id":41747},"what-archived-costs-you-in-practice","What \"archived\" costs you in practice",[14,41750,41751],{},"The word \"archived\" on GitHub is deceptively soft. In practice, for a library in your import graph, it means four concrete things:",[1624,41753,41754,41760,41770,41776],{},[49,41755,41756,41759],{},[1629,41757,41758],{},"No security patches."," If a memory-safety issue lands in the TTF parser, nobody is merging the fix upstream. You can fork it yourself. Most teams won't.",[49,41761,41762,41765,41766,41769],{},[1629,41763,41764],{},"No Go toolchain forward-compat."," Go 1.25's loop variable semantics work fine with gofpdf today. Something about ",[18,41767,41768],{},"for range"," over an integer, or a future std-lib deprecation, can break the build tomorrow. You'll be the one patching a read-only repo's fork.",[49,41771,41772,41775],{},[1629,41773,41774],{},"No spec updates."," PDF 2.0 (ISO 32000-2) landed in 2020. gofpdf implements most of PDF 1.7. Things like page-level associated files, rich XMP metadata, and modern digital signatures (PAdES-B-LT) are either absent or wired in by third-party glue.",[49,41777,41778,41781,41782,41784],{},[1629,41779,41780],{},"No CJK progress."," gofpdf's Unicode path was retrofitted onto a single-byte-font design. It works but embeds full fonts instead of subsets in most real configurations, and glyph-id collisions in certain CJK TTFs produce corrupted output. ",[18,41783,1557],{}," inherited the same architecture.",[14,41786,41787],{},"Security and forward-compat are the ones that bite teams in compliance conversations. \"Our PDF library is archived and gets no CVE patches\" is not an answer your auditor wants to hear.",[41,41789,41791],{"id":41790},"why-both-forks-died","Why both forks died",[14,41793,41794,41795],{},"It's tempting to explain the archives as maintainer burnout — a single person tired of reviewing PRs, a bus factor of one going off-line. That's part of it, but it isn't the whole story. ",[1629,41796,41797],{},"The architecture made it hard to keep up.",[14,41799,41800,41802,41803,3096,41806,3096,41809,41812],{},[18,41801,35345],{}," was a port of FPDF, a PHP library from 2002. The PHP original drove a cursor around the page and emitted content procedurally: ",[18,41804,41805],{},"SetXY(x, y)",[18,41807,41808],{},"Cell(w, h, text)",[18,41810,41811],{},"Ln(h)",". That model was a reasonable fit for 2002-era PHP, where the alternative was raw PostScript or proprietary toolkits. Ported to Go, it kept the cursor, kept the single-byte font tables, kept the manual page-break bookkeeping.",[14,41814,41815,41816,41819],{},"Each year, the gap between what people wanted to generate and what the cursor model could express grew. Invoices are tables. Reports are grids with repeating chrome. Shipping labels are QR codes plus locale-specific text. The cursor kept getting wrapped in helpers, and the helpers kept getting wrapped in tutorials, and by 2023 most of the code people wrote ",[4744,41817,41818],{},"against"," gofpdf wasn't gofpdf at all — it was a per-team glue layer that tried to pretend the cursor was a layout engine.",[14,41821,41822,41824],{},[18,41823,1557],{}," inherited this. The fork refactored the internals and fixed longstanding bugs, but it couldn't change the public API without breaking every downstream project. The shape of the library was frozen in 2002-era PHP, and the cost of keeping that shape alive grew faster than the benefit.",[14,41826,41827,41830],{},[1629,41828,41829],{},"So:"," two maintainers, two archives, one architectural reason. Rebuilding in 2026 means picking an approach that matches how PDFs actually get produced today — which looks a lot more like building a web page than driving a plotter.",[41,41832,41834],{"id":41833},"the-2026-go-pdf-landscape","The 2026 Go PDF landscape",[14,41836,41837],{},"Before recommending anything, here's the field. We'll use \"maintained\" loosely to mean \"a commit in the last 6 months and responsive issues.\"",[1516,41839,41840,41859],{},[1519,41841,41842],{},[1522,41843,41844,41846,41849,41851,41854,41857],{},[1525,41845,1527],{},[1525,41847,41848],{},"Status (2026-04)",[1525,41850,3286],{},[1525,41852,41853],{},"Native CJK",[1525,41855,41856],{},"Zero deps",[1525,41858,12264],{},[1532,41860,41861,41882,41902,41921,41943,41963,41986],{},[1522,41862,41863,41867,41872,41874,41877,41879],{},[1537,41864,41865],{},[18,41866,35345],{},[1537,41868,41869],{},[1629,41870,41871],{},"Archived 2021",[1537,41873,3375],{},[1537,41875,41876],{},"Retrofit",[1537,41878,28102],{},[1537,41880,41881],{},"The original. Still the top search result in most locales.",[1522,41883,41884,41888,41893,41895,41897,41899],{},[1537,41885,41886],{},[18,41887,1557],{},[1537,41889,41890],{},[1629,41891,41892],{},"Archived 2025",[1537,41894,3375],{},[1537,41896,41876],{},[1537,41898,28102],{},[1537,41900,41901],{},"Community fork of the above. Same architecture, same ceiling.",[1522,41903,41904,41908,41911,41913,41916,41918],{},[1537,41905,41906],{},[18,41907,1565],{},[1537,41909,41910],{},"Maintained",[1537,41912,3375],{},[1537,41914,41915],{},"Partial",[1537,41917,28102],{},[1537,41919,41920],{},"Low-level. You write coordinates. Good for form overlays.",[1522,41922,41923,41928,41930,41932,41935,41937],{},[1537,41924,41925,41927],{},[18,41926,35352],{}," v2",[1537,41929,41910],{},[1537,41931,3375],{},[1537,41933,41934],{},"Via gofpdf",[1537,41936,28099],{},[1537,41938,41939,41940,41942],{},"Grid-first builder, but depends on ",[18,41941,1557],{}," underneath.",[1522,41944,41945,41949,41951,41956,41958,41960],{},[1537,41946,41947],{},[18,41948,12369],{},[1537,41950,41910],{},[1537,41952,41953],{},[1629,41954,41955],{},"Commercial",[1537,41957,28102],{},[1537,41959,28099],{},[1537,41961,41962],{},"Feature-complete PDF SDK. Requires a paid license for commercial use.",[1522,41964,41965,41971,41973,41976,41978,41983],{},[1537,41966,41967,41970],{},[18,41968,41969],{},"chromedp"," + Chromium",[1537,41972,41910],{},[1537,41974,41975],{},"MIT + Chrome",[1537,41977,28102],{},[1537,41979,41980],{},[1629,41981,41982],{},"No — ships a browser",[1537,41984,41985],{},"HTML→PDF via headless Chrome. Huge runtime.",[1522,41987,41988,41992,41994,41996,42001,42005],{},[1537,41989,41990],{},[18,41991,1587],{},[1537,41993,41910],{},[1537,41995,3375],{},[1537,41997,41998],{},[1629,41999,42000],{},"Native",[1537,42002,42003],{},[1629,42004,28102],{},[1537,42006,42007],{},"Pure-Go reimplementation. Builder API, 12-column grid.",[14,42009,42010],{},"A few observations you can make from the table without running anything:",[14,42012,42013,4919,42016,42018,42019,42021],{},[1629,42014,42015],{},"Everything maintained is either commercial, carries a huge runtime, or sits on top of a soon-to-be-stale foundation.",[18,42017,1565],{}," is the exception — genuinely maintained and dependency-light — but it's a coordinate-level library. You're back to ",[18,42020,12972],{}," with a different package name.",[14,42023,42024,42027,42028,6589,42030,42032],{},[1629,42025,42026],{},"Maroto v2 is a grid-first builder with a good API."," The problem is that at the bottom of its ",[18,42029,27266],{},[18,42031,1557],{},". Every performance ceiling and CJK limitation in fpdf is also a ceiling for Maroto. A major v3 could break free of that — it isn't out yet.",[14,42034,42035,42038],{},[1629,42036,42037],{},"unipdf is feature-rich but not MIT-compatible for commercial use."," You pay per seat or per deployment. That's a fine choice if your revenue supports it; for an open-source side project or an early-stage startup, the license math doesn't work.",[14,42040,42041,42044],{},[1629,42042,42043],{},"chromedp works, but you're shipping a browser."," A 100 MB base image becomes a 1 GB+ image. Cold start on serverless is painful. Fonts still need to be installed in the container. The upside is that you can reuse your React templates; the downside is that you're running Chromium to render an invoice.",[14,42046,42047],{},"The gap is obvious: a pure-Go, zero-dep, CJK-native, grid-first library that doesn't require a commercial license or a browser runtime. That's what gpdf is.",[41,42049,42051],{"id":42050},"what-gpdf-actually-is","What gpdf actually is",[14,42053,42054,42055,42057],{},"gpdf (",[18,42056,3203],{},") is a clean reimplementation. Not a fork. The PDF wire-format writer, the layout engine, and the TrueType subsetter are all written from scratch in pure Go.",[14,42059,42060],{},"The three properties that matter for most teams:",[46,42062,42063,42075,42090],{},[49,42064,42065,4919,42068,42070,42071,42074],{},[1629,42066,42067],{},"Pure Go, no CGO.",[18,42069,21802],{}," is static. ",[18,42072,42073],{},"GOOS=linux GOARCH=arm64 go build"," works from a MacBook with no toolchain setup. Docker images stay small — a 12 MB distroless container runs it.",[49,42076,42077,4919,42080,27274,42083,42085,42086,42089],{},[1629,42078,42079],{},"Zero external dependencies.",[18,42081,42082],{},"go mod graph",[18,42084,29121],{}," shows one line: gpdf itself. The core uses only ",[18,42087,42088],{},"std",". (Optional add-ons for HTML→PDF or digital signatures bring in small dependencies, and they're opt-in.)",[49,42091,42092,4919,42095,42097],{},[1629,42093,42094],{},"Native CJK.",[18,42096,2798],{}," registers a TrueType font at document construction. Subset embedding happens at render time. A 200-character Japanese invoice carries ~30 KB of subset font data, not 5 MB of full font.",[14,42099,42100],{},"The API shape is declarative. You describe a tree of rows and columns; the layout engine places them. The grid is 12 columns — the same idiom Bootstrap has shipped since 2011. If you've written a single line of HTML/CSS, the gpdf API is familiar:",[109,42102,42104],{"className":111,"code":42103,"language":113,"meta":114,"style":114},"page := doc.AddPage()\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(8, func(c *template.ColBuilder) {\n        c.Text(\"Invoice #2026-0416\", template.FontSize(18), template.Bold())\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"2026-04-16\", template.AlignRight())\n    })\n})\n",[18,42105,42106,42120,42144,42174,42212,42216,42246,42272,42276],{"__ignoreMap":114},[118,42107,42108,42110,42112,42114,42116,42118],{"class":120,"line":121},[118,42109,17539],{"class":226},[118,42111,230],{"class":124},[118,42113,1185],{"class":226},[118,42115,25],{"class":124},[118,42117,1190],{"class":213},[118,42119,1193],{"class":124},[118,42121,42122,42124,42126,42128,42130,42132,42134,42136,42138,42140,42142],{"class":120,"line":132},[118,42123,5910],{"class":226},[118,42125,25],{"class":124},[118,42127,358],{"class":213},[118,42129,328],{"class":124},[118,42131,363],{"class":331},[118,42133,334],{"class":124},[118,42135,337],{"class":128},[118,42137,25],{"class":124},[118,42139,372],{"class":128},[118,42141,345],{"class":124},[118,42143,220],{"class":124},[118,42145,42146,42148,42150,42152,42154,42156,42158,42160,42162,42164,42166,42168,42170,42172],{"class":120,"line":139},[118,42147,5935],{"class":226},[118,42149,25],{"class":124},[118,42151,387],{"class":213},[118,42153,255],{"class":124},[118,42155,969],{"class":299},[118,42157,395],{"class":124},[118,42159,398],{"class":124},[118,42161,401],{"class":331},[118,42163,334],{"class":124},[118,42165,337],{"class":128},[118,42167,25],{"class":124},[118,42169,410],{"class":128},[118,42171,345],{"class":124},[118,42173,220],{"class":124},[118,42175,42176,42178,42180,42182,42184,42186,42188,42190,42192,42194,42196,42198,42200,42202,42204,42206,42208,42210],{"class":120,"line":149},[118,42177,5966],{"class":226},[118,42179,25],{"class":124},[118,42181,425],{"class":213},[118,42183,255],{"class":124},[118,42185,430],{"class":124},[118,42187,40652],{"class":433},[118,42189,430],{"class":124},[118,42191,395],{"class":124},[118,42193,233],{"class":226},[118,42195,25],{"class":124},[118,42197,455],{"class":213},[118,42199,255],{"class":124},[118,42201,1277],{"class":299},[118,42203,1280],{"class":124},[118,42205,233],{"class":226},[118,42207,25],{"class":124},[118,42209,445],{"class":213},[118,42211,1289],{"class":124},[118,42213,42214],{"class":120,"line":161},[118,42215,706],{"class":124},[118,42217,42218,42220,42222,42224,42226,42228,42230,42232,42234,42236,42238,42240,42242,42244],{"class":120,"line":166},[118,42219,5935],{"class":226},[118,42221,25],{"class":124},[118,42223,387],{"class":213},[118,42225,255],{"class":124},[118,42227,1493],{"class":299},[118,42229,395],{"class":124},[118,42231,398],{"class":124},[118,42233,401],{"class":331},[118,42235,334],{"class":124},[118,42237,337],{"class":128},[118,42239,25],{"class":124},[118,42241,410],{"class":128},[118,42243,345],{"class":124},[118,42245,220],{"class":124},[118,42247,42248,42250,42252,42254,42256,42258,42260,42262,42264,42266,42268,42270],{"class":120,"line":176},[118,42249,5966],{"class":226},[118,42251,25],{"class":124},[118,42253,425],{"class":213},[118,42255,255],{"class":124},[118,42257,430],{"class":124},[118,42259,40852],{"class":433},[118,42261,430],{"class":124},[118,42263,395],{"class":124},[118,42265,233],{"class":226},[118,42267,25],{"class":124},[118,42269,519],{"class":213},[118,42271,1289],{"class":124},[118,42273,42274],{"class":120,"line":186},[118,42275,706],{"class":124},[118,42277,42278],{"class":120,"line":196},[118,42279,1944],{"class":124},[14,42281,42282,42283,42285,42286,42289,42290,42292],{},"More on the grid in ",[3163,42284,6902],{"href":6901},". The one-sentence version: ",[18,42287,42288],{},"Col(span, fn)"," takes a span from 1 to 12, and ",[18,42291,6592],{}," is the fraction of the row it takes.",[41,42294,42296],{"id":42295},"the-minimal-go-pdffpdf-gpdf-diff","The minimal go-pdf/fpdf → gpdf diff",[14,42298,42299,42300,42302,42303,42305,42306,42308,42309,42312],{},"If you're coming from ",[18,42301,1557],{}," specifically (not ",[18,42304,35345],{},"), the good news is the API surfaces are almost identical — ",[18,42307,1557],{}," is a fork that changed almost nothing at the call-site level. The migration to gpdf is the same migration our ",[3163,42310,42311],{"href":4839},"gofpdf guide"," walks through, with one import-path rename.",[14,42314,42315],{},"Here's the smallest possible diff — a \"generate PDF\" HTTP handler:",[14,42317,42318],{},[1629,42319,42320],{},"Before — go-pdf/fpdf:",[109,42322,42324],{"className":111,"code":42323,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"net/http\"\n\n    \"github.com/go-pdf/fpdf\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    pdf := fpdf.New(\"P\", \"mm\", \"A4\", \"\")\n    pdf.AddPage()\n    pdf.SetFont(\"Arial\", \"B\", 16)\n    pdf.Cell(40, 10, \"Hello, World!\")\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := pdf.Output(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[18,42325,42326,42332,42336,42342,42351,42355,42364,42368,42372,42410,42455,42465,42498,42524,42528,42563,42591,42621,42625],{"__ignoreMap":114},[118,42327,42328,42330],{"class":120,"line":121},[118,42329,125],{"class":124},[118,42331,129],{"class":128},[118,42333,42334],{"class":120,"line":132},[118,42335,136],{"emptyLinePlaceholder":135},[118,42337,42338,42340],{"class":120,"line":139},[118,42339,143],{"class":142},[118,42341,146],{"class":124},[118,42343,42344,42346,42349],{"class":120,"line":149},[118,42345,152],{"class":124},[118,42347,42348],{"class":128},"net/http",[118,42350,158],{"class":124},[118,42352,42353],{"class":120,"line":161},[118,42354,136],{"emptyLinePlaceholder":135},[118,42356,42357,42359,42362],{"class":120,"line":166},[118,42358,152],{"class":124},[118,42360,42361],{"class":128},"github.com/go-pdf/fpdf",[118,42363,158],{"class":124},[118,42365,42366],{"class":120,"line":176},[118,42367,199],{"class":124},[118,42369,42370],{"class":120,"line":186},[118,42371,136],{"emptyLinePlaceholder":135},[118,42373,42374,42376,42379,42381,42383,42386,42388,42391,42393,42396,42398,42401,42403,42406,42408],{"class":120,"line":196},[118,42375,210],{"class":124},[118,42377,42378],{"class":213}," handler",[118,42380,255],{"class":124},[118,42382,37071],{"class":331},[118,42384,42385],{"class":128}," http",[118,42387,25],{"class":124},[118,42389,42390],{"class":128},"ResponseWriter",[118,42392,395],{"class":124},[118,42394,42395],{"class":331}," r",[118,42397,334],{"class":124},[118,42399,42400],{"class":128},"http",[118,42402,25],{"class":124},[118,42404,42405],{"class":128},"Request",[118,42407,345],{"class":124},[118,42409,220],{"class":124},[118,42411,42412,42414,42416,42419,42421,42423,42425,42427,42430,42432,42434,42436,42439,42441,42443,42445,42447,42449,42451,42453],{"class":120,"line":202},[118,42413,13507],{"class":226},[118,42415,230],{"class":124},[118,42417,42418],{"class":226}," fpdf",[118,42420,25],{"class":124},[118,42422,238],{"class":213},[118,42424,255],{"class":124},[118,42426,430],{"class":124},[118,42428,42429],{"class":433},"P",[118,42431,430],{"class":124},[118,42433,395],{"class":124},[118,42435,1146],{"class":124},[118,42437,42438],{"class":433},"mm",[118,42440,430],{"class":124},[118,42442,395],{"class":124},[118,42444,1146],{"class":124},[118,42446,263],{"class":433},[118,42448,430],{"class":124},[118,42450,395],{"class":124},[118,42452,13660],{"class":124},[118,42454,199],{"class":124},[118,42456,42457,42459,42461,42463],{"class":120,"line":207},[118,42458,13525],{"class":226},[118,42460,25],{"class":124},[118,42462,1190],{"class":213},[118,42464,1193],{"class":124},[118,42466,42467,42469,42471,42473,42475,42477,42480,42482,42484,42486,42489,42491,42493,42496],{"class":120,"line":223},[118,42468,13525],{"class":226},[118,42470,25],{"class":124},[118,42472,13647],{"class":213},[118,42474,255],{"class":124},[118,42476,430],{"class":124},[118,42478,42479],{"class":433},"Arial",[118,42481,430],{"class":124},[118,42483,395],{"class":124},[118,42485,1146],{"class":124},[118,42487,42488],{"class":433},"B",[118,42490,430],{"class":124},[118,42492,395],{"class":124},[118,42494,42495],{"class":299}," 16",[118,42497,199],{"class":124},[118,42499,42500,42502,42504,42506,42508,42510,42512,42514,42516,42518,42520,42522],{"class":120,"line":244},[118,42501,13525],{"class":226},[118,42503,25],{"class":124},[118,42505,12975],{"class":213},[118,42507,255],{"class":124},[118,42509,9910],{"class":299},[118,42511,395],{"class":124},[118,42513,11241],{"class":299},[118,42515,395],{"class":124},[118,42517,1146],{"class":124},[118,42519,13749],{"class":433},[118,42521,430],{"class":124},[118,42523,199],{"class":124},[118,42525,42526],{"class":120,"line":269},[118,42527,136],{"emptyLinePlaceholder":135},[118,42529,42530,42533,42535,42537,42540,42543,42545,42547,42550,42552,42554,42556,42559,42561],{"class":120,"line":306},[118,42531,42532],{"class":226},"    w",[118,42534,25],{"class":124},[118,42536,325],{"class":213},[118,42538,42539],{"class":124},"().",[118,42541,42542],{"class":213},"Set",[118,42544,255],{"class":124},[118,42546,430],{"class":124},[118,42548,42549],{"class":433},"Content-Type",[118,42551,430],{"class":124},[118,42553,395],{"class":124},[118,42555,1146],{"class":124},[118,42557,42558],{"class":433},"application/pdf",[118,42560,430],{"class":124},[118,42562,199],{"class":124},[118,42564,42565,42567,42569,42571,42573,42575,42577,42579,42581,42583,42585,42587,42589],{"class":120,"line":312},[118,42566,1408],{"class":142},[118,42568,1391],{"class":226},[118,42570,230],{"class":124},[118,42572,7260],{"class":226},[118,42574,25],{"class":124},[118,42576,13378],{"class":213},[118,42578,255],{"class":124},[118,42580,37071],{"class":226},[118,42582,7902],{"class":124},[118,42584,1391],{"class":226},[118,42586,1413],{"class":124},[118,42588,1416],{"class":124},[118,42590,220],{"class":124},[118,42592,42593,42596,42598,42601,42603,42605,42607,42610,42612,42614,42616,42619],{"class":120,"line":317},[118,42594,42595],{"class":226},"        http",[118,42597,25],{"class":124},[118,42599,42600],{"class":213},"Error",[118,42602,255],{"class":124},[118,42604,37071],{"class":226},[118,42606,395],{"class":124},[118,42608,42609],{"class":226}," err",[118,42611,25],{"class":124},[118,42613,42600],{"class":213},[118,42615,448],{"class":124},[118,42617,42618],{"class":299}," 500",[118,42620,199],{"class":124},[118,42622,42623],{"class":120,"line":350},[118,42624,1375],{"class":124},[118,42626,42627],{"class":120,"line":379},[118,42628,1479],{"class":124},[14,42630,42631],{},[1629,42632,13844],{},[109,42634,42636],{"className":111,"code":42635,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"net/http\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Hello, World!\", template.FontSize(16), template.Bold())\n        })\n    })\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := doc.Render(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[18,42637,42638,42644,42648,42654,42662,42666,42674,42682,42690,42694,42698,42730,42744,42762,42792,42796,42800,42814,42838,42868,42907,42911,42915,42919,42949,42977,43003,43007],{"__ignoreMap":114},[118,42639,42640,42642],{"class":120,"line":121},[118,42641,125],{"class":124},[118,42643,129],{"class":128},[118,42645,42646],{"class":120,"line":132},[118,42647,136],{"emptyLinePlaceholder":135},[118,42649,42650,42652],{"class":120,"line":139},[118,42651,143],{"class":142},[118,42653,146],{"class":124},[118,42655,42656,42658,42660],{"class":120,"line":149},[118,42657,152],{"class":124},[118,42659,42348],{"class":128},[118,42661,158],{"class":124},[118,42663,42664],{"class":120,"line":161},[118,42665,136],{"emptyLinePlaceholder":135},[118,42667,42668,42670,42672],{"class":120,"line":166},[118,42669,152],{"class":124},[118,42671,3203],{"class":128},[118,42673,158],{"class":124},[118,42675,42676,42678,42680],{"class":120,"line":176},[118,42677,152],{"class":124},[118,42679,171],{"class":128},[118,42681,158],{"class":124},[118,42683,42684,42686,42688],{"class":120,"line":186},[118,42685,152],{"class":124},[118,42687,191],{"class":128},[118,42689,158],{"class":124},[118,42691,42692],{"class":120,"line":196},[118,42693,199],{"class":124},[118,42695,42696],{"class":120,"line":202},[118,42697,136],{"emptyLinePlaceholder":135},[118,42699,42700,42702,42704,42706,42708,42710,42712,42714,42716,42718,42720,42722,42724,42726,42728],{"class":120,"line":207},[118,42701,210],{"class":124},[118,42703,42378],{"class":213},[118,42705,255],{"class":124},[118,42707,37071],{"class":331},[118,42709,42385],{"class":128},[118,42711,25],{"class":124},[118,42713,42390],{"class":128},[118,42715,395],{"class":124},[118,42717,42395],{"class":331},[118,42719,334],{"class":124},[118,42721,42400],{"class":128},[118,42723,25],{"class":124},[118,42725,42405],{"class":128},[118,42727,345],{"class":124},[118,42729,220],{"class":124},[118,42731,42732,42734,42736,42738,42740,42742],{"class":120,"line":223},[118,42733,227],{"class":226},[118,42735,230],{"class":124},[118,42737,3595],{"class":226},[118,42739,25],{"class":124},[118,42741,3600],{"class":213},[118,42743,241],{"class":124},[118,42745,42746,42748,42750,42752,42754,42756,42758,42760],{"class":120,"line":244},[118,42747,3607],{"class":226},[118,42749,25],{"class":124},[118,42751,252],{"class":213},[118,42753,255],{"class":124},[118,42755,258],{"class":226},[118,42757,25],{"class":124},[118,42759,263],{"class":226},[118,42761,266],{"class":124},[118,42763,42764,42766,42768,42770,42772,42774,42776,42778,42780,42782,42784,42786,42788,42790],{"class":120,"line":269},[118,42765,3607],{"class":226},[118,42767,25],{"class":124},[118,42769,276],{"class":213},[118,42771,255],{"class":124},[118,42773,258],{"class":226},[118,42775,25],{"class":124},[118,42777,285],{"class":213},[118,42779,255],{"class":124},[118,42781,258],{"class":226},[118,42783,25],{"class":124},[118,42785,294],{"class":213},[118,42787,255],{"class":124},[118,42789,300],{"class":299},[118,42791,303],{"class":124},[118,42793,42794],{"class":120,"line":306},[118,42795,309],{"class":124},[118,42797,42798],{"class":120,"line":312},[118,42799,136],{"emptyLinePlaceholder":135},[118,42801,42802,42804,42806,42808,42810,42812],{"class":120,"line":317},[118,42803,5431],{"class":226},[118,42805,230],{"class":124},[118,42807,1185],{"class":226},[118,42809,25],{"class":124},[118,42811,1190],{"class":213},[118,42813,1193],{"class":124},[118,42815,42816,42818,42820,42822,42824,42826,42828,42830,42832,42834,42836],{"class":120,"line":350},[118,42817,5494],{"class":226},[118,42819,25],{"class":124},[118,42821,358],{"class":213},[118,42823,328],{"class":124},[118,42825,363],{"class":331},[118,42827,334],{"class":124},[118,42829,337],{"class":128},[118,42831,25],{"class":124},[118,42833,372],{"class":128},[118,42835,345],{"class":124},[118,42837,220],{"class":124},[118,42839,42840,42842,42844,42846,42848,42850,42852,42854,42856,42858,42860,42862,42864,42866],{"class":120,"line":379},[118,42841,1737],{"class":226},[118,42843,25],{"class":124},[118,42845,387],{"class":213},[118,42847,255],{"class":124},[118,42849,20],{"class":299},[118,42851,395],{"class":124},[118,42853,398],{"class":124},[118,42855,401],{"class":331},[118,42857,334],{"class":124},[118,42859,337],{"class":128},[118,42861,25],{"class":124},[118,42863,410],{"class":128},[118,42865,345],{"class":124},[118,42867,220],{"class":124},[118,42869,42870,42872,42874,42876,42878,42880,42882,42884,42886,42888,42890,42892,42894,42897,42899,42901,42903,42905],{"class":120,"line":417},[118,42871,1768],{"class":226},[118,42873,25],{"class":124},[118,42875,425],{"class":213},[118,42877,255],{"class":124},[118,42879,430],{"class":124},[118,42881,13749],{"class":433},[118,42883,430],{"class":124},[118,42885,395],{"class":124},[118,42887,233],{"class":226},[118,42889,25],{"class":124},[118,42891,455],{"class":213},[118,42893,255],{"class":124},[118,42895,42896],{"class":299},"16",[118,42898,1280],{"class":124},[118,42900,233],{"class":226},[118,42902,25],{"class":124},[118,42904,445],{"class":213},[118,42906,1289],{"class":124},[118,42908,42909],{"class":120,"line":466},[118,42910,574],{"class":124},[118,42912,42913],{"class":120,"line":472},[118,42914,706],{"class":124},[118,42916,42917],{"class":120,"line":503},[118,42918,136],{"emptyLinePlaceholder":135},[118,42920,42921,42923,42925,42927,42929,42931,42933,42935,42937,42939,42941,42943,42945,42947],{"class":120,"line":537},[118,42922,42532],{"class":226},[118,42924,25],{"class":124},[118,42926,325],{"class":213},[118,42928,42539],{"class":124},[118,42930,42542],{"class":213},[118,42932,255],{"class":124},[118,42934,430],{"class":124},[118,42936,42549],{"class":433},[118,42938,430],{"class":124},[118,42940,395],{"class":124},[118,42942,1146],{"class":124},[118,42944,42558],{"class":433},[118,42946,430],{"class":124},[118,42948,199],{"class":124},[118,42950,42951,42953,42955,42957,42959,42961,42963,42965,42967,42969,42971,42973,42975],{"class":120,"line":566},[118,42952,1408],{"class":142},[118,42954,1391],{"class":226},[118,42956,230],{"class":124},[118,42958,1185],{"class":226},[118,42960,25],{"class":124},[118,42962,29105],{"class":213},[118,42964,255],{"class":124},[118,42966,37071],{"class":226},[118,42968,7902],{"class":124},[118,42970,1391],{"class":226},[118,42972,1413],{"class":124},[118,42974,1416],{"class":124},[118,42976,220],{"class":124},[118,42978,42979,42981,42983,42985,42987,42989,42991,42993,42995,42997,42999,43001],{"class":120,"line":571},[118,42980,42595],{"class":226},[118,42982,25],{"class":124},[118,42984,42600],{"class":213},[118,42986,255],{"class":124},[118,42988,37071],{"class":226},[118,42990,395],{"class":124},[118,42992,42609],{"class":226},[118,42994,25],{"class":124},[118,42996,42600],{"class":213},[118,42998,448],{"class":124},[118,43000,42618],{"class":299},[118,43002,199],{"class":124},[118,43004,43005],{"class":120,"line":577},[118,43006,1375],{"class":124},[118,43008,43009],{"class":120,"line":602},[118,43010,1479],{"class":124},[14,43012,43013,43014,43016,43017,43020,43021,43023,43024,43026],{},"Three-line cursor code becomes three-call builder code. The structure shows up in the source instead of hiding inside the order of ",[18,43015,12975],{}," calls. For CJK, add ",[18,43018,43019],{},"gpdf.WithFont(\"NotoSansJP\", ttfBytes)"," — no ",[18,43022,2745],{},", no filesystem path, no UTF-8 flag. See ",[3163,43025,9792],{"href":9791}," for the full walkthrough.",[14,43028,8198,43029,43032,43033,43035],{},[3163,43030,43031],{"href":4839},"gofpdf migration guide"," has five more before/after pairs covering tables, repeating headers/footers, page numbers, and absolute positioning. Everything there applies verbatim to ",[18,43034,1557],{}," users — just swap the import path.",[41,43037,43039],{"id":43038},"the-benchmark-picture","The benchmark picture",[14,43041,43042,43043,43045],{},"\"Faster\" is easy to claim and hard to earn. The table below is from ",[18,43044,17780],{}," on an Apple M1 running Go 1.25. Workloads are what production code actually does — not micro-benchmarks chosen to flatter one library.",[1516,43047,43048,43062],{},[1519,43049,43050],{},[1522,43051,43052,43054,43056,43058,43060],{},[1525,43053,17691],{},[1525,43055,1587],{},[1525,43057,1539],{},[1525,43059,11345],{},[1525,43061,17700],{},[1532,43063,43064,43079,43094,43108],{},[1522,43065,43066,43069,43073,43075,43077],{},[1537,43067,43068],{},"Single page (hello)",[1537,43070,43071],{},[1629,43072,3248],{},[1537,43074,17717],{},[1537,43076,17714],{},[1537,43078,17720],{},[1522,43080,43081,43084,43088,43090,43092],{},[1537,43082,43083],{},"4×10 line-items table",[1537,43085,43086],{},[1629,43087,16053],{},[1537,43089,17735],{},[1537,43091,17732],{},[1537,43093,17738],{},[1522,43095,43096,43098,43102,43104,43106],{},[1537,43097,17743],{},[1537,43099,43100],{},[1629,43101,4131],{},[1537,43103,17752],{},[1537,43105,17738],{},[1537,43107,17755],{},[1522,43109,43110,43112,43116,43118,43120],{},[1537,43111,17760],{},[1537,43113,43114],{},[1629,43115,17765],{},[1537,43117,17771],{},[1537,43119,17768],{},[1537,43121,17774],{},[14,43123,43124,43125,43128],{},"At 13 µs per single page, one core produces ~75,000 hello-world PDFs per second. At 108 µs per invoice, ~9,000 per second. ",[1629,43126,43127],{},"The point is not bragging rights","; it's that you can stop thinking about whether to cache or async-queue PDF generation. For most workloads, generating on the request path is fine.",[14,43130,43131,43132,43134],{},"Maroto v2 shows up slow on the table benchmark because it drives ",[18,43133,1557],{}," underneath and adds its own layout pass on top. That's not a criticism of the Maroto API — the API is good — it's a structural cost of sitting on the fpdf foundation. When Maroto v3 drops the fpdf dependency, expect this column to change.",[14,43136,43137],{},"The 100-page benchmark is worth dwelling on. gpdf's streaming writer emits content as rows are laid out; gofpdf buffers more state per page. For a pagination-heavy workload (monthly reports, catalogs, compliance exports), the difference is minutes vs seconds at the upper end of document sizes.",[41,43139,43141,43142,43144],{"id":43140},"when-gpdf-is-not-the-right-pick","When gpdf is ",[4744,43143,18100],{}," the right pick",[14,43146,43147,43148,43150],{},"Every migration post has to answer \"when should I ",[4744,43149,18100],{}," move?\" Honest answers:",[46,43152,43153,43166,43174,43186],{},[49,43154,43155,43158,43159,43162,43163,43165],{},[1629,43156,43157],{},"AcroForm / fillable forms."," If your use case is generating PDFs that users open in Acrobat and type into, gpdf's form-field support is still minimal. ",[18,43160,43161],{},"unidoc"," is more complete here; ",[18,43164,1565],{}," has partial AcroForm support. A future gpdf release will close this gap, but today it's a gap.",[49,43167,43168,4919,43171,43173],{},[1629,43169,43170],{},"Arbitrary vector paths and complex drawing.",[18,43172,2530],{}," draws a horizontal rule inside a column. If you need beziers, custom paths, or gradient fills for charts or technical drawings, gpdf isn't there. (Pre-rendered chart images work fine — this is about drawing primitives, not embedding.)",[49,43175,43176,43182,43183,43185],{},[1629,43177,43178,43179,43181],{},"Existing gofpdf codebases with heavy ",[18,43180,12972],{}," use."," If your code is 2,000 lines of cursor manipulation, the migration is a rewrite, not a find-and-replace. The rewritten code is almost always shorter, but \"almost always\" is cold comfort on the day the deadline lands. The ",[3163,43184,37975],{"href":4839}," estimates effort honestly.",[49,43187,43188,43191,43192,43195,43196,43198],{},[1629,43189,43190],{},"You need HTML → PDF with full CSS support right now."," gpdf has an HTML subset in its ",[18,43193,43194],{},"gpdf-pro"," add-on, but full CSS parity with Chromium is not a goal. If your template is a complicated React component, ",[18,43197,41969],{}," or a commercial API is a more direct fit.",[14,43200,43201],{},"If none of those bite, gpdf is the default. If one of them does, the right move is usually to run both libraries side-by-side — gpdf for the new PDFs, the incumbent for the edge case — and migrate the edge case later once gpdf catches up.",[41,43203,43205],{"id":43204},"the-compliance-angle","The compliance angle",[14,43207,43208,43209,43212],{},"One thing we don't see discussed enough in ecosystem posts: ",[1629,43210,43211],{},"archived dependencies show up in SOC 2 and ISO 27001 audits."," The auditor wants to know that third-party code in your supply chain is actively maintained. \"Archived in 2021\" triggers a finding. \"Archived in 2025\" triggers a finding. \"Fork we maintain internally\" triggers follow-up questions about how you'll patch a zero-day.",[14,43214,43215,43216,43218],{},"This is mostly why teams on larger companies' security reviews have been quietly asking us when gpdf will hit a stable v1. The answer is: it already has. ",[18,43217,3203],{}," is tagged, semver-stable, and the v1 API surface is frozen. The project has a security contact, a responsible disclosure policy, and CI that runs against Go 1.22 through 1.26.",[14,43220,43221,43222,43224],{},"You don't migrate ",[4744,43223,14495],{}," the audit. You migrate because the audit is about to ask you to anyway.",[41,43226,3054],{"id":3053},[14,43228,43229,43232,43233,12386,43235,43237],{},[1629,43230,43231],{},"Is \"the modern Go PDF stack\" just gpdf, or multiple libraries?","\nFor most teams, it's gpdf alone — the single library covers document creation, CJK, tables, grids, pagination, and output. Teams with fillable-form requirements pair it with ",[18,43234,1565],{},[18,43236,43161],{}," for those documents specifically. Teams with chart-heavy exports pre-render charts to PNG and embed them. \"Stack\" here means a short list, not a layered architecture.",[14,43239,43240,43243],{},[1629,43241,43242],{},"Can I run gpdf and go-pdf/fpdf side-by-side during migration?","\nYes. They're different import paths and different types. Route new endpoints to gpdf and leave old ones on go-pdf/fpdf until you have time to rewrite. There's no runtime conflict.",[14,43245,43246,43249],{},[1629,43247,43248],{},"Will there be a go-pdf/fpdf v3 or a new fork?","\nMaybe. The bet behind gpdf isn't that nobody will ever un-archive the fork — it's that the architecture doesn't scale to what people are building today. A new fork would inherit the same limitations unless it rewrites the layout model, at which point it's closer to gpdf than to fpdf.",[14,43251,43252,43257,43258,3096,43260,3096,43262,43265],{},[1629,43253,17922,43254,43256],{},[18,43255,1565],{}," as a modern alternative?","\nIt's genuinely maintained and zero-dep. The API is coordinate-level — ",[18,43259,13706],{},[18,43261,13721],{},[18,43263,43264],{},"CellWithOption"," — so it suits form overlays and fixed templates well. For invoice-like documents with tables and repeating chrome, you end up writing a layout helper on top, which is the same pit gofpdf users fell into. gpdf and gopdf don't really compete — they solve adjacent problems.",[14,43267,43268,43271,43273],{},[1629,43269,43270],{},"Does gpdf have a commercial/hosted version?",[18,43272,37755],{}," is coming — a hosted API that accepts JSON templates and returns PDFs. It's not public yet. When it launches, this blog will have a post about it. The OSS library will remain MIT, zero-dep, and independently useful.",[14,43275,43276,43279],{},[1629,43277,43278],{},"What's the roadmap priority order?","\nPublic gpdf roadmap as of 2026-04: (1) AcroForm form fields, (2) full PDF/A-3 compliance, (3) expanded HTML→PDF coverage in gpdf-pro, (4) RTL text support (Arabic, Hebrew). Feedback on priority is welcome on GitHub issues.",[41,43281,4794],{"id":4793},[14,43283,6933],{},[109,43285,43286],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,43287,43288],{"__ignoreMap":114},[118,43289,43290,43292,43294],{"class":120,"line":121},[118,43291,113],{"class":128},[118,43293,3156],{"class":433},[118,43295,3159],{"class":433},[14,43297,43298,3169,43301],{},[3163,43299,3168],{"href":3165,"rel":43300},[3167],[3163,43302,3174],{"href":3172,"rel":43303},[3167],[41,43305,4821],{"id":4820},[46,43307,43308,43313,43318,43323],{},[49,43309,43310,43312],{},[3163,43311,18018],{"href":4839}," — the line-by-line API mapping",[49,43314,43315,43317],{},[3163,43316,4790],{"href":3381}," — deeper head-to-head benchmarks and feature grids",[49,43319,43320,43322],{},[3163,43321,6902],{"href":6901}," — the builder idiom that replaces cursor-pushing",[49,43324,43325,43327,43328,43330],{},[3163,43326,9792],{"href":9791}," — CJK without the ",[18,43329,2745],{}," dance",[3176,43332,43333],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}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}",{"title":114,"searchDepth":132,"depth":132,"links":43335},[43336,43337,43338,43339,43340,43341,43342,43343,43344,43346,43347,43348,43349],{"id":43,"depth":132,"text":44},{"id":41700,"depth":132,"text":41701},{"id":41747,"depth":132,"text":41748},{"id":41790,"depth":132,"text":41791},{"id":41833,"depth":132,"text":41834},{"id":42050,"depth":132,"text":42051},{"id":42295,"depth":132,"text":42296},{"id":43038,"depth":132,"text":43039},{"id":43140,"depth":132,"text":43345},"When gpdf is not the right pick",{"id":43204,"depth":132,"text":43205},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"jung-kurt/gofpdf archived in 2021. go-pdf/fpdf followed in 2025. Here's the Go PDF stack we actually use in 2026 — gpdf, the trade-offs, and why.",{},"/blog/go-pdf-fpdf-archived",{"title":41668,"description":43350},"blog/006.go-pdf-fpdf-archived",[4868,4866,4867],"BPfj9SokRykHBry1cLYmBWy4iexNKmU-6ObpO8-086U",{"id":43358,"title":43359,"author":43360,"body":43361,"date":40852,"description":44968,"draft":3196,"extension":3197,"howTo":44969,"image":3220,"meta":44992,"navigation":135,"path":4327,"seo":44993,"stem":44994,"tags":44995,"updated":3220,"__hash__":44996},"blog/blog/007.japanese-pdf-in-go.md","Japanese PDFs in Go: the 2026 definitive guide",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":43362,"toc":44954},[43363,43365,43381,43385,43388,43394,43397,43403,43407,43418,43456,43464,43470,43474,43477,43590,43593,43599,43605,43609,43617,44426,44429,44477,44486,44490,44496,44499,44502,44547,44560,44563,44588,44604,44608,44611,44617,44620,44634,44637,44717,44727,44731,44734,44743,44752,44776,44790,44793,44797,44804,44810,44816,44830,44833,44835,44858,44864,44870,44884,44894,44903,44905,44908,44920,44928,44930,44952],[41,43364,44],{"id":43},[14,43366,43367,43368,43370,43371,1649,43374,43376,43377,43380],{},"If your Go PDF renders ",[18,43369,38829],{}," as five tofu boxes, the fix is two lines of setup, not a rewrite. Load a Japanese TTF, pass ",[18,43372,43373],{},"gpdf.WithFont",[18,43375,3600],{},", write Japanese. ",[1629,43378,43379],{},"gpdf subsets the glyph table automatically",", so the output carries only the characters you actually used — around 30 KB, not the 5 MB full font. This guide is the map: why Japanese PDF generation is weirdly hard in Go, the four real options in 2026, a complete working example, font subsetting internals, mixed-script edge cases, and what still doesn't work.",[41,43382,43384],{"id":43383},"why-this-guide-exists","Why this guide exists",[14,43386,43387],{},"A Japanese-text PDF in Go should be five minutes of work. For a lot of teams it's a day and a half.",[14,43389,43390,43391,43393],{},"The usual story: someone swaps in ",[18,43392,2745],{},", the PDF renders blank rectangles — the infamous 豆腐 — and a senior engineer spends an afternoon figuring out whether the problem is the font path, the subset flag, the CMap, the UTF-8 flag, or the PDF reader. By the end of the day there's a Slack thread titled \"WHY IS 漢字 STILL BROKEN\" and a pull request that adds three helper functions everyone already regrets.",[14,43395,43396],{},"The root cause isn't any one of those things. It's that Go's longest-lived PDF library was designed in 2002 for PHP and Latin-1, and almost every Japanese tutorial written since has been fighting that legacy. This guide is the 2026 version: what actually works when you start clean, and what's still genuinely hard.",[14,43398,43399,43400,43402],{},"All code in this post runs against ",[18,43401,1587],{}," v1.x as of 2026-04. The benchmark numbers are from an Apple M1 with Go 1.25.",[41,43404,43406],{"id":43405},"the-tofu-problem-in-90-seconds","The tofu problem in 90 seconds",[14,43408,43409,43410,43413,43414,43417],{},"PDF doesn't care about Unicode. It cares about ",[1629,43411,43412],{},"glyph IDs"," — integer indices into a font's embedded glyph table. When you write ",[18,43415,43416],{},"\"こんにちは\""," to a PDF, somebody has to:",[1624,43419,43420,43429,43435,43441],{},[49,43421,43422,43425,43426,43428],{},[1629,43423,43424],{},"Parse the TTF"," and find the glyph ID for each code point (via the font's ",[18,43427,21331],{}," subtable).",[49,43430,43431,43434],{},[1629,43432,43433],{},"Write a ToUnicode CMap"," so the PDF reader can map glyphs back to text when the user copies or searches.",[49,43436,43437,43440],{},[1629,43438,43439],{},"Subset"," the font so the PDF doesn't carry all 20,000 glyphs of Noto Sans JP.",[49,43442,43443,43446,43447,3096,43449,3096,43452,43455],{},[1629,43444,43445],{},"Embed the result"," with correctly stitched ",[18,43448,20794],{},[18,43450,43451],{},"OS/2",[18,43453,43454],{},"head",", and encoding objects.",[14,43457,43458,43459,54,43461,43463],{},"If any of those steps is missing or wrong, the reader can't find a glyph for the code point and paints a tofu box. The archived ",[18,43460,35345],{},[18,43462,1557],{}," lineages retrofitted all of this onto a single-byte-font internal model — the original FPDF from 2002 only knew about Latin-1. That's why setup is fragile, why the output often embeds the full font instead of a subset, and why the failure modes vary by OS and PDF reader.",[14,43465,43466,43467,43469],{},"gpdf treats CJK as a first-class case. The TTF subsetter is in the core package. The ToUnicode CMap is written automatically. There is no ",[18,43468,2745],{}," dance because there is no single-byte-font legacy to retrofit around.",[41,43471,43473],{"id":43472},"the-four-real-options-in-2026","The four real options in 2026",[14,43475,43476],{},"Before writing any code: the honest field. \"Japanese-capable\" means \"will render arbitrary Japanese text without crashes or tofu, given a correct TTF.\"",[1516,43478,43479,43498],{},[1519,43480,43481],{},[1522,43482,43483,43485,43487,43490,43493,43496],{},[1525,43484,9439],{},[1525,43486,3286],{},[1525,43488,43489],{},"Deps",[1525,43491,43492],{},"CJK path",[1525,43494,43495],{},"PDF size for 300-char doc",[1525,43497,12264],{},[1532,43499,43500,43523,43544,43566],{},[1522,43501,43502,43507,43509,43512,43517,43520],{},[1537,43503,43504,43506],{},[18,43505,1557],{}," (archived 2025)",[1537,43508,3375],{},[1537,43510,43511],{},"stdlib",[1537,43513,43514,43516],{},[18,43515,2745],{}," retrofit",[1537,43518,43519],{},"~5 MB (full font)",[1537,43521,43522],{},"Retrofitted onto Latin-1 core. Subsetting is opt-in and imperfect.",[1522,43524,43525,43529,43531,43533,43538,43541],{},[1537,43526,43527],{},[18,43528,1565],{},[1537,43530,3375],{},[1537,43532,43511],{},[1537,43534,43535,43537],{},[18,43536,13586],{}," + manual",[1537,43539,43540],{},"~3 MB typical",[1537,43542,43543],{},"Low-level. You write coordinates. Subsetting exists but you drive it.",[1522,43545,43546,43550,43552,43557,43560,43563],{},[1537,43547,43548,41970],{},[18,43549,41969],{},[1537,43551,41975],{},[1537,43553,43554],{},[1629,43555,43556],{},"Chromium binary",[1537,43558,43559],{},"Native via browser",[1537,43561,43562],{},"varies",[1537,43564,43565],{},"HTML/CSS. Needs fonts installed in the container. 500 MB+ image.",[1522,43567,43568,43572,43574,43579,43582,43587],{},[1537,43569,43570],{},[18,43571,1587],{},[1537,43573,3375],{},[1537,43575,43576],{},[1629,43577,43578],{},"stdlib only",[1537,43580,43581],{},"Native, automatic subset",[1537,43583,43584],{},[1629,43585,43586],{},"~30 KB",[1537,43588,43589],{},"Pure Go. Builder API. ToUnicode CMap written for you.",[14,43591,43592],{},"Two things worth underlining:",[14,43594,43595,43598],{},[1629,43596,43597],{},"The 160× size difference between \"full font embedded\" and \"automatic subset\" is not a rounding error."," A customer-facing e-commerce invoice with ten line items needs maybe 120 unique Japanese glyphs. Embedding the full Noto Sans JP (5.1 MB) on every invoice means your object storage bill includes the same 5 MB of glyph data 10 million times by the end of the year. Subset embedding carries only the glyphs you used.",[14,43600,43601,43604],{},[1629,43602,43603],{},"\"Chromedp works\" is true and also the most expensive answer."," If your team already runs a headless Chrome fleet for screenshotting, piggybacking on it for PDFs is fine. If you don't, standing one up just to print 日本語 is a lot of infrastructure for a problem that's 40 lines of Go.",[41,43606,43608],{"id":43607},"the-shortest-path-that-works","The shortest path that works",[14,43610,43611,43612,43614,43615,25],{},"Start with this. It's complete — copy, save as ",[18,43613,102],{},", drop two TTFs next to it, ",[18,43616,106],{},[109,43618,43620],{"className":111,"code":43619,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    regular, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    bold, err := os.ReadFile(\"NotoSansJP-Bold.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", regular),\n        gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"請求書\", template.FontFamily(\"NotoSansJP-Bold\"), template.FontSize(22))\n            c.Text(\"2026 年 4 月 16 日\")\n        })\n    })\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(7, func(c *template.ColBuilder) {\n            c.Text(\"株式会社 ABC 御中\", template.FontSize(13))\n            c.Text(\"〒 100-0001 東京都千代田区千代田 1-1\")\n        })\n        r.Col(5, func(c *template.ColBuilder) {\n            c.Text(\"合計 ¥ 128,000\", template.FontFamily(\"NotoSansJP-Bold\"), template.AlignRight())\n            c.Text(\"支払期限: 2026-05-31\", template.AlignRight())\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice-ja.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,43621,43622,43628,43632,43638,43646,43654,43658,43666,43674,43682,43686,43690,43700,43726,43738,43752,43756,43783,43795,43809,43813,43817,43831,43849,43879,43901,43923,43945,43949,43953,43967,43991,44021,44067,44086,44090,44094,44118,44148,44180,44199,44203,44233,44276,44303,44307,44311,44315,44333,44345,44359,44363,44404,44418,44422],{"__ignoreMap":114},[118,43623,43624,43626],{"class":120,"line":121},[118,43625,125],{"class":124},[118,43627,129],{"class":128},[118,43629,43630],{"class":120,"line":132},[118,43631,136],{"emptyLinePlaceholder":135},[118,43633,43634,43636],{"class":120,"line":139},[118,43635,143],{"class":142},[118,43637,146],{"class":124},[118,43639,43640,43642,43644],{"class":120,"line":149},[118,43641,152],{"class":124},[118,43643,5303],{"class":128},[118,43645,158],{"class":124},[118,43647,43648,43650,43652],{"class":120,"line":161},[118,43649,152],{"class":124},[118,43651,155],{"class":128},[118,43653,158],{"class":124},[118,43655,43656],{"class":120,"line":166},[118,43657,136],{"emptyLinePlaceholder":135},[118,43659,43660,43662,43664],{"class":120,"line":176},[118,43661,152],{"class":124},[118,43663,3203],{"class":128},[118,43665,158],{"class":124},[118,43667,43668,43670,43672],{"class":120,"line":186},[118,43669,152],{"class":124},[118,43671,171],{"class":128},[118,43673,158],{"class":124},[118,43675,43676,43678,43680],{"class":120,"line":196},[118,43677,152],{"class":124},[118,43679,191],{"class":128},[118,43681,158],{"class":124},[118,43683,43684],{"class":120,"line":202},[118,43685,199],{"class":124},[118,43687,43688],{"class":120,"line":207},[118,43689,136],{"emptyLinePlaceholder":135},[118,43691,43692,43694,43696,43698],{"class":120,"line":223},[118,43693,210],{"class":124},[118,43695,214],{"class":213},[118,43697,217],{"class":124},[118,43699,220],{"class":124},[118,43701,43702,43704,43706,43708,43710,43712,43714,43716,43718,43720,43722,43724],{"class":120,"line":244},[118,43703,20888],{"class":226},[118,43705,395],{"class":124},[118,43707,1391],{"class":226},[118,43709,230],{"class":124},[118,43711,1447],{"class":226},[118,43713,25],{"class":124},[118,43715,9545],{"class":213},[118,43717,255],{"class":124},[118,43719,430],{"class":124},[118,43721,9552],{"class":433},[118,43723,430],{"class":124},[118,43725,199],{"class":124},[118,43727,43728,43730,43732,43734,43736],{"class":120,"line":269},[118,43729,1408],{"class":142},[118,43731,1391],{"class":226},[118,43733,1413],{"class":124},[118,43735,1416],{"class":124},[118,43737,220],{"class":124},[118,43739,43740,43742,43744,43746,43748,43750],{"class":120,"line":306},[118,43741,5818],{"class":226},[118,43743,25],{"class":124},[118,43745,5823],{"class":213},[118,43747,255],{"class":124},[118,43749,1429],{"class":226},[118,43751,199],{"class":124},[118,43753,43754],{"class":120,"line":312},[118,43755,1375],{"class":124},[118,43757,43758,43761,43763,43765,43767,43769,43771,43773,43775,43777,43779,43781],{"class":120,"line":317},[118,43759,43760],{"class":226},"    bold",[118,43762,395],{"class":124},[118,43764,1391],{"class":226},[118,43766,230],{"class":124},[118,43768,1447],{"class":226},[118,43770,25],{"class":124},[118,43772,9545],{"class":213},[118,43774,255],{"class":124},[118,43776,430],{"class":124},[118,43778,32335],{"class":433},[118,43780,430],{"class":124},[118,43782,199],{"class":124},[118,43784,43785,43787,43789,43791,43793],{"class":120,"line":350},[118,43786,1408],{"class":142},[118,43788,1391],{"class":226},[118,43790,1413],{"class":124},[118,43792,1416],{"class":124},[118,43794,220],{"class":124},[118,43796,43797,43799,43801,43803,43805,43807],{"class":120,"line":379},[118,43798,5818],{"class":226},[118,43800,25],{"class":124},[118,43802,5823],{"class":213},[118,43804,255],{"class":124},[118,43806,1429],{"class":226},[118,43808,199],{"class":124},[118,43810,43811],{"class":120,"line":417},[118,43812,1375],{"class":124},[118,43814,43815],{"class":120,"line":466},[118,43816,136],{"emptyLinePlaceholder":135},[118,43818,43819,43821,43823,43825,43827,43829],{"class":120,"line":472},[118,43820,227],{"class":226},[118,43822,230],{"class":124},[118,43824,3595],{"class":226},[118,43826,25],{"class":124},[118,43828,3600],{"class":213},[118,43830,241],{"class":124},[118,43832,43833,43835,43837,43839,43841,43843,43845,43847],{"class":120,"line":503},[118,43834,3607],{"class":226},[118,43836,25],{"class":124},[118,43838,252],{"class":213},[118,43840,255],{"class":124},[118,43842,258],{"class":226},[118,43844,25],{"class":124},[118,43846,263],{"class":226},[118,43848,266],{"class":124},[118,43850,43851,43853,43855,43857,43859,43861,43863,43865,43867,43869,43871,43873,43875,43877],{"class":120,"line":537},[118,43852,3607],{"class":226},[118,43854,25],{"class":124},[118,43856,276],{"class":213},[118,43858,255],{"class":124},[118,43860,258],{"class":226},[118,43862,25],{"class":124},[118,43864,285],{"class":213},[118,43866,255],{"class":124},[118,43868,258],{"class":226},[118,43870,25],{"class":124},[118,43872,294],{"class":213},[118,43874,255],{"class":124},[118,43876,300],{"class":299},[118,43878,303],{"class":124},[118,43880,43881,43883,43885,43887,43889,43891,43893,43895,43897,43899],{"class":120,"line":566},[118,43882,3607],{"class":226},[118,43884,25],{"class":124},[118,43886,2798],{"class":213},[118,43888,255],{"class":124},[118,43890,430],{"class":124},[118,43892,2805],{"class":433},[118,43894,430],{"class":124},[118,43896,395],{"class":124},[118,43898,21029],{"class":226},[118,43900,266],{"class":124},[118,43902,43903,43905,43907,43909,43911,43913,43915,43917,43919,43921],{"class":120,"line":571},[118,43904,3607],{"class":226},[118,43906,25],{"class":124},[118,43908,2798],{"class":213},[118,43910,255],{"class":124},[118,43912,430],{"class":124},[118,43914,32124],{"class":433},[118,43916,430],{"class":124},[118,43918,395],{"class":124},[118,43920,21621],{"class":226},[118,43922,266],{"class":124},[118,43924,43925,43927,43929,43931,43933,43935,43937,43939,43941,43943],{"class":120,"line":577},[118,43926,3607],{"class":226},[118,43928,25],{"class":124},[118,43930,12501],{"class":213},[118,43932,255],{"class":124},[118,43934,430],{"class":124},[118,43936,2805],{"class":433},[118,43938,430],{"class":124},[118,43940,395],{"class":124},[118,43942,14386],{"class":299},[118,43944,266],{"class":124},[118,43946,43947],{"class":120,"line":602},[118,43948,309],{"class":124},[118,43950,43951],{"class":120,"line":633},[118,43952,136],{"emptyLinePlaceholder":135},[118,43954,43955,43957,43959,43961,43963,43965],{"class":120,"line":668},[118,43956,5431],{"class":226},[118,43958,230],{"class":124},[118,43960,1185],{"class":226},[118,43962,25],{"class":124},[118,43964,1190],{"class":213},[118,43966,1193],{"class":124},[118,43968,43969,43971,43973,43975,43977,43979,43981,43983,43985,43987,43989],{"class":120,"line":693},[118,43970,5494],{"class":226},[118,43972,25],{"class":124},[118,43974,358],{"class":213},[118,43976,328],{"class":124},[118,43978,363],{"class":331},[118,43980,334],{"class":124},[118,43982,337],{"class":128},[118,43984,25],{"class":124},[118,43986,372],{"class":128},[118,43988,345],{"class":124},[118,43990,220],{"class":124},[118,43992,43993,43995,43997,43999,44001,44003,44005,44007,44009,44011,44013,44015,44017,44019],{"class":120,"line":698},[118,43994,1737],{"class":226},[118,43996,25],{"class":124},[118,43998,387],{"class":213},[118,44000,255],{"class":124},[118,44002,20],{"class":299},[118,44004,395],{"class":124},[118,44006,398],{"class":124},[118,44008,401],{"class":331},[118,44010,334],{"class":124},[118,44012,337],{"class":128},[118,44014,25],{"class":124},[118,44016,410],{"class":128},[118,44018,345],{"class":124},[118,44020,220],{"class":124},[118,44022,44023,44025,44027,44029,44031,44033,44035,44037,44039,44041,44043,44045,44047,44049,44051,44053,44055,44057,44059,44061,44063,44065],{"class":120,"line":703},[118,44024,1768],{"class":226},[118,44026,25],{"class":124},[118,44028,425],{"class":213},[118,44030,255],{"class":124},[118,44032,430],{"class":124},[118,44034,39532],{"class":433},[118,44036,430],{"class":124},[118,44038,395],{"class":124},[118,44040,233],{"class":226},[118,44042,25],{"class":124},[118,44044,2926],{"class":213},[118,44046,255],{"class":124},[118,44048,430],{"class":124},[118,44050,32124],{"class":433},[118,44052,430],{"class":124},[118,44054,1280],{"class":124},[118,44056,233],{"class":226},[118,44058,25],{"class":124},[118,44060,455],{"class":213},[118,44062,255],{"class":124},[118,44064,33365],{"class":299},[118,44066,463],{"class":124},[118,44068,44069,44071,44073,44075,44077,44079,44082,44084],{"class":120,"line":709},[118,44070,1768],{"class":226},[118,44072,25],{"class":124},[118,44074,425],{"class":213},[118,44076,255],{"class":124},[118,44078,430],{"class":124},[118,44080,44081],{"class":433},"2026 年 4 月 16 日",[118,44083,430],{"class":124},[118,44085,199],{"class":124},[118,44087,44088],{"class":120,"line":714},[118,44089,574],{"class":124},[118,44091,44092],{"class":120,"line":740},[118,44093,706],{"class":124},[118,44095,44096,44098,44100,44102,44104,44106,44108,44110,44112,44114,44116],{"class":120,"line":765},[118,44097,5494],{"class":226},[118,44099,25],{"class":124},[118,44101,358],{"class":213},[118,44103,328],{"class":124},[118,44105,363],{"class":331},[118,44107,334],{"class":124},[118,44109,337],{"class":128},[118,44111,25],{"class":124},[118,44113,372],{"class":128},[118,44115,345],{"class":124},[118,44117,220],{"class":124},[118,44119,44120,44122,44124,44126,44128,44130,44132,44134,44136,44138,44140,44142,44144,44146],{"class":120,"line":796},[118,44121,1737],{"class":226},[118,44123,25],{"class":124},[118,44125,387],{"class":213},[118,44127,255],{"class":124},[118,44129,7559],{"class":299},[118,44131,395],{"class":124},[118,44133,398],{"class":124},[118,44135,401],{"class":331},[118,44137,334],{"class":124},[118,44139,337],{"class":128},[118,44141,25],{"class":124},[118,44143,410],{"class":128},[118,44145,345],{"class":124},[118,44147,220],{"class":124},[118,44149,44150,44152,44154,44156,44158,44160,44163,44165,44167,44169,44171,44173,44175,44178],{"class":120,"line":819},[118,44151,1768],{"class":226},[118,44153,25],{"class":124},[118,44155,425],{"class":213},[118,44157,255],{"class":124},[118,44159,430],{"class":124},[118,44161,44162],{"class":433},"株式会社 ABC 御中",[118,44164,430],{"class":124},[118,44166,395],{"class":124},[118,44168,233],{"class":226},[118,44170,25],{"class":124},[118,44172,455],{"class":213},[118,44174,255],{"class":124},[118,44176,44177],{"class":299},"13",[118,44179,463],{"class":124},[118,44181,44182,44184,44186,44188,44190,44192,44195,44197],{"class":120,"line":851},[118,44183,1768],{"class":226},[118,44185,25],{"class":124},[118,44187,425],{"class":213},[118,44189,255],{"class":124},[118,44191,430],{"class":124},[118,44193,44194],{"class":433},"〒 100-0001 東京都千代田区千代田 1-1",[118,44196,430],{"class":124},[118,44198,199],{"class":124},[118,44200,44201],{"class":120,"line":875},[118,44202,574],{"class":124},[118,44204,44205,44207,44209,44211,44213,44215,44217,44219,44221,44223,44225,44227,44229,44231],{"class":120,"line":880},[118,44206,1737],{"class":226},[118,44208,25],{"class":124},[118,44210,387],{"class":213},[118,44212,255],{"class":124},[118,44214,1311],{"class":299},[118,44216,395],{"class":124},[118,44218,398],{"class":124},[118,44220,401],{"class":331},[118,44222,334],{"class":124},[118,44224,337],{"class":128},[118,44226,25],{"class":124},[118,44228,410],{"class":128},[118,44230,345],{"class":124},[118,44232,220],{"class":124},[118,44234,44235,44237,44239,44241,44243,44245,44248,44250,44252,44254,44256,44258,44260,44262,44264,44266,44268,44270,44272,44274],{"class":120,"line":885},[118,44236,1768],{"class":226},[118,44238,25],{"class":124},[118,44240,425],{"class":213},[118,44242,255],{"class":124},[118,44244,430],{"class":124},[118,44246,44247],{"class":433},"合計 ¥ 128,000",[118,44249,430],{"class":124},[118,44251,395],{"class":124},[118,44253,233],{"class":226},[118,44255,25],{"class":124},[118,44257,2926],{"class":213},[118,44259,255],{"class":124},[118,44261,430],{"class":124},[118,44263,32124],{"class":433},[118,44265,430],{"class":124},[118,44267,1280],{"class":124},[118,44269,233],{"class":226},[118,44271,25],{"class":124},[118,44273,519],{"class":213},[118,44275,1289],{"class":124},[118,44277,44278,44280,44282,44284,44286,44288,44291,44293,44295,44297,44299,44301],{"class":120,"line":910},[118,44279,1768],{"class":226},[118,44281,25],{"class":124},[118,44283,425],{"class":213},[118,44285,255],{"class":124},[118,44287,430],{"class":124},[118,44289,44290],{"class":433},"支払期限: 2026-05-31",[118,44292,430],{"class":124},[118,44294,395],{"class":124},[118,44296,233],{"class":226},[118,44298,25],{"class":124},[118,44300,519],{"class":213},[118,44302,1289],{"class":124},[118,44304,44305],{"class":120,"line":941},[118,44306,574],{"class":124},[118,44308,44309],{"class":120,"line":974},[118,44310,706],{"class":124},[118,44312,44313],{"class":120,"line":997},[118,44314,136],{"emptyLinePlaceholder":135},[118,44316,44317,44319,44321,44323,44325,44327,44329,44331],{"class":120,"line":1002},[118,44318,5787],{"class":226},[118,44320,395],{"class":124},[118,44322,1391],{"class":226},[118,44324,230],{"class":124},[118,44326,1185],{"class":226},[118,44328,25],{"class":124},[118,44330,1400],{"class":213},[118,44332,1193],{"class":124},[118,44334,44335,44337,44339,44341,44343],{"class":120,"line":1033},[118,44336,1408],{"class":142},[118,44338,1391],{"class":226},[118,44340,1413],{"class":124},[118,44342,1416],{"class":124},[118,44344,220],{"class":124},[118,44346,44347,44349,44351,44353,44355,44357],{"class":120,"line":1065},[118,44348,5818],{"class":226},[118,44350,25],{"class":124},[118,44352,5823],{"class":213},[118,44354,255],{"class":124},[118,44356,1429],{"class":226},[118,44358,199],{"class":124},[118,44360,44361],{"class":120,"line":1088},[118,44362,1375],{"class":124},[118,44364,44365,44367,44369,44371,44373,44375,44377,44379,44381,44384,44386,44388,44390,44392,44394,44396,44398,44400,44402],{"class":120,"line":1093},[118,44366,1408],{"class":142},[118,44368,1391],{"class":226},[118,44370,230],{"class":124},[118,44372,1447],{"class":226},[118,44374,25],{"class":124},[118,44376,1452],{"class":213},[118,44378,255],{"class":124},[118,44380,430],{"class":124},[118,44382,44383],{"class":433},"invoice-ja.pdf",[118,44385,430],{"class":124},[118,44387,395],{"class":124},[118,44389,5859],{"class":226},[118,44391,395],{"class":124},[118,44393,1471],{"class":299},[118,44395,7902],{"class":124},[118,44397,1391],{"class":226},[118,44399,1413],{"class":124},[118,44401,1416],{"class":124},[118,44403,220],{"class":124},[118,44405,44406,44408,44410,44412,44414,44416],{"class":120,"line":1098},[118,44407,5818],{"class":226},[118,44409,25],{"class":124},[118,44411,5823],{"class":213},[118,44413,255],{"class":124},[118,44415,1429],{"class":226},[118,44417,199],{"class":124},[118,44419,44420],{"class":120,"line":1103},[118,44421,1375],{"class":124},[118,44423,44424],{"class":120,"line":1108},[118,44425,1479],{"class":124},[14,44427,44428],{},"Things to notice without me narrating them to death:",[46,44430,44431,44446,44455,44469],{},[49,44432,44433,4919,44440,44442,44443,44445],{},[1629,44434,5240,44435,44437,44438,25],{},[18,44436,2745],{},", no UTF-8 flag, no font path argument to ",[18,44439,425],{},[18,44441,43373],{}," registers a family; ",[18,44444,8551],{}," just writes Unicode. The plumbing is internal.",[49,44447,44448,44451,44452,44454],{},[1629,44449,44450],{},"Bold is a separate family, not a flag."," This matches how TTFs ship (Noto Sans JP Regular and Noto Sans JP Bold are distinct files with different ",[18,44453,20794],{}," tables). Gothic and Mincho variants, or Source Han Sans JP Normal/Heavy, follow the same pattern.",[49,44456,44457,4919,44460,54,44463,44466,44467,25],{},[1629,44458,44459],{},"Layout is grid, not cursor.",[18,44461,44462],{},"r.Col(7, ...)",[18,44464,44465],{},"r.Col(5, ...)"," add to 12. Widths are declarative; you don't compute x-coordinates. More on this in ",[3163,44468,6902],{"href":6901},[49,44470,44471,44476],{},[1629,44472,44473,44475],{},[18,44474,9497],{}," is locale-agnostic."," The Japanese \"¥ 128,000\" right-aligns the same way as \"$1,280.00\" would. The text content doesn't change the layout code.",[14,44478,44479,44480,44482,44483,44485],{},"Open the resulting ",[18,44481,44383],{}," in any reader. Select \"株式会社 ABC 御中\". Paste into a text editor. You get ",[18,44484,44162],{},", not a jumble. That's the ToUnicode CMap working; gpdf writes one by default.",[41,44487,44489],{"id":44488},"font-subsetting-the-hidden-size-bomb","Font subsetting: the hidden size bomb",[14,44491,44492,44493,25],{},"Here is the single most important property of CJK-in-PDF that tutorials skip: ",[1629,44494,44495],{},"subset embedding",[14,44497,44498],{},"A TTF font is a collection of glyph outlines plus metadata tables. Noto Sans JP Regular ships about 17,500 glyphs and weighs 5.1 MB. A typical invoice uses 60 to 200 unique Japanese characters. Embedding the full font on every document is an order-of-magnitude waste.",[14,44500,44501],{},"Subset embedding keeps only the glyphs you used. gpdf does this automatically. You can see it by running the example above and inspecting the output:",[109,44503,44505],{"className":3145,"code":44504,"language":3147,"meta":114,"style":114},"$ ls -l invoice-ja.pdf\n-rw-r--r--  1 dev  staff  34892 Apr 16 10:12 invoice-ja.pdf\n",[18,44506,44507,44520],{"__ignoreMap":114},[118,44508,44509,44511,44514,44517],{"class":120,"line":121},[118,44510,7539],{"class":128},[118,44512,44513],{"class":433}," ls",[118,44515,44516],{"class":433}," -l",[118,44518,44519],{"class":433}," invoice-ja.pdf\n",[118,44521,44522,44525,44528,44531,44534,44537,44540,44542,44545],{"class":120,"line":132},[118,44523,44524],{"class":128},"-rw-r--r--",[118,44526,44527],{"class":299},"  1",[118,44529,44530],{"class":433}," dev",[118,44532,44533],{"class":433},"  staff",[118,44535,44536],{"class":299},"  34892",[118,44538,44539],{"class":433}," Apr",[118,44541,42495],{"class":299},[118,44543,44544],{"class":433}," 10:12",[118,44546,44519],{"class":433},[14,44548,44549,44550,54,44552,44555,44556,44559],{},"34 KB. For comparison, the same document generated with ",[18,44551,1557],{},[18,44553,44554],{},"AddUTF8Font(\"NotoSansJP\", \"NotoSansJP-Regular.ttf\", true)"," — where the third argument is the UTF-8 flag — is ",[1629,44557,44558],{},"4.9 MB",". Same input, same output text, 143× larger file. The reason is the fpdf code path embeds the entire font table rather than subsetting it at emit time.",[14,44561,44562],{},"A few consequences worth naming:",[46,44564,44565,44576,44582],{},[49,44566,44567,44568,44571,44572,44575],{},"At ",[1629,44569,44570],{},"10 invoices per second"," (a normal SaaS scale), the subsetting difference is the difference between ",[1629,44573,44574],{},"0.3 MB/s and 43 MB/s"," of outbound PDF bytes. Your load balancer has an opinion on that.",[49,44577,44578,44581],{},[1629,44579,44580],{},"Cold storage bills"," scale linearly with PDF size. Five million archived invoices at 5 MB each is 25 TB. At 30 KB each, it's 150 GB. Object storage pricing makes this a four-figures-versus-two-figures monthly line item.",[49,44583,44584,44587],{},[1629,44585,44586],{},"Email delivery"," has 10–25 MB attachment limits depending on provider. A 5 MB Japanese invoice plus any other attachment plus MIME encoding starts bumping into that ceiling.",[14,44589,44590,44591,3096,44594,3096,44597,9386,44600,44603],{},"gpdf subsets at render time. There's no flag to turn it on. You can see which glyphs ended up in the output by running gpdf's verification tool locally, but the short version is: if you used ",[18,44592,44593],{},"株",[18,44595,44596],{},"式",[18,44598,44599],{},"会",[18,44601,44602],{},"社",", those four glyphs are in the output and the other 17,496 are not.",[41,44605,44607],{"id":44606},"mixed-scripts-kanji-kana-ascii-on-one-line","Mixed scripts: kanji + kana + ASCII on one line",[14,44609,44610],{},"Japanese text is rarely Japanese-only. A real-world line in a Japanese document looks like this:",[109,44612,44615],{"className":44613,"code":44614,"language":4993},[34105],"API の P95 レイテンシは 50 ms 未満です。\n",[18,44616,44614],{"__ignoreMap":114},[14,44618,44619],{},"That's five scripts: romaji (ASCII Latin), katakana, hiragana, kanji (Han), and numerals. A naive implementation picks the wrong font for the ASCII parts and you end up with a monospaced \"API\" next to proportional Japanese, which looks terrible.",[14,44621,44622,44623,44626,44627,54,44630,44633],{},"gpdf's default behavior is to render ",[1629,44624,44625],{},"every code point in the registered family",". If Noto Sans JP is your default, ",[18,44628,44629],{},"API",[18,44631,44632],{},"50 ms"," are drawn with Noto Sans JP's Latin glyphs, which Noto provides (most Japanese superfamilies do). The result looks like a single typeface, because it is.",[14,44635,44636],{},"If you want to mix families deliberately — say, use a condensed sans for ASCII and Noto Sans JP for Japanese — register both and override per-text-call:",[109,44638,44640],{"className":111,"code":44639,"language":113,"meta":114,"style":114},"c.Text(\"API の P95 レイテンシは 50 ms 未満です。\",\n    template.FontFamily(\"NotoSansJP\"))\nc.Text(\"API latency (P95) is under 50 ms.\",\n    template.FontFamily(\"InterVariable\"))\n",[18,44641,44642,44661,44679,44698],{"__ignoreMap":114},[118,44643,44644,44646,44648,44650,44652,44654,44657,44659],{"class":120,"line":121},[118,44645,401],{"class":226},[118,44647,25],{"class":124},[118,44649,425],{"class":213},[118,44651,255],{"class":124},[118,44653,430],{"class":124},[118,44655,44656],{"class":433},"API の P95 レイテンシは 50 ms 未満です。",[118,44658,430],{"class":124},[118,44660,2643],{"class":124},[118,44662,44663,44665,44667,44669,44671,44673,44675,44677],{"class":120,"line":132},[118,44664,2775],{"class":226},[118,44666,25],{"class":124},[118,44668,2926],{"class":213},[118,44670,255],{"class":124},[118,44672,430],{"class":124},[118,44674,2805],{"class":433},[118,44676,430],{"class":124},[118,44678,463],{"class":124},[118,44680,44681,44683,44685,44687,44689,44691,44694,44696],{"class":120,"line":139},[118,44682,401],{"class":226},[118,44684,25],{"class":124},[118,44686,425],{"class":213},[118,44688,255],{"class":124},[118,44690,430],{"class":124},[118,44692,44693],{"class":433},"API latency (P95) is under 50 ms.",[118,44695,430],{"class":124},[118,44697,2643],{"class":124},[118,44699,44700,44702,44704,44706,44708,44710,44713,44715],{"class":120,"line":149},[118,44701,2775],{"class":226},[118,44703,25],{"class":124},[118,44705,2926],{"class":213},[118,44707,255],{"class":124},[118,44709,430],{"class":124},[118,44711,44712],{"class":433},"InterVariable",[118,44714,430],{"class":124},[118,44716,463],{"class":124},[14,44718,44719,44720,44722,44723,44726],{},"Two ",[18,44721,8551],{}," calls, two families, no script-detection logic in your code. If you need intra-line mixing (ASCII Inter + Japanese Noto in the ",[4744,44724,44725],{},"same"," sentence), that's coming in gpdf v1.2; today the workaround is to split at script boundaries manually and lay out with a horizontal row of columns.",[41,44728,44730],{"id":44729},"what-still-hurts","What still hurts",[14,44732,44733],{},"The Japanese PDF story in Go is 95% solved. Here's the 5%.",[14,44735,44736,44739,44740,25],{},[1629,44737,44738],{},"Vertical text (縦書き) is not there yet."," gpdf renders horizontal text only in v1.x. Traditional Japanese layout — right-to-left columns of top-to-bottom characters with the appropriate glyph rotation and punctuation repositioning — is a deep layout engine change, not a rendering tweak. The open issue has a proposed design; it'll land when it lands. For now, if you need 縦書き for books or formal correspondence, generate with a tool that supports it (Word, InDesign, or a pandoc + LuaLaTeX pipeline) and embed the output PDF with ",[18,44741,44742],{},"gpdf.Merge",[14,44744,44745,37865,44748,44751],{},[1629,44746,44747],{},"Ruby annotations (振り仮名) are workaround-only.",[18,44749,44750],{},"c.Ruby(\"漢字\", \"かんじ\")"," primitive. If you need ruby for children's content or language textbooks, the workaround is a two-row column: small kana text on top, regular kanji below, aligned. It works, but it's manual, and fine kerning across furigana boundaries takes care.",[14,44753,44754,44757,44758,3096,44761,3096,44764,44767,44768,44770,44771,44775],{},[1629,44755,44756],{},"Complex fallbacks across multiple CJK fonts."," If a user submits text that mixes Japanese kanji with Chinese-only characters (the character forms differ — ",[18,44759,44760],{},"直",[18,44762,44763],{},"骨",[18,44765,44766],{},"角"," render subtly differently in CN vs JP), you need to manually split and use two families. gpdf doesn't auto-fall-back across families within a single ",[18,44769,8551],{}," call. In practice, very few documents need this; if yours does, see ",[3163,44772,44774],{"href":44773},"/blog/","Multi-language PDFs: mixing JP/CN/KR/EN",". (Article pending — B-070.)",[14,44777,44778,44781,44782,44785,44786,44789],{},[1629,44779,44780],{},"PDF/A-2b strict compliance with Japanese."," gpdf produces PDF/A output via ",[18,44783,44784],{},"gpdf.WithPDFA",", but the tight compliance requirements around embedded glyph metadata, the ",[18,44787,44788],{},"ActualText"," span on every CJK run, and tagged structure trees are still being ironed out for the CJK case. If you're exporting for long-term archival under 電子帳簿保存法, validate with a third-party tool (veraPDF is free) before committing.",[14,44791,44792],{},"None of these are blockers for the common cases: invoices, reports, statements, receipts, certificates. They're worth naming because somebody reading this is about to hit one of them in production, and \"it's on the roadmap\" is less useful than \"here's the workaround.\"",[41,44794,44796],{"id":44795},"a-word-on-compliance","A word on compliance",[14,44798,44799,44800,44803],{},"One piece of ecosystem context that usually goes unsaid: ",[1629,44801,44802],{},"Japanese PDF generation in 2026 is not just a typography problem."," Two regulatory shifts push it into the compliance conversation.",[14,44805,8198,44806,44809],{},[1629,44807,44808],{},"適格請求書 (qualified invoice) regime"," under the consumption-tax reform requires invoices to include specific fields (registered business number, applicable tax rate, breakdown) and to be retained in a tamper-evident way. PDFs are the default format for this, and \"tamper-evident\" maps to PDF digital signatures — PAdES-B-LT in the strict case.",[14,44811,8198,44812,44815],{},[1629,44813,44814],{},"電子帳簿保存法 (e-book storage act)",", revised in 2024, extended retention mandates to include invoices stored in electronic form. Archived PDFs must meet certain integrity requirements. PDF/A-2b or PDF/A-3b is the de-facto target format.",[14,44817,44818,44819,44822,44823,44826,44827,44829],{},"Both requirements lean on ",[1629,44820,44821],{},"native PDF features"," — signatures, long-term validation, PDF/A embedded metadata. HTML-to-PDF via headless browser does not meet either cleanly; Chromium's PDF output isn't PDF/A-compliant and can't embed digital signatures in a single step. A native Go stack (gpdf + ",[18,44824,44825],{},"gpdf/signature"," for PAdES + ",[18,44828,44784],{},") does the whole chain in one pipeline without leaving the process.",[14,44831,44832],{},"This is a future-topic flag rather than a deep dive — signature and PDF/A each deserve their own hero article (they're B-067 and B-068 on the backlog). But if you're choosing a Japanese PDF stack today and compliance is anywhere on your radar, pick a stack that can do signatures and PDF/A natively. The migration tax from \"works today\" to \"passes audit\" is real.",[41,44834,3054],{"id":3053},[14,44836,44837,44840,44841,12386,44844,44847,44848,4500,44851,44854,44855,44857],{},[1629,44838,44839],{},"Do I need to install fonts on the server or in the container?","\nNo. gpdf reads TTF bytes; it doesn't go through the system font cache. ",[18,44842,44843],{},"os.ReadFile(\"NotoSansJP-Regular.ttf\")",[18,44845,44846],{},"//go:embed NotoSansJP-Regular.ttf"," works identically on macOS, Linux, and Windows, inside a distroless container, and on AWS Lambda. No ",[18,44849,44850],{},"fontconfig",[18,44852,44853],{},"fc-cache -fv",". This is one of the reasons gpdf works in ",[18,44856,4728],{}," images.",[14,44859,44860,44863],{},[1629,44861,44862],{},"Noto Sans JP vs Source Han Sans JP — does it matter?","\nThey're the same font family under two names. Adobe publishes Source Han Sans JP; Google repackages it as Noto Sans JP. Glyph coverage is identical. Pick whichever license distribution fits your legal review; both are SIL Open Font License. For brand-neutral documentation we default to Noto Sans JP because the file names are easier to remember.",[14,44865,44866,44869],{},[1629,44867,44868],{},"What about 游ゴシック (Yu Gothic) or Hiragino?","\nThose are OS-bundled proprietary fonts. You can use them if your deployment target has licensed them (Windows Server bundles Yu Gothic; macOS bundles Hiragino), but you'll need to source the TTF file and confirm redistribution terms for your container build. For open deployments, stick with Noto Sans JP or IPAex Gothic (both free redistribution).",[14,44871,44872,44879,44880,44883],{},[1629,44873,44874,44875,44878],{},"The PDF renders but ",[18,44876,44877],{},"Ctrl+F"," search finds nothing. Why?","\nAlmost always a ToUnicode CMap issue. gpdf writes one automatically, so if you're seeing this with gpdf, open an issue with the reader name. If you're seeing it with gofpdf, the fix is to enable the UTF-8 flag ",[4744,44881,44882],{},"and"," ensure the reader supports CID fonts — old Preview.app versions on macOS have known issues. Test with Adobe Reader or Chrome as a control.",[14,44885,44886,44889,44890,44893],{},[1629,44887,44888],{},"How do I add a JIS X 0213 character that's not in the font?","\nYou don't — there's no glyph to draw. The practical answer is \"use a font that covers JIS X 0213.\" Noto Sans JP covers the full BMP plus JIS X 0213 Level 1. For rare historical variants, Hanazono Mincho (花園明朝) is the last-mile fallback. If a code point isn't in any font, gpdf emits the Unicode replacement character (U+FFFD) rather than a silent tofu — so you'll see ",[18,44891,44892],{},"�"," in the output and know to check.",[14,44895,44896,44899,44900,44902],{},[1629,44897,44898],{},"Is there a performance cost to CJK vs ASCII?","\nSmall. gpdf's benchmark for a \"complex CJK invoice\" is 133 µs per document on an Apple M1, vs 108 µs for a 4×10 ASCII line-items table. That's a ~23% overhead, almost entirely from the larger glyph-lookup and subsetting work. For reference, ",[18,44901,1557],{}," on the same CJK benchmark is 254 µs, and Maroto v2 is 10.4 ms. Japanese rendering isn't the bottleneck in your service.",[41,44904,4794],{"id":4793},[14,44906,44907],{},"gpdf is a Go library for generating PDFs. MIT, zero external dependencies, native CJK.",[109,44909,44910],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,44911,44912],{"__ignoreMap":114},[118,44913,44914,44916,44918],{"class":120,"line":121},[118,44915,113],{"class":128},[118,44917,3156],{"class":433},[118,44919,3159],{"class":433},[14,44921,44922,3169,44925],{},[3163,44923,3168],{"href":3165,"rel":44924},[3167],[3163,44926,3174],{"href":3172,"rel":44927},[3167],[41,44929,4821],{"id":4820},[46,44931,44932,44937,44942,44947],{},[49,44933,44934,44936],{},[3163,44935,9792],{"href":9791}," — the three-line recipe without the background",[49,44938,44939,44941],{},[3163,44940,33001],{"href":33000}," — Regular / Bold / Medium weight setup",[49,44943,44944,44946],{},[3163,44945,6902],{"href":6901}," — the layout idiom that replaces cursor math",[49,44948,44949,44951],{},[3163,44950,41668],{"href":43352}," — the broader 2026 landscape",[3176,44953,36541],{},{"title":114,"searchDepth":132,"depth":132,"links":44955},[44956,44957,44958,44959,44960,44961,44962,44963,44964,44965,44966,44967],{"id":43,"depth":132,"text":44},{"id":43383,"depth":132,"text":43384},{"id":43405,"depth":132,"text":43406},{"id":43472,"depth":132,"text":43473},{"id":43607,"depth":132,"text":43608},{"id":44488,"depth":132,"text":44489},{"id":44606,"depth":132,"text":44607},{"id":44729,"depth":132,"text":44730},{"id":44795,"depth":132,"text":44796},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"How to generate Japanese PDFs in Go in 2026 — fonts, TrueType subsets, mixed kanji/kana/ASCII, and why CGO and Chromium are unnecessary.",{"name":44970,"totalTime":12928,"tools":44971,"steps":44973},"Generate a Japanese PDF in Go with native TrueType subsetting",[3202,44972],"NotoSansJP-Regular.ttf and NotoSansJP-Bold.ttf (or any CJK-capable TTF pair)",[44974,44977,44980,44983,44986,44989],{"name":44975,"text":44976},"Install gpdf and fetch the fonts","Run go get github.com/gpdf-dev/gpdf. Download Noto Sans JP Regular and Bold from Google Fonts and drop the TTF files next to main.go. No CGO, no system font setup.",{"name":44978,"text":44979},"Load the TTF bytes at startup","Read both TTF files with os.ReadFile into []byte buffers. //go:embed works if you want the fonts compiled into the binary for single-file deployment.",{"name":44981,"text":44982},"Register the fonts at document construction","Pass gpdf.WithFont(\"NotoSansJP\", regular) and gpdf.WithFont(\"NotoSansJP-Bold\", bold) to gpdf.NewDocument. The family name is arbitrary — it's the handle you reference later.",{"name":44984,"text":44985},"Set the Japanese font as default","Add gpdf.WithDefaultFont(\"NotoSansJP\", 11). Every c.Text call then picks up the Japanese font without an explicit FontFamily option.",{"name":44987,"text":44988},"Write the document tree with c.Text","Inside a page.AutoRow block, call r.Col(span, fn) and c.Text(\"こんにちは、世界。\"). Bold and size are template options, not separate methods.",{"name":44990,"text":44991},"Generate and inspect the output","Call doc.Generate() to get []byte, write to disk with os.WriteFile. Open the PDF, select the text, paste it into a text editor — the ToUnicode CMap guarantees copy-paste works.",{},{"title":43359,"description":44968},"blog/007.japanese-pdf-in-go",[3226,9860,39151],"aNkV491aPdo_bx8JSeCMJkNC3ZG1ntAE00Knj97RoH4",{"id":44998,"title":4790,"author":44999,"body":45000,"date":46550,"description":46551,"draft":3196,"extension":3197,"howTo":3220,"image":3220,"meta":46552,"navigation":135,"path":3381,"seo":46553,"stem":46554,"tags":46555,"updated":3220,"__hash__":46556},"blog/blog/002.go-pdf-library-showdown-2026.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":45001,"toc":46532},[45002,45004,45010,45030,45033,45039,45043,45046,45076,45079,45082,45086,45264,45267,45271,45283,45286,45312,45318,45322,45406,45414,45418,45425,45430,45436,45442,45448,45452,45461,45538,45541,45545,45599,45602,45607,45611,45648,45652,45655,45696,45699,45703,45706,46382,46398,46402,46405,46443,46445,46451,46461,46467,46473,46482,46484,46486,46498,46506,46508,46530],[41,45003,44],{"id":43},[14,45005,45006,45007,45009],{},"Five years ago, the Go PDF library search landed you on ",[1629,45008,35345],{},". Today it's archived. So is its community fork. What's left is a much smaller field than the search results suggest:",[46,45011,45012,45018,45024],{},[49,45013,45014,45017],{},[1629,45015,45016],{},"Maintained and actively developed:"," gpdf (this team), signintech/gopdf, johnfercher/maroto v2 — but Maroto still depends on an archived gofpdf.",[49,45019,45020,45023],{},[1629,45021,45022],{},"Archived:"," jung-kurt/gofpdf (2021), go-pdf/fpdf (2025).",[49,45025,45026,45029],{},[1629,45027,45028],{},"Commercial / AGPL:"," unidoc/unipdf.",[14,45031,45032],{},"This post benchmarks the maintained libraries on four workloads, lays out licenses and dependency graphs, and makes a recommendation by use case. We run it again next year.",[14,45034,45035,45036,45038],{},"Bias disclosure: we ship gpdf. The benchmark code is public (",[18,45037,27100],{},") — clone it, re-run the numbers, tell us where we're wrong.",[41,45040,45042],{"id":45041},"what-were-actually-comparing","What we're actually comparing",[14,45044,45045],{},"The phrase \"Go PDF library\" covers at least three different tools pretending to be the same category:",[1624,45047,45048,45058,45068],{},[49,45049,45050,45053,45054,3096,45056,25],{},[1629,45051,45052],{},"Low-level PDF writers"," — you push bytes and draw with primitives. ",[18,45055,35345],{},[18,45057,1565],{},[49,45059,45060,45063,45064,3096,45066,25],{},[1629,45061,45062],{},"Layout libraries that wrap a writer"," — declarative rows and columns on top. ",[18,45065,12349],{},[18,45067,1587],{},[49,45069,45070,45073,45074,25],{},[1629,45071,45072],{},"Full document suites"," — parsing, signing, PDF/A, OCR, redaction. ",[18,45075,12369],{},[14,45077,45078],{},"Picking \"the best Go PDF library\" without saying which category you need is how most recommendation threads on Reddit go sideways. We try to keep the distinction visible in each comparison below.",[14,45080,45081],{},"Missing from the lineup: anything that shells out to a headless Chromium (go-rod, chromedp). Those aren't PDF libraries; they're browsers that happen to print. Great for fidelity on CSS-heavy designs, bad for cold start, bad for memory, bad for distroless. If your spec is \"our designer hands me HTML+CSS and wants pixel-perfect rendering,\" those tools exist and we're not competing with them in this post.",[41,45083,45085],{"id":45084},"the-scoreboard","The scoreboard",[1516,45087,45088,45114],{},[1519,45089,45090],{},[1522,45091,45092,45094,45097,45100,45102,45105,45108,45111],{},[1525,45093,1527],{},[1525,45095,45096],{},"Last release",[1525,45098,45099],{},"Archived",[1525,45101,3286],{},[1525,45103,45104],{},"Core deps",[1525,45106,45107],{},"CJK",[1525,45109,45110],{},"Layout grid",[1525,45112,45113],{},"2026 status",[1532,45115,45116,45146,45165,45189,45216,45241],{},[1522,45117,45118,45123,45126,45129,45131,45135,45138,45141],{},[1537,45119,45120,45122],{},[1629,45121,1587],{}," (this team)",[1537,45124,45125],{},"active",[1537,45127,45128],{},"—",[1537,45130,3375],{},[1537,45132,45133],{},[1629,45134,4159],{},[1537,45136,45137],{},"native",[1537,45139,45140],{},"12-col",[1537,45142,45143],{},[1629,45144,45145],{},"maintained",[1522,45147,45148,45150,45152,45154,45156,45158,45161,45163],{},[1537,45149,1565],{},[1537,45151,45125],{},[1537,45153,45128],{},[1537,45155,3375],{},[1537,45157,4159],{},[1537,45159,45160],{},"manual TTF",[1537,45162,20237],{},[1537,45164,45145],{},[1522,45166,45167,45169,45171,45173,45175,45180,45183,45186],{},[1537,45168,12349],{},[1537,45170,45125],{},[1537,45172,45128],{},[1537,45174,3375],{},[1537,45176,45177],{},[1629,45178,45179],{},"gofpdf (archived)",[1537,45181,45182],{},"via gofpdf",[1537,45184,45185],{},"row/col",[1537,45187,45188],{},"maintained on a dead base",[1522,45190,45191,45193,45196,45201,45203,45205,45209,45211],{},[1537,45192,35345],{},[1537,45194,45195],{},"2021",[1537,45197,45198],{},[1629,45199,45200],{},"2021-09-08",[1537,45202,3375],{},[1537,45204,4159],{},[1537,45206,45207],{},[18,45208,2745],{},[1537,45210,20237],{},[1537,45212,45213],{},[1629,45214,45215],{},"archived",[1522,45217,45218,45220,45223,45227,45229,45231,45235,45237],{},[1537,45219,1557],{},[1537,45221,45222],{},"2023",[1537,45224,45225],{},[1629,45226,41693],{},[1537,45228,3375],{},[1537,45230,4159],{},[1537,45232,45233],{},[18,45234,2745],{},[1537,45236,20237],{},[1537,45238,45239],{},[1629,45240,45215],{},[1522,45242,45243,45245,45247,45249,45254,45257,45259,45261],{},[1537,45244,12369],{},[1537,45246,45125],{},[1537,45248,45128],{},[1537,45250,45251],{},[1629,45252,45253],{},"AGPL-3.0 / Commercial",[1537,45255,45256],{},"many",[1537,45258,20249],{},[1537,45260,20237],{},[1537,45262,45263],{},"commercial",[14,45265,45266],{},"Three things to notice. One: half the field is archived. Two: Maroto is maintained, but its foundation isn't — that's a supply-chain problem even if it builds today. Three: if you aren't willing to accept the AGPL, unidoc becomes a commercial-license conversation, not a technical one.",[41,45268,45270],{"id":45269},"the-benchmark","The benchmark",[14,45272,45273,45274,45279,45280,45282],{},"Code: ",[3163,45275,45277],{"href":4171,"rel":45276},[3167],[18,45278,27100],{}," in the gpdf repo. Environment: Apple M1 (Max, 32 GB, macOS 14.5), Go 1.25, no CGO. Each case runs for at least five seconds wall time. ",[18,45281,36655],{}," was on; we report ns/op and allocations.",[14,45284,45285],{},"We picked the four cases below because they reflect what people actually generate, not something micro-synthetic:",[1624,45287,45288,45294,45300,45306],{},[49,45289,45290,45293],{},[1629,45291,45292],{},"Single page hello world."," One page, one line of text, one font. Sets the per-document overhead floor.",[49,45295,45296,45299],{},[1629,45297,45298],{},"4×10 invoice table."," A header row, ten body rows, column alignments, thin borders. The \"generate my invoice\" shape.",[49,45301,45302,45305],{},[1629,45303,45304],{},"100-page paginated report."," Repeating header, footer, page numbers, body text on each page. Measures pagination cost.",[49,45307,45308,45311],{},[1629,45309,45310],{},"Complex CJK invoice."," Mixed Japanese (Hiragana, Katakana, Kanji), a 4×15 line-items table, header, footer with page numbers, embedded NotoSansJP TrueType subset.",[14,45313,45314,45315,45317],{},"Not included: ",[18,45316,12369],{},". Its binary is license-gated, and reproducing its published benchmark methodology inside a public benchmark repo would be misleading without the same license terms. If you're evaluating unidoc, run its own benchmarks — they publish them.",[1613,45319,45321],{"id":45320},"results","Results",[1516,45323,45324,45340],{},[1519,45325,45326],{},[1522,45327,45328,45330,45332,45334,45336,45338],{},[1525,45329,4093],{},[1525,45331,1587],{},[1525,45333,1565],{},[1525,45335,17700],{},[1525,45337,1539],{},[1525,45339,1557],{},[1532,45341,45342,45358,45374,45390],{},[1522,45343,45344,45346,45350,45352,45354,45356],{},[1537,45345,36681],{},[1537,45347,45348],{},[1629,45349,3248],{},[1537,45351,17714],{},[1537,45353,17720],{},[1537,45355,17717],{},[1537,45357,36690],{},[1522,45359,45360,45362,45366,45368,45370,45372],{},[1537,45361,17725],{},[1537,45363,45364],{},[1629,45365,16053],{},[1537,45367,17732],{},[1537,45369,36712],{},[1537,45371,17735],{},[1537,45373,36707],{},[1522,45375,45376,45378,45382,45384,45386,45388],{},[1537,45377,4126],{},[1537,45379,45380],{},[1629,45381,4131],{},[1537,45383,36712],{},[1537,45385,36731],{},[1537,45387,36723],{},[1537,45389,36726],{},[1522,45391,45392,45394,45398,45400,45402,45404],{},[1537,45393,17760],{},[1537,45395,45396],{},[1629,45397,17765],{},[1537,45399,17768],{},[1537,45401,36749],{},[1537,45403,17771],{},[1537,45405,36744],{},[14,45407,45408,45410,45411,45413],{},[18,45409,36744],{}," for go-pdf/fpdf on the CJK case: the default ",[18,45412,2745],{}," path panics on NotoSansJP's cmap format-12 table in the version we tested. Fixable with a patch, but the fork is archived — nobody's shipping the fix.",[1613,45415,45417],{"id":45416},"reading-the-numbers","Reading the numbers",[14,45419,45420,45421,45424],{},"The order is stable across workloads. gpdf is ",[1629,45422,45423],{},"10–30× faster"," than the second-fastest library on every case we tested, which is neither exotic nor accidental. Three design choices compound:",[14,45426,45427,45429],{},[1629,45428,36613],{}," gpdf doesn't build an intermediate AST and then serialize it. Builders write to the PDF content stream directly as they resolve, which eliminates roughly half the allocations the other libraries do. This is what moves the needle on the 100-page benchmark, where allocation pressure hits the GC hardest — 683 µs versus 19,800 µs isn't a tuning difference, it's a different architecture.",[14,45431,45432,45435],{},[1629,45433,45434],{},"No reflection on the hot path."," Every type the layout engine touches is concrete. This sounds like micro-optimization — and individually, it is — but compounded over a 100-page report, interface dispatch starts to show up in profiles. We kept it out.",[14,45437,45438,45441],{},[1629,45439,45440],{},"TrueType subsetter that doesn't re-walk on every operation."," gofpdf's font machinery re-reads the cmap table on every glyph lookup; gpdf resolves once and caches. For Latin-only content this barely matters. For CJK, where a single paragraph might touch 150 unique glyphs across Kanji, Hiragana, Katakana, and punctuation, it's the gap between \"fast enough for synchronous generation\" and \"push to a queue.\"",[14,45443,45444,45445,45447],{},"A caveat we'll volunteer that the benchmark table won't: ",[1629,45446,36768],{},". The interesting threshold is \"fast enough to generate on the request path.\" Every library in this comparison clears that threshold for a single hello-world page. Only gpdf clears it for a 100-page report with repeating chrome. If your biggest document is a one-page receipt, all four maintained libraries are fine; pick by API ergonomics and license instead.",[41,45449,45451],{"id":45450},"dependencies","Dependencies",[14,45453,45454,45455,45457,45458,45460],{},"What ",[18,45456,42082],{}," prints after a fresh ",[18,45459,27085],{}," of each:",[1516,45462,45463,45475],{},[1519,45464,45465],{},[1522,45466,45467,45469,45472],{},[1525,45468,1527],{},[1525,45470,45471],{},"External modules",[1525,45473,45474],{},"Transitive archived deps",[1532,45476,45477,45490,45498,45508,45516,45528],{},[1522,45478,45479,45484,45488],{},[1537,45480,45481,45483],{},[1629,45482,1587],{}," (core)",[1537,45485,45486],{},[1629,45487,4159],{},[1537,45489,45128],{},[1522,45491,45492,45494,45496],{},[1537,45493,1565],{},[1537,45495,4159],{},[1537,45497,45128],{},[1522,45499,45500,45502,45505],{},[1537,45501,1539],{},[1537,45503,45504],{},"0 (but itself is archived)",[1537,45506,45507],{},"itself",[1522,45509,45510,45512,45514],{},[1537,45511,1557],{},[1537,45513,45504],{},[1537,45515,45507],{},[1522,45517,45518,45520,45525],{},[1537,45519,12349],{},[1537,45521,45522],{},[1629,45523,45524],{},"gofpdf (archived 2021)",[1537,45526,45527],{},"yes — gofpdf",[1522,45529,45530,45532,45535],{},[1537,45531,12369],{},[1537,45533,45534],{},"many (imaging, crypto, compression)",[1537,45536,45537],{},"none archived",[14,45539,45540],{},"For teams with a \"no archived deps in production\" lint rule, Maroto v2 today fails it via its gofpdf transitive. The Maroto maintainers have been rewriting the backend off gofpdf for over a year; when that ships, this row changes. Worth checking the Maroto repo before making a decision on this basis — the state may have moved between when we wrote this and when you're reading it.",[41,45542,45544],{"id":45543},"licensing","Licensing",[1516,45546,45547,45555],{},[1519,45548,45549],{},[1522,45550,45551,45553],{},[1525,45552,1527],{},[1525,45554,3286],{},[1532,45556,45557,45564,45570,45576,45582,45588],{},[1522,45558,45559,45562],{},[1537,45560,45561],{},"gpdf (core)",[1537,45563,3375],{},[1522,45565,45566,45568],{},[1537,45567,1565],{},[1537,45569,3375],{},[1522,45571,45572,45574],{},[1537,45573,12349],{},[1537,45575,3375],{},[1522,45577,45578,45580],{},[1537,45579,1539],{},[1537,45581,3375],{},[1522,45583,45584,45586],{},[1537,45585,1557],{},[1537,45587,3375],{},[1522,45589,45590,45594],{},[1537,45591,45592],{},[1629,45593,12369],{},[1537,45595,45596],{},[1629,45597,45598],{},"AGPL-3.0 or commercial license",[14,45600,45601],{},"Unidoc's AGPL terms are strict. If you use it in a server that users interact with over a network, your server code has to be released under AGPL too — a non-starter for most closed-source SaaS. That usually leaves the commercial license as the only real option, and their pricing isn't published publicly: plan on a conversation with sales.",[14,45603,45604,45605,25],{},"This is the single biggest thing people miss in GitHub-star-count comparisons. Unidoc has the most features and the most stars. It also has a license that closes the door on most commercial use cases without a purchase. We don't say this as a dig at unidoc — their model is a legitimate one and the product is excellent — but you should know before ",[18,45606,27085],{},[41,45608,45610],{"id":45609},"maintenance-status-plain-english-edition","Maintenance status, plain-English edition",[46,45612,45613,45618,45623,45628,45636,45643],{},[49,45614,45615,45617],{},[1629,45616,1587],{}," — primary maintainer is this team (gpdf-dev). Active releases every 2–4 weeks; roadmap in the repo; CI runs on Go 1.22 through 1.26; issue response within a few business days on the main repo. We have skin in the game.",[49,45619,45620,45622],{},[1629,45621,1565],{}," — active with a smaller commit cadence. Issues get attention; PRs tend to merge within a few weeks. Primary use case remains low-level generation.",[49,45624,45625,45627],{},[1629,45626,17700],{}," — active. The v2 rewrite landed in 2023 and is stable. The gofpdf dependency is known and the team is working on replacing it; check the repo for current state before committing.",[49,45629,45630,45632,45633,45635],{},[1629,45631,1539],{}," — archived 2021-09-08. Banner on the repo: ",[4744,45634,41711],{}," No security patches, no bug fixes.",[49,45637,45638,45640,45641,25],{},[1629,45639,1557],{}," — archived in 2025. The README now recommends using a different library. We wrote a dedicated migration guide: ",[3163,45642,18018],{"href":4839},[49,45644,45645,45647],{},[1629,45646,12369],{}," — active, commercial team, well-resourced. Enterprise support is available.",[41,45649,45651],{"id":45650},"how-to-pick","How to pick",[14,45653,45654],{},"We're going to try an actual decision tree rather than a feature matrix, because \"most features\" isn't usually the right question:",[46,45656,45657,45663,45669,45675,45681,45690],{},[49,45658,45659,45662],{},[1629,45660,45661],{},"\"I have a Go codebase that generates invoices, reports, or documents, I want MIT, zero deps, and my documents sometimes contain CJK.\""," → gpdf.",[49,45664,45665,45668],{},[1629,45666,45667],{},"\"I'm doing low-level PDF generation with custom geometry and I want a small, stable, hand-on-the-wheel library.\""," → signintech/gopdf.",[49,45670,45671,45674],{},[1629,45672,45673],{},"\"I already have Maroto-flavored layout code that works today.\""," → stay on Maroto v2 until the gofpdf-removal lands, then re-evaluate. The API isn't the problem.",[49,45676,45677,45680],{},[1629,45678,45679],{},"\"I need PDF/A, OCR, redaction, digital signatures, and my employer will pay for a commercial license.\""," → unidoc/unipdf, with the license conversation up front.",[49,45682,45683,45686,45687],{},[1629,45684,45685],{},"\"I'm still on gofpdf and it works.\""," → fine today. Plan the migration before the next CVE lands in an unrelated dependency and you're stuck on unmaintained code. ",[3163,45688,45689],{"href":4839},"Migration guide here.",[49,45691,45692,45695],{},[1629,45693,45694],{},"\"Pixel-perfect HTML/CSS to PDF rendering.\""," → none of the above. Use go-rod or chromedp with a headless Chromium, and plan for the cold-start cost.",[14,45697,45698],{},"We ship gpdf, so of course we think gpdf is the right default for the first bucket and most of the fifth. Read the benchmark code, run it locally, don't take the table at face value.",[41,45700,45702],{"id":45701},"a-30-line-gpdf-example","A 30-line gpdf example",[14,45704,45705],{},"Because \"fastest\" and \"smallest dep graph\" only matter if the code is bearable to read. This is a complete, runnable invoice page — no pseudo-code, no missing imports:",[109,45707,45709],{"className":111,"code":45708,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\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 := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"INVOICE #2026-0042\", template.Bold(), template.FontSize(20))\n            c.Spacer(document.Mm(6))\n            c.Table(\n                []string{\"Description\", \"Qty\", \"Unit\", \"Amount\"},\n                [][]string{\n                    {\"Frontend dev\", \"40 hrs\", \"$150.00\", \"$6,000.00\"},\n                    {\"Backend dev\",  \"60 hrs\", \"$150.00\", \"$9,000.00\"},\n                    {\"UI design\",    \"20 hrs\", \"$120.00\", \"$2,400.00\"},\n                },\n                template.ColumnWidths(50, 15, 15, 20),\n                template.TableHeaderStyle(\n                    template.Bold(),\n                    template.TextColor(pdf.White),\n                    template.BgColor(pdf.RGBHex(0x1A237E)),\n                ),\n            )\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,45710,45711,45717,45721,45727,45735,45743,45747,45755,45763,45771,45779,45783,45787,45797,45811,45829,45859,45863,45867,45881,45905,45935,45974,45996,46006,46046,46054,46090,46126,46162,46166,46192,46202,46212,46230,46252,46256,46260,46264,46268,46272,46290,46302,46316,46320,46360,46374,46378],{"__ignoreMap":114},[118,45712,45713,45715],{"class":120,"line":121},[118,45714,125],{"class":124},[118,45716,129],{"class":128},[118,45718,45719],{"class":120,"line":132},[118,45720,136],{"emptyLinePlaceholder":135},[118,45722,45723,45725],{"class":120,"line":139},[118,45724,143],{"class":142},[118,45726,146],{"class":124},[118,45728,45729,45731,45733],{"class":120,"line":149},[118,45730,152],{"class":124},[118,45732,5303],{"class":128},[118,45734,158],{"class":124},[118,45736,45737,45739,45741],{"class":120,"line":161},[118,45738,152],{"class":124},[118,45740,155],{"class":128},[118,45742,158],{"class":124},[118,45744,45745],{"class":120,"line":166},[118,45746,136],{"emptyLinePlaceholder":135},[118,45748,45749,45751,45753],{"class":120,"line":176},[118,45750,152],{"class":124},[118,45752,3203],{"class":128},[118,45754,158],{"class":124},[118,45756,45757,45759,45761],{"class":120,"line":186},[118,45758,152],{"class":124},[118,45760,171],{"class":128},[118,45762,158],{"class":124},[118,45764,45765,45767,45769],{"class":120,"line":196},[118,45766,152],{"class":124},[118,45768,181],{"class":128},[118,45770,158],{"class":124},[118,45772,45773,45775,45777],{"class":120,"line":202},[118,45774,152],{"class":124},[118,45776,191],{"class":128},[118,45778,158],{"class":124},[118,45780,45781],{"class":120,"line":207},[118,45782,199],{"class":124},[118,45784,45785],{"class":120,"line":223},[118,45786,136],{"emptyLinePlaceholder":135},[118,45788,45789,45791,45793,45795],{"class":120,"line":244},[118,45790,210],{"class":124},[118,45792,214],{"class":213},[118,45794,217],{"class":124},[118,45796,220],{"class":124},[118,45798,45799,45801,45803,45805,45807,45809],{"class":120,"line":269},[118,45800,227],{"class":226},[118,45802,230],{"class":124},[118,45804,3595],{"class":226},[118,45806,25],{"class":124},[118,45808,3600],{"class":213},[118,45810,241],{"class":124},[118,45812,45813,45815,45817,45819,45821,45823,45825,45827],{"class":120,"line":306},[118,45814,3607],{"class":226},[118,45816,25],{"class":124},[118,45818,252],{"class":213},[118,45820,255],{"class":124},[118,45822,258],{"class":226},[118,45824,25],{"class":124},[118,45826,263],{"class":226},[118,45828,266],{"class":124},[118,45830,45831,45833,45835,45837,45839,45841,45843,45845,45847,45849,45851,45853,45855,45857],{"class":120,"line":312},[118,45832,3607],{"class":226},[118,45834,25],{"class":124},[118,45836,276],{"class":213},[118,45838,255],{"class":124},[118,45840,258],{"class":226},[118,45842,25],{"class":124},[118,45844,285],{"class":213},[118,45846,255],{"class":124},[118,45848,258],{"class":226},[118,45850,25],{"class":124},[118,45852,294],{"class":213},[118,45854,255],{"class":124},[118,45856,300],{"class":299},[118,45858,303],{"class":124},[118,45860,45861],{"class":120,"line":317},[118,45862,309],{"class":124},[118,45864,45865],{"class":120,"line":350},[118,45866,136],{"emptyLinePlaceholder":135},[118,45868,45869,45871,45873,45875,45877,45879],{"class":120,"line":379},[118,45870,5431],{"class":226},[118,45872,230],{"class":124},[118,45874,1185],{"class":226},[118,45876,25],{"class":124},[118,45878,1190],{"class":213},[118,45880,1193],{"class":124},[118,45882,45883,45885,45887,45889,45891,45893,45895,45897,45899,45901,45903],{"class":120,"line":417},[118,45884,5494],{"class":226},[118,45886,25],{"class":124},[118,45888,358],{"class":213},[118,45890,328],{"class":124},[118,45892,363],{"class":331},[118,45894,334],{"class":124},[118,45896,337],{"class":128},[118,45898,25],{"class":124},[118,45900,372],{"class":128},[118,45902,345],{"class":124},[118,45904,220],{"class":124},[118,45906,45907,45909,45911,45913,45915,45917,45919,45921,45923,45925,45927,45929,45931,45933],{"class":120,"line":466},[118,45908,1737],{"class":226},[118,45910,25],{"class":124},[118,45912,387],{"class":213},[118,45914,255],{"class":124},[118,45916,20],{"class":299},[118,45918,395],{"class":124},[118,45920,398],{"class":124},[118,45922,401],{"class":331},[118,45924,334],{"class":124},[118,45926,337],{"class":128},[118,45928,25],{"class":124},[118,45930,410],{"class":128},[118,45932,345],{"class":124},[118,45934,220],{"class":124},[118,45936,45937,45939,45941,45943,45945,45947,45950,45952,45954,45956,45958,45960,45962,45964,45966,45968,45970,45972],{"class":120,"line":472},[118,45938,1768],{"class":226},[118,45940,25],{"class":124},[118,45942,425],{"class":213},[118,45944,255],{"class":124},[118,45946,430],{"class":124},[118,45948,45949],{"class":433},"INVOICE #2026-0042",[118,45951,430],{"class":124},[118,45953,395],{"class":124},[118,45955,233],{"class":226},[118,45957,25],{"class":124},[118,45959,445],{"class":213},[118,45961,448],{"class":124},[118,45963,233],{"class":226},[118,45965,25],{"class":124},[118,45967,455],{"class":213},[118,45969,255],{"class":124},[118,45971,300],{"class":299},[118,45973,463],{"class":124},[118,45975,45976,45978,45980,45982,45984,45986,45988,45990,45992,45994],{"class":120,"line":503},[118,45977,1768],{"class":226},[118,45979,25],{"class":124},[118,45981,675],{"class":213},[118,45983,255],{"class":124},[118,45985,258],{"class":226},[118,45987,25],{"class":124},[118,45989,294],{"class":213},[118,45991,255],{"class":124},[118,45993,392],{"class":299},[118,45995,463],{"class":124},[118,45997,45998,46000,46002,46004],{"class":120,"line":537},[118,45999,1768],{"class":226},[118,46001,25],{"class":124},[118,46003,4929],{"class":213},[118,46005,241],{"class":124},[118,46007,46008,46010,46012,46014,46016,46018,46020,46022,46024,46026,46028,46030,46032,46034,46036,46038,46040,46042,46044],{"class":120,"line":566},[118,46009,19681],{"class":124},[118,46011,1131],{"class":1130},[118,46013,1134],{"class":124},[118,46015,430],{"class":124},[118,46017,14460],{"class":433},[118,46019,430],{"class":124},[118,46021,395],{"class":124},[118,46023,1146],{"class":124},[118,46025,14469],{"class":433},[118,46027,430],{"class":124},[118,46029,395],{"class":124},[118,46031,1146],{"class":124},[118,46033,14478],{"class":433},[118,46035,430],{"class":124},[118,46037,395],{"class":124},[118,46039,1146],{"class":124},[118,46041,7320],{"class":433},[118,46043,430],{"class":124},[118,46045,8191],{"class":124},[118,46047,46048,46050,46052],{"class":120,"line":571},[118,46049,19725],{"class":124},[118,46051,1131],{"class":1130},[118,46053,7406],{"class":124},[118,46055,46056,46058,46060,46062,46064,46066,46068,46070,46072,46074,46076,46078,46080,46082,46084,46086,46088],{"class":120,"line":577},[118,46057,19734],{"class":124},[118,46059,430],{"class":124},[118,46061,24374],{"class":433},[118,46063,430],{"class":124},[118,46065,395],{"class":124},[118,46067,1146],{"class":124},[118,46069,24383],{"class":433},[118,46071,430],{"class":124},[118,46073,395],{"class":124},[118,46075,1146],{"class":124},[118,46077,24392],{"class":433},[118,46079,430],{"class":124},[118,46081,395],{"class":124},[118,46083,1146],{"class":124},[118,46085,24401],{"class":433},[118,46087,430],{"class":124},[118,46089,8191],{"class":124},[118,46091,46092,46094,46096,46098,46100,46102,46104,46106,46108,46110,46112,46114,46116,46118,46120,46122,46124],{"class":120,"line":602},[118,46093,19734],{"class":124},[118,46095,430],{"class":124},[118,46097,24414],{"class":433},[118,46099,430],{"class":124},[118,46101,395],{"class":124},[118,46103,19796],{"class":124},[118,46105,24423],{"class":433},[118,46107,430],{"class":124},[118,46109,395],{"class":124},[118,46111,1146],{"class":124},[118,46113,24392],{"class":433},[118,46115,430],{"class":124},[118,46117,395],{"class":124},[118,46119,1146],{"class":124},[118,46121,24440],{"class":433},[118,46123,430],{"class":124},[118,46125,8191],{"class":124},[118,46127,46128,46130,46132,46134,46136,46138,46140,46142,46144,46146,46148,46150,46152,46154,46156,46158,46160],{"class":120,"line":633},[118,46129,19734],{"class":124},[118,46131,430],{"class":124},[118,46133,24453],{"class":433},[118,46135,430],{"class":124},[118,46137,395],{"class":124},[118,46139,152],{"class":124},[118,46141,24462],{"class":433},[118,46143,430],{"class":124},[118,46145,395],{"class":124},[118,46147,1146],{"class":124},[118,46149,24471],{"class":433},[118,46151,430],{"class":124},[118,46153,395],{"class":124},[118,46155,1146],{"class":124},[118,46157,24480],{"class":433},[118,46159,430],{"class":124},[118,46161,8191],{"class":124},[118,46163,46164],{"class":120,"line":668},[118,46165,19978],{"class":124},[118,46167,46168,46170,46172,46174,46176,46178,46180,46182,46184,46186,46188,46190],{"class":120,"line":693},[118,46169,2648],{"class":226},[118,46171,25],{"class":124},[118,46173,7733],{"class":213},[118,46175,255],{"class":124},[118,46177,1510],{"class":299},[118,46179,395],{"class":124},[118,46181,9915],{"class":299},[118,46183,395],{"class":124},[118,46185,9915],{"class":299},[118,46187,395],{"class":124},[118,46189,7742],{"class":299},[118,46191,266],{"class":124},[118,46193,46194,46196,46198,46200],{"class":120,"line":698},[118,46195,2648],{"class":226},[118,46197,25],{"class":124},[118,46199,7762],{"class":213},[118,46201,241],{"class":124},[118,46203,46204,46206,46208,46210],{"class":120,"line":703},[118,46205,540],{"class":226},[118,46207,25],{"class":124},[118,46209,445],{"class":213},[118,46211,2655],{"class":124},[118,46213,46214,46216,46218,46220,46222,46224,46226,46228],{"class":120,"line":709},[118,46215,540],{"class":226},[118,46217,25],{"class":124},[118,46219,545],{"class":213},[118,46221,255],{"class":124},[118,46223,550],{"class":226},[118,46225,25],{"class":124},[118,46227,7781],{"class":226},[118,46229,266],{"class":124},[118,46231,46232,46234,46236,46238,46240,46242,46244,46246,46248,46250],{"class":120,"line":714},[118,46233,540],{"class":226},[118,46235,25],{"class":124},[118,46237,7792],{"class":213},[118,46239,255],{"class":124},[118,46241,550],{"class":226},[118,46243,25],{"class":124},[118,46245,658],{"class":213},[118,46247,255],{"class":124},[118,46249,7269],{"class":299},[118,46251,3646],{"class":124},[118,46253,46254],{"class":120,"line":740},[118,46255,7804],{"class":124},[118,46257,46258],{"class":120,"line":765},[118,46259,7809],{"class":124},[118,46261,46262],{"class":120,"line":796},[118,46263,574],{"class":124},[118,46265,46266],{"class":120,"line":819},[118,46267,706],{"class":124},[118,46269,46270],{"class":120,"line":851},[118,46271,136],{"emptyLinePlaceholder":135},[118,46273,46274,46276,46278,46280,46282,46284,46286,46288],{"class":120,"line":875},[118,46275,5787],{"class":226},[118,46277,395],{"class":124},[118,46279,1391],{"class":226},[118,46281,230],{"class":124},[118,46283,1185],{"class":226},[118,46285,25],{"class":124},[118,46287,1400],{"class":213},[118,46289,1193],{"class":124},[118,46291,46292,46294,46296,46298,46300],{"class":120,"line":880},[118,46293,1408],{"class":142},[118,46295,1391],{"class":226},[118,46297,1413],{"class":124},[118,46299,1416],{"class":124},[118,46301,220],{"class":124},[118,46303,46304,46306,46308,46310,46312,46314],{"class":120,"line":885},[118,46305,5818],{"class":226},[118,46307,25],{"class":124},[118,46309,5823],{"class":213},[118,46311,255],{"class":124},[118,46313,1429],{"class":226},[118,46315,199],{"class":124},[118,46317,46318],{"class":120,"line":910},[118,46319,1375],{"class":124},[118,46321,46322,46324,46326,46328,46330,46332,46334,46336,46338,46340,46342,46344,46346,46348,46350,46352,46354,46356,46358],{"class":120,"line":941},[118,46323,1408],{"class":142},[118,46325,1391],{"class":226},[118,46327,230],{"class":124},[118,46329,1447],{"class":226},[118,46331,25],{"class":124},[118,46333,1452],{"class":213},[118,46335,255],{"class":124},[118,46337,430],{"class":124},[118,46339,4015],{"class":433},[118,46341,430],{"class":124},[118,46343,395],{"class":124},[118,46345,5859],{"class":226},[118,46347,395],{"class":124},[118,46349,1471],{"class":299},[118,46351,7902],{"class":124},[118,46353,1391],{"class":226},[118,46355,1413],{"class":124},[118,46357,1416],{"class":124},[118,46359,220],{"class":124},[118,46361,46362,46364,46366,46368,46370,46372],{"class":120,"line":974},[118,46363,5818],{"class":226},[118,46365,25],{"class":124},[118,46367,5823],{"class":213},[118,46369,255],{"class":124},[118,46371,1429],{"class":226},[118,46373,199],{"class":124},[118,46375,46376],{"class":120,"line":997},[118,46377,1375],{"class":124},[118,46379,46380],{"class":120,"line":1002},[118,46381,1479],{"class":124},[14,46383,46384,46385,46387,46388,20607,46391,46394,46395,46397],{},"Zero ",[18,46386,12972],{},". Zero manual column-width math. Swap ",[18,46389,46390],{},"\"Description\"",[18,46392,46393],{},"\"品目\""," and add ",[18,46396,43019],{}," to the document options — it renders Japanese without any other change.",[41,46399,46401],{"id":46400},"what-we-chose-to-leave-out","What we chose to leave out",[14,46403,46404],{},"Every comparison post has a section for \"omitted because of X.\" Ours:",[46,46406,46407,46413,46424,46434],{},[49,46408,46409,46412],{},[1629,46410,46411],{},"Private gofpdf forks."," There are production forks inside companies. We couldn't benchmark code we can't see.",[49,46414,46415,46419,46420,46423],{},[1629,46416,46417,25],{},[18,46418,27218],{}," It's in every list of \"Go PDF libraries,\" but it's primarily a PDF ",[1629,46421,46422],{},"processor"," (merge, split, encrypt, stamp), not a generator. Out of scope for this post; a processing-oriented article is planned separately.",[49,46425,46426,46433],{},[1629,46427,46428,46429,46432],{},"Anything wrapping ",[18,46430,46431],{},"gotenberg"," or a headless-browser service."," Not a library. Not a fair comparison.",[49,46435,46436,46442],{},[1629,46437,46438,46439,46441],{},"Our own ",[18,46440,1587],{}," benchmarks."," The core numbers are what matters for the comparison.",[41,46444,3054],{"id":3053},[14,46446,46447,46450],{},[1629,46448,46449],{},"Why is gpdf 10× faster than gofpdf? What's the trick?","\nNo single trick. Three designs compound: single-pass layout (no AST between builder and writer), concrete types on the hot path, and a TrueType subsetter that caches the cmap. Any one of these in isolation gives a 2× gain. Stacked, it's an order of magnitude.",[14,46452,46453,46456,46457,46460],{},[1629,46454,46455],{},"Can I actually re-run this benchmark?","\nYes. ",[18,46458,46459],{},"git clone https://github.com/gpdf-dev/gpdf && cd gpdf/_benchmark && go test -bench=. -benchmem",". If the numbers don't match what's in this post — same machine architecture, same Go version — open an issue. Benchmark drift is real; we'd rather know.",[14,46462,46463,46466],{},[1629,46464,46465],{},"Is gofpdf coming back?","\nRealistically, no. The last commit is from 2021. The issue tracker is closed. Even if someone re-opened it, the architecture (single cursor, single-byte fonts, no grid) is the wrong starting point for 2026. Better to treat it as a historical artifact and migrate.",[14,46468,46469,46472],{},[1629,46470,46471],{},"What about Java iText / Python ReportLab / Node pdfkit?","\nCross-language benchmarks are a different post. Short version: Go generally wins on throughput and cold-start, loses on feature breadth (especially on HTML-to-PDF fidelity). For teams already on Go, gpdf is faster and smaller than any of those cross-language options; for teams on Python or Node, the migration cost usually only pays back at high-volume scale.",[14,46474,46475,46478,46479,46481],{},[1629,46476,46477],{},"Is this comparison going to stay fair if gpdf's competitors improve?","\nYes. We run this every year. If ",[18,46480,1565],{}," ships a table API that halves its time, that's in the 2027 post. If Maroto v2 finishes removing gofpdf, that row changes. The benchmark code is public specifically so nobody has to take our word for it.",[41,46483,4794],{"id":4793},[14,46485,38010],{},[109,46487,46488],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,46489,46490],{"__ignoreMap":114},[118,46491,46492,46494,46496],{"class":120,"line":121},[118,46493,113],{"class":128},[118,46495,3156],{"class":433},[118,46497,3159],{"class":433},[14,46499,46500,3169,46503],{},[3163,46501,3168],{"href":3165,"rel":46502},[3167],[3163,46504,3174],{"href":3172,"rel":46505},[3167],[41,46507,4821],{"id":4820},[46,46509,46510,46515,46522],{},[49,46511,46512,46514],{},[3163,46513,18018],{"href":4839}," — the full API mapping with five before/after code pairs.",[49,46516,46517,27353,46520,25],{},[3163,46518,27352],{"href":3172,"rel":46519},[3167],[18,46521,27266],{},[49,46523,46524,46525,25],{},"The benchmark code itself: ",[3163,46526,46528],{"href":4171,"rel":46527},[3167],[18,46529,27100],{},[3176,46531,3178],{},{"title":114,"searchDepth":132,"depth":132,"links":46533},[46534,46535,46536,46537,46541,46542,46543,46544,46545,46546,46547,46548,46549],{"id":43,"depth":132,"text":44},{"id":45041,"depth":132,"text":45042},{"id":45084,"depth":132,"text":45085},{"id":45269,"depth":132,"text":45270,"children":46538},[46539,46540],{"id":45320,"depth":139,"text":45321},{"id":45416,"depth":139,"text":45417},{"id":45450,"depth":132,"text":45451},{"id":45543,"depth":132,"text":45544},{"id":45609,"depth":132,"text":45610},{"id":45650,"depth":132,"text":45651},{"id":45701,"depth":132,"text":45702},{"id":46400,"depth":132,"text":46401},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"2026-04-15","The Go PDF library landscape in 2026: every active and archived library, benchmarked on 4 workloads, with license and dependency details.",{},{"title":4790,"description":46551},"blog/002.go-pdf-library-showdown-2026",[4866,4867],"CKsh5JpCtm-FDnoCIklxyOnN4BD5y7TMRVMk77Ic6us",{"id":46558,"title":9792,"author":46559,"body":46560,"date":46550,"description":47848,"draft":3196,"extension":3197,"howTo":47849,"image":3220,"meta":47864,"navigation":135,"path":9791,"seo":47865,"stem":47866,"tags":47867,"updated":3220,"__hash__":47868},"blog/blog/003.embed-japanese-font.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":46561,"toc":47837},[46562,46564,46574,46576,46588,46590,47094,47108,47112,47115,47121,47130,47139,47143,47152,47297,47311,47315,47321,47746,47753,47757,47763,47769,47784,47786,47811,47813,47815,47827,47835],[41,46563,4879],{"id":4878},[14,46565,46566,46567,46570,46571,46573],{},"How do I render Japanese (or any CJK) text in a PDF generated with ",[3163,46568,1587],{"href":3165,"rel":46569},[3167]," — without the ",[18,46572,2745],{}," dance, without CGO, without a five-megabyte font embedded on every document?",[41,46575,44],{"id":43},[14,46577,20767,46578,1649,46581,46583,46584,46587],{},[18,46579,46580],{},"gpdf.WithFont(\"NotoSansJP\", fontBytes)",[18,46582,3600],{},". Optionally set it as the default. Write Japanese. ",[1629,46585,46586],{},"Three lines of setup, and gpdf subsets the glyphs automatically"," so the final PDF carries only the characters you actually used.",[41,46589,35516],{"id":35515},[109,46591,46593],{"className":111,"code":46592,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", font),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"こんにちは、世界。\", template.FontSize(24), template.Bold())\n            c.Text(\"日本語 PDF、これだけ。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"hello.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,46594,46595,46601,46605,46611,46619,46627,46631,46639,46647,46655,46659,46663,46673,46699,46711,46725,46729,46733,46747,46765,46795,46817,46839,46843,46847,46861,46885,46915,46953,46972,46976,46980,46984,47002,47014,47028,47032,47072,47086,47090],{"__ignoreMap":114},[118,46596,46597,46599],{"class":120,"line":121},[118,46598,125],{"class":124},[118,46600,129],{"class":128},[118,46602,46603],{"class":120,"line":132},[118,46604,136],{"emptyLinePlaceholder":135},[118,46606,46607,46609],{"class":120,"line":139},[118,46608,143],{"class":142},[118,46610,146],{"class":124},[118,46612,46613,46615,46617],{"class":120,"line":149},[118,46614,152],{"class":124},[118,46616,5303],{"class":128},[118,46618,158],{"class":124},[118,46620,46621,46623,46625],{"class":120,"line":161},[118,46622,152],{"class":124},[118,46624,155],{"class":128},[118,46626,158],{"class":124},[118,46628,46629],{"class":120,"line":166},[118,46630,136],{"emptyLinePlaceholder":135},[118,46632,46633,46635,46637],{"class":120,"line":176},[118,46634,152],{"class":124},[118,46636,3203],{"class":128},[118,46638,158],{"class":124},[118,46640,46641,46643,46645],{"class":120,"line":186},[118,46642,152],{"class":124},[118,46644,171],{"class":128},[118,46646,158],{"class":124},[118,46648,46649,46651,46653],{"class":120,"line":196},[118,46650,152],{"class":124},[118,46652,191],{"class":128},[118,46654,158],{"class":124},[118,46656,46657],{"class":120,"line":202},[118,46658,199],{"class":124},[118,46660,46661],{"class":120,"line":207},[118,46662,136],{"emptyLinePlaceholder":135},[118,46664,46665,46667,46669,46671],{"class":120,"line":223},[118,46666,210],{"class":124},[118,46668,214],{"class":213},[118,46670,217],{"class":124},[118,46672,220],{"class":124},[118,46674,46675,46677,46679,46681,46683,46685,46687,46689,46691,46693,46695,46697],{"class":120,"line":244},[118,46676,35604],{"class":226},[118,46678,395],{"class":124},[118,46680,1391],{"class":226},[118,46682,230],{"class":124},[118,46684,1447],{"class":226},[118,46686,25],{"class":124},[118,46688,9545],{"class":213},[118,46690,255],{"class":124},[118,46692,430],{"class":124},[118,46694,9552],{"class":433},[118,46696,430],{"class":124},[118,46698,199],{"class":124},[118,46700,46701,46703,46705,46707,46709],{"class":120,"line":269},[118,46702,1408],{"class":142},[118,46704,1391],{"class":226},[118,46706,1413],{"class":124},[118,46708,1416],{"class":124},[118,46710,220],{"class":124},[118,46712,46713,46715,46717,46719,46721,46723],{"class":120,"line":306},[118,46714,5818],{"class":226},[118,46716,25],{"class":124},[118,46718,5823],{"class":213},[118,46720,255],{"class":124},[118,46722,1429],{"class":226},[118,46724,199],{"class":124},[118,46726,46727],{"class":120,"line":312},[118,46728,1375],{"class":124},[118,46730,46731],{"class":120,"line":317},[118,46732,136],{"emptyLinePlaceholder":135},[118,46734,46735,46737,46739,46741,46743,46745],{"class":120,"line":350},[118,46736,227],{"class":226},[118,46738,230],{"class":124},[118,46740,3595],{"class":226},[118,46742,25],{"class":124},[118,46744,3600],{"class":213},[118,46746,241],{"class":124},[118,46748,46749,46751,46753,46755,46757,46759,46761,46763],{"class":120,"line":379},[118,46750,3607],{"class":226},[118,46752,25],{"class":124},[118,46754,252],{"class":213},[118,46756,255],{"class":124},[118,46758,1587],{"class":226},[118,46760,25],{"class":124},[118,46762,263],{"class":226},[118,46764,266],{"class":124},[118,46766,46767,46769,46771,46773,46775,46777,46779,46781,46783,46785,46787,46789,46791,46793],{"class":120,"line":417},[118,46768,3607],{"class":226},[118,46770,25],{"class":124},[118,46772,276],{"class":213},[118,46774,255],{"class":124},[118,46776,258],{"class":226},[118,46778,25],{"class":124},[118,46780,285],{"class":213},[118,46782,255],{"class":124},[118,46784,258],{"class":226},[118,46786,25],{"class":124},[118,46788,294],{"class":213},[118,46790,255],{"class":124},[118,46792,300],{"class":299},[118,46794,303],{"class":124},[118,46796,46797,46799,46801,46803,46805,46807,46809,46811,46813,46815],{"class":120,"line":466},[118,46798,3607],{"class":226},[118,46800,25],{"class":124},[118,46802,2798],{"class":213},[118,46804,255],{"class":124},[118,46806,430],{"class":124},[118,46808,2805],{"class":433},[118,46810,430],{"class":124},[118,46812,395],{"class":124},[118,46814,35744],{"class":226},[118,46816,266],{"class":124},[118,46818,46819,46821,46823,46825,46827,46829,46831,46833,46835,46837],{"class":120,"line":472},[118,46820,3607],{"class":226},[118,46822,25],{"class":124},[118,46824,12501],{"class":213},[118,46826,255],{"class":124},[118,46828,430],{"class":124},[118,46830,2805],{"class":433},[118,46832,430],{"class":124},[118,46834,395],{"class":124},[118,46836,32153],{"class":299},[118,46838,266],{"class":124},[118,46840,46841],{"class":120,"line":503},[118,46842,309],{"class":124},[118,46844,46845],{"class":120,"line":537},[118,46846,136],{"emptyLinePlaceholder":135},[118,46848,46849,46851,46853,46855,46857,46859],{"class":120,"line":566},[118,46850,5431],{"class":226},[118,46852,230],{"class":124},[118,46854,1185],{"class":226},[118,46856,25],{"class":124},[118,46858,1190],{"class":213},[118,46860,1193],{"class":124},[118,46862,46863,46865,46867,46869,46871,46873,46875,46877,46879,46881,46883],{"class":120,"line":571},[118,46864,5494],{"class":226},[118,46866,25],{"class":124},[118,46868,358],{"class":213},[118,46870,328],{"class":124},[118,46872,363],{"class":331},[118,46874,334],{"class":124},[118,46876,337],{"class":128},[118,46878,25],{"class":124},[118,46880,372],{"class":128},[118,46882,345],{"class":124},[118,46884,220],{"class":124},[118,46886,46887,46889,46891,46893,46895,46897,46899,46901,46903,46905,46907,46909,46911,46913],{"class":120,"line":577},[118,46888,1737],{"class":226},[118,46890,25],{"class":124},[118,46892,387],{"class":213},[118,46894,255],{"class":124},[118,46896,20],{"class":299},[118,46898,395],{"class":124},[118,46900,398],{"class":124},[118,46902,401],{"class":331},[118,46904,334],{"class":124},[118,46906,337],{"class":128},[118,46908,25],{"class":124},[118,46910,410],{"class":128},[118,46912,345],{"class":124},[118,46914,220],{"class":124},[118,46916,46917,46919,46921,46923,46925,46927,46929,46931,46933,46935,46937,46939,46941,46943,46945,46947,46949,46951],{"class":120,"line":602},[118,46918,1768],{"class":226},[118,46920,25],{"class":124},[118,46922,425],{"class":213},[118,46924,255],{"class":124},[118,46926,430],{"class":124},[118,46928,17618],{"class":433},[118,46930,430],{"class":124},[118,46932,395],{"class":124},[118,46934,233],{"class":226},[118,46936,25],{"class":124},[118,46938,455],{"class":213},[118,46940,255],{"class":124},[118,46942,3777],{"class":299},[118,46944,1280],{"class":124},[118,46946,233],{"class":226},[118,46948,25],{"class":124},[118,46950,445],{"class":213},[118,46952,1289],{"class":124},[118,46954,46955,46957,46959,46961,46963,46965,46968,46970],{"class":120,"line":633},[118,46956,1768],{"class":226},[118,46958,25],{"class":124},[118,46960,425],{"class":213},[118,46962,255],{"class":124},[118,46964,430],{"class":124},[118,46966,46967],{"class":433},"日本語 PDF、これだけ。",[118,46969,430],{"class":124},[118,46971,199],{"class":124},[118,46973,46974],{"class":120,"line":668},[118,46975,574],{"class":124},[118,46977,46978],{"class":120,"line":693},[118,46979,706],{"class":124},[118,46981,46982],{"class":120,"line":698},[118,46983,136],{"emptyLinePlaceholder":135},[118,46985,46986,46988,46990,46992,46994,46996,46998,47000],{"class":120,"line":703},[118,46987,5787],{"class":226},[118,46989,395],{"class":124},[118,46991,1391],{"class":226},[118,46993,230],{"class":124},[118,46995,1185],{"class":226},[118,46997,25],{"class":124},[118,46999,1400],{"class":213},[118,47001,1193],{"class":124},[118,47003,47004,47006,47008,47010,47012],{"class":120,"line":709},[118,47005,1408],{"class":142},[118,47007,1391],{"class":226},[118,47009,1413],{"class":124},[118,47011,1416],{"class":124},[118,47013,220],{"class":124},[118,47015,47016,47018,47020,47022,47024,47026],{"class":120,"line":714},[118,47017,5818],{"class":226},[118,47019,25],{"class":124},[118,47021,5823],{"class":213},[118,47023,255],{"class":124},[118,47025,1429],{"class":226},[118,47027,199],{"class":124},[118,47029,47030],{"class":120,"line":740},[118,47031,1375],{"class":124},[118,47033,47034,47036,47038,47040,47042,47044,47046,47048,47050,47052,47054,47056,47058,47060,47062,47064,47066,47068,47070],{"class":120,"line":765},[118,47035,1408],{"class":142},[118,47037,1391],{"class":226},[118,47039,230],{"class":124},[118,47041,1447],{"class":226},[118,47043,25],{"class":124},[118,47045,1452],{"class":213},[118,47047,255],{"class":124},[118,47049,430],{"class":124},[118,47051,13805],{"class":433},[118,47053,430],{"class":124},[118,47055,395],{"class":124},[118,47057,5859],{"class":226},[118,47059,395],{"class":124},[118,47061,1471],{"class":299},[118,47063,7902],{"class":124},[118,47065,1391],{"class":226},[118,47067,1413],{"class":124},[118,47069,1416],{"class":124},[118,47071,220],{"class":124},[118,47073,47074,47076,47078,47080,47082,47084],{"class":120,"line":796},[118,47075,5818],{"class":226},[118,47077,25],{"class":124},[118,47079,5823],{"class":213},[118,47081,255],{"class":124},[118,47083,1429],{"class":226},[118,47085,199],{"class":124},[118,47087,47088],{"class":120,"line":819},[118,47089,1375],{"class":124},[118,47091,47092],{"class":120,"line":851},[118,47093,1479],{"class":124},[14,47095,35498,47096,20777,47098,47101,47102,47104,47105,47107],{},[18,47097,9552],{},[3163,47099,38646],{"href":36478,"rel":47100},[3167]," and drop it next to ",[18,47103,102],{},". Run ",[18,47106,106],{},". You get a one-page PDF with Japanese text.",[41,47109,47111],{"id":47110},"what-those-three-lines-actually-do","What those three lines actually do",[14,47113,47114],{},"Two things happen under the hood, and neither one needs any help from you.",[14,47116,47117,47120],{},[1629,47118,47119],{},"Subset embedding."," Noto Sans JP ships with around 17,000 glyphs — the regular weight is about 5 MB on disk. If every PDF embedded the whole font, a receipt with four lines of Japanese would still cost you five megabytes of font data. gpdf walks the text you rendered, figures out which glyph IDs you used, and writes only that subset into the PDF. A short invoice usually ends up carrying 20–40 KB of font data instead of 5 MB.",[14,47122,47123,47124,47126,47127,47129],{},"gofpdf could do subset embedding too, but it needed ",[18,47125,2745],{}," called with a filesystem path and a UTF-8 flag, and font switching mid-document was awkward. gpdf registers the font once at document construction and then every ",[18,47128,8551],{}," call just references it by family name. There's no per-call bookkeeping.",[14,47131,47132,47135,47136,47138],{},[1629,47133,47134],{},"No CGO."," This matters more than it sounds. A lot of font handling in other ecosystems routes through FreeType or HarfBuzz, which means a C dependency, which means your build caches invalidate differently, your Docker images gain layers, and cross-compilation from macOS to linux/arm64 becomes a thing you have to think about. gpdf parses TrueType tables in pure Go. ",[18,47137,21802],{}," stays static. Ship a distroless container with the Go binary and the TTF file; that's all.",[41,47140,47142],{"id":47141},"bold-and-italic-variants","Bold and italic variants",[14,47144,47145,47146,47148,47149,47151],{},"Japanese Noto ships a separate file per weight. To use ",[1629,47147,21479],{},", register the bold TTF under the ",[18,47150,36378],{}," suffix:",[109,47153,47155],{"className":111,"code":47154,"language":113,"meta":114,"style":114},"reg, _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\nbold, _ := os.ReadFile(\"NotoSansJP-Bold.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", reg),\n    gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n)\n",[18,47156,47157,47183,47209,47213,47227,47249,47271,47293],{"__ignoreMap":114},[118,47158,47159,47161,47163,47165,47167,47169,47171,47173,47175,47177,47179,47181],{"class":120,"line":121},[118,47160,36232],{"class":226},[118,47162,395],{"class":124},[118,47164,3999],{"class":226},[118,47166,230],{"class":124},[118,47168,1447],{"class":226},[118,47170,25],{"class":124},[118,47172,9545],{"class":213},[118,47174,255],{"class":124},[118,47176,430],{"class":124},[118,47178,9552],{"class":433},[118,47180,430],{"class":124},[118,47182,199],{"class":124},[118,47184,47185,47187,47189,47191,47193,47195,47197,47199,47201,47203,47205,47207],{"class":120,"line":132},[118,47186,21479],{"class":226},[118,47188,395],{"class":124},[118,47190,3999],{"class":226},[118,47192,230],{"class":124},[118,47194,1447],{"class":226},[118,47196,25],{"class":124},[118,47198,9545],{"class":213},[118,47200,255],{"class":124},[118,47202,430],{"class":124},[118,47204,32335],{"class":433},[118,47206,430],{"class":124},[118,47208,199],{"class":124},[118,47210,47211],{"class":120,"line":139},[118,47212,136],{"emptyLinePlaceholder":135},[118,47214,47215,47217,47219,47221,47223,47225],{"class":120,"line":149},[118,47216,2760],{"class":226},[118,47218,230],{"class":124},[118,47220,3595],{"class":226},[118,47222,25],{"class":124},[118,47224,3600],{"class":213},[118,47226,241],{"class":124},[118,47228,47229,47231,47233,47235,47237,47239,47241,47243,47245,47247],{"class":120,"line":161},[118,47230,4532],{"class":226},[118,47232,25],{"class":124},[118,47234,2798],{"class":213},[118,47236,255],{"class":124},[118,47238,430],{"class":124},[118,47240,2805],{"class":433},[118,47242,430],{"class":124},[118,47244,395],{"class":124},[118,47246,36321],{"class":226},[118,47248,266],{"class":124},[118,47250,47251,47253,47255,47257,47259,47261,47263,47265,47267,47269],{"class":120,"line":166},[118,47252,4532],{"class":226},[118,47254,25],{"class":124},[118,47256,2798],{"class":213},[118,47258,255],{"class":124},[118,47260,430],{"class":124},[118,47262,32124],{"class":433},[118,47264,430],{"class":124},[118,47266,395],{"class":124},[118,47268,21621],{"class":226},[118,47270,266],{"class":124},[118,47272,47273,47275,47277,47279,47281,47283,47285,47287,47289,47291],{"class":120,"line":176},[118,47274,4532],{"class":226},[118,47276,25],{"class":124},[118,47278,12501],{"class":213},[118,47280,255],{"class":124},[118,47282,430],{"class":124},[118,47284,2805],{"class":433},[118,47286,430],{"class":124},[118,47288,395],{"class":124},[118,47290,32153],{"class":299},[118,47292,266],{"class":124},[118,47294,47295],{"class":120,"line":186},[118,47296,199],{"class":124},[14,47298,47299,47300,47302,47303,47305,47306,54,47308,47310],{},"Now ",[18,47301,21701],{}," picks up the ",[18,47304,36378],{}," variant. Same convention for ",[18,47307,32033],{},[18,47309,31466],{},". If you don't register the variant, bold falls back to a synthesized weight — readable on screen but not typographically honest. For production invoices, register the real weight.",[41,47312,47314],{"id":47313},"multiple-cjk-languages-in-the-same-document","Multiple CJK languages in the same document",[14,47316,47317,47318,47320],{},"Registering more than one family is fine — gpdf tracks them independently. Use ",[18,47319,20780],{}," to switch per text:",[109,47322,47324],{"className":111,"code":47323,"language":113,"meta":114,"style":114},"jp, _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\nsc, _ := os.ReadFile(\"NotoSansSC-Regular.ttf\")\nkr, _ := os.ReadFile(\"NotoSansKR-Regular.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", jp),\n    gpdf.WithFont(\"NotoSansSC\", sc),\n    gpdf.WithFont(\"NotoSansKR\", kr),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 12),\n)\n\npage.AutoRow(func(r *template.RowBuilder) {\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"日本語\")\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"中文\", template.FontFamily(\"NotoSansSC\"))\n    })\n    r.Col(4, func(c *template.ColBuilder) {\n        c.Text(\"한국어\", template.FontFamily(\"NotoSansKR\"))\n    })\n})\n",[18,47325,47326,47353,47381,47409,47413,47427,47450,47474,47498,47520,47524,47528,47552,47582,47600,47604,47634,47669,47673,47703,47738,47742],{"__ignoreMap":114},[118,47327,47328,47331,47333,47335,47337,47339,47341,47343,47345,47347,47349,47351],{"class":120,"line":121},[118,47329,47330],{"class":226},"jp",[118,47332,395],{"class":124},[118,47334,3999],{"class":226},[118,47336,230],{"class":124},[118,47338,1447],{"class":226},[118,47340,25],{"class":124},[118,47342,9545],{"class":213},[118,47344,255],{"class":124},[118,47346,430],{"class":124},[118,47348,9552],{"class":433},[118,47350,430],{"class":124},[118,47352,199],{"class":124},[118,47354,47355,47358,47360,47362,47364,47366,47368,47370,47372,47374,47377,47379],{"class":120,"line":132},[118,47356,47357],{"class":226},"sc",[118,47359,395],{"class":124},[118,47361,3999],{"class":226},[118,47363,230],{"class":124},[118,47365,1447],{"class":226},[118,47367,25],{"class":124},[118,47369,9545],{"class":213},[118,47371,255],{"class":124},[118,47373,430],{"class":124},[118,47375,47376],{"class":433},"NotoSansSC-Regular.ttf",[118,47378,430],{"class":124},[118,47380,199],{"class":124},[118,47382,47383,47386,47388,47390,47392,47394,47396,47398,47400,47402,47405,47407],{"class":120,"line":139},[118,47384,47385],{"class":226},"kr",[118,47387,395],{"class":124},[118,47389,3999],{"class":226},[118,47391,230],{"class":124},[118,47393,1447],{"class":226},[118,47395,25],{"class":124},[118,47397,9545],{"class":213},[118,47399,255],{"class":124},[118,47401,430],{"class":124},[118,47403,47404],{"class":433},"NotoSansKR-Regular.ttf",[118,47406,430],{"class":124},[118,47408,199],{"class":124},[118,47410,47411],{"class":120,"line":149},[118,47412,136],{"emptyLinePlaceholder":135},[118,47414,47415,47417,47419,47421,47423,47425],{"class":120,"line":161},[118,47416,2760],{"class":226},[118,47418,230],{"class":124},[118,47420,3595],{"class":226},[118,47422,25],{"class":124},[118,47424,3600],{"class":213},[118,47426,241],{"class":124},[118,47428,47429,47431,47433,47435,47437,47439,47441,47443,47445,47448],{"class":120,"line":166},[118,47430,4532],{"class":226},[118,47432,25],{"class":124},[118,47434,2798],{"class":213},[118,47436,255],{"class":124},[118,47438,430],{"class":124},[118,47440,2805],{"class":433},[118,47442,430],{"class":124},[118,47444,395],{"class":124},[118,47446,47447],{"class":226}," jp",[118,47449,266],{"class":124},[118,47451,47452,47454,47456,47458,47460,47462,47465,47467,47469,47472],{"class":120,"line":176},[118,47453,4532],{"class":226},[118,47455,25],{"class":124},[118,47457,2798],{"class":213},[118,47459,255],{"class":124},[118,47461,430],{"class":124},[118,47463,47464],{"class":433},"NotoSansSC",[118,47466,430],{"class":124},[118,47468,395],{"class":124},[118,47470,47471],{"class":226}," sc",[118,47473,266],{"class":124},[118,47475,47476,47478,47480,47482,47484,47486,47489,47491,47493,47496],{"class":120,"line":186},[118,47477,4532],{"class":226},[118,47479,25],{"class":124},[118,47481,2798],{"class":213},[118,47483,255],{"class":124},[118,47485,430],{"class":124},[118,47487,47488],{"class":433},"NotoSansKR",[118,47490,430],{"class":124},[118,47492,395],{"class":124},[118,47494,47495],{"class":226}," kr",[118,47497,266],{"class":124},[118,47499,47500,47502,47504,47506,47508,47510,47512,47514,47516,47518],{"class":120,"line":196},[118,47501,4532],{"class":226},[118,47503,25],{"class":124},[118,47505,12501],{"class":213},[118,47507,255],{"class":124},[118,47509,430],{"class":124},[118,47511,2805],{"class":433},[118,47513,430],{"class":124},[118,47515,395],{"class":124},[118,47517,32153],{"class":299},[118,47519,266],{"class":124},[118,47521,47522],{"class":120,"line":202},[118,47523,199],{"class":124},[118,47525,47526],{"class":120,"line":207},[118,47527,136],{"emptyLinePlaceholder":135},[118,47529,47530,47532,47534,47536,47538,47540,47542,47544,47546,47548,47550],{"class":120,"line":223},[118,47531,5910],{"class":226},[118,47533,25],{"class":124},[118,47535,358],{"class":213},[118,47537,328],{"class":124},[118,47539,363],{"class":331},[118,47541,334],{"class":124},[118,47543,337],{"class":128},[118,47545,25],{"class":124},[118,47547,372],{"class":128},[118,47549,345],{"class":124},[118,47551,220],{"class":124},[118,47553,47554,47556,47558,47560,47562,47564,47566,47568,47570,47572,47574,47576,47578,47580],{"class":120,"line":244},[118,47555,5935],{"class":226},[118,47557,25],{"class":124},[118,47559,387],{"class":213},[118,47561,255],{"class":124},[118,47563,1493],{"class":299},[118,47565,395],{"class":124},[118,47567,398],{"class":124},[118,47569,401],{"class":331},[118,47571,334],{"class":124},[118,47573,337],{"class":128},[118,47575,25],{"class":124},[118,47577,410],{"class":128},[118,47579,345],{"class":124},[118,47581,220],{"class":124},[118,47583,47584,47586,47588,47590,47592,47594,47596,47598],{"class":120,"line":269},[118,47585,5966],{"class":226},[118,47587,25],{"class":124},[118,47589,425],{"class":213},[118,47591,255],{"class":124},[118,47593,430],{"class":124},[118,47595,9521],{"class":433},[118,47597,430],{"class":124},[118,47599,199],{"class":124},[118,47601,47602],{"class":120,"line":306},[118,47603,706],{"class":124},[118,47605,47606,47608,47610,47612,47614,47616,47618,47620,47622,47624,47626,47628,47630,47632],{"class":120,"line":312},[118,47607,5935],{"class":226},[118,47609,25],{"class":124},[118,47611,387],{"class":213},[118,47613,255],{"class":124},[118,47615,1493],{"class":299},[118,47617,395],{"class":124},[118,47619,398],{"class":124},[118,47621,401],{"class":331},[118,47623,334],{"class":124},[118,47625,337],{"class":128},[118,47627,25],{"class":124},[118,47629,410],{"class":128},[118,47631,345],{"class":124},[118,47633,220],{"class":124},[118,47635,47636,47638,47640,47642,47644,47646,47649,47651,47653,47655,47657,47659,47661,47663,47665,47667],{"class":120,"line":317},[118,47637,5966],{"class":226},[118,47639,25],{"class":124},[118,47641,425],{"class":213},[118,47643,255],{"class":124},[118,47645,430],{"class":124},[118,47647,47648],{"class":433},"中文",[118,47650,430],{"class":124},[118,47652,395],{"class":124},[118,47654,233],{"class":226},[118,47656,25],{"class":124},[118,47658,2926],{"class":213},[118,47660,255],{"class":124},[118,47662,430],{"class":124},[118,47664,47464],{"class":433},[118,47666,430],{"class":124},[118,47668,463],{"class":124},[118,47670,47671],{"class":120,"line":350},[118,47672,706],{"class":124},[118,47674,47675,47677,47679,47681,47683,47685,47687,47689,47691,47693,47695,47697,47699,47701],{"class":120,"line":379},[118,47676,5935],{"class":226},[118,47678,25],{"class":124},[118,47680,387],{"class":213},[118,47682,255],{"class":124},[118,47684,1493],{"class":299},[118,47686,395],{"class":124},[118,47688,398],{"class":124},[118,47690,401],{"class":331},[118,47692,334],{"class":124},[118,47694,337],{"class":128},[118,47696,25],{"class":124},[118,47698,410],{"class":128},[118,47700,345],{"class":124},[118,47702,220],{"class":124},[118,47704,47705,47707,47709,47711,47713,47715,47718,47720,47722,47724,47726,47728,47730,47732,47734,47736],{"class":120,"line":417},[118,47706,5966],{"class":226},[118,47708,25],{"class":124},[118,47710,425],{"class":213},[118,47712,255],{"class":124},[118,47714,430],{"class":124},[118,47716,47717],{"class":433},"한국어",[118,47719,430],{"class":124},[118,47721,395],{"class":124},[118,47723,233],{"class":226},[118,47725,25],{"class":124},[118,47727,2926],{"class":213},[118,47729,255],{"class":124},[118,47731,430],{"class":124},[118,47733,47488],{"class":433},[118,47735,430],{"class":124},[118,47737,463],{"class":124},[118,47739,47740],{"class":120,"line":466},[118,47741,706],{"class":124},[118,47743,47744],{"class":120,"line":472},[118,47745,1944],{"class":124},[14,47747,47748,47749,47752],{},"Han unification means the Unicode codepoints between Japanese and Simplified Chinese overlap, but the glyphs are drawn differently. Picking the right font isn't just aesthetic — ",[1629,47750,47751],{},"the same codepoint renders as a different character shape depending on the font",". If you're producing invoices for both markets, get both files registered.",[41,47754,47756],{"id":47755},"the-tofu-trap","The tofu trap",[14,47758,47759,47760,47762],{},"If you write Japanese but forget ",[18,47761,2798],{},", gpdf falls back to the Base-14 PDF fonts — none of which cover the CJK range. The characters render as blank rectangles, what Unicode people call \"tofu boxes\":",[109,47764,47767],{"className":47765,"code":47766,"language":4993},[34105],"□□□□□、□□。\n",[18,47768,47766],{"__ignoreMap":114},[14,47770,47771,47772,47774,47775,47777,47778,47780,47781,47783],{},"If you see that output, you forgot to register a CJK font, or you wrote the text in a font family that doesn't include those glyphs. The fix is always the same: add ",[18,47773,2798],{}," and either use ",[18,47776,12501],{}," or pass ",[18,47779,36444],{}," on the ",[18,47782,8551],{}," call.",[41,47785,12870],{"id":12869},[46,47787,47788,47797,47802],{},[49,47789,47790,47792,47793,47796],{},[3163,47791,18018],{"href":4839}," — if you're coming from ",[18,47794,47795],{},"pdf.AddUTF8Font"," and want the full migration map",[49,47798,47799,47801],{},[3163,47800,4790],{"href":3381}," — how gpdf compares to gofpdf, gopdf, Maroto, and unipdf on CJK",[49,47803,47804,47807,47808,47810],{},[3163,47805,40308],{"href":40306,"rel":47806},[3167]," — the full ",[18,47809,2798],{}," reference including variant naming rules",[41,47812,4794],{"id":4793},[14,47814,6933],{},[109,47816,47817],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,47818,47819],{"__ignoreMap":114},[118,47820,47821,47823,47825],{"class":120,"line":121},[118,47822,113],{"class":128},[118,47824,3156],{"class":433},[118,47826,3159],{"class":433},[14,47828,47829,3169,47832],{},[3163,47830,3168],{"href":3165,"rel":47831},[3167],[3163,47833,3174],{"href":3172,"rel":47834},[3167],[3176,47836,36541],{},{"title":114,"searchDepth":132,"depth":132,"links":47838},[47839,47840,47841,47842,47843,47844,47845,47846,47847],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":35515,"depth":132,"text":35516},{"id":47110,"depth":132,"text":47111},{"id":47141,"depth":132,"text":47142},{"id":47313,"depth":132,"text":47314},{"id":47755,"depth":132,"text":47756},{"id":12869,"depth":132,"text":12870},{"id":4793,"depth":132,"text":4794},"Register a Japanese TrueType font with gpdf.WithFont at document construction. Three lines, subset embedding happens automatically, no CGO.",{"name":47850,"totalTime":6973,"tools":47851,"steps":47853},"Embed a Japanese TrueType font in a gpdf document",[3202,47852],"NotoSansJP-Regular.ttf (or any CJK-capable TTF)",[47854,47856,47858,47861],{"name":22082,"text":47855},"Read the NotoSansJP-Regular.ttf file with os.ReadFile into a []byte at program start. Embedding with //go:embed works too if you want the font compiled into the binary.",{"name":36568,"text":47857},"Pass gpdf.WithFont(\"NotoSansJP\", fontBytes) to gpdf.NewDocument. The family name is arbitrary — use whatever you'll reference later. gpdf subsets the glyph table at render time.",{"name":47859,"text":47860},"Set it as the default font","Add gpdf.WithDefaultFont(\"NotoSansJP\", 12) so every c.Text call uses the Japanese font without an explicit FontFamily option.",{"name":47862,"text":47863},"Write Japanese text and generate the PDF","Call c.Text(\"こんにちは、世界。\") inside a column. Then doc.Generate() returns []byte, which you write to disk with os.WriteFile.",{},{"title":9792,"description":47848},"blog/003.embed-japanese-font",[6996,9860,3226],"oIQ3huT9bjHomFsJMkF-dLDyOMS5CiA5Kkq43cuS0RU",{"id":47870,"title":33001,"author":47871,"body":47872,"date":46550,"description":49195,"draft":3196,"extension":3197,"howTo":49196,"image":3220,"meta":49211,"navigation":135,"path":33000,"seo":49212,"stem":49213,"tags":49214,"updated":3220,"__hash__":49215},"blog/blog/004.noto-sans-jp-with-gpdf.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":47873,"toc":49182},[47874,47876,47883,47885,47901,47903,48407,48421,48425,48435,48459,48464,48487,48490,48494,48497,48564,48584,48595,48599,48602,48747,48761,48764,48832,48842,48846,48849,48852,48912,48915,48918,48922,48925,48930,48942,48950,48963,48967,48970,49120,49127,49129,49156,49158,49160,49172,49180],[41,47875,4879],{"id":4878},[14,47877,47878,47879,47882],{},"You want Japanese text in a ",[3163,47880,1587],{"href":3165,"rel":47881},[3167]," document, you've picked Noto Sans JP — Google's free, SIL-OFL-licensed sans-serif that covers the full JIS range — and you want to know the three things nobody spells out: which file to download, which weights to register, and the one footgun hiding in the zip.",[41,47884,44],{"id":43},[14,47886,47887,47888,4919,47891,35502,47893,47896,47897,47900],{},"Use the ",[1629,47889,47890],{},"static",[18,47892,9552],{},[18,47894,47895],{},"static/"," folder inside the Google Fonts zip. Not the variable font at the root. Pass it to ",[18,47898,47899],{},"gpdf.WithFont(\"NotoSansJP\", bytes)"," and set it as the default. gpdf subsets the ~17,000 glyphs down to whatever you rendered — an invoice typically carries 20–40 KB of font data in the final PDF.",[41,47902,35516],{"id":35515},[109,47904,47906],{"className":111,"code":47905,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    font, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(gpdf.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", font),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"請求書\", template.FontSize(28), template.Bold())\n            c.Text(\"Noto Sans JP、これで十分。\")\n        })\n    })\n\n    data, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", data, 0o644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[18,47907,47908,47914,47918,47924,47932,47940,47944,47952,47960,47968,47972,47976,47986,48012,48024,48038,48042,48046,48060,48078,48108,48130,48152,48156,48160,48174,48198,48228,48266,48285,48289,48293,48297,48315,48327,48341,48345,48385,48399,48403],{"__ignoreMap":114},[118,47909,47910,47912],{"class":120,"line":121},[118,47911,125],{"class":124},[118,47913,129],{"class":128},[118,47915,47916],{"class":120,"line":132},[118,47917,136],{"emptyLinePlaceholder":135},[118,47919,47920,47922],{"class":120,"line":139},[118,47921,143],{"class":142},[118,47923,146],{"class":124},[118,47925,47926,47928,47930],{"class":120,"line":149},[118,47927,152],{"class":124},[118,47929,5303],{"class":128},[118,47931,158],{"class":124},[118,47933,47934,47936,47938],{"class":120,"line":161},[118,47935,152],{"class":124},[118,47937,155],{"class":128},[118,47939,158],{"class":124},[118,47941,47942],{"class":120,"line":166},[118,47943,136],{"emptyLinePlaceholder":135},[118,47945,47946,47948,47950],{"class":120,"line":176},[118,47947,152],{"class":124},[118,47949,3203],{"class":128},[118,47951,158],{"class":124},[118,47953,47954,47956,47958],{"class":120,"line":186},[118,47955,152],{"class":124},[118,47957,171],{"class":128},[118,47959,158],{"class":124},[118,47961,47962,47964,47966],{"class":120,"line":196},[118,47963,152],{"class":124},[118,47965,191],{"class":128},[118,47967,158],{"class":124},[118,47969,47970],{"class":120,"line":202},[118,47971,199],{"class":124},[118,47973,47974],{"class":120,"line":207},[118,47975,136],{"emptyLinePlaceholder":135},[118,47977,47978,47980,47982,47984],{"class":120,"line":223},[118,47979,210],{"class":124},[118,47981,214],{"class":213},[118,47983,217],{"class":124},[118,47985,220],{"class":124},[118,47987,47988,47990,47992,47994,47996,47998,48000,48002,48004,48006,48008,48010],{"class":120,"line":244},[118,47989,35604],{"class":226},[118,47991,395],{"class":124},[118,47993,1391],{"class":226},[118,47995,230],{"class":124},[118,47997,1447],{"class":226},[118,47999,25],{"class":124},[118,48001,9545],{"class":213},[118,48003,255],{"class":124},[118,48005,430],{"class":124},[118,48007,9552],{"class":433},[118,48009,430],{"class":124},[118,48011,199],{"class":124},[118,48013,48014,48016,48018,48020,48022],{"class":120,"line":269},[118,48015,1408],{"class":142},[118,48017,1391],{"class":226},[118,48019,1413],{"class":124},[118,48021,1416],{"class":124},[118,48023,220],{"class":124},[118,48025,48026,48028,48030,48032,48034,48036],{"class":120,"line":306},[118,48027,5818],{"class":226},[118,48029,25],{"class":124},[118,48031,5823],{"class":213},[118,48033,255],{"class":124},[118,48035,1429],{"class":226},[118,48037,199],{"class":124},[118,48039,48040],{"class":120,"line":312},[118,48041,1375],{"class":124},[118,48043,48044],{"class":120,"line":317},[118,48045,136],{"emptyLinePlaceholder":135},[118,48047,48048,48050,48052,48054,48056,48058],{"class":120,"line":350},[118,48049,227],{"class":226},[118,48051,230],{"class":124},[118,48053,3595],{"class":226},[118,48055,25],{"class":124},[118,48057,3600],{"class":213},[118,48059,241],{"class":124},[118,48061,48062,48064,48066,48068,48070,48072,48074,48076],{"class":120,"line":379},[118,48063,3607],{"class":226},[118,48065,25],{"class":124},[118,48067,252],{"class":213},[118,48069,255],{"class":124},[118,48071,1587],{"class":226},[118,48073,25],{"class":124},[118,48075,263],{"class":226},[118,48077,266],{"class":124},[118,48079,48080,48082,48084,48086,48088,48090,48092,48094,48096,48098,48100,48102,48104,48106],{"class":120,"line":417},[118,48081,3607],{"class":226},[118,48083,25],{"class":124},[118,48085,276],{"class":213},[118,48087,255],{"class":124},[118,48089,258],{"class":226},[118,48091,25],{"class":124},[118,48093,285],{"class":213},[118,48095,255],{"class":124},[118,48097,258],{"class":226},[118,48099,25],{"class":124},[118,48101,294],{"class":213},[118,48103,255],{"class":124},[118,48105,300],{"class":299},[118,48107,303],{"class":124},[118,48109,48110,48112,48114,48116,48118,48120,48122,48124,48126,48128],{"class":120,"line":466},[118,48111,3607],{"class":226},[118,48113,25],{"class":124},[118,48115,2798],{"class":213},[118,48117,255],{"class":124},[118,48119,430],{"class":124},[118,48121,2805],{"class":433},[118,48123,430],{"class":124},[118,48125,395],{"class":124},[118,48127,35744],{"class":226},[118,48129,266],{"class":124},[118,48131,48132,48134,48136,48138,48140,48142,48144,48146,48148,48150],{"class":120,"line":472},[118,48133,3607],{"class":226},[118,48135,25],{"class":124},[118,48137,12501],{"class":213},[118,48139,255],{"class":124},[118,48141,430],{"class":124},[118,48143,2805],{"class":433},[118,48145,430],{"class":124},[118,48147,395],{"class":124},[118,48149,14386],{"class":299},[118,48151,266],{"class":124},[118,48153,48154],{"class":120,"line":503},[118,48155,309],{"class":124},[118,48157,48158],{"class":120,"line":537},[118,48159,136],{"emptyLinePlaceholder":135},[118,48161,48162,48164,48166,48168,48170,48172],{"class":120,"line":566},[118,48163,5431],{"class":226},[118,48165,230],{"class":124},[118,48167,1185],{"class":226},[118,48169,25],{"class":124},[118,48171,1190],{"class":213},[118,48173,1193],{"class":124},[118,48175,48176,48178,48180,48182,48184,48186,48188,48190,48192,48194,48196],{"class":120,"line":571},[118,48177,5494],{"class":226},[118,48179,25],{"class":124},[118,48181,358],{"class":213},[118,48183,328],{"class":124},[118,48185,363],{"class":331},[118,48187,334],{"class":124},[118,48189,337],{"class":128},[118,48191,25],{"class":124},[118,48193,372],{"class":128},[118,48195,345],{"class":124},[118,48197,220],{"class":124},[118,48199,48200,48202,48204,48206,48208,48210,48212,48214,48216,48218,48220,48222,48224,48226],{"class":120,"line":577},[118,48201,1737],{"class":226},[118,48203,25],{"class":124},[118,48205,387],{"class":213},[118,48207,255],{"class":124},[118,48209,20],{"class":299},[118,48211,395],{"class":124},[118,48213,398],{"class":124},[118,48215,401],{"class":331},[118,48217,334],{"class":124},[118,48219,337],{"class":128},[118,48221,25],{"class":124},[118,48223,410],{"class":128},[118,48225,345],{"class":124},[118,48227,220],{"class":124},[118,48229,48230,48232,48234,48236,48238,48240,48242,48244,48246,48248,48250,48252,48254,48256,48258,48260,48262,48264],{"class":120,"line":602},[118,48231,1768],{"class":226},[118,48233,25],{"class":124},[118,48235,425],{"class":213},[118,48237,255],{"class":124},[118,48239,430],{"class":124},[118,48241,39532],{"class":433},[118,48243,430],{"class":124},[118,48245,395],{"class":124},[118,48247,233],{"class":226},[118,48249,25],{"class":124},[118,48251,455],{"class":213},[118,48253,255],{"class":124},[118,48255,7462],{"class":299},[118,48257,1280],{"class":124},[118,48259,233],{"class":226},[118,48261,25],{"class":124},[118,48263,445],{"class":213},[118,48265,1289],{"class":124},[118,48267,48268,48270,48272,48274,48276,48278,48281,48283],{"class":120,"line":633},[118,48269,1768],{"class":226},[118,48271,25],{"class":124},[118,48273,425],{"class":213},[118,48275,255],{"class":124},[118,48277,430],{"class":124},[118,48279,48280],{"class":433},"Noto Sans JP、これで十分。",[118,48282,430],{"class":124},[118,48284,199],{"class":124},[118,48286,48287],{"class":120,"line":668},[118,48288,574],{"class":124},[118,48290,48291],{"class":120,"line":693},[118,48292,706],{"class":124},[118,48294,48295],{"class":120,"line":698},[118,48296,136],{"emptyLinePlaceholder":135},[118,48298,48299,48301,48303,48305,48307,48309,48311,48313],{"class":120,"line":703},[118,48300,5787],{"class":226},[118,48302,395],{"class":124},[118,48304,1391],{"class":226},[118,48306,230],{"class":124},[118,48308,1185],{"class":226},[118,48310,25],{"class":124},[118,48312,1400],{"class":213},[118,48314,1193],{"class":124},[118,48316,48317,48319,48321,48323,48325],{"class":120,"line":709},[118,48318,1408],{"class":142},[118,48320,1391],{"class":226},[118,48322,1413],{"class":124},[118,48324,1416],{"class":124},[118,48326,220],{"class":124},[118,48328,48329,48331,48333,48335,48337,48339],{"class":120,"line":714},[118,48330,5818],{"class":226},[118,48332,25],{"class":124},[118,48334,5823],{"class":213},[118,48336,255],{"class":124},[118,48338,1429],{"class":226},[118,48340,199],{"class":124},[118,48342,48343],{"class":120,"line":740},[118,48344,1375],{"class":124},[118,48346,48347,48349,48351,48353,48355,48357,48359,48361,48363,48365,48367,48369,48371,48373,48375,48377,48379,48381,48383],{"class":120,"line":765},[118,48348,1408],{"class":142},[118,48350,1391],{"class":226},[118,48352,230],{"class":124},[118,48354,1447],{"class":226},[118,48356,25],{"class":124},[118,48358,1452],{"class":213},[118,48360,255],{"class":124},[118,48362,430],{"class":124},[118,48364,4015],{"class":433},[118,48366,430],{"class":124},[118,48368,395],{"class":124},[118,48370,5859],{"class":226},[118,48372,395],{"class":124},[118,48374,1471],{"class":299},[118,48376,7902],{"class":124},[118,48378,1391],{"class":226},[118,48380,1413],{"class":124},[118,48382,1416],{"class":124},[118,48384,220],{"class":124},[118,48386,48387,48389,48391,48393,48395,48397],{"class":120,"line":796},[118,48388,5818],{"class":226},[118,48390,25],{"class":124},[118,48392,5823],{"class":213},[118,48394,255],{"class":124},[118,48396,1429],{"class":226},[118,48398,199],{"class":124},[118,48400,48401],{"class":120,"line":819},[118,48402,1375],{"class":124},[118,48404,48405],{"class":120,"line":851},[118,48406,1479],{"class":124},[14,48408,48409,48410,39727,48413,39730,48416,48418,48419,25],{},"Download the Noto Sans JP zip from ",[3163,48411,38646],{"href":36478,"rel":48412},[3167],[18,48414,48415],{},"static/NotoSansJP-Regular.ttf",[18,48417,102],{},", and run ",[18,48420,106],{},[41,48422,48424],{"id":48423},"grab-the-static-ttf-not-the-variable-font","Grab the static TTF, not the variable font",[14,48426,48427,48428,17968,48431,48434],{},"Open the Google Fonts page, hit ",[1629,48429,48430],{},"Get font",[1629,48432,48433],{},"Download all",", unzip. You get two things that look interchangeable and aren't:",[46,48436,48437,48447],{},[49,48438,48439,48442,48443,48446],{},[18,48440,48441],{},"NotoSansJP-VariableFont_wght.ttf"," at the root — the ",[1629,48444,48445],{},"variable"," font, ~7 MB, carries every weight between 100 and 900 in a single file",[49,48448,48449,48451,48452,1490,48455,48458],{},[18,48450,47895],{}," — nine separate TTFs, ",[18,48453,48454],{},"NotoSansJP-Thin.ttf",[18,48456,48457],{},"NotoSansJP-Black.ttf",", each ~5 MB",[14,48460,48461,48462,25],{},"Pick from ",[18,48463,47895],{},[14,48465,48466,48467,9386,48469,48471,48472,3096,48475,48478,48479,48482,48483,48486],{},"gpdf's TrueType parser is deliberately scoped. It handles glyph outlines, composite glyphs, ",[18,48468,21331],{},[18,48470,21340],{}," — the tables you need to render fixed-weight type. It does not evaluate ",[18,48473,48474],{},"fvar",[18,48476,48477],{},"gvar",", or ",[18,48480,48481],{},"HVAR",", which are the OpenType tables that make variable fonts actually variable. Hand it the ",[18,48484,48485],{},"VariableFont_wght.ttf"," and the parser either errors out or falls through to the default instance glyphs, silently ignoring any weight axis you thought you were setting.",[14,48488,48489],{},"The file-size math also argues against it. A variable font bundles every weight's outlines — that's the whole point. If you use only Regular, you're paying for eight weights of outline data that never render. Static Regular is 5 MB; the variable font is 7 MB. Subsetting will prune both down, but the static file is cleaner input.",[41,48491,48493],{"id":48492},"the-four-lines-that-matter","The four lines that matter",[14,48495,48496],{},"Everything interesting is in the constructor options:",[109,48498,48500],{"className":111,"code":48499,"language":113,"meta":114,"style":114},"doc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", font),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n)\n",[18,48501,48502,48516,48538,48560],{"__ignoreMap":114},[118,48503,48504,48506,48508,48510,48512,48514],{"class":120,"line":121},[118,48505,2760],{"class":226},[118,48507,230],{"class":124},[118,48509,3595],{"class":226},[118,48511,25],{"class":124},[118,48513,3600],{"class":213},[118,48515,241],{"class":124},[118,48517,48518,48520,48522,48524,48526,48528,48530,48532,48534,48536],{"class":120,"line":132},[118,48519,4532],{"class":226},[118,48521,25],{"class":124},[118,48523,2798],{"class":213},[118,48525,255],{"class":124},[118,48527,430],{"class":124},[118,48529,2805],{"class":433},[118,48531,430],{"class":124},[118,48533,395],{"class":124},[118,48535,35744],{"class":226},[118,48537,266],{"class":124},[118,48539,48540,48542,48544,48546,48548,48550,48552,48554,48556,48558],{"class":120,"line":139},[118,48541,4532],{"class":226},[118,48543,25],{"class":124},[118,48545,12501],{"class":213},[118,48547,255],{"class":124},[118,48549,430],{"class":124},[118,48551,2805],{"class":433},[118,48553,430],{"class":124},[118,48555,395],{"class":124},[118,48557,14386],{"class":299},[118,48559,266],{"class":124},[118,48561,48562],{"class":120,"line":149},[118,48563,199],{"class":124},[14,48565,48566,48567,48570,48571,12386,48574,12386,48577,48580,48581,48583],{},"The family name (",[18,48568,48569],{},"\"NotoSansJP\"",") is arbitrary. gpdf uses it as a lookup key — not a filesystem path, not a field read from the font's metadata. Call it ",[18,48572,48573],{},"\"body\"",[18,48575,48576],{},"\"jp\"",[18,48578,48579],{},"\"Noto\""," if that reads better in your codebase. Just keep it consistent with the argument you pass to ",[18,48582,20780],{}," later.",[14,48585,48586,48588,48589,48591,48592,48594],{},[18,48587,12501],{}," is the one that saves you from writing ",[18,48590,38856],{}," on every single ",[18,48593,8551],{}," call. Skip it and gpdf falls back to Helvetica for unlabeled text, and Helvetica covers zero CJK codepoints. You'll get tofu boxes on half the document and spend an hour figuring out why only the headings render.",[41,48596,48598],{"id":48597},"which-weights-do-you-actually-need","Which weights do you actually need?",[14,48600,48601],{},"Most invoices, receipts, and reports need two: Regular and Bold. Register both:",[109,48603,48605],{"className":111,"code":48604,"language":113,"meta":114,"style":114},"reg,  _ := os.ReadFile(\"NotoSansJP-Regular.ttf\")\nbold, _ := os.ReadFile(\"NotoSansJP-Bold.ttf\")\n\ndoc := gpdf.NewDocument(\n    gpdf.WithFont(\"NotoSansJP\", reg),\n    gpdf.WithFont(\"NotoSansJP-Bold\", bold),\n    gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n)\n",[18,48606,48607,48633,48659,48663,48677,48699,48721,48743],{"__ignoreMap":114},[118,48608,48609,48611,48613,48615,48617,48619,48621,48623,48625,48627,48629,48631],{"class":120,"line":121},[118,48610,36232],{"class":226},[118,48612,395],{"class":124},[118,48614,36237],{"class":226},[118,48616,230],{"class":124},[118,48618,1447],{"class":226},[118,48620,25],{"class":124},[118,48622,9545],{"class":213},[118,48624,255],{"class":124},[118,48626,430],{"class":124},[118,48628,9552],{"class":433},[118,48630,430],{"class":124},[118,48632,199],{"class":124},[118,48634,48635,48637,48639,48641,48643,48645,48647,48649,48651,48653,48655,48657],{"class":120,"line":132},[118,48636,21479],{"class":226},[118,48638,395],{"class":124},[118,48640,3999],{"class":226},[118,48642,230],{"class":124},[118,48644,1447],{"class":226},[118,48646,25],{"class":124},[118,48648,9545],{"class":213},[118,48650,255],{"class":124},[118,48652,430],{"class":124},[118,48654,32335],{"class":433},[118,48656,430],{"class":124},[118,48658,199],{"class":124},[118,48660,48661],{"class":120,"line":139},[118,48662,136],{"emptyLinePlaceholder":135},[118,48664,48665,48667,48669,48671,48673,48675],{"class":120,"line":149},[118,48666,2760],{"class":226},[118,48668,230],{"class":124},[118,48670,3595],{"class":226},[118,48672,25],{"class":124},[118,48674,3600],{"class":213},[118,48676,241],{"class":124},[118,48678,48679,48681,48683,48685,48687,48689,48691,48693,48695,48697],{"class":120,"line":161},[118,48680,4532],{"class":226},[118,48682,25],{"class":124},[118,48684,2798],{"class":213},[118,48686,255],{"class":124},[118,48688,430],{"class":124},[118,48690,2805],{"class":433},[118,48692,430],{"class":124},[118,48694,395],{"class":124},[118,48696,36321],{"class":226},[118,48698,266],{"class":124},[118,48700,48701,48703,48705,48707,48709,48711,48713,48715,48717,48719],{"class":120,"line":166},[118,48702,4532],{"class":226},[118,48704,25],{"class":124},[118,48706,2798],{"class":213},[118,48708,255],{"class":124},[118,48710,430],{"class":124},[118,48712,32124],{"class":433},[118,48714,430],{"class":124},[118,48716,395],{"class":124},[118,48718,21621],{"class":226},[118,48720,266],{"class":124},[118,48722,48723,48725,48727,48729,48731,48733,48735,48737,48739,48741],{"class":120,"line":176},[118,48724,4532],{"class":226},[118,48726,25],{"class":124},[118,48728,12501],{"class":213},[118,48730,255],{"class":124},[118,48732,430],{"class":124},[118,48734,2805],{"class":433},[118,48736,430],{"class":124},[118,48738,395],{"class":124},[118,48740,14386],{"class":299},[118,48742,266],{"class":124},[118,48744,48745],{"class":120,"line":186},[118,48746,199],{"class":124},[14,48748,48749,48750,48752,48753,48755,48756,54,48758,48760],{},"With ",[18,48751,36378],{}," registered under that suffix, ",[18,48754,21701],{}," picks it up automatically. Same rule for ",[18,48757,32033],{},[18,48759,31466],{},". Note that Noto Sans JP doesn't ship an italic — CJK fonts generally don't, because the glyph design doesn't have an obvious oblique. If your layout wants italic emphasis on a Japanese run, reach for color, size, or weight instead.",[14,48762,48763],{},"Marketing brochures occasionally want Medium or SemiBold for pull quotes. That's fine. Register them under any suffix and address them by family name directly:",[109,48765,48767],{"className":111,"code":48766,"language":113,"meta":114,"style":114},"gpdf.WithFont(\"NotoSansJP-Medium\", medium)\n// ...\nc.Text(\"見出し\", template.FontFamily(\"NotoSansJP-Medium\"))\n",[18,48768,48769,48793,48797],{"__ignoreMap":114},[118,48770,48771,48773,48775,48777,48779,48781,48784,48786,48788,48791],{"class":120,"line":121},[118,48772,1587],{"class":226},[118,48774,25],{"class":124},[118,48776,2798],{"class":213},[118,48778,255],{"class":124},[118,48780,430],{"class":124},[118,48782,48783],{"class":433},"NotoSansJP-Medium",[118,48785,430],{"class":124},[118,48787,395],{"class":124},[118,48789,48790],{"class":226}," medium",[118,48792,199],{"class":124},[118,48794,48795],{"class":120,"line":132},[118,48796,9606],{"class":3981},[118,48798,48799,48801,48803,48805,48807,48809,48812,48814,48816,48818,48820,48822,48824,48826,48828,48830],{"class":120,"line":139},[118,48800,401],{"class":226},[118,48802,25],{"class":124},[118,48804,425],{"class":213},[118,48806,255],{"class":124},[118,48808,430],{"class":124},[118,48810,48811],{"class":433},"見出し",[118,48813,430],{"class":124},[118,48815,395],{"class":124},[118,48817,233],{"class":226},[118,48819,25],{"class":124},[118,48821,2926],{"class":213},[118,48823,255],{"class":124},[118,48825,430],{"class":124},[118,48827,48783],{"class":433},[118,48829,430],{"class":124},[118,48831,463],{"class":124},[14,48833,48834,48835,1592,48837,1592,48839,48841],{},"The suffix-based Bold/Italic shortcut only hooks up for the literal ",[18,48836,36378],{},[18,48838,32033],{},[18,48840,31466],{}," names. Anything else, you reference by family name.",[41,48843,48845],{"id":48844},"the-size-after-subsetting","The size after subsetting",[14,48847,48848],{},"Noto Sans JP Regular is ~5 MB on disk. That number pushes people into building separate CDNs for font files or post-processing PDFs to strip embedded fonts. Neither is necessary with gpdf.",[14,48850,48851],{},"Here's what actually lands in the PDF:",[1516,48853,48854,48866],{},[1519,48855,48856],{},[1522,48857,48858,48860,48863],{},[1525,48859,22581],{},[1525,48861,48862],{},"Glyphs used",[1525,48864,48865],{},"Font data in PDF",[1532,48867,48868,48879,48890,48901],{},[1522,48869,48870,48873,48876],{},[1537,48871,48872],{},"One-line receipt (~15 chars)",[1537,48874,48875],{},"~14",[1537,48877,48878],{},"~11 KB",[1522,48880,48881,48884,48887],{},[1537,48882,48883],{},"Typical invoice (~200 chars)",[1537,48885,48886],{},"~80",[1537,48888,48889],{},"~28 KB",[1522,48891,48892,48895,48898],{},[1537,48893,48894],{},"10-page report (~8,000 chars)",[1537,48896,48897],{},"~900",[1537,48899,48900],{},"~180 KB",[1522,48902,48903,48906,48909],{},[1537,48904,48905],{},"Dictionary-style dump (full JIS Level 1)",[1537,48907,48908],{},"~6,800",[1537,48910,48911],{},"~2.1 MB",[14,48913,48914],{},"(gpdf v1.0, static subsetting on. Numbers shift by a few KB depending on which glyph IDs fall in the CFF and hmtx tables.)",[14,48916,48917],{},"For an invoice that ends up 50 KB, more than half of the bytes are font data. That's still a rounding error next to the 5 MB you'd embed without subsetting, and the PDF viewer opens it instantly.",[41,48919,48921],{"id":48920},"noto-sans-jp-vs-noto-sans-cjk-jp-dont-mix-these-up","Noto Sans JP vs Noto Sans CJK JP — don't mix these up",[14,48923,48924],{},"There are two Noto families that both claim to handle Japanese, and the naming makes them sound interchangeable. They aren't.",[14,48926,48927,48929],{},[1629,48928,36064],{}," is the one you want. Distributed as TTF, one language, each weight is a separate file. This is the Google Fonts download.",[14,48931,48932,48935,48936,48938,48939,25],{},[1629,48933,48934],{},"Noto Sans CJK JP"," is the pan-CJK super-family. Distributed as OpenType Collection (",[18,48937,36406],{},"), a single file containing Japanese, Simplified Chinese, Traditional Chinese, and Korean glyphs unified in one package. It's what shipped in earlier Noto releases and what you'll find on ",[18,48940,48941],{},"notofonts.github.io/noto-cjk",[14,48943,48944,48945,11424,48947,48949],{},"gpdf supports TTF directly. TTC is a container format — you'd need to pick the right face index before handing bytes to ",[18,48946,2798],{},[18,48948,21331],{}," inside each face is tuned for a specific CJK locale, which means you're making implicit choices about Han unification. Easier to make those choices explicitly by picking the JP-specific TTF.",[14,48951,48952,48953,48956,48957,12386,48960,48962],{},"Starting today? Use Noto Sans JP. Already have ",[18,48954,48955],{},"NotoSansCJK-Regular.ttc"," in a legacy project? Extract the JP face with ",[18,48958,48959],{},"pyftsubset",[18,48961,36193],{}," and check the resulting TTF into your repo as the canonical artifact.",[41,48964,48966],{"id":48965},"embedding-the-font-into-the-binary","Embedding the font into the binary",[14,48968,48969],{},"PDF generators usually run in containers, and the cleanest way to ship the font is to compile it in:",[109,48971,48973],{"className":111,"code":48972,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    _ \"embed\"\n\n    \"github.com/gpdf-dev/gpdf\"\n)\n\n//go:embed NotoSansJP-Regular.ttf\nvar notoJP []byte\n\nfunc main() {\n    doc := gpdf.NewDocument(\n        gpdf.WithFont(\"NotoSansJP\", notoJP),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 11),\n    )\n    // ...\n}\n",[18,48974,48975,48981,48985,48991,49001,49005,49013,49017,49021,49025,49035,49039,49049,49063,49085,49107,49111,49116],{"__ignoreMap":114},[118,48976,48977,48979],{"class":120,"line":121},[118,48978,125],{"class":124},[118,48980,129],{"class":128},[118,48982,48983],{"class":120,"line":132},[118,48984,136],{"emptyLinePlaceholder":135},[118,48986,48987,48989],{"class":120,"line":139},[118,48988,143],{"class":142},[118,48990,146],{"class":124},[118,48992,48993,48995,48997,48999],{"class":120,"line":149},[118,48994,1442],{"class":226},[118,48996,430],{"class":124},[118,48998,21730],{"class":128},[118,49000,158],{"class":124},[118,49002,49003],{"class":120,"line":161},[118,49004,136],{"emptyLinePlaceholder":135},[118,49006,49007,49009,49011],{"class":120,"line":166},[118,49008,152],{"class":124},[118,49010,3203],{"class":128},[118,49012,158],{"class":124},[118,49014,49015],{"class":120,"line":176},[118,49016,199],{"class":124},[118,49018,49019],{"class":120,"line":186},[118,49020,136],{"emptyLinePlaceholder":135},[118,49022,49023],{"class":120,"line":196},[118,49024,17404],{"class":3981},[118,49026,49027,49029,49031,49033],{"class":120,"line":202},[118,49028,16492],{"class":124},[118,49030,17411],{"class":226},[118,49032,16498],{"class":124},[118,49034,16501],{"class":1130},[118,49036,49037],{"class":120,"line":207},[118,49038,136],{"emptyLinePlaceholder":135},[118,49040,49041,49043,49045,49047],{"class":120,"line":223},[118,49042,210],{"class":124},[118,49044,214],{"class":213},[118,49046,217],{"class":124},[118,49048,220],{"class":124},[118,49050,49051,49053,49055,49057,49059,49061],{"class":120,"line":244},[118,49052,227],{"class":226},[118,49054,230],{"class":124},[118,49056,3595],{"class":226},[118,49058,25],{"class":124},[118,49060,3600],{"class":213},[118,49062,241],{"class":124},[118,49064,49065,49067,49069,49071,49073,49075,49077,49079,49081,49083],{"class":120,"line":269},[118,49066,3607],{"class":226},[118,49068,25],{"class":124},[118,49070,2798],{"class":213},[118,49072,255],{"class":124},[118,49074,430],{"class":124},[118,49076,2805],{"class":433},[118,49078,430],{"class":124},[118,49080,395],{"class":124},[118,49082,17502],{"class":226},[118,49084,266],{"class":124},[118,49086,49087,49089,49091,49093,49095,49097,49099,49101,49103,49105],{"class":120,"line":306},[118,49088,3607],{"class":226},[118,49090,25],{"class":124},[118,49092,12501],{"class":213},[118,49094,255],{"class":124},[118,49096,430],{"class":124},[118,49098,2805],{"class":433},[118,49100,430],{"class":124},[118,49102,395],{"class":124},[118,49104,14386],{"class":299},[118,49106,266],{"class":124},[118,49108,49109],{"class":120,"line":312},[118,49110,309],{"class":124},[118,49112,49113],{"class":120,"line":317},[118,49114,49115],{"class":3981},"    // ...\n",[118,49117,49118],{"class":120,"line":350},[118,49119,1479],{"class":124},[14,49121,49122,49123,49126],{},"Binary grows from ~8 MB to ~13 MB. In return, your Docker image has one artifact instead of two, ",[18,49124,49125],{},"COPY --from=builder /app /app"," is all you need, and nobody can ship a broken container missing the font file. For a batch job generating thousands of PDFs a day, this is the right default.",[41,49128,12870],{"id":12869},[46,49130,49131,49136,49143,49148],{},[49,49132,49133,49135],{},[3163,49134,9792],{"href":9791}," — the general recipe, applies to any CJK TTF",[49,49137,49138,49140,49141],{},[3163,49139,18018],{"href":4839}," — migration map from ",[18,49142,47795],{},[49,49144,49145,49147],{},[3163,49146,4790],{"href":3381}," — how gpdf compares on CJK specifically",[49,49149,49150,47807,49153,49155],{},[3163,49151,40308],{"href":40306,"rel":49152},[3167],[18,49154,2798],{}," reference and variant naming rules",[41,49157,4794],{"id":4793},[14,49159,6933],{},[109,49161,49162],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,49163,49164],{"__ignoreMap":114},[118,49165,49166,49168,49170],{"class":120,"line":121},[118,49167,113],{"class":128},[118,49169,3156],{"class":433},[118,49171,3159],{"class":433},[14,49173,49174,3169,49177],{},[3163,49175,3168],{"href":3165,"rel":49176},[3167],[3163,49178,3174],{"href":3172,"rel":49179},[3167],[3176,49181,22061],{},{"title":114,"searchDepth":132,"depth":132,"links":49183},[49184,49185,49186,49187,49188,49189,49190,49191,49192,49193,49194],{"id":4878,"depth":132,"text":4879},{"id":43,"depth":132,"text":44},{"id":35515,"depth":132,"text":35516},{"id":48423,"depth":132,"text":48424},{"id":48492,"depth":132,"text":48493},{"id":48597,"depth":132,"text":48598},{"id":48844,"depth":132,"text":48845},{"id":48920,"depth":132,"text":48921},{"id":48965,"depth":132,"text":48966},{"id":12869,"depth":132,"text":12870},{"id":4793,"depth":132,"text":4794},"Register the static NotoSansJP-Regular.ttf with gpdf.WithFont. Skip the variable font — gpdf's pure-Go parser does not read fvar tables. Subsetting lands around 30 KB.",{"name":49197,"totalTime":6973,"tools":49198,"steps":49200},"Use Noto Sans JP as the default font in a gpdf document",[3202,49199],"NotoSansJP-Regular.ttf (static TTF from Google Fonts)",[49201,49204,49206,49208],{"name":49202,"text":49203},"Download the static TTF from Google Fonts","Grab Noto Sans JP from fonts.google.com, unzip the bundle, and pick static/NotoSansJP-Regular.ttf. Do not use the NotoSansJP-VariableFont_wght.ttf sitting at the root.",{"name":36565,"text":49205},"Read NotoSansJP-Regular.ttf with os.ReadFile, or compile it in with //go:embed for a single-artifact binary.",{"name":36568,"text":49207},"Pass gpdf.WithFont(\"NotoSansJP\", fontBytes) and gpdf.WithDefaultFont(\"NotoSansJP\", 11) to gpdf.NewDocument. No AddUTF8Font, no filesystem path.",{"name":49209,"text":49210},"Write Japanese text and generate","Call c.Text(\"請求書\") inside a column. doc.Generate() returns []byte and gpdf subsets only the glyphs you actually used into the final PDF.",{},{"title":33001,"description":49195},"blog/004.noto-sans-jp-with-gpdf",[6996,9860,3226],"7rP-alXR4JUGZHoH-tuUYwIr44CiQCqi1txUETXq-rI",{"id":49217,"title":18018,"author":49218,"body":49219,"date":53846,"description":53847,"draft":3196,"extension":3197,"howTo":53848,"image":3220,"meta":53869,"navigation":135,"path":4839,"seo":53870,"stem":53871,"tags":53872,"updated":3220,"__hash__":53873},"blog/blog/001.gofpdf-migration.md",{"name":3233,"url":3234,"avatar":3235},{"type":11,"value":49220,"toc":53830},[49221,49223,49238,49248,49251,49263,49266,49270,49276,49282,49289,49292,49296,49304,49330,49336,49338,49341,49590,49602,49632,49636,49639,49644,49815,49819,50199,50208,50220,50224,50234,50238,50945,50948,50952,51304,51318,51321,51325,51331,51335,51493,51499,51503,51944,51947,51956,51959,51962,51964,51982,51986,52295,52300,52304,52949,52963,52967,52980,52984,53246,53250,53568,53584,53588,53597,53673,53676,53679,53683,53686,53725,53728,53730,53736,53754,53763,53775,53781,53783,53785,53797,53805,53807,53827],[41,49222,44],{"id":43},[14,49224,49225,49227,49228,49230,49231,49233,49234,49237],{},[1629,49226,1587],{}," is a pure-Go, zero-dependency PDF library that handles CJK natively (no ",[18,49229,2745],{}," dance), uses a 12-column grid instead of pixel-pushing with ",[18,49232,12972],{},", and runs roughly ",[1629,49235,49236],{},"10× faster than gofpdf"," on the same workloads. The migration is mostly about replacing imperative cursor calls with declarative builders. This guide walks the mapping with five before/after pairs.",[14,49239,49240,49241,49244,49245,49247],{},"A teammate opened a fresh Go project last week, ran ",[18,49242,49243],{},"go get github.com/jung-kurt/gofpdf",", and pinged me ten minutes later with a screenshot of the GitHub banner: ",[1629,49246,41711],{}," Then a follow-up: \"Wait, the fork is archived too?\"",[14,49249,49250],{},"Yes. Both of them.",[14,49252,49253,49255,49256,49259,49260,49262],{},[18,49254,35345],{}," was archived on ",[1629,49257,49258],{},"September 8, 2021",". The community fork at ",[18,49261,1557],{}," shipped its last release in 2023 and was archived in 2025. The Go PDF library that two thirds of Stack Overflow answers still point to has been read-only for over four years, and the fork that was supposed to replace it is gone too.",[14,49264,49265],{},"If you have a gofpdf codebase in production, this post is a migration map. If you're starting a new project and reflexively reached for gofpdf because that's what the search results showed, this is the alternative.",[41,49267,49269],{"id":49268},"why-gofpdf-is-actually-staying-dead","Why gofpdf is actually staying dead",[14,49271,49272,49273,49275],{},"Open-source libraries don't always die. Sometimes the maintainer steps back and someone else picks it up. That's what most people assumed would happen with gofpdf — and for a while, it did. The community fork at ",[18,49274,1557],{}," reorganized the code, fixed a few long-standing bugs, accepted PRs, and felt like a genuine continuation.",[14,49277,49278,49279],{},"Then in early 2025 the fork was archived too. The README now reads, in part: ",[4744,49280,49281],{},"\"This project is no longer actively maintained. Consider using a different library.\"",[14,49283,49284,49285,49288],{},"The reason matters less than the consequence: every Go project that depends on gofpdf is now sitting on ",[1629,49286,49287],{},"two layers"," of unmaintained code. Security issues won't be patched. The PDF 2.0 spec landed in 2020 and gofpdf still doesn't support most of what changed. Go 1.25's loop variable semantics work fine with gofpdf today, but anything that breaks tomorrow is on you to fix in a fork.",[14,49290,49291],{},"This isn't a \"the library has bugs\" problem. It's a supply chain problem.",[41,49293,49295],{"id":49294},"what-people-actually-use-gofpdf-for","What people actually use gofpdf for",[14,49297,49298,49299,54,49301,49303],{},"Before getting into the mapping, it helps to be specific about the workloads that actually get migrated. From the issue trackers and Stack Overflow questions on both ",[18,49300,35345],{},[18,49302,1557],{},", the dominant uses are:",[1624,49305,49306,49312,49318,49324],{},[49,49307,49308,49311],{},[1629,49309,49310],{},"Invoices and receipts"," — header, customer block, line items table, totals, footer.",[49,49313,49314,49317],{},[1629,49315,49316],{},"Reports"," — multi-page documents with repeating headers, page numbers, charts as images.",[49,49319,49320,49323],{},[1629,49321,49322],{},"Forms and certificates"," — fixed-position text overlaid on a template.",[49,49325,49326,49329],{},[1629,49327,49328],{},"CJK documents"," — invoices and shipping labels in Japanese, Chinese, Korean.",[14,49331,49332,49333,49335],{},"The first three are well-served by gpdf's builder API. The fourth — CJK — is where gpdf has the largest gap over gofpdf. gofpdf required you to call ",[18,49334,2745],{},", manage a TTF file path, and hope your text didn't contain characters outside the basic plane. gpdf treats CJK as a first-class case: register a TrueType font, write Japanese, get a PDF.",[41,49337,13069],{"id":13068},[14,49339,49340],{},"The table below is the cheat sheet. Sections after it walk five concrete before/after pairs.",[1516,49342,49343,49353],{},[1519,49344,49345],{},[1522,49346,49347,49349,49351],{},[1525,49348,13081],{},[1525,49350,1539],{},[1525,49352,1587],{},[1532,49354,49355,49370,49389,49409,49425,49439,49455,49473,49488,49502,49519,49533,49547,49563,49577],{},[1522,49356,49357,49360,49365],{},[1537,49358,49359],{},"Create a document",[1537,49361,49362],{},[18,49363,49364],{},"gofpdf.New(\"P\", \"mm\", \"A4\", \"\")",[1537,49366,49367],{},[18,49368,49369],{},"gpdf.NewDocument(gpdf.WithPageSize(document.A4))",[1522,49371,49372,49375,49379],{},[1537,49373,49374],{},"Add a page",[1537,49376,49377],{},[18,49378,13127],{},[1537,49380,49381,4919,49383],{},[18,49382,11460],{},[4744,49384,49385,49386,345],{},"(returns a ",[18,49387,49388],{},"*PageBuilder",[1522,49390,49391,49394,49399],{},[1537,49392,49393],{},"Set a font",[1537,49395,49396],{},[18,49397,49398],{},"pdf.SetFont(\"Arial\", \"B\", 16)",[1537,49400,49401,3096,49403,3096,49405,49408],{},[18,49402,20780],{},[18,49404,21701],{},[18,49406,49407],{},"template.FontSize(16)"," as text options",[1522,49410,49411,49414,49419],{},[1537,49412,49413],{},"Register a TTF (CJK)",[1537,49415,49416],{},[18,49417,49418],{},"pdf.AddUTF8Font(\"noto\", \"\", \"NotoSansJP-Regular.ttf\")",[1537,49420,49421,4919,49423],{},[18,49422,43019],{},[4744,49424,13223],{},[1522,49426,49427,49430,49435],{},[1537,49428,49429],{},"Write a single line",[1537,49431,49432],{},[18,49433,49434],{},"pdf.Cell(40, 10, \"hi\")",[1537,49436,49437],{},[18,49438,13165],{},[1522,49440,49441,49444,49449],{},[1537,49442,49443],{},"Write wrapped text",[1537,49445,49446],{},[18,49447,49448],{},"pdf.MultiCell(0, 10, body, \"\", \"L\", false)",[1537,49450,49451,4919,49453],{},[18,49452,13183],{},[4744,49454,13186],{},[1522,49456,49457,49460,49465],{},[1537,49458,49459],{},"Set text color",[1537,49461,49462],{},[18,49463,49464],{},"pdf.SetTextColor(255, 0, 0)",[1537,49466,49467,4919,49470],{},[18,49468,49469],{},"template.TextColor(pdf.Red)",[4744,49471,49472],{},"(per-text option)",[1522,49474,49475,49478,49483],{},[1537,49476,49477],{},"Draw a horizontal rule",[1537,49479,49480],{},[18,49481,49482],{},"pdf.Line(x1, y1, x2, y2)",[1537,49484,49485],{},[18,49486,49487],{},"c.Line(template.LineThickness(document.Pt(1)))",[1522,49489,49490,49493,49498],{},[1537,49491,49492],{},"Embed an image",[1537,49494,49495],{},[18,49496,49497],{},"pdf.ImageOptions(\"logo.png\", x, y, w, h, ...)",[1537,49499,49500],{},[18,49501,22744],{},[1522,49503,49504,49507,49512],{},[1537,49505,49506],{},"Set XY cursor",[1537,49508,49509],{},[18,49510,49511],{},"pdf.SetXY(x, y)",[1537,49513,49514],{},[4744,49515,49516,49517,345],{},"(no equivalent — use rows/cols, or ",[18,49518,17839],{},[1522,49520,49521,49524,49529],{},[1537,49522,49523],{},"Repeating header",[1537,49525,49526],{},[18,49527,49528],{},"pdf.SetHeaderFunc(fn)",[1537,49530,49531],{},[18,49532,53],{},[1522,49534,49535,49538,49543],{},[1537,49536,49537],{},"Repeating footer",[1537,49539,49540],{},[18,49541,49542],{},"pdf.SetFooterFunc(fn)",[1537,49544,49545],{},[18,49546,57],{},[1522,49548,49549,49551,49557],{},[1537,49550,13342],{},[1537,49552,49553,49554],{},"manual: ",[18,49555,49556],{},"pdf.PageNo()",[1537,49558,49559,1592,49561],{},[18,49560,66],{},[18,49562,70],{},[1522,49564,49565,49568,49573],{},[1537,49566,49567],{},"Output to file",[1537,49569,49570],{},[18,49571,49572],{},"pdf.OutputFileAndClose(\"out.pdf\")",[1537,49574,49575],{},[18,49576,13388],{},[1522,49578,49579,49581,49586],{},[1537,49580,13393],{},[1537,49582,49583],{},[18,49584,49585],{},"pdf.Output(w)",[1537,49587,49588],{},[18,49589,13406],{},[14,49591,49592,49593,49596,49597,49599,49600,25],{},"The shape change is the biggest thing: gofpdf is ",[1629,49594,49595],{},"imperative",", gpdf is ",[1629,49598,22878],{},". In gofpdf you push a cursor around the page and write whatever it points at. In gpdf you describe a tree of rows and columns and let the layout engine place things. The first few snippets feel longer in gpdf. By the third you stop missing ",[18,49601,12972],{},[14,49603,49604,49605,3096,49608,3096,49611,49614,49615,3096,49618,3096,49621,3096,49624,49627,49628,49631],{},"A note on units. gofpdf lets you pick a base unit at construction (",[18,49606,49607],{},"\"mm\"",[18,49609,49610],{},"\"pt\"",[18,49612,49613],{},"\"in\"","). gpdf is always points internally and gives you helpers — ",[18,49616,49617],{},"document.Mm(20)",[18,49619,49620],{},"document.Pt(12)",[18,49622,49623],{},"document.Cm(1)",[18,49625,49626],{},"document.In(0.5)"," — for whichever you prefer at the call site. This is closer to CSS than to gofpdf, and once you have a header on every page using ",[18,49629,49630],{},"document.Mm(15)"," margins, you stop thinking about it.",[41,49633,49635],{"id":49634},"before-after-1-the-simplest-possible-pdf","Before / After 1: the simplest possible PDF",[14,49637,49638],{},"The \"hello world\" pair. gofpdf's brevity is what made it so quotable. gpdf's version is a few more lines because it's building a tree, not driving a cursor.",[14,49640,49641],{},[1629,49642,49643],{},"Before — gofpdf:",[109,49645,49647],{"className":111,"code":49646,"language":113,"meta":114,"style":114},"package main\n\nimport \"github.com/jung-kurt/gofpdf\"\n\nfunc main() {\n    pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\n    pdf.AddPage()\n    pdf.SetFont(\"Arial\", \"B\", 24)\n    pdf.Cell(40, 10, \"Hello, World!\")\n    pdf.OutputFileAndClose(\"hello.pdf\")\n}\n",[18,49648,49649,49655,49659,49670,49674,49684,49727,49737,49767,49793,49811],{"__ignoreMap":114},[118,49650,49651,49653],{"class":120,"line":121},[118,49652,125],{"class":124},[118,49654,129],{"class":128},[118,49656,49657],{"class":120,"line":132},[118,49658,136],{"emptyLinePlaceholder":135},[118,49660,49661,49663,49665,49668],{"class":120,"line":139},[118,49662,143],{"class":142},[118,49664,1146],{"class":124},[118,49666,49667],{"class":128},"github.com/jung-kurt/gofpdf",[118,49669,158],{"class":124},[118,49671,49672],{"class":120,"line":149},[118,49673,136],{"emptyLinePlaceholder":135},[118,49675,49676,49678,49680,49682],{"class":120,"line":161},[118,49677,210],{"class":124},[118,49679,214],{"class":213},[118,49681,217],{"class":124},[118,49683,220],{"class":124},[118,49685,49686,49688,49690,49693,49695,49697,49699,49701,49703,49705,49707,49709,49711,49713,49715,49717,49719,49721,49723,49725],{"class":120,"line":166},[118,49687,13507],{"class":226},[118,49689,230],{"class":124},[118,49691,49692],{"class":226}," gofpdf",[118,49694,25],{"class":124},[118,49696,238],{"class":213},[118,49698,255],{"class":124},[118,49700,430],{"class":124},[118,49702,42429],{"class":433},[118,49704,430],{"class":124},[118,49706,395],{"class":124},[118,49708,1146],{"class":124},[118,49710,42438],{"class":433},[118,49712,430],{"class":124},[118,49714,395],{"class":124},[118,49716,1146],{"class":124},[118,49718,263],{"class":433},[118,49720,430],{"class":124},[118,49722,395],{"class":124},[118,49724,13660],{"class":124},[118,49726,199],{"class":124},[118,49728,49729,49731,49733,49735],{"class":120,"line":176},[118,49730,13525],{"class":226},[118,49732,25],{"class":124},[118,49734,1190],{"class":213},[118,49736,1193],{"class":124},[118,49738,49739,49741,49743,49745,49747,49749,49751,49753,49755,49757,49759,49761,49763,49765],{"class":120,"line":186},[118,49740,13525],{"class":226},[118,49742,25],{"class":124},[118,49744,13647],{"class":213},[118,49746,255],{"class":124},[118,49748,430],{"class":124},[118,49750,42479],{"class":433},[118,49752,430],{"class":124},[118,49754,395],{"class":124},[118,49756,1146],{"class":124},[118,49758,42488],{"class":433},[118,49760,430],{"class":124},[118,49762,395],{"class":124},[118,49764,13665],{"class":299},[118,49766,199],{"class":124},[118,49768,49769,49771,49773,49775,49777,49779,49781,49783,49785,49787,49789,49791],{"class":120,"line":196},[118,49770,13525],{"class":226},[118,49772,25],{"class":124},[118,49774,12975],{"class":213},[118,49776,255],{"class":124},[118,49778,9910],{"class":299},[118,49780,395],{"class":124},[118,49782,11241],{"class":299},[118,49784,395],{"class":124},[118,49786,1146],{"class":124},[118,49788,13749],{"class":433},[118,49790,430],{"class":124},[118,49792,199],{"class":124},[118,49794,49795,49797,49799,49801,49803,49805,49807,49809],{"class":120,"line":202},[118,49796,13525],{"class":226},[118,49798,25],{"class":124},[118,49800,37132],{"class":213},[118,49802,255],{"class":124},[118,49804,430],{"class":124},[118,49806,13805],{"class":433},[118,49808,430],{"class":124},[118,49810,199],{"class":124},[118,49812,49813],{"class":120,"line":207},[118,49814,1479],{"class":124},[14,49816,49817],{},[1629,49818,13844],{},[109,49820,49821],{"className":111,"code":13847,"language":113,"meta":114,"style":114},[18,49822,49823,49829,49833,49839,49847,49855,49859,49867,49875,49883,49887,49891,49901,49915,49933,49963,49967,49971,49985,50009,50039,50077,50081,50085,50089,50107,50119,50133,50137,50177,50191,50195],{"__ignoreMap":114},[118,49824,49825,49827],{"class":120,"line":121},[118,49826,125],{"class":124},[118,49828,129],{"class":128},[118,49830,49831],{"class":120,"line":132},[118,49832,136],{"emptyLinePlaceholder":135},[118,49834,49835,49837],{"class":120,"line":139},[118,49836,143],{"class":142},[118,49838,146],{"class":124},[118,49840,49841,49843,49845],{"class":120,"line":149},[118,49842,152],{"class":124},[118,49844,5303],{"class":128},[118,49846,158],{"class":124},[118,49848,49849,49851,49853],{"class":120,"line":161},[118,49850,152],{"class":124},[118,49852,155],{"class":128},[118,49854,158],{"class":124},[118,49856,49857],{"class":120,"line":166},[118,49858,136],{"emptyLinePlaceholder":135},[118,49860,49861,49863,49865],{"class":120,"line":176},[118,49862,152],{"class":124},[118,49864,3203],{"class":128},[118,49866,158],{"class":124},[118,49868,49869,49871,49873],{"class":120,"line":186},[118,49870,152],{"class":124},[118,49872,171],{"class":128},[118,49874,158],{"class":124},[118,49876,49877,49879,49881],{"class":120,"line":196},[118,49878,152],{"class":124},[118,49880,191],{"class":128},[118,49882,158],{"class":124},[118,49884,49885],{"class":120,"line":202},[118,49886,199],{"class":124},[118,49888,49889],{"class":120,"line":207},[118,49890,136],{"emptyLinePlaceholder":135},[118,49892,49893,49895,49897,49899],{"class":120,"line":223},[118,49894,210],{"class":124},[118,49896,214],{"class":213},[118,49898,217],{"class":124},[118,49900,220],{"class":124},[118,49902,49903,49905,49907,49909,49911,49913],{"class":120,"line":244},[118,49904,227],{"class":226},[118,49906,230],{"class":124},[118,49908,3595],{"class":226},[118,49910,25],{"class":124},[118,49912,3600],{"class":213},[118,49914,241],{"class":124},[118,49916,49917,49919,49921,49923,49925,49927,49929,49931],{"class":120,"line":269},[118,49918,3607],{"class":226},[118,49920,25],{"class":124},[118,49922,252],{"class":213},[118,49924,255],{"class":124},[118,49926,258],{"class":226},[118,49928,25],{"class":124},[118,49930,263],{"class":226},[118,49932,266],{"class":124},[118,49934,49935,49937,49939,49941,49943,49945,49947,49949,49951,49953,49955,49957,49959,49961],{"class":120,"line":306},[118,49936,3607],{"class":226},[118,49938,25],{"class":124},[118,49940,276],{"class":213},[118,49942,255],{"class":124},[118,49944,258],{"class":226},[118,49946,25],{"class":124},[118,49948,285],{"class":213},[118,49950,255],{"class":124},[118,49952,258],{"class":226},[118,49954,25],{"class":124},[118,49956,294],{"class":213},[118,49958,255],{"class":124},[118,49960,300],{"class":299},[118,49962,303],{"class":124},[118,49964,49965],{"class":120,"line":312},[118,49966,309],{"class":124},[118,49968,49969],{"class":120,"line":317},[118,49970,136],{"emptyLinePlaceholder":135},[118,49972,49973,49975,49977,49979,49981,49983],{"class":120,"line":350},[118,49974,5431],{"class":226},[118,49976,230],{"class":124},[118,49978,1185],{"class":226},[118,49980,25],{"class":124},[118,49982,1190],{"class":213},[118,49984,1193],{"class":124},[118,49986,49987,49989,49991,49993,49995,49997,49999,50001,50003,50005,50007],{"class":120,"line":379},[118,49988,5494],{"class":226},[118,49990,25],{"class":124},[118,49992,358],{"class":213},[118,49994,328],{"class":124},[118,49996,363],{"class":331},[118,49998,334],{"class":124},[118,50000,337],{"class":128},[118,50002,25],{"class":124},[118,50004,372],{"class":128},[118,50006,345],{"class":124},[118,50008,220],{"class":124},[118,50010,50011,50013,50015,50017,50019,50021,50023,50025,50027,50029,50031,50033,50035,50037],{"class":120,"line":417},[118,50012,1737],{"class":226},[118,50014,25],{"class":124},[118,50016,387],{"class":213},[118,50018,255],{"class":124},[118,50020,20],{"class":299},[118,50022,395],{"class":124},[118,50024,398],{"class":124},[118,50026,401],{"class":331},[118,50028,334],{"class":124},[118,50030,337],{"class":128},[118,50032,25],{"class":124},[118,50034,410],{"class":128},[118,50036,345],{"class":124},[118,50038,220],{"class":124},[118,50040,50041,50043,50045,50047,50049,50051,50053,50055,50057,50059,50061,50063,50065,50067,50069,50071,50073,50075],{"class":120,"line":466},[118,50042,1768],{"class":226},[118,50044,25],{"class":124},[118,50046,425],{"class":213},[118,50048,255],{"class":124},[118,50050,430],{"class":124},[118,50052,13749],{"class":433},[118,50054,430],{"class":124},[118,50056,395],{"class":124},[118,50058,233],{"class":226},[118,50060,25],{"class":124},[118,50062,455],{"class":213},[118,50064,255],{"class":124},[118,50066,3777],{"class":299},[118,50068,1280],{"class":124},[118,50070,233],{"class":226},[118,50072,25],{"class":124},[118,50074,445],{"class":213},[118,50076,1289],{"class":124},[118,50078,50079],{"class":120,"line":472},[118,50080,574],{"class":124},[118,50082,50083],{"class":120,"line":503},[118,50084,706],{"class":124},[118,50086,50087],{"class":120,"line":537},[118,50088,136],{"emptyLinePlaceholder":135},[118,50090,50091,50093,50095,50097,50099,50101,50103,50105],{"class":120,"line":566},[118,50092,5787],{"class":226},[118,50094,395],{"class":124},[118,50096,1391],{"class":226},[118,50098,230],{"class":124},[118,50100,1185],{"class":226},[118,50102,25],{"class":124},[118,50104,1400],{"class":213},[118,50106,1193],{"class":124},[118,50108,50109,50111,50113,50115,50117],{"class":120,"line":571},[118,50110,1408],{"class":142},[118,50112,1391],{"class":226},[118,50114,1413],{"class":124},[118,50116,1416],{"class":124},[118,50118,220],{"class":124},[118,50120,50121,50123,50125,50127,50129,50131],{"class":120,"line":577},[118,50122,5818],{"class":226},[118,50124,25],{"class":124},[118,50126,5823],{"class":213},[118,50128,255],{"class":124},[118,50130,1429],{"class":226},[118,50132,199],{"class":124},[118,50134,50135],{"class":120,"line":602},[118,50136,1375],{"class":124},[118,50138,50139,50141,50143,50145,50147,50149,50151,50153,50155,50157,50159,50161,50163,50165,50167,50169,50171,50173,50175],{"class":120,"line":633},[118,50140,1408],{"class":142},[118,50142,1391],{"class":226},[118,50144,230],{"class":124},[118,50146,1447],{"class":226},[118,50148,25],{"class":124},[118,50150,1452],{"class":213},[118,50152,255],{"class":124},[118,50154,430],{"class":124},[118,50156,13805],{"class":433},[118,50158,430],{"class":124},[118,50160,395],{"class":124},[118,50162,5859],{"class":226},[118,50164,395],{"class":124},[118,50166,1471],{"class":299},[118,50168,7902],{"class":124},[118,50170,1391],{"class":226},[118,50172,1413],{"class":124},[118,50174,1416],{"class":124},[118,50176,220],{"class":124},[118,50178,50179,50181,50183,50185,50187,50189],{"class":120,"line":668},[118,50180,5818],{"class":226},[118,50182,25],{"class":124},[118,50184,5823],{"class":213},[118,50186,255],{"class":124},[118,50188,1429],{"class":226},[118,50190,199],{"class":124},[118,50192,50193],{"class":120,"line":693},[118,50194,1375],{"class":124},[118,50196,50197],{"class":120,"line":698},[118,50198,1479],{"class":124},[14,50200,50201,50202,50204,50205,50207],{},"The grid does the work. ",[18,50203,358],{}," adds a row whose height is determined by its content; ",[18,50206,30021],{}," says \"this column spans all 12 grid columns.\" Same idea as Bootstrap, applied to a PDF page.",[14,50209,50210,50212,50213,50216,50217,50219],{},[18,50211,1400],{}," returns the bytes; ",[18,50214,50215],{},"Render(w)"," streams to an ",[18,50218,35317],{}," if you'd rather not allocate. There's no \"close the file\" step because gpdf doesn't own a file handle.",[41,50221,50223],{"id":50222},"before-after-2-a-table-of-line-items","Before / After 2: a table of line items",[14,50225,50226,50227,50229,50230,50233],{},"Tables are where gofpdf gets verbose. There's no built-in table; you call ",[18,50228,12975],{}," in nested loops, manage your own column widths, and do ",[18,50231,50232],{},"Ln(-1)"," to move to the next row. Half the gofpdf invoice tutorials on the internet are mostly table boilerplate.",[14,50235,50236],{},[1629,50237,49643],{},[109,50239,50241],{"className":111,"code":50240,"language":113,"meta":114,"style":114},"pdf.SetFont(\"Arial\", \"B\", 11)\npdf.SetFillColor(220, 220, 220)\npdf.CellFormat(80, 8, \"Description\", \"1\", 0, \"L\", true, 0, \"\")\npdf.CellFormat(20, 8, \"Qty\",         \"1\", 0, \"C\", true, 0, \"\")\npdf.CellFormat(30, 8, \"Unit\",        \"1\", 0, \"R\", true, 0, \"\")\npdf.CellFormat(30, 8, \"Amount\",      \"1\", 1, \"R\", true, 0, \"\")\n\npdf.SetFont(\"Arial\", \"\", 11)\nitems := [][]string{\n    {\"Frontend dev\", \"40 hrs\", \"$150.00\", \"$6,000.00\"},\n    {\"Backend dev\",  \"60 hrs\", \"$150.00\", \"$9,000.00\"},\n    {\"UI design\",    \"20 hrs\", \"$120.00\", \"$2,400.00\"},\n}\nfor _, row := range items {\n    pdf.CellFormat(80, 8, row[0], \"1\", 0, \"L\", false, 0, \"\")\n    pdf.CellFormat(20, 8, row[1], \"1\", 0, \"C\", false, 0, \"\")\n    pdf.CellFormat(30, 8, row[2], \"1\", 0, \"R\", false, 0, \"\")\n    pdf.CellFormat(30, 8, row[3], \"1\", 1, \"R\", false, 0, \"\")\n}\n",[18,50242,50243,50273,50297,50357,50417,50476,50534,50538,50564,50577,50613,50649,50685,50689,50707,50767,50825,50883,50941],{"__ignoreMap":114},[118,50244,50245,50247,50249,50251,50253,50255,50257,50259,50261,50263,50265,50267,50269,50271],{"class":120,"line":121},[118,50246,550],{"class":226},[118,50248,25],{"class":124},[118,50250,13647],{"class":213},[118,50252,255],{"class":124},[118,50254,430],{"class":124},[118,50256,42479],{"class":433},[118,50258,430],{"class":124},[118,50260,395],{"class":124},[118,50262,1146],{"class":124},[118,50264,42488],{"class":433},[118,50266,430],{"class":124},[118,50268,395],{"class":124},[118,50270,14386],{"class":299},[118,50272,199],{"class":124},[118,50274,50275,50277,50279,50281,50283,50286,50288,50291,50293,50295],{"class":120,"line":132},[118,50276,550],{"class":226},[118,50278,25],{"class":124},[118,50280,11349],{"class":213},[118,50282,255],{"class":124},[118,50284,50285],{"class":299},"220",[118,50287,395],{"class":124},[118,50289,50290],{"class":299}," 220",[118,50292,395],{"class":124},[118,50294,50290],{"class":299},[118,50296,199],{"class":124},[118,50298,50299,50301,50303,50305,50307,50309,50311,50313,50315,50317,50319,50321,50323,50325,50327,50329,50331,50333,50335,50337,50340,50342,50344,50347,50349,50351,50353,50355],{"class":120,"line":139},[118,50300,550],{"class":226},[118,50302,25],{"class":124},[118,50304,11353],{"class":213},[118,50306,255],{"class":124},[118,50308,11834],{"class":299},[118,50310,395],{"class":124},[118,50312,16933],{"class":299},[118,50314,395],{"class":124},[118,50316,1146],{"class":124},[118,50318,14460],{"class":433},[118,50320,430],{"class":124},[118,50322,395],{"class":124},[118,50324,1146],{"class":124},[118,50326,2402],{"class":433},[118,50328,430],{"class":124},[118,50330,395],{"class":124},[118,50332,7344],{"class":299},[118,50334,395],{"class":124},[118,50336,1146],{"class":124},[118,50338,50339],{"class":433},"L",[118,50341,430],{"class":124},[118,50343,395],{"class":124},[118,50345,50346],{"class":12009}," true",[118,50348,395],{"class":124},[118,50350,7344],{"class":299},[118,50352,395],{"class":124},[118,50354,13660],{"class":124},[118,50356,199],{"class":124},[118,50358,50359,50361,50363,50365,50367,50369,50371,50373,50375,50377,50379,50381,50383,50386,50388,50390,50392,50394,50396,50398,50401,50403,50405,50407,50409,50411,50413,50415],{"class":120,"line":149},[118,50360,550],{"class":226},[118,50362,25],{"class":124},[118,50364,11353],{"class":213},[118,50366,255],{"class":124},[118,50368,300],{"class":299},[118,50370,395],{"class":124},[118,50372,16933],{"class":299},[118,50374,395],{"class":124},[118,50376,1146],{"class":124},[118,50378,14469],{"class":433},[118,50380,430],{"class":124},[118,50382,395],{"class":124},[118,50384,50385],{"class":124},"         \"",[118,50387,2402],{"class":433},[118,50389,430],{"class":124},[118,50391,395],{"class":124},[118,50393,7344],{"class":299},[118,50395,395],{"class":124},[118,50397,1146],{"class":124},[118,50399,50400],{"class":433},"C",[118,50402,430],{"class":124},[118,50404,395],{"class":124},[118,50406,50346],{"class":12009},[118,50408,395],{"class":124},[118,50410,7344],{"class":299},[118,50412,395],{"class":124},[118,50414,13660],{"class":124},[118,50416,199],{"class":124},[118,50418,50419,50421,50423,50425,50427,50429,50431,50433,50435,50437,50439,50441,50443,50445,50447,50449,50451,50453,50455,50457,50460,50462,50464,50466,50468,50470,50472,50474],{"class":120,"line":161},[118,50420,550],{"class":226},[118,50422,25],{"class":124},[118,50424,11353],{"class":213},[118,50426,255],{"class":124},[118,50428,18610],{"class":299},[118,50430,395],{"class":124},[118,50432,16933],{"class":299},[118,50434,395],{"class":124},[118,50436,1146],{"class":124},[118,50438,14478],{"class":433},[118,50440,430],{"class":124},[118,50442,395],{"class":124},[118,50444,26887],{"class":124},[118,50446,2402],{"class":433},[118,50448,430],{"class":124},[118,50450,395],{"class":124},[118,50452,7344],{"class":299},[118,50454,395],{"class":124},[118,50456,1146],{"class":124},[118,50458,50459],{"class":433},"R",[118,50461,430],{"class":124},[118,50463,395],{"class":124},[118,50465,50346],{"class":12009},[118,50467,395],{"class":124},[118,50469,7344],{"class":299},[118,50471,395],{"class":124},[118,50473,13660],{"class":124},[118,50475,199],{"class":124},[118,50477,50478,50480,50482,50484,50486,50488,50490,50492,50494,50496,50498,50500,50502,50504,50506,50508,50510,50512,50514,50516,50518,50520,50522,50524,50526,50528,50530,50532],{"class":120,"line":166},[118,50479,550],{"class":226},[118,50481,25],{"class":124},[118,50483,11353],{"class":213},[118,50485,255],{"class":124},[118,50487,18610],{"class":299},[118,50489,395],{"class":124},[118,50491,16933],{"class":299},[118,50493,395],{"class":124},[118,50495,1146],{"class":124},[118,50497,7320],{"class":433},[118,50499,430],{"class":124},[118,50501,395],{"class":124},[118,50503,19828],{"class":124},[118,50505,2402],{"class":433},[118,50507,430],{"class":124},[118,50509,395],{"class":124},[118,50511,7363],{"class":299},[118,50513,395],{"class":124},[118,50515,1146],{"class":124},[118,50517,50459],{"class":433},[118,50519,430],{"class":124},[118,50521,395],{"class":124},[118,50523,50346],{"class":12009},[118,50525,395],{"class":124},[118,50527,7344],{"class":299},[118,50529,395],{"class":124},[118,50531,13660],{"class":124},[118,50533,199],{"class":124},[118,50535,50536],{"class":120,"line":176},[118,50537,136],{"emptyLinePlaceholder":135},[118,50539,50540,50542,50544,50546,50548,50550,50552,50554,50556,50558,50560,50562],{"class":120,"line":186},[118,50541,550],{"class":226},[118,50543,25],{"class":124},[118,50545,13647],{"class":213},[118,50547,255],{"class":124},[118,50549,430],{"class":124},[118,50551,42479],{"class":433},[118,50553,430],{"class":124},[118,50555,395],{"class":124},[118,50557,13660],{"class":124},[118,50559,395],{"class":124},[118,50561,14386],{"class":299},[118,50563,199],{"class":124},[118,50565,50566,50569,50571,50573,50575],{"class":120,"line":196},[118,50567,50568],{"class":226},"items ",[118,50570,230],{"class":124},[118,50572,5121],{"class":124},[118,50574,1131],{"class":1130},[118,50576,7406],{"class":124},[118,50578,50579,50581,50583,50585,50587,50589,50591,50593,50595,50597,50599,50601,50603,50605,50607,50609,50611],{"class":120,"line":202},[118,50580,8119],{"class":124},[118,50582,430],{"class":124},[118,50584,24374],{"class":433},[118,50586,430],{"class":124},[118,50588,395],{"class":124},[118,50590,1146],{"class":124},[118,50592,24383],{"class":433},[118,50594,430],{"class":124},[118,50596,395],{"class":124},[118,50598,1146],{"class":124},[118,50600,24392],{"class":433},[118,50602,430],{"class":124},[118,50604,395],{"class":124},[118,50606,1146],{"class":124},[118,50608,24401],{"class":433},[118,50610,430],{"class":124},[118,50612,8191],{"class":124},[118,50614,50615,50617,50619,50621,50623,50625,50627,50629,50631,50633,50635,50637,50639,50641,50643,50645,50647],{"class":120,"line":207},[118,50616,8119],{"class":124},[118,50618,430],{"class":124},[118,50620,24414],{"class":433},[118,50622,430],{"class":124},[118,50624,395],{"class":124},[118,50626,19796],{"class":124},[118,50628,24423],{"class":433},[118,50630,430],{"class":124},[118,50632,395],{"class":124},[118,50634,1146],{"class":124},[118,50636,24392],{"class":433},[118,50638,430],{"class":124},[118,50640,395],{"class":124},[118,50642,1146],{"class":124},[118,50644,24440],{"class":433},[118,50646,430],{"class":124},[118,50648,8191],{"class":124},[118,50650,50651,50653,50655,50657,50659,50661,50663,50665,50667,50669,50671,50673,50675,50677,50679,50681,50683],{"class":120,"line":223},[118,50652,8119],{"class":124},[118,50654,430],{"class":124},[118,50656,24453],{"class":433},[118,50658,430],{"class":124},[118,50660,395],{"class":124},[118,50662,152],{"class":124},[118,50664,24462],{"class":433},[118,50666,430],{"class":124},[118,50668,395],{"class":124},[118,50670,1146],{"class":124},[118,50672,24471],{"class":433},[118,50674,430],{"class":124},[118,50676,395],{"class":124},[118,50678,1146],{"class":124},[118,50680,24480],{"class":433},[118,50682,430],{"class":124},[118,50684,8191],{"class":124},[118,50686,50687],{"class":120,"line":244},[118,50688,1479],{"class":124},[118,50690,50691,50693,50695,50697,50699,50701,50703,50705],{"class":120,"line":269},[118,50692,14495],{"class":142},[118,50694,14776],{"class":226},[118,50696,395],{"class":124},[118,50698,15544],{"class":226},[118,50700,230],{"class":124},[118,50702,1124],{"class":142},[118,50704,15551],{"class":226},[118,50706,7406],{"class":124},[118,50708,50709,50711,50713,50715,50717,50719,50721,50723,50725,50728,50730,50732,50734,50736,50738,50740,50742,50744,50746,50748,50750,50752,50754,50757,50759,50761,50763,50765],{"class":120,"line":306},[118,50710,13525],{"class":226},[118,50712,25],{"class":124},[118,50714,11353],{"class":213},[118,50716,255],{"class":124},[118,50718,11834],{"class":299},[118,50720,395],{"class":124},[118,50722,16933],{"class":299},[118,50724,395],{"class":124},[118,50726,50727],{"class":226}," row",[118,50729,15332],{"class":124},[118,50731,4159],{"class":299},[118,50733,15337],{"class":124},[118,50735,1146],{"class":124},[118,50737,2402],{"class":433},[118,50739,430],{"class":124},[118,50741,395],{"class":124},[118,50743,7344],{"class":299},[118,50745,395],{"class":124},[118,50747,1146],{"class":124},[118,50749,50339],{"class":433},[118,50751,430],{"class":124},[118,50753,395],{"class":124},[118,50755,50756],{"class":12009}," false",[118,50758,395],{"class":124},[118,50760,7344],{"class":299},[118,50762,395],{"class":124},[118,50764,13660],{"class":124},[118,50766,199],{"class":124},[118,50768,50769,50771,50773,50775,50777,50779,50781,50783,50785,50787,50789,50791,50793,50795,50797,50799,50801,50803,50805,50807,50809,50811,50813,50815,50817,50819,50821,50823],{"class":120,"line":312},[118,50770,13525],{"class":226},[118,50772,25],{"class":124},[118,50774,11353],{"class":213},[118,50776,255],{"class":124},[118,50778,300],{"class":299},[118,50780,395],{"class":124},[118,50782,16933],{"class":299},[118,50784,395],{"class":124},[118,50786,50727],{"class":226},[118,50788,15332],{"class":124},[118,50790,2402],{"class":299},[118,50792,15337],{"class":124},[118,50794,1146],{"class":124},[118,50796,2402],{"class":433},[118,50798,430],{"class":124},[118,50800,395],{"class":124},[118,50802,7344],{"class":299},[118,50804,395],{"class":124},[118,50806,1146],{"class":124},[118,50808,50400],{"class":433},[118,50810,430],{"class":124},[118,50812,395],{"class":124},[118,50814,50756],{"class":12009},[118,50816,395],{"class":124},[118,50818,7344],{"class":299},[118,50820,395],{"class":124},[118,50822,13660],{"class":124},[118,50824,199],{"class":124},[118,50826,50827,50829,50831,50833,50835,50837,50839,50841,50843,50845,50847,50849,50851,50853,50855,50857,50859,50861,50863,50865,50867,50869,50871,50873,50875,50877,50879,50881],{"class":120,"line":317},[118,50828,13525],{"class":226},[118,50830,25],{"class":124},[118,50832,11353],{"class":213},[118,50834,255],{"class":124},[118,50836,18610],{"class":299},[118,50838,395],{"class":124},[118,50840,16933],{"class":299},[118,50842,395],{"class":124},[118,50844,50727],{"class":226},[118,50846,15332],{"class":124},[118,50848,870],{"class":299},[118,50850,15337],{"class":124},[118,50852,1146],{"class":124},[118,50854,2402],{"class":433},[118,50856,430],{"class":124},[118,50858,395],{"class":124},[118,50860,7344],{"class":299},[118,50862,395],{"class":124},[118,50864,1146],{"class":124},[118,50866,50459],{"class":433},[118,50868,430],{"class":124},[118,50870,395],{"class":124},[118,50872,50756],{"class":12009},[118,50874,395],{"class":124},[118,50876,7344],{"class":299},[118,50878,395],{"class":124},[118,50880,13660],{"class":124},[118,50882,199],{"class":124},[118,50884,50885,50887,50889,50891,50893,50895,50897,50899,50901,50903,50905,50907,50909,50911,50913,50915,50917,50919,50921,50923,50925,50927,50929,50931,50933,50935,50937,50939],{"class":120,"line":350},[118,50886,13525],{"class":226},[118,50888,25],{"class":124},[118,50890,11353],{"class":213},[118,50892,255],{"class":124},[118,50894,18610],{"class":299},[118,50896,395],{"class":124},[118,50898,16933],{"class":299},[118,50900,395],{"class":124},[118,50902,50727],{"class":226},[118,50904,15332],{"class":124},[118,50906,688],{"class":299},[118,50908,15337],{"class":124},[118,50910,1146],{"class":124},[118,50912,2402],{"class":433},[118,50914,430],{"class":124},[118,50916,395],{"class":124},[118,50918,7363],{"class":299},[118,50920,395],{"class":124},[118,50922,1146],{"class":124},[118,50924,50459],{"class":433},[118,50926,430],{"class":124},[118,50928,395],{"class":124},[118,50930,50756],{"class":12009},[118,50932,395],{"class":124},[118,50934,7344],{"class":299},[118,50936,395],{"class":124},[118,50938,13660],{"class":124},[118,50940,199],{"class":124},[118,50942,50943],{"class":120,"line":379},[118,50944,1479],{"class":124},[14,50946,50947],{},"Counts widths in your head, and good luck if a description wraps.",[14,50949,50950],{},[1629,50951,13844],{},[109,50953,50954],{"className":111,"code":24249,"language":113,"meta":114,"style":114},[18,50955,50956,50980,51010,51020,51060,51068,51104,51140,51176,51180,51206,51216,51226,51244,51266,51270,51292,51296,51300],{"__ignoreMap":114},[118,50957,50958,50960,50962,50964,50966,50968,50970,50972,50974,50976,50978],{"class":120,"line":121},[118,50959,5910],{"class":226},[118,50961,25],{"class":124},[118,50963,358],{"class":213},[118,50965,328],{"class":124},[118,50967,363],{"class":331},[118,50969,334],{"class":124},[118,50971,337],{"class":128},[118,50973,25],{"class":124},[118,50975,372],{"class":128},[118,50977,345],{"class":124},[118,50979,220],{"class":124},[118,50981,50982,50984,50986,50988,50990,50992,50994,50996,50998,51000,51002,51004,51006,51008],{"class":120,"line":132},[118,50983,5935],{"class":226},[118,50985,25],{"class":124},[118,50987,387],{"class":213},[118,50989,255],{"class":124},[118,50991,20],{"class":299},[118,50993,395],{"class":124},[118,50995,398],{"class":124},[118,50997,401],{"class":331},[118,50999,334],{"class":124},[118,51001,337],{"class":128},[118,51003,25],{"class":124},[118,51005,410],{"class":128},[118,51007,345],{"class":124},[118,51009,220],{"class":124},[118,51011,51012,51014,51016,51018],{"class":120,"line":139},[118,51013,5966],{"class":226},[118,51015,25],{"class":124},[118,51017,4929],{"class":213},[118,51019,241],{"class":124},[118,51021,51022,51024,51026,51028,51030,51032,51034,51036,51038,51040,51042,51044,51046,51048,51050,51052,51054,51056,51058],{"class":120,"line":149},[118,51023,15869],{"class":124},[118,51025,1131],{"class":1130},[118,51027,1134],{"class":124},[118,51029,430],{"class":124},[118,51031,14460],{"class":433},[118,51033,430],{"class":124},[118,51035,395],{"class":124},[118,51037,1146],{"class":124},[118,51039,14469],{"class":433},[118,51041,430],{"class":124},[118,51043,395],{"class":124},[118,51045,1146],{"class":124},[118,51047,14478],{"class":433},[118,51049,430],{"class":124},[118,51051,395],{"class":124},[118,51053,1146],{"class":124},[118,51055,7320],{"class":433},[118,51057,430],{"class":124},[118,51059,8191],{"class":124},[118,51061,51062,51064,51066],{"class":120,"line":161},[118,51063,24360],{"class":124},[118,51065,1131],{"class":1130},[118,51067,7406],{"class":124},[118,51069,51070,51072,51074,51076,51078,51080,51082,51084,51086,51088,51090,51092,51094,51096,51098,51100,51102],{"class":120,"line":166},[118,51071,24369],{"class":124},[118,51073,430],{"class":124},[118,51075,24374],{"class":433},[118,51077,430],{"class":124},[118,51079,395],{"class":124},[118,51081,1146],{"class":124},[118,51083,24383],{"class":433},[118,51085,430],{"class":124},[118,51087,395],{"class":124},[118,51089,1146],{"class":124},[118,51091,24392],{"class":433},[118,51093,430],{"class":124},[118,51095,395],{"class":124},[118,51097,1146],{"class":124},[118,51099,24401],{"class":433},[118,51101,430],{"class":124},[118,51103,8191],{"class":124},[118,51105,51106,51108,51110,51112,51114,51116,51118,51120,51122,51124,51126,51128,51130,51132,51134,51136,51138],{"class":120,"line":176},[118,51107,24369],{"class":124},[118,51109,430],{"class":124},[118,51111,24414],{"class":433},[118,51113,430],{"class":124},[118,51115,395],{"class":124},[118,51117,19796],{"class":124},[118,51119,24423],{"class":433},[118,51121,430],{"class":124},[118,51123,395],{"class":124},[118,51125,1146],{"class":124},[118,51127,24392],{"class":433},[118,51129,430],{"class":124},[118,51131,395],{"class":124},[118,51133,1146],{"class":124},[118,51135,24440],{"class":433},[118,51137,430],{"class":124},[118,51139,8191],{"class":124},[118,51141,51142,51144,51146,51148,51150,51152,51154,51156,51158,51160,51162,51164,51166,51168,51170,51172,51174],{"class":120,"line":186},[118,51143,24369],{"class":124},[118,51145,430],{"class":124},[118,51147,24453],{"class":433},[118,51149,430],{"class":124},[118,51151,395],{"class":124},[118,51153,152],{"class":124},[118,51155,24462],{"class":433},[118,51157,430],{"class":124},[118,51159,395],{"class":124},[118,51161,1146],{"class":124},[118,51163,24471],{"class":433},[118,51165,430],{"class":124},[118,51167,395],{"class":124},[118,51169,1146],{"class":124},[118,51171,24480],{"class":433},[118,51173,430],{"class":124},[118,51175,8191],{"class":124},[118,51177,51178],{"class":120,"line":196},[118,51179,11600],{"class":124},[118,51181,51182,51184,51186,51188,51190,51192,51194,51196,51198,51200,51202,51204],{"class":120,"line":202},[118,51183,6061],{"class":226},[118,51185,25],{"class":124},[118,51187,7733],{"class":213},[118,51189,255],{"class":124},[118,51191,1510],{"class":299},[118,51193,395],{"class":124},[118,51195,9915],{"class":299},[118,51197,395],{"class":124},[118,51199,9915],{"class":299},[118,51201,395],{"class":124},[118,51203,7742],{"class":299},[118,51205,266],{"class":124},[118,51207,51208,51210,51212,51214],{"class":120,"line":207},[118,51209,6061],{"class":226},[118,51211,25],{"class":124},[118,51213,7762],{"class":213},[118,51215,241],{"class":124},[118,51217,51218,51220,51222,51224],{"class":120,"line":223},[118,51219,2648],{"class":226},[118,51221,25],{"class":124},[118,51223,445],{"class":213},[118,51225,2655],{"class":124},[118,51227,51228,51230,51232,51234,51236,51238,51240,51242],{"class":120,"line":244},[118,51229,2648],{"class":226},[118,51231,25],{"class":124},[118,51233,545],{"class":213},[118,51235,255],{"class":124},[118,51237,550],{"class":226},[118,51239,25],{"class":124},[118,51241,7781],{"class":226},[118,51243,266],{"class":124},[118,51245,51246,51248,51250,51252,51254,51256,51258,51260,51262,51264],{"class":120,"line":269},[118,51247,2648],{"class":226},[118,51249,25],{"class":124},[118,51251,7792],{"class":213},[118,51253,255],{"class":124},[118,51255,550],{"class":226},[118,51257,25],{"class":124},[118,51259,658],{"class":213},[118,51261,255],{"class":124},[118,51263,7269],{"class":299},[118,51265,3646],{"class":124},[118,51267,51268],{"class":120,"line":306},[118,51269,16007],{"class":124},[118,51271,51272,51274,51276,51278,51280,51282,51284,51286,51288,51290],{"class":120,"line":312},[118,51273,6061],{"class":226},[118,51275,25],{"class":124},[118,51277,9973],{"class":213},[118,51279,255],{"class":124},[118,51281,550],{"class":226},[118,51283,25],{"class":124},[118,51285,658],{"class":213},[118,51287,255],{"class":124},[118,51289,9986],{"class":299},[118,51291,3646],{"class":124},[118,51293,51294],{"class":120,"line":317},[118,51295,6166],{"class":124},[118,51297,51298],{"class":120,"line":350},[118,51299,706],{"class":124},[118,51301,51302],{"class":120,"line":379},[118,51303,1944],{"class":124},[14,51305,51306,6589,51309,51311,51312,51314,51315,51317],{},[18,51307,51308],{},"ColumnWidths(50, 15, 15, 20)",[1629,51310,24620],{},", not absolute millimeters. Drop the table into a ",[18,51313,11193],{}," and the same percentages still work. That's the kind of thing you can't get out of ",[18,51316,11353],{}," without a wrapper.",[14,51319,51320],{},"Wrapping is automatic. Page breaks are automatic — if the table runs past the bottom margin, the header repeats on the next page.",[41,51322,51324],{"id":51323},"before-after-3-japanese-text-without-the-dance","Before / After 3: Japanese text without the dance",[14,51326,51327,51328,51330],{},"This is the one that pushed me off gofpdf. To render Japanese in gofpdf you call ",[18,51329,2745],{},", point it at a TTF on disk, set the font, and pray. Subsetting works most of the time. Some TTFs trigger glyph-id collisions and emit garbled output. The error messages don't help.",[14,51332,51333],{},[1629,51334,49643],{},[109,51336,51338],{"className":111,"code":51337,"language":113,"meta":114,"style":114},"pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\npdf.AddUTF8Font(\"notosansjp\", \"\", \"NotoSansJP-Regular.ttf\")\npdf.AddPage()\npdf.SetFont(\"notosansjp\", \"\", 14)\npdf.Cell(0, 10, \"こんにちは、世界。\")\npdf.OutputFileAndClose(\"ja.pdf\")\n",[18,51339,51340,51383,51413,51423,51449,51475],{"__ignoreMap":114},[118,51341,51342,51345,51347,51349,51351,51353,51355,51357,51359,51361,51363,51365,51367,51369,51371,51373,51375,51377,51379,51381],{"class":120,"line":121},[118,51343,51344],{"class":226},"pdf ",[118,51346,230],{"class":124},[118,51348,49692],{"class":226},[118,51350,25],{"class":124},[118,51352,238],{"class":213},[118,51354,255],{"class":124},[118,51356,430],{"class":124},[118,51358,42429],{"class":433},[118,51360,430],{"class":124},[118,51362,395],{"class":124},[118,51364,1146],{"class":124},[118,51366,42438],{"class":433},[118,51368,430],{"class":124},[118,51370,395],{"class":124},[118,51372,1146],{"class":124},[118,51374,263],{"class":433},[118,51376,430],{"class":124},[118,51378,395],{"class":124},[118,51380,13660],{"class":124},[118,51382,199],{"class":124},[118,51384,51385,51387,51389,51391,51393,51395,51397,51399,51401,51403,51405,51407,51409,51411],{"class":120,"line":132},[118,51386,550],{"class":226},[118,51388,25],{"class":124},[118,51390,2745],{"class":213},[118,51392,255],{"class":124},[118,51394,430],{"class":124},[118,51396,38871],{"class":433},[118,51398,430],{"class":124},[118,51400,395],{"class":124},[118,51402,13660],{"class":124},[118,51404,395],{"class":124},[118,51406,1146],{"class":124},[118,51408,9552],{"class":433},[118,51410,430],{"class":124},[118,51412,199],{"class":124},[118,51414,51415,51417,51419,51421],{"class":120,"line":139},[118,51416,550],{"class":226},[118,51418,25],{"class":124},[118,51420,1190],{"class":213},[118,51422,1193],{"class":124},[118,51424,51425,51427,51429,51431,51433,51435,51437,51439,51441,51443,51445,51447],{"class":120,"line":149},[118,51426,550],{"class":226},[118,51428,25],{"class":124},[118,51430,13647],{"class":213},[118,51432,255],{"class":124},[118,51434,430],{"class":124},[118,51436,38871],{"class":433},[118,51438,430],{"class":124},[118,51440,395],{"class":124},[118,51442,13660],{"class":124},[118,51444,395],{"class":124},[118,51446,16239],{"class":299},[118,51448,199],{"class":124},[118,51450,51451,51453,51455,51457,51459,51461,51463,51465,51467,51469,51471,51473],{"class":120,"line":161},[118,51452,550],{"class":226},[118,51454,25],{"class":124},[118,51456,12975],{"class":213},[118,51458,255],{"class":124},[118,51460,4159],{"class":299},[118,51462,395],{"class":124},[118,51464,11241],{"class":299},[118,51466,395],{"class":124},[118,51468,1146],{"class":124},[118,51470,17618],{"class":433},[118,51472,430],{"class":124},[118,51474,199],{"class":124},[118,51476,51477,51479,51481,51483,51485,51487,51489,51491],{"class":120,"line":166},[118,51478,550],{"class":226},[118,51480,25],{"class":124},[118,51482,37132],{"class":213},[118,51484,255],{"class":124},[118,51486,430],{"class":124},[118,51488,24899],{"class":433},[118,51490,430],{"class":124},[118,51492,199],{"class":124},[14,51494,51495,51496,51498],{},"Two land mines: the TTF must exist at the path you give, at runtime, on the host that runs your binary (so your Docker image has to ship the font). And ",[18,51497,12975],{}," of \"0\" width means \"to the right margin,\" which for CJK text often clips because the width estimator doesn't account for full-width glyphs correctly.",[14,51500,51501],{},[1629,51502,13844],{},[109,51504,51506],{"className":111,"code":51505,"language":113,"meta":114,"style":114},"package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    fontData, err := os.ReadFile(\"NotoSansJP-Regular.ttf\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n        gpdf.WithFont(\"NotoSansJP\", fontData),\n        gpdf.WithDefaultFont(\"NotoSansJP\", 14),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"こんにちは、世界。\")\n            c.Text(\"吾輩は猫である。名前はまだ無い。\")\n            c.Text(\"東京都渋谷区神宮前1-2-3\")\n        })\n    })\n\n    data, _ := doc.Generate()\n    os.WriteFile(\"ja.pdf\", data, 0o644)\n}\n",[18,51507,51508,51514,51518,51524,51532,51540,51544,51552,51560,51568,51572,51576,51586,51613,51625,51639,51643,51647,51661,51679,51709,51732,51754,51758,51762,51776,51800,51830,51848,51866,51884,51888,51892,51896,51914,51940],{"__ignoreMap":114},[118,51509,51510,51512],{"class":120,"line":121},[118,51511,125],{"class":124},[118,51513,129],{"class":128},[118,51515,51516],{"class":120,"line":132},[118,51517,136],{"emptyLinePlaceholder":135},[118,51519,51520,51522],{"class":120,"line":139},[118,51521,143],{"class":142},[118,51523,146],{"class":124},[118,51525,51526,51528,51530],{"class":120,"line":149},[118,51527,152],{"class":124},[118,51529,5303],{"class":128},[118,51531,158],{"class":124},[118,51533,51534,51536,51538],{"class":120,"line":161},[118,51535,152],{"class":124},[118,51537,155],{"class":128},[118,51539,158],{"class":124},[118,51541,51542],{"class":120,"line":166},[118,51543,136],{"emptyLinePlaceholder":135},[118,51545,51546,51548,51550],{"class":120,"line":176},[118,51547,152],{"class":124},[118,51549,3203],{"class":128},[118,51551,158],{"class":124},[118,51553,51554,51556,51558],{"class":120,"line":186},[118,51555,152],{"class":124},[118,51557,171],{"class":128},[118,51559,158],{"class":124},[118,51561,51562,51564,51566],{"class":120,"line":196},[118,51563,152],{"class":124},[118,51565,191],{"class":128},[118,51567,158],{"class":124},[118,51569,51570],{"class":120,"line":202},[118,51571,199],{"class":124},[118,51573,51574],{"class":120,"line":207},[118,51575,136],{"emptyLinePlaceholder":135},[118,51577,51578,51580,51582,51584],{"class":120,"line":223},[118,51579,210],{"class":124},[118,51581,214],{"class":213},[118,51583,217],{"class":124},[118,51585,220],{"class":124},[118,51587,51588,51591,51593,51595,51597,51599,51601,51603,51605,51607,51609,51611],{"class":120,"line":244},[118,51589,51590],{"class":226},"    fontData",[118,51592,395],{"class":124},[118,51594,1391],{"class":226},[118,51596,230],{"class":124},[118,51598,1447],{"class":226},[118,51600,25],{"class":124},[118,51602,9545],{"class":213},[118,51604,255],{"class":124},[118,51606,430],{"class":124},[118,51608,9552],{"class":433},[118,51610,430],{"class":124},[118,51612,199],{"class":124},[118,51614,51615,51617,51619,51621,51623],{"class":120,"line":269},[118,51616,1408],{"class":142},[118,51618,1391],{"class":226},[118,51620,1413],{"class":124},[118,51622,1416],{"class":124},[118,51624,220],{"class":124},[118,51626,51627,51629,51631,51633,51635,51637],{"class":120,"line":306},[118,51628,5818],{"class":226},[118,51630,25],{"class":124},[118,51632,5823],{"class":213},[118,51634,255],{"class":124},[118,51636,1429],{"class":226},[118,51638,199],{"class":124},[118,51640,51641],{"class":120,"line":312},[118,51642,1375],{"class":124},[118,51644,51645],{"class":120,"line":317},[118,51646,136],{"emptyLinePlaceholder":135},[118,51648,51649,51651,51653,51655,51657,51659],{"class":120,"line":350},[118,51650,227],{"class":226},[118,51652,230],{"class":124},[118,51654,3595],{"class":226},[118,51656,25],{"class":124},[118,51658,3600],{"class":213},[118,51660,241],{"class":124},[118,51662,51663,51665,51667,51669,51671,51673,51675,51677],{"class":120,"line":379},[118,51664,3607],{"class":226},[118,51666,25],{"class":124},[118,51668,252],{"class":213},[118,51670,255],{"class":124},[118,51672,258],{"class":226},[118,51674,25],{"class":124},[118,51676,263],{"class":226},[118,51678,266],{"class":124},[118,51680,51681,51683,51685,51687,51689,51691,51693,51695,51697,51699,51701,51703,51705,51707],{"class":120,"line":417},[118,51682,3607],{"class":226},[118,51684,25],{"class":124},[118,51686,276],{"class":213},[118,51688,255],{"class":124},[118,51690,258],{"class":226},[118,51692,25],{"class":124},[118,51694,285],{"class":213},[118,51696,255],{"class":124},[118,51698,258],{"class":226},[118,51700,25],{"class":124},[118,51702,294],{"class":213},[118,51704,255],{"class":124},[118,51706,300],{"class":299},[118,51708,303],{"class":124},[118,51710,51711,51713,51715,51717,51719,51721,51723,51725,51727,51730],{"class":120,"line":466},[118,51712,3607],{"class":226},[118,51714,25],{"class":124},[118,51716,2798],{"class":213},[118,51718,255],{"class":124},[118,51720,430],{"class":124},[118,51722,2805],{"class":433},[118,51724,430],{"class":124},[118,51726,395],{"class":124},[118,51728,51729],{"class":226}," fontData",[118,51731,266],{"class":124},[118,51733,51734,51736,51738,51740,51742,51744,51746,51748,51750,51752],{"class":120,"line":472},[118,51735,3607],{"class":226},[118,51737,25],{"class":124},[118,51739,12501],{"class":213},[118,51741,255],{"class":124},[118,51743,430],{"class":124},[118,51745,2805],{"class":433},[118,51747,430],{"class":124},[118,51749,395],{"class":124},[118,51751,16239],{"class":299},[118,51753,266],{"class":124},[118,51755,51756],{"class":120,"line":503},[118,51757,309],{"class":124},[118,51759,51760],{"class":120,"line":537},[118,51761,136],{"emptyLinePlaceholder":135},[118,51763,51764,51766,51768,51770,51772,51774],{"class":120,"line":566},[118,51765,5431],{"class":226},[118,51767,230],{"class":124},[118,51769,1185],{"class":226},[118,51771,25],{"class":124},[118,51773,1190],{"class":213},[118,51775,1193],{"class":124},[118,51777,51778,51780,51782,51784,51786,51788,51790,51792,51794,51796,51798],{"class":120,"line":571},[118,51779,5494],{"class":226},[118,51781,25],{"class":124},[118,51783,358],{"class":213},[118,51785,328],{"class":124},[118,51787,363],{"class":331},[118,51789,334],{"class":124},[118,51791,337],{"class":128},[118,51793,25],{"class":124},[118,51795,372],{"class":128},[118,51797,345],{"class":124},[118,51799,220],{"class":124},[118,51801,51802,51804,51806,51808,51810,51812,51814,51816,51818,51820,51822,51824,51826,51828],{"class":120,"line":577},[118,51803,1737],{"class":226},[118,51805,25],{"class":124},[118,51807,387],{"class":213},[118,51809,255],{"class":124},[118,51811,20],{"class":299},[118,51813,395],{"class":124},[118,51815,398],{"class":124},[118,51817,401],{"class":331},[118,51819,334],{"class":124},[118,51821,337],{"class":128},[118,51823,25],{"class":124},[118,51825,410],{"class":128},[118,51827,345],{"class":124},[118,51829,220],{"class":124},[118,51831,51832,51834,51836,51838,51840,51842,51844,51846],{"class":120,"line":602},[118,51833,1768],{"class":226},[118,51835,25],{"class":124},[118,51837,425],{"class":213},[118,51839,255],{"class":124},[118,51841,430],{"class":124},[118,51843,17618],{"class":433},[118,51845,430],{"class":124},[118,51847,199],{"class":124},[118,51849,51850,51852,51854,51856,51858,51860,51862,51864],{"class":120,"line":633},[118,51851,1768],{"class":226},[118,51853,25],{"class":124},[118,51855,425],{"class":213},[118,51857,255],{"class":124},[118,51859,430],{"class":124},[118,51861,17637],{"class":433},[118,51863,430],{"class":124},[118,51865,199],{"class":124},[118,51867,51868,51870,51872,51874,51876,51878,51880,51882],{"class":120,"line":668},[118,51869,1768],{"class":226},[118,51871,25],{"class":124},[118,51873,425],{"class":213},[118,51875,255],{"class":124},[118,51877,430],{"class":124},[118,51879,17656],{"class":433},[118,51881,430],{"class":124},[118,51883,199],{"class":124},[118,51885,51886],{"class":120,"line":693},[118,51887,574],{"class":124},[118,51889,51890],{"class":120,"line":698},[118,51891,706],{"class":124},[118,51893,51894],{"class":120,"line":703},[118,51895,136],{"emptyLinePlaceholder":135},[118,51897,51898,51900,51902,51904,51906,51908,51910,51912],{"class":120,"line":709},[118,51899,5787],{"class":226},[118,51901,395],{"class":124},[118,51903,3999],{"class":226},[118,51905,230],{"class":124},[118,51907,1185],{"class":226},[118,51909,25],{"class":124},[118,51911,1400],{"class":213},[118,51913,1193],{"class":124},[118,51915,51916,51918,51920,51922,51924,51926,51928,51930,51932,51934,51936,51938],{"class":120,"line":714},[118,51917,32691],{"class":226},[118,51919,25],{"class":124},[118,51921,1452],{"class":213},[118,51923,255],{"class":124},[118,51925,430],{"class":124},[118,51927,24899],{"class":433},[118,51929,430],{"class":124},[118,51931,395],{"class":124},[118,51933,5859],{"class":226},[118,51935,395],{"class":124},[118,51937,1471],{"class":299},[118,51939,199],{"class":124},[118,51941,51942],{"class":120,"line":740},[118,51943,1479],{"class":124},[14,51945,51946],{},"Two things are different.",[14,51948,51949,51950,51952,51953,51955],{},"First, you pass ",[1629,51951,25358],{},", not a path. Embed the TTF with ",[18,51954,17916],{}," and your binary becomes self-contained. No \"font not found\" in production because someone forgot to mount a volume.",[14,51957,51958],{},"Second, gpdf's TrueType subsetter understands CJK cmap formats (4, 6, 12) and Identity-H encoding. The output PDF only carries the glyphs you actually used — embedding NotoSansJP for a 200-character invoice produces a ~30 KB font subset, not a 4 MB full embed. If you've ever watched gofpdf write a 5 MB PDF for one page of Japanese, this is the thing you'll notice first.",[14,51960,51961],{},"For a deeper walkthrough of CJK-specific options — IPAex Gothic, Source Han Sans, fallback chains — see the upcoming companion post on Japanese fonts.",[41,51963,25373],{"id":25372},[14,51965,51966,51967,54,51969,51972,51973,51976,51977,54,51979,25],{},"The gofpdf pattern for repeating chrome is ",[18,51968,1668],{},[18,51970,51971],{},"SetFooterFunc"," — both take a ",[18,51974,51975],{},"func()"," that runs against the current cursor. Page numbers come from ",[18,51978,49556],{},[18,51980,51981],{},"pdf.AliasNbPages()",[14,51983,51984],{},[1629,51985,49643],{},[109,51987,51989],{"className":111,"code":51988,"language":113,"meta":114,"style":114},"pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\npdf.SetHeaderFunc(func() {\n    pdf.SetFont(\"Arial\", \"B\", 12)\n    pdf.Cell(0, 10, \"ACME Corporation\")\n    pdf.Ln(15)\n})\npdf.SetFooterFunc(func() {\n    pdf.SetY(-15)\n    pdf.SetFont(\"Arial\", \"I\", 8)\n    pdf.CellFormat(0, 10,\n        fmt.Sprintf(\"Page %d/{nb}\", pdf.PageNo()),\n        \"\", 0, \"C\", false, 0, \"\")\n})\npdf.AliasNbPages(\"\")\npdf.AddPage()\n// ... body ...\n",[18,51990,51991,52033,52045,52075,52101,52116,52120,52132,52147,52178,52196,52230,52261,52265,52280,52290],{"__ignoreMap":114},[118,51992,51993,51995,51997,51999,52001,52003,52005,52007,52009,52011,52013,52015,52017,52019,52021,52023,52025,52027,52029,52031],{"class":120,"line":121},[118,51994,51344],{"class":226},[118,51996,230],{"class":124},[118,51998,49692],{"class":226},[118,52000,25],{"class":124},[118,52002,238],{"class":213},[118,52004,255],{"class":124},[118,52006,430],{"class":124},[118,52008,42429],{"class":433},[118,52010,430],{"class":124},[118,52012,395],{"class":124},[118,52014,1146],{"class":124},[118,52016,42438],{"class":433},[118,52018,430],{"class":124},[118,52020,395],{"class":124},[118,52022,1146],{"class":124},[118,52024,263],{"class":433},[118,52026,430],{"class":124},[118,52028,395],{"class":124},[118,52030,13660],{"class":124},[118,52032,199],{"class":124},[118,52034,52035,52037,52039,52041,52043],{"class":120,"line":132},[118,52036,550],{"class":226},[118,52038,25],{"class":124},[118,52040,1668],{"class":213},[118,52042,16777],{"class":124},[118,52044,220],{"class":124},[118,52046,52047,52049,52051,52053,52055,52057,52059,52061,52063,52065,52067,52069,52071,52073],{"class":120,"line":139},[118,52048,13525],{"class":226},[118,52050,25],{"class":124},[118,52052,13647],{"class":213},[118,52054,255],{"class":124},[118,52056,430],{"class":124},[118,52058,42479],{"class":433},[118,52060,430],{"class":124},[118,52062,395],{"class":124},[118,52064,1146],{"class":124},[118,52066,42488],{"class":433},[118,52068,430],{"class":124},[118,52070,395],{"class":124},[118,52072,32153],{"class":299},[118,52074,199],{"class":124},[118,52076,52077,52079,52081,52083,52085,52087,52089,52091,52093,52095,52097,52099],{"class":120,"line":149},[118,52078,13525],{"class":226},[118,52080,25],{"class":124},[118,52082,12975],{"class":213},[118,52084,255],{"class":124},[118,52086,4159],{"class":299},[118,52088,395],{"class":124},[118,52090,11241],{"class":299},[118,52092,395],{"class":124},[118,52094,1146],{"class":124},[118,52096,16281],{"class":433},[118,52098,430],{"class":124},[118,52100,199],{"class":124},[118,52102,52103,52105,52107,52110,52112,52114],{"class":120,"line":161},[118,52104,13525],{"class":226},[118,52106,25],{"class":124},[118,52108,52109],{"class":213},"Ln",[118,52111,255],{"class":124},[118,52113,5420],{"class":299},[118,52115,199],{"class":124},[118,52117,52118],{"class":120,"line":166},[118,52119,1944],{"class":124},[118,52121,52122,52124,52126,52128,52130],{"class":120,"line":176},[118,52123,550],{"class":226},[118,52125,25],{"class":124},[118,52127,51971],{"class":213},[118,52129,16777],{"class":124},[118,52131,220],{"class":124},[118,52133,52134,52136,52138,52140,52143,52145],{"class":120,"line":186},[118,52135,13525],{"class":226},[118,52137,25],{"class":124},[118,52139,13721],{"class":213},[118,52141,52142],{"class":124},"(-",[118,52144,5420],{"class":299},[118,52146,199],{"class":124},[118,52148,52149,52151,52153,52155,52157,52159,52161,52163,52165,52167,52170,52172,52174,52176],{"class":120,"line":196},[118,52150,13525],{"class":226},[118,52152,25],{"class":124},[118,52154,13647],{"class":213},[118,52156,255],{"class":124},[118,52158,430],{"class":124},[118,52160,42479],{"class":433},[118,52162,430],{"class":124},[118,52164,395],{"class":124},[118,52166,1146],{"class":124},[118,52168,52169],{"class":433},"I",[118,52171,430],{"class":124},[118,52173,395],{"class":124},[118,52175,16933],{"class":299},[118,52177,199],{"class":124},[118,52179,52180,52182,52184,52186,52188,52190,52192,52194],{"class":120,"line":202},[118,52181,13525],{"class":226},[118,52183,25],{"class":124},[118,52185,11353],{"class":213},[118,52187,255],{"class":124},[118,52189,4159],{"class":299},[118,52191,395],{"class":124},[118,52193,11241],{"class":299},[118,52195,2643],{"class":124},[118,52197,52198,52201,52203,52205,52207,52209,52211,52213,52216,52218,52220,52222,52224,52227],{"class":120,"line":207},[118,52199,52200],{"class":226},"        fmt",[118,52202,25],{"class":124},[118,52204,7416],{"class":213},[118,52206,255],{"class":124},[118,52208,430],{"class":124},[118,52210,16978],{"class":433},[118,52212,2323],{"class":7426},[118,52214,52215],{"class":433},"/{nb}",[118,52217,430],{"class":124},[118,52219,395],{"class":124},[118,52221,7260],{"class":226},[118,52223,25],{"class":124},[118,52225,52226],{"class":213},"PageNo",[118,52228,52229],{"class":124},"()),\n",[118,52231,52232,52235,52237,52239,52241,52243,52245,52247,52249,52251,52253,52255,52257,52259],{"class":120,"line":223},[118,52233,52234],{"class":124},"        \"\"",[118,52236,395],{"class":124},[118,52238,7344],{"class":299},[118,52240,395],{"class":124},[118,52242,1146],{"class":124},[118,52244,50400],{"class":433},[118,52246,430],{"class":124},[118,52248,395],{"class":124},[118,52250,50756],{"class":12009},[118,52252,395],{"class":124},[118,52254,7344],{"class":299},[118,52256,395],{"class":124},[118,52258,13660],{"class":124},[118,52260,199],{"class":124},[118,52262,52263],{"class":120,"line":244},[118,52264,1944],{"class":124},[118,52266,52267,52269,52271,52273,52275,52278],{"class":120,"line":269},[118,52268,550],{"class":226},[118,52270,25],{"class":124},[118,52272,35],{"class":213},[118,52274,255],{"class":124},[118,52276,52277],{"class":124},"\"\"",[118,52279,199],{"class":124},[118,52281,52282,52284,52286,52288],{"class":120,"line":306},[118,52283,550],{"class":226},[118,52285,25],{"class":124},[118,52287,1190],{"class":213},[118,52289,1193],{"class":124},[118,52291,52292],{"class":120,"line":312},[118,52293,52294],{"class":3981},"// ... body ...\n",[14,52296,52297,52299],{},[18,52298,1548],{}," is a sentinel that gofpdf rewrites at output time with the total page count. It works, it's just one of those things you have to know.",[14,52301,52302],{},[1629,52303,13844],{},[109,52305,52307],{"className":111,"code":52306,"language":113,"meta":114,"style":114},"doc := gpdf.NewDocument(\n    gpdf.WithPageSize(document.A4),\n    gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n)\n\ndoc.Header(func(p *template.PageBuilder) {\n    p.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"ACME Corporation\", template.Bold(), template.FontSize(12))\n            c.Line(template.LineColor(pdf.Gray(0.7)))\n            c.Spacer(document.Mm(4))\n        })\n    })\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(\"ACME Corporation\",\n                template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            // \"Page X / Y\" — both pieces are placeholders resolved\n            // by the layout engine after pagination.\n            c.PageNumber(template.AlignRight(),\n                template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n    })\n})\n\nfor i := 0; i \u003C 10; i++ {\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(fmt.Sprintf(\"Body content for page %d.\", i+1))\n        })\n    })\n}\n",[18,52308,52309,52323,52341,52371,52375,52379,52403,52427,52457,52495,52525,52547,52551,52555,52559,52563,52587,52611,52641,52659,52693,52697,52727,52732,52737,52755,52789,52793,52797,52801,52805,52831,52845,52869,52899,52937,52941,52945],{"__ignoreMap":114},[118,52310,52311,52313,52315,52317,52319,52321],{"class":120,"line":121},[118,52312,2760],{"class":226},[118,52314,230],{"class":124},[118,52316,3595],{"class":226},[118,52318,25],{"class":124},[118,52320,3600],{"class":213},[118,52322,241],{"class":124},[118,52324,52325,52327,52329,52331,52333,52335,52337,52339],{"class":120,"line":132},[118,52326,4532],{"class":226},[118,52328,25],{"class":124},[118,52330,252],{"class":213},[118,52332,255],{"class":124},[118,52334,258],{"class":226},[118,52336,25],{"class":124},[118,52338,263],{"class":226},[118,52340,266],{"class":124},[118,52342,52343,52345,52347,52349,52351,52353,52355,52357,52359,52361,52363,52365,52367,52369],{"class":120,"line":139},[118,52344,4532],{"class":226},[118,52346,25],{"class":124},[118,52348,276],{"class":213},[118,52350,255],{"class":124},[118,52352,258],{"class":226},[118,52354,25],{"class":124},[118,52356,285],{"class":213},[118,52358,255],{"class":124},[118,52360,258],{"class":226},[118,52362,25],{"class":124},[118,52364,294],{"class":213},[118,52366,255],{"class":124},[118,52368,300],{"class":299},[118,52370,303],{"class":124},[118,52372,52373],{"class":120,"line":149},[118,52374,199],{"class":124},[118,52376,52377],{"class":120,"line":161},[118,52378,136],{"emptyLinePlaceholder":135},[118,52380,52381,52383,52385,52387,52389,52391,52393,52395,52397,52399,52401],{"class":120,"line":166},[118,52382,1687],{"class":226},[118,52384,25],{"class":124},[118,52386,325],{"class":213},[118,52388,328],{"class":124},[118,52390,14],{"class":331},[118,52392,334],{"class":124},[118,52394,337],{"class":128},[118,52396,25],{"class":124},[118,52398,342],{"class":128},[118,52400,345],{"class":124},[118,52402,220],{"class":124},[118,52404,52405,52407,52409,52411,52413,52415,52417,52419,52421,52423,52425],{"class":120,"line":176},[118,52406,1712],{"class":226},[118,52408,25],{"class":124},[118,52410,358],{"class":213},[118,52412,328],{"class":124},[118,52414,363],{"class":331},[118,52416,334],{"class":124},[118,52418,337],{"class":128},[118,52420,25],{"class":124},[118,52422,372],{"class":128},[118,52424,345],{"class":124},[118,52426,220],{"class":124},[118,52428,52429,52431,52433,52435,52437,52439,52441,52443,52445,52447,52449,52451,52453,52455],{"class":120,"line":186},[118,52430,1737],{"class":226},[118,52432,25],{"class":124},[118,52434,387],{"class":213},[118,52436,255],{"class":124},[118,52438,20],{"class":299},[118,52440,395],{"class":124},[118,52442,398],{"class":124},[118,52444,401],{"class":331},[118,52446,334],{"class":124},[118,52448,337],{"class":128},[118,52450,25],{"class":124},[118,52452,410],{"class":128},[118,52454,345],{"class":124},[118,52456,220],{"class":124},[118,52458,52459,52461,52463,52465,52467,52469,52471,52473,52475,52477,52479,52481,52483,52485,52487,52489,52491,52493],{"class":120,"line":196},[118,52460,1768],{"class":226},[118,52462,25],{"class":124},[118,52464,425],{"class":213},[118,52466,255],{"class":124},[118,52468,430],{"class":124},[118,52470,16281],{"class":433},[118,52472,430],{"class":124},[118,52474,395],{"class":124},[118,52476,233],{"class":226},[118,52478,25],{"class":124},[118,52480,445],{"class":213},[118,52482,448],{"class":124},[118,52484,233],{"class":226},[118,52486,25],{"class":124},[118,52488,455],{"class":213},[118,52490,255],{"class":124},[118,52492,20],{"class":299},[118,52494,463],{"class":124},[118,52496,52497,52499,52501,52503,52505,52507,52509,52511,52513,52515,52517,52519,52521,52523],{"class":120,"line":202},[118,52498,1768],{"class":226},[118,52500,25],{"class":124},[118,52502,640],{"class":213},[118,52504,255],{"class":124},[118,52506,337],{"class":226},[118,52508,25],{"class":124},[118,52510,649],{"class":213},[118,52512,255],{"class":124},[118,52514,550],{"class":226},[118,52516,25],{"class":124},[118,52518,555],{"class":213},[118,52520,255],{"class":124},[118,52522,846],{"class":299},[118,52524,563],{"class":124},[118,52526,52527,52529,52531,52533,52535,52537,52539,52541,52543,52545],{"class":120,"line":207},[118,52528,1768],{"class":226},[118,52530,25],{"class":124},[118,52532,675],{"class":213},[118,52534,255],{"class":124},[118,52536,258],{"class":226},[118,52538,25],{"class":124},[118,52540,294],{"class":213},[118,52542,255],{"class":124},[118,52544,1493],{"class":299},[118,52546,463],{"class":124},[118,52548,52549],{"class":120,"line":223},[118,52550,574],{"class":124},[118,52552,52553],{"class":120,"line":244},[118,52554,706],{"class":124},[118,52556,52557],{"class":120,"line":269},[118,52558,1944],{"class":124},[118,52560,52561],{"class":120,"line":306},[118,52562,136],{"emptyLinePlaceholder":135},[118,52564,52565,52567,52569,52571,52573,52575,52577,52579,52581,52583,52585],{"class":120,"line":312},[118,52566,1687],{"class":226},[118,52568,25],{"class":124},[118,52570,721],{"class":213},[118,52572,328],{"class":124},[118,52574,14],{"class":331},[118,52576,334],{"class":124},[118,52578,337],{"class":128},[118,52580,25],{"class":124},[118,52582,342],{"class":128},[118,52584,345],{"class":124},[118,52586,220],{"class":124},[118,52588,52589,52591,52593,52595,52597,52599,52601,52603,52605,52607,52609],{"class":120,"line":317},[118,52590,1712],{"class":226},[118,52592,25],{"class":124},[118,52594,358],{"class":213},[118,52596,328],{"class":124},[118,52598,363],{"class":331},[118,52600,334],{"class":124},[118,52602,337],{"class":128},[118,52604,25],{"class":124},[118,52606,372],{"class":128},[118,52608,345],{"class":124},[118,52610,220],{"class":124},[118,52612,52613,52615,52617,52619,52621,52623,52625,52627,52629,52631,52633,52635,52637,52639],{"class":120,"line":350},[118,52614,1737],{"class":226},[118,52616,25],{"class":124},[118,52618,387],{"class":213},[118,52620,255],{"class":124},[118,52622,392],{"class":299},[118,52624,395],{"class":124},[118,52626,398],{"class":124},[118,52628,401],{"class":331},[118,52630,334],{"class":124},[118,52632,337],{"class":128},[118,52634,25],{"class":124},[118,52636,410],{"class":128},[118,52638,345],{"class":124},[118,52640,220],{"class":124},[118,52642,52643,52645,52647,52649,52651,52653,52655,52657],{"class":120,"line":379},[118,52644,1768],{"class":226},[118,52646,25],{"class":124},[118,52648,425],{"class":213},[118,52650,255],{"class":124},[118,52652,430],{"class":124},[118,52654,16281],{"class":433},[118,52656,430],{"class":124},[118,52658,2643],{"class":124},[118,52660,52661,52663,52665,52667,52669,52671,52673,52675,52677,52679,52681,52683,52685,52687,52689,52691],{"class":120,"line":417},[118,52662,2648],{"class":226},[118,52664,25],{"class":124},[118,52666,455],{"class":213},[118,52668,255],{"class":124},[118,52670,969],{"class":299},[118,52672,1280],{"class":124},[118,52674,233],{"class":226},[118,52676,25],{"class":124},[118,52678,545],{"class":213},[118,52680,255],{"class":124},[118,52682,550],{"class":226},[118,52684,25],{"class":124},[118,52686,555],{"class":213},[118,52688,255],{"class":124},[118,52690,560],{"class":299},[118,52692,563],{"class":124},[118,52694,52695],{"class":120,"line":466},[118,52696,574],{"class":124},[118,52698,52699,52701,52703,52705,52707,52709,52711,52713,52715,52717,52719,52721,52723,52725],{"class":120,"line":472},[118,52700,1737],{"class":226},[118,52702,25],{"class":124},[118,52704,387],{"class":213},[118,52706,255],{"class":124},[118,52708,392],{"class":299},[118,52710,395],{"class":124},[118,52712,398],{"class":124},[118,52714,401],{"class":331},[118,52716,334],{"class":124},[118,52718,337],{"class":128},[118,52720,25],{"class":124},[118,52722,410],{"class":128},[118,52724,345],{"class":124},[118,52726,220],{"class":124},[118,52728,52729],{"class":120,"line":503},[118,52730,52731],{"class":3981},"            // \"Page X / Y\" — both pieces are placeholders resolved\n",[118,52733,52734],{"class":120,"line":537},[118,52735,52736],{"class":3981},"            // by the layout engine after pagination.\n",[118,52738,52739,52741,52743,52745,52747,52749,52751,52753],{"class":120,"line":566},[118,52740,1768],{"class":226},[118,52742,25],{"class":124},[118,52744,1040],{"class":213},[118,52746,255],{"class":124},[118,52748,337],{"class":226},[118,52750,25],{"class":124},[118,52752,519],{"class":213},[118,52754,2655],{"class":124},[118,52756,52757,52759,52761,52763,52765,52767,52769,52771,52773,52775,52777,52779,52781,52783,52785,52787],{"class":120,"line":571},[118,52758,2648],{"class":226},[118,52760,25],{"class":124},[118,52762,455],{"class":213},[118,52764,255],{"class":124},[118,52766,969],{"class":299},[118,52768,1280],{"class":124},[118,52770,233],{"class":226},[118,52772,25],{"class":124},[118,52774,545],{"class":213},[118,52776,255],{"class":124},[118,52778,550],{"class":226},[118,52780,25],{"class":124},[118,52782,555],{"class":213},[118,52784,255],{"class":124},[118,52786,560],{"class":299},[118,52788,563],{"class":124},[118,52790,52791],{"class":120,"line":577},[118,52792,574],{"class":124},[118,52794,52795],{"class":120,"line":602},[118,52796,706],{"class":124},[118,52798,52799],{"class":120,"line":633},[118,52800,1944],{"class":124},[118,52802,52803],{"class":120,"line":668},[118,52804,136],{"emptyLinePlaceholder":135},[118,52806,52807,52809,52811,52813,52815,52817,52819,52821,52823,52825,52827,52829],{"class":120,"line":693},[118,52808,14495],{"class":142},[118,52810,7358],{"class":226},[118,52812,230],{"class":124},[118,52814,7344],{"class":299},[118,52816,7366],{"class":124},[118,52818,7358],{"class":226},[118,52820,26185],{"class":124},[118,52822,11241],{"class":299},[118,52824,7366],{"class":124},[118,52826,1114],{"class":226},[118,52828,7380],{"class":124},[118,52830,220],{"class":124},[118,52832,52833,52835,52837,52839,52841,52843],{"class":120,"line":698},[118,52834,5431],{"class":226},[118,52836,230],{"class":124},[118,52838,1185],{"class":226},[118,52840,25],{"class":124},[118,52842,1190],{"class":213},[118,52844,1193],{"class":124},[118,52846,52847,52849,52851,52853,52855,52857,52859,52861,52863,52865,52867],{"class":120,"line":703},[118,52848,5494],{"class":226},[118,52850,25],{"class":124},[118,52852,358],{"class":213},[118,52854,328],{"class":124},[118,52856,363],{"class":331},[118,52858,334],{"class":124},[118,52860,337],{"class":128},[118,52862,25],{"class":124},[118,52864,372],{"class":128},[118,52866,345],{"class":124},[118,52868,220],{"class":124},[118,52870,52871,52873,52875,52877,52879,52881,52883,52885,52887,52889,52891,52893,52895,52897],{"class":120,"line":709},[118,52872,1737],{"class":226},[118,52874,25],{"class":124},[118,52876,387],{"class":213},[118,52878,255],{"class":124},[118,52880,20],{"class":299},[118,52882,395],{"class":124},[118,52884,398],{"class":124},[118,52886,401],{"class":331},[118,52888,334],{"class":124},[118,52890,337],{"class":128},[118,52892,25],{"class":124},[118,52894,410],{"class":128},[118,52896,345],{"class":124},[118,52898,220],{"class":124},[118,52900,52901,52903,52905,52907,52909,52911,52913,52915,52917,52919,52921,52923,52925,52927,52929,52931,52933,52935],{"class":120,"line":714},[118,52902,1768],{"class":226},[118,52904,25],{"class":124},[118,52906,425],{"class":213},[118,52908,255],{"class":124},[118,52910,7108],{"class":226},[118,52912,25],{"class":124},[118,52914,7416],{"class":213},[118,52916,255],{"class":124},[118,52918,430],{"class":124},[118,52920,26286],{"class":433},[118,52922,2323],{"class":7426},[118,52924,25],{"class":433},[118,52926,430],{"class":124},[118,52928,395],{"class":124},[118,52930,1114],{"class":226},[118,52932,1339],{"class":124},[118,52934,2402],{"class":299},[118,52936,463],{"class":124},[118,52938,52939],{"class":120,"line":740},[118,52940,574],{"class":124},[118,52942,52943],{"class":120,"line":765},[118,52944,706],{"class":124},[118,52946,52947],{"class":120,"line":796},[118,52948,1479],{"class":124},[14,52950,52951,54,52953,52955,52956,52958,52959,52962],{},[18,52952,1040],{},[18,52954,510],{}," are placeholders. They get expanded after pagination, when the layout engine knows how many pages exist. No ",[18,52957,1548],{}," sentinel, no manual ",[18,52960,52961],{},"SetY(-15)"," to peg the footer to the bottom — the footer is just a tree, and the engine reserves space for it on every page automatically.",[41,52964,52966],{"id":52965},"before-after-5-producing-bytes-for-an-http-handler","Before / After 5: producing bytes for an HTTP handler",[14,52968,52969,52970,52972,52973,52976,52977,52979],{},"Most production gofpdf code doesn't write to a file. It writes to an ",[18,52971,35317],{}," — usually a ",[18,52974,52975],{},"http.ResponseWriter"," returning ",[18,52978,42558],{}," to a browser. This is the pair where gpdf's API is closest to gofpdf's.",[14,52981,52982],{},[1629,52983,49643],{},[109,52985,52987],{"className":111,"code":52986,"language":113,"meta":114,"style":114},"func handler(w http.ResponseWriter, r *http.Request) {\n    pdf := gofpdf.New(\"P\", \"mm\", \"A4\", \"\")\n    pdf.AddPage()\n    pdf.SetFont(\"Arial\", \"\", 12)\n    pdf.Cell(0, 10, \"Generated at \"+time.Now().Format(time.RFC3339))\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := pdf.Output(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[18,52988,52989,53021,53063,53073,53099,53150,53154,53184,53212,53238,53242],{"__ignoreMap":114},[118,52990,52991,52993,52995,52997,52999,53001,53003,53005,53007,53009,53011,53013,53015,53017,53019],{"class":120,"line":121},[118,52992,210],{"class":124},[118,52994,42378],{"class":213},[118,52996,255],{"class":124},[118,52998,37071],{"class":331},[118,53000,42385],{"class":128},[118,53002,25],{"class":124},[118,53004,42390],{"class":128},[118,53006,395],{"class":124},[118,53008,42395],{"class":331},[118,53010,334],{"class":124},[118,53012,42400],{"class":128},[118,53014,25],{"class":124},[118,53016,42405],{"class":128},[118,53018,345],{"class":124},[118,53020,220],{"class":124},[118,53022,53023,53025,53027,53029,53031,53033,53035,53037,53039,53041,53043,53045,53047,53049,53051,53053,53055,53057,53059,53061],{"class":120,"line":132},[118,53024,13507],{"class":226},[118,53026,230],{"class":124},[118,53028,49692],{"class":226},[118,53030,25],{"class":124},[118,53032,238],{"class":213},[118,53034,255],{"class":124},[118,53036,430],{"class":124},[118,53038,42429],{"class":433},[118,53040,430],{"class":124},[118,53042,395],{"class":124},[118,53044,1146],{"class":124},[118,53046,42438],{"class":433},[118,53048,430],{"class":124},[118,53050,395],{"class":124},[118,53052,1146],{"class":124},[118,53054,263],{"class":433},[118,53056,430],{"class":124},[118,53058,395],{"class":124},[118,53060,13660],{"class":124},[118,53062,199],{"class":124},[118,53064,53065,53067,53069,53071],{"class":120,"line":139},[118,53066,13525],{"class":226},[118,53068,25],{"class":124},[118,53070,1190],{"class":213},[118,53072,1193],{"class":124},[118,53074,53075,53077,53079,53081,53083,53085,53087,53089,53091,53093,53095,53097],{"class":120,"line":149},[118,53076,13525],{"class":226},[118,53078,25],{"class":124},[118,53080,13647],{"class":213},[118,53082,255],{"class":124},[118,53084,430],{"class":124},[118,53086,42479],{"class":433},[118,53088,430],{"class":124},[118,53090,395],{"class":124},[118,53092,13660],{"class":124},[118,53094,395],{"class":124},[118,53096,32153],{"class":299},[118,53098,199],{"class":124},[118,53100,53101,53103,53105,53107,53109,53111,53113,53115,53117,53119,53122,53124,53126,53129,53131,53134,53136,53139,53141,53143,53145,53148],{"class":120,"line":161},[118,53102,13525],{"class":226},[118,53104,25],{"class":124},[118,53106,12975],{"class":213},[118,53108,255],{"class":124},[118,53110,4159],{"class":299},[118,53112,395],{"class":124},[118,53114,11241],{"class":299},[118,53116,395],{"class":124},[118,53118,1146],{"class":124},[118,53120,53121],{"class":433},"Generated at ",[118,53123,430],{"class":124},[118,53125,1339],{"class":124},[118,53127,53128],{"class":226},"time",[118,53130,25],{"class":124},[118,53132,53133],{"class":213},"Now",[118,53135,42539],{"class":124},[118,53137,53138],{"class":213},"Format",[118,53140,255],{"class":124},[118,53142,53128],{"class":226},[118,53144,25],{"class":124},[118,53146,53147],{"class":226},"RFC3339",[118,53149,463],{"class":124},[118,53151,53152],{"class":120,"line":166},[118,53153,136],{"emptyLinePlaceholder":135},[118,53155,53156,53158,53160,53162,53164,53166,53168,53170,53172,53174,53176,53178,53180,53182],{"class":120,"line":176},[118,53157,42532],{"class":226},[118,53159,25],{"class":124},[118,53161,325],{"class":213},[118,53163,42539],{"class":124},[118,53165,42542],{"class":213},[118,53167,255],{"class":124},[118,53169,430],{"class":124},[118,53171,42549],{"class":433},[118,53173,430],{"class":124},[118,53175,395],{"class":124},[118,53177,1146],{"class":124},[118,53179,42558],{"class":433},[118,53181,430],{"class":124},[118,53183,199],{"class":124},[118,53185,53186,53188,53190,53192,53194,53196,53198,53200,53202,53204,53206,53208,53210],{"class":120,"line":186},[118,53187,1408],{"class":142},[118,53189,1391],{"class":226},[118,53191,230],{"class":124},[118,53193,7260],{"class":226},[118,53195,25],{"class":124},[118,53197,13378],{"class":213},[118,53199,255],{"class":124},[118,53201,37071],{"class":226},[118,53203,7902],{"class":124},[118,53205,1391],{"class":226},[118,53207,1413],{"class":124},[118,53209,1416],{"class":124},[118,53211,220],{"class":124},[118,53213,53214,53216,53218,53220,53222,53224,53226,53228,53230,53232,53234,53236],{"class":120,"line":196},[118,53215,42595],{"class":226},[118,53217,25],{"class":124},[118,53219,42600],{"class":213},[118,53221,255],{"class":124},[118,53223,37071],{"class":226},[118,53225,395],{"class":124},[118,53227,42609],{"class":226},[118,53229,25],{"class":124},[118,53231,42600],{"class":213},[118,53233,448],{"class":124},[118,53235,42618],{"class":299},[118,53237,199],{"class":124},[118,53239,53240],{"class":120,"line":202},[118,53241,1375],{"class":124},[118,53243,53244],{"class":120,"line":207},[118,53245,1479],{"class":124},[14,53247,53248],{},[1629,53249,13844],{},[109,53251,53253],{"className":111,"code":53252,"language":113,"meta":114,"style":114},"func handler(w http.ResponseWriter, r *http.Request) {\n    doc := gpdf.NewDocument(\n        gpdf.WithPageSize(document.A4),\n        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    )\n\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Text(\"Generated at \" + time.Now().Format(time.RFC3339))\n        })\n    })\n\n    w.Header().Set(\"Content-Type\", \"application/pdf\")\n    if err := doc.Render(w); err != nil {\n        http.Error(w, err.Error(), 500)\n    }\n}\n",[18,53254,53255,53287,53301,53319,53349,53353,53357,53371,53395,53425,53464,53468,53472,53476,53506,53534,53560,53564],{"__ignoreMap":114},[118,53256,53257,53259,53261,53263,53265,53267,53269,53271,53273,53275,53277,53279,53281,53283,53285],{"class":120,"line":121},[118,53258,210],{"class":124},[118,53260,42378],{"class":213},[118,53262,255],{"class":124},[118,53264,37071],{"class":331},[118,53266,42385],{"class":128},[118,53268,25],{"class":124},[118,53270,42390],{"class":128},[118,53272,395],{"class":124},[118,53274,42395],{"class":331},[118,53276,334],{"class":124},[118,53278,42400],{"class":128},[118,53280,25],{"class":124},[118,53282,42405],{"class":128},[118,53284,345],{"class":124},[118,53286,220],{"class":124},[118,53288,53289,53291,53293,53295,53297,53299],{"class":120,"line":132},[118,53290,227],{"class":226},[118,53292,230],{"class":124},[118,53294,3595],{"class":226},[118,53296,25],{"class":124},[118,53298,3600],{"class":213},[118,53300,241],{"class":124},[118,53302,53303,53305,53307,53309,53311,53313,53315,53317],{"class":120,"line":139},[118,53304,3607],{"class":226},[118,53306,25],{"class":124},[118,53308,252],{"class":213},[118,53310,255],{"class":124},[118,53312,258],{"class":226},[118,53314,25],{"class":124},[118,53316,263],{"class":226},[118,53318,266],{"class":124},[118,53320,53321,53323,53325,53327,53329,53331,53333,53335,53337,53339,53341,53343,53345,53347],{"class":120,"line":149},[118,53322,3607],{"class":226},[118,53324,25],{"class":124},[118,53326,276],{"class":213},[118,53328,255],{"class":124},[118,53330,258],{"class":226},[118,53332,25],{"class":124},[118,53334,285],{"class":213},[118,53336,255],{"class":124},[118,53338,258],{"class":226},[118,53340,25],{"class":124},[118,53342,294],{"class":213},[118,53344,255],{"class":124},[118,53346,300],{"class":299},[118,53348,303],{"class":124},[118,53350,53351],{"class":120,"line":161},[118,53352,309],{"class":124},[118,53354,53355],{"class":120,"line":166},[118,53356,136],{"emptyLinePlaceholder":135},[118,53358,53359,53361,53363,53365,53367,53369],{"class":120,"line":176},[118,53360,5431],{"class":226},[118,53362,230],{"class":124},[118,53364,1185],{"class":226},[118,53366,25],{"class":124},[118,53368,1190],{"class":213},[118,53370,1193],{"class":124},[118,53372,53373,53375,53377,53379,53381,53383,53385,53387,53389,53391,53393],{"class":120,"line":186},[118,53374,5494],{"class":226},[118,53376,25],{"class":124},[118,53378,358],{"class":213},[118,53380,328],{"class":124},[118,53382,363],{"class":331},[118,53384,334],{"class":124},[118,53386,337],{"class":128},[118,53388,25],{"class":124},[118,53390,372],{"class":128},[118,53392,345],{"class":124},[118,53394,220],{"class":124},[118,53396,53397,53399,53401,53403,53405,53407,53409,53411,53413,53415,53417,53419,53421,53423],{"class":120,"line":196},[118,53398,1737],{"class":226},[118,53400,25],{"class":124},[118,53402,387],{"class":213},[118,53404,255],{"class":124},[118,53406,20],{"class":299},[118,53408,395],{"class":124},[118,53410,398],{"class":124},[118,53412,401],{"class":331},[118,53414,334],{"class":124},[118,53416,337],{"class":128},[118,53418,25],{"class":124},[118,53420,410],{"class":128},[118,53422,345],{"class":124},[118,53424,220],{"class":124},[118,53426,53427,53429,53431,53433,53435,53437,53439,53441,53443,53446,53448,53450,53452,53454,53456,53458,53460,53462],{"class":120,"line":202},[118,53428,1768],{"class":226},[118,53430,25],{"class":124},[118,53432,425],{"class":213},[118,53434,255],{"class":124},[118,53436,430],{"class":124},[118,53438,53121],{"class":433},[118,53440,430],{"class":124},[118,53442,1334],{"class":124},[118,53444,53445],{"class":226}," time",[118,53447,25],{"class":124},[118,53449,53133],{"class":213},[118,53451,42539],{"class":124},[118,53453,53138],{"class":213},[118,53455,255],{"class":124},[118,53457,53128],{"class":226},[118,53459,25],{"class":124},[118,53461,53147],{"class":226},[118,53463,463],{"class":124},[118,53465,53466],{"class":120,"line":207},[118,53467,574],{"class":124},[118,53469,53470],{"class":120,"line":223},[118,53471,706],{"class":124},[118,53473,53474],{"class":120,"line":244},[118,53475,136],{"emptyLinePlaceholder":135},[118,53477,53478,53480,53482,53484,53486,53488,53490,53492,53494,53496,53498,53500,53502,53504],{"class":120,"line":269},[118,53479,42532],{"class":226},[118,53481,25],{"class":124},[118,53483,325],{"class":213},[118,53485,42539],{"class":124},[118,53487,42542],{"class":213},[118,53489,255],{"class":124},[118,53491,430],{"class":124},[118,53493,42549],{"class":433},[118,53495,430],{"class":124},[118,53497,395],{"class":124},[118,53499,1146],{"class":124},[118,53501,42558],{"class":433},[118,53503,430],{"class":124},[118,53505,199],{"class":124},[118,53507,53508,53510,53512,53514,53516,53518,53520,53522,53524,53526,53528,53530,53532],{"class":120,"line":306},[118,53509,1408],{"class":142},[118,53511,1391],{"class":226},[118,53513,230],{"class":124},[118,53515,1185],{"class":226},[118,53517,25],{"class":124},[118,53519,29105],{"class":213},[118,53521,255],{"class":124},[118,53523,37071],{"class":226},[118,53525,7902],{"class":124},[118,53527,1391],{"class":226},[118,53529,1413],{"class":124},[118,53531,1416],{"class":124},[118,53533,220],{"class":124},[118,53535,53536,53538,53540,53542,53544,53546,53548,53550,53552,53554,53556,53558],{"class":120,"line":312},[118,53537,42595],{"class":226},[118,53539,25],{"class":124},[118,53541,42600],{"class":213},[118,53543,255],{"class":124},[118,53545,37071],{"class":226},[118,53547,395],{"class":124},[118,53549,42609],{"class":226},[118,53551,25],{"class":124},[118,53553,42600],{"class":213},[118,53555,448],{"class":124},[118,53557,42618],{"class":299},[118,53559,199],{"class":124},[118,53561,53562],{"class":120,"line":317},[118,53563,1375],{"class":124},[118,53565,53566],{"class":120,"line":350},[118,53567,1479],{"class":124},[14,53569,53570,53571,53573,53574,23639,53577,53579,53580,53583],{},"Same shape. ",[18,53572,13406],{}," streams the PDF directly into the response. If you want to set ",[18,53575,53576],{},"Content-Length",[18,53578,21327],{}," first to get the byte slice, then ",[18,53581,53582],{},"len()"," it.",[41,53585,53587],{"id":53586},"how-fast-is-fast-enough","How fast is \"fast enough\"?",[14,53589,53590,53591,53593,53594,53596],{},"gpdf is roughly ",[1629,53592,49236],{}," on the workloads people actually run. The numbers below are from ",[18,53595,27100],{}," on an Apple M1 with Go 1.25.",[1516,53598,53599,53613],{},[1519,53600,53601],{},[1522,53602,53603,53605,53607,53609,53611],{},[1525,53604,17691],{},[1525,53606,1587],{},[1525,53608,1539],{},[1525,53610,11345],{},[1525,53612,17700],{},[1532,53614,53615,53629,53644,53659],{},[1522,53616,53617,53619,53623,53625,53627],{},[1537,53618,17707],{},[1537,53620,53621],{},[1629,53622,3248],{},[1537,53624,17717],{},[1537,53626,17714],{},[1537,53628,17720],{},[1522,53630,53631,53634,53638,53640,53642],{},[1537,53632,53633],{},"4×10 table",[1537,53635,53636],{},[1629,53637,16053],{},[1537,53639,17735],{},[1537,53641,17732],{},[1537,53643,17738],{},[1522,53645,53646,53649,53653,53655,53657],{},[1537,53647,53648],{},"100-page document",[1537,53650,53651],{},[1629,53652,4131],{},[1537,53654,17752],{},[1537,53656,17738],{},[1537,53658,17755],{},[1522,53660,53661,53663,53667,53669,53671],{},[1537,53662,17760],{},[1537,53664,53665],{},[1629,53666,17765],{},[1537,53668,17771],{},[1537,53670,17768],{},[1537,53672,17774],{},[14,53674,53675],{},"These aren't synthetic — the table benchmark is a 4-column, 10-row invoice line-items table; the 100-page benchmark is a paginated report with a repeating header and page numbers. The shape matches what production code actually does.",[14,53677,53678],{},"A small note on what these numbers mean. At 13 µs per single page, a single core can produce ~75,000 hello-world PDFs per second. At 108 µs per table-rich page, ~9,000 invoices per second. The point isn't bragging rights — it's that you can stop thinking about whether to cache or async-queue PDF generation. For most workloads, generating on the request path is fine.",[41,53680,53682],{"id":53681},"what-youre-giving-up","What you're giving up",[14,53684,53685],{},"Nothing in this guide is interesting if it papers over real gaps. Here's what gpdf doesn't do that gofpdf does:",[46,53687,53688,53696,53713,53719],{},[49,53689,53690,4919,53693,53695],{},[1629,53691,53692],{},"Arbitrary line angles, beziers, and complex paths.",[18,53694,2530],{}," draws a horizontal rule across a column. If you're producing technical drawings or charts with custom geometry, gpdf isn't there yet. (Charts as pre-rendered images: works fine.)",[49,53697,53698,53703,53704,53706,53707,53709,53710,53712],{},[1629,53699,53700,53702],{},[18,53701,12972],{}," and absolute cursor work."," You can do absolute positioning with ",[18,53705,17839],{},", but if your existing code is 2,000 lines of ",[18,53708,12972],{}," followed by ",[18,53711,12975],{},", the migration is more like a rewrite. The upside is the rewritten code is usually half the size.",[49,53714,53715,53718],{},[1629,53716,53717],{},"Form fields (AcroForm)."," gpdf doesn't yet generate fillable form fields. If your PDFs are form templates that users fill in a viewer, stay on a library that supports AcroForm — for now.",[49,53720,53721,53724],{},[1629,53722,53723],{},"Annotations and bookmarks."," Basic outline support exists; rich annotations don't.",[14,53726,53727],{},"If none of those bite, the migration is straight-through. If one of them does, file an issue — the roadmap is driven by what people ask for.",[41,53729,3054],{"id":3053},[14,53731,53732,53735],{},[1629,53733,53734],{},"Is gpdf a fork of gofpdf?","\nNo. gpdf is a clean reimplementation. The PDF wire-format work, the layout engine, the TrueType subsetter — all written from scratch in pure Go. There's no shared lineage with gofpdf or its forks. The reason it has to be a clean rewrite is that gofpdf's architecture is built around a single mutable cursor; you can't get a declarative grid out of that without breaking every existing call site.",[14,53737,53738,53741,53742,27274,53744,53746,53747,53749,53750,53753],{},[1629,53739,53740],{},"Does gpdf have any external dependencies?","\nThe core library has zero. Run ",[18,53743,27273],{},[18,53745,29121],{}," and you'll see one line. The ",[18,53748,43194],{}," add-on (HTML→PDF, AES encryption, signatures, PDF/A) pulls in ",[18,53751,53752],{},"golang.org/x/net"," for HTML parsing, but that's opt-in and not required for migration.",[14,53755,53756,53759,53760,53762],{},[1629,53757,53758],{},"What about CGO? gofpdf was CGO-free, what about gpdf?","\nSame. Pure Go, no CGO. Cross-compile with ",[18,53761,42073],{}," and ship a static binary. This matters for distroless and Alpine images, where dragging in a CGO toolchain doubles your container size.",[14,53764,53765,53771,53772,53774],{},[1629,53766,53767,53768,53770],{},"My existing gofpdf code uses ",[18,53769,12972],{}," for absolute positioning everywhere. Can I migrate without rewriting?","\nYou can wrap ",[18,53773,17839],{}," and get something that feels similar. But if your code is structured around cursor manipulation, the layout-engine model is a mental shift, not a syntactic one. Read the 12-column grid post (link to come) before estimating the work — most teams find the rewrite is shorter than the original.",[14,53776,53777,53780],{},[1629,53778,53779],{},"What if go-pdf/fpdf gets unarchived?","\nThen you have one more option. The bet behind gpdf isn't that gofpdf will stay archived forever — it's that the architecture (cursor-based, single-byte fonts, no native CJK) is a dead end regardless of who maintains it. PDF generation in 2026 looks more like building a web page than driving a plotter, and the API should reflect that.",[41,53782,4794],{"id":4793},[14,53784,6933],{},[109,53786,53787],{"className":3145,"code":3146,"language":3147,"meta":114,"style":114},[18,53788,53789],{"__ignoreMap":114},[118,53790,53791,53793,53795],{"class":120,"line":121},[118,53792,113],{"class":128},[118,53794,3156],{"class":433},[118,53796,3159],{"class":433},[14,53798,53799,3169,53802],{},[3163,53800,3168],{"href":3165,"rel":53801},[3167],[3163,53803,3174],{"href":3172,"rel":53804},[3167],[41,53806,4821],{"id":4820},[46,53808,53809,53815,53820],{},[49,53810,53811,53812],{},"How does the 12-column grid work in gpdf? ",[4744,53813,53814],{},"(coming soon)",[49,53816,53817,53818],{},"How do I embed a Japanese font in gpdf? ",[4744,53819,53814],{},[49,53821,53822,27353,53825],{},[3163,53823,27352],{"href":3172,"rel":53824},[3167],[18,53826,27266],{},[3176,53828,53829],{},"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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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);}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":114,"searchDepth":132,"depth":132,"links":53831},[53832,53833,53834,53835,53836,53837,53838,53839,53840,53841,53842,53843,53844,53845],{"id":43,"depth":132,"text":44},{"id":49268,"depth":132,"text":49269},{"id":49294,"depth":132,"text":49295},{"id":13068,"depth":132,"text":13069},{"id":49634,"depth":132,"text":49635},{"id":50222,"depth":132,"text":50223},{"id":51323,"depth":132,"text":51324},{"id":25372,"depth":132,"text":25373},{"id":52965,"depth":132,"text":52966},{"id":53586,"depth":132,"text":53587},{"id":53681,"depth":132,"text":53682},{"id":3053,"depth":132,"text":3054},{"id":4793,"depth":132,"text":4794},{"id":4820,"depth":132,"text":4821},"2026-04-14","jung-kurt/gofpdf was archived in 2021. This guide maps every gofpdf API to gpdf — a pure-Go replacement with native CJK support and zero dependencies.",{"name":53849,"totalTime":53850,"tools":53851,"steps":53852},"Migrate a Go project from gofpdf to gpdf","PT30M",[3202],[53853,53855,53858,53861,53864,53867],{"name":18057,"text":53854},"Swap github.com/jung-kurt/gofpdf (archived 2021) and github.com/go-pdf/fpdf (archived 2025) for github.com/gpdf-dev/gpdf, github.com/gpdf-dev/gpdf/document, and github.com/gpdf-dev/gpdf/template.",{"name":53856,"text":53857},"Construct the document with builders instead of cursors","Call gpdf.NewDocument with WithPageSize, WithMargins, and optional WithFont. Instead of driving a cursor with SetXY, add pages via doc.AddPage() and describe content with RowBuilder and ColBuilder.",{"name":53859,"text":53860},"Convert Cell and MultiCell calls to declarative Text","Replace pdf.Cell and pdf.MultiCell with c.Text(...) inside a column. Text wraps at the column boundary automatically, so MultiCell's trailing flag disappears. Font size, weight, and color become per-text options.",{"name":53862,"text":53863},"Register CJK fonts through WithFont","For Japanese, Chinese, or Korean text, replace pdf.AddUTF8Font with gpdf.WithFont(name, ttfBytes) at document construction. No more TTF path bookkeeping or UTF-8 flag — subset embedding happens automatically.",{"name":53865,"text":53866},"Rewrite tables with rows and columns","Drop the nested Cell loops with manual column widths. Use row.Col(n, fn) inside an AutoRow to build table rows; the 12-column grid computes widths and handles page breaks.",{"name":27402,"text":53868},"Replace pdf.OutputFileAndClose(path) with doc.Generate() plus os.WriteFile(path, data, 0o644), or use doc.Render(w) to stream to an io.Writer.",{},{"title":18018,"description":53847},"blog/001.gofpdf-migration",[4868,4866,3226],"C79nIaskTm6Lc-3G0GK5-u-6m6Xgsii7H92u25eN0VM",1779199013750]