<?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: AlonDrilich</title>
    <description>The latest articles on DEV Community by AlonDrilich (@alondrilich).</description>
    <link>https://dev.to/alondrilich</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%2F4235908%2Fe56eccc9-0683-4f74-8d26-70bcfd908476.png</url>
      <title>DEV Community: AlonDrilich</title>
      <link>https://dev.to/alondrilich</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alondrilich"/>
    <language>en</language>
    <item>
      <title>That $2.57 domain renews at $43.77: check the renewal price before you register</title>
      <dc:creator>AlonDrilich</dc:creator>
      <pubDate>Mon, 09 Nov 2026 21:33:28 +0000</pubDate>
      <link>https://dev.to/alondrilich/that-257-domain-renews-at-4377-check-the-renewal-price-before-you-register-3ken</link>
      <guid>https://dev.to/alondrilich/that-257-domain-renews-at-4377-check-the-renewal-price-before-you-register-3ken</guid>
      <description>&lt;p&gt;You find a good name for a side project. The registrar shows &lt;code&gt;.store&lt;/code&gt; for $2.57, you register it, and a year later the renewal email says $43.77.&lt;/p&gt;

&lt;p&gt;Nothing went wrong. Many extensions are priced this way on purpose: a low first year to get the registration, and the real price from year two. The renewal price is published, but checkout pages put the first-year price front and centre.&lt;/p&gt;

&lt;h2&gt;
  
  
  How big the gap is
&lt;/h2&gt;

&lt;p&gt;I keep a weekly snapshot of one registrar's public list prices (Porkbun) for 532 extensions. In the snapshot from 9 November 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;309 of the 532 extensions renew at 2× their first-year price or more.&lt;/li&gt;
&lt;li&gt;188 renew at 5× or more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first year tells you little about what a name costs to keep. Here are five years of ownership (first year plus four renewals) for some extensions developers often buy:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;First year&lt;/th&gt;
&lt;th&gt;Renewal&lt;/th&gt;
&lt;th&gt;5 years&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;.xyz&lt;/td&gt;
&lt;td&gt;$2.04&lt;/td&gt;
&lt;td&gt;$14.21&lt;/td&gt;
&lt;td&gt;$58.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.com&lt;/td&gt;
&lt;td&gt;$11.81&lt;/td&gt;
&lt;td&gt;$11.81&lt;/td&gt;
&lt;td&gt;$59.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.dev&lt;/td&gt;
&lt;td&gt;$8.75&lt;/td&gt;
&lt;td&gt;$12.87&lt;/td&gt;
&lt;td&gt;$60.23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.app&lt;/td&gt;
&lt;td&gt;$8.75&lt;/td&gt;
&lt;td&gt;$14.93&lt;/td&gt;
&lt;td&gt;$68.47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.online&lt;/td&gt;
&lt;td&gt;$1.96&lt;/td&gt;
&lt;td&gt;$28.84&lt;/td&gt;
&lt;td&gt;$117.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.shop&lt;/td&gt;
&lt;td&gt;$2.06&lt;/td&gt;
&lt;td&gt;$31.41&lt;/td&gt;
&lt;td&gt;$127.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.co&lt;/td&gt;
&lt;td&gt;$15.76&lt;/td&gt;
&lt;td&gt;$31.20&lt;/td&gt;
&lt;td&gt;$140.56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.store&lt;/td&gt;
&lt;td&gt;$2.57&lt;/td&gt;
&lt;td&gt;$43.77&lt;/td&gt;
&lt;td&gt;$177.65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.tech&lt;/td&gt;
&lt;td&gt;$6.99&lt;/td&gt;
&lt;td&gt;$50.98&lt;/td&gt;
&lt;td&gt;$210.91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.io&lt;/td&gt;
&lt;td&gt;$28.12&lt;/td&gt;
&lt;td&gt;$51.80&lt;/td&gt;
&lt;td&gt;$235.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.ai&lt;/td&gt;
&lt;td&gt;$82.70&lt;/td&gt;
&lt;td&gt;$82.70&lt;/td&gt;
&lt;td&gt;$413.50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;.online&lt;/code&gt; costs less than &lt;code&gt;.com&lt;/code&gt; in year one and about twice as much over five years. &lt;code&gt;.ai&lt;/code&gt; is expensive from day one, but at least it doesn't change.&lt;/p&gt;

&lt;p&gt;These are one registrar's list prices on one date, in USD. Other registrars price differently, and promotions change often, so treat the table as a reason to check rather than as a quote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it yourself in a few lines
&lt;/h2&gt;

&lt;p&gt;Porkbun publishes its prices at an endpoint that needs no API key. This prints the extensions whose renewal is at least five times the first year:&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.porkbun.com/api/json/v3/pricing/get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&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="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pricing&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pricing&lt;/span&gt;&lt;span class="p"&gt;)&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;tld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&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;tld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;renewal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renewal&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renewal&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renewal&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;renewal&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs in Node 18+ (save it as an &lt;code&gt;.mjs&lt;/code&gt; file) or in the browser console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you register
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Look for the renewal price, not just the first-year price. Most registrars list both on their pricing page.&lt;/li&gt;
&lt;li&gt;Multiply it out over the years you expect to keep the name.&lt;/li&gt;
&lt;li&gt;If you're choosing between two extensions, compare the renewals. The cheaper first year often turns out to be the dearer domain.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full table for all 532 extensions, updated weekly, is on GitHub as CSV under CC BY 4.0: &lt;a href="https://github.com/AlonDrilich/tld-renewal-prices" rel="noopener noreferrer"&gt;AlonDrilich/tld-renewal-prices&lt;/a&gt; (DOI &lt;a href="https://doi.org/10.5281/zenodo.24082643" rel="noopener noreferrer"&gt;10.5281/zenodo.24082643&lt;/a&gt;). There's also a sortable version at &lt;a href="https://namesale.store/renewal-prices" rel="noopener noreferrer"&gt;namesale.store/renewal-prices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Not affiliated with Porkbun. Its endpoint is simply the easiest public source to snapshot.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>domains</category>
      <category>beginners</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Why your pdf-lib signature lands in the wrong place on scanned PDFs</title>
      <dc:creator>AlonDrilich</dc:creator>
      <pubDate>Mon, 09 Nov 2026 21:28:33 +0000</pubDate>
      <link>https://dev.to/alondrilich/why-your-pdf-lib-signature-lands-in-the-wrong-place-on-scanned-pdfs-26e4</link>
      <guid>https://dev.to/alondrilich/why-your-pdf-lib-signature-lands-in-the-wrong-place-on-scanned-pdfs-26e4</guid>
      <description>&lt;p&gt;You render a PDF with pdf.js, let the user click where the signature goes, and draw it with pdf-lib. It works on every test file you have. Then someone uploads a scan, and the signature shows up sideways, in the wrong corner, or off the page.&lt;/p&gt;

&lt;p&gt;The bug is almost never in your click handling. It comes from two coordinate systems that agree on ordinary pages and disagree on the ones that come from scanners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two coordinate systems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What pdf-lib draws in.&lt;/strong&gt; &lt;code&gt;page.drawImage()&lt;/code&gt; and friends work in the page's raw &lt;em&gt;user space&lt;/em&gt;. The origin is at the bottom-left of the MediaBox, y points up, and the page's &lt;code&gt;/Rotate&lt;/code&gt; entry is ignored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the user sees.&lt;/strong&gt; A viewer, pdf.js included, shows the &lt;em&gt;CropBox&lt;/em&gt; (the visible part of the page), turned by &lt;code&gt;/Rotate&lt;/code&gt;, with the origin at the top-left and y pointing down.&lt;/p&gt;

&lt;p&gt;On a plain portrait page with no crop, the difference is a y-flip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;viewY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the line most tutorials give you. It's correct until one of these shows up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/Rotate 90&lt;/code&gt; (or 180, 270).&lt;/strong&gt; Scanners often store a landscape page as portrait pixels plus a rotate flag. The viewer turns it. pdf-lib doesn't, so your x and y axes are swapped, and the image is drawn rotated relative to what the user saw.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CropBox smaller than the MediaBox.&lt;/strong&gt; The viewer shows only the crop. Your click coordinates are relative to the crop's corner, but pdf-lib measures from the MediaBox corner, so everything is shifted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both at once.&lt;/strong&gt; That's common in real scanned contracts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The mapping
&lt;/h2&gt;

&lt;p&gt;For a point in view space (points, top-left origin, y down) on a page whose visible box starts at &lt;code&gt;(bx, by)&lt;/code&gt; and is &lt;code&gt;bw × bh&lt;/code&gt; points:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;/Rotate&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;user-space x&lt;/th&gt;
&lt;th&gt;user-space y&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bx + vx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;by + bh - vy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bx + vy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;by + vx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bx + bw - vx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;by + vy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;270&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bx + bw - vy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;by + bh - vx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The displayed width and height also swap for 90 and 270. Drawing an image &lt;em&gt;upright&lt;/em&gt; means passing the matching rotation to pdf-lib, and accounting for the fact that pdf-lib rotates around the image's own origin, not its centre. That last part is where most hand-rolled fixes break.&lt;/p&gt;

&lt;p&gt;One more edge case: pdf.js treats a &lt;code&gt;/Rotate&lt;/code&gt; value that isn't a multiple of 90 as 0. Do the same, or your output won't match what the user saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small library for it
&lt;/h2&gt;

&lt;p&gt;I pulled this logic into &lt;a href="https://github.com/AlonDrilich/pdf-view-coords" rel="noopener noreferrer"&gt;pdf-view-coords&lt;/a&gt;, a small MIT TypeScript helper for pdf-lib. You stay in view space, and it does the translation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PDFDocument&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pdf-lib&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;drawImageInView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rectFromFractions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pdf-view-coords&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;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PDFDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPage&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;png&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embedPng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signaturePng&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The box the user drew, as fractions of the rendered page size.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rectFromFractions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.08&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;drawImageInView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;png&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// right place, upright, aspect ratio kept&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;viewport(page)&lt;/code&gt; also gives you &lt;code&gt;toUser(x, y)&lt;/code&gt; and &lt;code&gt;toView(x, y)&lt;/code&gt; if you'd rather draw yourself. The tests check every rotation, with and without a crop, and confirm that the area pdf-lib actually paints matches the view-space rectangle you asked for.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://alondrilich.github.io/pdf-view-coords/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; runs in the browser. Load your own PDF, click on a rotated page, and compare where plain pdf-lib puts the box with where the library puts it.&lt;/p&gt;

&lt;p&gt;Install it from GitHub for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;github:AlonDrilich/pdf-view-coords pdf-lib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've hit a PDF where it still lands wrong, open an issue with the file. Odd real-world PDFs are exactly what it needs to be tested against.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>pdf</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
