Files
BookIA/typographie-francaise.lua
Vincent Laperrousaz 2dc8087eab Mise en page KDP: conformité PDF/EPUB, marges, notes, titres
- PDF: police Cochineal, interligne/alinéa, marges KDP (0.4in), exposants
  de note à 7.5pt, correctif centrage \newline dans titlesec (bug
  reproductible), contraste surtitre/titre pour Introduction/Conclusion/
  Préface via \eyebrowtitle
- EPUB: epub.css conforme aux règles KDP (police sur body, pas de
  couleur forcée, marges en %), structure-epub.lua reconstruit les
  titres LaTeX bruts, liens de retour bidirectionnels sur les notes
  (méthode KDP recommandée), séparateur deux-points harmonisé dans la
  TOC (Introduction/Conclusion/Parties)
- Nouveaux filtres: typographie-francaise.lua (espaces insécables),
  structure-epub.lua
- compiler-livre.sh: ajustements marges et appel des filtres
2026-07-27 20:53:40 +02:00

103 lines
3.8 KiB
Lua

--[[
typographie-francaise.lua — filtre Pandoc
Applique les règles d'espacement de la typographie française au moment
de la compilation, pour le PDF comme pour l'EPUB. Le manuscrit reste
écrit normalement (espace ordinaire) : c'est ce filtre qui convertit
ces espaces en espaces insécables au bon endroit.
Pourquoi un filtre et non babel :
Pandoc charge babel avec l'option « shorthands=off » (codée en dur dans
son gabarit LaTeX), ce qui désactive définitivement les raccourcis de
babel-french chargés de l'espace fine avant les ponctuations doubles.
Aucune réactivation depuis le préambule n'est possible : les raccourcis
ne sont même pas déclarés (vérifié par sonde sur le catcode de « : »,
qui reste à 12). Le filtre traite donc le problème en amont, une fois
pour les deux formats de sortie.
Principe directeur : NORMALISER, JAMAIS INVENTER.
Le filtre ne transforme que les espaces déjà présents dans le
manuscrit. Un titre anglais cité en note (« ...Teen's Suicide? »), qui
n'a pas d'espace avant son point d'interrogation, reste donc intact —
ce qui est la règle : on ne francise pas la ponctuation d'un titre
étranger.
Transformations appliquées :
1. Espace fine insécable (U+202F) avant : ; ! ? et avant »
2. Espace fine insécable après «
3. Espace insécable (U+00A0) avant %
4. Espace fine insécable dans les groupes de milliers (150 000)
Rendu LaTeX : U+202F et U+00A0 sont déclarés dans preambule.tex
(\DeclareUnicodeCharacter) pour produire un crénage rigide, qui ne
s'étire pas à la justification.
]]
local FINE = "\u{202F}" -- espace fine insécable
local INSEC = "\u{00A0}" -- espace insécable de largeur normale
-- Ponctuations qui réclament une espace fine insécable AVANT elles.
-- Le guillemet fermant est testé à part, et non dans la classe
-- [:;!?] : les classes de caractères de Lua travaillent sur les OCTETS,
-- si bien que « » » (0xC2 0xBB) placé dans une classe y ferait aussi
-- entrer « « » (0xC2 0xAB), qui partage son premier octet — bug
-- constaté, le guillemet ouvrant recevait l'espace du mauvais côté.
local function needs_fine_before(text)
return text:match("^[:;!?]") ~= nil or text:match("^»") ~= nil
end
local function ends_with_opening_quote(text)
return text:match("«$") ~= nil
end
local function ends_with_digit(text)
return text:match("%d$") ~= nil
end
-- Groupe de milliers : exactement trois chiffres, éventuellement suivis
-- d'une ponctuation, mais jamais d'un quatrième chiffre.
local function is_thousands_group(text)
return text:match("^%d%d%d$") ~= nil or text:match("^%d%d%d[^%d]") ~= nil
end
function Inlines(inlines)
local out = pandoc.List()
local i = 1
while i <= #inlines do
local cur = inlines[i]
local nxt = inlines[i + 1]
-- « ... » : espace fine collée au guillemet ouvrant
if cur.t == "Str" and ends_with_opening_quote(cur.text)
and nxt and nxt.t == "Space" then
out:insert(pandoc.Str(cur.text .. FINE))
i = i + 2
elseif cur.t == "Space" and nxt and nxt.t == "Str" then
if needs_fine_before(nxt.text) then
-- espace fine avant : ; ! ? »
out:insert(pandoc.Str(FINE .. nxt.text))
i = i + 2
elseif nxt.text:match("^%%") then
-- espace insécable avant le signe pour cent
out:insert(pandoc.Str(INSEC .. nxt.text))
i = i + 2
elseif is_thousands_group(nxt.text) and i > 1
and inlines[i - 1].t == "Str"
and ends_with_digit(inlines[i - 1].text) then
-- séparateur de milliers insécable : 150 000, 32 millions
out:insert(pandoc.Str(FINE .. nxt.text))
i = i + 2
else
out:insert(cur)
i = i + 1
end
else
out:insert(cur)
i = i + 1
end
end
return out
end