[{"data":1,"prerenderedAt":2336},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns":3,"surround:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns":2308,"ancestors:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns":2316,"children:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns":2321,"related:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns":2322},{"id":4,"title":5,"body":6,"dateModified":2274,"datePublished":2274,"description":2275,"extension":2276,"faq":2277,"keyword":2286,"kind":2287,"meta":2288,"navigation":112,"path":2297,"seo":2298,"slug":2301,"stem":2302,"topics":2303,"type":2306,"__hash__":2307},"docs\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns\u002Findex.md","Check a PDF Export for Cut-Off Columns",{"type":7,"value":8,"toc":2263},"minimark",[9,24,29,57,69,73,76,739,746,750,757,1306,1309,1453,1457,1465,1693,1714,1833,1913,1917,1920,2116,2124,2135,2139,2179,2183,2198,2202,2208,2214,2220,2226,2230,2259],[10,11,12,13,17,18,23],"p",{},"The most common complaint about an automated PDF report is not wrong numbers — it is a report that is hard to read: the last four columns printed on separate pages after all the rows, so totals sit three pages away from the customer names they belong to; a revenue column showing ",[14,15,16],"code",{},"########"," because the figures grew wider than the column; or, after someone \"fixed\" that with fit-to-page, a table shrunk to six-point type. Each looks fine in the workbook on screen and only goes wrong in the PDF, which in an unattended job nobody looks at before it is emailed out. This page reads the PDF back with pdfplumber and checks for all three problems, then fixes the workbook's page setup with openpyxl and shows the check passing. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002F","Exporting Excel Reports to PDF",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pdfplumber\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," openpyxl",[39,55,56],{"class":49}," pdfplumber\n",[10,58,59,60,63,64,68],{},"LibreOffice (",[14,61,62],{},"soffice",") does the conversion, as in ",[19,65,67],{"href":66},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice\u002F","Convert Excel to PDF on Linux with LibreOffice","; the checks work on any PDF, whichever tool produced it. pdfplumber gives the position and font size of every word on a page, which is what the checks need.",[25,70,72],{"id":71},"step-1-a-report-that-exports-badly","Step 1 — A report that exports badly",[10,74,75],{},"A fourteen-column sales sheet, portrait page, no scaling — the defaults — with the header row set to repeat on each page and a net-amount column that is a little too narrow for its largest values:",[30,77,81],{"className":78,"code":79,"language":80,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import subprocess\nfrom openpyxl import Workbook\n\nCOLUMNS = [\"Order\", \"Date\", \"Customer\", \"Region\", \"Product\", \"Category\", \"Qty\",\n           \"Price\", \"Discount\", \"Net\", \"Tax\", \"Gross\", \"Rep\", \"Status\"]\nWIDTHS = [10, 11, 24, 9, 14, 9, 6, 10, 9, 8, 7, 14, 10, 8]\n\nwb = Workbook()\nws = wb.active\nws.title = \"Sales\"\nws.append(COLUMNS)\nfor i in range(60):\n    ws.append([f\"SO-{4000 + i}\", f\"2026-09-{i % 28 + 1:02d}\", f\"Customer name {i} Ltd\", \"North\", f\"Product {i}\", \"Cat\",\n               i + 1, 12.5, 0.05, 12345.891 * (i + 1), 0.2, 15000.5 * (i + 1), \"A. Rep\", \"Open\"])\n    ws.cell(ws.max_row, 10).number_format = \"#,##0.00\"\nfor letter, width in zip(\"ABCDEFGHIJKLMN\", WIDTHS):\n    ws.column_dimensions[letter].width = width\nws.print_title_rows = \"1:1\"\nwb.save(\"sales.xlsx\")\n\ndef export(xlsx):\n    subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"pdf\", \"--outdir\", \"pdf\", xlsx], check=True, capture_output=True, timeout=180)\n    return f\"pdf\u002F{xlsx.rsplit('.', 1)[0]}.pdf\"\n\nprint(export(\"sales.xlsx\"))\n","python",[14,82,83,93,107,114,164,203,277,282,294,305,316,327,351,456,524,540,564,575,586,597,602,615,681,720,725],{"__ignoreMap":35},[39,84,85,89],{"class":41,"line":42},[39,86,88],{"class":87},"s-kum","import",[39,90,92],{"class":91},"skGVy"," subprocess\n",[39,94,96,99,102,104],{"class":41,"line":95},2,[39,97,98],{"class":87},"from",[39,100,101],{"class":91}," openpyxl ",[39,103,88],{"class":87},[39,105,106],{"class":91}," Workbook\n",[39,108,110],{"class":41,"line":109},3,[39,111,113],{"emptyLinePlaceholder":112},true,"\n",[39,115,117,121,124,127,130,133,136,138,141,143,146,148,151,153,156,158,161],{"class":41,"line":116},4,[39,118,120],{"class":119},"sP0c6","COLUMNS",[39,122,123],{"class":87}," =",[39,125,126],{"class":91}," [",[39,128,129],{"class":49},"\"Order\"",[39,131,132],{"class":91},", ",[39,134,135],{"class":49},"\"Date\"",[39,137,132],{"class":91},[39,139,140],{"class":49},"\"Customer\"",[39,142,132],{"class":91},[39,144,145],{"class":49},"\"Region\"",[39,147,132],{"class":91},[39,149,150],{"class":49},"\"Product\"",[39,152,132],{"class":91},[39,154,155],{"class":49},"\"Category\"",[39,157,132],{"class":91},[39,159,160],{"class":49},"\"Qty\"",[39,162,163],{"class":91},",\n",[39,165,167,170,172,175,177,180,182,185,187,190,192,195,197,200],{"class":41,"line":166},5,[39,168,169],{"class":49},"           \"Price\"",[39,171,132],{"class":91},[39,173,174],{"class":49},"\"Discount\"",[39,176,132],{"class":91},[39,178,179],{"class":49},"\"Net\"",[39,181,132],{"class":91},[39,183,184],{"class":49},"\"Tax\"",[39,186,132],{"class":91},[39,188,189],{"class":49},"\"Gross\"",[39,191,132],{"class":91},[39,193,194],{"class":49},"\"Rep\"",[39,196,132],{"class":91},[39,198,199],{"class":49},"\"Status\"",[39,201,202],{"class":91},"]\n",[39,204,206,209,211,213,216,218,221,223,226,228,231,233,236,238,240,242,245,247,249,251,253,255,258,260,263,265,267,269,271,273,275],{"class":41,"line":205},6,[39,207,208],{"class":119},"WIDTHS",[39,210,123],{"class":87},[39,212,126],{"class":91},[39,214,215],{"class":119},"10",[39,217,132],{"class":91},[39,219,220],{"class":119},"11",[39,222,132],{"class":91},[39,224,225],{"class":119},"24",[39,227,132],{"class":91},[39,229,230],{"class":119},"9",[39,232,132],{"class":91},[39,234,235],{"class":119},"14",[39,237,132],{"class":91},[39,239,230],{"class":119},[39,241,132],{"class":91},[39,243,244],{"class":119},"6",[39,246,132],{"class":91},[39,248,215],{"class":119},[39,250,132],{"class":91},[39,252,230],{"class":119},[39,254,132],{"class":91},[39,256,257],{"class":119},"8",[39,259,132],{"class":91},[39,261,262],{"class":119},"7",[39,264,132],{"class":91},[39,266,235],{"class":119},[39,268,132],{"class":91},[39,270,215],{"class":119},[39,272,132],{"class":91},[39,274,257],{"class":119},[39,276,202],{"class":91},[39,278,280],{"class":41,"line":279},7,[39,281,113],{"emptyLinePlaceholder":112},[39,283,285,288,291],{"class":41,"line":284},8,[39,286,287],{"class":91},"wb ",[39,289,290],{"class":87},"=",[39,292,293],{"class":91}," Workbook()\n",[39,295,297,300,302],{"class":41,"line":296},9,[39,298,299],{"class":91},"ws ",[39,301,290],{"class":87},[39,303,304],{"class":91}," wb.active\n",[39,306,308,311,313],{"class":41,"line":307},10,[39,309,310],{"class":91},"ws.title ",[39,312,290],{"class":87},[39,314,315],{"class":49}," \"Sales\"\n",[39,317,319,322,324],{"class":41,"line":318},11,[39,320,321],{"class":91},"ws.append(",[39,323,120],{"class":119},[39,325,326],{"class":91},")\n",[39,328,330,333,336,339,342,345,348],{"class":41,"line":329},12,[39,331,332],{"class":87},"for",[39,334,335],{"class":91}," i ",[39,337,338],{"class":87},"in",[39,340,341],{"class":119}," range",[39,343,344],{"class":91},"(",[39,346,347],{"class":119},"60",[39,349,350],{"class":91},"):\n",[39,352,354,357,360,363,367,370,373,376,379,382,384,386,389,391,394,397,400,402,405,408,410,412,414,416,419,421,424,426,429,431,434,436,438,441,443,445,447,449,451,454],{"class":41,"line":353},13,[39,355,356],{"class":91},"    ws.append([",[39,358,359],{"class":87},"f",[39,361,362],{"class":49},"\"SO-",[39,364,366],{"class":365},"sSjpA","{",[39,368,369],{"class":119},"4000",[39,371,372],{"class":87}," +",[39,374,375],{"class":91}," i",[39,377,378],{"class":365},"}",[39,380,381],{"class":49},"\"",[39,383,132],{"class":91},[39,385,359],{"class":87},[39,387,388],{"class":49},"\"2026-09-",[39,390,366],{"class":365},[39,392,393],{"class":91},"i ",[39,395,396],{"class":87},"%",[39,398,399],{"class":119}," 28",[39,401,372],{"class":87},[39,403,404],{"class":119}," 1",[39,406,407],{"class":87},":02d",[39,409,378],{"class":365},[39,411,381],{"class":49},[39,413,132],{"class":91},[39,415,359],{"class":87},[39,417,418],{"class":49},"\"Customer name ",[39,420,366],{"class":365},[39,422,423],{"class":91},"i",[39,425,378],{"class":365},[39,427,428],{"class":49}," Ltd\"",[39,430,132],{"class":91},[39,432,433],{"class":49},"\"North\"",[39,435,132],{"class":91},[39,437,359],{"class":87},[39,439,440],{"class":49},"\"Product ",[39,442,366],{"class":365},[39,444,423],{"class":91},[39,446,378],{"class":365},[39,448,381],{"class":49},[39,450,132],{"class":91},[39,452,453],{"class":49},"\"Cat\"",[39,455,163],{"class":91},[39,457,459,462,465,467,469,472,474,477,479,482,485,488,490,492,495,498,500,503,505,507,509,511,513,516,518,521],{"class":41,"line":458},14,[39,460,461],{"class":91},"               i ",[39,463,464],{"class":87},"+",[39,466,404],{"class":119},[39,468,132],{"class":91},[39,470,471],{"class":119},"12.5",[39,473,132],{"class":91},[39,475,476],{"class":119},"0.05",[39,478,132],{"class":91},[39,480,481],{"class":119},"12345.891",[39,483,484],{"class":87}," *",[39,486,487],{"class":91}," (i ",[39,489,464],{"class":87},[39,491,404],{"class":119},[39,493,494],{"class":91},"), ",[39,496,497],{"class":119},"0.2",[39,499,132],{"class":91},[39,501,502],{"class":119},"15000.5",[39,504,484],{"class":87},[39,506,487],{"class":91},[39,508,464],{"class":87},[39,510,404],{"class":119},[39,512,494],{"class":91},[39,514,515],{"class":49},"\"A. Rep\"",[39,517,132],{"class":91},[39,519,520],{"class":49},"\"Open\"",[39,522,523],{"class":91},"])\n",[39,525,527,530,532,535,537],{"class":41,"line":526},15,[39,528,529],{"class":91},"    ws.cell(ws.max_row, ",[39,531,215],{"class":119},[39,533,534],{"class":91},").number_format ",[39,536,290],{"class":87},[39,538,539],{"class":49}," \"#,##0.00\"\n",[39,541,543,545,548,550,553,555,558,560,562],{"class":41,"line":542},16,[39,544,332],{"class":87},[39,546,547],{"class":91}," letter, width ",[39,549,338],{"class":87},[39,551,552],{"class":119}," zip",[39,554,344],{"class":91},[39,556,557],{"class":49},"\"ABCDEFGHIJKLMN\"",[39,559,132],{"class":91},[39,561,208],{"class":119},[39,563,350],{"class":91},[39,565,567,570,572],{"class":41,"line":566},17,[39,568,569],{"class":91},"    ws.column_dimensions[letter].width ",[39,571,290],{"class":87},[39,573,574],{"class":91}," width\n",[39,576,578,581,583],{"class":41,"line":577},18,[39,579,580],{"class":91},"ws.print_title_rows ",[39,582,290],{"class":87},[39,584,585],{"class":49}," \"1:1\"\n",[39,587,589,592,595],{"class":41,"line":588},19,[39,590,591],{"class":91},"wb.save(",[39,593,594],{"class":49},"\"sales.xlsx\"",[39,596,326],{"class":91},[39,598,600],{"class":41,"line":599},20,[39,601,113],{"emptyLinePlaceholder":112},[39,603,605,608,612],{"class":41,"line":604},21,[39,606,607],{"class":87},"def",[39,609,611],{"class":610},"s_Opv"," export",[39,613,614],{"class":91},"(xlsx):\n",[39,616,618,621,624,626,629,631,634,636,639,641,644,646,648,651,655,657,660,662,665,667,669,671,674,676,679],{"class":41,"line":617},22,[39,619,620],{"class":91},"    subprocess.run([",[39,622,623],{"class":49},"\"soffice\"",[39,625,132],{"class":91},[39,627,628],{"class":49},"\"--headless\"",[39,630,132],{"class":91},[39,632,633],{"class":49},"\"--convert-to\"",[39,635,132],{"class":91},[39,637,638],{"class":49},"\"pdf\"",[39,640,132],{"class":91},[39,642,643],{"class":49},"\"--outdir\"",[39,645,132],{"class":91},[39,647,638],{"class":49},[39,649,650],{"class":91},", xlsx], ",[39,652,654],{"class":653},"sa561","check",[39,656,290],{"class":87},[39,658,659],{"class":119},"True",[39,661,132],{"class":91},[39,663,664],{"class":653},"capture_output",[39,666,290],{"class":87},[39,668,659],{"class":119},[39,670,132],{"class":91},[39,672,673],{"class":653},"timeout",[39,675,290],{"class":87},[39,677,678],{"class":119},"180",[39,680,326],{"class":91},[39,682,684,687,690,693,695,698,701,703,706,709,712,715,717],{"class":41,"line":683},23,[39,685,686],{"class":87},"    return",[39,688,689],{"class":87}," f",[39,691,692],{"class":49},"\"pdf\u002F",[39,694,366],{"class":365},[39,696,697],{"class":91},"xlsx.rsplit(",[39,699,700],{"class":49},"'.'",[39,702,132],{"class":91},[39,704,705],{"class":119},"1",[39,707,708],{"class":91},")[",[39,710,711],{"class":119},"0",[39,713,714],{"class":91},"]",[39,716,378],{"class":365},[39,718,719],{"class":49},".pdf\"\n",[39,721,723],{"class":41,"line":722},24,[39,724,113],{"emptyLinePlaceholder":112},[39,726,728,731,734,736],{"class":41,"line":727},25,[39,729,730],{"class":119},"print",[39,732,733],{"class":91},"(export(",[39,735,594],{"class":49},[39,737,738],{"class":91},"))\n",[10,740,741,742,745],{},"Nothing here is unusual, and on screen the sheet looks fine. Repeating the header row with ",[14,743,744],{},"print_title_rows"," is good practice and also makes the check below easier, since every page then starts with the names of the columns it holds.",[25,747,749],{"id":748},"step-2-read-the-pdf-back-and-check-it","Step 2 — Read the PDF back and check it",[10,751,752,753,756],{},"Three checks, all based on the words pdfplumber finds on each page: which columns' headers are on the page (the top line of words), whether any cell shows ",[14,754,755],{},"###",", and the smallest font size used:",[30,758,760],{"className":78,"code":759,"language":80,"meta":35,"style":35},"import pdfplumber\n\ndef check_pdf(path, columns, min_font=7.0):\n    problems, summary = [], []\n    with pdfplumber.open(path) as pdf:\n        for n, page in enumerate(pdf.pages, start=1):\n            words = page.extract_words(extra_attrs=[\"size\"])\n            top = min(w[\"top\"] for w in words)\n            header = [w[\"text\"] for w in words if abs(w[\"top\"] - top) \u003C 2]\n            hashes = sum(\"###\" in w[\"text\"] for w in words)\n            smallest = min(w[\"size\"] for w in words)\n            summary.append((n, header[0], header[-1], hashes, round(smallest, 1)))\n            if header[0] != columns[0]:\n                problems.append(f\"page {n}: columns {header[0]}–{header[-1]} printed apart from {columns[0]}\")\n            if hashes:\n                problems.append(f\"page {n}: ### in {hashes} places (column too narrow)\")\n            if smallest \u003C min_font:\n                problems.append(f\"page {n}: text shrunk to {smallest:.1f} pt\")\n    return len(pdf.pages), summary, problems\n\npages, summary, problems = check_pdf(\"pdf\u002Fsales.pdf\", COLUMNS)\nprint(pages, \"pages\")\nfor row in summary:\n    print(\"  page {}: columns {}–{}, ### found {}, smallest font {} pt\".format(*row))\nprint(\"\\n\".join(problems))\n",[14,761,762,768,772,789,799,813,838,861,890,940,973,996,1024,1047,1111,1118,1147,1159,1191,1201,1205,1224,1236,1248,1290],{"__ignoreMap":35},[39,763,764,766],{"class":41,"line":42},[39,765,88],{"class":87},[39,767,56],{"class":91},[39,769,770],{"class":41,"line":95},[39,771,113],{"emptyLinePlaceholder":112},[39,773,774,776,779,782,784,787],{"class":41,"line":109},[39,775,607],{"class":87},[39,777,778],{"class":610}," check_pdf",[39,780,781],{"class":91},"(path, columns, min_font",[39,783,290],{"class":87},[39,785,786],{"class":119},"7.0",[39,788,350],{"class":91},[39,790,791,794,796],{"class":41,"line":116},[39,792,793],{"class":91},"    problems, summary ",[39,795,290],{"class":87},[39,797,798],{"class":91}," [], []\n",[39,800,801,804,807,810],{"class":41,"line":166},[39,802,803],{"class":87},"    with",[39,805,806],{"class":91}," pdfplumber.open(path) ",[39,808,809],{"class":87},"as",[39,811,812],{"class":91}," pdf:\n",[39,814,815,818,821,823,826,829,832,834,836],{"class":41,"line":205},[39,816,817],{"class":87},"        for",[39,819,820],{"class":91}," n, page ",[39,822,338],{"class":87},[39,824,825],{"class":119}," enumerate",[39,827,828],{"class":91},"(pdf.pages, ",[39,830,831],{"class":653},"start",[39,833,290],{"class":87},[39,835,705],{"class":119},[39,837,350],{"class":91},[39,839,840,843,845,848,851,853,856,859],{"class":41,"line":279},[39,841,842],{"class":91},"            words ",[39,844,290],{"class":87},[39,846,847],{"class":91}," page.extract_words(",[39,849,850],{"class":653},"extra_attrs",[39,852,290],{"class":87},[39,854,855],{"class":91},"[",[39,857,858],{"class":49},"\"size\"",[39,860,523],{"class":91},[39,862,863,866,868,871,874,877,880,882,885,887],{"class":41,"line":284},[39,864,865],{"class":91},"            top ",[39,867,290],{"class":87},[39,869,870],{"class":119}," min",[39,872,873],{"class":91},"(w[",[39,875,876],{"class":49},"\"top\"",[39,878,879],{"class":91},"] ",[39,881,332],{"class":87},[39,883,884],{"class":91}," w ",[39,886,338],{"class":87},[39,888,889],{"class":91}," words)\n",[39,891,892,895,897,900,903,905,907,909,911,914,917,920,922,924,926,929,932,935,938],{"class":41,"line":296},[39,893,894],{"class":91},"            header ",[39,896,290],{"class":87},[39,898,899],{"class":91}," [w[",[39,901,902],{"class":49},"\"text\"",[39,904,879],{"class":91},[39,906,332],{"class":87},[39,908,884],{"class":91},[39,910,338],{"class":87},[39,912,913],{"class":91}," words ",[39,915,916],{"class":87},"if",[39,918,919],{"class":119}," abs",[39,921,873],{"class":91},[39,923,876],{"class":49},[39,925,879],{"class":91},[39,927,928],{"class":87},"-",[39,930,931],{"class":91}," top) ",[39,933,934],{"class":87},"\u003C",[39,936,937],{"class":119}," 2",[39,939,202],{"class":91},[39,941,942,945,947,950,952,955,958,961,963,965,967,969,971],{"class":41,"line":307},[39,943,944],{"class":91},"            hashes ",[39,946,290],{"class":87},[39,948,949],{"class":119}," sum",[39,951,344],{"class":91},[39,953,954],{"class":49},"\"###\"",[39,956,957],{"class":87}," in",[39,959,960],{"class":91}," w[",[39,962,902],{"class":49},[39,964,879],{"class":91},[39,966,332],{"class":87},[39,968,884],{"class":91},[39,970,338],{"class":87},[39,972,889],{"class":91},[39,974,975,978,980,982,984,986,988,990,992,994],{"class":41,"line":318},[39,976,977],{"class":91},"            smallest ",[39,979,290],{"class":87},[39,981,870],{"class":119},[39,983,873],{"class":91},[39,985,858],{"class":49},[39,987,879],{"class":91},[39,989,332],{"class":87},[39,991,884],{"class":91},[39,993,338],{"class":87},[39,995,889],{"class":91},[39,997,998,1001,1003,1006,1008,1010,1013,1016,1019,1021],{"class":41,"line":329},[39,999,1000],{"class":91},"            summary.append((n, header[",[39,1002,711],{"class":119},[39,1004,1005],{"class":91},"], header[",[39,1007,928],{"class":87},[39,1009,705],{"class":119},[39,1011,1012],{"class":91},"], hashes, ",[39,1014,1015],{"class":119},"round",[39,1017,1018],{"class":91},"(smallest, ",[39,1020,705],{"class":119},[39,1022,1023],{"class":91},")))\n",[39,1025,1026,1029,1032,1034,1036,1039,1042,1044],{"class":41,"line":353},[39,1027,1028],{"class":87},"            if",[39,1030,1031],{"class":91}," header[",[39,1033,711],{"class":119},[39,1035,879],{"class":91},[39,1037,1038],{"class":87},"!=",[39,1040,1041],{"class":91}," columns[",[39,1043,711],{"class":119},[39,1045,1046],{"class":91},"]:\n",[39,1048,1049,1052,1054,1057,1059,1062,1064,1067,1069,1072,1074,1076,1078,1081,1083,1085,1087,1089,1091,1093,1096,1098,1101,1103,1105,1107,1109],{"class":41,"line":458},[39,1050,1051],{"class":91},"                problems.append(",[39,1053,359],{"class":87},[39,1055,1056],{"class":49},"\"page ",[39,1058,366],{"class":365},[39,1060,1061],{"class":91},"n",[39,1063,378],{"class":365},[39,1065,1066],{"class":49},": columns ",[39,1068,366],{"class":365},[39,1070,1071],{"class":91},"header[",[39,1073,711],{"class":119},[39,1075,714],{"class":91},[39,1077,378],{"class":365},[39,1079,1080],{"class":49},"–",[39,1082,366],{"class":365},[39,1084,1071],{"class":91},[39,1086,928],{"class":87},[39,1088,705],{"class":119},[39,1090,714],{"class":91},[39,1092,378],{"class":365},[39,1094,1095],{"class":49}," printed apart from ",[39,1097,366],{"class":365},[39,1099,1100],{"class":91},"columns[",[39,1102,711],{"class":119},[39,1104,714],{"class":91},[39,1106,378],{"class":365},[39,1108,381],{"class":49},[39,1110,326],{"class":91},[39,1112,1113,1115],{"class":41,"line":526},[39,1114,1028],{"class":87},[39,1116,1117],{"class":91}," hashes:\n",[39,1119,1120,1122,1124,1126,1128,1130,1132,1135,1137,1140,1142,1145],{"class":41,"line":542},[39,1121,1051],{"class":91},[39,1123,359],{"class":87},[39,1125,1056],{"class":49},[39,1127,366],{"class":365},[39,1129,1061],{"class":91},[39,1131,378],{"class":365},[39,1133,1134],{"class":49},": ### in ",[39,1136,366],{"class":365},[39,1138,1139],{"class":91},"hashes",[39,1141,378],{"class":365},[39,1143,1144],{"class":49}," places (column too narrow)\"",[39,1146,326],{"class":91},[39,1148,1149,1151,1154,1156],{"class":41,"line":566},[39,1150,1028],{"class":87},[39,1152,1153],{"class":91}," smallest ",[39,1155,934],{"class":87},[39,1157,1158],{"class":91}," min_font:\n",[39,1160,1161,1163,1165,1167,1169,1171,1173,1176,1178,1181,1184,1186,1189],{"class":41,"line":577},[39,1162,1051],{"class":91},[39,1164,359],{"class":87},[39,1166,1056],{"class":49},[39,1168,366],{"class":365},[39,1170,1061],{"class":91},[39,1172,378],{"class":365},[39,1174,1175],{"class":49},": text shrunk to ",[39,1177,366],{"class":365},[39,1179,1180],{"class":91},"smallest",[39,1182,1183],{"class":87},":.1f",[39,1185,378],{"class":365},[39,1187,1188],{"class":49}," pt\"",[39,1190,326],{"class":91},[39,1192,1193,1195,1198],{"class":41,"line":588},[39,1194,686],{"class":87},[39,1196,1197],{"class":119}," len",[39,1199,1200],{"class":91},"(pdf.pages), summary, problems\n",[39,1202,1203],{"class":41,"line":599},[39,1204,113],{"emptyLinePlaceholder":112},[39,1206,1207,1210,1212,1215,1218,1220,1222],{"class":41,"line":604},[39,1208,1209],{"class":91},"pages, summary, problems ",[39,1211,290],{"class":87},[39,1213,1214],{"class":91}," check_pdf(",[39,1216,1217],{"class":49},"\"pdf\u002Fsales.pdf\"",[39,1219,132],{"class":91},[39,1221,120],{"class":119},[39,1223,326],{"class":91},[39,1225,1226,1228,1231,1234],{"class":41,"line":617},[39,1227,730],{"class":119},[39,1229,1230],{"class":91},"(pages, ",[39,1232,1233],{"class":49},"\"pages\"",[39,1235,326],{"class":91},[39,1237,1238,1240,1243,1245],{"class":41,"line":683},[39,1239,332],{"class":87},[39,1241,1242],{"class":91}," row ",[39,1244,338],{"class":87},[39,1246,1247],{"class":91}," summary:\n",[39,1249,1250,1253,1255,1258,1261,1263,1265,1267,1269,1272,1274,1277,1279,1281,1284,1287],{"class":41,"line":722},[39,1251,1252],{"class":119},"    print",[39,1254,344],{"class":91},[39,1256,1257],{"class":49},"\"  page ",[39,1259,1260],{"class":365},"{}",[39,1262,1066],{"class":49},[39,1264,1260],{"class":365},[39,1266,1080],{"class":49},[39,1268,1260],{"class":365},[39,1270,1271],{"class":49},", ### found ",[39,1273,1260],{"class":365},[39,1275,1276],{"class":49},", smallest font ",[39,1278,1260],{"class":365},[39,1280,1188],{"class":49},[39,1282,1283],{"class":91},".format(",[39,1285,1286],{"class":87},"*",[39,1288,1289],{"class":91},"row))\n",[39,1291,1292,1294,1296,1298,1301,1303],{"class":41,"line":727},[39,1293,730],{"class":119},[39,1295,344],{"class":91},[39,1297,381],{"class":49},[39,1299,1300],{"class":365},"\\n",[39,1302,381],{"class":49},[39,1304,1305],{"class":91},".join(problems))\n",[10,1307,1308],{},"The export has four pages. Pages 1 and 2 hold columns Order to Qty for all sixty rows; pages 3 and 4 hold Price to Status — so a reader matching a gross amount to its customer has to flip between page 3 and page 1. The check reports both spill pages and the hash marks in the Net column — 42 on page 3 and 18 on page 4 — because the column, eight characters wide, cannot show amounts formatted with thousands separators and two decimals. Unlike text, which spills into the empty cell next to it, a number that does not fit is replaced by hash marks, so the PDF loses the figures entirely rather than just looking untidy. Fonts are full size, because nothing is scaled. These are exactly the problems a person would see, found without anyone opening the file.",[1310,1311,1319,1320,1319,1324,1319,1328,1319,1335,1319,1345,1319,1356,1319,1363,1319,1368,1319,1374,1319,1379,1319,1382,1319,1387,1319,1392,1319,1396,1319,1400,1319,1404,1319,1407,1319,1412,1319,1419,1319,1424,1319,1427,1319,1430,1319,1434,1319,1437,1319,1440,1319,1445,1319,1449,1319,1451],"svg",{"viewBox":1312,"role":1313,"ariaLabelledBy":1314,"xmlns":1317,"style":1318},"4 0 752 142","img",[1315,1316],"co-pages-t","co-pages-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[1321,1322,1323],"title",{"id":1315},"Where the columns ended up",[1325,1326,1327],"desc",{"id":1316},"With a portrait page and no scaling, columns Order to Qty print on pages one and two for all rows, then columns Price to Status print on pages three and four. Net amounts show as hash marks because the column is too narrow for them.",[1329,1330],"rect",{"x":1331,"y":711,"width":1332,"height":1333,"fill":1334},"4","752","142","#ffffff",[1329,1336],{"x":1337,"y":1338,"width":1339,"height":1340,"rx":1341,"fill":1342,"stroke":1343,"style":1344},"20.0","16.0","153.0","110.5","12","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[1346,1347,1349],"g",{"transform":1348},"translate(84.5,37.8) scale(1.000)",[1350,1351],"path",{"d":1352,"fill":1353,"stroke":1354,"style":1355},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--teal-ink,#0b6157)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[1357,1358,1362],"text",{"x":1359,"y":1360,"style":1361},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Page 1",[1357,1364,1367],{"x":1359,"y":1365,"style":1366},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Order–Qty, first rows",[41,1369],{"x1":1370,"y1":1371,"x2":1372,"y2":1371,"stroke":1373,"style":1344},"178.0","71.2","200.0","var(--brand,#5b5cf0)",[1375,1376],"polygon",{"points":1377,"fill":1378},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[1329,1380],{"x":1381,"y":1338,"width":1339,"height":1340,"rx":1341,"fill":1342,"stroke":1343,"style":1344},"209.0",[1346,1383,1385],{"transform":1384},"translate(273.5,29.0) scale(1.000)",[1350,1386],{"d":1352,"fill":1353,"stroke":1354,"style":1355},[1357,1388,1391],{"x":1389,"y":1390,"style":1361},"285.5","75.5","Page 2",[1357,1393,1395],{"x":1389,"y":1394,"style":1366},"95.0","Order–Qty, remaining",[1357,1397,1399],{"x":1389,"y":1398,"style":1366},"112.5","rows",[41,1401],{"x1":1402,"y1":1371,"x2":1403,"y2":1371,"stroke":1373,"style":1344},"367.0","389.0",[1375,1405],{"points":1406,"fill":1378},"393.0,71.2 384.0,76.2 384.0,66.2",[1329,1408],{"x":1409,"y":1338,"width":1339,"height":1340,"rx":1341,"fill":1410,"stroke":1411,"style":1344},"398.0","#fdefd8","var(--gold,#b4740a)",[1346,1413,1415],{"transform":1414},"translate(462.5,29.0) scale(1.000)",[1350,1416],{"d":1417,"fill":1353,"stroke":1418,"style":1355},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[1357,1420,1423],{"x":1421,"y":1390,"style":1422},"474.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Page 3",[1357,1425,1426],{"x":1421,"y":1394,"style":1366},"Price–Status, ### in",[1357,1428,1429],{"x":1421,"y":1398,"style":1366},"Net",[41,1431],{"x1":1432,"y1":1371,"x2":1433,"y2":1371,"stroke":1373,"style":1344},"556.0","578.0",[1375,1435],{"points":1436,"fill":1378},"582.0,71.2 573.0,76.2 573.0,66.2",[1329,1438],{"x":1439,"y":1338,"width":1339,"height":1340,"rx":1341,"fill":1410,"stroke":1411,"style":1344},"587.0",[1346,1441,1443],{"transform":1442},"translate(651.5,29.0) scale(1.000)",[1350,1444],{"d":1417,"fill":1353,"stroke":1418,"style":1355},[1357,1446,1448],{"x":1447,"y":1390,"style":1422},"663.5","Page 4",[1357,1450,1426],{"x":1447,"y":1394,"style":1366},[1357,1452,1429],{"x":1447,"y":1398,"style":1366},[25,1454,1456],{"id":1455},"step-3-fix-the-page-setup","Step 3 — Fix the page setup",[10,1458,1459,1460,1464],{},"The usual fix is landscape orientation, ",[1461,1462,1463],"em",{},"fit all columns on one page wide"," with as many pages tall as needed, and a wider column where values overflow:",[30,1466,1468],{"className":78,"code":1467,"language":80,"meta":35,"style":35},"from openpyxl import load_workbook\nfrom openpyxl.worksheet.properties import PageSetupProperties\n\nwb = load_workbook(\"sales.xlsx\")\nws = wb[\"Sales\"]\nws.page_setup.orientation = \"landscape\"\nws.sheet_properties.pageSetUpPr = PageSetupProperties(fitToPage=True)\nws.page_setup.fitToWidth = 1\nws.page_setup.fitToHeight = 0\nws.column_dimensions[\"J\"].width = 13\nwb.save(\"sales_fixed.xlsx\")\n\npages, summary, problems = check_pdf(export(\"sales_fixed.xlsx\"), COLUMNS)\nprint(pages, \"pages\")\nfor row in summary:\n    print(\"  page {}: columns {}–{}, ### found {}, smallest font {} pt\".format(*row))\nprint(\"problems:\", problems or \"none\")\n",[14,1469,1470,1481,1493,1497,1510,1524,1534,1553,1563,1573,1589,1598,1602,1619,1629,1639,1673],{"__ignoreMap":35},[39,1471,1472,1474,1476,1478],{"class":41,"line":42},[39,1473,98],{"class":87},[39,1475,101],{"class":91},[39,1477,88],{"class":87},[39,1479,1480],{"class":91}," load_workbook\n",[39,1482,1483,1485,1488,1490],{"class":41,"line":95},[39,1484,98],{"class":87},[39,1486,1487],{"class":91}," openpyxl.worksheet.properties ",[39,1489,88],{"class":87},[39,1491,1492],{"class":91}," PageSetupProperties\n",[39,1494,1495],{"class":41,"line":109},[39,1496,113],{"emptyLinePlaceholder":112},[39,1498,1499,1501,1503,1506,1508],{"class":41,"line":116},[39,1500,287],{"class":91},[39,1502,290],{"class":87},[39,1504,1505],{"class":91}," load_workbook(",[39,1507,594],{"class":49},[39,1509,326],{"class":91},[39,1511,1512,1514,1516,1519,1522],{"class":41,"line":166},[39,1513,299],{"class":91},[39,1515,290],{"class":87},[39,1517,1518],{"class":91}," wb[",[39,1520,1521],{"class":49},"\"Sales\"",[39,1523,202],{"class":91},[39,1525,1526,1529,1531],{"class":41,"line":205},[39,1527,1528],{"class":91},"ws.page_setup.orientation ",[39,1530,290],{"class":87},[39,1532,1533],{"class":49}," \"landscape\"\n",[39,1535,1536,1539,1541,1544,1547,1549,1551],{"class":41,"line":279},[39,1537,1538],{"class":91},"ws.sheet_properties.pageSetUpPr ",[39,1540,290],{"class":87},[39,1542,1543],{"class":91}," PageSetupProperties(",[39,1545,1546],{"class":653},"fitToPage",[39,1548,290],{"class":87},[39,1550,659],{"class":119},[39,1552,326],{"class":91},[39,1554,1555,1558,1560],{"class":41,"line":284},[39,1556,1557],{"class":91},"ws.page_setup.fitToWidth ",[39,1559,290],{"class":87},[39,1561,1562],{"class":119}," 1\n",[39,1564,1565,1568,1570],{"class":41,"line":296},[39,1566,1567],{"class":91},"ws.page_setup.fitToHeight ",[39,1569,290],{"class":87},[39,1571,1572],{"class":119}," 0\n",[39,1574,1575,1578,1581,1584,1586],{"class":41,"line":307},[39,1576,1577],{"class":91},"ws.column_dimensions[",[39,1579,1580],{"class":49},"\"J\"",[39,1582,1583],{"class":91},"].width ",[39,1585,290],{"class":87},[39,1587,1588],{"class":119}," 13\n",[39,1590,1591,1593,1596],{"class":41,"line":318},[39,1592,591],{"class":91},[39,1594,1595],{"class":49},"\"sales_fixed.xlsx\"",[39,1597,326],{"class":91},[39,1599,1600],{"class":41,"line":329},[39,1601,113],{"emptyLinePlaceholder":112},[39,1603,1604,1606,1608,1611,1613,1615,1617],{"class":41,"line":353},[39,1605,1209],{"class":91},[39,1607,290],{"class":87},[39,1609,1610],{"class":91}," check_pdf(export(",[39,1612,1595],{"class":49},[39,1614,494],{"class":91},[39,1616,120],{"class":119},[39,1618,326],{"class":91},[39,1620,1621,1623,1625,1627],{"class":41,"line":458},[39,1622,730],{"class":119},[39,1624,1230],{"class":91},[39,1626,1233],{"class":49},[39,1628,326],{"class":91},[39,1630,1631,1633,1635,1637],{"class":41,"line":526},[39,1632,332],{"class":87},[39,1634,1242],{"class":91},[39,1636,338],{"class":87},[39,1638,1247],{"class":91},[39,1640,1641,1643,1645,1647,1649,1651,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671],{"class":41,"line":542},[39,1642,1252],{"class":119},[39,1644,344],{"class":91},[39,1646,1257],{"class":49},[39,1648,1260],{"class":365},[39,1650,1066],{"class":49},[39,1652,1260],{"class":365},[39,1654,1080],{"class":49},[39,1656,1260],{"class":365},[39,1658,1271],{"class":49},[39,1660,1260],{"class":365},[39,1662,1276],{"class":49},[39,1664,1260],{"class":365},[39,1666,1188],{"class":49},[39,1668,1283],{"class":91},[39,1670,1286],{"class":87},[39,1672,1289],{"class":91},[39,1674,1675,1677,1679,1682,1685,1688,1691],{"class":41,"line":566},[39,1676,730],{"class":119},[39,1678,344],{"class":91},[39,1680,1681],{"class":49},"\"problems:\"",[39,1683,1684],{"class":91},", problems ",[39,1686,1687],{"class":87},"or",[39,1689,1690],{"class":49}," \"none\"",[39,1692,326],{"class":91},[10,1694,1695,1698,1699,1702,1703,1706,1707,1709,1710,23],{},[14,1696,1697],{},"fitToPage=True"," switches the sheet from a fixed zoom to fit-to-pages mode; ",[14,1700,1701],{},"fitToWidth = 1"," asks for one page across and ",[14,1704,1705],{},"fitToHeight = 0"," means \"as many pages down as needed\" — without the zero, the whole table would be squeezed onto a single page. The fixed export has two pages, each with every column from Order to Status, no ",[14,1708,755],{}," and a smallest font of 8.8 points: LibreOffice scaled the sheet to 80%, which is still comfortably readable. More page setup options are covered in ",[19,1711,1713],{"href":1712},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Ffit-an-excel-sheet-to-one-page-in-pdf\u002F","Fit an Excel Sheet to One Page in PDF",[1310,1715,1319,1720,1319,1723,1319,1726,1319,1729,1319,1735,1319,1741,1319,1747,1319,1750,1319,1754,1319,1757,1319,1761,1319,1766,1319,1772,1319,1774,1319,1778,1319,1780,1319,1783,1319,1785,1319,1788,1319,1791,1319,1794,1319,1796,1319,1800,1319,1802,1319,1805,1319,1807,1319,1810,1319,1813,1319,1817,1319,1819,1319,1823,1319,1825,1319,1828,1319,1830],{"viewBox":1716,"role":1313,"ariaLabelledBy":1717,"xmlns":1317,"style":1318},"4 0 752 174",[1718,1719],"co-checks-t","co-checks-d",[1321,1721,1722],{"id":1718},"Three checks and what they catch",[1325,1724,1725],{"id":1719},"The header check finds pages whose first column is not the key column, which means columns spilled onto separate pages. The hash check finds numbers too wide for their column. The font check finds tables scaled down so far that they are hard to read.",[1329,1727],{"x":1331,"y":711,"width":1332,"height":1728,"fill":1334},"174",[1329,1730],{"x":1337,"y":1338,"width":1731,"height":1732,"rx":711,"fill":1334,"stroke":1733,"style":1734},"83.6","40.0","var(--line,#cdd5e6)","stroke-width:1px",[1329,1736],{"x":1737,"y":1338,"width":1738,"height":1732,"rx":711,"fill":1739,"stroke":1373,"style":1740},"103.6","212.1","#f0f4ff","stroke-width:1.2px",[1357,1742,1746],{"x":1743,"y":1744,"style":1745},"209.7","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","looks for",[1329,1748],{"x":1749,"y":1338,"width":1738,"height":1732,"rx":711,"fill":1739,"stroke":1373,"style":1740},"315.7",[1357,1751,1753],{"x":1752,"y":1744,"style":1745},"421.8","means",[1329,1755],{"x":1756,"y":1338,"width":1738,"height":1732,"rx":711,"fill":1739,"stroke":1373,"style":1740},"527.9",[1357,1758,1760],{"x":1759,"y":1744,"style":1745},"633.9","fix",[1329,1762],{"x":1337,"y":1763,"width":1731,"height":1764,"rx":711,"fill":1765,"stroke":1733,"style":1734},"56.0","34.0","#e7ebef",[1357,1767,1771],{"x":1768,"y":1769,"style":1770},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","header",[1329,1773],{"x":1737,"y":1763,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1775,1777],{"x":1743,"y":1776,"style":1366},"77.0","first column missing",[1329,1779],{"x":1749,"y":1763,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1781,1782],{"x":1752,"y":1776,"style":1366},"columns spilled",[1329,1784],{"x":1756,"y":1763,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1786,1787],{"x":1759,"y":1776,"style":1366},"landscape, fit to width",[1329,1789],{"x":1337,"y":1790,"width":1731,"height":1764,"rx":711,"fill":1765,"stroke":1733,"style":1734},"90.0",[1357,1792,755],{"x":1768,"y":1793,"style":1770},"112.0",[1329,1795],{"x":1737,"y":1790,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1797,1799],{"x":1743,"y":1798,"style":1366},"111.0","hash cells",[1329,1801],{"x":1749,"y":1790,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1803,1804],{"x":1752,"y":1798,"style":1366},"column too narrow",[1329,1806],{"x":1756,"y":1790,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1808,1809],{"x":1759,"y":1798,"style":1366},"widen or shorten format",[1329,1811],{"x":1337,"y":1812,"width":1731,"height":1764,"rx":711,"fill":1765,"stroke":1733,"style":1734},"124.0",[1357,1814,1816],{"x":1768,"y":1815,"style":1770},"146.0","font size",[1329,1818],{"x":1737,"y":1812,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1820,1822],{"x":1743,"y":1821,"style":1366},"145.0","below 7 pt",[1329,1824],{"x":1749,"y":1812,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1826,1827],{"x":1752,"y":1821,"style":1366},"over-scaled",[1329,1829],{"x":1756,"y":1812,"width":1738,"height":1764,"rx":711,"fill":1334,"stroke":1733,"style":1734},[1357,1831,1832],{"x":1759,"y":1821,"style":1366},"fewer columns or A3",[1310,1834,1319,1839,1319,1842,1319,1845,1319,1848,1319,1852,1319,1858,1319,1863,1319,1868,1319,1872,1319,1876,1319,1880,1319,1884,1319,1886,1319,1892,1319,1897,1319,1901,1319,1904,1319,1907,1319,1910],{"viewBox":1835,"role":1313,"ariaLabelledBy":1836,"xmlns":1317,"style":1318},"4 0 752 233",[1837,1838],"co-before-after-t","co-before-after-d",[1321,1840,1841],{"id":1837},"The same sheet before and after fixing the page setup",[1325,1843,1844],{"id":1838},"Before the fix the export ran to four pages, with columns Price to Status separated from the order numbers and hash marks in the Net column at full size text. After switching to landscape, fitting one page wide and widening the Net column, it has two pages with every column on each and text at 8.8 points.",[1329,1846],{"x":1331,"y":711,"width":1332,"height":1847,"fill":1334},"233",[1329,1849],{"x":1337,"y":1338,"width":1850,"height":1851,"rx":235,"fill":1410,"stroke":1411,"style":1344},"351.0","201.0",[1346,1853,1855],{"transform":1854},"translate(182.5,29.0) scale(1.083)",[1350,1856],{"d":1417,"fill":1353,"stroke":1418,"style":1857},"stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[1357,1859,1862],{"x":1860,"y":1769,"style":1861},"195.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Default page setup",[41,1864],{"x1":1865,"y1":1866,"x2":1867,"y2":1866,"stroke":1411,"style":1734},"36.0","91.0","355.0",[1357,1869,1871],{"x":1860,"y":1798,"style":1870},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","portrait",[1357,1873,1875],{"x":1860,"y":1874,"style":1870},"137.0","4 pages, columns split",[1357,1877,1879],{"x":1860,"y":1878,"style":1870},"163.0","### in Net",[1357,1881,1883],{"x":1860,"y":1882,"style":1870},"189.0","11 pt text",[1329,1885],{"x":1403,"y":1338,"width":1850,"height":1851,"rx":235,"fill":1342,"stroke":1343,"style":1344},[1346,1887,1889],{"transform":1888},"translate(551.5,29.0) scale(1.083)",[1350,1890],{"d":1891,"fill":1353,"stroke":1354,"style":1857},"M5 12.5l4.5 4.5L19 7",[1357,1893,1896],{"x":1894,"y":1769,"style":1895},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Landscape, fit to width",[41,1898],{"x1":1899,"y1":1866,"x2":1900,"y2":1866,"stroke":1343,"style":1734},"405.0","724.0",[1357,1902,1903],{"x":1894,"y":1798,"style":1870},"landscape",[1357,1905,1906],{"x":1894,"y":1874,"style":1870},"2 pages, all columns",[1357,1908,1909],{"x":1894,"y":1878,"style":1870},"no hash marks",[1357,1911,1912],{"x":1894,"y":1882,"style":1870},"8.8 pt text (80%)",[25,1914,1916],{"id":1915},"step-4-make-it-a-gate-in-the-job","Step 4 — Make it a gate in the job",[10,1918,1919],{},"The check is cheap — well under a second for a few pages — so run it on every export and refuse to send a report that fails:",[30,1921,1923],{"className":78,"code":1922,"language":80,"meta":35,"style":35},"def export_checked(xlsx, columns):\n    pdf_path = export(xlsx)\n    _, _, problems = check_pdf(pdf_path, columns)\n    if problems:\n        raise RuntimeError(f\"{pdf_path} failed layout checks:\\n  \" + \"\\n  \".join(problems))\n    return pdf_path\n\nfor name in [\"sales_fixed.xlsx\", \"sales.xlsx\"]:\n    try:\n        print(\"ok:\", export_checked(name, COLUMNS))\n    except RuntimeError as e:\n        print(str(e).splitlines()[0], f\"({len(str(e).splitlines()) - 1} problems)\")\n",[14,1924,1925,1935,1945,1955,1963,2003,2010,2014,2033,2041,2058,2071],{"__ignoreMap":35},[39,1926,1927,1929,1932],{"class":41,"line":42},[39,1928,607],{"class":87},[39,1930,1931],{"class":610}," export_checked",[39,1933,1934],{"class":91},"(xlsx, columns):\n",[39,1936,1937,1940,1942],{"class":41,"line":95},[39,1938,1939],{"class":91},"    pdf_path ",[39,1941,290],{"class":87},[39,1943,1944],{"class":91}," export(xlsx)\n",[39,1946,1947,1950,1952],{"class":41,"line":109},[39,1948,1949],{"class":91},"    _, _, problems ",[39,1951,290],{"class":87},[39,1953,1954],{"class":91}," check_pdf(pdf_path, columns)\n",[39,1956,1957,1960],{"class":41,"line":116},[39,1958,1959],{"class":87},"    if",[39,1961,1962],{"class":91}," problems:\n",[39,1964,1965,1968,1971,1973,1975,1977,1979,1982,1984,1987,1989,1992,1994,1997,1999,2001],{"class":41,"line":166},[39,1966,1967],{"class":87},"        raise",[39,1969,1970],{"class":119}," RuntimeError",[39,1972,344],{"class":91},[39,1974,359],{"class":87},[39,1976,381],{"class":49},[39,1978,366],{"class":365},[39,1980,1981],{"class":91},"pdf_path",[39,1983,378],{"class":365},[39,1985,1986],{"class":49}," failed layout checks:",[39,1988,1300],{"class":365},[39,1990,1991],{"class":49},"  \"",[39,1993,372],{"class":87},[39,1995,1996],{"class":49}," \"",[39,1998,1300],{"class":365},[39,2000,1991],{"class":49},[39,2002,1305],{"class":91},[39,2004,2005,2007],{"class":41,"line":205},[39,2006,686],{"class":87},[39,2008,2009],{"class":91}," pdf_path\n",[39,2011,2012],{"class":41,"line":279},[39,2013,113],{"emptyLinePlaceholder":112},[39,2015,2016,2018,2021,2023,2025,2027,2029,2031],{"class":41,"line":284},[39,2017,332],{"class":87},[39,2019,2020],{"class":91}," name ",[39,2022,338],{"class":87},[39,2024,126],{"class":91},[39,2026,1595],{"class":49},[39,2028,132],{"class":91},[39,2030,594],{"class":49},[39,2032,1046],{"class":91},[39,2034,2035,2038],{"class":41,"line":296},[39,2036,2037],{"class":87},"    try",[39,2039,2040],{"class":91},":\n",[39,2042,2043,2046,2048,2051,2054,2056],{"class":41,"line":307},[39,2044,2045],{"class":119},"        print",[39,2047,344],{"class":91},[39,2049,2050],{"class":49},"\"ok:\"",[39,2052,2053],{"class":91},", export_checked(name, ",[39,2055,120],{"class":119},[39,2057,738],{"class":91},[39,2059,2060,2063,2065,2068],{"class":41,"line":318},[39,2061,2062],{"class":87},"    except",[39,2064,1970],{"class":119},[39,2066,2067],{"class":87}," as",[39,2069,2070],{"class":91}," e:\n",[39,2072,2073,2075,2077,2080,2083,2085,2088,2090,2093,2095,2098,2100,2102,2105,2107,2109,2111,2114],{"class":41,"line":329},[39,2074,2045],{"class":119},[39,2076,344],{"class":91},[39,2078,2079],{"class":119},"str",[39,2081,2082],{"class":91},"(e).splitlines()[",[39,2084,711],{"class":119},[39,2086,2087],{"class":91},"], ",[39,2089,359],{"class":87},[39,2091,2092],{"class":49},"\"(",[39,2094,366],{"class":365},[39,2096,2097],{"class":119},"len",[39,2099,344],{"class":91},[39,2101,2079],{"class":119},[39,2103,2104],{"class":91},"(e).splitlines()) ",[39,2106,928],{"class":87},[39,2108,404],{"class":119},[39,2110,378],{"class":365},[39,2112,2113],{"class":49}," problems)\"",[39,2115,326],{"class":91},[10,2117,2118,2119,2123],{},"The fixed workbook passes; the original fails with its four problems listed. A failure should stop the send and alert someone, as with any other failed step — ",[19,2120,2122],{"href":2121},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fvalidate-an-excel-report-before-sending-it\u002F","Validate an Excel Report Before Sending It"," describes the same gate for the workbook's contents. The checks are deliberately simple heuristics. The header check relies on repeated print titles; for sheets without them, look for pages that contain none of the key column's values instead. The font threshold is a judgement — seven points is about the smallest people read comfortably on paper — and a table that can only fit by going smaller than that needs fewer columns, a larger paper size or splitting into two tables rather than more scaling.",[10,2125,2126,2127,2130,2131,2134],{},"It is also worth running the check on the ",[1461,2128,2129],{},"largest"," plausible input, not just this month's. Overflow depends on the data: a column wide enough for this quarter's figures fails when a large order arrives or a customer with a long name signs up. A test that builds the report from a fixture with the widest values you expect — the biggest amounts, the longest names — and runs ",[14,2132,2133],{},"check_pdf"," on the export catches the problem when the code changes, rather than on the day the data grows.",[25,2136,2138],{"id":2137},"common-pitfalls","Common pitfalls",[2140,2141,2142,2150,2159,2167,2173],"ul",{},[2143,2144,2145,2149],"li",{},[2146,2147,2148],"strong",{},"Trusting the screen view."," Overflow and spills appear only in the printed layout.",[2143,2151,2152,2158],{},[2146,2153,2154,2157],{},[14,2155,2156],{},"fitToHeight"," left at 1."," Use 0 so the table may run to as many pages as needed.",[2143,2160,2161,2166],{},[2146,2162,2163,2164,23],{},"Forgetting ",[14,2165,1697],{}," Without it the fit settings are ignored.",[2143,2168,2169,2172],{},[2146,2170,2171],{},"Fitting very wide sheets to one page."," Check the font size; split the table if it is unreadable.",[2143,2174,2175,2178],{},[2146,2176,2177],{},"No print titles."," Repeat the header row so every page is readable and checkable.",[25,2180,2182],{"id":2181},"conclusion","Conclusion",[10,2184,2185,2186,2188,2189,2191,2192,2194,2195,2197],{},"Read every PDF export back with pdfplumber before sending it: check that each page's header starts with the key column (otherwise columns spilled onto separate pages), that no word contains ",[14,2187,755],{}," (a numeric column is too narrow), and that the smallest font is still readable. Fix failures in the workbook's page setup — landscape, ",[14,2190,1546],{}," with ",[14,2193,1701],{}," and ",[14,2196,1705],{},", wider columns — and make the check a gate that stops the report from being sent.",[25,2199,2201],{"id":2200},"frequently-asked-questions","Frequently asked questions",[10,2203,2204,2207],{},[2146,2205,2206],{},"Why does my Excel PDF put some columns on separate pages?","\nThe sheet is wider than the page, so Excel and LibreOffice print the extra columns on following pages. Set fit to one page wide, and landscape orientation if needed.",[10,2209,2210,2213],{},[2146,2211,2212],{},"How do I detect ### in a PDF export with Python?","\nExtract the words on each page with pdfplumber and look for any that contain ###, which is how a number too wide for its column is printed.",[10,2215,2216,2219],{},[2146,2217,2218],{},"How do I fit all columns on one page width with openpyxl?","\nTurn on fit-to-page in the sheet properties (the fitToPage flag of PageSetupProperties), then set ws.page_setup.fitToWidth = 1 and ws.page_setup.fitToHeight = 0.",[10,2221,2222,2225],{},[2146,2223,2224],{},"How small can fit-to-page make the text?","\nExcel and LibreOffice scale down to 10 percent, far below readable; check the smallest font size in the PDF and split the table if it falls below about 7 points.",[25,2227,2229],{"id":2228},"related","Related",[2140,2231,2232,2238,2245,2252],{},[2143,2233,2234,2235,2237],{},"Up one level: ",[19,2236,22],{"href":21}," — PDFs from Excel in Python.",[2143,2239,2240,2244],{},[19,2241,2243],{"href":2242},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-print-area-and-page-setup-with-openpyxl\u002F","Set the Print Area and Page Setup with openpyxl"," — all the page setup settings.",[2143,2246,2247,2251],{},[19,2248,2250],{"href":2249},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-headers-footers-and-page-numbers-to-an-excel-pdf\u002F","Add Headers, Footers and Page Numbers to an Excel PDF"," — finishing the printed pages.",[2143,2253,2254,2258],{},[19,2255,2257],{"href":2256},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcreate-a-pdf-report-from-excel-data-with-reportlab\u002F","Create a PDF Report from Excel Data with ReportLab"," — full control of the layout.",[2260,2261,2262],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":35,"searchDepth":95,"depth":95,"links":2264},[2265,2266,2267,2268,2269,2270,2271,2272,2273],{"id":27,"depth":95,"text":28},{"id":71,"depth":95,"text":72},{"id":748,"depth":95,"text":749},{"id":1455,"depth":95,"text":1456},{"id":1915,"depth":95,"text":1916},{"id":2137,"depth":95,"text":2138},{"id":2181,"depth":95,"text":2182},{"id":2200,"depth":95,"text":2201},{"id":2228,"depth":95,"text":2229},"2026-10-02","Catch the PDF exports nobody wants to receive before they are sent: detect columns that spill onto extra pages, numbers printed as ### because a column is too narrow, and text shrunk below a readable size by fit-to-page, by reading the PDF back with pdfplumber, then fix the page setup with openpyxl and confirm the check passes.","md",[2278,2280,2282,2284],{"q":2206,"a":2279},"The sheet is wider than the page, so Excel and LibreOffice print the extra columns on following pages. Set fit to one page wide, and landscape orientation if needed.",{"q":2212,"a":2281},"Extract the words on each page with pdfplumber and look for any that contain ###, which is how a number too wide for its column is printed.",{"q":2218,"a":2283},"Turn on fit-to-page in the sheet properties (the fitToPage flag of PageSetupProperties), then set ws.page_setup.fitToWidth = 1 and ws.page_setup.fitToHeight = 0.",{"q":2224,"a":2285},"Excel and LibreOffice scale down to 10 percent, far below readable; check the smallest font size in the PDF and split the table if it falls below about 7 points.","check a pdf export for cut-off columns",null,{"breadcrumb":2289},[2290,2293,2296],{"name":2291,"item":2292},"Home","\u002F",{"name":2294,"item":2295},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns",{"title":2299,"description":2300},"Check an Excel PDF Export for Cut-Off Columns with Python","Automatically check Excel-to-PDF exports with pdfplumber: columns spilling onto extra pages, ### in narrow numeric columns, shrunken fonts, then fix with fit-to-width and landscape in openpyxl.","check-a-pdf-export-for-cut-off-columns","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns\u002Findex",[2304,2305],"pdf-export","reliable-jobs","how-to","EAsjMw3d82DcKKOfDreqiT-hhUFqWf1YZkGavwj3GDg",[2309,2312],{"title":2250,"path":2310,"stem":2311,"children":-1},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-headers-footers-and-page-numbers-to-an-excel-pdf","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-headers-footers-and-page-numbers-to-an-excel-pdf\u002Findex",{"title":2313,"path":2314,"stem":2315,"children":-1},"Convert an Excel File to PDF with Python","\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python\u002Findex",[2317,2319],{"path":2318,"title":22},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf",{"path":2320,"title":2294},"\u002Fautomating-reporting-workflows",[],[2323,2326,2329,2332,2333,2334],{"path":2324,"title":2325,"kind":2306},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python","Fill PDF Forms from Excel Rows with Python",{"path":2327,"title":2328,"kind":2306},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-pdf-per-excel-row","Generate One PDF per Excel Row",{"path":2330,"title":2331,"kind":2306},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-bookmarks-and-a-cover-page-to-an-excel-pdf-export","Add Bookmarks and a Cover Page to an Excel PDF Export",{"path":2310,"title":2250,"kind":2306},{"path":2314,"title":2313,"kind":2306},{"path":2335,"title":67,"kind":2306},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice",1790972134386]