npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@astratra/pdf

v0.1.0

Published

Layout primitives for PDFKit: text that never overruns its box, and tables that paginate without splitting a row.

Readme

@astratra/pdf

Deux problèmes de PDFKit, résolus une fois pour toutes : du texte qui déborde de sa case, et des tableaux qui se coupent au mauvais endroit.

Ce package n'importe pas PDFKit. Tu lui passes ton document, il travaille dessus. Aucune dépendance à l'exécution.

Le texte qui déborde

PDFKit, à qui l'on donne une position absolue sans largeur, laisse la chaîne courir jusqu'au bord de la page puis la replie — et la ligne suivante se pose par-dessus ce qui était dessous. C'est ainsi qu'un nom d'école trop long passe sous le bandeau du titre, et qu'un libellé d'article se pose sur la colonne des prix.

Donner une largeur ne suffit pas : depuis la 0.18, PDFKit ignore lineBreak: false dès qu'une largeur est fournie, replie quand même, et son option ellipsis ne se déclenche jamais.

const { fitText, line } = require('@astratra/pdf');

// Coupé, points de suspension compris, mesuré avec la police ACTIVE.
line(doc, school.name, 40, 100, 195);

Utilise line partout où la valeur vient de données que tu ne contrôles pas : un nom, un libellé, une adresse. Bornée et coupée, elle ne peut plus mordre sur sa voisine.

Pourquoi la mesure porte sur la hauteur

fitText juge en demandant « est-ce que ça tient encore sur UNE ligne ? », pas en comparant des largeurs. Le replieur de PDFKit accumule la largeur mot à mot et casse une ligne qui tient pourtant au point près selon la mesure d'ensemble. La hauteur est la seule question dont la réponse correspond à ce qui est réellement dessiné — et elle vaut pour toutes les versions.

La coupe se fait par dichotomie : une recherche linéaire ferait un appel aux métriques de police par caractère, et ces documents ont des centaines de cellules.

Le tableau qui se pagine

const { drawTable } = require('@astratra/pdf');

const { y } = drawTable(doc, {
  x: 40, y: 170, width: 515,
  columns: [
    { key: 'subject', label: 'MATIÈRE', width: 200 },
    { key: 'score',   label: 'NOTE',    width: 60, align: 'right',
      format: (v) => `${v} / 20` },
    { key: 'comment', label: 'APPRÉCIATION', wrap: true, fontSize: 7 },
  ],
  rows: results,
  // Réserve la place du bloc de synthèse qui suit.
  bottom: doc.page.height - 200,
  zebra: '#FAFAFA',
});

Trois comportements, chacun appris à la dure :

L'en-tête se redessine sur chaque page. Sinon la page deux est un mur de chiffres sans étiquette.

La hauteur d'une rangée suit sa cellule la plus haute. Une appréciation d'enseignant est une information ; la tronquer la perdrait. C'est le rôle de wrap: true — tout le reste est coupé, parce qu'une valeur qui grandit en silence est une valeur qui chevauche.

Une rangée ne chevauche jamais une coupure de page. La moitié en bas d'une page et la moitié en haut de la suivante se lisent comme deux rangées différentes.

bottom est l'autre moitié de cette dernière règle : il réserve la place de ce qui vient après le tableau. Sans lui, un bloc de totaux part sur une page à lui tout seul, ou pire, sur le pied de page.

Les colonnes sans width se partagent ce qui reste du cadre — déclarer chaque largeur à la main est la façon dont un tableau cesse de faire la somme de son propre cadre après une modification.

drawTable renvoie { y, pages, rows } : y est le curseur pour la suite.

Le bloc qui ne se coupe pas

const { keepTogether } = require('@astratra/pdf');

// Un panneau de synthèse, une ligne de signature, un total.
let y = keepTogether(doc, { y: cursor, height: 130 });
doc.rect(350, y, 205, 60).stroke();

La moitié d'un bloc en bas d'une page est pire qu'une coupure avant lui.

Ce que ce package ne fait pas

  • Il ne dessine pas tes documents : facture, reçu, bulletin, c'est ton métier.
  • Il n'impose ni police, ni couleur, ni format de page.
  • Il n'importe pas PDFKit — c'est une dépendance de pair, optionnelle.
  • Il ne génère ni QR code, ni code-barres, ni image.

Tests

npm test --workspace @astratra/pdf

Les tests tournent sur un vrai document PDFKit : chaque affirmation porte sur ce que PDFKit fait réellement des métriques de police, pas sur notre arithmétique.