[{"data":1,"prerenderedAt":2153},["ShallowReactive",2],{"blog-en-bootstrap-grid-thinking-for-pdf":3},{"id":4,"title":5,"author":6,"body":10,"date":2139,"description":2140,"draft":2141,"extension":2142,"howTo":2143,"image":2143,"meta":2144,"navigation":910,"path":2145,"seo":2146,"stem":2147,"tags":2148,"updated":2143,"__hash__":2152},"blog/blog/017.bootstrap-grid-thinking-for-pdf.md","Bootstrap thinking for PDF: gpdf's 12-column grid",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":2116},"minimark",[13,18,26,34,38,50,63,70,86,90,93,117,124,127,131,134,167,178,192,195,199,202,225,228,232,237,248,272,276,279,289,293,304,308,315,326,450,453,457,464,613,639,645,649,665,669,817,836,840,863,878,886,890,893,1927,1937,1941,1944,1969,1972,1976,1986,2004,2014,2028,2038,2042,2049,2053,2056,2073,2087,2091,2112],[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,25],"p",{},[22,23,24],"strong",{},"gpdf uses the 12-column grid from Bootstrap. Twelve, because it divides cleanly into 1, 2, 3, 4, 6 — the splits you actually want. We kept the integer span model and threw out everything else: no breakpoints, no gutters, no order, no auto-fill."," A page is a stack of rows; each row is one horizontal box; columns inside it are widths in twelfths.",[19,27,28,29,33],{},"That's the whole grid. The implementation is around 30 lines of Go. The interesting part is what we ",[30,31,32],"em",{},"didn't"," port.",[14,35,37],{"id":36},"why-this-article-exists","Why this article exists",[19,39,40,41,45,46,49],{},"gpdf is a Go library for generating PDFs. The high-level layout API is a builder: ",[42,43,44],"code",{},"page.AutoRow → r.Col(span, fn) → c.Text/Image/Table",". New users see ",[42,47,48],{},"r.Col(4, ...)"," and ask three questions:",[51,52,53,57,60],"ol",{},[54,55,56],"li",{},"Why 12? Why not 16, 24, or \"as many as you want\"?",[54,58,59],{},"Is this CSS Grid? Bootstrap? Something else?",[54,61,62],{},"What happens if my spans don't add up to 12?",[19,64,65,66,69],{},"This post answers those by walking through the design choices behind the API. They mostly come down to one principle: ",[22,67,68],{},"PDF rendering needs to be predictable, not adaptive."," A web page reflows on resize. A PDF doesn't. That single difference removes most of what makes web grid systems hard, and lets us ship a much smaller idea.",[19,71,72,73,76,77,81,82,85],{},"If you only want to know ",[30,74,75],{},"how"," to use the grid, the recipe at ",[78,79,80],"a",{"href":80},"/blog/12-column-grid"," is more direct. This post is about ",[30,83,84],{},"why"," it looks the way it does.",[14,87,89],{"id":88},"the-three-options-for-laying-out-a-pdf","The three options for laying out a PDF",[19,91,92],{},"When we started the high-level API, we had three real choices:",[51,94,95,101,107],{},[54,96,97,100],{},[22,98,99],{},"Absolute positioning."," \"Draw text at (72, 540) in points.\" This is what most low-level Go PDF libraries give you. Maximum power, terrible ergonomics. You compute every coordinate yourself.",[54,102,103,106],{},[22,104,105],{},"Flow + flexbox."," Stack content top-to-bottom; rows distribute children horizontally with grow/shrink ratios. Powerful, but the layout pass is non-trivial — you need a constraint solver, and rounding errors compound.",[54,108,109,112,113,116],{},[22,110,111],{},"Fixed-grid + ratios."," A page is a stack of rows. A row is divided into N equal slots. Each column claims some integer number of slots. Width = ",[42,114,115],{},"slots / N * row_width",". No constraint solver. No grow/shrink.",[19,118,119,120,123],{},"We picked option 3. Bootstrap got there over a decade ago for the same reason: ",[22,121,122],{},"most layouts you actually need are integer-fraction layouts."," Two columns of equal width. A 1/3 + 2/3 split. Four cards across. A 25-50-25 row. None of those need a constraint solver.",[19,125,126],{},"The remaining question was: how many slots?",[14,128,130],{"id":129},"why-12","Why 12",[19,132,133],{},"12 isn't magic, but it isn't arbitrary either. Think about which integer divisions you ever actually want in a document:",[135,136,137,143,149,155,161],"ul",{},[54,138,139,142],{},[22,140,141],{},"2 columns"," — left/right halves",[54,144,145,148],{},[22,146,147],{},"3 columns"," — thirds (gallery, three-card row)",[54,150,151,154],{},[22,152,153],{},"4 columns"," — quarters (KPI strip)",[54,156,157,160],{},[22,158,159],{},"6 columns"," — sixths (rare, but useful for narrow side panels)",[54,162,163,166],{},[22,164,165],{},"12 columns"," — twelfths (rare; thin separators)",[19,168,169,170,173,174,177],{},"Notice the divisors of 12: 1, 2, 3, 4, 6, 12. That's ",[30,171,172],{},"every"," useful integer split up to a sixth. 10 doesn't give you thirds. 16 doesn't give you thirds either. 24 gives you everything but doubles the cognitive load — you'd write ",[42,175,176],{},"r.Col(8, ...)"," and have to remember whether that means a third (24/3) or two-thirds (8/12). 12 is the smallest number that covers the splits people actually use.",[19,179,180,181,184,185,187,188,191],{},"Bootstrap landed on 12 in 2011 for exactly this reason. CSS Grid later went further and let you write ",[42,182,183],{},"1fr 2fr 1fr"," directly, removing the magic number. But fractions are not a free lunch — they push more work onto whoever reads your layout. ",[42,186,48],{}," is concretely \"one third of the row.\" ",[42,189,190],{},"r.Col(2fr, ...)"," requires you to look at every sibling before you know what it means.",[19,193,194],{},"For PDFs, where layouts are stable and inspected by hand, the integer model wins.",[14,196,198],{"id":197},"what-we-kept-from-bootstrap","What we kept from Bootstrap",[19,200,201],{},"Three things, and only three:",[51,203,204,210,219],{},[54,205,206,209],{},[22,207,208],{},"Twelve."," The denominator. The only number on the dial.",[54,211,212,215,216,218],{},[22,213,214],{},"Span as an integer 1–12."," Not a fraction, not a CSS unit. ",[42,217,48],{}," claims four-twelfths of the row.",[54,220,221,224],{},[22,222,223],{},"The mental model."," A page is a stack of rows. A row is divided into columns. Same shape as the grid you've been writing in HTML for ten years.",[19,226,227],{},"That's it. Now the actually interesting part.",[14,229,231],{"id":230},"what-we-threw-out","What we threw out",[233,234,236],"h3",{"id":235},"breakpoints","Breakpoints",[19,238,239,240,243,244,247],{},"Bootstrap's ",[42,241,242],{},"col-md-6 col-lg-4"," makes a column claim half the row on tablets and a third on desktops. Useful on the web. ",[22,245,246],{},"Meaningless in a PDF."," A PDF page is a fixed canvas. There's no viewport to query, no resize event, no media query. We deleted breakpoints entirely.",[19,249,250,251,254,255,254,258,254,261,254,264,267,268,271],{},"The savings are larger than they look. Breakpoints are the reason CSS frameworks ship ",[42,252,253],{},"col-xs-*",", ",[42,256,257],{},"col-sm-*",[42,259,260],{},"col-md-*",[42,262,263],{},"col-lg-*",[42,265,266],{},"col-xl-*"," variants — five copies of the same column class. None of them exist in gpdf. The API is ",[42,269,270],{},"r.Col(span int, fn func(*ColBuilder))",". One signature. One mental slot.",[233,273,275],{"id":274},"gutters","Gutters",[19,277,278],{},"Bootstrap rows have horizontal padding between columns by default. PDFs don't need a default, because the right margin between columns depends entirely on what you're rendering — a tightly packed table has no gutter, a hero section has 24pt of breathing room, an invoice line might want 0.5pt for a separator. We chose to make spacing explicit.",[19,280,281,282,285,286],{},"If you want a gutter, you put it in: drop a ",[42,283,284],{},"c.Spacer(...)"," between columns, or wrap the inner content in a Box with padding. The grid itself never inserts pixels you didn't ask for. ",[22,287,288],{},"No gutter is the right default for a print medium where every point counts.",[233,290,292],{"id":291},"order","Order",[19,294,295,296,299,300,303],{},"CSS lets you reorder columns visually with ",[42,297,298],{},"order: 2",". Useful for responsive design, where the same DOM should produce a different visual order on small screens. ",[22,301,302],{},"Useless for PDFs."," The order columns appear in the file is the order they appear on the page. We never even considered adding it.",[233,305,307],{"id":306},"auto-fill-auto-fit","Auto-fill / auto-fit",[19,309,310,311,314],{},"CSS Grid has ",[42,312,313],{},"repeat(auto-fit, minmax(200px, 1fr))"," — fill the row with as many 200px-minimum columns as fit. Beautiful for image galleries on the web. For a PDF, you know the page width at build time. You don't need the layout engine to figure it out.",[19,316,317,318,321,322,325],{},"If you want a 4-card row, write ",[42,319,320],{},"r.Col(3, ...)"," four times. If you want a 6-card row, write ",[42,323,324],{},"r.Col(2, ...)"," six times. The \"auto\" version is one for-loop in your own code:",[327,328,333],"pre",{"className":329,"code":330,"language":331,"meta":332,"style":332},"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","",[42,334,335,367,414,438,444],{"__ignoreMap":332},[336,337,340,344,348,352,355,358,361,364],"span",{"class":338,"line":339},"line",1,[336,341,343],{"class":342},"s7zQu","for",[336,345,347],{"class":346},"sTEyZ"," _",[336,349,351],{"class":350},"sMK4o",",",[336,353,354],{"class":346}," item ",[336,356,357],{"class":350},":=",[336,359,360],{"class":342}," range",[336,362,363],{"class":346}," items ",[336,365,366],{"class":350},"{\n",[336,368,370,373,376,380,383,387,389,392,396,399,403,405,408,411],{"class":338,"line":369},2,[336,371,372],{"class":346},"    r",[336,374,375],{"class":350},".",[336,377,379],{"class":378},"s2Zo4","Col",[336,381,382],{"class":350},"(",[336,384,386],{"class":385},"sbssI","3",[336,388,351],{"class":350},[336,390,391],{"class":350}," func(",[336,393,395],{"class":394},"sHdIc","c",[336,397,398],{"class":350}," *",[336,400,402],{"class":401},"sBMFI","template",[336,404,375],{"class":350},[336,406,407],{"class":401},"ColBuilder",[336,409,410],{"class":350},")",[336,412,413],{"class":350}," {\n",[336,415,417,420,422,425,427,430,432,435],{"class":338,"line":416},3,[336,418,419],{"class":346},"        c",[336,421,375],{"class":350},[336,423,424],{"class":378},"Text",[336,426,382],{"class":350},[336,428,429],{"class":346},"item",[336,431,375],{"class":350},[336,433,434],{"class":346},"Name",[336,436,437],{"class":350},")\n",[336,439,441],{"class":338,"line":440},4,[336,442,443],{"class":350},"    })\n",[336,445,447],{"class":338,"line":446},5,[336,448,449],{"class":350},"}\n",[19,451,452],{},"Three lines. We didn't need to bake them into the framework.",[233,454,456],{"id":455},"span-sum-enforcement","Span-sum enforcement",[19,458,459,460,463],{},"Here's the surprising one: ",[22,461,462],{},"gpdf does not require column spans to sum to 12."," This is on purpose.",[327,465,467],{"className":329,"code":466,"language":331,"meta":332,"style":332},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"Left third\") })\n    r.Col(4, func(c *template.ColBuilder) { c.Text(\"Middle third\") })\n    // sum = 8. The right third is just empty.\n})\n",[42,468,469,498,553,602,608],{"__ignoreMap":332},[336,470,471,474,476,479,482,485,487,489,491,494,496],{"class":338,"line":339},[336,472,473],{"class":346},"page",[336,475,375],{"class":350},[336,477,478],{"class":378},"AutoRow",[336,480,481],{"class":350},"(func(",[336,483,484],{"class":394},"r",[336,486,398],{"class":350},[336,488,402],{"class":401},[336,490,375],{"class":350},[336,492,493],{"class":401},"RowBuilder",[336,495,410],{"class":350},[336,497,413],{"class":350},[336,499,500,502,504,506,508,511,513,515,517,519,521,523,525,527,530,533,535,537,539,542,546,548,550],{"class":338,"line":369},[336,501,372],{"class":346},[336,503,375],{"class":350},[336,505,379],{"class":378},[336,507,382],{"class":350},[336,509,510],{"class":385},"4",[336,512,351],{"class":350},[336,514,391],{"class":350},[336,516,395],{"class":394},[336,518,398],{"class":350},[336,520,402],{"class":401},[336,522,375],{"class":350},[336,524,407],{"class":401},[336,526,410],{"class":350},[336,528,529],{"class":350}," {",[336,531,532],{"class":346}," c",[336,534,375],{"class":350},[336,536,424],{"class":378},[336,538,382],{"class":350},[336,540,541],{"class":350},"\"",[336,543,545],{"class":544},"sfazB","Left third",[336,547,541],{"class":350},[336,549,410],{"class":350},[336,551,552],{"class":350}," })\n",[336,554,555,557,559,561,563,565,567,569,571,573,575,577,579,581,583,585,587,589,591,593,596,598,600],{"class":338,"line":416},[336,556,372],{"class":346},[336,558,375],{"class":350},[336,560,379],{"class":378},[336,562,382],{"class":350},[336,564,510],{"class":385},[336,566,351],{"class":350},[336,568,391],{"class":350},[336,570,395],{"class":394},[336,572,398],{"class":350},[336,574,402],{"class":401},[336,576,375],{"class":350},[336,578,407],{"class":401},[336,580,410],{"class":350},[336,582,529],{"class":350},[336,584,532],{"class":346},[336,586,375],{"class":350},[336,588,424],{"class":378},[336,590,382],{"class":350},[336,592,541],{"class":350},[336,594,595],{"class":544},"Middle third",[336,597,541],{"class":350},[336,599,410],{"class":350},[336,601,552],{"class":350},[336,603,604],{"class":338,"line":440},[336,605,607],{"class":606},"sHwdD","    // sum = 8. The right third is just empty.\n",[336,609,610],{"class":338,"line":446},[336,611,612],{"class":350},"})\n",[19,614,615,616,619,620,623,624,627,628,623,631,634,635,638],{},"The library treats each column as ",[42,617,618],{},"span/12 * row_width",", period. If you put 4 + 4 in a row, the third \"slot\" of width is empty. If you put 7 + 8, the second column overflows past the row boundary — also intentional, because sometimes you want overflow (e.g., aligning to a layout grid that's wider than the page itself). Spans clamp to 1–12 (so ",[42,621,622],{},"Col(0, ...)"," becomes ",[42,625,626],{},"Col(1, ...)"," and ",[42,629,630],{},"Col(99, ...)",[42,632,633],{},"Col(12, ...)",", see ",[42,636,637],{},"gpdf/template/grid.go:120","), but no auto-wrapping, no auto-balancing.",[19,640,641,642],{},"Bootstrap's old \"wrap into next row when columns sum past 12\" behaviour solved a real responsive problem. PDFs don't have that problem. We replaced it with a simpler contract: ",[22,643,644],{},"what you wrote is what you get.",[233,646,648],{"id":647},"containers-fluid-mode-no-gutters-mode-offsets-pushpull","Containers, fluid mode, no-gutters mode, offsets, push/pull",[19,650,651,652,254,655,254,658,661,662,664],{},"None of it. We don't ship ",[42,653,654],{},"container-fluid",[42,656,657],{},"col-md-offset-3",[42,659,660],{},"col-md-push-2",", or any other Bootstrap utility-class equivalent. If you want to push a column right, wrap it: put an ",[42,663,320],{}," of empty content before it. Eight more characters, zero new concepts.",[14,666,668],{"id":667},"gpdf-vs-bootstrap-vs-css-grid","gpdf vs Bootstrap vs CSS Grid",[670,671,672,691],"table",{},[673,674,675],"thead",{},[676,677,678,682,685,688],"tr",{},[679,680,681],"th",{},"Feature",[679,683,684],{},"Bootstrap (CSS)",[679,686,687],{},"CSS Grid (CSS)",[679,689,690],{},"gpdf (Go)",[692,693,694,710,728,741,757,774,787,801],"tbody",{},[676,695,696,700,702,708],{},[697,698,699],"td",{},"Grid size",[697,701,165],{},[697,703,704,705,410],{},"Arbitrary (",[42,706,707],{},"grid-template-columns",[697,709,165],{},[676,711,712,715,718,725],{},[697,713,714],{},"Unit",[697,716,717],{},"Class names",[697,719,720,721,724],{},"Fractions (",[42,722,723],{},"fr","), pixels, %",[697,726,727],{},"Integer span 1–12",[676,729,730,732,735,738],{},[697,731,236],{},[697,733,734],{},"5 (xs/sm/md/lg/xl)",[697,736,737],{},"Via media queries",[697,739,740],{},"None",[676,742,743,746,753,755],{},[697,744,745],{},"Default gutter",[697,747,748,749,752],{},"Yes (",[42,750,751],{},"gx-*"," controls it)",[697,754,740],{},[697,756,740],{},[676,758,759,762,767,772],{},[697,760,761],{},"Visual reorder",[697,763,764],{},[42,765,766],{},"order-*",[697,768,769,771],{},[42,770,291],{}," property",[697,773,740],{},[676,775,776,779,782,785],{},[697,777,778],{},"Auto-fill",[697,780,781],{},"No",[697,783,784],{},"Yes",[697,786,781],{},[676,788,789,792,795,798],{},[697,790,791],{},"Wrap when sum > 12",[697,793,794],{},"Yes (legacy) / No (flex)",[697,796,797],{},"N/A",[697,799,800],{},"No (overflow allowed)",[676,802,803,806,809,812],{},[697,804,805],{},"Implementation size",[697,807,808],{},"~3,000 LoC SCSS",[697,810,811],{},"Inside the browser",[697,813,814],{},[22,815,816],{},"~30 LoC Go",[19,818,819,820,823,824,827,828,831,832,835],{},"The \"30 LoC\" number is real. Open ",[42,821,822],{},"gpdf/template/grid.go"," and count: a constant (",[42,825,826],{},"gridColumns = 12","), a builder method that clamps integers, and a build pass that emits one ",[42,829,830],{},"Box"," per row with horizontal direction and ",[42,833,834],{},"Pct(span/12*100)"," widths per child. There's no measurement pass, no flex algorithm, no rebalancing. The width arithmetic is the algorithm.",[14,837,839],{"id":838},"how-gpdf-renders-this-internally","How gpdf renders this internally",[19,841,842,843,846,847,850,851,854,855,858,859,862],{},"When you call ",[42,844,845],{},"r.Col(4, fn)",", gpdf appends a ",[42,848,849],{},"colEntry{span: 4, fn: fn}"," to the row. When the document builds, each entry becomes a ",[42,852,853],{},"document.Box"," with ",[42,856,857],{},"Width: document.Pct(33.333…)"," and the column's content nested inside. The row itself is a Box with ",[42,860,861],{},"Direction: DirectionHorizontal",". The PDF writer (Layer 1) walks Boxes in document order and emits content streams; the layout engine (Layer 2) does width and height resolution; the grid (Layer 3) does the integer-to-percentage conversion.",[19,864,865,866,869,870,873,874,877],{},"The reason this stays at 30 lines is that ",[22,867,868],{},"percentages and integers compose"," without rounding errors at the layout boundary. A column inside a column inside a column still ends up as a stack of ",[42,871,872],{},"Pct"," multiplies, all done in ",[42,875,876],{},"float64",". The error budget is well below a typographic point even for deeply nested layouts.",[19,879,880,881,885],{},"If you want to see the chain end-to-end, ",[78,882,884],{"href":883},"/blog/why-gpdf-is-faster","why gpdf is 10× faster than alternatives"," covers the rendering pipeline. The grid is one of the cheapest layers in it — single-page render time on an M1 is around 13 µs, and the grid contributes a few hundred nanoseconds of that.",[14,887,889],{"id":888},"a-complete-example","A complete example",[19,891,892],{},"Two-column header (4/8 split), then a full-width table row, then a 3/3/3/3 KPI strip:",[327,894,896],{"className":329,"code":895,"language":331,"meta":332,"style":332},"package main\n\nimport (\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := template.NewDocument(document.PageSize(document.A4))\n\n    doc.Page(func(p *template.PageBuilder) {\n        // 4/8 split: logo block on the left, address on the right.\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(4, func(c *template.ColBuilder) {\n                c.Text(\"ACME, Inc.\", template.FontSize(18), template.Bold())\n            })\n            r.Col(8, func(c *template.ColBuilder) {\n                c.Text(\"123 Industrial Way\", template.AlignRight())\n                c.Text(\"Tokyo, Japan 100-0001\", template.AlignRight())\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // Full-width row (one 12-span column) for a table.\n        p.AutoRow(func(r *template.RowBuilder) {\n            r.Col(12, func(c *template.ColBuilder) {\n                c.Table([]string{\"Item\", \"Qty\", \"Price\"}, [][]string{\n                    {\"Widget A\", \"2\", \"¥1,000\"},\n                    {\"Widget B\", \"1\", \"¥2,500\"},\n                })\n            })\n        })\n\n        p.Spacer(document.Mm(10))\n\n        // KPI strip: four equal columns of 3-span each.\n        kpis := []struct{ label, value string }{\n            {\"Subtotal\", \"¥4,500\"},\n            {\"Tax (10%)\", \"¥450\"},\n            {\"Shipping\", \"¥0\"},\n            {\"Total\", \"¥4,950\"},\n        }\n        p.AutoRow(func(r *template.RowBuilder) {\n            for _, k := range kpis {\n                k := k\n                r.Col(3, func(c *template.ColBuilder) {\n                    c.Text(k.label, template.FontSize(8))\n                    c.Text(k.value, template.FontSize(14), template.Bold())\n                })\n            }\n        })\n    })\n\n    f, _ := os.Create(\"invoice.pdf\")\n    defer f.Close()\n    doc.Render(f)\n}\n",[42,897,898,906,912,920,931,935,945,955,960,965,979,1017,1022,1050,1056,1082,1114,1160,1166,1198,1227,1255,1260,1266,1271,1297,1302,1308,1333,1365,1421,1455,1487,1493,1498,1503,1508,1531,1536,1542,1566,1590,1613,1636,1659,1665,1690,1712,1723,1755,1789,1830,1835,1841,1846,1851,1856,1888,1905,1922],{"__ignoreMap":332},[336,899,900,903],{"class":338,"line":339},[336,901,902],{"class":350},"package",[336,904,905],{"class":401}," main\n",[336,907,908],{"class":338,"line":369},[336,909,911],{"emptyLinePlaceholder":910},true,"\n",[336,913,914,917],{"class":338,"line":416},[336,915,916],{"class":342},"import",[336,918,919],{"class":350}," (\n",[336,921,922,925,928],{"class":338,"line":440},[336,923,924],{"class":350},"    \"",[336,926,927],{"class":401},"os",[336,929,930],{"class":350},"\"\n",[336,932,933],{"class":338,"line":446},[336,934,911],{"emptyLinePlaceholder":910},[336,936,938,940,943],{"class":338,"line":937},6,[336,939,924],{"class":350},[336,941,942],{"class":401},"github.com/gpdf-dev/gpdf/document",[336,944,930],{"class":350},[336,946,948,950,953],{"class":338,"line":947},7,[336,949,924],{"class":350},[336,951,952],{"class":401},"github.com/gpdf-dev/gpdf/template",[336,954,930],{"class":350},[336,956,958],{"class":338,"line":957},8,[336,959,437],{"class":350},[336,961,963],{"class":338,"line":962},9,[336,964,911],{"emptyLinePlaceholder":910},[336,966,968,971,974,977],{"class":338,"line":967},10,[336,969,970],{"class":350},"func",[336,972,973],{"class":378}," main",[336,975,976],{"class":350},"()",[336,978,413],{"class":350},[336,980,982,985,987,990,992,995,997,1000,1002,1005,1007,1009,1011,1014],{"class":338,"line":981},11,[336,983,984],{"class":346},"    doc ",[336,986,357],{"class":350},[336,988,989],{"class":346}," template",[336,991,375],{"class":350},[336,993,994],{"class":378},"NewDocument",[336,996,382],{"class":350},[336,998,999],{"class":346},"document",[336,1001,375],{"class":350},[336,1003,1004],{"class":378},"PageSize",[336,1006,382],{"class":350},[336,1008,999],{"class":346},[336,1010,375],{"class":350},[336,1012,1013],{"class":346},"A4",[336,1015,1016],{"class":350},"))\n",[336,1018,1020],{"class":338,"line":1019},12,[336,1021,911],{"emptyLinePlaceholder":910},[336,1023,1025,1028,1030,1033,1035,1037,1039,1041,1043,1046,1048],{"class":338,"line":1024},13,[336,1026,1027],{"class":346},"    doc",[336,1029,375],{"class":350},[336,1031,1032],{"class":378},"Page",[336,1034,481],{"class":350},[336,1036,19],{"class":394},[336,1038,398],{"class":350},[336,1040,402],{"class":401},[336,1042,375],{"class":350},[336,1044,1045],{"class":401},"PageBuilder",[336,1047,410],{"class":350},[336,1049,413],{"class":350},[336,1051,1053],{"class":338,"line":1052},14,[336,1054,1055],{"class":606},"        // 4/8 split: logo block on the left, address on the right.\n",[336,1057,1059,1062,1064,1066,1068,1070,1072,1074,1076,1078,1080],{"class":338,"line":1058},15,[336,1060,1061],{"class":346},"        p",[336,1063,375],{"class":350},[336,1065,478],{"class":378},[336,1067,481],{"class":350},[336,1069,484],{"class":394},[336,1071,398],{"class":350},[336,1073,402],{"class":401},[336,1075,375],{"class":350},[336,1077,493],{"class":401},[336,1079,410],{"class":350},[336,1081,413],{"class":350},[336,1083,1085,1088,1090,1092,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112],{"class":338,"line":1084},16,[336,1086,1087],{"class":346},"            r",[336,1089,375],{"class":350},[336,1091,379],{"class":378},[336,1093,382],{"class":350},[336,1095,510],{"class":385},[336,1097,351],{"class":350},[336,1099,391],{"class":350},[336,1101,395],{"class":394},[336,1103,398],{"class":350},[336,1105,402],{"class":401},[336,1107,375],{"class":350},[336,1109,407],{"class":401},[336,1111,410],{"class":350},[336,1113,413],{"class":350},[336,1115,1117,1120,1122,1124,1126,1128,1131,1133,1135,1137,1139,1142,1144,1147,1150,1152,1154,1157],{"class":338,"line":1116},17,[336,1118,1119],{"class":346},"                c",[336,1121,375],{"class":350},[336,1123,424],{"class":378},[336,1125,382],{"class":350},[336,1127,541],{"class":350},[336,1129,1130],{"class":544},"ACME, Inc.",[336,1132,541],{"class":350},[336,1134,351],{"class":350},[336,1136,989],{"class":346},[336,1138,375],{"class":350},[336,1140,1141],{"class":378},"FontSize",[336,1143,382],{"class":350},[336,1145,1146],{"class":385},"18",[336,1148,1149],{"class":350},"),",[336,1151,989],{"class":346},[336,1153,375],{"class":350},[336,1155,1156],{"class":378},"Bold",[336,1158,1159],{"class":350},"())\n",[336,1161,1163],{"class":338,"line":1162},18,[336,1164,1165],{"class":350},"            })\n",[336,1167,1169,1171,1173,1175,1177,1180,1182,1184,1186,1188,1190,1192,1194,1196],{"class":338,"line":1168},19,[336,1170,1087],{"class":346},[336,1172,375],{"class":350},[336,1174,379],{"class":378},[336,1176,382],{"class":350},[336,1178,1179],{"class":385},"8",[336,1181,351],{"class":350},[336,1183,391],{"class":350},[336,1185,395],{"class":394},[336,1187,398],{"class":350},[336,1189,402],{"class":401},[336,1191,375],{"class":350},[336,1193,407],{"class":401},[336,1195,410],{"class":350},[336,1197,413],{"class":350},[336,1199,1201,1203,1205,1207,1209,1211,1214,1216,1218,1220,1222,1225],{"class":338,"line":1200},20,[336,1202,1119],{"class":346},[336,1204,375],{"class":350},[336,1206,424],{"class":378},[336,1208,382],{"class":350},[336,1210,541],{"class":350},[336,1212,1213],{"class":544},"123 Industrial Way",[336,1215,541],{"class":350},[336,1217,351],{"class":350},[336,1219,989],{"class":346},[336,1221,375],{"class":350},[336,1223,1224],{"class":378},"AlignRight",[336,1226,1159],{"class":350},[336,1228,1230,1232,1234,1236,1238,1240,1243,1245,1247,1249,1251,1253],{"class":338,"line":1229},21,[336,1231,1119],{"class":346},[336,1233,375],{"class":350},[336,1235,424],{"class":378},[336,1237,382],{"class":350},[336,1239,541],{"class":350},[336,1241,1242],{"class":544},"Tokyo, Japan 100-0001",[336,1244,541],{"class":350},[336,1246,351],{"class":350},[336,1248,989],{"class":346},[336,1250,375],{"class":350},[336,1252,1224],{"class":378},[336,1254,1159],{"class":350},[336,1256,1258],{"class":338,"line":1257},22,[336,1259,1165],{"class":350},[336,1261,1263],{"class":338,"line":1262},23,[336,1264,1265],{"class":350},"        })\n",[336,1267,1269],{"class":338,"line":1268},24,[336,1270,911],{"emptyLinePlaceholder":910},[336,1272,1274,1276,1278,1281,1283,1285,1287,1290,1292,1295],{"class":338,"line":1273},25,[336,1275,1061],{"class":346},[336,1277,375],{"class":350},[336,1279,1280],{"class":378},"Spacer",[336,1282,382],{"class":350},[336,1284,999],{"class":346},[336,1286,375],{"class":350},[336,1288,1289],{"class":378},"Mm",[336,1291,382],{"class":350},[336,1293,1294],{"class":385},"10",[336,1296,1016],{"class":350},[336,1298,1300],{"class":338,"line":1299},26,[336,1301,911],{"emptyLinePlaceholder":910},[336,1303,1305],{"class":338,"line":1304},27,[336,1306,1307],{"class":606},"        // Full-width row (one 12-span column) for a table.\n",[336,1309,1311,1313,1315,1317,1319,1321,1323,1325,1327,1329,1331],{"class":338,"line":1310},28,[336,1312,1061],{"class":346},[336,1314,375],{"class":350},[336,1316,478],{"class":378},[336,1318,481],{"class":350},[336,1320,484],{"class":394},[336,1322,398],{"class":350},[336,1324,402],{"class":401},[336,1326,375],{"class":350},[336,1328,493],{"class":401},[336,1330,410],{"class":350},[336,1332,413],{"class":350},[336,1334,1336,1338,1340,1342,1344,1347,1349,1351,1353,1355,1357,1359,1361,1363],{"class":338,"line":1335},29,[336,1337,1087],{"class":346},[336,1339,375],{"class":350},[336,1341,379],{"class":378},[336,1343,382],{"class":350},[336,1345,1346],{"class":385},"12",[336,1348,351],{"class":350},[336,1350,391],{"class":350},[336,1352,395],{"class":394},[336,1354,398],{"class":350},[336,1356,402],{"class":401},[336,1358,375],{"class":350},[336,1360,407],{"class":401},[336,1362,410],{"class":350},[336,1364,413],{"class":350},[336,1366,1368,1370,1372,1375,1378,1382,1385,1387,1390,1392,1394,1397,1400,1402,1404,1406,1409,1411,1414,1417,1419],{"class":338,"line":1367},30,[336,1369,1119],{"class":346},[336,1371,375],{"class":350},[336,1373,1374],{"class":378},"Table",[336,1376,1377],{"class":350},"([]",[336,1379,1381],{"class":1380},"spNyl","string",[336,1383,1384],{"class":350},"{",[336,1386,541],{"class":350},[336,1388,1389],{"class":544},"Item",[336,1391,541],{"class":350},[336,1393,351],{"class":350},[336,1395,1396],{"class":350}," \"",[336,1398,1399],{"class":544},"Qty",[336,1401,541],{"class":350},[336,1403,351],{"class":350},[336,1405,1396],{"class":350},[336,1407,1408],{"class":544},"Price",[336,1410,541],{"class":350},[336,1412,1413],{"class":350},"},",[336,1415,1416],{"class":350}," [][]",[336,1418,1381],{"class":1380},[336,1420,366],{"class":350},[336,1422,1424,1427,1429,1432,1434,1436,1438,1441,1443,1445,1447,1450,1452],{"class":338,"line":1423},31,[336,1425,1426],{"class":350},"                    {",[336,1428,541],{"class":350},[336,1430,1431],{"class":544},"Widget A",[336,1433,541],{"class":350},[336,1435,351],{"class":350},[336,1437,1396],{"class":350},[336,1439,1440],{"class":544},"2",[336,1442,541],{"class":350},[336,1444,351],{"class":350},[336,1446,1396],{"class":350},[336,1448,1449],{"class":544},"¥1,000",[336,1451,541],{"class":350},[336,1453,1454],{"class":350},"},\n",[336,1456,1458,1460,1462,1465,1467,1469,1471,1474,1476,1478,1480,1483,1485],{"class":338,"line":1457},32,[336,1459,1426],{"class":350},[336,1461,541],{"class":350},[336,1463,1464],{"class":544},"Widget B",[336,1466,541],{"class":350},[336,1468,351],{"class":350},[336,1470,1396],{"class":350},[336,1472,1473],{"class":544},"1",[336,1475,541],{"class":350},[336,1477,351],{"class":350},[336,1479,1396],{"class":350},[336,1481,1482],{"class":544},"¥2,500",[336,1484,541],{"class":350},[336,1486,1454],{"class":350},[336,1488,1490],{"class":338,"line":1489},33,[336,1491,1492],{"class":350},"                })\n",[336,1494,1496],{"class":338,"line":1495},34,[336,1497,1165],{"class":350},[336,1499,1501],{"class":338,"line":1500},35,[336,1502,1265],{"class":350},[336,1504,1506],{"class":338,"line":1505},36,[336,1507,911],{"emptyLinePlaceholder":910},[336,1509,1511,1513,1515,1517,1519,1521,1523,1525,1527,1529],{"class":338,"line":1510},37,[336,1512,1061],{"class":346},[336,1514,375],{"class":350},[336,1516,1280],{"class":378},[336,1518,382],{"class":350},[336,1520,999],{"class":346},[336,1522,375],{"class":350},[336,1524,1289],{"class":378},[336,1526,382],{"class":350},[336,1528,1294],{"class":385},[336,1530,1016],{"class":350},[336,1532,1534],{"class":338,"line":1533},38,[336,1535,911],{"emptyLinePlaceholder":910},[336,1537,1539],{"class":338,"line":1538},39,[336,1540,1541],{"class":606},"        // KPI strip: four equal columns of 3-span each.\n",[336,1543,1545,1548,1550,1553,1556,1558,1561,1563],{"class":338,"line":1544},40,[336,1546,1547],{"class":346},"        kpis ",[336,1549,357],{"class":350},[336,1551,1552],{"class":350}," []struct{",[336,1554,1555],{"class":346}," label",[336,1557,351],{"class":350},[336,1559,1560],{"class":346}," value ",[336,1562,1381],{"class":1380},[336,1564,1565],{"class":350}," }{\n",[336,1567,1569,1572,1574,1577,1579,1581,1583,1586,1588],{"class":338,"line":1568},41,[336,1570,1571],{"class":350},"            {",[336,1573,541],{"class":350},[336,1575,1576],{"class":544},"Subtotal",[336,1578,541],{"class":350},[336,1580,351],{"class":350},[336,1582,1396],{"class":350},[336,1584,1585],{"class":544},"¥4,500",[336,1587,541],{"class":350},[336,1589,1454],{"class":350},[336,1591,1593,1595,1597,1600,1602,1604,1606,1609,1611],{"class":338,"line":1592},42,[336,1594,1571],{"class":350},[336,1596,541],{"class":350},[336,1598,1599],{"class":544},"Tax (10%)",[336,1601,541],{"class":350},[336,1603,351],{"class":350},[336,1605,1396],{"class":350},[336,1607,1608],{"class":544},"¥450",[336,1610,541],{"class":350},[336,1612,1454],{"class":350},[336,1614,1616,1618,1620,1623,1625,1627,1629,1632,1634],{"class":338,"line":1615},43,[336,1617,1571],{"class":350},[336,1619,541],{"class":350},[336,1621,1622],{"class":544},"Shipping",[336,1624,541],{"class":350},[336,1626,351],{"class":350},[336,1628,1396],{"class":350},[336,1630,1631],{"class":544},"¥0",[336,1633,541],{"class":350},[336,1635,1454],{"class":350},[336,1637,1639,1641,1643,1646,1648,1650,1652,1655,1657],{"class":338,"line":1638},44,[336,1640,1571],{"class":350},[336,1642,541],{"class":350},[336,1644,1645],{"class":544},"Total",[336,1647,541],{"class":350},[336,1649,351],{"class":350},[336,1651,1396],{"class":350},[336,1653,1654],{"class":544},"¥4,950",[336,1656,541],{"class":350},[336,1658,1454],{"class":350},[336,1660,1662],{"class":338,"line":1661},45,[336,1663,1664],{"class":350},"        }\n",[336,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688],{"class":338,"line":1667},46,[336,1669,1061],{"class":346},[336,1671,375],{"class":350},[336,1673,478],{"class":378},[336,1675,481],{"class":350},[336,1677,484],{"class":394},[336,1679,398],{"class":350},[336,1681,402],{"class":401},[336,1683,375],{"class":350},[336,1685,493],{"class":401},[336,1687,410],{"class":350},[336,1689,413],{"class":350},[336,1691,1693,1696,1698,1700,1703,1705,1707,1710],{"class":338,"line":1692},47,[336,1694,1695],{"class":342},"            for",[336,1697,347],{"class":346},[336,1699,351],{"class":350},[336,1701,1702],{"class":346}," k ",[336,1704,357],{"class":350},[336,1706,360],{"class":342},[336,1708,1709],{"class":346}," kpis ",[336,1711,366],{"class":350},[336,1713,1715,1718,1720],{"class":338,"line":1714},48,[336,1716,1717],{"class":346},"                k ",[336,1719,357],{"class":350},[336,1721,1722],{"class":346}," k\n",[336,1724,1726,1729,1731,1733,1735,1737,1739,1741,1743,1745,1747,1749,1751,1753],{"class":338,"line":1725},49,[336,1727,1728],{"class":346},"                r",[336,1730,375],{"class":350},[336,1732,379],{"class":378},[336,1734,382],{"class":350},[336,1736,386],{"class":385},[336,1738,351],{"class":350},[336,1740,391],{"class":350},[336,1742,395],{"class":394},[336,1744,398],{"class":350},[336,1746,402],{"class":401},[336,1748,375],{"class":350},[336,1750,407],{"class":401},[336,1752,410],{"class":350},[336,1754,413],{"class":350},[336,1756,1758,1761,1763,1765,1767,1770,1772,1775,1777,1779,1781,1783,1785,1787],{"class":338,"line":1757},50,[336,1759,1760],{"class":346},"                    c",[336,1762,375],{"class":350},[336,1764,424],{"class":378},[336,1766,382],{"class":350},[336,1768,1769],{"class":346},"k",[336,1771,375],{"class":350},[336,1773,1774],{"class":346},"label",[336,1776,351],{"class":350},[336,1778,989],{"class":346},[336,1780,375],{"class":350},[336,1782,1141],{"class":378},[336,1784,382],{"class":350},[336,1786,1179],{"class":385},[336,1788,1016],{"class":350},[336,1790,1792,1794,1796,1798,1800,1802,1804,1807,1809,1811,1813,1815,1817,1820,1822,1824,1826,1828],{"class":338,"line":1791},51,[336,1793,1760],{"class":346},[336,1795,375],{"class":350},[336,1797,424],{"class":378},[336,1799,382],{"class":350},[336,1801,1769],{"class":346},[336,1803,375],{"class":350},[336,1805,1806],{"class":346},"value",[336,1808,351],{"class":350},[336,1810,989],{"class":346},[336,1812,375],{"class":350},[336,1814,1141],{"class":378},[336,1816,382],{"class":350},[336,1818,1819],{"class":385},"14",[336,1821,1149],{"class":350},[336,1823,989],{"class":346},[336,1825,375],{"class":350},[336,1827,1156],{"class":378},[336,1829,1159],{"class":350},[336,1831,1833],{"class":338,"line":1832},52,[336,1834,1492],{"class":350},[336,1836,1838],{"class":338,"line":1837},53,[336,1839,1840],{"class":350},"            }\n",[336,1842,1844],{"class":338,"line":1843},54,[336,1845,1265],{"class":350},[336,1847,1849],{"class":338,"line":1848},55,[336,1850,443],{"class":350},[336,1852,1854],{"class":338,"line":1853},56,[336,1855,911],{"emptyLinePlaceholder":910},[336,1857,1859,1862,1864,1867,1869,1872,1874,1877,1879,1881,1884,1886],{"class":338,"line":1858},57,[336,1860,1861],{"class":346},"    f",[336,1863,351],{"class":350},[336,1865,1866],{"class":346}," _ ",[336,1868,357],{"class":350},[336,1870,1871],{"class":346}," os",[336,1873,375],{"class":350},[336,1875,1876],{"class":378},"Create",[336,1878,382],{"class":350},[336,1880,541],{"class":350},[336,1882,1883],{"class":544},"invoice.pdf",[336,1885,541],{"class":350},[336,1887,437],{"class":350},[336,1889,1891,1894,1897,1899,1902],{"class":338,"line":1890},58,[336,1892,1893],{"class":342},"    defer",[336,1895,1896],{"class":346}," f",[336,1898,375],{"class":350},[336,1900,1901],{"class":378},"Close",[336,1903,1904],{"class":350},"()\n",[336,1906,1908,1910,1912,1915,1917,1920],{"class":338,"line":1907},59,[336,1909,1027],{"class":346},[336,1911,375],{"class":350},[336,1913,1914],{"class":378},"Render",[336,1916,382],{"class":350},[336,1918,1919],{"class":346},"f",[336,1921,437],{"class":350},[336,1923,1925],{"class":338,"line":1924},60,[336,1926,449],{"class":350},[19,1928,1929,1930,1933,1934,1936],{},"That's a real, working program. ",[42,1931,1932],{},"go get github.com/gpdf-dev/gpdf"," and run it; ",[42,1935,1883],{}," lands in your working directory. Render time on an M1: about 130 µs.",[14,1938,1940],{"id":1939},"when-the-integer-model-is-wrong","When the integer model is wrong",[19,1942,1943],{},"The integer-twelfths model is genuinely the wrong choice in two cases. Honest list, since you'll hit at least one eventually:",[51,1945,1946,1963],{},[54,1947,1948,1951,1952,1954,1955,1958,1959,1962],{},[22,1949,1950],{},"You need exact pixel-perfect widths."," \"This column must be exactly 73.5pt wide.\" ",[42,1953,872],{}," won't get you there because ",[42,1956,1957],{},"73.5/total*12"," is rarely an integer. Use ",[42,1960,1961],{},"page.Absolute(...)"," for the few elements that need fixed coordinates and let the grid handle everything else. Mixing both is fine; they live on the same page.",[54,1964,1965,1968],{},[22,1966,1967],{},"You need newspaper-style column flow."," A paragraph that fills one column and continues into the next. The grid does not do this. We don't have a column-flow text engine yet. If you need it, file an issue — we know it's missing.",[19,1970,1971],{},"For everything else — invoices, reports, contracts, brochures, decks — the 12-grid is a tighter fit than CSS, not a looser one.",[14,1973,1975],{"id":1974},"frequently-asked-questions","Frequently asked questions",[19,1977,1978,1981,1982,1985],{},[22,1979,1980],{},"Q: Can I change the 12 to something else, like 24?","\nNo. ",[42,1983,1984],{},"gridColumns"," is a constant. Changing it would invalidate every existing template. We picked 12 once and committed.",[19,1987,1988,1991,1992,1995,1996,1999,2000,2003],{},[22,1989,1990],{},"Q: What if I want to nest a row inside a column?","\nYou can. ",[42,1993,1994],{},"c.AutoRow(...)"," creates a sub-row inside the column. Spans inside the sub-row are 1–12 of the ",[30,1997,1998],{},"parent column's"," width, not the page width. Nesting composes cleanly because every level is just ",[42,2001,2002],{},"Pct(span/12 * 100)"," of its parent.",[19,2005,2006,2009,2010,2013],{},[22,2007,2008],{},"Q: Does this work for landscape pages?","\nYes. The grid is page-size agnostic. ",[42,2011,2012],{},"r.Col(6, ...)"," is half the row whether the row is 210mm wide (A4 portrait) or 297mm wide (A4 landscape).",[19,2015,2016,2023,2024,2027],{},[22,2017,2018,2019,2022],{},"Q: Why is there no ",[42,2020,2021],{},"r.Col2(span, span, fn1, fn2)"," shortcut for two-column rows?","\nBecause saving one line by adding API surface is a bad trade. If you find yourself repeating a row pattern, write a Go function that takes a ",[42,2025,2026],{},"*template.PageBuilder"," and adds it. The grid stays minimal so user-level patterns can grow without conflict.",[19,2029,2030,2037],{},[22,2031,2032,2033,2036],{},"Q: What about CSS Grid features like ",[42,2034,2035],{},"grid-area"," and named lines?","\nNot in gpdf, and not on the roadmap. The cost-benefit doesn't pencil out for PDFs.",[14,2039,2041],{"id":2040},"recap","Recap",[19,2043,2044,2045,2048],{},"The 12-column grid is the smallest layout primitive that handles the splits real documents need. We borrowed the number from Bootstrap, kept the integer model, and dropped breakpoints, gutters, order, auto-fill, span-sum enforcement, and the rest of the responsive-web baggage. What's left is one constant, one builder method, and one width formula — about 30 lines of Go. It composes through nesting, plays nicely with ",[42,2046,2047],{},"Absolute"," for the few cases the grid can't express, and never silently rebalances what you wrote.",[14,2050,2052],{"id":2051},"try-gpdf","Try gpdf",[19,2054,2055],{},"gpdf is a Go PDF library — MIT, zero dependencies, CJK out of the box.",[327,2057,2061],{"className":2058,"code":2059,"language":2060,"meta":332,"style":332},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash",[42,2062,2063],{"__ignoreMap":332},[336,2064,2065,2067,2070],{"class":338,"line":339},[336,2066,331],{"class":401},[336,2068,2069],{"class":544}," get",[336,2071,2072],{"class":544}," github.com/gpdf-dev/gpdf\n",[19,2074,2075,2081,2082],{},[78,2076,2080],{"href":2077,"rel":2078},"https://github.com/gpdf-dev/gpdf",[2079],"nofollow","⭐ Star on GitHub"," · ",[78,2083,2086],{"href":2084,"rel":2085},"https://gpdf.dev/docs/quickstart",[2079],"Read the docs",[14,2088,2090],{"id":2089},"what-to-read-next","What to read next",[135,2092,2093,2099,2105],{},[54,2094,2095,2098],{},[78,2096,2097],{"href":80},"How does the 12-column grid work in gpdf?"," — the recipe version, with more code patterns",[54,2100,2101,2104],{},[78,2102,2103],{"href":883},"Why gpdf is 10× faster than alternatives"," — internals of the rendering pipeline",[54,2106,2107,2111],{},[78,2108,2110],{"href":2109},"/docs/quickstart","Quickstart"," — generate your first PDF in five minutes",[2113,2114,2115],"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":332,"searchDepth":369,"depth":369,"links":2117},[2118,2119,2120,2121,2122,2123,2131,2132,2133,2134,2135,2136,2137,2138],{"id":16,"depth":369,"text":17},{"id":36,"depth":369,"text":37},{"id":88,"depth":369,"text":89},{"id":129,"depth":369,"text":130},{"id":197,"depth":369,"text":198},{"id":230,"depth":369,"text":231,"children":2124},[2125,2126,2127,2128,2129,2130],{"id":235,"depth":416,"text":236},{"id":274,"depth":416,"text":275},{"id":291,"depth":416,"text":292},{"id":306,"depth":416,"text":307},{"id":455,"depth":416,"text":456},{"id":647,"depth":416,"text":648},{"id":667,"depth":369,"text":668},{"id":838,"depth":369,"text":839},{"id":888,"depth":369,"text":889},{"id":1939,"depth":369,"text":1940},{"id":1974,"depth":369,"text":1975},{"id":2040,"depth":369,"text":2041},{"id":2051,"depth":369,"text":2052},{"id":2089,"depth":369,"text":2090},"2026-04-29","gpdf borrows the 12-column grid from Bootstrap, but PDFs aren't web pages. Here's why 12 still works, and what we threw out: breakpoints, gutters, order.",false,"md",null,{},"/blog/bootstrap-grid-thinking-for-pdf",{"title":5,"description":2140},"blog/017.bootstrap-grid-thinking-for-pdf",[2149,2150,2151],"internals","templates","comparison","4cIC9au31OgjbmXd0g_cJuPcut1Bt4nbixIgadPfl4Q",1779199010204]