La bibliothèque, sur votre propre machine
Le moteur qui écrit les documents entre dans votre propre logiciel, en Rust ou en Python, et crée ses pages sur votre machine. Cette page dit ce que cela veut dire pour ce que vous avez déjà.
Résumé
De quoi il s'agit
Un fichier PDF garde exactement la même allure partout. Vous l'ouvrez sur votre ordinateur, votre voisin l'ouvre sur un téléphone, l'imprimeur le sort sur sa machine, et c'est la même page au millimètre près. C'est pour ça que les factures, les contrats et les bulletins de paie voyagent sous cette forme.
HQF Development fabrique le moteur qui écrit ces fichiers. Pas un logiciel que vous ouvrez et dans lequel vous cliquez : un moteur, que la personne qui écrit vos logiciels met à l'intérieur des siens.
Ce qu'est une bibliothèque
En informatique, une bibliothèque est un jeu de pièces toutes faites. Pensez au moteur d'une voiture. Le constructeur n'en refabrique pas un pour chaque modèle : il prend un moteur qui existe, il le monte dans la carrosserie qu'il a dessinée, et il branche la pédale dessus. Le moteur fait tourner les roues ; la voiture reste la sienne.
Une bibliothèque, c'est ce moteur. La personne qui écrit votre logiciel de facturation garde son logiciel, ses écrans, ses habitudes, sa manière de travailler. Elle y glisse notre moteur, elle lui dit voilà les lignes de la facture, voilà le logo, voilà le total, et le moteur lui rend le fichier PDF.
L'autre façon de faire, et ce qui change
Il existe une autre façon de faire : envoyer les informations à un ordinateur ailleurs sur internet, qui crée le document et le renvoie. C'est comme confier votre courrier à un imprimeur à l'autre bout de la ville. Ça marche très bien, et l'équipe le propose aussi. Mais il faut porter le pli là-bas, attendre, et revenir le chercher.
Avec la bibliothèque, l'imprimeur est dans la pièce d'à côté. Trois choses changent, et les trois se sentent tout de suite.
- C'est immédiat. Le document est créé sur votre propre machine, au moment même du clic qui l'a demandé. Rien ne part, rien ne revient.
- Vos informations restent chez vous. Les noms de vos clients, leurs adresses, leurs montants restent sur votre machine du début à la fin. La bibliothèque travaille entièrement sur place, avec ce qu'elle contient dans son propre paquet. Vos données ne quittent jamais la machine qui la fait tourner.
- Elle se suffit à elle-même. Votre logiciel crée ses documents même quand la connexion est coupée, et sur un site isolé. La seule machine à surveiller est la vôtre.
Ce que ça fabrique
Des factures, des confirmations de commande, des bons de livraison, des bulletins de paie, des contrats, des relevés, des planches d'étiquettes, des catalogues, des diplômes, des cartes d'embarquement, des livres entiers. Avec les tableaux qui se poursuivent d'une page à l'autre, les logos, les photos, les QR codes qu'un téléphone lit pour payer, les cases qu'on remplit à l'écran, et le mot de passe qui verrouille le fichier si vous le demandez.
Ce que vous recevez, et comment cela vous arrive
Vous créez un compte sur ce site, vous payez, et le fichier à installer vous attend dans ce compte. Vous vous connectez, vous cliquez dessus, et il s'enregistre sur votre ordinateur, exactement comme n'importe quel autre fichier qu'on télécharge. La même page contient votre facture.
À côté du fichier se trouve votre clé de licence : une suite de lettres et de chiffres, à vous seul, que votre logiciel reçoit une fois et qui indique au moteur qu'il est une copie payante. Remettez le fichier et cette suite à la personne qui développe votre logiciel, et elle a tout.
Tout vient d'ici, de votre compte. Quand un fichier plus récent est prêt, il vous attend au même endroit, et vous venez le chercher quand cela vous arrange.
Ce que vous achetez, c'est le droit d'utiliser le moteur, compilé et prêt à tourner. Son code source reste chez HQF Development, comme un constructeur automobile vend le moteur et garde les plans d'après lesquels il l'a construit.
À qui cette page est destinée
Donnez l'adresse de cette page à la personne qui développe vos logiciels : plus bas, elle trouvera tout ce qu'il lui faut pour juger.
Techniquement
Un seul moteur, atteint de deux façons — une bibliothèque Rust et une roue Python — la surface publique qu'ils exposent, la façon dont on les télécharge tous les deux depuis votre compte ici, et la clé de licence qu'ils lisent à l'exécution.
Deux faces
Le moteur est écrit en Rust et s'utilise depuis Rust directement, comme une dépendance ordinaire de votre projet.
Le côté Python est une extension compilée, livrée déjà construite : rien à compiler sur la machine visée, aucune bibliothèque système à installer, aucune chaîne d'outils PDF à installer par-dessus. Elle est construite sur l'ABI stable de CPython : une seule roue Python sert toutes les versions de l'interpréteur à partir de CPython 3.9, sur Linux, Windows et macOS.
Une interface C, pour appeler le moteur depuis d'autres langages, est prévue dans une version future.
Ce qu'il écrit
- Texte : TrueType et OpenType analysées et écrites dans le fichier, réduites aux seuls glyphes tracés ; Type 1 transporté entier, le programme tel quel ; un fichier WOFF, la forme servie à un navigateur, lu comme la police à l'intérieur de son emballage ; lettres liées, petites capitales, chiffres elzéviriens, crénage ; une police de secours derrière une autre ; les quatorze polices standard qu'un logiciel de lecture est censé fournir ; les polices Type 3 dont les glyphes sont des dessins ; l'écriture verticale.
- Mise en page : paragraphes coupés à une largeur, colonnes, listes, taquets de tabulation et points de conduite, portions de style différent à l'intérieur d'un paragraphe, tableaux paginés sur autant de pages qu'il leur en faut, blocs empilés sans une seule coordonnée écrite à la main.
- Dessin : chemins, courbes, arcs, angles arrondis, détourage, dégradés axiaux et radiaux, motifs de pavage, transparence et modes de fusion, masques adoucis, dessins réutilisables posés autant de fois qu'on veut pour le poids d'un seul.
- Images : JPEG, PNG et données brutes ; gris, RVB, CMJN, palette ; couche de transparence, masques au pochoir, chemins de détourage, profils ICC, orientation.
- Couleur : RVB, CMJN, gris, espaces calibrés, L*a*b*, profils ICC, ton direct, plusieurs encres nommées à la fois, tables indexées.
- Codes : Code 128, Code 39, Code 93, ITF-14, Codabar, EAN-13, UPC-A, EAN-8, UPC-E, GS1 DataBar, compléments à deux et à cinq chiffres, code QR, Data Matrix, PDF417, Aztec.
- Parties interactives : champs de saisie, cases à cocher, listes déroulantes, groupes de boutons radio, boutons d'action, champs de signature, liens, signets, étiquettes de page, fils d'articles, calques, transitions de page.
- Normes : PDF/A pour l'archivage, PDF/X pour la presse, PDF/UA pour l'accessibilité, Factur-X pour la facture électronique, métadonnées XMP avec des schémas à vous, balisage complet des pages.
- Protection : chiffrement AES-256, mot de passe utilisateur et mot de passe propriétaire, droits par action.
- Lecture : ouvrir un PDF écrit par un autre programme et en lire les pages, les métadonnées, les liens, les champs de formulaire, les signets, les calques et la structure ; en reprendre une page entière dans le document qu'on écrit.
- Poids du fichier et délai jusqu'à la première page : réglage de la compression, flux d'objets, sortie linéarisée.
Ce qui est livré, et comment une construction se le procure
La livraison est un téléchargement depuis votre compte sur ce site. Connectez-vous et la page de téléchargement liste ce que couvre votre licence, chaque artefact figé sur une version : la roue Python côté Python, et la bibliothèque Rust compilée côté Rust. Un clic télécharge le fichier. Si vous avez payé par carte, le même lien est aussi envoyé à l'adresse que vous avez donnée.
La roue Python s'installe telle quelle dans un environnement virtuel. Le côté Rust se déclare dans votre manifeste en pointant la copie que vous avez téléchargée, si bien qu'une construction le résout depuis votre propre dépôt comme n'importe quelle autre dépendance épinglée — et le résout à l'intérieur d'un réseau qui n'atteint rien hors de lui-même.
Votre clé de licence se trouve sur la même page de compte que les fichiers et les factures. C'est une chaîne signée, lue à l'exécution depuis un fichier, une variable d'environnement ou un gestionnaire de secrets, et remise au document en un seul appel. Ce que la licence achète, c'est le droit de faire tourner le moteur compilé, ce que fixent les conditions générales de vente ; les sources restent chez HQF Development.
Une nouvelle version est publiée sur la même page : mettre à jour, c'est télécharger l'artefact voulu et y pointer votre dépendance. La clé de licence que vous détenez déjà continue de fonctionner.
Ce qu'il demande à la machine qui l'exécute
Elle fonctionne sur place. Tout ce dont elle a besoin est dans le paquet : les mesures des quatorze polices standard, l'unique profil de couleur qu'elle construit elle-même, les tables de codes. Une vérification lit le fichier livré lui-même et énumère chaque appel extérieur qu'il est capable de faire : elle échoue si un seul d'entre eux peut ouvrir une connexion, résoudre un nom sur le réseau ou démarrer une session chiffrée. Vos données restent sur la machine qui l'exécute.
C'est une clé de licence signée, remise par l'équipe à l'achat, qui la déverrouille. La clé se lit dans un fichier, dans une variable d'environnement ou dans un gestionnaire de secrets, et se donne au document en un seul appel. Tant qu'aucune clé n'est donnée, le moteur écrit une copie d'essai : chaque page sort avec un filigrane, chaque lettre est tracée comme une forme au lieu d'être écrite comme du texte, le fichier ne contient aucune police, ni formulaire ni structure ne sont écrits, et une poignée d'appels refusent net, en nommant la licence qu'ils réclament. C'est un essai qui fonctionne, sur la forme d'un document.
Comment un document se monte
Une seule forme, toujours : on crée un document, on y ajoute des ressources — une police, une image, un dessin, un dégradé — qui rendent chacune une poignée, on remplit un flux de contenu avec des opérateurs de dessin, on en fait une page, on ajoute la page au document, on demande les octets.
Trois moteurs de mise en page se tiennent au-dessus de ce socle et vous épargnent toute coordonnée : le flux de texte coupe un paragraphe à une largeur, le tableau mesure ses lignes sur ce qu'elles contiennent et se pagine tout seul, l'empilement pose les blocs les uns sous les autres. À côté : couleur, images, formulaires, navigation, métadonnées, conformité, protection, et lecture d'un PDF existant.
Le détail, avec ses chiffres
Les chiffres famille par famille, le même programme écrit des deux côtés, et la forme sous laquelle un refus revient.
La surface publique en un coup d'œil
La version 1.307.1 du moteur expose 44 modules publics et 220 noms à la racine de la bibliothèque Rust ; la face Python déclare 312 classes, parce qu'un seul espace de noms y réunit ce que le Rust range dans un module par famille. 109 exemples complets sont livrés avec, chacun en deux exemplaires jumeaux, un en Rust et un en Python. Avant toute intégration à la bibliothèque, chacun d'eux est exécuté dans les deux langages et les deux fichiers comparés octet par octet, pour qu'aucun ne vieillisse en silence.
| Famille | Ce qu'on y trouve |
|---|---|
| Le document et la page |
Document, Page, Content, Version, Compression, PageGroup, PageTurn, TabOrder
|
| Polices et texte |
Font, FontHandle, StandardFont, Type1Font, Type3Font, MissingGlyph, Run, WritingMode
|
| Mise en page |
TextFlow, Table, Row, Cell, Columns, ColumnWidth, RichText, Style, Rect, Shadow
|
| Blocs empilés, filets de tableau et tampons |
layout: Stack, Block, FittedTable, Rule, Stroke, Border, Padding, Margin, VerticalFlow, TaggedTable, Stamp
|
| Couleur |
Color, Rgb, Cmyk, Paint, CalGray, CalRgb, Lab, IccBased, Indexed, Separation, DeviceN, OutputIntent
|
| Images et ajustement |
Image, ImageHandle, ImageSpace, Resolution, Orientation, ClippingPath, FitBox, FitMode, Anchor, Turn, Mirror
|
| Dégradés, motifs, fonctions |
Axial, Radial, TilingPattern, Spacing, Function, Calculation, Op
|
| État graphique |
ExtGState, BlendMode, SoftMask, OverprintMode, BlackPointCompensation
|
| Dessins et calques |
Drawing, Layer, LayerConfiguration, LayerRule, LayerPolicy, LayerIntent, LayerPurpose, LayerWork, PageRole
|
| Codes |
Code128, Ean13, Ean8, UpcA, QrCode, DataMatrix, Pdf417, Aztec
|
| Formulaires et signature |
TextField, CheckBox, ChoiceField, RadioGroup, PushButton, SignatureField, FieldBorder, Signature, Signer, IncrementalUpdate
|
| Navigation |
Bookmark, Link, Article, PageLabel, OpenAction, PageFit, ViewerPreferences, Transition
et tout ce que contient le module des actions
|
| Annotations | le module des annotations : carrés, cercles, lignes, polygones et lignes brisées |
| Accessibilité |
StructureTree, StructElement, Marks, Scope, Artifact
|
| Métadonnées et normes |
metadata: Metadata, Xmp, XmpSchema, PdfA, PdfX, PdfUa, Invoice, Attachment; Standard, Report, Finding
|
| Protection |
Encryption, Permissions
|
| Lecture |
read: Reader, ImportedPage, Information, FormEntry, Outline, LayerEntry, Structure, Parts
|
| Licence et signalement |
License, Error, Warning, ResourceKind
|
| Bas niveau |
cos, filter, incremental, platform
|
Le plus court document utile
En Rust
use hqf_pdf::content::Content;
use hqf_pdf::cos::Name;
use hqf_pdf::{Document, License, Page};
let mut c = Content::new();
c.set_fill_rgb(0.20, 0.40, 0.80)?;
c.rect(50.0, 700.0, 495.0, 90.0)?;
c.fill();
let mut page = Page::a4();
page.content = c.into_bytes();
let mut doc = Document::new();
doc.set_license(License::from_key(key)?);
doc.set_info(Name::new("Title"), "hqf-pdf sample invoice");
doc.add_page(page)?;
let bytes = doc.to_bytes()?;
En Python
import hqf_pdf
content = hqf_pdf.Content()
content.set_fill(hqf_pdf.Rgb(0.20, 0.40, 0.80))
content.rect(50.0, 700.0, 495.0, 90.0)
content.fill()
page = hqf_pdf.Page.a4()
page.set_content(content)
document = hqf_pdf.Document()
document.set_license(hqf_pdf.License.from_key(key))
document.set_info("Title", "hqf-pdf sample invoice")
document.add_page(page)
data = document.to_bytes()
Du texte dans une police embarquée
Seuls les glyphes que le document trace entrent dans le fichier : une page qui contient une ligne contient de quoi écrire une ligne, pas la police entière. La mesure passe par le même objet que le tracé, et c'est ce qui maintient le texte dans son cadre.
En Rust
let font = Font::parse(fs::read(&font_path)?)?;
let handle = doc.add_font(font);
let mut c = Content::new();
c.begin_text();
c.set_font(&handle, 14.0)?;
c.text_position(60.0, 760.0)?;
for line in &lines {
c.show_glyphs(&handle.glyphs(line));
c.text_position(0.0, -28.0)?;
}
c.end_text();
let width = handle.measure(lines.last().map_or("", String::as_str), 14.0);
En Python
font = hqf_pdf.Font.from_path(font_path)
handle = document.add_font(font)
content = hqf_pdf.Content()
content.begin_text()
content.set_font(handle, 14.0)
content.text_position(60.0, 760.0)
for line in lines:
content.show_glyphs(handle.glyphs(line))
content.text_position(0.0, -28.0)
content.end_text()
width = handle.measure(lines[-1], 14.0)
Un paragraphe qui se coupe tout seul
En Rust
let flow = TextFlow::new(&handle, 11.0).align(align).leading(15.0);
let lines = flow.break_lines(words.paragraph, box_width);
c.begin_text();
flow.draw(&mut c, &lines, x, top, box_width)?;
c.end_text();
En Python
flow = hqf_pdf.TextFlow(handle, 11.0, align=align, leading=15.0)
lines = flow.break_lines(words.paragraph, box_width)
flow.draw(content, lines, x, top, box_width)
Un tableau qui se poursuit page après page
La hauteur d'une rangée n'est jamais donnée : elle se mesure sur le texte que la rangée porte, une fois connues les largeurs de colonnes. C'est ce qui permet au tableau de décider, rangée après rangée, si la suivante tient encore. Ajuster ne dessine rien : cela renvoie la géométrie, si bien qu'un tableau se mesure avant que quiconque ne décide où il va. Appelez-le une fois par page jusqu'à ce qu'il annonce qu'il a fini, ou demandez tout l'enchaînement en un seul appel et recevez un placement par page.
En Rust
let columns = Columns::new(
vec![
ColumnWidth::Fraction(1.0),
ColumnWidth::Points(46.0),
ColumnWidth::Points(94.0),
ColumnWidth::Points(94.0),
],
TABLE_WIDTH,
)?;
let mut table = Table::new(columns);
table.header(1);
table
.rule(Rule::Frame, Stroke::black(0.8))
.rule(Rule::HorizontalOther, Stroke::new(0.25, Rgb::gray(0.75)));
let pad = Padding::symmetric(5.0, 4.0);
table.push(
Row::new()
.cell(Cell::new(text, 9.0, label).padding(pad))
.cell(Cell::new(text, 9.0, value).padding(pad).align(Align::Right)),
);
let mut start = 0;
loop {
let placed = table.fit(TableFrame::new(MARGIN, TOP, TOP - BOTTOM), start)?;
let mut content = Content::new();
placed.draw(&mut content)?;
let mut page = Page::a4();
page.content = content.into_bytes();
doc.add_page(page)?;
match placed.outcome() {
FitProgress::Done => break,
FitProgress::BoxFull { next_row } => start = next_row,
FitProgress::RowTooTall { row } => {
return Err(format!("row {row} is taller than a page").into());
}
outcome => return Err(format!("the table fitted to {outcome:?}").into()),
}
}
En Python
columns = hqf_pdf.Columns(
[
hqf_pdf.ColumnWidth.fraction(1.0),
hqf_pdf.ColumnWidth.points(46.0),
hqf_pdf.ColumnWidth.points(94.0),
hqf_pdf.ColumnWidth.points(94.0),
],
TABLE_WIDTH,
)
table = hqf_pdf.Table(columns)
table.header(1)
table.rule(hqf_pdf.Rule.frame(), hqf_pdf.Stroke(0.8))
grey = hqf_pdf.Rgb.gray(0.75)
table.rule(hqf_pdf.Rule.horizontal_other(), hqf_pdf.Stroke(0.25, grey))
pad = hqf_pdf.Padding.symmetric(5.0, 4.0)
table.push(
hqf_pdf.Row(
[
hqf_pdf.Cell(font, 9.0, label, padding=pad),
hqf_pdf.Cell(font, 9.0, value, padding=pad, align=hqf_pdf.Align.Right),
]
)
)
start = 0
while True:
placed = table.fit(MARGIN, TOP, TOP - BOTTOM, start)
content = hqf_pdf.Content()
placed.draw(content)
page = hqf_pdf.Page.a4()
page.set_content(content)
document.add_page(page)
if placed.done:
break
start = placed.next_row
Comment les refus reviennent
En Rust, tout ce qui peut refuser renvoie un résultat qui contient le type d'erreur de la bibliothèque : une énumération ouverte de 96 variantes, chacune nommant précisément ce qui s'est passé — une police mal formée, une image mal formée, une police absente, une page absente, deux champs de même nom, quelque chose qui manque pour l'imprimerie, quelque chose qui manque pour l'archivage, quelque chose que le niveau de conformité interdit, une rangée plus haute que sa boîte, aucune licence, une licence invalide, un fichier chiffré. Un client filtre sur le cas exact plutôt que sur le texte d'un message.
Une donnée venue de l'extérieur — un PDF, une police, une image, un profil — qui ne tient pas est un résultat ordinaire, renvoyé comme tel et jamais un plantage. Il reste 16 arrêts brutaux sur les chemins de production. Chacun énonce l'invariant qu'il tient, et aucun n'est atteignable depuis une valeur fournie par l'appelant.
En Python, les mêmes refus reviennent sous forme d'exceptions, toutes celles de la bibliothèque sous une seule classe qu'un client attrape d'un bloc. Les 2 cas de mise en page — une ligne de tableau plus haute que le cadre qu'on lui a donné, et un bloc empilé dans le même cas — partagent en dessous une classe à eux : une boucle de pagination les attrape ensemble par cette classe commune, ou chacun séparément.
À côté des refus, des remarques. L'écriture des octets a une forme qui rend compte : elle renvoie les octets et la liste des avertissements que le document formule sur lui-même ; le fichier est écrit et valide dans les deux cas, et un avertissement désigne un document qui n'est probablement pas celui que le client voulait. L'un d'eux nomme une police entrée contre l'autorisation inscrite dans le fichier de police lui-même : c'est la licence sous laquelle la police a été achetée qui tranche, et la remarque met la question devant le client, à qui elle revient. Le contrôle contre une norme va plus loin : il renvoie tout ce que l'on sait se dresser entre le document et cette norme, sans rien écrire ni rien changer.
Ce que la version courante couvre
Version 1.307.1 du moteur : écriture et lecture de PDF, les trois générations de protection en lecture et le chiffrement AES-256 en écriture, PDF/A-2, PDF/A-3 et PDF/A-4, PDF/X-4, PDF/UA-1 et PDF/UA-2, Factur-X, balisage complet, signature PAdES dont le client fournit le signataire, sortie linéarisée, flux d'objets, et reprise de pages venues d'autres fichiers.
Le manuel
Le manuel complet du moteur — chaque appel, chaque réglage, chaque piège — est remis lors d'un échange direct avec l'équipe.
Ce qui aide vraiment, c'est la partie qui répond à votre question, avec l'exemple qui correspond à votre cas. Le chemin est donc celui-ci. Vous créez un compte sur ce site, vous nous écrivez ce que vous cherchez à faire — une facture qui contient son XML, un tableau de deux mille lignes, une feuille qui part en presse — et l'équipe vous renvoie la partie du manuel qui traite exactement cela, avec le code des deux côtés, Rust et Python.
Vous parlez aux gens qui ont écrit le moteur, et la réponse arrive taillée pour votre projet. En attendant, les 109 exemples livrés avec la bibliothèque couvrent l'essentiel de ce qu'on peut lui demander, et chacun s'exécute tel quel.
Où aller ensuite
Tout ce qu'elle met sur une page, groupe par groupe L'autre voie : demander le document au service Créez un compte, et dites-nous ce que vous construisez Posez-nous la question que cette page a laissée de côté