[{"data":1,"prerenderedAt":2531},["ShallowReactive",2],{"blog-zh-invoice-pdf-go-under-50-lines":3},{"id":4,"title":5,"author":6,"body":10,"date":2497,"description":2498,"draft":2499,"extension":2500,"howTo":2501,"image":2522,"meta":2523,"navigation":130,"path":2524,"seo":2525,"stem":2526,"tags":2527,"updated":2522,"__hash__":2530},"blogZh/zh/blog/012.invoice-pdf-go-under-50-lines.md","用 Go 生成发票 PDF：50 行以下的完整代码",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":2477},"minimark",[13,18,36,39,64,70,74,77,80,96,99,104,107,110,1144,1147,1156,1159,1162,1165,1236,1255,1258,1294,1307,1310,1417,1432,1441,1444,1607,1616,1636,1639,1642,1693,1699,1702,1809,1827,1830,1833,1978,1981,1990,1994,2004,2127,2133,2239,2249,2252,2308,2311,2354,2357,2361,2367,2380,2390,2400,2414,2418,2421,2433,2447,2450,2473],[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,22,26,27,31,32,35],"p",{},"用 Go 写一个可运行的发票 PDF，端到端 ",[23,24,25],"strong",{},"50 行","。一个 ",[28,29,30],"code",{},"main.go","，一次 ",[28,33,34],{},"go get","，不用 Chromium，不用 CGO，没有模板语言，没有 HTML。带表格、条纹行、右对齐合计。代码在下方，其余内容讲每一块的作用和这种写法何时会失效。",[19,37,38],{},"想直接看代码:",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash","",[28,47,48],{"__ignoreMap":45},[49,50,53,57,61],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"sBMFI","go",[49,58,60],{"class":59},"sfazB"," get",[49,62,63],{"class":59}," github.com/gpdf-dev/gpdf\n",[19,65,66,67,69],{},"然后粘贴下一节的 ",[28,68,30],{},"。",[14,71,73],{"id":72},"为什么50-行以下是我们关注的阈值","为什么\"50 行以下\"是我们关注的阈值",[19,75,76],{},"坦白讲，搜索\"go generate invoice pdf\"出来的结果大多是两种:(a) 推荐起一个无头 Chromium;(b) 展示 400 行低级 PDF 操作符来渲染一个表格。两者技术上都没错，但都不是这个任务应有的形状。",[19,78,79],{},"一张合理的发票需要:",[81,82,83,87,90,93],"ul",{},[84,85,86],"li",{},"发行方和客户信息的表头",[84,88,89],{},"发票号和到期日",[84,91,92],{},"明细行表格",[84,94,95],{},"合计金额",[19,97,98],{},"四件事。所以代码也应该是四块。超过一屏的话，是库选错了。",[19,100,101,103],{},[23,102,25],{}," 大致是普通编辑器一屏能放下的上限，也是审核者会从头看到尾而不是直接跳到测试的阈值。达到这个门槛，生成结果贴到 Slack 一条消息里，别人就能通过这条消息学会这个库。这是目标线。",[19,105,106],{},"下方代码经过 gofmt 格式化，import 完整展开，所有错误路径都处理。没有隐藏的辅助包，没有小技巧。看到什么就是什么能编译的内容。",[14,108,25],{"id":109},"_50-行",[40,111,114],{"className":112,"code":113,"language":56,"meta":45,"style":45},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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 公司\", template.FontSize(22), template.Bold())\n            c.Text(\"上海市浦东新区世纪大道 100 号\")\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"发票 #INV-2026-001\", template.Bold(), template.AlignRight())\n            c.Text(\"到期日: 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{\"项目\", \"数量\", \"单价\", \"金额\"},\n                [][]string{\n                    {\"前端开发\", \"40 小时\", \"¥1,080\", \"¥43,200\"},\n                    {\"后端开发\", \"60 小时\", \"¥1,080\", \"¥64,800\"},\n                    {\"UI/UX 设计\", \"20 小时\", \"¥900\", \"¥18,000\"},\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(\"合计: ¥126,000\", 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",[28,115,116,125,132,142,154,164,169,179,189,199,209,215,220,236,279,298,331,370,419,439,445,476,514,542,547,553,578,610,635,648,699,710,752,792,833,839,873,918,944,950,999,1004,1009,1031,1047,1065,1071,1118,1133,1138],{"__ignoreMap":45},[49,117,118,122],{"class":51,"line":52},[49,119,121],{"class":120},"sMK4o","package",[49,123,124],{"class":55}," main\n",[49,126,128],{"class":51,"line":127},2,[49,129,131],{"emptyLinePlaceholder":130},true,"\n",[49,133,135,139],{"class":51,"line":134},3,[49,136,138],{"class":137},"s7zQu","import",[49,140,141],{"class":120}," (\n",[49,143,145,148,151],{"class":51,"line":144},4,[49,146,147],{"class":120},"    \"",[49,149,150],{"class":55},"log",[49,152,153],{"class":120},"\"\n",[49,155,157,159,162],{"class":51,"line":156},5,[49,158,147],{"class":120},[49,160,161],{"class":55},"os",[49,163,153],{"class":120},[49,165,167],{"class":51,"line":166},6,[49,168,131],{"emptyLinePlaceholder":130},[49,170,172,174,177],{"class":51,"line":171},7,[49,173,147],{"class":120},[49,175,176],{"class":55},"github.com/gpdf-dev/gpdf",[49,178,153],{"class":120},[49,180,182,184,187],{"class":51,"line":181},8,[49,183,147],{"class":120},[49,185,186],{"class":55},"github.com/gpdf-dev/gpdf/document",[49,188,153],{"class":120},[49,190,192,194,197],{"class":51,"line":191},9,[49,193,147],{"class":120},[49,195,196],{"class":55},"github.com/gpdf-dev/gpdf/pdf",[49,198,153],{"class":120},[49,200,202,204,207],{"class":51,"line":201},10,[49,203,147],{"class":120},[49,205,206],{"class":55},"github.com/gpdf-dev/gpdf/template",[49,208,153],{"class":120},[49,210,212],{"class":51,"line":211},11,[49,213,214],{"class":120},")\n",[49,216,218],{"class":51,"line":217},12,[49,219,131],{"emptyLinePlaceholder":130},[49,221,223,226,230,233],{"class":51,"line":222},13,[49,224,225],{"class":120},"func",[49,227,229],{"class":228},"s2Zo4"," main",[49,231,232],{"class":120},"()",[49,234,235],{"class":120}," {\n",[49,237,239,243,246,249,252,255,258,261,263,266,268,271,273,276],{"class":51,"line":238},14,[49,240,242],{"class":241},"sTEyZ","    doc ",[49,244,245],{"class":120},":=",[49,247,248],{"class":241}," gpdf",[49,250,251],{"class":120},".",[49,253,254],{"class":228},"NewDocument",[49,256,257],{"class":120},"(",[49,259,260],{"class":241},"template",[49,262,251],{"class":120},[49,264,265],{"class":228},"WithPageSize",[49,267,257],{"class":120},[49,269,270],{"class":241},"document",[49,272,251],{"class":120},[49,274,275],{"class":241},"A4",[49,277,278],{"class":120},"))\n",[49,280,282,285,287,290,292,295],{"class":51,"line":281},15,[49,283,284],{"class":241},"    page ",[49,286,245],{"class":120},[49,288,289],{"class":241}," doc",[49,291,251],{"class":120},[49,293,294],{"class":228},"AddPage",[49,296,297],{"class":120},"()\n",[49,299,301,304,306,309,312,316,319,321,323,326,329],{"class":51,"line":300},16,[49,302,303],{"class":241},"    page",[49,305,251],{"class":120},[49,307,308],{"class":228},"AutoRow",[49,310,311],{"class":120},"(func(",[49,313,315],{"class":314},"sHdIc","r",[49,317,318],{"class":120}," *",[49,320,260],{"class":55},[49,322,251],{"class":120},[49,324,325],{"class":55},"RowBuilder",[49,327,328],{"class":120},")",[49,330,235],{"class":120},[49,332,334,337,339,342,344,348,351,354,357,359,361,363,366,368],{"class":51,"line":333},17,[49,335,336],{"class":241},"        r",[49,338,251],{"class":120},[49,340,341],{"class":228},"Col",[49,343,257],{"class":120},[49,345,347],{"class":346},"sbssI","6",[49,349,350],{"class":120},",",[49,352,353],{"class":120}," func(",[49,355,356],{"class":314},"c",[49,358,318],{"class":120},[49,360,260],{"class":55},[49,362,251],{"class":120},[49,364,365],{"class":55},"ColBuilder",[49,367,328],{"class":120},[49,369,235],{"class":120},[49,371,373,376,378,381,383,386,389,391,393,396,398,401,403,406,409,411,413,416],{"class":51,"line":372},18,[49,374,375],{"class":241},"            c",[49,377,251],{"class":120},[49,379,380],{"class":228},"Text",[49,382,257],{"class":120},[49,384,385],{"class":120},"\"",[49,387,388],{"class":59},"ACME 公司",[49,390,385],{"class":120},[49,392,350],{"class":120},[49,394,395],{"class":241}," template",[49,397,251],{"class":120},[49,399,400],{"class":228},"FontSize",[49,402,257],{"class":120},[49,404,405],{"class":346},"22",[49,407,408],{"class":120},"),",[49,410,395],{"class":241},[49,412,251],{"class":120},[49,414,415],{"class":228},"Bold",[49,417,418],{"class":120},"())\n",[49,420,422,424,426,428,430,432,435,437],{"class":51,"line":421},19,[49,423,375],{"class":241},[49,425,251],{"class":120},[49,427,380],{"class":228},[49,429,257],{"class":120},[49,431,385],{"class":120},[49,433,434],{"class":59},"上海市浦东新区世纪大道 100 号",[49,436,385],{"class":120},[49,438,214],{"class":120},[49,440,442],{"class":51,"line":441},20,[49,443,444],{"class":120},"        })\n",[49,446,448,450,452,454,456,458,460,462,464,466,468,470,472,474],{"class":51,"line":447},21,[49,449,336],{"class":241},[49,451,251],{"class":120},[49,453,341],{"class":228},[49,455,257],{"class":120},[49,457,347],{"class":346},[49,459,350],{"class":120},[49,461,353],{"class":120},[49,463,356],{"class":314},[49,465,318],{"class":120},[49,467,260],{"class":55},[49,469,251],{"class":120},[49,471,365],{"class":55},[49,473,328],{"class":120},[49,475,235],{"class":120},[49,477,479,481,483,485,487,489,492,494,496,498,500,502,505,507,509,512],{"class":51,"line":478},22,[49,480,375],{"class":241},[49,482,251],{"class":120},[49,484,380],{"class":228},[49,486,257],{"class":120},[49,488,385],{"class":120},[49,490,491],{"class":59},"发票 #INV-2026-001",[49,493,385],{"class":120},[49,495,350],{"class":120},[49,497,395],{"class":241},[49,499,251],{"class":120},[49,501,415],{"class":228},[49,503,504],{"class":120},"(),",[49,506,395],{"class":241},[49,508,251],{"class":120},[49,510,511],{"class":228},"AlignRight",[49,513,418],{"class":120},[49,515,517,519,521,523,525,527,530,532,534,536,538,540],{"class":51,"line":516},23,[49,518,375],{"class":241},[49,520,251],{"class":120},[49,522,380],{"class":228},[49,524,257],{"class":120},[49,526,385],{"class":120},[49,528,529],{"class":59},"到期日: 2026-03-31",[49,531,385],{"class":120},[49,533,350],{"class":120},[49,535,395],{"class":241},[49,537,251],{"class":120},[49,539,511],{"class":228},[49,541,418],{"class":120},[49,543,545],{"class":51,"line":544},24,[49,546,444],{"class":120},[49,548,550],{"class":51,"line":549},25,[49,551,552],{"class":120},"    })\n",[49,554,556,558,560,562,564,566,568,570,572,574,576],{"class":51,"line":555},26,[49,557,303],{"class":241},[49,559,251],{"class":120},[49,561,308],{"class":228},[49,563,311],{"class":120},[49,565,315],{"class":314},[49,567,318],{"class":120},[49,569,260],{"class":55},[49,571,251],{"class":120},[49,573,325],{"class":55},[49,575,328],{"class":120},[49,577,235],{"class":120},[49,579,581,583,585,587,589,592,594,596,598,600,602,604,606,608],{"class":51,"line":580},27,[49,582,336],{"class":241},[49,584,251],{"class":120},[49,586,341],{"class":228},[49,588,257],{"class":120},[49,590,591],{"class":346},"12",[49,593,350],{"class":120},[49,595,353],{"class":120},[49,597,356],{"class":314},[49,599,318],{"class":120},[49,601,260],{"class":55},[49,603,251],{"class":120},[49,605,365],{"class":55},[49,607,328],{"class":120},[49,609,235],{"class":120},[49,611,613,615,617,620,622,624,626,629,631,633],{"class":51,"line":612},28,[49,614,375],{"class":241},[49,616,251],{"class":120},[49,618,619],{"class":228},"Spacer",[49,621,257],{"class":120},[49,623,270],{"class":241},[49,625,251],{"class":120},[49,627,628],{"class":228},"Mm",[49,630,257],{"class":120},[49,632,347],{"class":346},[49,634,278],{"class":120},[49,636,638,640,642,645],{"class":51,"line":637},29,[49,639,375],{"class":241},[49,641,251],{"class":120},[49,643,644],{"class":228},"Table",[49,646,647],{"class":120},"(\n",[49,649,651,654,658,661,663,666,668,670,673,676,678,680,682,685,687,689,691,694,696],{"class":51,"line":650},30,[49,652,653],{"class":120},"                []",[49,655,657],{"class":656},"spNyl","string",[49,659,660],{"class":120},"{",[49,662,385],{"class":120},[49,664,665],{"class":59},"项目",[49,667,385],{"class":120},[49,669,350],{"class":120},[49,671,672],{"class":120}," \"",[49,674,675],{"class":59},"数量",[49,677,385],{"class":120},[49,679,350],{"class":120},[49,681,672],{"class":120},[49,683,684],{"class":59},"单价",[49,686,385],{"class":120},[49,688,350],{"class":120},[49,690,672],{"class":120},[49,692,693],{"class":59},"金额",[49,695,385],{"class":120},[49,697,698],{"class":120},"},\n",[49,700,702,705,707],{"class":51,"line":701},31,[49,703,704],{"class":120},"                [][]",[49,706,657],{"class":656},[49,708,709],{"class":120},"{\n",[49,711,713,716,718,721,723,725,727,730,732,734,736,739,741,743,745,748,750],{"class":51,"line":712},32,[49,714,715],{"class":120},"                    {",[49,717,385],{"class":120},[49,719,720],{"class":59},"前端开发",[49,722,385],{"class":120},[49,724,350],{"class":120},[49,726,672],{"class":120},[49,728,729],{"class":59},"40 小时",[49,731,385],{"class":120},[49,733,350],{"class":120},[49,735,672],{"class":120},[49,737,738],{"class":59},"¥1,080",[49,740,385],{"class":120},[49,742,350],{"class":120},[49,744,672],{"class":120},[49,746,747],{"class":59},"¥43,200",[49,749,385],{"class":120},[49,751,698],{"class":120},[49,753,755,757,759,762,764,766,768,771,773,775,777,779,781,783,785,788,790],{"class":51,"line":754},33,[49,756,715],{"class":120},[49,758,385],{"class":120},[49,760,761],{"class":59},"后端开发",[49,763,385],{"class":120},[49,765,350],{"class":120},[49,767,672],{"class":120},[49,769,770],{"class":59},"60 小时",[49,772,385],{"class":120},[49,774,350],{"class":120},[49,776,672],{"class":120},[49,778,738],{"class":59},[49,780,385],{"class":120},[49,782,350],{"class":120},[49,784,672],{"class":120},[49,786,787],{"class":59},"¥64,800",[49,789,385],{"class":120},[49,791,698],{"class":120},[49,793,795,797,799,802,804,806,808,811,813,815,817,820,822,824,826,829,831],{"class":51,"line":794},34,[49,796,715],{"class":120},[49,798,385],{"class":120},[49,800,801],{"class":59},"UI/UX 设计",[49,803,385],{"class":120},[49,805,350],{"class":120},[49,807,672],{"class":120},[49,809,810],{"class":59},"20 小时",[49,812,385],{"class":120},[49,814,350],{"class":120},[49,816,672],{"class":120},[49,818,819],{"class":59},"¥900",[49,821,385],{"class":120},[49,823,350],{"class":120},[49,825,672],{"class":120},[49,827,828],{"class":59},"¥18,000",[49,830,385],{"class":120},[49,832,698],{"class":120},[49,834,836],{"class":51,"line":835},35,[49,837,838],{"class":120},"                },\n",[49,840,842,845,847,850,852,855,857,860,862,865,867,870],{"class":51,"line":841},36,[49,843,844],{"class":241},"                template",[49,846,251],{"class":120},[49,848,849],{"class":228},"ColumnWidths",[49,851,257],{"class":120},[49,853,854],{"class":346},"40",[49,856,350],{"class":120},[49,858,859],{"class":346}," 15",[49,861,350],{"class":120},[49,863,864],{"class":346}," 20",[49,866,350],{"class":120},[49,868,869],{"class":346}," 25",[49,871,872],{"class":120},"),\n",[49,874,876,878,880,883,885,887,889,891,893,895,897,900,902,905,907,910,912,915],{"class":51,"line":875},37,[49,877,844],{"class":241},[49,879,251],{"class":120},[49,881,882],{"class":228},"TableHeaderStyle",[49,884,257],{"class":120},[49,886,260],{"class":241},[49,888,251],{"class":120},[49,890,415],{"class":228},[49,892,504],{"class":120},[49,894,395],{"class":241},[49,896,251],{"class":120},[49,898,899],{"class":228},"BgColor",[49,901,257],{"class":120},[49,903,904],{"class":241},"pdf",[49,906,251],{"class":120},[49,908,909],{"class":228},"RGBHex",[49,911,257],{"class":120},[49,913,914],{"class":346},"0xF0F0F0",[49,916,917],{"class":120},"))),\n",[49,919,921,923,925,928,930,932,934,936,938,941],{"class":51,"line":920},38,[49,922,844],{"class":241},[49,924,251],{"class":120},[49,926,927],{"class":228},"TableStripe",[49,929,257],{"class":120},[49,931,904],{"class":241},[49,933,251],{"class":120},[49,935,909],{"class":228},[49,937,257],{"class":120},[49,939,940],{"class":346},"0xFAFAFA",[49,942,943],{"class":120},")),\n",[49,945,947],{"class":51,"line":946},39,[49,948,949],{"class":120},"            )\n",[49,951,953,955,957,959,961,963,966,968,970,972,974,976,978,980,982,984,986,988,990,992,994,997],{"class":51,"line":952},40,[49,954,375],{"class":241},[49,956,251],{"class":120},[49,958,380],{"class":228},[49,960,257],{"class":120},[49,962,385],{"class":120},[49,964,965],{"class":59},"合计: ¥126,000",[49,967,385],{"class":120},[49,969,350],{"class":120},[49,971,395],{"class":241},[49,973,251],{"class":120},[49,975,511],{"class":228},[49,977,504],{"class":120},[49,979,395],{"class":241},[49,981,251],{"class":120},[49,983,415],{"class":228},[49,985,504],{"class":120},[49,987,395],{"class":241},[49,989,251],{"class":120},[49,991,400],{"class":228},[49,993,257],{"class":120},[49,995,996],{"class":346},"14",[49,998,278],{"class":120},[49,1000,1002],{"class":51,"line":1001},41,[49,1003,444],{"class":120},[49,1005,1007],{"class":51,"line":1006},42,[49,1008,552],{"class":120},[49,1010,1012,1015,1017,1020,1022,1024,1026,1029],{"class":51,"line":1011},43,[49,1013,1014],{"class":241},"    b",[49,1016,350],{"class":120},[49,1018,1019],{"class":241}," err ",[49,1021,245],{"class":120},[49,1023,289],{"class":241},[49,1025,251],{"class":120},[49,1027,1028],{"class":228},"Generate",[49,1030,297],{"class":120},[49,1032,1034,1037,1039,1042,1045],{"class":51,"line":1033},44,[49,1035,1036],{"class":137},"    if",[49,1038,1019],{"class":241},[49,1040,1041],{"class":120},"!=",[49,1043,1044],{"class":120}," nil",[49,1046,235],{"class":120},[49,1048,1050,1053,1055,1058,1060,1063],{"class":51,"line":1049},45,[49,1051,1052],{"class":241},"        log",[49,1054,251],{"class":120},[49,1056,1057],{"class":228},"Fatal",[49,1059,257],{"class":120},[49,1061,1062],{"class":241},"err",[49,1064,214],{"class":120},[49,1066,1068],{"class":51,"line":1067},46,[49,1069,1070],{"class":120},"    }\n",[49,1072,1074,1076,1078,1080,1083,1085,1088,1090,1092,1095,1097,1099,1102,1104,1107,1110,1112,1114,1116],{"class":51,"line":1073},47,[49,1075,1036],{"class":137},[49,1077,1019],{"class":241},[49,1079,245],{"class":120},[49,1081,1082],{"class":241}," os",[49,1084,251],{"class":120},[49,1086,1087],{"class":228},"WriteFile",[49,1089,257],{"class":120},[49,1091,385],{"class":120},[49,1093,1094],{"class":59},"invoice.pdf",[49,1096,385],{"class":120},[49,1098,350],{"class":120},[49,1100,1101],{"class":241}," b",[49,1103,350],{"class":120},[49,1105,1106],{"class":346}," 0644",[49,1108,1109],{"class":120},");",[49,1111,1019],{"class":241},[49,1113,1041],{"class":120},[49,1115,1044],{"class":120},[49,1117,235],{"class":120},[49,1119,1121,1123,1125,1127,1129,1131],{"class":51,"line":1120},48,[49,1122,1052],{"class":241},[49,1124,251],{"class":120},[49,1126,1057],{"class":228},[49,1128,257],{"class":120},[49,1130,1062],{"class":241},[49,1132,214],{"class":120},[49,1134,1136],{"class":51,"line":1135},49,[49,1137,1070],{"class":120},[49,1139,1141],{"class":51,"line":1140},50,[49,1142,1143],{"class":120},"}\n",[19,1145,1146],{},"注意: 上方代码中的中文字符在默认字体下会显示为豆腐块 (□)。中文字体嵌入见最后一节\"中文支持\"。先看结构。",[19,1148,1149,1152,1153,1155],{},[28,1150,1151],{},"go run ."," 在当前目录生成 ",[28,1154,1094],{},"。M1 上整个程序几毫秒完成，真正的 PDF 生成不到 150 µs，其余是进程启动时间。",[14,1157,1158],{"id":1158},"每块代码在做什么",[1160,1161,138],"h3",{"id":138},[19,1163,1164],{},"gpdf 的 4 个包:",[81,1166,1167,1180,1213,1227],{},[84,1168,1169,1171,1172,1175,1176,1179],{},[28,1170,176],{}," — 门面包。只用 ",[28,1173,1174],{},"gpdf.NewDocument","，内部是 ",[28,1177,1178],{},"template.New"," 的薄封装。",[84,1181,1182,1184,1185,1187,1188,1187,1191,1187,1194,1187,1197,1187,1200,1203,1204,1187,1206,1187,1209,1212],{},[28,1183,186],{}," — 单位 (",[28,1186,628],{},", ",[28,1189,1190],{},"Pt",[28,1192,1193],{},"Cm",[28,1195,1196],{},"In",[28,1198,1199],{},"Em",[28,1201,1202],{},"Pct",")、纸张大小 (",[28,1205,275],{},[28,1207,1208],{},"Letter",[28,1210,1211],{},"Legal",")、页边距。",[84,1214,1215,1217,1218,1187,1220,1187,1223,1226],{},[28,1216,196],{}," — 颜色原语 (",[28,1219,909],{},[28,1221,1222],{},"Gray",[28,1224,1225],{},"pdf.White"," 等常量)。",[84,1228,1229,1231,1232,1235],{},[28,1230,206],{}," — Builder API 本体。所有 ",[28,1233,1234],{},"template."," 前缀的选项、布局函数、样式修饰符都在这里。",[19,1237,1238,1239,1242,1243,1246,1247,1250,1251,1254],{},"零外部依赖。执行 ",[28,1240,1241],{},"go get github.com/gpdf-dev/gpdf"," 后 ",[28,1244,1245],{},"go.mod"," 的 ",[28,1248,1249],{},"require"," 只有一行，没有 ",[28,1252,1253],{},"indirect"," 雪崩。",[1160,1256,1257],{"id":1257},"文档构建",[40,1259,1261],{"className":112,"code":1260,"language":56,"meta":45,"style":45},"doc := gpdf.NewDocument(template.WithPageSize(document.A4))\n",[28,1262,1263],{"__ignoreMap":45},[49,1264,1265,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1290,1292],{"class":51,"line":52},[49,1266,1267],{"class":241},"doc ",[49,1269,245],{"class":120},[49,1271,248],{"class":241},[49,1273,251],{"class":120},[49,1275,254],{"class":228},[49,1277,257],{"class":120},[49,1279,260],{"class":241},[49,1281,251],{"class":120},[49,1283,265],{"class":228},[49,1285,257],{"class":120},[49,1287,270],{"class":241},[49,1289,251],{"class":120},[49,1291,275],{"class":241},[49,1293,278],{"class":120},[19,1295,1296,1298,1299,1302,1303,1306],{},[28,1297,1174],{}," 接受 ",[28,1300,1301],{},"...template.Option"," 变参。页面大小、页边距、默认字体、元数据、自定义字体全部是 ",[28,1304,1305],{},"WithXxx"," 选项。默认页边距 20 mm。",[1160,1308,1309],{"id":1309},"表头行",[40,1311,1313],{"className":112,"code":1312,"language":56,"meta":45,"style":45},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(6, func(c *template.ColBuilder) { ... })\n    r.Col(6, func(c *template.ColBuilder) { ... })\n})\n",[28,1314,1315,1340,1378,1412],{"__ignoreMap":45},[49,1316,1317,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338],{"class":51,"line":52},[49,1318,1319],{"class":241},"page",[49,1321,251],{"class":120},[49,1323,308],{"class":228},[49,1325,311],{"class":120},[49,1327,315],{"class":314},[49,1329,318],{"class":120},[49,1331,260],{"class":55},[49,1333,251],{"class":120},[49,1335,325],{"class":55},[49,1337,328],{"class":120},[49,1339,235],{"class":120},[49,1341,1342,1345,1347,1349,1351,1353,1355,1357,1359,1361,1363,1365,1367,1369,1372,1375],{"class":51,"line":127},[49,1343,1344],{"class":241},"    r",[49,1346,251],{"class":120},[49,1348,341],{"class":228},[49,1350,257],{"class":120},[49,1352,347],{"class":346},[49,1354,350],{"class":120},[49,1356,353],{"class":120},[49,1358,356],{"class":314},[49,1360,318],{"class":120},[49,1362,260],{"class":55},[49,1364,251],{"class":120},[49,1366,365],{"class":55},[49,1368,328],{"class":120},[49,1370,1371],{"class":120}," {",[49,1373,1374],{"class":120}," ...",[49,1376,1377],{"class":120}," })\n",[49,1379,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410],{"class":51,"line":134},[49,1381,1344],{"class":241},[49,1383,251],{"class":120},[49,1385,341],{"class":228},[49,1387,257],{"class":120},[49,1389,347],{"class":346},[49,1391,350],{"class":120},[49,1393,353],{"class":120},[49,1395,356],{"class":314},[49,1397,318],{"class":120},[49,1399,260],{"class":55},[49,1401,251],{"class":120},[49,1403,365],{"class":55},[49,1405,328],{"class":120},[49,1407,1371],{"class":120},[49,1409,1374],{"class":120},[49,1411,1377],{"class":120},[49,1413,1414],{"class":51,"line":144},[49,1415,1416],{"class":120},"})\n",[19,1418,1419,1420,1423,1424,1427,1428,1431],{},"gpdf 采用 ",[23,1421,1422],{},"12 列网格","，与 Bootstrap 同一心智模型。一行水平有 12 个单位，",[28,1425,1426],{},"r.Col(6, ...)"," 占一半，两个 ",[28,1429,1430],{},"Col(6)"," 加起来正好填满。",[19,1433,1434,1436,1437,1440],{},[28,1435,308],{}," 表示行高由最高的列决定。列内用 ",[28,1438,1439],{},"c.Text(...)"," 从上到下堆叠，不用显式定位，builder 内部维护光标并按元素渲染高度推进。",[1160,1442,1443],{"id":1443},"明细表格",[40,1445,1447],{"className":112,"code":1446,"language":56,"meta":45,"style":45},"c.Table(\n    []string{\"项目\", \"数量\", \"单价\", \"金额\"},\n    [][]string{ /* rows */ },\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",[28,1448,1449,1459,1500,1516,1543,1581,1603],{"__ignoreMap":45},[49,1450,1451,1453,1455,1457],{"class":51,"line":52},[49,1452,356],{"class":241},[49,1454,251],{"class":120},[49,1456,644],{"class":228},[49,1458,647],{"class":120},[49,1460,1461,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486,1488,1490,1492,1494,1496,1498],{"class":51,"line":127},[49,1462,1463],{"class":120},"    []",[49,1465,657],{"class":656},[49,1467,660],{"class":120},[49,1469,385],{"class":120},[49,1471,665],{"class":59},[49,1473,385],{"class":120},[49,1475,350],{"class":120},[49,1477,672],{"class":120},[49,1479,675],{"class":59},[49,1481,385],{"class":120},[49,1483,350],{"class":120},[49,1485,672],{"class":120},[49,1487,684],{"class":59},[49,1489,385],{"class":120},[49,1491,350],{"class":120},[49,1493,672],{"class":120},[49,1495,693],{"class":59},[49,1497,385],{"class":120},[49,1499,698],{"class":120},[49,1501,1502,1505,1507,1509,1513],{"class":51,"line":134},[49,1503,1504],{"class":120},"    [][]",[49,1506,657],{"class":656},[49,1508,660],{"class":120},[49,1510,1512],{"class":1511},"sHwdD"," /* rows */",[49,1514,1515],{"class":120}," },\n",[49,1517,1518,1521,1523,1525,1527,1529,1531,1533,1535,1537,1539,1541],{"class":51,"line":144},[49,1519,1520],{"class":241},"    template",[49,1522,251],{"class":120},[49,1524,849],{"class":228},[49,1526,257],{"class":120},[49,1528,854],{"class":346},[49,1530,350],{"class":120},[49,1532,859],{"class":346},[49,1534,350],{"class":120},[49,1536,864],{"class":346},[49,1538,350],{"class":120},[49,1540,869],{"class":346},[49,1542,872],{"class":120},[49,1544,1545,1547,1549,1551,1553,1555,1557,1559,1561,1563,1565,1567,1569,1571,1573,1575,1577,1579],{"class":51,"line":156},[49,1546,1520],{"class":241},[49,1548,251],{"class":120},[49,1550,882],{"class":228},[49,1552,257],{"class":120},[49,1554,260],{"class":241},[49,1556,251],{"class":120},[49,1558,415],{"class":228},[49,1560,504],{"class":120},[49,1562,395],{"class":241},[49,1564,251],{"class":120},[49,1566,899],{"class":228},[49,1568,257],{"class":120},[49,1570,904],{"class":241},[49,1572,251],{"class":120},[49,1574,909],{"class":228},[49,1576,257],{"class":120},[49,1578,914],{"class":346},[49,1580,917],{"class":120},[49,1582,1583,1585,1587,1589,1591,1593,1595,1597,1599,1601],{"class":51,"line":166},[49,1584,1520],{"class":241},[49,1586,251],{"class":120},[49,1588,927],{"class":228},[49,1590,257],{"class":120},[49,1592,904],{"class":241},[49,1594,251],{"class":120},[49,1596,909],{"class":228},[49,1598,257],{"class":120},[49,1600,940],{"class":346},[49,1602,943],{"class":120},[49,1604,1605],{"class":51,"line":171},[49,1606,214],{"class":120},[19,1608,1609,1611,1612,1615],{},[28,1610,849],{}," 是",[23,1613,1614],{},"百分比","而不是绝对点值，四个数的和应为 100。和不等于 100 不会报错但最右列可能溢出——这是唯一的坑。",[19,1617,1618,1620,1621,1187,1623,1187,1626,1187,1628,1631,1632,1635],{},[28,1619,882],{}," 接受所有文本选项 (",[28,1622,415],{},[28,1624,1625],{},"TextColor",[28,1627,899],{},[28,1629,1630],{},"AlignCenter",")。",[28,1633,1634],{},"TableStripe(color)"," 渲染斑马行。",[19,1637,1638],{},"表格自动处理行高和分页。超出当前页时 gpdf 自动换页并在续页上重绘表头。",[1160,1640,1641],{"id":1641},"合计",[40,1643,1645],{"className":112,"code":1644,"language":56,"meta":45,"style":45},"c.Text(\"合计: ¥126,000\", template.AlignRight(), template.Bold(), template.FontSize(14))\n",[28,1646,1647],{"__ignoreMap":45},[49,1648,1649,1651,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691],{"class":51,"line":52},[49,1650,356],{"class":241},[49,1652,251],{"class":120},[49,1654,380],{"class":228},[49,1656,257],{"class":120},[49,1658,385],{"class":120},[49,1660,965],{"class":59},[49,1662,385],{"class":120},[49,1664,350],{"class":120},[49,1666,395],{"class":241},[49,1668,251],{"class":120},[49,1670,511],{"class":228},[49,1672,504],{"class":120},[49,1674,395],{"class":241},[49,1676,251],{"class":120},[49,1678,415],{"class":228},[49,1680,504],{"class":120},[49,1682,395],{"class":241},[49,1684,251],{"class":120},[49,1686,400],{"class":228},[49,1688,257],{"class":120},[49,1690,996],{"class":346},[49,1692,278],{"class":120},[19,1694,1695,1696,1698],{},"表格外的另一个 ",[28,1697,380],{}," 调用，右对齐，稍大号字体。",[1160,1700,1701],{"id":1701},"生成与写入",[40,1703,1705],{"className":112,"code":1704,"language":56,"meta":45,"style":45},"b, err := doc.Generate()\nif err != nil { log.Fatal(err) }\nif err := os.WriteFile(\"invoice.pdf\", b, 0644); err != nil { log.Fatal(err) }\n",[28,1706,1707,1726,1755],{"__ignoreMap":45},[49,1708,1709,1712,1714,1716,1718,1720,1722,1724],{"class":51,"line":52},[49,1710,1711],{"class":241},"b",[49,1713,350],{"class":120},[49,1715,1019],{"class":241},[49,1717,245],{"class":120},[49,1719,289],{"class":241},[49,1721,251],{"class":120},[49,1723,1028],{"class":228},[49,1725,297],{"class":120},[49,1727,1728,1731,1733,1735,1737,1739,1742,1744,1746,1748,1750,1752],{"class":51,"line":127},[49,1729,1730],{"class":137},"if",[49,1732,1019],{"class":241},[49,1734,1041],{"class":120},[49,1736,1044],{"class":120},[49,1738,1371],{"class":120},[49,1740,1741],{"class":241}," log",[49,1743,251],{"class":120},[49,1745,1057],{"class":228},[49,1747,257],{"class":120},[49,1749,1062],{"class":241},[49,1751,328],{"class":120},[49,1753,1754],{"class":120}," }\n",[49,1756,1757,1759,1761,1763,1765,1767,1769,1771,1773,1775,1777,1779,1781,1783,1785,1787,1789,1791,1793,1795,1797,1799,1801,1803,1805,1807],{"class":51,"line":134},[49,1758,1730],{"class":137},[49,1760,1019],{"class":241},[49,1762,245],{"class":120},[49,1764,1082],{"class":241},[49,1766,251],{"class":120},[49,1768,1087],{"class":228},[49,1770,257],{"class":120},[49,1772,385],{"class":120},[49,1774,1094],{"class":59},[49,1776,385],{"class":120},[49,1778,350],{"class":120},[49,1780,1101],{"class":241},[49,1782,350],{"class":120},[49,1784,1106],{"class":346},[49,1786,1109],{"class":120},[49,1788,1019],{"class":241},[49,1790,1041],{"class":120},[49,1792,1044],{"class":120},[49,1794,1371],{"class":120},[49,1796,1741],{"class":241},[49,1798,251],{"class":120},[49,1800,1057],{"class":228},[49,1802,257],{"class":120},[49,1804,1062],{"class":241},[49,1806,328],{"class":120},[49,1808,1754],{"class":120},[19,1810,1811,1814,1815,1818,1819,1822,1823,1826],{},[28,1812,1813],{},"doc.Generate()"," 返回 ",[28,1816,1817],{},"([]byte, error)","，不触碰文件系统。字节切片本身就是完整 PDF，可以写盘、上传 S3、作为 HTTP 响应 ",[28,1820,1821],{},"w.Write(b)"," 或邮件附件。也可以用 ",[28,1824,1825],{},"doc.Render(w io.Writer)"," 流式写入。",[14,1828,1829],{"id":1829},"中文支持",[19,1831,1832],{},"上述代码默认字体只有 Latin 字形，中文会显示为豆腐。用 Noto Sans SC 嵌入一个 TTF 即可:",[40,1834,1836],{"className":112,"code":1835,"language":56,"meta":45,"style":45},"ttf, err := os.ReadFile(\"NotoSansSC-Regular.ttf\")\nif err != nil { log.Fatal(err) }\ndoc := gpdf.NewDocument(\n    template.WithPageSize(document.A4),\n    template.WithFont(\"NotoSansSC\", ttf),\n    template.WithDefaultFont(\"NotoSansSC\", 10),\n)\n",[28,1837,1838,1867,1893,1907,1925,1950,1974],{"__ignoreMap":45},[49,1839,1840,1843,1845,1847,1849,1851,1853,1856,1858,1860,1863,1865],{"class":51,"line":52},[49,1841,1842],{"class":241},"ttf",[49,1844,350],{"class":120},[49,1846,1019],{"class":241},[49,1848,245],{"class":120},[49,1850,1082],{"class":241},[49,1852,251],{"class":120},[49,1854,1855],{"class":228},"ReadFile",[49,1857,257],{"class":120},[49,1859,385],{"class":120},[49,1861,1862],{"class":59},"NotoSansSC-Regular.ttf",[49,1864,385],{"class":120},[49,1866,214],{"class":120},[49,1868,1869,1871,1873,1875,1877,1879,1881,1883,1885,1887,1889,1891],{"class":51,"line":127},[49,1870,1730],{"class":137},[49,1872,1019],{"class":241},[49,1874,1041],{"class":120},[49,1876,1044],{"class":120},[49,1878,1371],{"class":120},[49,1880,1741],{"class":241},[49,1882,251],{"class":120},[49,1884,1057],{"class":228},[49,1886,257],{"class":120},[49,1888,1062],{"class":241},[49,1890,328],{"class":120},[49,1892,1754],{"class":120},[49,1894,1895,1897,1899,1901,1903,1905],{"class":51,"line":134},[49,1896,1267],{"class":241},[49,1898,245],{"class":120},[49,1900,248],{"class":241},[49,1902,251],{"class":120},[49,1904,254],{"class":228},[49,1906,647],{"class":120},[49,1908,1909,1911,1913,1915,1917,1919,1921,1923],{"class":51,"line":144},[49,1910,1520],{"class":241},[49,1912,251],{"class":120},[49,1914,265],{"class":228},[49,1916,257],{"class":120},[49,1918,270],{"class":241},[49,1920,251],{"class":120},[49,1922,275],{"class":241},[49,1924,872],{"class":120},[49,1926,1927,1929,1931,1934,1936,1938,1941,1943,1945,1948],{"class":51,"line":156},[49,1928,1520],{"class":241},[49,1930,251],{"class":120},[49,1932,1933],{"class":228},"WithFont",[49,1935,257],{"class":120},[49,1937,385],{"class":120},[49,1939,1940],{"class":59},"NotoSansSC",[49,1942,385],{"class":120},[49,1944,350],{"class":120},[49,1946,1947],{"class":241}," ttf",[49,1949,872],{"class":120},[49,1951,1952,1954,1956,1959,1961,1963,1965,1967,1969,1972],{"class":51,"line":166},[49,1953,1520],{"class":241},[49,1955,251],{"class":120},[49,1957,1958],{"class":228},"WithDefaultFont",[49,1960,257],{"class":120},[49,1962,385],{"class":120},[49,1964,1940],{"class":59},[49,1966,385],{"class":120},[49,1968,350],{"class":120},[49,1970,1971],{"class":346}," 10",[49,1973,872],{"class":120},[49,1975,1976],{"class":51,"line":171},[49,1977,214],{"class":120},[19,1979,1980],{},"gpdf 自动做字形子集化，不会把整个 3 MB TTF 嵌入 PDF。只有实际用到的字形被嵌入。",[19,1982,1983,1984,1989],{},"繁体中文见 ",[1985,1986,1988],"a",{"href":1987},"/zh/blog/japanese-pdf-in-go","Go 处理中文 PDF — 简繁字体选型"," (相似逻辑)。",[14,1991,1993],{"id":1992},"不破坏-50-行前提下美化","不破坏 50 行前提下美化",[19,1995,1996,1999,2000,2003],{},[23,1997,1998],{},"品牌色。"," 挑一个 hex 值 (例如深蓝 ",[28,2001,2002],{},"0x1A237E",") 贯穿公司名和表头:",[40,2005,2007],{"className":112,"code":2006,"language":56,"meta":45,"style":45},"brand := pdf.RGBHex(0x1A237E)\nc.Text(\"ACME 公司\", template.FontSize(22), template.Bold(), template.TextColor(brand))\ntemplate.TableHeaderStyle(template.Bold(), template.TextColor(pdf.White), template.BgColor(brand)),\n",[28,2008,2009,2029,2080],{"__ignoreMap":45},[49,2010,2011,2014,2016,2019,2021,2023,2025,2027],{"class":51,"line":52},[49,2012,2013],{"class":241},"brand ",[49,2015,245],{"class":120},[49,2017,2018],{"class":241}," pdf",[49,2020,251],{"class":120},[49,2022,909],{"class":228},[49,2024,257],{"class":120},[49,2026,2002],{"class":346},[49,2028,214],{"class":120},[49,2030,2031,2033,2035,2037,2039,2041,2043,2045,2047,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2073,2075,2078],{"class":51,"line":127},[49,2032,356],{"class":241},[49,2034,251],{"class":120},[49,2036,380],{"class":228},[49,2038,257],{"class":120},[49,2040,385],{"class":120},[49,2042,388],{"class":59},[49,2044,385],{"class":120},[49,2046,350],{"class":120},[49,2048,395],{"class":241},[49,2050,251],{"class":120},[49,2052,400],{"class":228},[49,2054,257],{"class":120},[49,2056,405],{"class":346},[49,2058,408],{"class":120},[49,2060,395],{"class":241},[49,2062,251],{"class":120},[49,2064,415],{"class":228},[49,2066,504],{"class":120},[49,2068,395],{"class":241},[49,2070,251],{"class":120},[49,2072,1625],{"class":228},[49,2074,257],{"class":120},[49,2076,2077],{"class":241},"brand",[49,2079,278],{"class":120},[49,2081,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2108,2110,2113,2115,2117,2119,2121,2123,2125],{"class":51,"line":134},[49,2083,260],{"class":241},[49,2085,251],{"class":120},[49,2087,882],{"class":228},[49,2089,257],{"class":120},[49,2091,260],{"class":241},[49,2093,251],{"class":120},[49,2095,415],{"class":228},[49,2097,504],{"class":120},[49,2099,395],{"class":241},[49,2101,251],{"class":120},[49,2103,1625],{"class":228},[49,2105,257],{"class":120},[49,2107,904],{"class":241},[49,2109,251],{"class":120},[49,2111,2112],{"class":241},"White",[49,2114,408],{"class":120},[49,2116,395],{"class":241},[49,2118,251],{"class":120},[49,2120,899],{"class":228},[49,2122,257],{"class":120},[49,2124,2077],{"class":241},[49,2126,943],{"class":120},[19,2128,2129,2132],{},[23,2130,2131],{},"小计和税额。"," 合计上方分行列出:",[40,2134,2136],{"className":112,"code":2135,"language":56,"meta":45,"style":45},"c.Text(\"小计: ¥126,000\", template.AlignRight())\nc.Text(\"增值税 (13%): ¥16,380\", template.AlignRight())\nc.Text(\"合计: ¥142,380\", template.AlignRight(), template.Bold(), template.FontSize(14))\n",[28,2137,2138,2165,2192],{"__ignoreMap":45},[49,2139,2140,2142,2144,2146,2148,2150,2153,2155,2157,2159,2161,2163],{"class":51,"line":52},[49,2141,356],{"class":241},[49,2143,251],{"class":120},[49,2145,380],{"class":228},[49,2147,257],{"class":120},[49,2149,385],{"class":120},[49,2151,2152],{"class":59},"小计: ¥126,000",[49,2154,385],{"class":120},[49,2156,350],{"class":120},[49,2158,395],{"class":241},[49,2160,251],{"class":120},[49,2162,511],{"class":228},[49,2164,418],{"class":120},[49,2166,2167,2169,2171,2173,2175,2177,2180,2182,2184,2186,2188,2190],{"class":51,"line":127},[49,2168,356],{"class":241},[49,2170,251],{"class":120},[49,2172,380],{"class":228},[49,2174,257],{"class":120},[49,2176,385],{"class":120},[49,2178,2179],{"class":59},"增值税 (13%): ¥16,380",[49,2181,385],{"class":120},[49,2183,350],{"class":120},[49,2185,395],{"class":241},[49,2187,251],{"class":120},[49,2189,511],{"class":228},[49,2191,418],{"class":120},[49,2193,2194,2196,2198,2200,2202,2204,2207,2209,2211,2213,2215,2217,2219,2221,2223,2225,2227,2229,2231,2233,2235,2237],{"class":51,"line":134},[49,2195,356],{"class":241},[49,2197,251],{"class":120},[49,2199,380],{"class":228},[49,2201,257],{"class":120},[49,2203,385],{"class":120},[49,2205,2206],{"class":59},"合计: ¥142,380",[49,2208,385],{"class":120},[49,2210,350],{"class":120},[49,2212,395],{"class":241},[49,2214,251],{"class":120},[49,2216,511],{"class":228},[49,2218,504],{"class":120},[49,2220,395],{"class":241},[49,2222,251],{"class":120},[49,2224,415],{"class":228},[49,2226,504],{"class":120},[49,2228,395],{"class":241},[49,2230,251],{"class":120},[49,2232,400],{"class":228},[49,2234,257],{"class":120},[49,2236,996],{"class":346},[49,2238,278],{"class":120},[19,2240,2241,2244,2245,2248],{},[23,2242,2243],{},"增值税发票要素。"," 开票方的统一社会信用代码、购方名称和纳税人识别号只是多几行 ",[28,2246,2247],{},"c.Text","，布局不变。",[14,2250,2251],{"id":2251},"运行",[40,2253,2255],{"className":42,"code":2254,"language":44,"meta":45,"style":45},"mkdir invoice-demo\ncd invoice-demo\ngo mod init example.com/invoice-demo\ngo get github.com/gpdf-dev/gpdf\n# 粘贴 main.go\ngo run .\n",[28,2256,2257,2265,2272,2285,2293,2298],{"__ignoreMap":45},[49,2258,2259,2262],{"class":51,"line":52},[49,2260,2261],{"class":55},"mkdir",[49,2263,2264],{"class":59}," invoice-demo\n",[49,2266,2267,2270],{"class":51,"line":127},[49,2268,2269],{"class":228},"cd",[49,2271,2264],{"class":59},[49,2273,2274,2276,2279,2282],{"class":51,"line":134},[49,2275,56],{"class":55},[49,2277,2278],{"class":59}," mod",[49,2280,2281],{"class":59}," init",[49,2283,2284],{"class":59}," example.com/invoice-demo\n",[49,2286,2287,2289,2291],{"class":51,"line":144},[49,2288,56],{"class":55},[49,2290,60],{"class":59},[49,2292,63],{"class":59},[49,2294,2295],{"class":51,"line":156},[49,2296,2297],{"class":1511},"# 粘贴 main.go\n",[49,2299,2300,2302,2305],{"class":51,"line":166},[49,2301,56],{"class":55},[49,2303,2304],{"class":59}," run",[49,2306,2307],{"class":59}," .\n",[14,2309,2310],{"id":2310},"这种写法什么时候会失效",[81,2312,2313,2323,2332,2348],{},[84,2314,2315,2318,2319,2322],{},[23,2316,2317],{},"明细变成数据。"," 从数据库或 JSON 读取时，只是构造 ",[28,2320,2321],{},"[][]string"," 的过程变了，表格代码不变。",[84,2324,2325,2328,2329,69],{},[23,2326,2327],{},"需要复用布局。"," 一旦要循环生成多张发票，把主体抽到 ",[28,2330,2331],{},"func renderInvoice(doc *template.Document, inv Invoice)",[84,2333,2334,2337,2338,2342,2343,2347],{},[23,2335,2336],{},"布局有条件分支。"," 不同客户列不同时，Builder API 会变冗长，换成 ",[1985,2339,2341],{"href":2340},"/zh/docs/guide/json-templates","JSON 模板入口"," 或 ",[1985,2344,2346],{"href":2345},"/zh/docs/guide/go-templates","Go 模板入口"," 更合适。",[84,2349,2350,2353],{},[23,2351,2352],{},"需要 CJK 字符。"," 见上方中文支持节。",[19,2355,2356],{},"以上四种情况都不会要求\"推倒重来\"，都是增量扩展。",[14,2358,2360],{"id":2359},"faq","FAQ",[19,2362,2363,2366],{},[23,2364,2365],{},"可以商用吗？","\n可以。gpdf 是 MIT 许可证，闭源商用产品也可嵌入，无署名要求。",[19,2368,2369,2376,2377,69],{},[23,2370,2371,2372,2375],{},"可以不经过字节切片直接写到 ",[28,2373,2374],{},"io.Writer"," 吗？","\n可以。",[28,2378,2379],{},"doc.Render(w io.Writer) error",[19,2381,2382,2385,2386,2389],{},[23,2383,2384],{},"实际有多快？","\n上方 50 行在 M1 上约 100 µs 生成 PDF。单页 hello world 是 ",[23,2387,2388],{},"13 µs","。批量场景下 gpdf 不会成为瓶颈。",[19,2391,2392,2399],{},[23,2393,2394,2395,2398],{},"有没有 ",[28,2396,2397],{},"gpdf.Invoice"," 辅助函数？","\n没有。发票格式在不同国家差异很大，任何简化都会漏掉某些场景。50 行的起点比一个构造器更灵活。",[19,2401,2402,2405,2406,2409,2410,69],{},[23,2403,2404],{},"支持 PDF/A 吗？","\n支持 PDF/A-2b，构建时传 ",[28,2407,2408],{},"gpdf.WithPDFA(pdfa.Level2B)","。详见 ",[1985,2411,2413],{"href":2412},"/zh/docs/guide/pdf-a","纯 Go 构建 PDF/A-2b",[14,2415,2417],{"id":2416},"试用-gpdf","试用 gpdf",[19,2419,2420],{},"gpdf 是 Go 的 PDF 生成库。MIT 许可证、零依赖、原生 CJK，基准测试中比其他库快 10–30 倍。",[40,2422,2423],{"className":42,"code":43,"language":44,"meta":45,"style":45},[28,2424,2425],{"__ignoreMap":45},[49,2426,2427,2429,2431],{"class":51,"line":52},[49,2428,56],{"class":55},[49,2430,60],{"class":59},[49,2432,63],{"class":59},[19,2434,2435,2441,2442],{},[1985,2436,2440],{"href":2437,"rel":2438},"https://github.com/gpdf-dev/gpdf",[2439],"nofollow","⭐ 在 GitHub Star"," · ",[1985,2443,2446],{"href":2444,"rel":2445},"https://gpdf.dev/zh/docs/quickstart",[2439],"阅读文档",[14,2448,2449],{"id":2449},"下一步阅读",[81,2451,2452,2459,2466],{},[84,2453,2454,2458],{},[1985,2455,2457],{"href":2456},"/zh/blog/why-gpdf-is-faster","为什么 gpdf 比其他 Go PDF 库快 10–30 倍"," — 上面\"数百微秒\"说法的基准数字。",[84,2460,2461,2465],{},[1985,2462,2464],{"href":2463},"/zh/blog/go-pdf-library-showdown-2026","2026 Go PDF 库横评"," — 同样的发票用 gofpdf / gopdf / Maroto 写各自需要多少行。",[84,2467,2468,2472],{},[1985,2469,2471],{"href":2470},"/zh/blog/12-column-grid","gpdf 的 12 列网格是怎么工作的"," — 上方表头行使用的布局模型细节。",[2474,2475,2476],"style",{},"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":45,"searchDepth":127,"depth":127,"links":2478},[2479,2480,2481,2482,2490,2491,2492,2493,2494,2495,2496],{"id":16,"depth":127,"text":17},{"id":72,"depth":127,"text":73},{"id":109,"depth":127,"text":25},{"id":1158,"depth":127,"text":1158,"children":2483},[2484,2485,2486,2487,2488,2489],{"id":138,"depth":134,"text":138},{"id":1257,"depth":134,"text":1257},{"id":1309,"depth":134,"text":1309},{"id":1443,"depth":134,"text":1443},{"id":1641,"depth":134,"text":1641},{"id":1701,"depth":134,"text":1701},{"id":1829,"depth":127,"text":1829},{"id":1992,"depth":127,"text":1993},{"id":2251,"depth":127,"text":2251},{"id":2310,"depth":127,"text":2310},{"id":2359,"depth":127,"text":2360},{"id":2416,"depth":127,"text":2417},{"id":2449,"depth":127,"text":2449},"2026-04-21","可运行的完整发票 PDF 生成器，50 行 Go 代码。零依赖，不需要 Chromium，不需要 CGO。gpdf 一个包搞定表头、表格、合计。",false,"md",{"name":2502,"totalTime":2503,"tools":2504,"steps":2506},"用 gpdf 在 Go 中生成发票 PDF","PT10M",[2505],"Go 1.22+",[2507,2510,2513,2516,2519],{"name":2508,"text":2509},"安装 gpdf","在模块中执行 go get github.com/gpdf-dev/gpdf。gpdf 零依赖，go.sum 只有一行，不引入 CGO。",{"name":2511,"text":2512},"构建文档","调用 gpdf.NewDocument 并传入 template.WithPageSize(document.A4)。doc.AddPage() 返回 PageBuilder，后续把 AutoRow 叠加上去。",{"name":2514,"text":2515},"用两个 6 列单元格布局表头","在 page.AutoRow 中调用两次 r.Col(6, ...)。左侧放公司信息，右侧用 template.AlignRight() 右对齐发票号和到期日。",{"name":2517,"text":2518},"渲染明细表格","在 12 列单元格内调用 c.Table，传入表头切片和行切片。用 template.ColumnWidths 和 template.TableHeaderStyle 控制宽度和样式。",{"name":2520,"text":2521},"生成并写入字节","调用 doc.Generate() 获得 []byte，然后 os.WriteFile(\"invoice.pdf\", b, 0644)。不需要临时文件，也不调用外部二进制。",null,{},"/zh/blog/invoice-pdf-go-under-50-lines",{"title":5,"description":2498},"zh/blog/012.invoice-pdf-go-under-50-lines",[2528,2529],"tutorial","templates","pqNrUwjVGhX17RCs8_Y7Z5NXRDPcEeyH_7fGUMas7zw",1779199017989]