[{"data":1,"prerenderedAt":2127},["ShallowReactive",2],{"blog-zh-bootstrap-grid-thinking-for-pdf":3},{"id":4,"title":5,"author":6,"body":10,"date":2113,"description":2114,"draft":2115,"extension":2116,"howTo":2117,"image":2117,"meta":2118,"navigation":890,"path":2119,"seo":2120,"stem":2121,"tags":2122,"updated":2117,"__hash__":2126},"blogZh/zh/blog/017.bootstrap-grid-thinking-for-pdf.md","把 Bootstrap 思维带进 PDF：gpdf 的 12 栏网格",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":2090},"minimark",[13,18,26,29,32,44,57,64,73,77,80,104,111,114,118,121,154,165,179,182,186,189,212,215,218,222,233,257,260,263,274,277,288,292,299,310,434,437,441,448,597,623,629,633,649,653,800,818,821,844,859,867,870,873,1906,1916,1919,1922,1947,1950,1953,1963,1981,1991,2005,2015,2018,2025,2029,2032,2049,2063,2066,2086],[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,25],"p",{},[22,23,24],"strong",{},"gpdf 沿用了 Bootstrap 的 12 栏网格。选 12，是因为它能整除成 1、2、3、4、6——也就是你真正会用的所有切分。我们保留了整数 span 的模型，把其他都扔掉了：没有断点、没有 gutter、没有 order、没有 auto-fill。"," 一页是若干行的堆叠，一行是一个水平 Box，行内的列以「12 分之几」的方式取宽。",[19,27,28],{},"仅此而已。实现大约 30 行 Go 代码。有意思的是「我们没有移植什么」。",[14,30,31],{"id":31},"为什么写这篇文章",[19,33,34,35,39,40,43],{},"gpdf 是一个 Go 的 PDF 生成库。高层布局 API 是一组 Builder：",[36,37,38],"code",{},"page.AutoRow → r.Col(span, fn) → c.Text/Image/Table","。新用户看到 ",[36,41,42],{},"r.Col(4, ...)"," 通常会问三个问题：",[45,46,47,51,54],"ol",{},[48,49,50],"li",{},"为什么是 12？为什么不是 16、24，或者「想要多少都可以」？",[48,52,53],{},"这是 CSS Grid？Bootstrap？还是别的什么？",[48,55,56],{},"如果我的 span 总和不是 12 会怎样？",[19,58,59,60,63],{},"本文沿着设计决定一步步回答这些问题。所有判断都归结为一个原则：",[22,61,62],{},"PDF 的渲染需要的是可预测，而不是自适应","。Web 页面在调整尺寸时会重新流动，PDF 不会。这一个区别就消除了 Web 网格系统的大部分复杂度，让我们能交付一个体积更小的设计。",[19,65,66,67,72],{},"如果你只想知道「怎么用」，",[68,69,71],"a",{"href":70},"/zh/blog/12-column-grid","/blog/12-column-grid"," 的菜谱版更直接。本文谈的是「为什么长这样」。",[14,74,76],{"id":75},"设计-pdf-布局的三种选择","设计 PDF 布局的三种选择",[19,78,79],{},"我们做高层 API 的时候，现实的选项只有三个：",[45,81,82,88,94],{},[48,83,84,87],{},[22,85,86],{},"绝对定位","。「在 (72, 540) pt 处绘制文本」。Go 大多数低阶 PDF 库给的就是这个。最大的自由度，最差的人体工程。每个坐标都得自己算。",[48,89,90,93],{},[22,91,92],{},"流式 + flexbox","。内容自上而下堆叠，行内的子元素以 grow/shrink 比例横向分配。功能强但布局过程不平凡——需要约束求解器，舍入误差还会累积。",[48,95,96,99,100,103],{},[22,97,98],{},"固定网格 + 比例","。一页是若干行的堆叠，一行被切成 N 个等宽槽位。每列占用整数个槽位。宽度 = ",[36,101,102],{},"槽位 / N × 行宽","。不需要约束求解器。没有 grow/shrink。",[19,105,106,107,110],{},"我们选了第三个。Bootstrap 十多年前出于同样的理由做了相同的选择：",[22,108,109],{},"实务里要用到的布局，绝大多数都是整数比例的布局","。两等分。1/3 + 2/3。一行四张卡片。25-50-25 的行。这些都不需要约束求解器。",[19,112,113],{},"剩下的问题是：N 取多少？",[14,115,117],{"id":116},"为什么是-12","为什么是 12",[19,119,120],{},"12 不是魔法，但也不是随便。想想文档里你真正想要的整数切分：",[122,123,124,130,136,142,148],"ul",{},[48,125,126,129],{},[22,127,128],{},"2 列"," —— 左右半分",[48,131,132,135],{},[22,133,134],{},"3 列"," —— 三等分（三卡片画廊）",[48,137,138,141],{},[22,139,140],{},"4 列"," —— 四等分（KPI 横条）",[48,143,144,147],{},[22,145,146],{},"6 列"," —— 六等分（窄边栏，偶尔用）",[48,149,150,153],{},[22,151,152],{},"12 列"," —— 十二等分（很少用，细分隔条）",[19,155,156,157,160,161,164],{},"12 的因数：1、2、3、4、6、12。也就是说六分之一以内",[22,158,159],{},"所有","实用的整数切分都进来了。10 给不了三等分。16 同样给不了。24 全都能给，但认知负担翻倍——你看到 ",[36,162,163],{},"r.Col(8, ...)"," 还得想这是 1/3（24 ÷ 3）还是 2/3（8 ÷ 12）。12 是覆盖人们常用切分的最小数。",[19,166,167,168,171,172,174,175,178],{},"Bootstrap 在 2011 年也是出于这个理由落到 12 上。后来 CSS Grid 更进一步，让你直接写 ",[36,169,170],{},"1fr 2fr 1fr","，去掉了魔法数字。但比例并不免费——它把代价转嫁给读代码的人。",[36,173,42],{}," 直接告诉你「行的三分之一」。",[36,176,177],{},"r.Col(2fr, ...)"," 必须把所有兄弟都看一遍才能确定含义。",[19,180,181],{},"对于布局固定、靠肉眼调试的 PDF 而言，整数模型更合适。",[14,183,185],{"id":184},"我们从-bootstrap-保留了什么","我们从 Bootstrap 保留了什么",[19,187,188],{},"只有三件事：",[45,190,191,197,206],{},[48,192,193,196],{},[22,194,195],{},"12","。分母。仪表盘上唯一的数字。",[48,198,199,202,203,205],{},[22,200,201],{},"整数 1〜12 的 span","。不是分数，不是 CSS 单位。",[36,204,42],{}," 占行的 4/12。",[48,207,208,211],{},[22,209,210],{},"思维模型","。一页是若干行的堆叠，一行被切分成若干列。和你写了十年 HTML 的网格形状一致。",[19,213,214],{},"到这里都跟 Bootstrap 一样。下面才是有趣的部分。",[14,216,217],{"id":217},"我们扔掉了什么",[219,220,221],"h3",{"id":221},"断点",[19,223,224,225,228,229,232],{},"Bootstrap 的 ",[36,226,227],{},"col-md-6 col-lg-4"," 让一列在平板上占一半，在桌面上占三分之一。Web 里有用。",[22,230,231],{},"PDF 里没意义","。PDF 的页面是固定画布，没有 viewport 可以查询，没有 resize 事件，没有 media query。我们把断点彻底删掉了。",[19,234,235,236,239,240,239,243,239,246,239,249,252,253,256],{},"省下来的远比看起来多。CSS 框架之所以要发 ",[36,237,238],{},"col-xs-*","、",[36,241,242],{},"col-sm-*",[36,244,245],{},"col-md-*",[36,247,248],{},"col-lg-*",[36,250,251],{},"col-xl-*"," 五份相同的列类，根源就是断点。gpdf 全没有。API 是 ",[36,254,255],{},"r.Col(span int, fn func(*ColBuilder))","，一个签名，一个心智槽。",[219,258,259],{"id":259},"gutter",[19,261,262],{},"Bootstrap 的行默认在列间加水平 padding。PDF 不需要默认 gutter，因为列间间距完全取决于内容——紧凑表格用 0，hero 区用 24pt，发票行可能只要 0.5pt 的分隔线。所以我们让间距显式。",[19,264,265,266,269,270,273],{},"要 gutter 就自己加：在列之间塞 ",[36,267,268],{},"c.Spacer(...)","，或者把内层包进带 padding 的 Box。网格本身从不替你插入你没要求的像素。",[22,271,272],{},"对一切以点为单位的印刷介质来说，「无 gutter 默认」才是对的默认值","。",[219,275,276],{"id":276},"order",[19,278,279,280,283,284,287],{},"CSS 可以用 ",[36,281,282],{},"order: 2"," 调换列的视觉顺序。这是为响应式设计：同一份 DOM 在小屏上换一种顺序展示。",[22,285,286],{},"对 PDF 没用","。文件里列出现的顺序就是页面上呈现的顺序。我们连考虑都没考虑过。",[219,289,291],{"id":290},"auto-fill-auto-fit","auto-fill / auto-fit",[19,293,294,295,298],{},"CSS Grid 里有 ",[36,296,297],{},"repeat(auto-fit, minmax(200px, 1fr))","——按 200px 起的最小列宽尽量塞。Web 上的画廊很漂亮。但 PDF 在编译时就知道页面宽度，不需要让布局引擎来猜。",[19,300,301,302,305,306,309],{},"要四张卡片的行就 ",[36,303,304],{},"r.Col(3, ...)"," 写四遍，要六张就 ",[36,307,308],{},"r.Col(2, ...)"," 写六遍。「auto」版本就是用户自己代码里的一个 for 循环：",[311,312,317],"pre",{"className":313,"code":314,"language":315,"meta":316,"style":316},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","for _, item := range items {\n    r.Col(3, func(c *template.ColBuilder) {\n        c.Text(item.Name)\n    })\n}\n","go","",[36,318,319,351,398,422,428],{"__ignoreMap":316},[320,321,324,328,332,336,339,342,345,348],"span",{"class":322,"line":323},"line",1,[320,325,327],{"class":326},"s7zQu","for",[320,329,331],{"class":330},"sTEyZ"," _",[320,333,335],{"class":334},"sMK4o",",",[320,337,338],{"class":330}," item ",[320,340,341],{"class":334},":=",[320,343,344],{"class":326}," range",[320,346,347],{"class":330}," items ",[320,349,350],{"class":334},"{\n",[320,352,354,357,360,364,367,371,373,376,380,383,387,389,392,395],{"class":322,"line":353},2,[320,355,356],{"class":330},"    r",[320,358,359],{"class":334},".",[320,361,363],{"class":362},"s2Zo4","Col",[320,365,366],{"class":334},"(",[320,368,370],{"class":369},"sbssI","3",[320,372,335],{"class":334},[320,374,375],{"class":334}," func(",[320,377,379],{"class":378},"sHdIc","c",[320,381,382],{"class":334}," *",[320,384,386],{"class":385},"sBMFI","template",[320,388,359],{"class":334},[320,390,391],{"class":385},"ColBuilder",[320,393,394],{"class":334},")",[320,396,397],{"class":334}," {\n",[320,399,401,404,406,409,411,414,416,419],{"class":322,"line":400},3,[320,402,403],{"class":330},"        c",[320,405,359],{"class":334},[320,407,408],{"class":362},"Text",[320,410,366],{"class":334},[320,412,413],{"class":330},"item",[320,415,359],{"class":334},[320,417,418],{"class":330},"Name",[320,420,421],{"class":334},")\n",[320,423,425],{"class":322,"line":424},4,[320,426,427],{"class":334},"    })\n",[320,429,431],{"class":322,"line":430},5,[320,432,433],{"class":334},"}\n",[19,435,436],{},"三行。不需要写进框架。",[219,438,440],{"id":439},"强制-span-总和","强制 span 总和",[19,442,443,444,447],{},"可能让人意外的一条：",[22,445,446],{},"gpdf 不要求列的 span 总和等于 12","。这是有意的。",[311,449,451],{"className":313,"code":450,"language":315,"meta":316,"style":316},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"左 1/3\") })\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"中 1/3\") })\n    // 总和 = 8。右边的 1/3 就是空的。\n})\n",[36,452,453,482,537,586,592],{"__ignoreMap":316},[320,454,455,458,460,463,466,469,471,473,475,478,480],{"class":322,"line":323},[320,456,457],{"class":330},"page",[320,459,359],{"class":334},[320,461,462],{"class":362},"AutoRow",[320,464,465],{"class":334},"(func(",[320,467,468],{"class":378},"r",[320,470,382],{"class":334},[320,472,386],{"class":385},[320,474,359],{"class":334},[320,476,477],{"class":385},"RowBuilder",[320,479,394],{"class":334},[320,481,397],{"class":334},[320,483,484,486,488,490,492,495,497,499,501,503,505,507,509,511,514,517,519,521,523,526,530,532,534],{"class":322,"line":353},[320,485,356],{"class":330},[320,487,359],{"class":334},[320,489,363],{"class":362},[320,491,366],{"class":334},[320,493,494],{"class":369},"4",[320,496,335],{"class":334},[320,498,375],{"class":334},[320,500,379],{"class":378},[320,502,382],{"class":334},[320,504,386],{"class":385},[320,506,359],{"class":334},[320,508,391],{"class":385},[320,510,394],{"class":334},[320,512,513],{"class":334}," {",[320,515,516],{"class":330}," c",[320,518,359],{"class":334},[320,520,408],{"class":362},[320,522,366],{"class":334},[320,524,525],{"class":334},"\"",[320,527,529],{"class":528},"sfazB","左 1/3",[320,531,525],{"class":334},[320,533,394],{"class":334},[320,535,536],{"class":334}," })\n",[320,538,539,541,543,545,547,549,551,553,555,557,559,561,563,565,567,569,571,573,575,577,580,582,584],{"class":322,"line":400},[320,540,356],{"class":330},[320,542,359],{"class":334},[320,544,363],{"class":362},[320,546,366],{"class":334},[320,548,494],{"class":369},[320,550,335],{"class":334},[320,552,375],{"class":334},[320,554,379],{"class":378},[320,556,382],{"class":334},[320,558,386],{"class":385},[320,560,359],{"class":334},[320,562,391],{"class":385},[320,564,394],{"class":334},[320,566,513],{"class":334},[320,568,516],{"class":330},[320,570,359],{"class":334},[320,572,408],{"class":362},[320,574,366],{"class":334},[320,576,525],{"class":334},[320,578,579],{"class":528},"中 1/3",[320,581,525],{"class":334},[320,583,394],{"class":334},[320,585,536],{"class":334},[320,587,588],{"class":322,"line":424},[320,589,591],{"class":590},"sHwdD","    // 总和 = 8。右边的 1/3 就是空的。\n",[320,593,594],{"class":322,"line":430},[320,595,596],{"class":334},"})\n",[19,598,599,600,603,604,607,608,611,612,607,615,618,619,622],{},"库把每列当作 ",[36,601,602],{},"span/12 × 行宽"," 来处理，仅此而已。一行写 4 + 4，右边那个槽位就是空的。写 7 + 8，第二列就溢出到行外——这也是有意的，因为有时候你就想要溢出（比如和比页面更宽的版心对齐）。span 会被夹到 1〜12（",[36,605,606],{},"Col(0, ...)"," 变成 ",[36,609,610],{},"Col(1, ...)","，",[36,613,614],{},"Col(99, ...)",[36,616,617],{},"Col(12, ...)","，参见 ",[36,620,621],{},"gpdf/template/grid.go:120","），但不会自动换行，不会自动平衡。",[19,624,625,626,273],{},"Bootstrap 旧版「总和超过 12 就折到下一行」是为响应式问题设计的。PDF 没那个问题。我们用一个更简单的合同代替：",[22,627,628],{},"写什么就出什么",[219,630,632],{"id":631},"containerfluid-模式no-guttersoffsetpushpull","container、fluid 模式、no-gutters、offset、push/pull",[19,634,635,636,239,639,239,642,645,646,648],{},"通通没有。",[36,637,638],{},"container-fluid",[36,640,641],{},"col-md-offset-3",[36,643,644],{},"col-md-push-2","，所有 Bootstrap 工具类等价物都没在 gpdf。要把列向右推，自己包：在前面塞一个空的 ",[36,647,304],{},"。多写八个字符，不引入新概念。",[14,650,652],{"id":651},"gpdf-vs-bootstrap-vs-css-grid","gpdf vs Bootstrap vs CSS Grid",[654,655,656,675],"table",{},[657,658,659],"thead",{},[660,661,662,666,669,672],"tr",{},[663,664,665],"th",{},"特性",[663,667,668],{},"Bootstrap (CSS)",[663,670,671],{},"CSS Grid (CSS)",[663,673,674],{},"gpdf (Go)",[676,677,678,694,712,725,741,758,770,784],"tbody",{},[660,679,680,684,686,692],{},[681,682,683],"td",{},"网格大小",[681,685,152],{},[681,687,688,689,394],{},"任意 (",[36,690,691],{},"grid-template-columns",[681,693,152],{},[660,695,696,699,702,709],{},[681,697,698],{},"单位",[681,700,701],{},"类名",[681,703,704,705,708],{},"比例 (",[36,706,707],{},"fr",")、px、%",[681,710,711],{},"整数 span 1〜12",[660,713,714,716,719,722],{},[681,715,221],{},[681,717,718],{},"5 档 (xs/sm/md/lg/xl)",[681,720,721],{},"通过 media query",[681,723,724],{},"无",[660,726,727,730,737,739],{},[681,728,729],{},"默认 gutter",[681,731,732,733,736],{},"有 (",[36,734,735],{},"gx-*"," 控制)",[681,738,724],{},[681,740,724],{},[660,742,743,746,751,756],{},[681,744,745],{},"视觉重排",[681,747,748],{},[36,749,750],{},"order-*",[681,752,753,755],{},[36,754,276],{}," 属性",[681,757,724],{},[660,759,760,763,765,768],{},[681,761,762],{},"auto-fill",[681,764,724],{},[681,766,767],{},"有",[681,769,724],{},[660,771,772,775,778,781],{},[681,773,774],{},"总和 > 12 折行",[681,776,777],{},"有（旧版）/ 无（flex）",[681,779,780],{},"不适用",[681,782,783],{},"无（允许溢出）",[660,785,786,789,792,795],{},[681,787,788],{},"实现规模",[681,790,791],{},"约 3,000 行 SCSS",[681,793,794],{},"浏览器内",[681,796,797],{},[22,798,799],{},"约 30 行 Go",[19,801,802,803,806,807,810,811,814,815,273],{},"「30 行」是真数。打开 ",[36,804,805],{},"gpdf/template/grid.go"," 数一下：一个常量（",[36,808,809],{},"gridColumns = 12","）、一个把整数夹到范围内的 Builder 方法、一遍 build 过程为每行发出一个水平 Box，子元素的宽度是 ",[36,812,813],{},"Pct(span/12*100)","。没有度量过程，没有 flex 算法，没有再平衡。",[22,816,817],{},"宽度的算术就是算法本身",[14,819,820],{"id":820},"内部到底怎么做",[19,822,823,824,827,828,831,832,835,836,839,840,843],{},"调用 ",[36,825,826],{},"r.Col(4, fn)"," 时，gpdf 在行里 append 一个 ",[36,829,830],{},"colEntry{span: 4, fn: fn}","。文档构建时，每个 entry 变成一个 ",[36,833,834],{},"Width: document.Pct(33.333…)"," 的 ",[36,837,838],{},"document.Box","，列内容嵌套其中。行本身是 ",[36,841,842],{},"Direction: DirectionHorizontal"," 的 Box。PDF Writer（Layer 1）按文档顺序遍历 Box，发出内容流；布局引擎（Layer 2）解析宽高；网格（Layer 3）做整数到百分比的转换。仅此而已。",[19,845,846,847,850,851,854,855,858],{},"之所以能用 30 行打住，是因为",[22,848,849],{},"百分比与整数在布局边界处的合成不会带来舍入误差","。列里嵌列、再嵌列，最终是 ",[36,852,853],{},"float64"," 上的一串 ",[36,856,857],{},"Pct"," 乘法。即便嵌套很深，误差也远低于一个排印点。",[19,860,861,862,866],{},"想看完整链路，",[68,863,865],{"href":864},"/zh/blog/why-gpdf-is-faster","gpdf 为什么比同类快 10 倍"," 解释了渲染管线。网格是其中最便宜的一层——M1 上单页约 13 µs，网格只占其中几百纳秒。",[14,868,869],{"id":869},"一份完整可运行的示例",[19,871,872],{},"4/8 分割的表头、12 全宽的表格行、3/3/3/3 的 KPI 横条：",[311,874,876],{"className":313,"code":875,"language":315,"meta":316,"style":316},"package main\n\nimport (\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := template.NewDocument(document.PageSize(document.A4))\n\n    doc.Page(func(p *template.PageBuilder) {\n        // 4/8 分割：左 logo，右 地址\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(4, func(c *template.ColBuilder) {\n                c.Text(\"ACME, Inc.\", template.FontSize(18), template.Bold())\n            })\n            r.Col(8, func(c *template.ColBuilder) {\n                c.Text(\"北京市朝阳区工业大道 123 号\", template.AlignRight())\n                c.Text(\"邮编 100001\", template.AlignRight())\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // 全宽（12 span 1 列）的表格\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Table([]string{\"品名\", \"数量\", \"金额\"}, [][]string{\n                    {\"商品 A\", \"2\", \"¥1,000\"},\n                    {\"商品 B\", \"1\", \"¥2,500\"},\n                })\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // KPI 横条：3 span × 4 列\n        kpis := []struct{ label, value string }{\n            {\"小计\", \"¥4,500\"},\n            {\"增值税 (10%)\", \"¥450\"},\n            {\"运费\", \"¥0\"},\n            {\"总计\", \"¥4,950\"},\n        }\n        p.AutoRow(func(r *template.RowBuilder) {\n            for _, k := range kpis {\n                k := k\n                r.Col(3, func(c *template.ColBuilder) {\n                    c.Text(k.label, template.FontSize(8))\n                    c.Text(k.value, template.FontSize(14), template.Bold())\n                })\n            }\n        })\n    })\n\n    f, _ := os.Create(\"invoice.pdf\")\n    defer f.Close()\n    doc.Render(f)\n}\n",[36,877,878,886,892,900,911,915,925,935,940,945,959,997,1002,1030,1036,1062,1094,1140,1146,1178,1207,1235,1240,1246,1251,1277,1282,1288,1313,1344,1400,1434,1466,1472,1477,1482,1487,1510,1515,1521,1545,1569,1592,1615,1638,1644,1669,1691,1702,1734,1768,1809,1814,1820,1825,1830,1835,1867,1884,1901],{"__ignoreMap":316},[320,879,880,883],{"class":322,"line":323},[320,881,882],{"class":334},"package",[320,884,885],{"class":385}," main\n",[320,887,888],{"class":322,"line":353},[320,889,891],{"emptyLinePlaceholder":890},true,"\n",[320,893,894,897],{"class":322,"line":400},[320,895,896],{"class":326},"import",[320,898,899],{"class":334}," (\n",[320,901,902,905,908],{"class":322,"line":424},[320,903,904],{"class":334},"    \"",[320,906,907],{"class":385},"os",[320,909,910],{"class":334},"\"\n",[320,912,913],{"class":322,"line":430},[320,914,891],{"emptyLinePlaceholder":890},[320,916,918,920,923],{"class":322,"line":917},6,[320,919,904],{"class":334},[320,921,922],{"class":385},"github.com/gpdf-dev/gpdf/document",[320,924,910],{"class":334},[320,926,928,930,933],{"class":322,"line":927},7,[320,929,904],{"class":334},[320,931,932],{"class":385},"github.com/gpdf-dev/gpdf/template",[320,934,910],{"class":334},[320,936,938],{"class":322,"line":937},8,[320,939,421],{"class":334},[320,941,943],{"class":322,"line":942},9,[320,944,891],{"emptyLinePlaceholder":890},[320,946,948,951,954,957],{"class":322,"line":947},10,[320,949,950],{"class":334},"func",[320,952,953],{"class":362}," main",[320,955,956],{"class":334},"()",[320,958,397],{"class":334},[320,960,962,965,967,970,972,975,977,980,982,985,987,989,991,994],{"class":322,"line":961},11,[320,963,964],{"class":330},"    doc ",[320,966,341],{"class":334},[320,968,969],{"class":330}," template",[320,971,359],{"class":334},[320,973,974],{"class":362},"NewDocument",[320,976,366],{"class":334},[320,978,979],{"class":330},"document",[320,981,359],{"class":334},[320,983,984],{"class":362},"PageSize",[320,986,366],{"class":334},[320,988,979],{"class":330},[320,990,359],{"class":334},[320,992,993],{"class":330},"A4",[320,995,996],{"class":334},"))\n",[320,998,1000],{"class":322,"line":999},12,[320,1001,891],{"emptyLinePlaceholder":890},[320,1003,1005,1008,1010,1013,1015,1017,1019,1021,1023,1026,1028],{"class":322,"line":1004},13,[320,1006,1007],{"class":330},"    doc",[320,1009,359],{"class":334},[320,1011,1012],{"class":362},"Page",[320,1014,465],{"class":334},[320,1016,19],{"class":378},[320,1018,382],{"class":334},[320,1020,386],{"class":385},[320,1022,359],{"class":334},[320,1024,1025],{"class":385},"PageBuilder",[320,1027,394],{"class":334},[320,1029,397],{"class":334},[320,1031,1033],{"class":322,"line":1032},14,[320,1034,1035],{"class":590},"        // 4/8 分割：左 logo，右 地址\n",[320,1037,1039,1042,1044,1046,1048,1050,1052,1054,1056,1058,1060],{"class":322,"line":1038},15,[320,1040,1041],{"class":330},"        p",[320,1043,359],{"class":334},[320,1045,462],{"class":362},[320,1047,465],{"class":334},[320,1049,468],{"class":378},[320,1051,382],{"class":334},[320,1053,386],{"class":385},[320,1055,359],{"class":334},[320,1057,477],{"class":385},[320,1059,394],{"class":334},[320,1061,397],{"class":334},[320,1063,1065,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092],{"class":322,"line":1064},16,[320,1066,1067],{"class":330},"            r",[320,1069,359],{"class":334},[320,1071,363],{"class":362},[320,1073,366],{"class":334},[320,1075,494],{"class":369},[320,1077,335],{"class":334},[320,1079,375],{"class":334},[320,1081,379],{"class":378},[320,1083,382],{"class":334},[320,1085,386],{"class":385},[320,1087,359],{"class":334},[320,1089,391],{"class":385},[320,1091,394],{"class":334},[320,1093,397],{"class":334},[320,1095,1097,1100,1102,1104,1106,1108,1111,1113,1115,1117,1119,1122,1124,1127,1130,1132,1134,1137],{"class":322,"line":1096},17,[320,1098,1099],{"class":330},"                c",[320,1101,359],{"class":334},[320,1103,408],{"class":362},[320,1105,366],{"class":334},[320,1107,525],{"class":334},[320,1109,1110],{"class":528},"ACME, Inc.",[320,1112,525],{"class":334},[320,1114,335],{"class":334},[320,1116,969],{"class":330},[320,1118,359],{"class":334},[320,1120,1121],{"class":362},"FontSize",[320,1123,366],{"class":334},[320,1125,1126],{"class":369},"18",[320,1128,1129],{"class":334},"),",[320,1131,969],{"class":330},[320,1133,359],{"class":334},[320,1135,1136],{"class":362},"Bold",[320,1138,1139],{"class":334},"())\n",[320,1141,1143],{"class":322,"line":1142},18,[320,1144,1145],{"class":334},"            })\n",[320,1147,1149,1151,1153,1155,1157,1160,1162,1164,1166,1168,1170,1172,1174,1176],{"class":322,"line":1148},19,[320,1150,1067],{"class":330},[320,1152,359],{"class":334},[320,1154,363],{"class":362},[320,1156,366],{"class":334},[320,1158,1159],{"class":369},"8",[320,1161,335],{"class":334},[320,1163,375],{"class":334},[320,1165,379],{"class":378},[320,1167,382],{"class":334},[320,1169,386],{"class":385},[320,1171,359],{"class":334},[320,1173,391],{"class":385},[320,1175,394],{"class":334},[320,1177,397],{"class":334},[320,1179,1181,1183,1185,1187,1189,1191,1194,1196,1198,1200,1202,1205],{"class":322,"line":1180},20,[320,1182,1099],{"class":330},[320,1184,359],{"class":334},[320,1186,408],{"class":362},[320,1188,366],{"class":334},[320,1190,525],{"class":334},[320,1192,1193],{"class":528},"北京市朝阳区工业大道 123 号",[320,1195,525],{"class":334},[320,1197,335],{"class":334},[320,1199,969],{"class":330},[320,1201,359],{"class":334},[320,1203,1204],{"class":362},"AlignRight",[320,1206,1139],{"class":334},[320,1208,1210,1212,1214,1216,1218,1220,1223,1225,1227,1229,1231,1233],{"class":322,"line":1209},21,[320,1211,1099],{"class":330},[320,1213,359],{"class":334},[320,1215,408],{"class":362},[320,1217,366],{"class":334},[320,1219,525],{"class":334},[320,1221,1222],{"class":528},"邮编 100001",[320,1224,525],{"class":334},[320,1226,335],{"class":334},[320,1228,969],{"class":330},[320,1230,359],{"class":334},[320,1232,1204],{"class":362},[320,1234,1139],{"class":334},[320,1236,1238],{"class":322,"line":1237},22,[320,1239,1145],{"class":334},[320,1241,1243],{"class":322,"line":1242},23,[320,1244,1245],{"class":334},"        })\n",[320,1247,1249],{"class":322,"line":1248},24,[320,1250,891],{"emptyLinePlaceholder":890},[320,1252,1254,1256,1258,1261,1263,1265,1267,1270,1272,1275],{"class":322,"line":1253},25,[320,1255,1041],{"class":330},[320,1257,359],{"class":334},[320,1259,1260],{"class":362},"Spacer",[320,1262,366],{"class":334},[320,1264,979],{"class":330},[320,1266,359],{"class":334},[320,1268,1269],{"class":362},"Mm",[320,1271,366],{"class":334},[320,1273,1274],{"class":369},"10",[320,1276,996],{"class":334},[320,1278,1280],{"class":322,"line":1279},26,[320,1281,891],{"emptyLinePlaceholder":890},[320,1283,1285],{"class":322,"line":1284},27,[320,1286,1287],{"class":590},"        // 全宽（12 span 1 列）的表格\n",[320,1289,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309,1311],{"class":322,"line":1290},28,[320,1292,1041],{"class":330},[320,1294,359],{"class":334},[320,1296,462],{"class":362},[320,1298,465],{"class":334},[320,1300,468],{"class":378},[320,1302,382],{"class":334},[320,1304,386],{"class":385},[320,1306,359],{"class":334},[320,1308,477],{"class":385},[320,1310,394],{"class":334},[320,1312,397],{"class":334},[320,1314,1316,1318,1320,1322,1324,1326,1328,1330,1332,1334,1336,1338,1340,1342],{"class":322,"line":1315},29,[320,1317,1067],{"class":330},[320,1319,359],{"class":334},[320,1321,363],{"class":362},[320,1323,366],{"class":334},[320,1325,195],{"class":369},[320,1327,335],{"class":334},[320,1329,375],{"class":334},[320,1331,379],{"class":378},[320,1333,382],{"class":334},[320,1335,386],{"class":385},[320,1337,359],{"class":334},[320,1339,391],{"class":385},[320,1341,394],{"class":334},[320,1343,397],{"class":334},[320,1345,1347,1349,1351,1354,1357,1361,1364,1366,1369,1371,1373,1376,1379,1381,1383,1385,1388,1390,1393,1396,1398],{"class":322,"line":1346},30,[320,1348,1099],{"class":330},[320,1350,359],{"class":334},[320,1352,1353],{"class":362},"Table",[320,1355,1356],{"class":334},"([]",[320,1358,1360],{"class":1359},"spNyl","string",[320,1362,1363],{"class":334},"{",[320,1365,525],{"class":334},[320,1367,1368],{"class":528},"品名",[320,1370,525],{"class":334},[320,1372,335],{"class":334},[320,1374,1375],{"class":334}," \"",[320,1377,1378],{"class":528},"数量",[320,1380,525],{"class":334},[320,1382,335],{"class":334},[320,1384,1375],{"class":334},[320,1386,1387],{"class":528},"金额",[320,1389,525],{"class":334},[320,1391,1392],{"class":334},"},",[320,1394,1395],{"class":334}," [][]",[320,1397,1360],{"class":1359},[320,1399,350],{"class":334},[320,1401,1403,1406,1408,1411,1413,1415,1417,1420,1422,1424,1426,1429,1431],{"class":322,"line":1402},31,[320,1404,1405],{"class":334},"                    {",[320,1407,525],{"class":334},[320,1409,1410],{"class":528},"商品 A",[320,1412,525],{"class":334},[320,1414,335],{"class":334},[320,1416,1375],{"class":334},[320,1418,1419],{"class":528},"2",[320,1421,525],{"class":334},[320,1423,335],{"class":334},[320,1425,1375],{"class":334},[320,1427,1428],{"class":528},"¥1,000",[320,1430,525],{"class":334},[320,1432,1433],{"class":334},"},\n",[320,1435,1437,1439,1441,1444,1446,1448,1450,1453,1455,1457,1459,1462,1464],{"class":322,"line":1436},32,[320,1438,1405],{"class":334},[320,1440,525],{"class":334},[320,1442,1443],{"class":528},"商品 B",[320,1445,525],{"class":334},[320,1447,335],{"class":334},[320,1449,1375],{"class":334},[320,1451,1452],{"class":528},"1",[320,1454,525],{"class":334},[320,1456,335],{"class":334},[320,1458,1375],{"class":334},[320,1460,1461],{"class":528},"¥2,500",[320,1463,525],{"class":334},[320,1465,1433],{"class":334},[320,1467,1469],{"class":322,"line":1468},33,[320,1470,1471],{"class":334},"                })\n",[320,1473,1475],{"class":322,"line":1474},34,[320,1476,1145],{"class":334},[320,1478,1480],{"class":322,"line":1479},35,[320,1481,1245],{"class":334},[320,1483,1485],{"class":322,"line":1484},36,[320,1486,891],{"emptyLinePlaceholder":890},[320,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508],{"class":322,"line":1489},37,[320,1491,1041],{"class":330},[320,1493,359],{"class":334},[320,1495,1260],{"class":362},[320,1497,366],{"class":334},[320,1499,979],{"class":330},[320,1501,359],{"class":334},[320,1503,1269],{"class":362},[320,1505,366],{"class":334},[320,1507,1274],{"class":369},[320,1509,996],{"class":334},[320,1511,1513],{"class":322,"line":1512},38,[320,1514,891],{"emptyLinePlaceholder":890},[320,1516,1518],{"class":322,"line":1517},39,[320,1519,1520],{"class":590},"        // KPI 横条：3 span × 4 列\n",[320,1522,1524,1527,1529,1532,1535,1537,1540,1542],{"class":322,"line":1523},40,[320,1525,1526],{"class":330},"        kpis ",[320,1528,341],{"class":334},[320,1530,1531],{"class":334}," []struct{",[320,1533,1534],{"class":330}," label",[320,1536,335],{"class":334},[320,1538,1539],{"class":330}," value ",[320,1541,1360],{"class":1359},[320,1543,1544],{"class":334}," }{\n",[320,1546,1548,1551,1553,1556,1558,1560,1562,1565,1567],{"class":322,"line":1547},41,[320,1549,1550],{"class":334},"            {",[320,1552,525],{"class":334},[320,1554,1555],{"class":528},"小计",[320,1557,525],{"class":334},[320,1559,335],{"class":334},[320,1561,1375],{"class":334},[320,1563,1564],{"class":528},"¥4,500",[320,1566,525],{"class":334},[320,1568,1433],{"class":334},[320,1570,1572,1574,1576,1579,1581,1583,1585,1588,1590],{"class":322,"line":1571},42,[320,1573,1550],{"class":334},[320,1575,525],{"class":334},[320,1577,1578],{"class":528},"增值税 (10%)",[320,1580,525],{"class":334},[320,1582,335],{"class":334},[320,1584,1375],{"class":334},[320,1586,1587],{"class":528},"¥450",[320,1589,525],{"class":334},[320,1591,1433],{"class":334},[320,1593,1595,1597,1599,1602,1604,1606,1608,1611,1613],{"class":322,"line":1594},43,[320,1596,1550],{"class":334},[320,1598,525],{"class":334},[320,1600,1601],{"class":528},"运费",[320,1603,525],{"class":334},[320,1605,335],{"class":334},[320,1607,1375],{"class":334},[320,1609,1610],{"class":528},"¥0",[320,1612,525],{"class":334},[320,1614,1433],{"class":334},[320,1616,1618,1620,1622,1625,1627,1629,1631,1634,1636],{"class":322,"line":1617},44,[320,1619,1550],{"class":334},[320,1621,525],{"class":334},[320,1623,1624],{"class":528},"总计",[320,1626,525],{"class":334},[320,1628,335],{"class":334},[320,1630,1375],{"class":334},[320,1632,1633],{"class":528},"¥4,950",[320,1635,525],{"class":334},[320,1637,1433],{"class":334},[320,1639,1641],{"class":322,"line":1640},45,[320,1642,1643],{"class":334},"        }\n",[320,1645,1647,1649,1651,1653,1655,1657,1659,1661,1663,1665,1667],{"class":322,"line":1646},46,[320,1648,1041],{"class":330},[320,1650,359],{"class":334},[320,1652,462],{"class":362},[320,1654,465],{"class":334},[320,1656,468],{"class":378},[320,1658,382],{"class":334},[320,1660,386],{"class":385},[320,1662,359],{"class":334},[320,1664,477],{"class":385},[320,1666,394],{"class":334},[320,1668,397],{"class":334},[320,1670,1672,1675,1677,1679,1682,1684,1686,1689],{"class":322,"line":1671},47,[320,1673,1674],{"class":326},"            for",[320,1676,331],{"class":330},[320,1678,335],{"class":334},[320,1680,1681],{"class":330}," k ",[320,1683,341],{"class":334},[320,1685,344],{"class":326},[320,1687,1688],{"class":330}," kpis ",[320,1690,350],{"class":334},[320,1692,1694,1697,1699],{"class":322,"line":1693},48,[320,1695,1696],{"class":330},"                k ",[320,1698,341],{"class":334},[320,1700,1701],{"class":330}," k\n",[320,1703,1705,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732],{"class":322,"line":1704},49,[320,1706,1707],{"class":330},"                r",[320,1709,359],{"class":334},[320,1711,363],{"class":362},[320,1713,366],{"class":334},[320,1715,370],{"class":369},[320,1717,335],{"class":334},[320,1719,375],{"class":334},[320,1721,379],{"class":378},[320,1723,382],{"class":334},[320,1725,386],{"class":385},[320,1727,359],{"class":334},[320,1729,391],{"class":385},[320,1731,394],{"class":334},[320,1733,397],{"class":334},[320,1735,1737,1740,1742,1744,1746,1749,1751,1754,1756,1758,1760,1762,1764,1766],{"class":322,"line":1736},50,[320,1738,1739],{"class":330},"                    c",[320,1741,359],{"class":334},[320,1743,408],{"class":362},[320,1745,366],{"class":334},[320,1747,1748],{"class":330},"k",[320,1750,359],{"class":334},[320,1752,1753],{"class":330},"label",[320,1755,335],{"class":334},[320,1757,969],{"class":330},[320,1759,359],{"class":334},[320,1761,1121],{"class":362},[320,1763,366],{"class":334},[320,1765,1159],{"class":369},[320,1767,996],{"class":334},[320,1769,1771,1773,1775,1777,1779,1781,1783,1786,1788,1790,1792,1794,1796,1799,1801,1803,1805,1807],{"class":322,"line":1770},51,[320,1772,1739],{"class":330},[320,1774,359],{"class":334},[320,1776,408],{"class":362},[320,1778,366],{"class":334},[320,1780,1748],{"class":330},[320,1782,359],{"class":334},[320,1784,1785],{"class":330},"value",[320,1787,335],{"class":334},[320,1789,969],{"class":330},[320,1791,359],{"class":334},[320,1793,1121],{"class":362},[320,1795,366],{"class":334},[320,1797,1798],{"class":369},"14",[320,1800,1129],{"class":334},[320,1802,969],{"class":330},[320,1804,359],{"class":334},[320,1806,1136],{"class":362},[320,1808,1139],{"class":334},[320,1810,1812],{"class":322,"line":1811},52,[320,1813,1471],{"class":334},[320,1815,1817],{"class":322,"line":1816},53,[320,1818,1819],{"class":334},"            }\n",[320,1821,1823],{"class":322,"line":1822},54,[320,1824,1245],{"class":334},[320,1826,1828],{"class":322,"line":1827},55,[320,1829,427],{"class":334},[320,1831,1833],{"class":322,"line":1832},56,[320,1834,891],{"emptyLinePlaceholder":890},[320,1836,1838,1841,1843,1846,1848,1851,1853,1856,1858,1860,1863,1865],{"class":322,"line":1837},57,[320,1839,1840],{"class":330},"    f",[320,1842,335],{"class":334},[320,1844,1845],{"class":330}," _ ",[320,1847,341],{"class":334},[320,1849,1850],{"class":330}," os",[320,1852,359],{"class":334},[320,1854,1855],{"class":362},"Create",[320,1857,366],{"class":334},[320,1859,525],{"class":334},[320,1861,1862],{"class":528},"invoice.pdf",[320,1864,525],{"class":334},[320,1866,421],{"class":334},[320,1868,1870,1873,1876,1878,1881],{"class":322,"line":1869},58,[320,1871,1872],{"class":326},"    defer",[320,1874,1875],{"class":330}," f",[320,1877,359],{"class":334},[320,1879,1880],{"class":362},"Close",[320,1882,1883],{"class":334},"()\n",[320,1885,1887,1889,1891,1894,1896,1899],{"class":322,"line":1886},59,[320,1888,1007],{"class":330},[320,1890,359],{"class":334},[320,1892,1893],{"class":362},"Render",[320,1895,366],{"class":334},[320,1897,1898],{"class":330},"f",[320,1900,421],{"class":334},[320,1902,1904],{"class":322,"line":1903},60,[320,1905,433],{"class":334},[19,1907,1908,1909,1912,1913,1915],{},"这是真正能跑的程序。",[36,1910,1911],{},"go get github.com/gpdf-dev/gpdf"," 然后运行，当前目录就会出现 ",[36,1914,1862],{},"。M1 上渲染时间约 130 µs。",[14,1917,1918],{"id":1918},"整数模型不合适的场景",[19,1920,1921],{},"整数十二分之 n 的模型确实有两种场景不合适，老实列出来：",[45,1923,1924,1941],{},[48,1925,1926,1929,1930,1932,1933,1936,1937,1940],{},[22,1927,1928],{},"需要严格像素级精度的宽度","。「这一列必须正好 73.5pt」。",[36,1931,857],{}," 几乎做不到（",[36,1934,1935],{},"73.5 / 总宽 × 12"," 极少是整数）。少数需要固定坐标的元素用 ",[36,1938,1939],{},"page.Absolute(...)","，其他交给网格。两者可以同页混用。",[48,1942,1943,1946],{},[22,1944,1945],{},"需要报纸式的栏内续接","。一段文字在第一栏满了之后续到第二栏。网格不做这个。我们目前还没有栏式续接的文本引擎。需要的话欢迎提 issue——我们知道这块缺。",[19,1948,1949],{},"除此之外，发票、报表、合同、宣传册、提案——12 栏网格比 CSS 还更贴合，不会更松。",[14,1951,1952],{"id":1952},"常见问题",[19,1954,1955,1958,1959,1962],{},[22,1956,1957],{},"问：能把 12 改成 24 之类吗？","\n不能。",[36,1960,1961],{},"gridColumns"," 是常量，改了就废掉所有现有模板。我们一次决定 12，就固定了。",[19,1964,1965,1968,1969,1972,1973,1976,1977,1980],{},[22,1966,1967],{},"问：想在列里嵌套行怎么办？","\n可以。",[36,1970,1971],{},"c.AutoRow(...)"," 在列内创建子行。子行内的 1〜12 是相对",[22,1974,1975],{},"父列宽度","的，不是页面宽度。每一层都是「相对父级的 ",[36,1978,1979],{},"Pct(span/12 × 100)","」，所以嵌套合成很干净。",[19,1982,1983,1986,1987,1990],{},[22,1984,1985],{},"问：横向页面也行吗？","\n行。网格与页面尺寸无关。",[36,1988,1989],{},"r.Col(6, ...)"," 永远是行的一半，不论行宽是 210mm（A4 纵向）还是 297mm（A4 横向）。",[19,1992,1993,2000,2001,2004],{},[22,1994,1995,1996,1999],{},"问：为什么没有 ",[36,1997,1998],{},"r.Col2(span, span, fn1, fn2)"," 这种两列快捷写法？","\n为了少写一行去扩 API 表面，性价比太低。如果你在重复同一个行模式，写一个接收 ",[36,2002,2003],{},"*template.PageBuilder"," 的 Go 函数自己加进去就好。网格保持最小，用户层模式才能不冲突地生长。",[19,2006,2007,2014],{},[22,2008,2009,2010,2013],{},"问：CSS Grid 的 ",[36,2011,2012],{},"grid-area","、命名网格线呢？","\ngpdf 没有，路线图也没有。对 PDF 来说性价比不划算。",[14,2016,2017],{"id":2017},"小结",[19,2019,2020,2021,2024],{},"12 栏网格是「真实文档需要的切分」用最小成本提供的布局原语。我们从 Bootstrap 借了数字 12，保留整数模型，把断点、gutter、order、auto-fill、span 总和强制以及响应式 Web 的其余包袱全扔了。剩下的是一个常量、一个 Builder 方法、一个宽度公式——大约 30 行 Go。它通过嵌套优雅合成，与 ",[36,2022,2023],{},"Absolute"," 在网格无法表达的少数场合和平共存，从不悄悄重排你写的内容。",[14,2026,2028],{"id":2027},"试试-gpdf","试试 gpdf",[19,2030,2031],{},"gpdf 是 Go 的 PDF 生成库。MIT、零依赖、原生 CJK 支持。",[311,2033,2037],{"className":2034,"code":2035,"language":2036,"meta":316,"style":316},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[36,2038,2039],{"__ignoreMap":316},[320,2040,2041,2043,2046],{"class":322,"line":323},[320,2042,315],{"class":385},[320,2044,2045],{"class":528}," get",[320,2047,2048],{"class":528}," github.com/gpdf-dev/gpdf\n",[19,2050,2051,2057,2058],{},[68,2052,2056],{"href":2053,"rel":2054},"https://github.com/gpdf-dev/gpdf",[2055],"nofollow","⭐ Star on GitHub"," · ",[68,2059,2062],{"href":2060,"rel":2061},"https://gpdf.dev/zh/docs/quickstart",[2055],"阅读文档",[14,2064,2065],{"id":2065},"接下来读",[122,2067,2068,2074,2079],{},[48,2069,2070,2073],{},[68,2071,2072],{"href":70},"gpdf 的 12 栏网格怎么用？"," —— 菜谱版，更多代码示例",[48,2075,2076,2078],{},[68,2077,865],{"href":864}," —— 渲染管线内部解析",[48,2080,2081,2085],{},[68,2082,2084],{"href":2083},"/zh/docs/quickstart","Quickstart"," —— 五分钟生成第一份 PDF",[2087,2088,2089],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":316,"searchDepth":353,"depth":353,"links":2091},[2092,2093,2094,2095,2096,2097,2105,2106,2107,2108,2109,2110,2111,2112],{"id":16,"depth":353,"text":17},{"id":31,"depth":353,"text":31},{"id":75,"depth":353,"text":76},{"id":116,"depth":353,"text":117},{"id":184,"depth":353,"text":185},{"id":217,"depth":353,"text":217,"children":2098},[2099,2100,2101,2102,2103,2104],{"id":221,"depth":400,"text":221},{"id":259,"depth":400,"text":259},{"id":276,"depth":400,"text":276},{"id":290,"depth":400,"text":291},{"id":439,"depth":400,"text":440},{"id":631,"depth":400,"text":632},{"id":651,"depth":353,"text":652},{"id":820,"depth":353,"text":820},{"id":869,"depth":353,"text":869},{"id":1918,"depth":353,"text":1918},{"id":1952,"depth":353,"text":1952},{"id":2017,"depth":353,"text":2017},{"id":2027,"depth":353,"text":2028},{"id":2065,"depth":353,"text":2065},"2026-04-29","gpdf 的 PDF 布局借鉴了 Bootstrap 的 12 栏网格——只保留整数 span 模型，丢弃断点、间距、排序等所有响应式包袱。本文剖析这一设计判断。",false,"md",null,{},"/zh/blog/bootstrap-grid-thinking-for-pdf",{"title":5,"description":2114},"zh/blog/017.bootstrap-grid-thinking-for-pdf",[2123,2124,2125],"internals","templates","comparison","lHcUq5IlNOX5G3dm8S36ZcDmbbNbdy4ZjQskPqftsE8",1779199017679]