[{"data":1,"prerenderedAt":5393},["ShallowReactive",2],{"blog-zh-signintech-gopdf-migration":3},{"id":4,"title":5,"author":6,"body":10,"date":5352,"description":5353,"draft":5354,"extension":5355,"howTo":5356,"image":5383,"meta":5384,"navigation":546,"path":5385,"seo":5386,"stem":5387,"tags":5388,"updated":5383,"__hash__":5392},"blogZh/zh/blog/021.signintech-gopdf-migration.md","从 signintech/gopdf 迁移到 gpdf：少写坐标计算",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":5335},"minimark",[13,18,39,59,62,65,69,72,75,84,87,90,93,103,125,128,132,135,483,507,511,514,519,998,1003,1436,1443,1447,1450,1454,1913,1923,1927,2209,2226,2230,2236,2240,3031,3037,3041,3299,3305,3316,3320,3323,3327,3729,3736,3740,3997,4007,4011,4014,4018,4296,4299,4303,4681,4689,4693,4696,4974,4977,4980,4983,5078,5084,5091,5095,5098,5154,5161,5165,5171,5177,5191,5213,5235,5251,5257,5261,5264,5281,5296,5299,5331],[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,25,26,29,30,34,35,38],"p",{},[22,23,24],"strong",{},"gpdf"," 是带 12 列布局引擎的纯 Go PDF 库。",[22,27,28],{},"signintech/gopdf"," 是 PDF 坐标系的低层绑定。如果你已经用 gopdf 一段时间,代码库现在大部分由 ",[31,32,33],"code",{},"SetXY","、",[31,36,37],{},"Cell"," 和宽度算术构成,这篇文章展示这些调用在布局引擎下会塌缩成什么。",[19,40,41,42,44,45,44,47,50,51,54,55,58],{},"上周我和一位同行重构基于 signintech/gopdf 的发票生成器。五年累积。绘制明细行表格的函数有 280 行。其中真正干活的大约 40 行 — 格式化金额、格式化日期、按行重复。剩下 240 行在做坐标计算: 计算 x 位置、追踪 y、调用 ",[31,43,33],{},"、调用 ",[31,46,37],{},[31,48,49],{},"Br","、用 ",[31,52,53],{},"Line(x1, y1, x2, y2)"," 画边框、判断行是否能放进当前页、放不进时手动 ",[31,56,57],{},"AddPage"," 并重绘表头。",[19,60,61],{},"这就是生产环境的 gopdf。它不是糟糕的库 — 是薄、快、无 CGO 的 PDF 成像模型绑定,名副其实。有光标、有坐标,而工程师扮演布局引擎。",[19,63,64],{},"本文按函数把 gopdf API 映射到 gpdf。结论已在标题: 大多数代码行会消失,因为它们做的是运行时可以替你做的布局数学。",[14,66,68],{"id":67},"signintechgopdf-的优点-以及它不是什么","signintech/gopdf 的优点 — 以及它不是什么",[19,70,71],{},"进入「迁移走」叙事之前先讲清楚,gopdf 有真本事。",[19,73,74],{},"维护活跃。纯 Go (无 CGO),交叉编译和 Alpine 镜像都顺畅。支持 TrueType 字体包括 CJK。输出快 — gopdf 在成像原语上和 gpdf 是同一档,因为两者都直接写 PDF wire format,没有重型引擎。API 直接映射底层 PDF 模型: 有当前点,你移动它,你在那里画。如果你已经按 PDF 坐标思考,gopdf 用着舒服。",[19,76,77,78,80,81,83],{},"不舒服的地方是它没有布局系统。没有行、列、flex、网格的概念。没有自动分页: 内容超过下边距,内容就超过下边距 (或干脆跑出页面),除非你自己调用 ",[31,79,57],{},"。表格不是原语 — 是每个项目都重写一次的模式: 逐单元格 ",[31,82,37],{}," 调用、手画边框、自己写分页逻辑。",[19,85,86],{},"对单页证书或非常受控的固定模板表单,光标模型够用。对发票、报表、对账单、任何含可变长内容的文档,坐标计算随文档面积线性增长。这才是 gpdf 针对的工作负载。",[14,88,89],{"id":89},"心智模型转换",[19,91,92],{},"这是真正改变代码读感的部分。gpdf 有 gopdf 没有的两个想法。",[19,94,95,98,99,102],{},[22,96,97],{},"声明式树。"," 你不告诉渲染器东西放哪。你描述一棵 page → row → column → content 的树,布局引擎单遍解析位置。没有要推进的光标。两次连续 ",[31,100,101],{},"r.Col(...)"," 互不知情。",[19,104,105,108,109,112,113,116,117,120,121,124],{},[22,106,107],{},"12 列网格。"," 每行隐式宽 12 单位。你按列分配它们: ",[31,110,111],{},"r.Col(8, ...)"," 占 2/3,",[31,114,115],{},"r.Col(4, ...)"," 占 1/3。和 Bootstrap 与 Tailwind 在 HTML 中用的同一思想,搬到 PDF。你不再算 ",[31,118,119],{},"pageWidth - leftMargin - rightMargin"," 再除以 4。你写 ",[31,122,123],{},"r.Col(3, ...)"," 四次。",[19,126,127],{},"这两点就移除了大部分数学。后面的 Before/After 对子全部以同样的方式塌缩: 推进光标的命令式循环变成一棵小的声明式树。",[14,129,131],{"id":130},"api-映射表","API 映射表",[19,133,134],{},"先发参考表。后面五对具体对照。",[136,137,138,152],"table",{},[139,140,141],"thead",{},[142,143,144,148,150],"tr",{},[145,146,147],"th",{},"你想做",[145,149,28],{},[145,151,24],{},[153,154,155,171,186,201,221,239,257,276,294,310,325,340,355,370,391,413,435,450,465],"tbody",{},[142,156,157,161,166],{},[158,159,160],"td",{},"构造",[158,162,163],{},[31,164,165],{},"pdf := gopdf.GoPdf{}; pdf.Start(gopdf.Config{...})",[158,167,168],{},[31,169,170],{},"doc := gpdf.NewDocument(gpdf.WithPageSize(document.A4), ...)",[142,172,173,176,181],{},[158,174,175],{},"设置页面大小",[158,177,178],{},[31,179,180],{},"Config{PageSize: gopdf.PageSizeA4}",[158,182,183],{},[31,184,185],{},"gpdf.WithPageSize(document.A4)",[142,187,188,191,196],{},[158,189,190],{},"添加页面",[158,192,193],{},[31,194,195],{},"pdf.AddPage()",[158,197,198],{},[31,199,200],{},"page := doc.AddPage()",[142,202,203,206,216],{},[158,204,205],{},"移动光标",[158,207,208,211,212],{},[31,209,210],{},"pdf.SetX(40); pdf.SetY(80)"," ",[213,214,215],"em",{},"(到处)",[158,217,218],{},[213,219,220],{},"(无光标)",[142,222,223,226,231],{},[158,224,225],{},"单行文本",[158,227,228],{},[31,229,230],{},"pdf.SetXY(x, y); pdf.Cell(nil, \"hi\")",[158,232,233,211,236],{},[31,234,235],{},"c.Text(\"hi\")",[213,237,238],{},"(在列内)",[142,240,241,244,249],{},[158,242,243],{},"自动换行文本",[158,245,246],{},[31,247,248],{},"pdf.MultiCell(&gopdf.Rect{W: 200, H: 100}, body)",[158,250,251,211,254],{},[31,252,253],{},"c.Text(body)",[213,255,256],{},"(自动换行)",[142,258,259,262,267],{},[158,260,261],{},"换行",[158,263,264],{},[31,265,266],{},"pdf.Br(20)",[158,268,269],{},[213,270,271,272,275],{},"(行间隐式;需要时 ",[31,273,274],{},"c.Spacer(document.Mm(4))",")",[142,277,278,281,286],{},[158,279,280],{},"字体注册",[158,282,283],{},[31,284,285],{},"pdf.AddTTFFont(\"noto\", \"fonts/Noto.ttf\")",[158,287,288,211,291],{},[31,289,290],{},"gpdf.WithFont(\"Noto\", ttfBytes)",[213,292,293],{},"(构建时)",[142,295,296,299,304],{},[158,297,298],{},"设置当前字体",[158,300,301],{},[31,302,303],{},"pdf.SetFont(\"noto\", \"\", 14)",[158,305,306,307],{},"每段文本 ",[31,308,309],{},"template.FontFamily(\"Noto\"), template.FontSize(14)",[142,311,312,315,320],{},[158,313,314],{},"颜色",[158,316,317],{},[31,318,319],{},"pdf.SetTextColor(26, 35, 126)",[158,321,322],{},[31,323,324],{},"template.TextColor(pdf.RGBHex(0x1A237E))",[142,326,327,330,335],{},[158,328,329],{},"水平线",[158,331,332],{},[31,333,334],{},"pdf.Line(40, 100, 555, 100)",[158,336,337],{},[31,338,339],{},"c.Line(template.LineColor(pdf.Gray(0.7)))",[142,341,342,345,350],{},[158,343,344],{},"矩形",[158,346,347],{},[31,348,349],{},"pdf.RectFromUpperLeftWithStyle(x, y, w, h, \"FD\")",[158,351,352],{},[31,353,354],{},"c.Box(template.BgColor(...), template.Border(...))",[142,356,357,360,365],{},[158,358,359],{},"图像",[158,361,362],{},[31,363,364],{},"pdf.Image(\"logo.png\", x, y, &gopdf.Rect{W: 100, H: 50})",[158,366,367],{},[31,368,369],{},"c.Image(imgBytes, template.FitWidth(document.Mm(35)))",[142,371,372,375,386],{},[158,373,374],{},"手写表格",[158,376,377,378,380,381,380,384],{},"数十次 ",[31,379,37],{}," + ",[31,382,383],{},"Line",[31,385,33],{},[158,387,388],{},[31,389,390],{},"c.Table(headers, rows, template.ColumnWidths(...))",[142,392,393,396,405],{},[158,394,395],{},"页眉 / 页脚",[158,397,398,401,402],{},[31,399,400],{},"pdf.AddHeader(fn)"," / ",[31,403,404],{},"pdf.AddFooter(fn)",[158,406,407,401,410],{},[31,408,409],{},"doc.Header(fn)",[31,411,412],{},"doc.Footer(fn)",[142,414,415,418,424],{},[158,416,417],{},"页码",[158,419,420,421],{},"从自维护计数器格式化 ",[31,422,423],{},"\"Page %d of %d\"",[158,425,426,401,429,211,432],{},[31,427,428],{},"c.PageNumber()",[31,430,431],{},"c.TotalPages()",[213,433,434],{},"(占位符)",[142,436,437,440,445],{},[158,438,439],{},"加密",[158,441,442],{},[31,443,444],{},"Config{Protection: PDFProtectionConfig{...}}",[158,446,447],{},[31,448,449],{},"gpdf.WithEncryption(gpdf.AES256, \"user\", \"owner\", perms)",[142,451,452,455,460],{},[158,453,454],{},"输出",[158,456,457],{},[31,458,459],{},"pdf.WritePdf(\"out.pdf\")",[158,461,462],{},[31,463,464],{},"data, _ := doc.Generate(); os.WriteFile(\"out.pdf\", data, 0o644)",[142,466,467,470,478],{},[158,468,469],{},"输出到 writer",[158,471,472,401,475],{},[31,473,474],{},"pdf.Write(w)",[31,476,477],{},"pdf.ToBuffer()",[158,479,480],{},[31,481,482],{},"doc.Render(w)",[19,484,485,486,489,490,492,493,495,496,498,499,502,503,506],{},"两个结构性变化。第一,",[22,487,488],{},"光标消失","。表中标 ",[213,491,215],{}," 的行不是夸张 — 真实生产 gopdf 代码中 ",[31,494,33],{}," 出现次数超过 ",[31,497,37],{},"。在 gpdf 这些全部塌缩为零。第二,",[22,500,501],{},"像素变成百分比","。",[31,504,505],{},"Rect{W: 200, H: 100}"," 变成「这一列在容器的 12 单位中占 4」。把同一列放到半宽行内,无需改动比例自动保持。",[14,508,510],{"id":509},"before-after-1-hello-world","Before / After 1: hello world",[19,512,513],{},"最短可能的差异。看右侧少了什么。",[19,515,516],{},[22,517,518],{},"Before — signintech/gopdf:",[520,521,526],"pre",{"className":522,"code":523,"language":524,"meta":525,"style":525},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","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","go","",[31,527,528,541,548,558,570,575,585,591,596,612,634,678,690,695,749,767,773,818,833,838,843,860,877,912,927,932,937,972,987,992],{"__ignoreMap":525},[529,530,533,537],"span",{"class":531,"line":532},"line",1,[529,534,536],{"class":535},"sMK4o","package",[529,538,540],{"class":539},"sBMFI"," main\n",[529,542,544],{"class":531,"line":543},2,[529,545,547],{"emptyLinePlaceholder":546},true,"\n",[529,549,551,555],{"class":531,"line":550},3,[529,552,554],{"class":553},"s7zQu","import",[529,556,557],{"class":535}," (\n",[529,559,561,564,567],{"class":531,"line":560},4,[529,562,563],{"class":535},"    \"",[529,565,566],{"class":539},"log",[529,568,569],{"class":535},"\"\n",[529,571,573],{"class":531,"line":572},5,[529,574,547],{"emptyLinePlaceholder":546},[529,576,578,580,583],{"class":531,"line":577},6,[529,579,563],{"class":535},[529,581,582],{"class":539},"github.com/signintech/gopdf",[529,584,569],{"class":535},[529,586,588],{"class":531,"line":587},7,[529,589,590],{"class":535},")\n",[529,592,594],{"class":531,"line":593},8,[529,595,547],{"emptyLinePlaceholder":546},[529,597,599,602,606,609],{"class":531,"line":598},9,[529,600,601],{"class":535},"func",[529,603,605],{"class":604},"s2Zo4"," main",[529,607,608],{"class":535},"()",[529,610,611],{"class":535}," {\n",[529,613,615,619,622,625,628,631],{"class":531,"line":614},10,[529,616,618],{"class":617},"sTEyZ","    pdf ",[529,620,621],{"class":535},":=",[529,623,624],{"class":539}," gopdf",[529,626,627],{"class":535},".",[529,629,630],{"class":539},"GoPdf",[529,632,633],{"class":535},"{}\n",[529,635,637,640,642,645,648,651,653,656,659,662,665,668,670,672,675],{"class":531,"line":636},11,[529,638,639],{"class":617},"    pdf",[529,641,627],{"class":535},[529,643,644],{"class":604},"Start",[529,646,647],{"class":535},"(",[529,649,650],{"class":539},"gopdf",[529,652,627],{"class":535},[529,654,655],{"class":539},"Config",[529,657,658],{"class":535},"{",[529,660,661],{"class":617},"PageSize",[529,663,664],{"class":535},":",[529,666,667],{"class":535}," *",[529,669,650],{"class":617},[529,671,627],{"class":535},[529,673,674],{"class":617},"PageSizeA4",[529,676,677],{"class":535},"})\n",[529,679,681,683,685,687],{"class":531,"line":680},12,[529,682,639],{"class":617},[529,684,627],{"class":535},[529,686,57],{"class":604},[529,688,689],{"class":535},"()\n",[529,691,693],{"class":531,"line":692},13,[529,694,547],{"emptyLinePlaceholder":546},[529,696,698,701,704,706,709,711,714,716,719,723,725,728,731,734,736,739,741,744,747],{"class":531,"line":697},14,[529,699,700],{"class":553},"    if",[529,702,703],{"class":617}," err ",[529,705,621],{"class":535},[529,707,708],{"class":617}," pdf",[529,710,627],{"class":535},[529,712,713],{"class":604},"AddTTFFont",[529,715,647],{"class":535},[529,717,718],{"class":535},"\"",[529,720,722],{"class":721},"sfazB","helvetica",[529,724,718],{"class":535},[529,726,727],{"class":535},",",[529,729,730],{"class":535}," \"",[529,732,733],{"class":721},"fonts/Helvetica.ttf",[529,735,718],{"class":535},[529,737,738],{"class":535},");",[529,740,703],{"class":617},[529,742,743],{"class":535},"!=",[529,745,746],{"class":535}," nil",[529,748,611],{"class":535},[529,750,752,755,757,760,762,765],{"class":531,"line":751},15,[529,753,754],{"class":617},"        log",[529,756,627],{"class":535},[529,758,759],{"class":604},"Fatal",[529,761,647],{"class":535},[529,763,764],{"class":617},"err",[529,766,590],{"class":535},[529,768,770],{"class":531,"line":769},16,[529,771,772],{"class":535},"    }\n",[529,774,776,778,780,782,784,786,789,791,793,795,797,799,802,804,808,810,812,814,816],{"class":531,"line":775},17,[529,777,700],{"class":553},[529,779,703],{"class":617},[529,781,621],{"class":535},[529,783,708],{"class":617},[529,785,627],{"class":535},[529,787,788],{"class":604},"SetFont",[529,790,647],{"class":535},[529,792,718],{"class":535},[529,794,722],{"class":721},[529,796,718],{"class":535},[529,798,727],{"class":535},[529,800,801],{"class":535}," \"\"",[529,803,727],{"class":535},[529,805,807],{"class":806},"sbssI"," 24",[529,809,738],{"class":535},[529,811,703],{"class":617},[529,813,743],{"class":535},[529,815,746],{"class":535},[529,817,611],{"class":535},[529,819,821,823,825,827,829,831],{"class":531,"line":820},18,[529,822,754],{"class":617},[529,824,627],{"class":535},[529,826,759],{"class":604},[529,828,647],{"class":535},[529,830,764],{"class":617},[529,832,590],{"class":535},[529,834,836],{"class":531,"line":835},19,[529,837,772],{"class":535},[529,839,841],{"class":531,"line":840},20,[529,842,547],{"emptyLinePlaceholder":546},[529,844,846,848,850,853,855,858],{"class":531,"line":845},21,[529,847,639],{"class":617},[529,849,627],{"class":535},[529,851,852],{"class":604},"SetX",[529,854,647],{"class":535},[529,856,857],{"class":806},"40",[529,859,590],{"class":535},[529,861,863,865,867,870,872,875],{"class":531,"line":862},22,[529,864,639],{"class":617},[529,866,627],{"class":535},[529,868,869],{"class":604},"SetY",[529,871,647],{"class":535},[529,873,874],{"class":806},"80",[529,876,590],{"class":535},[529,878,880,882,884,886,888,890,892,895,897,900,902,904,906,908,910],{"class":531,"line":879},23,[529,881,700],{"class":553},[529,883,703],{"class":617},[529,885,621],{"class":535},[529,887,708],{"class":617},[529,889,627],{"class":535},[529,891,37],{"class":604},[529,893,894],{"class":535},"(nil,",[529,896,730],{"class":535},[529,898,899],{"class":721},"Hello, World!",[529,901,718],{"class":535},[529,903,738],{"class":535},[529,905,703],{"class":617},[529,907,743],{"class":535},[529,909,746],{"class":535},[529,911,611],{"class":535},[529,913,915,917,919,921,923,925],{"class":531,"line":914},24,[529,916,754],{"class":617},[529,918,627],{"class":535},[529,920,759],{"class":604},[529,922,647],{"class":535},[529,924,764],{"class":617},[529,926,590],{"class":535},[529,928,930],{"class":531,"line":929},25,[529,931,772],{"class":535},[529,933,935],{"class":531,"line":934},26,[529,936,547],{"emptyLinePlaceholder":546},[529,938,940,942,944,946,948,950,953,955,957,960,962,964,966,968,970],{"class":531,"line":939},27,[529,941,700],{"class":553},[529,943,703],{"class":617},[529,945,621],{"class":535},[529,947,708],{"class":617},[529,949,627],{"class":535},[529,951,952],{"class":604},"WritePdf",[529,954,647],{"class":535},[529,956,718],{"class":535},[529,958,959],{"class":721},"hello.pdf",[529,961,718],{"class":535},[529,963,738],{"class":535},[529,965,703],{"class":617},[529,967,743],{"class":535},[529,969,746],{"class":535},[529,971,611],{"class":535},[529,973,975,977,979,981,983,985],{"class":531,"line":974},28,[529,976,754],{"class":617},[529,978,627],{"class":535},[529,980,759],{"class":604},[529,982,647],{"class":535},[529,984,764],{"class":617},[529,986,590],{"class":535},[529,988,990],{"class":531,"line":989},29,[529,991,772],{"class":535},[529,993,995],{"class":531,"line":994},30,[529,996,997],{"class":535},"}\n",[19,999,1000],{},[22,1001,1002],{},"After — gpdf:",[520,1004,1006],{"className":522,"code":1005,"language":524,"meta":525,"style":525},"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",[31,1007,1008,1014,1018,1024,1032,1041,1045,1054,1063,1072,1076,1080,1090,1108,1131,1166,1171,1175,1191,1222,1258,1304,1309,1314,1318,1338,1350,1364,1368,1412,1426,1431],{"__ignoreMap":525},[529,1009,1010,1012],{"class":531,"line":532},[529,1011,536],{"class":535},[529,1013,540],{"class":539},[529,1015,1016],{"class":531,"line":543},[529,1017,547],{"emptyLinePlaceholder":546},[529,1019,1020,1022],{"class":531,"line":550},[529,1021,554],{"class":553},[529,1023,557],{"class":535},[529,1025,1026,1028,1030],{"class":531,"line":560},[529,1027,563],{"class":535},[529,1029,566],{"class":539},[529,1031,569],{"class":535},[529,1033,1034,1036,1039],{"class":531,"line":572},[529,1035,563],{"class":535},[529,1037,1038],{"class":539},"os",[529,1040,569],{"class":535},[529,1042,1043],{"class":531,"line":577},[529,1044,547],{"emptyLinePlaceholder":546},[529,1046,1047,1049,1052],{"class":531,"line":587},[529,1048,563],{"class":535},[529,1050,1051],{"class":539},"github.com/gpdf-dev/gpdf",[529,1053,569],{"class":535},[529,1055,1056,1058,1061],{"class":531,"line":593},[529,1057,563],{"class":535},[529,1059,1060],{"class":539},"github.com/gpdf-dev/gpdf/document",[529,1062,569],{"class":535},[529,1064,1065,1067,1070],{"class":531,"line":598},[529,1066,563],{"class":535},[529,1068,1069],{"class":539},"github.com/gpdf-dev/gpdf/template",[529,1071,569],{"class":535},[529,1073,1074],{"class":531,"line":614},[529,1075,590],{"class":535},[529,1077,1078],{"class":531,"line":636},[529,1079,547],{"emptyLinePlaceholder":546},[529,1081,1082,1084,1086,1088],{"class":531,"line":680},[529,1083,601],{"class":535},[529,1085,605],{"class":604},[529,1087,608],{"class":535},[529,1089,611],{"class":535},[529,1091,1092,1095,1097,1100,1102,1105],{"class":531,"line":692},[529,1093,1094],{"class":617},"    doc ",[529,1096,621],{"class":535},[529,1098,1099],{"class":617}," gpdf",[529,1101,627],{"class":535},[529,1103,1104],{"class":604},"NewDocument",[529,1106,1107],{"class":535},"(\n",[529,1109,1110,1113,1115,1118,1120,1123,1125,1128],{"class":531,"line":697},[529,1111,1112],{"class":617},"        gpdf",[529,1114,627],{"class":535},[529,1116,1117],{"class":604},"WithPageSize",[529,1119,647],{"class":535},[529,1121,1122],{"class":617},"document",[529,1124,627],{"class":535},[529,1126,1127],{"class":617},"A4",[529,1129,1130],{"class":535},"),\n",[529,1132,1133,1135,1137,1140,1142,1144,1146,1149,1151,1153,1155,1158,1160,1163],{"class":531,"line":751},[529,1134,1112],{"class":617},[529,1136,627],{"class":535},[529,1138,1139],{"class":604},"WithMargins",[529,1141,647],{"class":535},[529,1143,1122],{"class":617},[529,1145,627],{"class":535},[529,1147,1148],{"class":604},"UniformEdges",[529,1150,647],{"class":535},[529,1152,1122],{"class":617},[529,1154,627],{"class":535},[529,1156,1157],{"class":604},"Mm",[529,1159,647],{"class":535},[529,1161,1162],{"class":806},"20",[529,1164,1165],{"class":535},"))),\n",[529,1167,1168],{"class":531,"line":769},[529,1169,1170],{"class":535},"    )\n",[529,1172,1173],{"class":531,"line":775},[529,1174,547],{"emptyLinePlaceholder":546},[529,1176,1177,1180,1182,1185,1187,1189],{"class":531,"line":820},[529,1178,1179],{"class":617},"    page ",[529,1181,621],{"class":535},[529,1183,1184],{"class":617}," doc",[529,1186,627],{"class":535},[529,1188,57],{"class":604},[529,1190,689],{"class":535},[529,1192,1193,1196,1198,1201,1204,1208,1210,1213,1215,1218,1220],{"class":531,"line":835},[529,1194,1195],{"class":617},"    page",[529,1197,627],{"class":535},[529,1199,1200],{"class":604},"AutoRow",[529,1202,1203],{"class":535},"(func(",[529,1205,1207],{"class":1206},"sHdIc","r",[529,1209,667],{"class":535},[529,1211,1212],{"class":539},"template",[529,1214,627],{"class":535},[529,1216,1217],{"class":539},"RowBuilder",[529,1219,275],{"class":535},[529,1221,611],{"class":535},[529,1223,1224,1227,1229,1232,1234,1237,1239,1242,1245,1247,1249,1251,1254,1256],{"class":531,"line":840},[529,1225,1226],{"class":617},"        r",[529,1228,627],{"class":535},[529,1230,1231],{"class":604},"Col",[529,1233,647],{"class":535},[529,1235,1236],{"class":806},"12",[529,1238,727],{"class":535},[529,1240,1241],{"class":535}," func(",[529,1243,1244],{"class":1206},"c",[529,1246,667],{"class":535},[529,1248,1212],{"class":539},[529,1250,627],{"class":535},[529,1252,1253],{"class":539},"ColBuilder",[529,1255,275],{"class":535},[529,1257,611],{"class":535},[529,1259,1260,1263,1265,1268,1270,1272,1274,1276,1278,1281,1283,1286,1288,1291,1294,1296,1298,1301],{"class":531,"line":845},[529,1261,1262],{"class":617},"            c",[529,1264,627],{"class":535},[529,1266,1267],{"class":604},"Text",[529,1269,647],{"class":535},[529,1271,718],{"class":535},[529,1273,899],{"class":721},[529,1275,718],{"class":535},[529,1277,727],{"class":535},[529,1279,1280],{"class":617}," template",[529,1282,627],{"class":535},[529,1284,1285],{"class":604},"FontSize",[529,1287,647],{"class":535},[529,1289,1290],{"class":806},"24",[529,1292,1293],{"class":535},"),",[529,1295,1280],{"class":617},[529,1297,627],{"class":535},[529,1299,1300],{"class":604},"Bold",[529,1302,1303],{"class":535},"())\n",[529,1305,1306],{"class":531,"line":862},[529,1307,1308],{"class":535},"        })\n",[529,1310,1311],{"class":531,"line":879},[529,1312,1313],{"class":535},"    })\n",[529,1315,1316],{"class":531,"line":914},[529,1317,547],{"emptyLinePlaceholder":546},[529,1319,1320,1323,1325,1327,1329,1331,1333,1336],{"class":531,"line":929},[529,1321,1322],{"class":617},"    data",[529,1324,727],{"class":535},[529,1326,703],{"class":617},[529,1328,621],{"class":535},[529,1330,1184],{"class":617},[529,1332,627],{"class":535},[529,1334,1335],{"class":604},"Generate",[529,1337,689],{"class":535},[529,1339,1340,1342,1344,1346,1348],{"class":531,"line":934},[529,1341,700],{"class":553},[529,1343,703],{"class":617},[529,1345,743],{"class":535},[529,1347,746],{"class":535},[529,1349,611],{"class":535},[529,1351,1352,1354,1356,1358,1360,1362],{"class":531,"line":939},[529,1353,754],{"class":617},[529,1355,627],{"class":535},[529,1357,759],{"class":604},[529,1359,647],{"class":535},[529,1361,764],{"class":617},[529,1363,590],{"class":535},[529,1365,1366],{"class":531,"line":974},[529,1367,772],{"class":535},[529,1369,1370,1372,1374,1376,1379,1381,1384,1386,1388,1390,1392,1394,1397,1399,1402,1404,1406,1408,1410],{"class":531,"line":989},[529,1371,700],{"class":553},[529,1373,703],{"class":617},[529,1375,621],{"class":535},[529,1377,1378],{"class":617}," os",[529,1380,627],{"class":535},[529,1382,1383],{"class":604},"WriteFile",[529,1385,647],{"class":535},[529,1387,718],{"class":535},[529,1389,959],{"class":721},[529,1391,718],{"class":535},[529,1393,727],{"class":535},[529,1395,1396],{"class":617}," data",[529,1398,727],{"class":535},[529,1400,1401],{"class":806}," 0o644",[529,1403,738],{"class":535},[529,1405,703],{"class":617},[529,1407,743],{"class":535},[529,1409,746],{"class":535},[529,1411,611],{"class":535},[529,1413,1414,1416,1418,1420,1422,1424],{"class":531,"line":994},[529,1415,754],{"class":617},[529,1417,627],{"class":535},[529,1419,759],{"class":604},[529,1421,647],{"class":535},[529,1423,764],{"class":617},[529,1425,590],{"class":535},[529,1427,1429],{"class":531,"line":1428},31,[529,1430,772],{"class":535},[529,1432,1434],{"class":531,"line":1433},32,[529,1435,997],{"class":535},[19,1437,1438,1439,1442],{},"少了两样。Helvetica 是标准 14 字体之一,gpdf 自带,运行时无需 TTF 文件。",[31,1440,1441],{},"SetX(40); SetY(80)"," 没了 — 行自动落在页面边距内。多了一个跨满 12 单位的单列行。这套脚手架对 \"Hello, World!\" 显得重,但同样的脚手架支撑 100 页报表,这正是要点。",[14,1444,1446],{"id":1445},"before-after-2-4-列表头行","Before / After 2: 4 列表头行",[19,1448,1449],{},"这是坐标计算最显眼的地方。要一条横贯页面的 4 等宽单元格表头: 页宽减边距,除以 4。在 gopdf 你写这个除法。在 gpdf 你把 12 单位分成 4 份。",[19,1451,1452],{},[22,1453,518],{},[520,1455,1457],{"className":522,"code":1456,"language":524,"meta":525,"style":525},"const (\n    pageWidth   = 595.28 // A4 (pt)\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{\"描述\", \"数量\", \"单价\", \"金额\"}\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",[31,1458,1459,1466,1481,1491,1500,1510,1520,1524,1528,1552,1571,1575,1604,1630,1655,1659,1711,1735,1763,1802,1806,1834,1863,1877,1881,1885,1889],{"__ignoreMap":525},[529,1460,1461,1464],{"class":531,"line":532},[529,1462,1463],{"class":535},"const",[529,1465,557],{"class":535},[529,1467,1468,1471,1474,1477],{"class":531,"line":543},[529,1469,1470],{"class":617},"    pageWidth   ",[529,1472,1473],{"class":535},"=",[529,1475,1476],{"class":806}," 595.28",[529,1478,1480],{"class":1479},"sHwdD"," // A4 (pt)\n",[529,1482,1483,1486,1488],{"class":531,"line":550},[529,1484,1485],{"class":617},"    leftMargin  ",[529,1487,1473],{"class":535},[529,1489,1490],{"class":806}," 40.0\n",[529,1492,1493,1496,1498],{"class":531,"line":560},[529,1494,1495],{"class":617},"    rightMargin ",[529,1497,1473],{"class":535},[529,1499,1490],{"class":806},[529,1501,1502,1505,1507],{"class":531,"line":572},[529,1503,1504],{"class":617},"    rowY        ",[529,1506,1473],{"class":535},[529,1508,1509],{"class":806}," 100.0\n",[529,1511,1512,1515,1517],{"class":531,"line":577},[529,1513,1514],{"class":617},"    rowH        ",[529,1516,1473],{"class":535},[529,1518,1519],{"class":806}," 24.0\n",[529,1521,1522],{"class":531,"line":587},[529,1523,590],{"class":535},[529,1525,1526],{"class":531,"line":593},[529,1527,547],{"emptyLinePlaceholder":546},[529,1529,1530,1533,1535,1538,1541,1544,1546,1549],{"class":531,"line":598},[529,1531,1532],{"class":617},"contentWidth ",[529,1534,621],{"class":535},[529,1536,1537],{"class":617}," pageWidth ",[529,1539,1540],{"class":535},"-",[529,1542,1543],{"class":617}," leftMargin ",[529,1545,1540],{"class":535},[529,1547,1548],{"class":617}," rightMargin ",[529,1550,1551],{"class":1479},"// 515.28\n",[529,1553,1554,1557,1559,1562,1565,1568],{"class":531,"line":614},[529,1555,1556],{"class":617},"colW ",[529,1558,621],{"class":535},[529,1560,1561],{"class":617}," contentWidth ",[529,1563,1564],{"class":535},"/",[529,1566,1567],{"class":806}," 4",[529,1569,1570],{"class":1479},"                              // 128.82\n",[529,1572,1573],{"class":531,"line":636},[529,1574,547],{"emptyLinePlaceholder":546},[529,1576,1577,1580,1582,1584,1586,1588,1591,1593,1595,1597,1599,1602],{"class":531,"line":680},[529,1578,1579],{"class":617},"pdf",[529,1581,627],{"class":535},[529,1583,788],{"class":604},[529,1585,647],{"class":535},[529,1587,718],{"class":535},[529,1589,1590],{"class":721},"helvetica-bold",[529,1592,718],{"class":535},[529,1594,727],{"class":535},[529,1596,801],{"class":535},[529,1598,727],{"class":535},[529,1600,1601],{"class":806}," 11",[529,1603,590],{"class":535},[529,1605,1606,1608,1610,1613,1615,1618,1620,1623,1625,1628],{"class":531,"line":692},[529,1607,1579],{"class":617},[529,1609,627],{"class":535},[529,1611,1612],{"class":604},"SetFillColor",[529,1614,647],{"class":535},[529,1616,1617],{"class":806},"26",[529,1619,727],{"class":535},[529,1621,1622],{"class":806}," 35",[529,1624,727],{"class":535},[529,1626,1627],{"class":806}," 126",[529,1629,590],{"class":535},[529,1631,1632,1634,1636,1639,1641,1644,1646,1649,1651,1653],{"class":531,"line":697},[529,1633,1579],{"class":617},[529,1635,627],{"class":535},[529,1637,1638],{"class":604},"SetTextColor",[529,1640,647],{"class":535},[529,1642,1643],{"class":806},"255",[529,1645,727],{"class":535},[529,1647,1648],{"class":806}," 255",[529,1650,727],{"class":535},[529,1652,1648],{"class":806},[529,1654,590],{"class":535},[529,1656,1657],{"class":531,"line":751},[529,1658,547],{"emptyLinePlaceholder":546},[529,1660,1661,1664,1666,1669,1673,1675,1677,1680,1682,1684,1686,1689,1691,1693,1695,1698,1700,1702,1704,1707,1709],{"class":531,"line":769},[529,1662,1663],{"class":617},"headers ",[529,1665,621],{"class":535},[529,1667,1668],{"class":535}," []",[529,1670,1672],{"class":1671},"spNyl","string",[529,1674,658],{"class":535},[529,1676,718],{"class":535},[529,1678,1679],{"class":721},"描述",[529,1681,718],{"class":535},[529,1683,727],{"class":535},[529,1685,730],{"class":535},[529,1687,1688],{"class":721},"数量",[529,1690,718],{"class":535},[529,1692,727],{"class":535},[529,1694,730],{"class":535},[529,1696,1697],{"class":721},"单价",[529,1699,718],{"class":535},[529,1701,727],{"class":535},[529,1703,730],{"class":535},[529,1705,1706],{"class":721},"金额",[529,1708,718],{"class":535},[529,1710,997],{"class":535},[529,1712,1713,1716,1719,1721,1724,1726,1729,1732],{"class":531,"line":775},[529,1714,1715],{"class":553},"for",[529,1717,1718],{"class":617}," i",[529,1720,727],{"class":535},[529,1722,1723],{"class":617}," h ",[529,1725,621],{"class":535},[529,1727,1728],{"class":553}," range",[529,1730,1731],{"class":617}," headers ",[529,1733,1734],{"class":535},"{\n",[529,1736,1737,1740,1742,1744,1747,1750,1753,1756,1758,1761],{"class":531,"line":820},[529,1738,1739],{"class":617},"    x ",[529,1741,621],{"class":535},[529,1743,1543],{"class":617},[529,1745,1746],{"class":535},"+",[529,1748,1749],{"class":617}," colW",[529,1751,1752],{"class":535},"*",[529,1754,1755],{"class":1671},"float64",[529,1757,647],{"class":535},[529,1759,1760],{"class":617},"i",[529,1762,590],{"class":535},[529,1764,1765,1767,1769,1772,1774,1777,1779,1782,1784,1786,1788,1791,1793,1795,1798,1800],{"class":531,"line":835},[529,1766,639],{"class":617},[529,1768,627],{"class":535},[529,1770,1771],{"class":604},"RectFromUpperLeftWithStyle",[529,1773,647],{"class":535},[529,1775,1776],{"class":617},"x",[529,1778,727],{"class":535},[529,1780,1781],{"class":617}," rowY",[529,1783,727],{"class":535},[529,1785,1749],{"class":617},[529,1787,727],{"class":535},[529,1789,1790],{"class":617}," rowH",[529,1792,727],{"class":535},[529,1794,730],{"class":535},[529,1796,1797],{"class":721},"F",[529,1799,718],{"class":535},[529,1801,590],{"class":535},[529,1803,1804],{"class":531,"line":840},[529,1805,547],{"emptyLinePlaceholder":546},[529,1807,1808,1810,1812,1814,1816,1818,1820,1823,1825,1827,1829,1832],{"class":531,"line":845},[529,1809,639],{"class":617},[529,1811,627],{"class":535},[529,1813,33],{"class":604},[529,1815,647],{"class":535},[529,1817,1776],{"class":617},[529,1819,1746],{"class":535},[529,1821,1822],{"class":806},"6",[529,1824,727],{"class":535},[529,1826,1781],{"class":617},[529,1828,1746],{"class":535},[529,1830,1831],{"class":806},"7",[529,1833,590],{"class":535},[529,1835,1836,1838,1840,1842,1844,1846,1848,1850,1853,1855,1857,1859,1861],{"class":531,"line":862},[529,1837,700],{"class":553},[529,1839,703],{"class":617},[529,1841,621],{"class":535},[529,1843,708],{"class":617},[529,1845,627],{"class":535},[529,1847,37],{"class":604},[529,1849,894],{"class":535},[529,1851,1852],{"class":617}," h",[529,1854,738],{"class":535},[529,1856,703],{"class":617},[529,1858,743],{"class":535},[529,1860,746],{"class":535},[529,1862,611],{"class":535},[529,1864,1865,1867,1869,1871,1873,1875],{"class":531,"line":879},[529,1866,754],{"class":617},[529,1868,627],{"class":535},[529,1870,759],{"class":604},[529,1872,647],{"class":535},[529,1874,764],{"class":617},[529,1876,590],{"class":535},[529,1878,1879],{"class":531,"line":914},[529,1880,772],{"class":535},[529,1882,1883],{"class":531,"line":929},[529,1884,997],{"class":535},[529,1886,1887],{"class":531,"line":934},[529,1888,547],{"emptyLinePlaceholder":546},[529,1890,1891,1893,1895,1897,1899,1902,1904,1907,1909,1911],{"class":531,"line":939},[529,1892,1579],{"class":617},[529,1894,627],{"class":535},[529,1896,1638],{"class":604},[529,1898,647],{"class":535},[529,1900,1901],{"class":806},"0",[529,1903,727],{"class":535},[529,1905,1906],{"class":806}," 0",[529,1908,727],{"class":535},[529,1910,1906],{"class":806},[529,1912,590],{"class":535},[19,1914,1915,1916,1919,1920,1922],{},"四个常数,一次宽度减法,一次除法,带 ",[31,1917,1918],{},"colW*float64(i)"," 的循环 — 那个 float 转换只因为 Go 的 ",[31,1921,1752],{}," 不会自动把 int 提升为 float64。gpdf 版本里这些都不存在。",[19,1924,1925],{},[22,1926,1002],{},[520,1928,1930],{"className":522,"code":1929,"language":524,"meta":525,"style":525},"page.AutoRow(func(r *template.RowBuilder) {\n    headers := []string{\"描述\", \"数量\", \"单价\", \"金额\"}\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",[31,1931,1932,1957,2002,2022,2053,2064,2091,2128,2133,2149,2173,2193,2197,2201,2205],{"__ignoreMap":525},[529,1933,1934,1937,1939,1941,1943,1945,1947,1949,1951,1953,1955],{"class":531,"line":532},[529,1935,1936],{"class":617},"page",[529,1938,627],{"class":535},[529,1940,1200],{"class":604},[529,1942,1203],{"class":535},[529,1944,1207],{"class":1206},[529,1946,667],{"class":535},[529,1948,1212],{"class":539},[529,1950,627],{"class":535},[529,1952,1217],{"class":539},[529,1954,275],{"class":535},[529,1956,611],{"class":535},[529,1958,1959,1962,1964,1966,1968,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990,1992,1994,1996,1998,2000],{"class":531,"line":543},[529,1960,1961],{"class":617},"    headers ",[529,1963,621],{"class":535},[529,1965,1668],{"class":535},[529,1967,1672],{"class":1671},[529,1969,658],{"class":535},[529,1971,718],{"class":535},[529,1973,1679],{"class":721},[529,1975,718],{"class":535},[529,1977,727],{"class":535},[529,1979,730],{"class":535},[529,1981,1688],{"class":721},[529,1983,718],{"class":535},[529,1985,727],{"class":535},[529,1987,730],{"class":535},[529,1989,1697],{"class":721},[529,1991,718],{"class":535},[529,1993,727],{"class":535},[529,1995,730],{"class":535},[529,1997,1706],{"class":721},[529,1999,718],{"class":535},[529,2001,997],{"class":535},[529,2003,2004,2007,2010,2012,2014,2016,2018,2020],{"class":531,"line":550},[529,2005,2006],{"class":553},"    for",[529,2008,2009],{"class":617}," _",[529,2011,727],{"class":535},[529,2013,1723],{"class":617},[529,2015,621],{"class":535},[529,2017,1728],{"class":553},[529,2019,1731],{"class":617},[529,2021,1734],{"class":535},[529,2023,2024,2026,2028,2030,2032,2035,2037,2039,2041,2043,2045,2047,2049,2051],{"class":531,"line":560},[529,2025,1226],{"class":617},[529,2027,627],{"class":535},[529,2029,1231],{"class":604},[529,2031,647],{"class":535},[529,2033,2034],{"class":806},"3",[529,2036,727],{"class":535},[529,2038,1241],{"class":535},[529,2040,1244],{"class":1206},[529,2042,667],{"class":535},[529,2044,1212],{"class":539},[529,2046,627],{"class":535},[529,2048,1253],{"class":539},[529,2050,275],{"class":535},[529,2052,611],{"class":535},[529,2054,2055,2057,2059,2062],{"class":531,"line":572},[529,2056,1262],{"class":617},[529,2058,627],{"class":535},[529,2060,2061],{"class":604},"Box",[529,2063,1107],{"class":535},[529,2065,2066,2069,2071,2074,2076,2078,2080,2083,2085,2088],{"class":531,"line":577},[529,2067,2068],{"class":617},"                template",[529,2070,627],{"class":535},[529,2072,2073],{"class":604},"BgColor",[529,2075,647],{"class":535},[529,2077,1579],{"class":617},[529,2079,627],{"class":535},[529,2081,2082],{"class":604},"RGBHex",[529,2084,647],{"class":535},[529,2086,2087],{"class":806},"0x1A237E",[529,2089,2090],{"class":535},")),\n",[529,2092,2093,2095,2097,2100,2102,2104,2106,2108,2110,2113,2115,2118,2120,2122,2124,2126],{"class":531,"line":587},[529,2094,2068],{"class":617},[529,2096,627],{"class":535},[529,2098,2099],{"class":604},"Padding",[529,2101,647],{"class":535},[529,2103,1122],{"class":617},[529,2105,627],{"class":535},[529,2107,1157],{"class":604},[529,2109,647],{"class":535},[529,2111,2112],{"class":806},"2",[529,2114,1293],{"class":535},[529,2116,2117],{"class":617}," document",[529,2119,627],{"class":535},[529,2121,1157],{"class":604},[529,2123,647],{"class":535},[529,2125,2034],{"class":806},[529,2127,2090],{"class":535},[529,2129,2130],{"class":531,"line":593},[529,2131,2132],{"class":535},"            )\n",[529,2134,2135,2137,2139,2141,2143,2146],{"class":531,"line":598},[529,2136,1262],{"class":617},[529,2138,627],{"class":535},[529,2140,1267],{"class":604},[529,2142,647],{"class":535},[529,2144,2145],{"class":617},"h",[529,2147,2148],{"class":535},",\n",[529,2150,2151,2153,2155,2157,2160,2162,2164,2166,2168,2171],{"class":531,"line":614},[529,2152,2068],{"class":617},[529,2154,627],{"class":535},[529,2156,1300],{"class":604},[529,2158,2159],{"class":535},"(),",[529,2161,1280],{"class":617},[529,2163,627],{"class":535},[529,2165,1285],{"class":604},[529,2167,647],{"class":535},[529,2169,2170],{"class":806},"11",[529,2172,1130],{"class":535},[529,2174,2175,2177,2179,2182,2184,2186,2188,2191],{"class":531,"line":636},[529,2176,2068],{"class":617},[529,2178,627],{"class":535},[529,2180,2181],{"class":604},"TextColor",[529,2183,647],{"class":535},[529,2185,1579],{"class":617},[529,2187,627],{"class":535},[529,2189,2190],{"class":617},"White",[529,2192,1130],{"class":535},[529,2194,2195],{"class":531,"line":680},[529,2196,2132],{"class":535},[529,2198,2199],{"class":531,"line":692},[529,2200,1308],{"class":535},[529,2202,2203],{"class":531,"line":697},[529,2204,772],{"class":535},[529,2206,2207],{"class":531,"line":751},[529,2208,677],{"class":535},[19,2210,2211,2213,2214,2217,2218,2221,2222,2225],{},[31,2212,123],{}," 四次合计 12。宽度由网格处理。从 A4 换到 Letter,缩小边距,这段代码完全不依赖 ",[31,2215,2216],{},"pageWidth",",排布依然正确。要让第一列宽度是其他三列的两倍,把那一列改成 ",[31,2219,2220],{},"r.Col(6, ...)",",另一列改成 ",[31,2223,2224],{},"r.Col(2, ...)"," 即可。无需算术。",[14,2227,2229],{"id":2228},"before-after-3-跨页的发票表格","Before / After 3: 跨页的发票表格",[19,2231,2232,2233,2235],{},"重头戏。在 gopdf 中绘制跨多页表格基本是簿记: 追踪当前 y、画每一行、检查下一行是否容得下、容不下就调用 ",[31,2234,57],{}," 并重绘表头。状态机就在你的代码里。",[19,2237,2238],{},[22,2239,518],{},[520,2241,2243],{"className":522,"code":2242,"language":524,"meta":525,"style":525},"func drawInvoiceTable(pdf *gopdf.GoPdf, items [][4]string) error {\n    const (\n        pageH       = 841.89 // A4 高度\n        bottomLimit = pageH - 40\n        rowH        = 22.0\n        leftX       = 40.0\n    )\n    cols := []float64{260, 80, 80, 95}\n\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{\"描述\", \"数量\", \"单价\", \"金额\"} {\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\")\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",[31,2244,2245,2287,2294,2307,2322,2332,2341,2345,2377,2381,2402,2429,2451,2473,2483,2539,2582,2608,2637,2653,2658,2675,2680,2702,2728,2741,2745,2749,2766,2786,2806,2816,2832,2837,2842,2851,2871,2911,2938,2968,2977,2982,2997,3002,3012,3017,3026],{"__ignoreMap":525},[529,2246,2247,2249,2252,2254,2256,2258,2260,2262,2264,2266,2269,2272,2275,2278,2280,2282,2285],{"class":531,"line":532},[529,2248,601],{"class":535},[529,2250,2251],{"class":604}," drawInvoiceTable",[529,2253,647],{"class":535},[529,2255,1579],{"class":1206},[529,2257,667],{"class":535},[529,2259,650],{"class":539},[529,2261,627],{"class":535},[529,2263,630],{"class":539},[529,2265,727],{"class":535},[529,2267,2268],{"class":1206}," items",[529,2270,2271],{"class":535}," [][",[529,2273,2274],{"class":806},"4",[529,2276,2277],{"class":535},"]",[529,2279,1672],{"class":1671},[529,2281,275],{"class":535},[529,2283,2284],{"class":1671}," error",[529,2286,611],{"class":535},[529,2288,2289,2292],{"class":531,"line":543},[529,2290,2291],{"class":535},"    const",[529,2293,557],{"class":535},[529,2295,2296,2299,2301,2304],{"class":531,"line":550},[529,2297,2298],{"class":617},"        pageH       ",[529,2300,1473],{"class":535},[529,2302,2303],{"class":806}," 841.89",[529,2305,2306],{"class":1479}," // A4 高度\n",[529,2308,2309,2312,2314,2317,2319],{"class":531,"line":560},[529,2310,2311],{"class":617},"        bottomLimit ",[529,2313,1473],{"class":535},[529,2315,2316],{"class":617}," pageH ",[529,2318,1540],{"class":535},[529,2320,2321],{"class":806}," 40\n",[529,2323,2324,2327,2329],{"class":531,"line":572},[529,2325,2326],{"class":617},"        rowH        ",[529,2328,1473],{"class":535},[529,2330,2331],{"class":806}," 22.0\n",[529,2333,2334,2337,2339],{"class":531,"line":577},[529,2335,2336],{"class":617},"        leftX       ",[529,2338,1473],{"class":535},[529,2340,1490],{"class":806},[529,2342,2343],{"class":531,"line":587},[529,2344,1170],{"class":535},[529,2346,2347,2350,2352,2354,2356,2358,2361,2363,2366,2368,2370,2372,2375],{"class":531,"line":593},[529,2348,2349],{"class":617},"    cols ",[529,2351,621],{"class":535},[529,2353,1668],{"class":535},[529,2355,1755],{"class":1671},[529,2357,658],{"class":535},[529,2359,2360],{"class":806},"260",[529,2362,727],{"class":535},[529,2364,2365],{"class":806}," 80",[529,2367,727],{"class":535},[529,2369,2365],{"class":806},[529,2371,727],{"class":535},[529,2373,2374],{"class":806}," 95",[529,2376,997],{"class":535},[529,2378,2379],{"class":531,"line":598},[529,2380,547],{"emptyLinePlaceholder":546},[529,2382,2383,2386,2388,2390,2393,2396,2398,2400],{"class":531,"line":614},[529,2384,2385],{"class":617},"    drawHeader ",[529,2387,621],{"class":535},[529,2389,1241],{"class":535},[529,2391,2392],{"class":1206},"y",[529,2394,2395],{"class":1671}," float64",[529,2397,275],{"class":535},[529,2399,2395],{"class":1671},[529,2401,611],{"class":535},[529,2403,2404,2407,2409,2411,2413,2415,2417,2419,2421,2423,2425,2427],{"class":531,"line":636},[529,2405,2406],{"class":617},"        pdf",[529,2408,627],{"class":535},[529,2410,788],{"class":604},[529,2412,647],{"class":535},[529,2414,718],{"class":535},[529,2416,1590],{"class":721},[529,2418,718],{"class":535},[529,2420,727],{"class":535},[529,2422,801],{"class":535},[529,2424,727],{"class":535},[529,2426,1601],{"class":806},[529,2428,590],{"class":535},[529,2430,2431,2433,2435,2437,2439,2441,2443,2445,2447,2449],{"class":531,"line":680},[529,2432,2406],{"class":617},[529,2434,627],{"class":535},[529,2436,1612],{"class":604},[529,2438,647],{"class":535},[529,2440,1617],{"class":806},[529,2442,727],{"class":535},[529,2444,1622],{"class":806},[529,2446,727],{"class":535},[529,2448,1627],{"class":806},[529,2450,590],{"class":535},[529,2452,2453,2455,2457,2459,2461,2463,2465,2467,2469,2471],{"class":531,"line":692},[529,2454,2406],{"class":617},[529,2456,627],{"class":535},[529,2458,1638],{"class":604},[529,2460,647],{"class":535},[529,2462,1643],{"class":806},[529,2464,727],{"class":535},[529,2466,1648],{"class":806},[529,2468,727],{"class":535},[529,2470,1648],{"class":806},[529,2472,590],{"class":535},[529,2474,2475,2478,2480],{"class":531,"line":697},[529,2476,2477],{"class":617},"        x ",[529,2479,621],{"class":535},[529,2481,2482],{"class":617}," leftX\n",[529,2484,2485,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2537],{"class":531,"line":751},[529,2486,2487],{"class":553},"        for",[529,2489,1718],{"class":617},[529,2491,727],{"class":535},[529,2493,1723],{"class":617},[529,2495,621],{"class":535},[529,2497,1728],{"class":553},[529,2499,1668],{"class":535},[529,2501,1672],{"class":1671},[529,2503,658],{"class":535},[529,2505,718],{"class":535},[529,2507,1679],{"class":721},[529,2509,718],{"class":535},[529,2511,727],{"class":535},[529,2513,730],{"class":535},[529,2515,1688],{"class":721},[529,2517,718],{"class":535},[529,2519,727],{"class":535},[529,2521,730],{"class":535},[529,2523,1697],{"class":721},[529,2525,718],{"class":535},[529,2527,727],{"class":535},[529,2529,730],{"class":535},[529,2531,1706],{"class":721},[529,2533,718],{"class":535},[529,2535,2536],{"class":535},"}",[529,2538,611],{"class":535},[529,2540,2541,2544,2546,2548,2550,2552,2554,2557,2559,2562,2565,2567,2570,2572,2574,2576,2578,2580],{"class":531,"line":769},[529,2542,2543],{"class":617},"            pdf",[529,2545,627],{"class":535},[529,2547,1771],{"class":604},[529,2549,647],{"class":535},[529,2551,1776],{"class":617},[529,2553,727],{"class":535},[529,2555,2556],{"class":617}," y",[529,2558,727],{"class":535},[529,2560,2561],{"class":617}," cols",[529,2563,2564],{"class":535},"[",[529,2566,1760],{"class":617},[529,2568,2569],{"class":535},"],",[529,2571,1790],{"class":617},[529,2573,727],{"class":535},[529,2575,730],{"class":535},[529,2577,1797],{"class":721},[529,2579,718],{"class":535},[529,2581,590],{"class":535},[529,2583,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606],{"class":531,"line":775},[529,2585,2543],{"class":617},[529,2587,627],{"class":535},[529,2589,33],{"class":604},[529,2591,647],{"class":535},[529,2593,1776],{"class":617},[529,2595,1746],{"class":535},[529,2597,1822],{"class":806},[529,2599,727],{"class":535},[529,2601,2556],{"class":617},[529,2603,1746],{"class":535},[529,2605,1831],{"class":806},[529,2607,590],{"class":535},[529,2609,2610,2613,2615,2617,2619,2621,2623,2625,2627,2629,2631,2633,2635],{"class":531,"line":820},[529,2611,2612],{"class":553},"            if",[529,2614,703],{"class":617},[529,2616,621],{"class":535},[529,2618,708],{"class":617},[529,2620,627],{"class":535},[529,2622,37],{"class":604},[529,2624,894],{"class":535},[529,2626,1852],{"class":617},[529,2628,738],{"class":535},[529,2630,703],{"class":617},[529,2632,743],{"class":535},[529,2634,746],{"class":535},[529,2636,611],{"class":535},[529,2638,2639,2642,2644,2647,2649,2651],{"class":531,"line":835},[529,2640,2641],{"class":617},"                log",[529,2643,627],{"class":535},[529,2645,2646],{"class":604},"Println",[529,2648,647],{"class":535},[529,2650,764],{"class":617},[529,2652,590],{"class":535},[529,2654,2655],{"class":531,"line":840},[529,2656,2657],{"class":535},"            }\n",[529,2659,2660,2663,2666,2668,2670,2672],{"class":531,"line":845},[529,2661,2662],{"class":617},"            x ",[529,2664,2665],{"class":535},"+=",[529,2667,2561],{"class":617},[529,2669,2564],{"class":535},[529,2671,1760],{"class":617},[529,2673,2674],{"class":535},"]\n",[529,2676,2677],{"class":531,"line":862},[529,2678,2679],{"class":535},"        }\n",[529,2681,2682,2684,2686,2688,2690,2692,2694,2696,2698,2700],{"class":531,"line":879},[529,2683,2406],{"class":617},[529,2685,627],{"class":535},[529,2687,1638],{"class":604},[529,2689,647],{"class":535},[529,2691,1901],{"class":806},[529,2693,727],{"class":535},[529,2695,1906],{"class":806},[529,2697,727],{"class":535},[529,2699,1906],{"class":806},[529,2701,590],{"class":535},[529,2703,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726],{"class":531,"line":914},[529,2705,2406],{"class":617},[529,2707,627],{"class":535},[529,2709,788],{"class":604},[529,2711,647],{"class":535},[529,2713,718],{"class":535},[529,2715,722],{"class":721},[529,2717,718],{"class":535},[529,2719,727],{"class":535},[529,2721,801],{"class":535},[529,2723,727],{"class":535},[529,2725,1601],{"class":806},[529,2727,590],{"class":535},[529,2729,2730,2733,2736,2738],{"class":531,"line":929},[529,2731,2732],{"class":553},"        return",[529,2734,2735],{"class":617}," y ",[529,2737,1746],{"class":535},[529,2739,2740],{"class":617}," rowH\n",[529,2742,2743],{"class":531,"line":934},[529,2744,772],{"class":535},[529,2746,2747],{"class":531,"line":939},[529,2748,547],{"emptyLinePlaceholder":546},[529,2750,2751,2754,2756,2759,2761,2764],{"class":531,"line":974},[529,2752,2753],{"class":617},"    y ",[529,2755,621],{"class":535},[529,2757,2758],{"class":604}," drawHeader",[529,2760,647],{"class":535},[529,2762,2763],{"class":806},"100",[529,2765,590],{"class":535},[529,2767,2768,2770,2772,2774,2777,2779,2781,2784],{"class":531,"line":989},[529,2769,2006],{"class":553},[529,2771,2009],{"class":617},[529,2773,727],{"class":535},[529,2775,2776],{"class":617}," row ",[529,2778,621],{"class":535},[529,2780,1728],{"class":553},[529,2782,2783],{"class":617}," items ",[529,2785,1734],{"class":535},[529,2787,2788,2791,2793,2795,2798,2801,2804],{"class":531,"line":994},[529,2789,2790],{"class":553},"        if",[529,2792,2556],{"class":617},[529,2794,1746],{"class":535},[529,2796,2797],{"class":617},"rowH ",[529,2799,2800],{"class":535},">",[529,2802,2803],{"class":617}," bottomLimit ",[529,2805,1734],{"class":535},[529,2807,2808,2810,2812,2814],{"class":531,"line":1428},[529,2809,2543],{"class":617},[529,2811,627],{"class":535},[529,2813,57],{"class":604},[529,2815,689],{"class":535},[529,2817,2818,2821,2823,2825,2827,2830],{"class":531,"line":1433},[529,2819,2820],{"class":617},"            y ",[529,2822,1473],{"class":535},[529,2824,2758],{"class":604},[529,2826,647],{"class":535},[529,2828,2829],{"class":806},"60",[529,2831,590],{"class":535},[529,2833,2835],{"class":531,"line":2834},33,[529,2836,2679],{"class":535},[529,2838,2840],{"class":531,"line":2839},34,[529,2841,547],{"emptyLinePlaceholder":546},[529,2843,2845,2847,2849],{"class":531,"line":2844},35,[529,2846,2477],{"class":617},[529,2848,621],{"class":535},[529,2850,2482],{"class":617},[529,2852,2854,2856,2858,2860,2863,2865,2867,2869],{"class":531,"line":2853},36,[529,2855,2487],{"class":553},[529,2857,1718],{"class":617},[529,2859,727],{"class":535},[529,2861,2862],{"class":617}," cell ",[529,2864,621],{"class":535},[529,2866,1728],{"class":553},[529,2868,2776],{"class":617},[529,2870,1734],{"class":535},[529,2872,2874,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896,2898,2900,2902,2904,2907,2909],{"class":531,"line":2873},37,[529,2875,2543],{"class":617},[529,2877,627],{"class":535},[529,2879,1771],{"class":604},[529,2881,647],{"class":535},[529,2883,1776],{"class":617},[529,2885,727],{"class":535},[529,2887,2556],{"class":617},[529,2889,727],{"class":535},[529,2891,2561],{"class":617},[529,2893,2564],{"class":535},[529,2895,1760],{"class":617},[529,2897,2569],{"class":535},[529,2899,1790],{"class":617},[529,2901,727],{"class":535},[529,2903,730],{"class":535},[529,2905,2906],{"class":721},"D",[529,2908,718],{"class":535},[529,2910,590],{"class":535},[529,2912,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932,2934,2936],{"class":531,"line":2913},38,[529,2915,2543],{"class":617},[529,2917,627],{"class":535},[529,2919,33],{"class":604},[529,2921,647],{"class":535},[529,2923,1776],{"class":617},[529,2925,1746],{"class":535},[529,2927,1822],{"class":806},[529,2929,727],{"class":535},[529,2931,2556],{"class":617},[529,2933,1746],{"class":535},[529,2935,1831],{"class":806},[529,2937,590],{"class":535},[529,2939,2941,2943,2945,2947,2949,2951,2953,2955,2958,2960,2962,2964,2966],{"class":531,"line":2940},39,[529,2942,2612],{"class":553},[529,2944,703],{"class":617},[529,2946,621],{"class":535},[529,2948,708],{"class":617},[529,2950,627],{"class":535},[529,2952,37],{"class":604},[529,2954,894],{"class":535},[529,2956,2957],{"class":617}," cell",[529,2959,738],{"class":535},[529,2961,703],{"class":617},[529,2963,743],{"class":535},[529,2965,746],{"class":535},[529,2967,611],{"class":535},[529,2969,2971,2974],{"class":531,"line":2970},40,[529,2972,2973],{"class":553},"                return",[529,2975,2976],{"class":617}," err\n",[529,2978,2980],{"class":531,"line":2979},41,[529,2981,2657],{"class":535},[529,2983,2985,2987,2989,2991,2993,2995],{"class":531,"line":2984},42,[529,2986,2662],{"class":617},[529,2988,2665],{"class":535},[529,2990,2561],{"class":617},[529,2992,2564],{"class":535},[529,2994,1760],{"class":617},[529,2996,2674],{"class":535},[529,2998,3000],{"class":531,"line":2999},43,[529,3001,2679],{"class":535},[529,3003,3005,3008,3010],{"class":531,"line":3004},44,[529,3006,3007],{"class":617},"        y ",[529,3009,2665],{"class":535},[529,3011,2740],{"class":617},[529,3013,3015],{"class":531,"line":3014},45,[529,3016,772],{"class":535},[529,3018,3020,3023],{"class":531,"line":3019},46,[529,3021,3022],{"class":553},"    return",[529,3024,3025],{"class":535}," nil\n",[529,3027,3029],{"class":531,"line":3028},47,[529,3030,997],{"class":535},[19,3032,3033,3034,3036],{},"表格函数 30 行,只有 5 行关于数据。其余是布局: 硬编码高度、硬编码下界、分页后重绘表头的闭包、两个 ",[31,3035,1715],{}," 循环、每单元格两次光标推进。这是 gopdf 表格的中位数。",[19,3038,3039],{},[22,3040,1002],{},[520,3042,3044],{"className":522,"code":3043,"language":524,"meta":525,"style":525},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(12, func(c *template.ColBuilder) {\n        c.Table(\n            []string{\"描述\", \"数量\", \"单价\", \"金额\"},\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",[31,3045,3046,3070,3101,3113,3155,3165,3195,3206,3217,3235,3257,3262,3286,3291,3295],{"__ignoreMap":525},[529,3047,3048,3050,3052,3054,3056,3058,3060,3062,3064,3066,3068],{"class":531,"line":532},[529,3049,1936],{"class":617},[529,3051,627],{"class":535},[529,3053,1200],{"class":604},[529,3055,1203],{"class":535},[529,3057,1207],{"class":1206},[529,3059,667],{"class":535},[529,3061,1212],{"class":539},[529,3063,627],{"class":535},[529,3065,1217],{"class":539},[529,3067,275],{"class":535},[529,3069,611],{"class":535},[529,3071,3072,3075,3077,3079,3081,3083,3085,3087,3089,3091,3093,3095,3097,3099],{"class":531,"line":543},[529,3073,3074],{"class":617},"    r",[529,3076,627],{"class":535},[529,3078,1231],{"class":604},[529,3080,647],{"class":535},[529,3082,1236],{"class":806},[529,3084,727],{"class":535},[529,3086,1241],{"class":535},[529,3088,1244],{"class":1206},[529,3090,667],{"class":535},[529,3092,1212],{"class":539},[529,3094,627],{"class":535},[529,3096,1253],{"class":539},[529,3098,275],{"class":535},[529,3100,611],{"class":535},[529,3102,3103,3106,3108,3111],{"class":531,"line":550},[529,3104,3105],{"class":617},"        c",[529,3107,627],{"class":535},[529,3109,3110],{"class":604},"Table",[529,3112,1107],{"class":535},[529,3114,3115,3118,3120,3122,3124,3126,3128,3130,3132,3134,3136,3138,3140,3142,3144,3146,3148,3150,3152],{"class":531,"line":560},[529,3116,3117],{"class":535},"            []",[529,3119,1672],{"class":1671},[529,3121,658],{"class":535},[529,3123,718],{"class":535},[529,3125,1679],{"class":721},[529,3127,718],{"class":535},[529,3129,727],{"class":535},[529,3131,730],{"class":535},[529,3133,1688],{"class":721},[529,3135,718],{"class":535},[529,3137,727],{"class":535},[529,3139,730],{"class":535},[529,3141,1697],{"class":721},[529,3143,718],{"class":535},[529,3145,727],{"class":535},[529,3147,730],{"class":535},[529,3149,1706],{"class":721},[529,3151,718],{"class":535},[529,3153,3154],{"class":535},"},\n",[529,3156,3157,3160,3162],{"class":531,"line":572},[529,3158,3159],{"class":617},"            items",[529,3161,727],{"class":535},[529,3163,3164],{"class":1479}," // [][]string\n",[529,3166,3167,3170,3172,3175,3177,3180,3182,3185,3187,3189,3191,3193],{"class":531,"line":577},[529,3168,3169],{"class":617},"            template",[529,3171,627],{"class":535},[529,3173,3174],{"class":604},"ColumnWidths",[529,3176,647],{"class":535},[529,3178,3179],{"class":806},"55",[529,3181,727],{"class":535},[529,3183,3184],{"class":806}," 15",[529,3186,727],{"class":535},[529,3188,3184],{"class":806},[529,3190,727],{"class":535},[529,3192,3184],{"class":806},[529,3194,1130],{"class":535},[529,3196,3197,3199,3201,3204],{"class":531,"line":587},[529,3198,3169],{"class":617},[529,3200,627],{"class":535},[529,3202,3203],{"class":604},"TableHeaderStyle",[529,3205,1107],{"class":535},[529,3207,3208,3210,3212,3214],{"class":531,"line":593},[529,3209,2068],{"class":617},[529,3211,627],{"class":535},[529,3213,1300],{"class":604},[529,3215,3216],{"class":535},"(),\n",[529,3218,3219,3221,3223,3225,3227,3229,3231,3233],{"class":531,"line":598},[529,3220,2068],{"class":617},[529,3222,627],{"class":535},[529,3224,2181],{"class":604},[529,3226,647],{"class":535},[529,3228,1579],{"class":617},[529,3230,627],{"class":535},[529,3232,2190],{"class":617},[529,3234,1130],{"class":535},[529,3236,3237,3239,3241,3243,3245,3247,3249,3251,3253,3255],{"class":531,"line":614},[529,3238,2068],{"class":617},[529,3240,627],{"class":535},[529,3242,2073],{"class":604},[529,3244,647],{"class":535},[529,3246,1579],{"class":617},[529,3248,627],{"class":535},[529,3250,2082],{"class":604},[529,3252,647],{"class":535},[529,3254,2087],{"class":806},[529,3256,2090],{"class":535},[529,3258,3259],{"class":531,"line":636},[529,3260,3261],{"class":535},"            ),\n",[529,3263,3264,3266,3268,3271,3273,3275,3277,3279,3281,3284],{"class":531,"line":680},[529,3265,3169],{"class":617},[529,3267,627],{"class":535},[529,3269,3270],{"class":604},"TableStripe",[529,3272,647],{"class":535},[529,3274,1579],{"class":617},[529,3276,627],{"class":535},[529,3278,2082],{"class":604},[529,3280,647],{"class":535},[529,3282,3283],{"class":806},"0xF5F5F5",[529,3285,2090],{"class":535},[529,3287,3288],{"class":531,"line":692},[529,3289,3290],{"class":535},"        )\n",[529,3292,3293],{"class":531,"line":697},[529,3294,1313],{"class":535},[529,3296,3297],{"class":531,"line":751},[529,3298,677],{"class":535},[19,3300,3301,3302,3304],{},"就这些。自动分页。正文延续到的页面自动重复表头。条纹行只需一个选项。列宽是容器百分比,所以同一表格放进 ",[31,3303,2220],{}," 会按比例显示一半大小,无需重写。25 行的 gopdf 簿记函数消失。",[19,3306,3307,3308,3311,3312,3315],{},"一个具体数字。100 行发票渲染在 gpdf 中是 ",[22,3309,3310],{},"108 µs",",在 signintech/gopdf 中约 ",[22,3313,3314],{},"2.4 ms"," — gopdf 数字取决于你写的逐单元格模式因此会变。倍数不是重点;函数本身消失才是。",[14,3317,3319],{"id":3318},"before-after-4-段落旁边的图像","Before / After 4: 段落旁边的图像",[19,3321,3322],{},"常见模式: 左边公司 logo,右边地址块。",[19,3324,3325],{},[22,3326,518],{},[520,3328,3330],{"className":522,"code":3329,"language":524,"meta":525,"style":525},"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 公司\"); err != nil {\n    log.Fatal(err)\n}\n\npdf.SetFont(\"helvetica\", \"\", 10)\npdf.SetXY(rightX, blockY+20)\npdf.Cell(nil, \"上海市浦东新区世纪大道 100 号\")\npdf.SetXY(rightX, blockY+34)\npdf.Cell(nil, \"200120\")\npdf.SetXY(rightX, blockY+48)\npdf.Cell(nil, \"billing@acme.example\")\n",[31,3331,3332,3338,3347,3357,3367,3371,3375,3453,3468,3472,3476,3503,3522,3555,3569,3573,3577,3604,3626,3645,3668,3687,3710],{"__ignoreMap":525},[529,3333,3334,3336],{"class":531,"line":532},[529,3335,1463],{"class":535},[529,3337,557],{"class":535},[529,3339,3340,3343,3345],{"class":531,"line":543},[529,3341,3342],{"class":617},"    leftX  ",[529,3344,1473],{"class":535},[529,3346,1490],{"class":806},[529,3348,3349,3352,3354],{"class":531,"line":550},[529,3350,3351],{"class":617},"    rightX ",[529,3353,1473],{"class":535},[529,3355,3356],{"class":806}," 380.0\n",[529,3358,3359,3362,3364],{"class":531,"line":560},[529,3360,3361],{"class":617},"    blockY ",[529,3363,1473],{"class":535},[529,3365,3366],{"class":806}," 50.0\n",[529,3368,3369],{"class":531,"line":572},[529,3370,590],{"class":535},[529,3372,3373],{"class":531,"line":577},[529,3374,547],{"emptyLinePlaceholder":546},[529,3376,3377,3380,3382,3384,3386,3388,3391,3393,3395,3398,3400,3402,3405,3407,3410,3412,3415,3417,3419,3422,3424,3427,3429,3432,3434,3437,3439,3442,3445,3447,3449,3451],{"class":531,"line":587},[529,3378,3379],{"class":553},"if",[529,3381,703],{"class":617},[529,3383,621],{"class":535},[529,3385,708],{"class":617},[529,3387,627],{"class":535},[529,3389,3390],{"class":604},"Image",[529,3392,647],{"class":535},[529,3394,718],{"class":535},[529,3396,3397],{"class":721},"logo.png",[529,3399,718],{"class":535},[529,3401,727],{"class":535},[529,3403,3404],{"class":617}," leftX",[529,3406,727],{"class":535},[529,3408,3409],{"class":617}," blockY",[529,3411,727],{"class":535},[529,3413,3414],{"class":535}," &",[529,3416,650],{"class":539},[529,3418,627],{"class":535},[529,3420,3421],{"class":539},"Rect",[529,3423,658],{"class":535},[529,3425,3426],{"class":617},"W",[529,3428,664],{"class":535},[529,3430,3431],{"class":806}," 100",[529,3433,727],{"class":535},[529,3435,3436],{"class":617}," H",[529,3438,664],{"class":535},[529,3440,3441],{"class":806}," 60",[529,3443,3444],{"class":535},"});",[529,3446,703],{"class":617},[529,3448,743],{"class":535},[529,3450,746],{"class":535},[529,3452,611],{"class":535},[529,3454,3455,3458,3460,3462,3464,3466],{"class":531,"line":593},[529,3456,3457],{"class":617},"    log",[529,3459,627],{"class":535},[529,3461,759],{"class":604},[529,3463,647],{"class":535},[529,3465,764],{"class":617},[529,3467,590],{"class":535},[529,3469,3470],{"class":531,"line":598},[529,3471,997],{"class":535},[529,3473,3474],{"class":531,"line":614},[529,3475,547],{"emptyLinePlaceholder":546},[529,3477,3478,3480,3482,3484,3486,3488,3490,3492,3494,3496,3498,3501],{"class":531,"line":636},[529,3479,1579],{"class":617},[529,3481,627],{"class":535},[529,3483,788],{"class":604},[529,3485,647],{"class":535},[529,3487,718],{"class":535},[529,3489,1590],{"class":721},[529,3491,718],{"class":535},[529,3493,727],{"class":535},[529,3495,801],{"class":535},[529,3497,727],{"class":535},[529,3499,3500],{"class":806}," 14",[529,3502,590],{"class":535},[529,3504,3505,3507,3509,3511,3513,3516,3518,3520],{"class":531,"line":680},[529,3506,1579],{"class":617},[529,3508,627],{"class":535},[529,3510,33],{"class":604},[529,3512,647],{"class":535},[529,3514,3515],{"class":617},"rightX",[529,3517,727],{"class":535},[529,3519,3409],{"class":617},[529,3521,590],{"class":535},[529,3523,3524,3526,3528,3530,3532,3534,3536,3538,3540,3543,3545,3547,3549,3551,3553],{"class":531,"line":692},[529,3525,3379],{"class":553},[529,3527,703],{"class":617},[529,3529,621],{"class":535},[529,3531,708],{"class":617},[529,3533,627],{"class":535},[529,3535,37],{"class":604},[529,3537,894],{"class":535},[529,3539,730],{"class":535},[529,3541,3542],{"class":721},"ACME 公司",[529,3544,718],{"class":535},[529,3546,738],{"class":535},[529,3548,703],{"class":617},[529,3550,743],{"class":535},[529,3552,746],{"class":535},[529,3554,611],{"class":535},[529,3556,3557,3559,3561,3563,3565,3567],{"class":531,"line":697},[529,3558,3457],{"class":617},[529,3560,627],{"class":535},[529,3562,759],{"class":604},[529,3564,647],{"class":535},[529,3566,764],{"class":617},[529,3568,590],{"class":535},[529,3570,3571],{"class":531,"line":751},[529,3572,997],{"class":535},[529,3574,3575],{"class":531,"line":769},[529,3576,547],{"emptyLinePlaceholder":546},[529,3578,3579,3581,3583,3585,3587,3589,3591,3593,3595,3597,3599,3602],{"class":531,"line":775},[529,3580,1579],{"class":617},[529,3582,627],{"class":535},[529,3584,788],{"class":604},[529,3586,647],{"class":535},[529,3588,718],{"class":535},[529,3590,722],{"class":721},[529,3592,718],{"class":535},[529,3594,727],{"class":535},[529,3596,801],{"class":535},[529,3598,727],{"class":535},[529,3600,3601],{"class":806}," 10",[529,3603,590],{"class":535},[529,3605,3606,3608,3610,3612,3614,3616,3618,3620,3622,3624],{"class":531,"line":820},[529,3607,1579],{"class":617},[529,3609,627],{"class":535},[529,3611,33],{"class":604},[529,3613,647],{"class":535},[529,3615,3515],{"class":617},[529,3617,727],{"class":535},[529,3619,3409],{"class":617},[529,3621,1746],{"class":535},[529,3623,1162],{"class":806},[529,3625,590],{"class":535},[529,3627,3628,3630,3632,3634,3636,3638,3641,3643],{"class":531,"line":835},[529,3629,1579],{"class":617},[529,3631,627],{"class":535},[529,3633,37],{"class":604},[529,3635,894],{"class":535},[529,3637,730],{"class":535},[529,3639,3640],{"class":721},"上海市浦东新区世纪大道 100 号",[529,3642,718],{"class":535},[529,3644,590],{"class":535},[529,3646,3647,3649,3651,3653,3655,3657,3659,3661,3663,3666],{"class":531,"line":840},[529,3648,1579],{"class":617},[529,3650,627],{"class":535},[529,3652,33],{"class":604},[529,3654,647],{"class":535},[529,3656,3515],{"class":617},[529,3658,727],{"class":535},[529,3660,3409],{"class":617},[529,3662,1746],{"class":535},[529,3664,3665],{"class":806},"34",[529,3667,590],{"class":535},[529,3669,3670,3672,3674,3676,3678,3680,3683,3685],{"class":531,"line":845},[529,3671,1579],{"class":617},[529,3673,627],{"class":535},[529,3675,37],{"class":604},[529,3677,894],{"class":535},[529,3679,730],{"class":535},[529,3681,3682],{"class":721},"200120",[529,3684,718],{"class":535},[529,3686,590],{"class":535},[529,3688,3689,3691,3693,3695,3697,3699,3701,3703,3705,3708],{"class":531,"line":862},[529,3690,1579],{"class":617},[529,3692,627],{"class":535},[529,3694,33],{"class":604},[529,3696,647],{"class":535},[529,3698,3515],{"class":617},[529,3700,727],{"class":535},[529,3702,3409],{"class":617},[529,3704,1746],{"class":535},[529,3706,3707],{"class":806},"48",[529,3709,590],{"class":535},[529,3711,3712,3714,3716,3718,3720,3722,3725,3727],{"class":531,"line":879},[529,3713,1579],{"class":617},[529,3715,627],{"class":535},[529,3717,37],{"class":604},[529,3719,894],{"class":535},[529,3721,730],{"class":535},[529,3723,3724],{"class":721},"billing@acme.example",[529,3726,718],{"class":535},[529,3728,590],{"class":535},[19,3730,3731,3732,3735],{},"六个显式 y 坐标,右侧块从 ",[31,3733,3734],{},"rightX = 380"," 开始 — 因为有人决定 logo 宽 100,右侧块需要 240 像素间隔。把 logo 移到右侧每个数字都要变。",[19,3737,3738],{},[22,3739,1002],{},[520,3741,3743],{"className":522,"code":3742,"language":524,"meta":525,"style":525},"//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 公司\", template.Bold(), template.FontSize(14))\n        c.Text(\"上海市浦东新区世纪大道 100 号\")\n        c.Text(\"200120\")\n        c.Text(\"billing@acme.example\")\n    })\n})\n",[31,3744,3745,3750,3764,3768,3792,3822,3860,3864,3895,3935,3953,3971,3989,3993],{"__ignoreMap":525},[529,3746,3747],{"class":531,"line":532},[529,3748,3749],{"class":1479},"//go:embed logo.png\n",[529,3751,3752,3755,3758,3761],{"class":531,"line":543},[529,3753,3754],{"class":535},"var",[529,3756,3757],{"class":617}," logoData ",[529,3759,3760],{"class":535},"[]",[529,3762,3763],{"class":1671},"byte\n",[529,3765,3766],{"class":531,"line":550},[529,3767,547],{"emptyLinePlaceholder":546},[529,3769,3770,3772,3774,3776,3778,3780,3782,3784,3786,3788,3790],{"class":531,"line":560},[529,3771,1936],{"class":617},[529,3773,627],{"class":535},[529,3775,1200],{"class":604},[529,3777,1203],{"class":535},[529,3779,1207],{"class":1206},[529,3781,667],{"class":535},[529,3783,1212],{"class":539},[529,3785,627],{"class":535},[529,3787,1217],{"class":539},[529,3789,275],{"class":535},[529,3791,611],{"class":535},[529,3793,3794,3796,3798,3800,3802,3804,3806,3808,3810,3812,3814,3816,3818,3820],{"class":531,"line":572},[529,3795,3074],{"class":617},[529,3797,627],{"class":535},[529,3799,1231],{"class":604},[529,3801,647],{"class":535},[529,3803,2274],{"class":806},[529,3805,727],{"class":535},[529,3807,1241],{"class":535},[529,3809,1244],{"class":1206},[529,3811,667],{"class":535},[529,3813,1212],{"class":539},[529,3815,627],{"class":535},[529,3817,1253],{"class":539},[529,3819,275],{"class":535},[529,3821,611],{"class":535},[529,3823,3824,3826,3828,3830,3832,3835,3837,3839,3841,3844,3846,3848,3850,3852,3854,3857],{"class":531,"line":577},[529,3825,3105],{"class":617},[529,3827,627],{"class":535},[529,3829,3390],{"class":604},[529,3831,647],{"class":535},[529,3833,3834],{"class":617},"logoData",[529,3836,727],{"class":535},[529,3838,1280],{"class":617},[529,3840,627],{"class":535},[529,3842,3843],{"class":604},"FitWidth",[529,3845,647],{"class":535},[529,3847,1122],{"class":617},[529,3849,627],{"class":535},[529,3851,1157],{"class":604},[529,3853,647],{"class":535},[529,3855,3856],{"class":806},"35",[529,3858,3859],{"class":535},")))\n",[529,3861,3862],{"class":531,"line":587},[529,3863,1313],{"class":535},[529,3865,3866,3868,3870,3872,3874,3877,3879,3881,3883,3885,3887,3889,3891,3893],{"class":531,"line":593},[529,3867,3074],{"class":617},[529,3869,627],{"class":535},[529,3871,1231],{"class":604},[529,3873,647],{"class":535},[529,3875,3876],{"class":806},"8",[529,3878,727],{"class":535},[529,3880,1241],{"class":535},[529,3882,1244],{"class":1206},[529,3884,667],{"class":535},[529,3886,1212],{"class":539},[529,3888,627],{"class":535},[529,3890,1253],{"class":539},[529,3892,275],{"class":535},[529,3894,611],{"class":535},[529,3896,3897,3899,3901,3903,3905,3907,3909,3911,3913,3915,3917,3919,3921,3923,3925,3927,3929,3932],{"class":531,"line":598},[529,3898,3105],{"class":617},[529,3900,627],{"class":535},[529,3902,1267],{"class":604},[529,3904,647],{"class":535},[529,3906,718],{"class":535},[529,3908,3542],{"class":721},[529,3910,718],{"class":535},[529,3912,727],{"class":535},[529,3914,1280],{"class":617},[529,3916,627],{"class":535},[529,3918,1300],{"class":604},[529,3920,2159],{"class":535},[529,3922,1280],{"class":617},[529,3924,627],{"class":535},[529,3926,1285],{"class":604},[529,3928,647],{"class":535},[529,3930,3931],{"class":806},"14",[529,3933,3934],{"class":535},"))\n",[529,3936,3937,3939,3941,3943,3945,3947,3949,3951],{"class":531,"line":614},[529,3938,3105],{"class":617},[529,3940,627],{"class":535},[529,3942,1267],{"class":604},[529,3944,647],{"class":535},[529,3946,718],{"class":535},[529,3948,3640],{"class":721},[529,3950,718],{"class":535},[529,3952,590],{"class":535},[529,3954,3955,3957,3959,3961,3963,3965,3967,3969],{"class":531,"line":636},[529,3956,3105],{"class":617},[529,3958,627],{"class":535},[529,3960,1267],{"class":604},[529,3962,647],{"class":535},[529,3964,718],{"class":535},[529,3966,3682],{"class":721},[529,3968,718],{"class":535},[529,3970,590],{"class":535},[529,3972,3973,3975,3977,3979,3981,3983,3985,3987],{"class":531,"line":680},[529,3974,3105],{"class":617},[529,3976,627],{"class":535},[529,3978,1267],{"class":604},[529,3980,647],{"class":535},[529,3982,718],{"class":535},[529,3984,3724],{"class":721},[529,3986,718],{"class":535},[529,3988,590],{"class":535},[529,3990,3991],{"class":531,"line":692},[529,3992,1313],{"class":535},[529,3994,3995],{"class":531,"line":697},[529,3996,677],{"class":535},[19,3998,3999,4000,4003,4004,4006],{},"两列,4 + 8 = 12。图像按固定宽度适配,高度按宽高比 gpdf 自己算。每个 ",[31,4001,4002],{},"c.Text"," 流到上一行下方 — 没有 ",[31,4005,49],{},",没有 y 算术。要把 logo 放右边就互换列顺序。",[14,4008,4010],{"id":4009},"before-after-5-页脚的页码","Before / After 5: 页脚的页码",[19,4012,4013],{},"在 gopdf 中你自维护计数,因为渲染单遍,画第一个页脚时总数未知。多数代码库做两遍渲染: 第一遍数页数,第二遍把总数烧进去。",[19,4015,4016],{},[22,4017,518],{},[520,4019,4021],{"className":522,"code":4020,"language":524,"meta":525,"style":525},"totalPages := 0\npdf.AddFooter(func() {\n    totalPages++\n})\n\nbuildContent(&pdf)\nfinalTotal := totalPages\n\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(\"第 %d 页 / 共 %d 页\", pageNum, finalTotal))\n})\nbuildContent(&pdf2)\npdf2.WritePdf(\"report.pdf\")\n",[31,4022,4023,4033,4047,4055,4059,4063,4075,4085,4089,4104,4137,4146,4158,4165,4193,4212,4263,4267,4277],{"__ignoreMap":525},[529,4024,4025,4028,4030],{"class":531,"line":532},[529,4026,4027],{"class":617},"totalPages ",[529,4029,621],{"class":535},[529,4031,4032],{"class":806}," 0\n",[529,4034,4035,4037,4039,4042,4045],{"class":531,"line":543},[529,4036,1579],{"class":617},[529,4038,627],{"class":535},[529,4040,4041],{"class":604},"AddFooter",[529,4043,4044],{"class":535},"(func()",[529,4046,611],{"class":535},[529,4048,4049,4052],{"class":531,"line":550},[529,4050,4051],{"class":617},"    totalPages",[529,4053,4054],{"class":535},"++\n",[529,4056,4057],{"class":531,"line":560},[529,4058,677],{"class":535},[529,4060,4061],{"class":531,"line":572},[529,4062,547],{"emptyLinePlaceholder":546},[529,4064,4065,4068,4071,4073],{"class":531,"line":577},[529,4066,4067],{"class":604},"buildContent",[529,4069,4070],{"class":535},"(&",[529,4072,1579],{"class":617},[529,4074,590],{"class":535},[529,4076,4077,4080,4082],{"class":531,"line":587},[529,4078,4079],{"class":617},"finalTotal ",[529,4081,621],{"class":535},[529,4083,4084],{"class":617}," totalPages\n",[529,4086,4087],{"class":531,"line":593},[529,4088,547],{"emptyLinePlaceholder":546},[529,4090,4091,4094,4096,4098,4100,4102],{"class":531,"line":598},[529,4092,4093],{"class":617},"pdf2 ",[529,4095,621],{"class":535},[529,4097,624],{"class":539},[529,4099,627],{"class":535},[529,4101,630],{"class":539},[529,4103,633],{"class":535},[529,4105,4106,4109,4111,4113,4115,4117,4119,4121,4123,4125,4127,4129,4131,4133,4135],{"class":531,"line":614},[529,4107,4108],{"class":617},"pdf2",[529,4110,627],{"class":535},[529,4112,644],{"class":604},[529,4114,647],{"class":535},[529,4116,650],{"class":539},[529,4118,627],{"class":535},[529,4120,655],{"class":539},[529,4122,658],{"class":535},[529,4124,661],{"class":617},[529,4126,664],{"class":535},[529,4128,667],{"class":535},[529,4130,650],{"class":617},[529,4132,627],{"class":535},[529,4134,674],{"class":617},[529,4136,677],{"class":535},[529,4138,4139,4142,4144],{"class":531,"line":636},[529,4140,4141],{"class":617},"pageNum ",[529,4143,621],{"class":535},[529,4145,4032],{"class":806},[529,4147,4148,4150,4152,4154,4156],{"class":531,"line":680},[529,4149,4108],{"class":617},[529,4151,627],{"class":535},[529,4153,4041],{"class":604},[529,4155,4044],{"class":535},[529,4157,611],{"class":535},[529,4159,4160,4163],{"class":531,"line":692},[529,4161,4162],{"class":617},"    pageNum",[529,4164,4054],{"class":535},[529,4166,4167,4170,4172,4174,4176,4178,4180,4182,4184,4186,4188,4191],{"class":531,"line":697},[529,4168,4169],{"class":617},"    pdf2",[529,4171,627],{"class":535},[529,4173,788],{"class":604},[529,4175,647],{"class":535},[529,4177,718],{"class":535},[529,4179,722],{"class":721},[529,4181,718],{"class":535},[529,4183,727],{"class":535},[529,4185,801],{"class":535},[529,4187,727],{"class":535},[529,4189,4190],{"class":806}," 8",[529,4192,590],{"class":535},[529,4194,4195,4197,4199,4201,4203,4205,4207,4210],{"class":531,"line":751},[529,4196,4169],{"class":617},[529,4198,627],{"class":535},[529,4200,33],{"class":604},[529,4202,647],{"class":535},[529,4204,857],{"class":806},[529,4206,727],{"class":535},[529,4208,4209],{"class":806}," 800",[529,4211,590],{"class":535},[529,4213,4214,4216,4218,4220,4222,4225,4227,4230,4232,4234,4237,4241,4244,4246,4249,4251,4253,4256,4258,4261],{"class":531,"line":769},[529,4215,4169],{"class":617},[529,4217,627],{"class":535},[529,4219,37],{"class":604},[529,4221,894],{"class":535},[529,4223,4224],{"class":617}," fmt",[529,4226,627],{"class":535},[529,4228,4229],{"class":604},"Sprintf",[529,4231,647],{"class":535},[529,4233,718],{"class":535},[529,4235,4236],{"class":721},"第 ",[529,4238,4240],{"class":4239},"swJcz","%d",[529,4242,4243],{"class":721}," 页 / 共 ",[529,4245,4240],{"class":4239},[529,4247,4248],{"class":721}," 页",[529,4250,718],{"class":535},[529,4252,727],{"class":535},[529,4254,4255],{"class":617}," pageNum",[529,4257,727],{"class":535},[529,4259,4260],{"class":617}," finalTotal",[529,4262,3934],{"class":535},[529,4264,4265],{"class":531,"line":775},[529,4266,677],{"class":535},[529,4268,4269,4271,4273,4275],{"class":531,"line":820},[529,4270,4067],{"class":604},[529,4272,4070],{"class":535},[529,4274,4108],{"class":617},[529,4276,590],{"class":535},[529,4278,4279,4281,4283,4285,4287,4289,4292,4294],{"class":531,"line":835},[529,4280,4108],{"class":617},[529,4282,627],{"class":535},[529,4284,952],{"class":604},[529,4286,647],{"class":535},[529,4288,718],{"class":535},[529,4290,4291],{"class":721},"report.pdf",[529,4293,718],{"class":535},[529,4295,590],{"class":535},[19,4297,4298],{},"如果维护过 gopdf 代码,你写过这段。FAQ 里没有,但这是不解析输出就拿到诚实「第 X 页 / 共 Y 页」的唯一办法。",[19,4300,4301],{},[22,4302,1002],{},[520,4304,4306],{"className":522,"code":4305,"language":524,"meta":525,"style":525},"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 公司\",\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(\"第 \", template.Inline())\n                c.PageNumber(template.Inline())\n                c.Text(\" 页 / 共 \", template.Inline())\n                c.TotalPages(template.Inline())\n                c.Text(\" 页\", template.Inline())\n            }, template.FontSize(8), template.TextColor(pdf.Gray(0.5)))\n        })\n    })\n})\n",[31,4307,4308,4335,4360,4390,4408,4444,4448,4478,4514,4542,4561,4587,4606,4632,4669,4673,4677],{"__ignoreMap":525},[529,4309,4310,4313,4315,4318,4320,4322,4324,4326,4328,4331,4333],{"class":531,"line":532},[529,4311,4312],{"class":617},"doc",[529,4314,627],{"class":535},[529,4316,4317],{"class":604},"Footer",[529,4319,1203],{"class":535},[529,4321,19],{"class":1206},[529,4323,667],{"class":535},[529,4325,1212],{"class":539},[529,4327,627],{"class":535},[529,4329,4330],{"class":539},"PageBuilder",[529,4332,275],{"class":535},[529,4334,611],{"class":535},[529,4336,4337,4340,4342,4344,4346,4348,4350,4352,4354,4356,4358],{"class":531,"line":543},[529,4338,4339],{"class":617},"    p",[529,4341,627],{"class":535},[529,4343,1200],{"class":604},[529,4345,1203],{"class":535},[529,4347,1207],{"class":1206},[529,4349,667],{"class":535},[529,4351,1212],{"class":539},[529,4353,627],{"class":535},[529,4355,1217],{"class":539},[529,4357,275],{"class":535},[529,4359,611],{"class":535},[529,4361,4362,4364,4366,4368,4370,4372,4374,4376,4378,4380,4382,4384,4386,4388],{"class":531,"line":550},[529,4363,1226],{"class":617},[529,4365,627],{"class":535},[529,4367,1231],{"class":604},[529,4369,647],{"class":535},[529,4371,1822],{"class":806},[529,4373,727],{"class":535},[529,4375,1241],{"class":535},[529,4377,1244],{"class":1206},[529,4379,667],{"class":535},[529,4381,1212],{"class":539},[529,4383,627],{"class":535},[529,4385,1253],{"class":539},[529,4387,275],{"class":535},[529,4389,611],{"class":535},[529,4391,4392,4394,4396,4398,4400,4402,4404,4406],{"class":531,"line":560},[529,4393,1262],{"class":617},[529,4395,627],{"class":535},[529,4397,1267],{"class":604},[529,4399,647],{"class":535},[529,4401,718],{"class":535},[529,4403,3542],{"class":721},[529,4405,718],{"class":535},[529,4407,2148],{"class":535},[529,4409,4410,4412,4414,4416,4418,4420,4422,4424,4426,4428,4430,4432,4434,4437,4439,4442],{"class":531,"line":572},[529,4411,2068],{"class":617},[529,4413,627],{"class":535},[529,4415,1285],{"class":604},[529,4417,647],{"class":535},[529,4419,3876],{"class":806},[529,4421,1293],{"class":535},[529,4423,1280],{"class":617},[529,4425,627],{"class":535},[529,4427,2181],{"class":604},[529,4429,647],{"class":535},[529,4431,1579],{"class":617},[529,4433,627],{"class":535},[529,4435,4436],{"class":604},"Gray",[529,4438,647],{"class":535},[529,4440,4441],{"class":806},"0.5",[529,4443,3859],{"class":535},[529,4445,4446],{"class":531,"line":577},[529,4447,1308],{"class":535},[529,4449,4450,4452,4454,4456,4458,4460,4462,4464,4466,4468,4470,4472,4474,4476],{"class":531,"line":587},[529,4451,1226],{"class":617},[529,4453,627],{"class":535},[529,4455,1231],{"class":604},[529,4457,647],{"class":535},[529,4459,1822],{"class":806},[529,4461,727],{"class":535},[529,4463,1241],{"class":535},[529,4465,1244],{"class":1206},[529,4467,667],{"class":535},[529,4469,1212],{"class":539},[529,4471,627],{"class":535},[529,4473,1253],{"class":539},[529,4475,275],{"class":535},[529,4477,611],{"class":535},[529,4479,4480,4482,4484,4487,4489,4491,4493,4496,4498,4500,4502,4504,4506,4508,4510,4512],{"class":531,"line":593},[529,4481,1262],{"class":617},[529,4483,627],{"class":535},[529,4485,4486],{"class":604},"Stack",[529,4488,647],{"class":535},[529,4490,1212],{"class":617},[529,4492,627],{"class":535},[529,4494,4495],{"class":604},"AlignRight",[529,4497,2159],{"class":535},[529,4499,1241],{"class":535},[529,4501,1244],{"class":1206},[529,4503,667],{"class":535},[529,4505,1212],{"class":539},[529,4507,627],{"class":535},[529,4509,1253],{"class":539},[529,4511,275],{"class":535},[529,4513,611],{"class":535},[529,4515,4516,4519,4521,4523,4525,4527,4529,4531,4533,4535,4537,4540],{"class":531,"line":598},[529,4517,4518],{"class":617},"                c",[529,4520,627],{"class":535},[529,4522,1267],{"class":604},[529,4524,647],{"class":535},[529,4526,718],{"class":535},[529,4528,4236],{"class":721},[529,4530,718],{"class":535},[529,4532,727],{"class":535},[529,4534,1280],{"class":617},[529,4536,627],{"class":535},[529,4538,4539],{"class":604},"Inline",[529,4541,1303],{"class":535},[529,4543,4544,4546,4548,4551,4553,4555,4557,4559],{"class":531,"line":614},[529,4545,4518],{"class":617},[529,4547,627],{"class":535},[529,4549,4550],{"class":604},"PageNumber",[529,4552,647],{"class":535},[529,4554,1212],{"class":617},[529,4556,627],{"class":535},[529,4558,4539],{"class":604},[529,4560,1303],{"class":535},[529,4562,4563,4565,4567,4569,4571,4573,4575,4577,4579,4581,4583,4585],{"class":531,"line":636},[529,4564,4518],{"class":617},[529,4566,627],{"class":535},[529,4568,1267],{"class":604},[529,4570,647],{"class":535},[529,4572,718],{"class":535},[529,4574,4243],{"class":721},[529,4576,718],{"class":535},[529,4578,727],{"class":535},[529,4580,1280],{"class":617},[529,4582,627],{"class":535},[529,4584,4539],{"class":604},[529,4586,1303],{"class":535},[529,4588,4589,4591,4593,4596,4598,4600,4602,4604],{"class":531,"line":680},[529,4590,4518],{"class":617},[529,4592,627],{"class":535},[529,4594,4595],{"class":604},"TotalPages",[529,4597,647],{"class":535},[529,4599,1212],{"class":617},[529,4601,627],{"class":535},[529,4603,4539],{"class":604},[529,4605,1303],{"class":535},[529,4607,4608,4610,4612,4614,4616,4618,4620,4622,4624,4626,4628,4630],{"class":531,"line":692},[529,4609,4518],{"class":617},[529,4611,627],{"class":535},[529,4613,1267],{"class":604},[529,4615,647],{"class":535},[529,4617,718],{"class":535},[529,4619,4248],{"class":721},[529,4621,718],{"class":535},[529,4623,727],{"class":535},[529,4625,1280],{"class":617},[529,4627,627],{"class":535},[529,4629,4539],{"class":604},[529,4631,1303],{"class":535},[529,4633,4634,4637,4639,4641,4643,4645,4647,4649,4651,4653,4655,4657,4659,4661,4663,4665,4667],{"class":531,"line":697},[529,4635,4636],{"class":535},"            },",[529,4638,1280],{"class":617},[529,4640,627],{"class":535},[529,4642,1285],{"class":604},[529,4644,647],{"class":535},[529,4646,3876],{"class":806},[529,4648,1293],{"class":535},[529,4650,1280],{"class":617},[529,4652,627],{"class":535},[529,4654,2181],{"class":604},[529,4656,647],{"class":535},[529,4658,1579],{"class":617},[529,4660,627],{"class":535},[529,4662,4436],{"class":604},[529,4664,647],{"class":535},[529,4666,4441],{"class":806},[529,4668,3859],{"class":535},[529,4670,4671],{"class":531,"line":751},[529,4672,1308],{"class":535},[529,4674,4675],{"class":531,"line":769},[529,4676,1313],{"class":535},[529,4678,4679],{"class":531,"line":775},[529,4680,677],{"class":535},[19,4682,4683,4685,4686,4688],{},[31,4684,4550],{}," 和 ",[31,4687,4595],{}," 是占位符。布局引擎先分页,解析总数,再写入。一遍,无手动计数,无重复渲染。",[14,4690,4692],{"id":4691},"中文字体-不用手动子集化","中文字体: 不用手动子集化",[19,4694,4695],{},"signintech/gopdf 也支持 CJK,但要自己管字符集。注册 TTF、设置字符映射,文本含未在子集中的字形就会显示豆腐方块。gpdf 的 TrueType 子集化器走 cmap (格式 4、6、12),只嵌入实际使用的字形 — 无需手动子集列表。",[520,4697,4699],{"className":522,"code":4698,"language":524,"meta":525,"style":525},"//go:embed NotoSansSC-Regular.ttf\nvar notoSC []byte\n\ndoc := gpdf.NewDocument(\n    gpdf.WithPageSize(document.A4),\n    gpdf.WithMargins(document.UniformEdges(document.Mm(20))),\n    gpdf.WithFont(\"NotoSansSC\", notoSC),\n    gpdf.WithDefaultFont(\"NotoSansSC\", 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(\"上海市浦东新区世纪大道 100 号\")\n    })\n})\n",[31,4700,4701,4706,4717,4721,4736,4755,4785,4810,4833,4837,4841,4856,4880,4910,4929,4948,4966,4970],{"__ignoreMap":525},[529,4702,4703],{"class":531,"line":532},[529,4704,4705],{"class":1479},"//go:embed NotoSansSC-Regular.ttf\n",[529,4707,4708,4710,4713,4715],{"class":531,"line":543},[529,4709,3754],{"class":535},[529,4711,4712],{"class":617}," notoSC ",[529,4714,3760],{"class":535},[529,4716,3763],{"class":1671},[529,4718,4719],{"class":531,"line":550},[529,4720,547],{"emptyLinePlaceholder":546},[529,4722,4723,4726,4728,4730,4732,4734],{"class":531,"line":560},[529,4724,4725],{"class":617},"doc ",[529,4727,621],{"class":535},[529,4729,1099],{"class":617},[529,4731,627],{"class":535},[529,4733,1104],{"class":604},[529,4735,1107],{"class":535},[529,4737,4738,4741,4743,4745,4747,4749,4751,4753],{"class":531,"line":572},[529,4739,4740],{"class":617},"    gpdf",[529,4742,627],{"class":535},[529,4744,1117],{"class":604},[529,4746,647],{"class":535},[529,4748,1122],{"class":617},[529,4750,627],{"class":535},[529,4752,1127],{"class":617},[529,4754,1130],{"class":535},[529,4756,4757,4759,4761,4763,4765,4767,4769,4771,4773,4775,4777,4779,4781,4783],{"class":531,"line":577},[529,4758,4740],{"class":617},[529,4760,627],{"class":535},[529,4762,1139],{"class":604},[529,4764,647],{"class":535},[529,4766,1122],{"class":617},[529,4768,627],{"class":535},[529,4770,1148],{"class":604},[529,4772,647],{"class":535},[529,4774,1122],{"class":617},[529,4776,627],{"class":535},[529,4778,1157],{"class":604},[529,4780,647],{"class":535},[529,4782,1162],{"class":806},[529,4784,1165],{"class":535},[529,4786,4787,4789,4791,4794,4796,4798,4801,4803,4805,4808],{"class":531,"line":587},[529,4788,4740],{"class":617},[529,4790,627],{"class":535},[529,4792,4793],{"class":604},"WithFont",[529,4795,647],{"class":535},[529,4797,718],{"class":535},[529,4799,4800],{"class":721},"NotoSansSC",[529,4802,718],{"class":535},[529,4804,727],{"class":535},[529,4806,4807],{"class":617}," notoSC",[529,4809,1130],{"class":535},[529,4811,4812,4814,4816,4819,4821,4823,4825,4827,4829,4831],{"class":531,"line":593},[529,4813,4740],{"class":617},[529,4815,627],{"class":535},[529,4817,4818],{"class":604},"WithDefaultFont",[529,4820,647],{"class":535},[529,4822,718],{"class":535},[529,4824,4800],{"class":721},[529,4826,718],{"class":535},[529,4828,727],{"class":535},[529,4830,3500],{"class":806},[529,4832,1130],{"class":535},[529,4834,4835],{"class":531,"line":598},[529,4836,590],{"class":535},[529,4838,4839],{"class":531,"line":614},[529,4840,547],{"emptyLinePlaceholder":546},[529,4842,4843,4846,4848,4850,4852,4854],{"class":531,"line":636},[529,4844,4845],{"class":617},"page ",[529,4847,621],{"class":535},[529,4849,1184],{"class":617},[529,4851,627],{"class":535},[529,4853,57],{"class":604},[529,4855,689],{"class":535},[529,4857,4858,4860,4862,4864,4866,4868,4870,4872,4874,4876,4878],{"class":531,"line":680},[529,4859,1936],{"class":617},[529,4861,627],{"class":535},[529,4863,1200],{"class":604},[529,4865,1203],{"class":535},[529,4867,1207],{"class":1206},[529,4869,667],{"class":535},[529,4871,1212],{"class":539},[529,4873,627],{"class":535},[529,4875,1217],{"class":539},[529,4877,275],{"class":535},[529,4879,611],{"class":535},[529,4881,4882,4884,4886,4888,4890,4892,4894,4896,4898,4900,4902,4904,4906,4908],{"class":531,"line":692},[529,4883,3074],{"class":617},[529,4885,627],{"class":535},[529,4887,1231],{"class":604},[529,4889,647],{"class":535},[529,4891,1236],{"class":806},[529,4893,727],{"class":535},[529,4895,1241],{"class":535},[529,4897,1244],{"class":1206},[529,4899,667],{"class":535},[529,4901,1212],{"class":539},[529,4903,627],{"class":535},[529,4905,1253],{"class":539},[529,4907,275],{"class":535},[529,4909,611],{"class":535},[529,4911,4912,4914,4916,4918,4920,4922,4925,4927],{"class":531,"line":697},[529,4913,3105],{"class":617},[529,4915,627],{"class":535},[529,4917,1267],{"class":604},[529,4919,647],{"class":535},[529,4921,718],{"class":535},[529,4923,4924],{"class":721},"你好,世界。",[529,4926,718],{"class":535},[529,4928,590],{"class":535},[529,4930,4931,4933,4935,4937,4939,4941,4944,4946],{"class":531,"line":751},[529,4932,3105],{"class":617},[529,4934,627],{"class":535},[529,4936,1267],{"class":604},[529,4938,647],{"class":535},[529,4940,718],{"class":535},[529,4942,4943],{"class":721},"增值税专用发票",[529,4945,718],{"class":535},[529,4947,590],{"class":535},[529,4949,4950,4952,4954,4956,4958,4960,4962,4964],{"class":531,"line":769},[529,4951,3105],{"class":617},[529,4953,627],{"class":535},[529,4955,1267],{"class":604},[529,4957,647],{"class":535},[529,4959,718],{"class":535},[529,4961,3640],{"class":721},[529,4963,718],{"class":535},[529,4965,590],{"class":535},[529,4967,4968],{"class":531,"line":775},[529,4969,1313],{"class":535},[529,4971,4972],{"class":531,"line":820},[529,4973,677],{"class":535},[19,4975,4976],{},"200 字中文发票产生 ~30 KB 字体子集,而非 4 MB 完整嵌入。",[14,4978,4979],{"id":4979},"基准测试",[19,4981,4982],{},"同硬件、同负载,Apple M1 配 Go 1.25。",[136,4984,4985,5002],{},[139,4986,4987],{},[142,4988,4989,4992,4994,4996,4999],{},[145,4990,4991],{},"基准",[145,4993,24],{},[145,4995,28],{},[145,4997,4998],{},"gofpdf",[145,5000,5001],{},"Maroto v2",[153,5003,5004,5023,5041,5059],{},[142,5005,5006,5009,5014,5017,5020],{},[158,5007,5008],{},"单页",[158,5010,5011],{},[22,5012,5013],{},"13 µs",[158,5015,5016],{},"423 µs",[158,5018,5019],{},"132 µs",[158,5021,5022],{},"237 µs",[142,5024,5025,5028,5032,5035,5038],{},[158,5026,5027],{},"4×10 发票表格",[158,5029,5030],{},[22,5031,3310],{},[158,5033,5034],{},"835 µs",[158,5036,5037],{},"241 µs",[158,5039,5040],{},"8.6 ms",[142,5042,5043,5046,5051,5053,5056],{},[158,5044,5045],{},"100 页报告",[158,5047,5048],{},[22,5049,5050],{},"683 µs",[158,5052,5040],{},[158,5054,5055],{},"11.7 ms",[158,5057,5058],{},"19.8 ms",[142,5060,5061,5064,5069,5072,5075],{},[158,5062,5063],{},"复杂 CJK 发票",[158,5065,5066],{},[22,5067,5068],{},"133 µs",[158,5070,5071],{},"997 µs",[158,5073,5074],{},"254 µs",[158,5076,5077],{},"10.4 ms",[19,5079,5080,5081,502],{},"数字来自 ",[31,5082,5083],{},"gpdf/_benchmark/benchmark_test.go",[19,5085,5086,5087,5090],{},"每核 ",[22,5088,5089],{},"每张表格页 108 µs"," = 每秒约 9000 张发票。多数工作负载下 PDF 生成可留在请求路径上。",[14,5092,5094],{"id":5093},"gopdf-有但-gpdf-没有的","gopdf 有但 gpdf 没有的",[19,5096,5097],{},"诚实环节。如果你的 gopdf 用法依赖以下,迁移单靠本文不够。",[5099,5100,5101,5118,5124,5138],"ul",{},[5102,5103,5104,5109,5110,5113,5114,5117],"li",{},[22,5105,5106,502],{},[31,5107,5108],{},"ImportPage"," 从已有 PDF 导入一页并在上面盖章。gpdf 的 overlay (",[31,5111,5112],{},"gpdf.Overlay",") 处理常见场景,但不公开同样的 ",[31,5115,5116],{},"UseImportedTemplate"," 原语。",[5102,5119,5120,5123],{},[22,5121,5122],{},"多边形和椭圆原语。"," gpdf 不把任意路径作为一等公民。数据可视化用图表库渲染成 PNG/SVG 后嵌入。",[5102,5125,5126,5129,5130,5133,5134,5137],{},[22,5127,5128],{},"直接光标定位。"," 真要像素精确放置 (例如恰好在 ",[31,5131,5132],{},"(420, 240)"," 盖章) 时用 ",[31,5135,5136],{},"page.Absolute(x, y, fn)",",但这是逃生口。",[5102,5139,5140,5148,5149,401,5151,5153],{},[22,5141,5142,401,5145,502],{},[31,5143,5144],{},"PlaceHolderText",[31,5146,5147],{},"FillInPlaceHoldText"," 通用「先留位、后填」机制 gpdf 还没有;",[31,5150,4550],{},[31,5152,4595],{}," 占位符仅覆盖页码场景。",[19,5155,5156,5157,5160],{},"对于",[22,5158,5159],{},"发票、对账单、报表、证书、合同、收据、运输标签、装箱单、CJK 文档"," — gopdf 的多数实际负载 — 替换是完整的。",[14,5162,5164],{"id":5163},"faq","FAQ",[19,5166,5167,5170],{},[22,5168,5169],{},"gpdf 是 signintech/gopdf 的分支吗?","\n不是。gpdf 是纯 Go 的全新实现。无共享代码,无血缘。",[19,5172,5173,5176],{},[22,5174,5175],{},"两个都是纯 Go、CGO-free。换的实利是什么?","\n布局引擎。前面迁移段落 80% 是删坐标计算,这才是日常代码读感的差别。基准是次要。MIT 许可证两边一致,所以许可证不是因素。",[19,5178,5179,5182,5183,5186,5187,5190],{},[22,5180,5181],{},"能渐进迁移吗?","\n能。两库不冲突。各自产生独立 ",[31,5184,5185],{},"[]byte"," 输出。一段 gpdf 一段 gopdf,用 ",[31,5188,5189],{},"gpdf.Merge(a, b)"," 拼接。但实践中按文档整体迁移更省心 — 一份文件里两套心智模型容易混乱。",[19,5192,5193,5200,5201,5204,5205,5208,5209,5212],{},[22,5194,5195,5196,5199],{},"已有代码用 ",[31,5197,5198],{},"pdf.Image(path, ...)"," 从磁盘读 logo,必须 embed 吗?","\n不必。",[31,5202,5203],{},"c.Image(imageBytes, ...)"," 接收字节,可以 ",[31,5206,5207],{},"os.ReadFile"," 在运行时读。但 ",[31,5210,5211],{},"//go:embed"," 是更好的默认: 容器镜像无需可写文件系统,生产中资源不会丢失。",[19,5214,5215,5221,5222,34,5225,34,5228,5231,5232,502],{},[22,5216,5217,5220],{},[31,5218,5219],{},"gopdf.PageSizeA4"," 等页面尺寸常量?","\ngpdf 的 ",[31,5223,5224],{},"document.A4",[31,5226,5227],{},"document.Letter",[31,5229,5230],{},"document.Legal"," 等覆盖同一集合。自定义尺寸用 ",[31,5233,5234],{},"document.PageSize(document.Mm(210), document.Mm(297))",[19,5236,5237,5244,5246,5247,5250],{},[22,5238,5239,5240,5243],{},"用 ",[31,5241,5242],{},"pdf.Rotate"," 做斜向水印,有等价吗?",[31,5245,5136],{}," 接受旋转选项,典型「页面对角水印」一次 ",[31,5248,5249],{},"page.Absolute"," 调用即可。",[19,5252,5253,5256],{},[22,5254,5255],{},"有自动重写工具吗?","\n还没有。简单部分映射机械,表格重写是结构性的 — 删除簿记代码而不是翻译。每种文档手动迁移大约几小时。",[14,5258,5260],{"id":5259},"试用-gpdf","试用 gpdf",[19,5262,5263],{},"gpdf 是 Go 的 PDF 生成库。MIT 许可证、零依赖、原生 CJK 支持、12 列网格布局。",[520,5265,5269],{"className":5266,"code":5267,"language":5268,"meta":525,"style":525},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[31,5270,5271],{"__ignoreMap":525},[529,5272,5273,5275,5278],{"class":531,"line":532},[529,5274,524],{"class":539},[529,5276,5277],{"class":721}," get",[529,5279,5280],{"class":721}," github.com/gpdf-dev/gpdf\n",[19,5282,5283,5290,5291],{},[5284,5285,5289],"a",{"href":5286,"rel":5287},"https://github.com/gpdf-dev/gpdf",[5288],"nofollow","⭐ Star on GitHub"," · ",[5284,5292,5295],{"href":5293,"rel":5294},"https://gpdf.dev/zh/docs/quickstart",[5288],"阅读文档",[14,5297,5298],{"id":5298},"接下来读",[5099,5300,5301,5307,5313,5319,5325],{},[5102,5302,5303],{},[5284,5304,5306],{"href":5305},"/zh/blog/12-column-grid","12 列网格: 把 Bootstrap 思想带到 PDF 布局",[5102,5308,5309],{},[5284,5310,5312],{"href":5311},"/zh/blog/bootstrap-grid-thinking-for-pdf","Bootstrap 网格思维做 PDF",[5102,5314,5315],{},[5284,5316,5318],{"href":5317},"/zh/blog/gofpdf-migration","gofpdf 已归档。如何迁移到 gpdf",[5102,5320,5321],{},[5284,5322,5324],{"href":5323},"/zh/blog/unidoc-migration","unipdf 是 AGPL 或付费。如何迁移到 gpdf",[5102,5326,5327],{},[5284,5328,5330],{"href":5329},"/zh/blog/invoice-pdf-go-under-50-lines","Go 50 行内生成发票 PDF",[5332,5333,5334],"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 .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":525,"searchDepth":543,"depth":543,"links":5336},[5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347,5348,5349,5350,5351],{"id":16,"depth":543,"text":17},{"id":67,"depth":543,"text":68},{"id":89,"depth":543,"text":89},{"id":130,"depth":543,"text":131},{"id":509,"depth":543,"text":510},{"id":1445,"depth":543,"text":1446},{"id":2228,"depth":543,"text":2229},{"id":3318,"depth":543,"text":3319},{"id":4009,"depth":543,"text":4010},{"id":4691,"depth":543,"text":4692},{"id":4979,"depth":543,"text":4979},{"id":5093,"depth":543,"text":5094},{"id":5163,"depth":543,"text":5164},{"id":5259,"depth":543,"text":5260},{"id":5298,"depth":543,"text":5298},"2026-05-06","signintech/gopdf 能用，但每个单元格、每条线、每个页眉都是 (x, y) 计算。本文逐项映射 gopdf API 到 gpdf — 同样是 Go，不再写坐标。",false,"md",{"name":5357,"totalTime":5358,"tools":5359,"steps":5361},"将 Go 项目从 signintech/gopdf 迁移到 gpdf","PT40M",[5360],"Go 1.22+",[5362,5365,5368,5371,5374,5377,5380],{"name":5363,"text":5364},"替换 import 路径","把 github.com/signintech/gopdf 换成 github.com/gpdf-dev/gpdf、github.com/gpdf-dev/gpdf/document、github.com/gpdf-dev/gpdf/template。共享可变光标 gopdf.GoPdf{} 结构体被废弃,gpdf 中没有等价类型。",{"name":5366,"text":5367},"用 gpdf.NewDocument 替换 pdf.Start 和 pdf.AddPage","用 gpdf.NewDocument(WithPageSize(document.A4), WithMargins(...)) 构建文档。doc.AddPage() 返回 PageBuilder,而不是光标。无需手动追踪当前 y 坐标。",{"name":5369,"text":5370},"删除所有 SetX、SetY、SetXY 调用","删除所有光标调用。把内容包裹在 page.AutoRow(func(r *RowBuilder)) 和 r.Col(span, func(c *ColBuilder)) 中。12 列网格处理水平布局,AutoRow 处理垂直堆叠,无需手动 y 计算。",{"name":5372,"text":5373},"用 c.Text 替换 Cell 和 MultiCell","在列内部调用 c.Text(string, options...),无需计算 Rect 后调用 Cell 或 MultiCell。列知道自己的宽度,自动换行。",{"name":5375,"text":5376},"把手写表格循环重写为 c.Table","把逐单元格绘制头部和正文行的 SetXY/Cell 循环替换为 c.Table(headers, rows, template.ColumnWidths(...))。边框、分页、表头重复由布局引擎处理。",{"name":5378,"text":5379},"把字体注册改为字节","把 pdf.AddTTFFont(name, path) 替换为构建时的 gpdf.WithFont(name, ttfBytes)。用 //go:embed 将 TTF 嵌入二进制,运行时不再需要字体文件路径。",{"name":5381,"text":5382},"把 WritePdf 替换为 doc.Generate 加 os.WriteFile","把 pdf.WritePdf(path) 替换为 data, _ := doc.Generate() 然后 os.WriteFile(path, data, 0o644)。要直接流到 http.ResponseWriter 或 S3 PutObject 时使用 doc.Render(w)。",null,{},"/zh/blog/signintech-gopdf-migration",{"title":5,"description":5353},"zh/blog/021.signintech-gopdf-migration",[5389,5390,5391],"migration","comparison","tutorial","ZU7C4_rnEPJUV48ubJea5nC4vLKyM7IWaIhSdimMURY",1779199016715]