<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: anders lee</title>
    <description>The latest articles on DEV Community by anders lee (@anders_lee_28dbcc2fd8e034).</description>
    <link>https://dev.to/anders_lee_28dbcc2fd8e034</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4175492%2F7613d15a-11ea-4474-9276-ab13c5bc91fa.png</url>
      <title>DEV Community: anders lee</title>
      <link>https://dev.to/anders_lee_28dbcc2fd8e034</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anders_lee_28dbcc2fd8e034"/>
    <language>en</language>
    <item>
      <title>What I learned turning government property tax spreadsheets into one county dataset</title>
      <dc:creator>anders lee</dc:creator>
      <pubDate>Thu, 15 Oct 2026 05:30:00 +0000</pubDate>
      <link>https://dev.to/anders_lee_28dbcc2fd8e034/what-i-learned-turning-government-property-tax-spreadsheets-into-one-county-dataset-2849</link>
      <guid>https://dev.to/anders_lee_28dbcc2fd8e034/what-i-learned-turning-government-property-tax-spreadsheets-into-one-county-dataset-2849</guid>
      <description>&lt;p&gt;I wanted a simple answer to a simple question: &lt;em&gt;how much property tax does a typical homeowner pay in each U.S. county, and what are the actual local rates behind it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The data exists, but it's spread across the Census Bureau and fifty state revenue departments, each with its own format. I started with the national picture plus three states — Texas, North Carolina and Florida — and ended up with five clean CSVs: 3,133 counties, 51 states, 4,849 Texas taxing units, 690 North Carolina county/municipal rate rows and 67 Florida counties.&lt;/p&gt;

&lt;p&gt;Here are the parts that took longer than expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The national layer: Census ACS, and the values that aren't numbers
&lt;/h2&gt;

&lt;p&gt;The American Community Survey (5-year estimates, 2019–2023) has everything needed for an "effective rate":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;B25103&lt;/strong&gt; – median real estate taxes paid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B25077&lt;/strong&gt; – median value of owner-occupied homes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B25119&lt;/strong&gt; – median household income by tenure (owners only)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B25003&lt;/strong&gt; / &lt;strong&gt;B01003&lt;/strong&gt; – tenure and population&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Effective rate = median tax ÷ median home value. Easy — until you sort by it.&lt;/p&gt;

&lt;p&gt;Sixteen counties report a median tax of exactly &lt;strong&gt;10001&lt;/strong&gt;, and seven report exactly &lt;strong&gt;199&lt;/strong&gt;. Those aren't measurements. The ACS caps this table: &lt;code&gt;10001&lt;/code&gt; means "$10,000 or more" and &lt;code&gt;199&lt;/code&gt; means "less than $200". If you treat them as numbers, Marin County and most of northern New Jersey look like they pay the same bill, and their effective rates are understated.&lt;/p&gt;

&lt;p&gt;I kept the numeric value (so the column stays numeric) and added a flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10000+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;med_tax&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10001&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;200&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;med_tax&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;199&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For those rows the effective rate is a bound, not an estimate. Worth checking for in any ACS median you use — home value and income tables have their own caps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecticut&lt;/strong&gt; is the other trap. Since 2022 the Census uses Connecticut's nine planning regions as county equivalents instead of the eight historical counties, so FIPS codes from older shapefiles won't join.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Texas: one spreadsheet layout per year
&lt;/h2&gt;

&lt;p&gt;The Texas Comptroller publishes every taxing unit's rate — counties, cities, school districts, hospital districts, MUDs, emergency services districts. That's what makes Texas bills hard to estimate: a single address can sit in six or seven units.&lt;/p&gt;

&lt;p&gt;Two surprises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The 2024 and 2025 files don't share a layout.&lt;/strong&gt; Columns moved between years, so where a file has a usable header row I look columns up by header name instead of position:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header_row&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TOTAL COUNTY TAX RATE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit IDs changed shape too.&lt;/strong&gt; The 2024 total-rate file identifies each taxing unit with a five-part ID; the 2025 file uses three parts. Joining on the first three parts of the old ID matched units year over year:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unit_id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also derive a &lt;code&gt;unit_type&lt;/code&gt; (county, school, city, special, emergency, hospital, college) from the unit's name and ID, because the file doesn't label it. And 347 units have a rate of exactly 0 — mostly utility districts that exist on paper but levied nothing this year. Keep them, but exclude them before computing averages.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. North Carolina: per $100, plus an "effective" rate
&lt;/h2&gt;

&lt;p&gt;North Carolina's Department of Revenue publishes county and municipal rates &lt;strong&gt;per $100 of value&lt;/strong&gt;, the combined rate, and an &lt;em&gt;effective&lt;/em&gt; combined rate adjusted by each county's sales-assessment ratio. That last column matters: counties reappraise on different cycles (some last did it in 2018), so nominal rates aren't comparable across counties — effective ones are closer.&lt;/p&gt;

&lt;p&gt;Small thing: the source writes place names like &lt;code&gt;Mcadenville&lt;/code&gt; and &lt;code&gt;Mcdonald&lt;/code&gt;. One regex fixed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bMc([a-z])&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Florida: mills, and one county with no county tax
&lt;/h2&gt;

&lt;p&gt;Florida reports &lt;strong&gt;millage&lt;/strong&gt; — dollars per $1,000 — split into county operating, county debt, school operating, school debt, dependent and independent special districts, plus levies that only cover part of a county (MSTUs, cities) expressed as a countywide equivalent.&lt;/p&gt;

&lt;p&gt;Mixing units is the easiest mistake in this whole project: Texas and North Carolina are per $100, Florida per $1,000. I kept the source unit in every column name (&lt;code&gt;rate_per_100_2025&lt;/code&gt;, &lt;code&gt;total_mills&lt;/code&gt;) rather than converting silently.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;Duval County&lt;/strong&gt; shows a county operating millage of zero. That's not missing data: Jacksonville and Duval are consolidated, so the levy appears as a municipal rate instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What the data says
&lt;/h2&gt;

&lt;p&gt;A few results once everything lined up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The median U.S. homeowner pays &lt;strong&gt;$2,969&lt;/strong&gt; a year, about &lt;strong&gt;0.98%&lt;/strong&gt; of home value.&lt;/li&gt;
&lt;li&gt;State medians range from &lt;strong&gt;0.27%&lt;/strong&gt; (Hawaii) to &lt;strong&gt;2.23%&lt;/strong&gt; (New Jersey).&lt;/li&gt;
&lt;li&gt;Log home value vs. log tax bill correlates at about &lt;strong&gt;0.66&lt;/strong&gt; — strong, but the scatter around the line is where state policy shows up.&lt;/li&gt;
&lt;li&gt;Northern New Jersey and the New York suburbs dominate the "share of income" ranking: around 7% of owner household income goes to property tax.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways for working with government data
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Look for sentinel values&lt;/strong&gt; before sorting or averaging. Round numbers at the edges of a distribution are suspicious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the unit in the column name.&lt;/strong&gt; Per $100, per $1,000 and percent all look like small decimals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match on headers, not positions&lt;/strong&gt;, when a publisher has more than one year of files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat zeros and blanks differently&lt;/strong&gt; and document which is which.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the footnotes.&lt;/strong&gt; Duval, Connecticut and the ACS caps were all documented — just not in the spreadsheet itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The compiled CSVs are public under CC BY 4.0 as the &lt;em&gt;U.S. County Property Tax Rates&lt;/em&gt; dataset on Kaggle and Hugging Face. If you just want to look up a county or estimate a bill, I built the calculators on top of it at &lt;a href="https://countypropertytaxes.com/" rel="noopener noreferrer"&gt;countypropertytaxes.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>opendata</category>
      <category>pandas</category>
    </item>
    <item>
      <title>Writing a PDF by hand: graph paper that prints at exactly the right size</title>
      <dc:creator>anders lee</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:34:23 +0000</pubDate>
      <link>https://dev.to/anders_lee_28dbcc2fd8e034/writing-a-pdf-by-hand-graph-paper-that-prints-at-exactly-the-right-size-2i71</link>
      <guid>https://dev.to/anders_lee_28dbcc2fd8e034/writing-a-pdf-by-hand-graph-paper-that-prints-at-exactly-the-right-size-2i71</guid>
      <description>&lt;p&gt;I needed graph paper with 1/4 inch squares. Not "about a quarter inch": a floor plan at one square&lt;br&gt;
per foot is only useful if four squares measure exactly one inch. Many of the free printables I found&lt;br&gt;
were PNGs or PDFs wrapping a raster image, and the grid came out at 0.24 inch once printed.&lt;/p&gt;

&lt;p&gt;Two things cause that. The image has a resolution that doesn't map cleanly to inches, and the PDF&lt;br&gt;
page is the full sheet with the grid running to the edge, so the print dialog shrinks it into the&lt;br&gt;
printable area. Both go away if the grid is generated as vector lines on a page that already&lt;br&gt;
leaves a margin. A PDF that contains only lines turns out to be small enough to write by hand.&lt;/p&gt;
&lt;h2&gt;
  
  
  Points, not pixels
&lt;/h2&gt;

&lt;p&gt;PDF coordinates are in points: 1/72 inch. That makes the units trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PT_PER_IN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PT_PER_MM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;25.4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// US Letter is 612 × 792 pt, A4 is 595.28 × 841.89 pt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 1/4 inch square is 18 pt, a 5 mm square is 14.17 pt. There is no DPI anywhere, so nothing gets&lt;br&gt;
resampled.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fit whole squares, then center them
&lt;/h2&gt;

&lt;p&gt;The second rule: never cut a square at the margin. Take the area inside the margins, see how many&lt;br&gt;
whole squares fit, and center that block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fitCells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ny&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;nx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;area&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;ny&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;1e-6&lt;/code&gt; matters: 8 inches of space divided by 18 pt is exactly 32 squares, and without the&lt;br&gt;
epsilon floating point can give 31.999999 and lose a column. With 1/4 inch margins a Letter page&lt;br&gt;
holds 32 × 42 squares; A4 holds 31 × 44.&lt;/p&gt;

&lt;p&gt;Drawing the grid is then two loops of lines, with every Nth line heavier (a bold line per inch on&lt;br&gt;
quarter-inch paper).&lt;/p&gt;
&lt;h2&gt;
  
  
  The content stream
&lt;/h2&gt;

&lt;p&gt;A PDF page's drawing is a text stream of postfix operators. For lines you need four of them:&lt;br&gt;
&lt;code&gt;w&lt;/code&gt; (line width), &lt;code&gt;RG&lt;/code&gt; (stroke color), &lt;code&gt;m&lt;/code&gt; / &lt;code&gt;l&lt;/code&gt; (move / line to) and &lt;code&gt;S&lt;/code&gt; (stroke). One wrinkle:&lt;br&gt;
PDF's y axis points up, so if you lay the page out top-down like a screen, flip it when writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight postscript"&gt;&lt;code&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt; &lt;span class="nf"&gt;w&lt;/span&gt; &lt;span class="mf"&gt;0.557&lt;/span&gt; &lt;span class="mf"&gt;0.694&lt;/span&gt; &lt;span class="mf"&gt;0.839&lt;/span&gt; &lt;span class="nf"&gt;RG&lt;/span&gt;
&lt;span class="mf"&gt;36&lt;/span&gt; &lt;span class="mf"&gt;774&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt; &lt;span class="mf"&gt;36&lt;/span&gt; &lt;span class="mf"&gt;18&lt;/span&gt; &lt;span class="nf"&gt;l&lt;/span&gt;
&lt;span class="mf"&gt;54&lt;/span&gt; &lt;span class="mf"&gt;774&lt;/span&gt; &lt;span class="nf"&gt;m&lt;/span&gt; &lt;span class="mf"&gt;54&lt;/span&gt; &lt;span class="mf"&gt;18&lt;/span&gt; &lt;span class="nf"&gt;l&lt;/span&gt;
&lt;span class="nf"&gt;...&lt;/span&gt;
&lt;span class="nf"&gt;S&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grouping all lines of the same width and color under one &lt;code&gt;w … RG … S&lt;/code&gt; keeps the stream short.&lt;br&gt;
A full Letter page of 1/4 inch grid is about 76 lines.&lt;/p&gt;

&lt;p&gt;Two tricks for the other paper types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dots&lt;/strong&gt; are zero-length lines with round caps: set &lt;code&gt;1 J&lt;/code&gt;, then &lt;code&gt;x y m x y l&lt;/code&gt; with the line width
equal to the dot diameter. Far smaller than drawing circles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circles&lt;/strong&gt; (for polar paper) are four cubic Béziers per circle, with control points at
0.5523 × r from each quadrant point—the usual approximation, invisible on paper.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The file around it
&lt;/h2&gt;

&lt;p&gt;A minimal PDF is a handful of numbered objects, a cross-reference table giving the byte offset of&lt;br&gt;
each object, and a trailer. This is the whole writer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toPDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;objs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;objs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;&amp;lt; /Type /Catalog /Pages 2 0 R &amp;gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                                                    &lt;span class="c1"&gt;// 1&lt;/span&gt;
  &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;&amp;lt; /Type /Pages /Kids [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageIds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 0 R`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;] /Count &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
  &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;&amp;lt; /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding &amp;gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// 3&lt;/span&gt;
  &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;&amp;lt; /Length &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;&amp;gt;\nstream\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nendstream`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                     &lt;span class="c1"&gt;// 4&lt;/span&gt;
  &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;&amp;lt; /Title (Graph paper) /Producer (graphpaperpdf.net) &amp;gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                             &lt;span class="c1"&gt;// 5&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;&amp;lt; /Type /Page /Parent 2 0 R /MediaBox [0 0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] /Resources &amp;lt;&amp;lt; /Font &amp;lt;&amp;lt; /F1 3 0 R &amp;gt;&amp;gt; &amp;gt;&amp;gt; /Contents 4 0 R &amp;gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%PDF-1.4&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;xE2&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;xE3&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;xCF&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;xD3&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offsets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nx"&gt;objs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 0 obj\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\nendobj\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;xref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`xref\n0 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;objs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n0000000000 65535 f \n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;off&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; 00000 n \n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`trailer\n&amp;lt;&amp;lt; /Size &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;objs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; /Root 1 0 R /Info 5 0 R &amp;gt;&amp;gt;\nstartxref\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;xref&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n%%EOF\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things that bit me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Offsets are bytes, not characters.&lt;/strong&gt; Keep everything single-byte (the content is ASCII, the
binary comment on line 2 is Latin-1) and &lt;code&gt;string.length&lt;/code&gt; is the byte offset. One stray UTF-8
character and every offset after it is wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each xref line is exactly 20 bytes&lt;/strong&gt;, including the space before &lt;code&gt;\n&lt;/code&gt;. Viewers are forgiving;
some printers' PDF interpreters are not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/Length&lt;/code&gt; must match the stream.&lt;/strong&gt; Same single-byte rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nice part is &lt;code&gt;/Contents 4 0 R&lt;/code&gt; on every page: 100 pages of graph paper share one content&lt;br&gt;
stream. A one-page 1/4 inch grid is 2.3 KB; a hundred pages is 18.8 KB.&lt;/p&gt;
&lt;h2&gt;
  
  
  Text, and the π problem
&lt;/h2&gt;

&lt;p&gt;Labels (axis numbers on a coordinate plane, degrees on polar paper) use the built-in Helvetica, so&lt;br&gt;
no font embedding. That covers digits, minus and the degree sign (&lt;code&gt;\260&lt;/code&gt; in WinAnsi). Then I added&lt;br&gt;
radian labels, and π isn't in that encoding.&lt;/p&gt;

&lt;p&gt;The fix is old-school: the standard Symbol font has π at the position of the letter &lt;code&gt;p&lt;/code&gt;. So a&lt;br&gt;
label like &lt;code&gt;5π/6&lt;/code&gt; becomes one text object that switches fonts mid-string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight postscript"&gt;&lt;code&gt;&lt;span class="nf"&gt;BT&lt;/span&gt; &lt;span class="mf"&gt;0.333&lt;/span&gt; &lt;span class="mf"&gt;0.333&lt;/span&gt; &lt;span class="mf"&gt;0.333&lt;/span&gt; &lt;span class="nf"&gt;rg&lt;/span&gt; &lt;span class="mf"&gt;300&lt;/span&gt; &lt;span class="mf"&gt;410&lt;/span&gt; &lt;span class="nf"&gt;Td&lt;/span&gt;
&lt;span class="nv"&gt;/F1&lt;/span&gt; &lt;span class="mf"&gt;8&lt;/span&gt; &lt;span class="nf"&gt;Tf&lt;/span&gt; &lt;span class="s"&gt;(5)&lt;/span&gt; &lt;span class="nf"&gt;Tj&lt;/span&gt; &lt;span class="nv"&gt;/F2&lt;/span&gt; &lt;span class="mf"&gt;8&lt;/span&gt; &lt;span class="nf"&gt;Tf&lt;/span&gt; &lt;span class="s"&gt;(p)&lt;/span&gt; &lt;span class="nf"&gt;Tj&lt;/span&gt; &lt;span class="nv"&gt;/F1&lt;/span&gt; &lt;span class="mf"&gt;8&lt;/span&gt; &lt;span class="nf"&gt;Tf&lt;/span&gt; &lt;span class="s"&gt;(/6)&lt;/span&gt; &lt;span class="nf"&gt;Tj&lt;/span&gt; &lt;span class="nf"&gt;ET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Text position advances after each &lt;code&gt;Tj&lt;/code&gt;, so the pieces line up. Centering the label needs the&lt;br&gt;
advance widths, which for a handful of glyphs is a small lookup table (digits are 556/1000 em in&lt;br&gt;
Helvetica, π is 549 in Symbol).&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing a file format
&lt;/h2&gt;

&lt;p&gt;The tests that caught real bugs check structure rather than pixels: for every paper type, parse&lt;br&gt;
&lt;code&gt;startxref&lt;/code&gt;, confirm it points at &lt;code&gt;xref&lt;/code&gt;, and confirm each offset in the table lands on&lt;br&gt;
&lt;code&gt;N 0 obj&lt;/code&gt;; check that &lt;code&gt;/Length&lt;/code&gt; bytes later the stream says &lt;code&gt;endstream&lt;/code&gt;. Geometry tests pin the&lt;br&gt;
square counts (32 × 42 on Letter at 1/4 inch), so a refactor can't silently shave off a column.&lt;/p&gt;

&lt;h2&gt;
  
  
  Printing is the last mile
&lt;/h2&gt;

&lt;p&gt;Even a perfect PDF prints small if the dialog is set to "Fit to page". Since the page already has a&lt;br&gt;
1/4 inch margin—wider than the unprintable border of most home printers—"Actual size" / 100% is&lt;br&gt;
safe. Measuring ten squares (2.5&lt;br&gt;
inches on quarter-inch paper) is the quickest check.&lt;/p&gt;




&lt;p&gt;I built this into a free generator at &lt;a href="https://graphpaperpdf.net" rel="noopener noreferrer"&gt;graphpaperpdf.net&lt;/a&gt;: square grids&lt;br&gt;
in inches or millimetres, dot grid, isometric, polar, coordinate planes, lined and hexagon paper,&lt;br&gt;
on Letter or A4. The PDF is generated in the browser, so nothing is uploaded. If you have a grid&lt;br&gt;
type it doesn't do yet, I'd like to hear about it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>pdf</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Three equations that found misprints in a national steel standard</title>
      <dc:creator>anders lee</dc:creator>
      <pubDate>Sat, 10 Oct 2026 15:15:32 +0000</pubDate>
      <link>https://dev.to/anders_lee_28dbcc2fd8e034/three-equations-that-found-misprints-in-a-national-steel-standard-51fj</link>
      <guid>https://dev.to/anders_lee_28dbcc2fd8e034/three-equations-that-found-misprints-in-a-national-steel-standard-51fj</guid>
      <description>&lt;p&gt;I needed clean data for steel beams, channels and angles: weight per metre, dimensions and&lt;br&gt;
section properties. For Indian sections the source is IS 808, the national standard, made public by&lt;br&gt;
Public.Resource.Org. The 2021 edition is a born-digital PDF, the 1989 edition and the 1964 handbook&lt;br&gt;
are scans.&lt;/p&gt;

&lt;p&gt;Transcribing tables is easy. Knowing the transcription (and the printed table) is right is the&lt;br&gt;
interesting part. Section tables have a nice property: their columns are not independent. A few&lt;br&gt;
physical relations tie them together, so every row can check itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  The three relations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Mass follows from area.&lt;/strong&gt; Steel is 7850 kg/m³. One metre of a section with area A cm² is&lt;br&gt;
A × 10⁻⁴ m³, so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mass (kg/m) = 0.785 × A (cm²)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Radius of gyration follows from inertia and area.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r = sqrt(I / A)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Elastic modulus follows from inertia and depth&lt;/strong&gt; (for doubly symmetric beams, about the major axis):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Zx = Ix / (D / 2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough to check most columns of most rows. In Python, the core of it is a dozen lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="n"&gt;TOL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;  &lt;span class="c1"&gt;# tables print 3 to 4 significant figures
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;printed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;derived&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TOL&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# small values are printed to 0.1, so allow an absolute slack too
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;printed&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;derived&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;derived&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.06&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mf"&gt;0.785&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.785&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Iy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;derived&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;derived&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;derived&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Zx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;  &lt;span class="c1"&gt;# D in mm, Z in cm³
&lt;/span&gt;        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Zx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Zx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;D&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it over every row and anything it flags is either a transcription error (mine) or a misprint&lt;br&gt;
(theirs). I fixed mine; the rest went into an &lt;code&gt;errata.csv&lt;/code&gt; next to the source data, so the source&lt;br&gt;
CSVs keep the printed value and the build applies the correction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it found
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;IS 808:1989&lt;/strong&gt; (including its 1992 amendment):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Printed&lt;/th&gt;
&lt;th&gt;From the relations&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MC 100&lt;/td&gt;
&lt;td&gt;Zx&lt;/td&gt;
&lt;td&gt;33.5&lt;/td&gt;
&lt;td&gt;38.4&lt;/td&gt;
&lt;td&gt;Ix / (D/2) = 192 / 5.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MC 175 (heavy)&lt;/td&gt;
&lt;td&gt;mass&lt;/td&gt;
&lt;td&gt;22.7&lt;/td&gt;
&lt;td&gt;21.7&lt;/td&gt;
&lt;td&gt;0.785 × 27.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MC 250&lt;/td&gt;
&lt;td&gt;ry&lt;/td&gt;
&lt;td&gt;2.37&lt;/td&gt;
&lt;td&gt;2.33&lt;/td&gt;
&lt;td&gt;√(211 / 39.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ISA 35×35×6&lt;/td&gt;
&lt;td&gt;Iu&lt;/td&gt;
&lt;td&gt;5.5&lt;/td&gt;
&lt;td&gt;6.4&lt;/td&gt;
&lt;td&gt;also smaller than the 5 mm angle's 5.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ISA 65×65×4&lt;/td&gt;
&lt;td&gt;Ix, Iu&lt;/td&gt;
&lt;td&gt;21.70, 34.50&lt;/td&gt;
&lt;td&gt;20.2, 32.0&lt;/td&gt;
&lt;td&gt;the row added by the amendment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In &lt;strong&gt;IS 808:2021&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Printed&lt;/th&gt;
&lt;th&gt;Correct&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MC 75&lt;/td&gt;
&lt;td&gt;rz&lt;/td&gt;
&lt;td&gt;39.4 mm&lt;/td&gt;
&lt;td&gt;29.4 mm&lt;/td&gt;
&lt;td&gt;√(78.2×10⁴ / 908); the 1989 edition had 2.94 cm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MC 175*&lt;/td&gt;
&lt;td&gt;mass&lt;/td&gt;
&lt;td&gt;22.7&lt;/td&gt;
&lt;td&gt;21.4&lt;/td&gt;
&lt;td&gt;0.785 × 27.3 — &lt;strong&gt;the same 22.7 as in 1989&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;every ISA ≥ 100 mm&lt;/td&gt;
&lt;td&gt;rv&lt;/td&gt;
&lt;td&gt;×10&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;e.g. 200.0 printed where √(Iv / A) gives 20.0 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The MC 175 one is my favourite: a mass that does not match its own area, carried from the 1989&lt;br&gt;
table into the 2021 one, 32 years later.&lt;/p&gt;

&lt;p&gt;Big tables from other places are not immune either. The AISC Shapes Database (the American&lt;br&gt;
reference spreadsheet) has its metric and US columns side by side; comparing them row by row found&lt;br&gt;
one cell, the perimeter of S24×90, with an extra zero (18400 mm instead of 1840).&lt;/p&gt;

&lt;h2&gt;
  
  
  The 1964 value everybody still uses
&lt;/h2&gt;

&lt;p&gt;The relations also explained a long-standing confusion. Search for the weight of ISMB 200 and you&lt;br&gt;
get &lt;strong&gt;25.4 kg/m&lt;/strong&gt; from most charts and &lt;strong&gt;24.2 kg/m&lt;/strong&gt; from a few. Both are "right":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Edition&lt;/th&gt;
&lt;th&gt;Flange thickness&lt;/th&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Mass&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SP 6(1):1964 handbook&lt;/td&gt;
&lt;td&gt;10.8 mm&lt;/td&gt;
&lt;td&gt;32.33 cm²&lt;/td&gt;
&lt;td&gt;25.4 kg/m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IS 808:1989&lt;/td&gt;
&lt;td&gt;10.0 mm&lt;/td&gt;
&lt;td&gt;30.8 cm²&lt;/td&gt;
&lt;td&gt;24.2 kg/m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IS 808:2021&lt;/td&gt;
&lt;td&gt;10.0 mm&lt;/td&gt;
&lt;td&gt;30.8 cm²&lt;/td&gt;
&lt;td&gt;24.17 kg/m&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1989 revision thinned the flange. Charts copied from the old handbook never updated, so a&lt;br&gt;
12 m beam is quoted 15 kg heavier than what is rolled today. The same happened to ISMB 100&lt;br&gt;
(11.5 → 8.9 kg/m) and ISMB 300 (44.2 → 46.0 kg/m, heavier this time).&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways for any engineering dataset
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Look for redundancy.&lt;/strong&gt; Engineering tables usually contain derived columns. Recompute them
instead of trusting them; disagreement is signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the printed value.&lt;/strong&gt; Store corrections separately (&lt;code&gt;errata.csv&lt;/code&gt; with the reason and the
arithmetic) rather than editing the source, so anyone can audit the change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check two sources against each other&lt;/strong&gt; where they exist: two open libraries for European
sections, metric vs US columns for AISC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old editions matter.&lt;/strong&gt; If users quote a number that does not match the current standard, the
previous edition is often why — and showing both is more useful than silently picking one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cleaned tables are on GitHub as CSV/JSON:&lt;br&gt;
&lt;a href="https://github.com/imrubinshao/steel-section-weights" rel="noopener noreferrer"&gt;steel-section-weights&lt;/a&gt;. I also built them&lt;br&gt;
into a free reference site with a weight calculator and charts that show all three editions side by&lt;br&gt;
side: &lt;a href="https://steelweight-calculator.com/charts/ismb-weight-chart/" rel="noopener noreferrer"&gt;steelweight-calculator.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>engineering</category>
      <category>opendata</category>
    </item>
  </channel>
</rss>
