diff --git a/src/py/README.md b/src/py/README.md
index f5daf3d95592df44a0ad695728bb85e4c9f7373f..efb45c610f54f81f4d585aee1c9e601e8bf3aef6 100644
--- a/src/py/README.md
+++ b/src/py/README.md
@@ -7,9 +7,9 @@ la structure logique (titre, sous-titre, paragraphe, ...) d'un document pdf.
 Pour ce faire, le programme utilise des outils d'extraction du texte contenu
 dans des fichiers pdf.
 
-Ce programme est écrit en python 3. Il s’intitule pdf2blocks.py. 
+Ce programme est écrit en python (version 3). Il s’intitule pdf2blocks.py.
 
-Il prend en entrée un fichier pdf, contenant du texte, des tableaux
+Il prend en argument un fichier pdf, contenant du texte, des tableaux
 et des images.
 
 ## Utilisation
@@ -23,38 +23,142 @@ Le résultat est écrit sur la sortie standard. Il utilise la syntaxe
 pour décrire les titres, sous-titres, …, et séparer les paragraphes.
 
 Il utilise néanmoins 10 niveaux de titres et sous-titres, au lieu des six
-niveaux maximum du markdown. Ceci n'a pas d'incidence sur le résultat.
-
-# &&&&&&&&&&&&&&&&&&&&&&&&&
+niveaux maximum du markdown. Ceci n'a pas d'incidence sur la compatibilité
+du résultat avec le markdown.
+
+#### Exemple de résultat :
+###### Markdown
+    ________________________________
+    *page 7*
+
+    ##  Vignoble Beaujolais – Coteaux du
+    ## Lyonnais
+
+    ### Données du réseau : 27 parcelles renseignées sur 28
+
+    ### Stades phénologiques
+
+    #### Cépages
+    Gamay
+
+    Chardonnay
+
+    #### Le plus tardif
+    baies de la taille d'un pois, les
+    grappes pendent (31)
+    les baies se touchent (31-33)
+
+    #### Majoritaire
+    les baies se touchent (31-33)
+
+    #### Le plus avancé
+    fermeture de la grappe (33)
+
+###### Conversion html
+La syntaxe markdown est destinée à être transcrite directement en html.
+La sortie ci-dessus devient alors :
+
+    <hr />
+    <p><em>page 7</em></p>
+    <h2> Vignoble Beaujolais – Coteaux du</h2>
+    <h2>Lyonnais</h2>
+    <h3>Données du réseau : 27 parcelles renseignées sur 28</h3>
+    <h3>Stades phénologiques</h3>
+    <h4>Cépages</h4>
+    <p>Gamay</p>
+    <p>Chardonnay</p>
+    <h4>Le plus tardif</h4>
+    <p>baies de la taille d'un pois, les
+    grappes pendent (31)
+    les baies se touchent (31-33)</p>
+    <h4>Majoritaire</h4>
+    <p>les baies se touchent (31-33)</p>
+    <h4>Le plus avancé</h4>
+    <p>fermeture de la grappe (33)</p>
 
 ## Dépendances
 
-Il utilise actuellement *pdftotext* et *pdftohtml*, deux outils basés
-sur la librairie poppler, dérivée de Xpdf, avec laquelle ils sont
-distribués.
+*pdf2blocks* utilise actuellement *pdftotext* et *pdftohtml*, deux outils basés
+sur la librairie poppler, dérivée de Xpdf. Ces deux outils prennent en entrée
+un fichier pdf.
+
+#### pdftotext
+*pdftotext* est destiné à produire une sortie en texte brut,
+lisible dans une console texte par exemple.
+
+Il peut toutefois écrire ses résultats dans un format xml, avec la structure
+suivante :
+
+    <doc>
+      <page>
+        <flow>
+          <block>
+            <line>
+              <word>Un</word>
+              <word>mot</word>
+            </line>
+            (…)
+
+Les balises *block*, *line* et *word* contiennent des informations de
+position, à savoir les coordonnées absolues du plus petit rectangle qui
+contient l'élément désigné. Ces coordonnées sont exprimées à l'aide de
+quatre attributs : *xMin*, *yMin*, *xMax*, *yMax*, qui semblent exprimés
+en pixels. En particulier, pour un mot, `yMax - yMin` correspond à la taille
+de la police de caractères exprimée en pixels (*px*).
+
+L'outil *pdftotext* est particulièrement efficace pour identifier les longs
+paragraphes contenant plusieurs lignes. En revanche, il ne permet pas
+d'extraire correctement  la hiérarchie des titres et sous-titres.
+La sortie de l’outil ne donne pas suffisamment d'informations sur les
+polices de caractères : le nom de la police de caractères,
+les styles (gras, italique, ...) et les couleurs du texte ne sont pas
+décrits.
+
+#### pdftohtml
+*pdftohtml* est un outil qui vise à produire une page html qui ressemble
+au document pdf. Pour cela, il n'a pas besoin de respecter un ordre
+de lecture, car les éléments d'une page html peuvent être localisés.
+En revanche, il contient une description précise des polices de caractères
+utilisées dans le document.
+
+Plutôt qu'une page html, l'outil peut écrire ses résultats au format xml.
+
+Voici un exemple de sortie xml de l'outil *pdftohtml* :
+
+     <pdf2xml producer="poppler" version="0.80.0">
+     <page number="1" position="absolute" top="0" left="0" height="1262" width="892">
+     	<fontspec id="0" size="14" family="ABCDEE+Calibri" color="#000000"/>
+     	<fontspec id="1" size="10" family="ABCDEE+Verdana" color="#000000"/>
+     	<fontspec id="2" size="19" family="ABCDEE+Calibri,Bold" color="#000000"/>
+     	<fontspec id="3" size="16" family="ABCDEE+Calibri,Bold" color="#000000"/>
+     <text top="1216" left="76" width="16" height="17" font="0">1  </text>
+     <text top="1216" left="755" width="67" height="15" font="1">BSV n° 15 </text>
+     <text top="403" left="87" width="239" height="21" font="2"><b>A RETENIR CETTE SEMAINE </b></text>
+     <text top="473" left="87" width="54" height="18" font="3"><b>Météo </b></text>
+
+On remarque que le texte n'est plus donné mot par mot mais ligne par ligne.
+Ce n'est pas tout à fait exact : les lignes sont découpées en segments
+de même fonte. Par exemple, la la ligne
+
+> Le 2<sup>nd</sup> grand évènement,
 
-**pdftotext** peut générer un document xml contenant le texte du document
-pdf structuré en pages, *flows* et *blocks*. La structure renvoyée est
-généralement plutôt acceptable en termes de restitution du document
-pour une lecture dans une console texte.
+sera découpé en trois parties :
+- Le 2
+- nd
+- grand évènement,
 
-Elle est particulièrement efficace pour isoler de long paragraphes
-mais est inadaptée à l'identification de le hiérarchie des titres
-et sous-titres car elle ne donne pas d'information sur la taille,
-les styles (gras, italique, ...) et les couleurs des polices de caractère.
+Il en sera de même pour la ligne
 
-**pdftohtml** peut aussi générer un document xml, qui contient, pour chaque mot,
-sa police de caractères, le style, la taille et la couleur qui y
-sont appliqués ; le but de cet outil étant de restituer une page html
-visuellement la plus proche possible du document initial.
+> Le ***petit*** papillon.
 
-Le résultat de *pdf2blocks* est écrit sur la sortie standard, en utilisant
-la syntaxe markdown afin de permettre une conversion facile dans
-différents formats. D'autres sorties pourraient être envisagées.
+car un nouveau style (gras, italique) de la même police est défini comme une
+nouvelle police. Cependant, rien, dans le fichier xml, n'indique ce découpage.
+Il y a juste une succession de balises text. On peut toutefois se baser sur
+la valeur de l'attribut *top*. Mais celyui-ci n'est pas toujours égal
+dans une même ligne (notamment pour les indices et exposants).
 
-Le chapitre suivant décrit de quelle façon *pdf2blocks* récupère
-le résultat de *pdftotext* et *pdftohtml*, et quels traitements
-sont appliqués.
+Enfin, le découpage de colonnes fonctionne mal, et il arrive que du texte de
+deux colonnes successives se retrouve dans la même balise text.
 
 
 ## L'algorithme de pdf2blocks
@@ -64,78 +168,69 @@ Le programme *pdf2blocks* commence par lancer la commande suivante :
      pdftotext -bbox-layout -eol unix /path/to/file.pdf
 
 Le résultat de cette commande est stockée dans une liste python. Cette liste
-a été mal-nommée ***blocks***, qui est devenu le terme utilisé pour
+a été nommée ***blocks***, qui est devenu à l'usage le terme utilisé pour
 faire référence à "la liste dans laquelle est stocké le résultat de
 pdftotext".
 
-Chaque élément de cette liste est un dictionnaire python ayant
-la structure suivante :
+Cette liste reprend la structure xml de *pdftotext* à partir de la balise
+*block*, qui est le niveau de découpage qui nous a paru le mieux adapté.
+Les balises *page* ont été remplacées par un attribut de *blocks* donnant
+le numéro de page, et les balises *flow* par un nombre incrémenté à chaque fois
+que la balise est rencontrée.
+Ainsi, on peut identifier les blocs qui font partie d'un même flow.
+
+Les éléments de la liste *blocks* ont donc la structure suivante :
 
 - **page :** Le numéro de page.
-- **flow :** Un numéro, incrémenté à chaque fois qu'une balise "flow" est
-  rencontrée, afin d'identifier quels blocs appartiennent à un même *flow*.
-  Ceci n'est actuellement pas utilisé dans les traitements.
-- **x_min**, **x_max**, **y_min** et **y_max** : Les coordonnées du bloc
-  dans la page.
-- **h_min** et **h_max** : Le hauteur minimum et maximum des lignes du
-  bloc (calculée).
-- **nb_cars** et **nb_words** : Le nombre de caractères et de mots du bloc
-  (calculés aussi).
+- **flow :** Un identifiant unique pour chaque balise flow.
+- #-# **x_min**, **x_max**, **y_min** et **y_max** : Les coordonnées du bloc
+  dans la page
+- **lines :** Une liste, contenant les lignes du bloc. C'est aussi une liste
+  de dictionnaires, contenant :
+    - **words :** Une autre liste de dictionnaires, qui contient :
+        - **text :** le mot tel que renvoyé par *pdftotext*.
+
+Lors de l'extraction de ces informations depuis le résultat xml,
+un certain nombre d'attributs sont calculés et ajoutés à la structure :
+
+###### Pour *blocks* :
+- #-# **h_min** et **h_max** : Les hauteurs minimum et maximum des lignes du
+  bloc (calculées à partir des valeurs *height* de chaque ligne,
+  voir ci-dessous).
+- #-# **nb_cars** et **nb_words** : Le nombre de caractères et de mots du bloc.
 - **flags :** Une valeur sur 16 bits, initialisée à 0x0000, destinée à
   accueillir le résultat binaire d'un certain nombre de traitements.
-- **lines :** Une liste, contenant les lignes du bloc. C'est aussi une liste
-  de dictionnaires, ayant la structure suivante :
-    - **text :** Le texte contenu dans cette ligne. Cette ligne n'est pas
-      un résultat direct de *pdftotext*, dont le résultat est donné mot par mot.
-      Il est composé de chaque mot de la ligne séparé d'une espace, sauf si
-      le premier mot est un unique caractère plus grand que les suivants
-      (dans ce cas on considère que c'est un effet de texte).
-    - **height :** La valeur ```yMax - yMin``` (des coordonnées de la ligne
-      renvoyées par *pdftotext*).
-    - **nb_words**, **nb_cars** et **flags** : sont les mêmes que pour les 
-      blocs, avec les informations relatives à la ligne.
-    - **words :** Une autre liste de dictionnaires, avec la structure :
-        - **height :** la hauteur de ligne, comme pour la structure *lines*,
-        - **text :** qui est finalement le mot tel que renvoyé par *pdftotext*. 
-
-### pdftohtml
-Ensuite, le programme lance la commande *pdftohtml* :
-
-     pdftohtml -xml -i -stdout /path/to/file.pdf
-
-Le résultat de cette commande est aussi un fichier xml, contenant principalement
-deux informations :
-
-- les polices de caractères utilisées dans le document,
-- le texte composant le document, avec les polices de caractères associées.
 
-Le texte est renvoyé par segments de même police. Par exemple, la la ligne
+###### Pour *lines*
+- **text :** Le texte contenu dans cette ligne.
+  Il est composé de chaque mot de la ligne séparé d'une espace, sauf si
+  le premier mot est un unique caractère plus grand que les suivants
+  (dans ce cas on considère que c'est un effet de texte
+  et l'espace n'est pas ajoutée).
+- **height :** La valeur ```yMax - yMin``` (des coordonnées de la ligne
+  renvoyées par *pdftotext*, que nous n'avons pas conservées).
+- #-# **nb_words**, **nb_cars** et **flags** : sont les mêmes que pour les
+  blocs, avec les informations relatives à la ligne de texte.
 
-> Le 2<sup>nd</sup> grand évènement,
-
-sera découpé en trois parties :
-- Le 2
-- nd
-- grand évènement,
+###### Pour *words*
+- **height :** la hauteur de ligne, comme pour la structure *lines*,
 
-Il en sera de même pour la ligne
 
-> Le ***petit*** papillon.
+### pdftohtml
+Ensuite, le programme *pdf2blocks* lance la commande *pdftohtml* :
 
-car un nouveau style (gras, italique) de la même police est défini comme une
-nouvelle police.
+     pdftohtml -xml -i -stdout /path/to/file.pdf
 
-#### Les polices de caractères renvoyées par *pdftohtml*
+#### Les fontes renvoyées par *pdftohtml*
 Les polices de caractères renvoyées par *pdftohtml* sont stockées dans une liste
-python, nommée **fontspec** (du nom de la balise xml associée) et rapidement
-réduite en **fonts**. Les éléments de cette liste sont des dictionnaires python
-ayant la structure suivante :
-
-- **id :** Un identifiant unique pour désigner la police de caractères, utilisé
-  pour associer une portion de texte à sa police.
-- **size :** La taille de police de caractères, en "pixels" (px).
-- **family :** Le nom de la police de caractères, éventuellement suivi 
-  d'attributs de style séparés par des virgules. Exemples :
+python, nommée **fontspec** (du nom de la balise xml associée).
+Les éléments de cette liste sont des dictionnaires python ayant la structure
+suivante :
+
+- **id :** Un identifiant unique pour désigner la fonte.
+- **size :** La taille de la fonte, en "pixels" (px).
+- **family :** Le nom de la fonte, tel que renvoyé par *pdftohtml*,
+  éventuellement suivi d'attributs de style séparés par des virgules. Exemples :
     - "ABCDEE+Calibri"
     - "ABCDEE+Calibri,Bold"
     - "ABCDEE+Calibri,BoldItalic"
@@ -143,132 +238,144 @@ ayant la structure suivante :
 - **color :** La couleur du texte, en format html. Exemples :
     - "#000000"
     - "#b366b3"
+- **nb_cars** n'est pas présent dans le résultat xml de la commande
+  *pdftohtml*. Il est calculé lors de l'extraction du texte et contient
+  le nombre de caractères utilisant cette fonte.
 
 #### Le texte renvoyé par la commande *pdftohtml*
-Les segments de texte renvoyés par *pdftohtml* représentant la plupart du temps
-une ligne de texte entière, la liste qui les contient a été nommée **lines**,
-ce qui s'est avéré assez mal choisi. Ses éléments ont la structure suivante :
+Les segments de texte renvoyés par *pdftohtml* représentent la plupart du temps
+une ligne de texte entière. La liste qui les contient a été nommée **segments**.
+Ses éléments, extraits du résultat xml de *pdftohtml*,
+ont la structure suivante :
 
-- **text :** Le texte.
-- **font :** L'*id* de sa police de caractères (voir *fontspec* ci-dessus).
-- **page :** Le numéro de page.
-- **top**, **left**, **width** et **height** : Ces attributs sont ceux de la
-  balise xml "*text*" et permettent de localiser la portion de texte dans la
-  page.
+- **text :** Le texte du segment, contenu de la balise <text> de pdftohtml.
+- **font :** L'identifiant de la fonte pour ce segment.
+- **page :** Le numéro de page du segment.
+- **top**, **left**, **width** et **height** : Les coordonnées de la zone de
+  texte dans la page.
 
 ### Traitements
 À ce point, nous avons trois listes, **blocks**,
-**fontspec** et **lines**, dont la structure est décrite ci-dessus.
+**fontspec** et **segments**, dont la structure est décrite ci-dessus.
 
 La plupart des traitements sont effectués sur la liste **blocks**, qui contient
-une base de la structure du document. Le but est d'identifier le rôle de chacun
-des blocs (titre, sous-titre, ...).
+une base de la structure logique du document. Le but est d'identifier le rôle
+de chacun des blocs (titre, sous-titre, ...).
 
 La liste **blocks** contient le résultat de la commande *pdftotext*, dont le
 but est de restituer un texte lisible de haut en bas dans une console texte.
 C'est pourquoi nous considérons que les blocs sont ordonnés suivant le sens
-de lecture et aucun traitement n'est effectué pour ré-ordonner les blocs.
+de lecture et aucun traitement n'est effectué pour les ré-ordonner.
 
 Nous avons remarqué quelques erreurs d'ordonnancement des blocs, notamment dans
-le cas de documents multi-colonnes. Ceci est dû aux limites de l'algorithme
-de *pdftotext*, mais n'ayant pas de meilleur algorithme à suggérer, l'ordre
-des blocs est conservé tel quel pour le moment.
+le cas de documents multi-colonnes. Ceci est dû aux limites de *pdftotext*.
 
 Les chapitres suivants décrivent les traitements, effectués séquentiellement,
 sur les données issues des deux commandes *pdftotext* et *pdftohtml*.
 
-#### Taille de la police par défaut
-La taille de police de caractères la plus utilisée (en nombre de caractères)
-est considérée être la police par défaut.
+#### Taille de la fonte par défaut
+La fonte la plus utilisée (en nombre de caractères)
+est considérée être la fonte par défaut.
 
 Pour des raisons historiques (un grand nombre d'essais de traitements ont
-d'abord été faits), il a d'abord été compté les caractères pour chaque hauteur
-de ligne (arrondi à l'entier le plus proche de l'attribut **height** dans les
-lignes de la structure de **blocks**, qui semble correspondre à la taille de
-la police de caractères en pixels). 
+d'abord été faits), ce décompte a été effectué sur la liste **blocks**.
+Il consiste à compter le nombre de caractère pour chaque hauteur de ligne
+(attribut *height* des listes **lines**). Plus précisément, la hauteur
+de ligne considérée était l'entier le plus proche de la valeur de *height*.
 
 Il aurait été possible d'effectuer un comptage plus précis à l'aide des listes
-**lines** et **fontspec**, et même d'identifier précisément la police par
-défaut, toutefois il s'est avéré que l'imprécision de la première méthode
-était plus efficace. En effet, il n'est pas rare que les attributs de la police
+**lines** et **fontspec**, et même d'identifier précisément la fonte par
+défaut. Toutefois il s'est avéré que l'imprécision de la première méthode
+était plus efficace. En effet, il n'est pas rare que les attributs de la fonte
 par défaut changent au cours du document, par inattention du rédacteur ou
-du fait de co-écriture (le texte étant alors écrit à l'aide de logiciels avec
+du fait de co-écritures (le texte étant alors écrit à l'aide de logiciels avec
 des paramétrages différents, sur différentes machines, etc...).
 
-Choisir un taille de police par défaut, permet ensuite de faire de nombreuses
-hypothèses, comme par exemple *les polices plus petites sont utilisées pour*
-*les renvois en bas de page, les commentaires ou l'ours et peuvent*
-*être ignorées*, ou encore *les polices plus grandes sont probablement*
-*des titres*, …
+Déterminer la taille de la fonte par défaut permet de faire de nombreuses
+hypothèses sur l’usage du texte possédant une fonte donnée :
+Par exemple,  les fontes de tailles plus petites que la fonte par défaut
+sont utilisées pour les bas de page, les commentaires ou l'ours.
+Les fontes de taille plus grandes que la fonte par défaut sont utilisées
+pour les titres de section.
 
-Pour faciliter les tests sur ces différentes hypothèses, les éléments de
-**blocks** on été marqués ainsi :
+Les éléments de la liste blocks on été classées en fonction de  leur taille
+de fonte. Trois classes ont été définies : small font, big font et default font.
+Les résultats de cette classification sont stockés dans la clé flags
+des dictionnaires de la liste **blocks**. Ainsi :
 
-> - Si blocks[i]['h_max'] < default_font_size alors blocks[i]['flags'] |= SMALL_FONT
-> - Si blocks[i]['h_min'] > default_font_size alors blocks[i]['flags'] |= BIG_FONT`
-> - Si if blocks[i]['lines'][j]['height'] < default_font_size alors blocks[i]['lines'][j]['flags'] |= SMALL_FONT
-> - Si blocks[i]['lines'][j]['height'] > default_font_size alors blocks[i]['lines'][j]['flags'] |= BIG_FONT`
+> - Si `blocks[i]['h_max'] < default_font_size` alors `blocks[i]['flags'] |= SMALL_FONT`
+> - Si `blocks[i]['h_min'] > default_font_size` alors `blocks[i]['flags'] |= BIG_FONT`
+> - Si `blocks[i]['lines'][j]['height'] < default_font_size` alors
+  `blocks[i]['lines'][j]['flags'] |= SMALL_FONT`
+> - Si `blocks[i]['lines'][j]['height'] > default_font_size` alors
+  `blocks[i]['lines'][j]['flags'] |= BIG_FONT`
 
 
 #### Détection des pieds de page
 
-On appelle "pied de page" la numérotation des pages, quelques mots pour
-identifier le documents, comme par exemple "BSV n°17 du 15 juin 2019",
-et tout ce qui est écrit de la même façon en bas de chaque page du document.
+Les "pieds de page" d’un document sont une zone de texte répétée
+à chaque fin de page contenant par exemple le numéro de la page
+et un texte caractéristique du document comme son titre ou ses auteurs.
 
 Les notes de bas de page ne sont pas considérés comme faisant partie
 des pied de page.
 
-L'algorithme consiste à tester si la dernière ligne de chaque page est
-la même, en ne considérant que les caractères alphabétiques ([a-zA-Z]),
-afin de ne pas prendre en compte une éventuelle numéroation de pages.
+L'algorithme utilise les listes *lines* contenues dans **blocks**.
+Il consiste à tester si la dernière ligne de chaque page
+a le même contenu textuel, en ne considérant que les caractères alphabétiques
+([a-zA-Z]). Les caractères numériques ne sont pas pris en compte afin que
+les numéros de pages soient considérés comme des pieds de page.
 
-Si c'est le cas, alors on teste ensuite l'avant-dernière ligne,
-et ainsi de suite.
+Tant que des lignes sont détectées comme étant des pieds de page,
+on teste la ligne précédente, et ainsi de suite.
 
-De la même façon que pour les tailles de police de caractère, les lignes
+De la même façon que pour les tailles de fontes, les lignes
 trouvées sont marquées en utilisant le marqueur BOTTOM_PAGE.
 
 
-#### Attribution de polices de caractères aux blocs
-Il s'agit d'attribuer l'*id* d'une police de **fontspec** à chaque ligne
+#### Attribution de fontes aux blocs
+Il s'agit d'attribuer l'*id* d'une fonte de **fontspec** à chaque ligne
 de la liste **blocks**.
 
-**Afin d'aviter les méprises** entre la liste **lines** (utilisée ici) et
-la liste de même nom que l'on trouve dans la structure de **blocks**, nous
-appellerons cette dernière ***b-lines*** (pour *block-lines*).
+Par simplification de langage, on parlera de la liste **lines** pour désigner
+la concaténation des sous-listes *lines* de la liste **blocks**. Parcourir
+la liste **lines** consiste à considérer dans l'ordre les lignes renvoyées
+par la commande *pdftotext*. De même, parcourir la liste de **segments**
+consiste à considérer les lignes de texte renvoyées par la commande *pdftohtml*.
 
-L'algorithme consiste, pour chaque *b-line*, à calculer sa
+L'algorithme consiste, pour chaque ligne de **lines**, à calculer sa
 [distance de Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance)
-avec chaque ligne de la même page dans la liste **lines**.
-La ligne ayant le meilleur score (donc la distance la plus faible) est
-considérée comme étant celle qui correspond, et sa police de caractères est
-attribuée à la *b-line*.
+avec chaque ligne de la même page dans la liste **segments**.
+Le segment donnant le meilleur score (donc la distance la plus faible) est
+considéré comme étant celui qui correspond à la ligne. On attribue alors à cette
+ligne la fonte du segment correspondant.
 
-Ce traitement est nécessaire du fait de la segmentation des lignes renvoyées
-par *pdftohtml*.
+Ce calcul de distance est nécessaire du fait de la segmentation des lignes
+renvoyées par *pdftohtml*, qui ne correspondent pas exactement aux lignes
+renvoyées par *pdftotext*. Par ailleurs, l'ordre des lignes dans une même
+page est parfois différent entre les deux outils.
 
 À noter que cet algorithme n'est pas du tout optimisé. Il a été écrit pour
 tester le principe, qui semble satisfaisant, mais il nécessite d'être
 réécrit pour que son exécution soit plus rapide.
 
 
-#### Regroupement de polices de caractères
-L'objectif est de réduire autant que possible le nombre de polices de caractères
+#### Regroupement de fontes
+L'objectif est de réduire autant que possible le nombre de fontes utilisées
 pour éviter de polluer l'algorithme de détermination du rôle de chaque bloc.
-Dans l'idéal, une police est utilisée pour les titres de niveau 1, une autre
+Dans l'idéal, une fonte est utilisée pour les titres de niveau 1, une autre
 pour les titres de niveau 2, et ainsi de suite, ce qui permet de déduire
-la structure du document en regardant la succession des polices de caractères.
+la structure du document en regardant la succession des fontes.
 
 Ce modèle idéal n'est pas celui qui est généralement rencontré, et l'objectif
 est ici de s'en rapprocher.
 
 ##### Première hypothèse, non retenue
-Une première tentative a été faite de regrouper toutes les polices de même
-famille, même taille et même couleur en une seule, indépendamment des styles
-utilisés, considérant que ceux-ci servaient uniquement à mettre du texte
-en évidence. Il s'est avéré que cette hypothèse donnais de mauvais résultats,
-une simple mise en gras ou gras-italique étant souvent utilisée pour un titre.
+La première hypothèse de travail a été de considérer comme identiques les fontes
+d'une même police, même taille et même couleur qui n’avaient pas le meme style
+(gras, italique, …). Il s'est avéré que cette hypothèse donnait de mauvais
+résultats, une simple mise en gras ou gras-italique étant souvent utilisée
+pour un titre.
 
 Il arrive même, et ce n'est pas rare, que l'usage d'un élément de style sur
 la police par défaut soit utilisé non pas pour le dernier niveau de titre
@@ -278,9 +385,9 @@ donne des résultats désastreux pour la reconnaissance de la structure.
 Cette hypothèse a donc été abandonnée.
 
 ##### Seconde proposition
-Lors de l'attribution d'une police de caractères aux lignes des blocs,
-un pré-traitement, destiné à améliorer la reconnaissance des lignes,
-est effectué sur la liste **lines**. Celui-ci consiste à identifier
+Lors de l'attribution d'une fonte aux lignes des blocs,
+un pré-traitement, destiné à rendre plus efficace la reconnaissance,
+est effectué sur la liste des **segments**. Celui-ci consiste à identifier
 les segments de texte d'une même ligne, en se basant sur les coordonnées
 de localisation (et essentiellement sur la valeur de l'attribut *top*).
 
@@ -290,7 +397,7 @@ en évidence de texte soit réalisée en changeant la couleur de la police
 de caractères par exemple).
 
 La seconde proposition consiste donc à unifier toutes les polices de caractères
-rencontrées sur une même ligne.
+qui auront été rencontrées sur une même ligne.
 
 Il s'agit donc de considérer que si deux polices de caractères sont utilisées
 dans une même ligne, alors il s'agit d'une mise en évidence de texte et que
@@ -305,139 +412,38 @@ celles reconnues comme pied de page, marquées BOTTOM_PAGE.
 Certaines lignes ne comportant aucun caractère alphanumérique sont
 aussi ignorées.
 
-L'algorithme consiste à exécuter séquentiellement les étapes suivantes :
-
-1. On effectue un comptage des caractères pour chaque police de caractères
-  (après avoir appliqué les méthodes d'unification décrites ci-dessus).
-  La police la plus utilisée est désignée par *default_font*.
-1. Une liste contenant la succession des identifiants de polices de caractères
-  est créée, associée à la liste qui compte le nombre de lignes successives dans
-  cette police. Par exemple, l'extrait ci-dessous (entre crochets, l'identifiant
-  de la police de caractères) :
-
-        [3] OÏDIUM
-        [5] Eléments de biologie
-        [4] Situation actuelle
-        [2] Des contaminations sur grappes sont observées (…)
-        [2] avec symptômes d’oïdium (…)
-        [2] la plupart des parcelles sont indemnes.
-        [4] Analyse de risque
-        [2] La sensibilité de la vigne est (…)
-        [2] Le risque de nouvelles contaminations est très faible(…)
-        [2] Faites le point sur l’état sanitaire de vos parcelles.
-
-
-    donnera les listes suivantes :
-
-    > - **t** : 3 5 4 2 4 2 *(identifiants de polices)*
-    > - **n** : 1 1 1 3 1 3 *(nombre de lignes)*
-
-1. Pour toute police de caractères de **t**, le plus grand nombre de lignes est
-  stocké, avec un attribut nommé *maxl*, dans un dictionnaire **f**.
-
-    Dans l'exemple qui précède, **f** contient :
-
-        f : { '2' : { maxl:3 },  '3' : { maxl:1 },
-              '4' : { maxl:1 },  '5' : { maxl:1 } }
-
-1. Ensuite, considérant qu'un titre ne peut excéder un certain nombre de lignes
-  (actuellement deux, stocké dans un paramètre nommé TITLE_MAX_LINES),
-  il est défini :
-
-        f['id']['isnt_title'] = (f['id']['maxl'] > TITLE_MAX_LINES)
-
-    Une exception toutefois : la dernière police de caractères du tableau **t**
-    n'est pas un titre, car un document ne se termine pas par un titre.
-
-    Cette exception a aussi pour but de ne pas se retrouver dans la situation
-    où toutes les polices de caractères seraient identifiées comme utilisées
-    pour un titre (une des préconditions de l'algorithme étant qu'au moins une
-    police de caractères n'est pas associée à des titres).
-
-1. Un tableau **b**, à deux dimensions, est défini ainsi :
-
-        b[i][j] = Nombre de transitions de la police *i* vers *j*
-
-    Dans l'exemple ci-dessus, **b** contiendrait :
-
-    |  j \ i | 2 | 3 | 4 | 5 | *commentaires* |
-    | :----: |:-:|:-:|:-:|:-:|:----------- |
-    |  **2** |   |   | 2 |   |← Deux transitions de la police 4 vers 2 |
-    |  **3** |   |   |   |   |  |
-    |  **4** | 1 |   |   | 1 |← Une transition de 2 à 4 et une de 5 à 4 |
-    |  **5** |   | 1 |   |   |← …et une transition de 3 à 5. |
-
-    *(les cases vides sont des zéros mais n'on pas été remplies pour plus*
-    *de lisibilité)*
-
-1. Dans **f**, création d'un attribut nommé *'deep'* (au lieu de *depth*, 
-  désolé), initialisé ainsi :
-
-         f['id']['deep'] = 0 si f['id']['isnt_title'] est vrai.
-
-1. Ensuite, à l'aide de la table **b**, la valeur *deep* de toute police de
-  caractères dont le successeur a une valeur connue pour *deep* est calculée,
-  en se basant sur les règles suivantes : value of any font
-
-    1. S'il n'y a qu'une seule transition, on considère que les deux polices
-      de caractères désignent des éléments de même niveau (le nombre de
-      transitions minimum à considérer est un paramètre).
-    1. Sinon, si elle n'est pas déjà connue, la valeur de *deep* est celle
-      du successeur, incrémentée de 1.
-
-    Ceci est répété jusqu'à ce qu'aucune valeur de *deep* nouvelle ne soit
-    affectée. Le fait d'avoir mis à 0 le *deep* de la dernière police 
-    de caractères du tableau **t** garantit que toutes les polices de caractères
-    ont été prises en compte (car toute autre police précède celle
-    de la dernière ligne).
-
-1. Pour finir, les valeurs de *deep* sont inversées (on passe de [0..max_deep]
-  à [max_deep..0])
-
-  Le résultat, appliqué à l'exemple ci-dessus, est :
-
-  > |  font    | 2 | 3 | 4 | 5 |
-  > | :------: |:-:|:-:|:-:|:-:|
-  > | **deep** | 1 | 0 | 0 | 0 |
-
-  ce qui est pauvre car l'exemple ne comporte qu'une unique succession
-  de titre et sous-titres, mais si on enrichit l'extrait en y ajoutant
-  les lignes suivantes :
-
-       [3] TORDEUSES
-       [5] Eléments de biologie
-       [4] Situation actuelle
-       [2] Cochylis : 0 à 10 captures (…)
-
-  alors la structure devient :
-
-  > |  font    | 2 | 3 | 4 | 5 |
-  > | :------: |:-:|:-:|:-:|:-:|
-  > | **deep** | 3 | 0 | 2 | 1 |
-
-
-
-
-
-## Source files
-
-*(À réactualiser)*
-
-### pdf2blocks.py
-
-The main program. It calls differnt functions written in p2b_*.py files.
-
-### p2b_config.py
-
-Has to be edited to adjust parameters for execution of pdf2blocks.py.
-
-#### CMD_PDFTOTEXT and CMD_PDFTOHTML
-Should contain full path and name of *pdftotext* and *pdftohtml* binaries.
-
-### p2b_file.py
-
-Contains functions for reading pdf files.
-
-### p2b_utils.py
-
-Contains some utlities, mostly used for debugging puposes.
+L'algorithme utilise le jeu "réduit" de fontes, c'est à dire les fontes qui
+restent après que le regroupement de fontes décrit ci-dessus est effectué.
+
+Pour chacune de ces fontes, on attribue un indicateur *isnt_title*, basé sur
+l'hypothèse qu'un titre ne comporte pas plus de deux lignes. Autrement dit,
+s'il arrive que le nombre de lignes successives dans une même fonte excède
+une constante nommée TITLE_MAX_LINES (dont la valeur est actuellement 2),
+alors on considère que cette fonte n'est pas utilisée pour un titre.
+
+D'autre part, on fait l'hypothèse que la dernière fonte utilisée ne peut être
+une fonte de titre. Autrement dit, on considère qu'un document ne se termine
+jamais par un titre.
+
+On attribue ensuite à chaque fonte une valeur de profondeur :
+
+- une fonte marquée *isnt_title* a une profondeur de 0,
+- on s'intéresse ensuite aux fontes qui précèdent, dans le document,
+  les fontes dont la profondeur est connue. Pour celles-ci :
+  - s'il n'y a qu'une seule occurence, on leur attribue une profondeur
+    identique (une fonte qui ne précède qu'une seule fois dans le document
+    une autre fonte est considérée de même niveau),
+  - s'il y a plusieurs occurences alors la profondeur est incrémentée (s'il
+    arrive plusieurs fois qu'une fonte précède immédiatement une autre, on
+    considère qu'elle en est un titre).
+
+Le dernier point est exécuté jusqu'à ce qu'aucune nouvelle fonte ne soit
+marquée. On notera que la condition qui consiste à dire que la dernière fonte
+utilisée ne peut être un titre, a pour conséquence que toutes les fontes auront
+une valeur de profondeur déterminée à l'issue de ce traitement.
+
+La structure proposée pour le document s'appuiera sur l'inverse de la valeur
+de profondeur. Par exemple, si les profondeurs calculées ont des valeurs entre
+0 et 3, on considèrera que la fonte de profondeur 3 est utilisée pour un premier
+niveau de titre, la fonte de profondeur 2 pour des titre de niveau 2, et ainsi
+de suite. La profondeur de 0 désignant ce qui constitue le texte proprement dit.
diff --git a/src/py/p2b.py b/src/py/p2b.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b80c61742793d08f3a3171fa0eb02dcf133587f
--- /dev/null
+++ b/src/py/p2b.py
@@ -0,0 +1,649 @@
+import xml.etree.ElementTree as ET
+import os
+import sys
+import re
+
+# https://unix.stackexchange.com/questions/238180/execute-shell-commands-in-python
+import subprocess
+
+from p2b_utils import levenshtein
+
+### Script pour faire tout le corpus :
+# D=~/Boulot/Ontology/BSV/tmp/Corpus/2019/Viticulture; for i in ${D}/*.pdf; do j=$( basename "$i" | sed -e 's/\.pdf//' ); echo $j; python p2b.py ${D}/$j | tee ${D}/${j}.md | markdown -o ${D}/${j}.html ; done
+
+
+CMD_PDFTOTEXT = '/usr/sbin/pdftotext'
+CMD_PDFTOHTML = '/usr/sbin/pdftohtml'
+
+LEFT_THRESHOLD = 25 # In p2b_text_utils.add_lines() : the max horizontal space
+                    # to consider aligned items to be on the same line.
+
+FLAG_NONE = 0x0000
+SMALL_FONT = 0x0001
+# BIG_FONT = 0x0002 -> Unused
+PAGE_BOTTOM = 0x0004
+MANY_FONTS = 0x0010
+IS_BULLET = 0x0020
+DEFAULT_FONT_SIZE = 0x0040
+TITLE_SMALLER_THAN_SUBTITLE = 0x0080
+
+
+TITLE_MAX_LINES = 2
+
+TITLE_MIN_CHAR = 2 # To avoid “styled” bullet : we consider that a font never
+                  # used for more than TITLE_MIN_CHAR characters per line
+                  # is a kind of text styling and will take the next line's font
+
+SIMILARITY_THRESHOLD = 1.0
+
+# Celle là est un peu compliquée : Pour détecter la structure, on compte
+# le nombre de successions d'un changement de police de caractères vers
+# un autre (ex : la fonte 3 succède *2* fois à la fonte 8).
+# Si ce nombre est trop peu élevé (<= NB_SUCCESSION_FOR_SAME) alors
+# on considère que 8 n'est pas un titre de 3, et qu'ils sont au même niveau.
+# Sinon, on considère que 8 est un niveau au-dessus dans la hiérarchie des
+# titres, sous-titres, …
+NB_SUCCESSION_FOR_SAME = 0
+
+# Regex
+INDICES_EXPOSANTS_USUELS = [
+  'er|ère|ere', # 1er, 1ère, …
+  'nde?', # 2nd
+  'i?[eè]me', # 3ème, 4ieme, …
+  '°',
+]
+
+
+# +--------------------------------------------------------------+
+# |                       get_pdftotext                          |
+# +--------------------------------------------------------------+
+def get_pdftotext(filename):
+  # Calls pdftotext and retreive standard output in a string (o)
+  basename = os.path.splitext(filename)[0]
+  cmd = [CMD_PDFTOTEXT, '-bbox-layout', '-eol', 'unix', '%s.pdf' % basename, '-']
+  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  o, e = proc.communicate()
+  if (proc.returncode != 0):
+    print('-S-> Command pdftotext returned an error :')
+    print('     '  + e.decode('utf8'))
+    return []
+
+  # Parse xml code and create block table.
+  xml = o.decode('utf8')
+  root = ET.fromstring(xml)
+
+  page_num = 0
+  flow_num = 0
+  blocks = []
+  for body in root:
+    if (body.tag.endswith('body')):
+      for doc in body:
+        if (doc.tag.endswith('doc')):
+          for page in doc:
+            if (page.tag.endswith('page')):
+              page_num += 1
+              for fl in page:
+                if (fl.tag.endswith('flow')):
+                  flow_num += 1
+                  for bloc in fl:
+                    if (bloc.tag.endswith('block')):
+                      bl = {'page': page_num, 'flow': flow_num, 'lines': [],
+                            'flags': FLAG_NONE,
+                            'x_min': float(bloc.get('xMin')),
+                            'x_max': float(bloc.get('xMax')),
+                            'y_min': float(bloc.get('yMin')),
+                            'y_max': float(bloc.get('yMax')),
+                            }
+                      for line in bloc:
+                        if (line.tag.endswith('line')):
+                          h = float(line.get('yMax')) - float(line.get('yMin'))
+                          li = { 'text': '', 'height': h, 'words': [],
+                            'flags': FLAG_NONE,
+                            'x_min': float(bloc.get('xMin')),
+                            'x_max': float(bloc.get('xMax')),
+                            'y_min': float(bloc.get('yMin')),
+                            'y_max': float(bloc.get('yMax')),
+                          }
+                          last_nbcar = 0
+                          last_h = 0
+                          for word in line:
+                            if (word.tag.endswith('word')):
+                              hword = float(word.get('yMax')) - float(word.get('yMin'))
+                              li['words'].append({'height': hword, 'text': word.text})
+                              if ((hword != last_h) and (last_nbcar < 2)):
+                                  # This is to avoid separation of one big capital
+                                  # letter at the beginin of a title or paragraph.
+                                  last_h = hword
+                                  if len(re.sub(r'\W','', li['text'])) == 0:
+                                    li['text'] = "%s %s" % (li['text'], word.text)
+                                  else:
+                                    li['text'] = "%s%s" % (li['text'], word.text)
+                              else:
+                                  li['text'] = "%s %s" % (li['text'], word.text)
+                              li['text'] = li['text'].strip()
+                              last_nbcar = len(word.text)
+                          bl['lines'].append(li)
+                      blocks.append(bl)
+  return blocks
+
+
+# +--------------------------------------------------------------+
+# |                       get_pdftohtml                          |
+# +--------------------------------------------------------------+
+def get_pdftohtml(filename):
+  basename = os.path.splitext(filename)[0]
+  cmd = [CMD_PDFTOHTML, '-xml', '-i', '-stdout', '%s.pdf' % basename]
+  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  o, e = proc.communicate()
+  if (proc.returncode != 0):
+    print('-S-> Command pdftohtml returned an error :')
+    print('     '  + e.decode('utf8'))
+    return None
+
+  # Parse xml code and create block table.
+  xml = o.decode('utf8')
+  root = ET.fromstring(xml)
+
+  fontspec = []
+  segments = []
+  for page in root:
+    if (page.tag.endswith('page')):
+        pg = int(page.get('number'))
+        for tg in page:
+            if (tg.tag.endswith('fontspec')):
+                fontspec.append({
+                    'id': int(tg.get('id')),
+                    'size': int(tg.get('size')),
+                    'family': tg.get('family'),
+                    'color': tg.get('color'),
+                    'nb_cars': 0
+                })
+            elif (tg.tag.endswith('text')):
+                fnt = int(tg.get('font'))
+                top = int(tg.get('top'))
+                left = int(tg.get('left'))
+                width = int(tg.get('width'))
+                height = int(tg.get('height'))
+                while (tg.text is None) and (len(tg) > 0):
+                    tg = tg[0] # remove html style tags (like <b>, …)
+                if (tg.text is not None):
+                    li = "%s" % (tg.text)
+                    if (len(li.strip()) > 0):
+                        segments.append({'page': pg, 'font': fnt,
+                            'top': top, 'left': left,
+                            'width': width, 'height': height,
+                            'text': li.strip()
+                        })
+                        # Find font in fontspec
+                        for font in fontspec:
+                            if font['id'] == fnt: break
+                        font['nb_cars'] += len(li.strip())
+  return { 'fonts': fontspec, 'segments': segments }
+
+
+# +--------------------------------------------------------------+
+# |                   get_default_font_size                      |
+# +--------------------------------------------------------------+
+def get_default_font_size(fontspec):
+  sizes = {}
+  max_cars = 0
+  size_max_cars = 42 # Doesn't matter : it'll change
+  for f in fontspec:
+      if sizes.get(f['size']) is None:
+          sizes[f['size']] = f['nb_cars']
+      else:
+          sizes[f['size']] += f['nb_cars']
+      if sizes[f['size']] > max_cars:
+          max_cars = sizes[f['size']]
+          size_max_cars = f['size']
+  return size_max_cars
+
+
+# +--------------------------------------------------------------+
+# |                      mark_small_fonts                        |
+# +--------------------------------------------------------------+
+# RQ : Also marks bullet lines
+def mark_small_fonts(blocks, default_font_size):
+    for b in blocks:
+        for l in b['lines']:
+            if (round(l['height']) < default_font_size):
+                l['flags'] |= SMALL_FONT
+            if len(re.sub(r'\W','', l['text'])) == 0:
+                l['flags'] |= IS_BULLET
+
+
+# +--------------------------------------------------------------+
+# |                      mark_page_bottom                        |
+# +--------------------------------------------------------------+
+def mark_page_bottom(blocks):
+    if (blocks[-1]['page'] == 1): return
+
+    # Find indexes of last blocks in pages
+    bndx = []
+    for i in range(0, len(blocks) - 1):
+        if (blocks[i]['page'] != blocks[i+1]['page']):
+            bndx.append(i)
+    bndx.append(len(blocks)-1)
+
+    # Get last line indexes
+    lndx = []
+    for i in bndx:
+        lndx.append(len(blocks[i]['lines'])-1)
+
+    # Loop while finding always same characters in last lines
+    end = False
+    while not end:
+        txt = None
+        # Test if last lines characters are the same
+        for i,j in zip(bndx, lndx):
+            li = re.sub(r'[^a-zA-Z]', '', blocks[i]['lines'][j]['text'])
+            if txt is None: txt = li
+            else: end = (txt != li)
+        # All last line are the same, so mark them
+        if not end:
+            for i in range(0, len(bndx)):
+                blocks[bndx[i]]['lines'][lndx[i]]['flags'] |= PAGE_BOTTOM
+                lndx[i] -= 1
+                if (lndx[i] < 0):
+                    #-# blocks[bndx[i]]['flags'] |= PAGE_BOTTOM
+                    bndx[i] -= 1
+                    lndx[i] = len(blocks[bndx[i]]['lines']) - 1
+                    end = bndx[i] < 0
+
+# +--------------------------------------------------------------+
+# |                         is_ind_exp                           |
+# +--------------------------------------------------------------+
+# Is it an indice or exposant ?
+def is_ind_exp(str):
+  for ie in INDICES_EXPOSANTS_USUELS:
+      if re.match(ie, str):
+          return True
+  return False
+
+# +--------------------------------------------------------------+
+# |                         get_lines                            |
+# +--------------------------------------------------------------+
+# Extract lines from 'text' attribute returned by get_pdftohtml and associates
+# a font id (and the page number), which is the font used by the higher number
+# of characters of the line.
+# Does a column splitting considering the value of LEFT_THRESHOLD
+def get_lines(segments, fontspec):
+    last_top = -1
+    line_no = -1
+    last_right = 0
+    for txt in segments:
+        if (txt['top'] == last_top) and ((txt['left'] - last_right) <= LEFT_THRESHOLD):
+            txt['line'] = line_no
+        elif is_ind_exp(txt['text'].strip()):
+            txt['line'] = line_no
+        else:
+            line_no += 1
+            txt['line'] = line_no
+            last_top = txt['top']
+        last_right = txt['left'] + txt['width']
+
+    for f in fontspec:
+        if 'same_line' not in f:
+            f['same_line'] = []
+
+    lines = []
+    last_line = -2
+    li = ''
+    fnt = {}
+    page_num = segments[0]['page']
+    for txt in segments:
+        if (txt['line'] != last_line) or (txt == segments[-1]):
+            if (len(li.strip()) > 0):
+                fnt_no = -1; max_car = 0;
+                for f in fnt.keys():
+                    if (fnt[f] > max_car):
+                        max_car = fnt[f]
+                        fnt_no = f
+                lines.append({ 'text': li.strip(),
+                    'most_used_font': fnt_no,
+                    'nb_fonts': len(fnt),
+                    'page': page_num})
+            li = txt['text'].strip()
+            last_line = txt['line']
+            for fi1 in fnt.keys():
+                for fi2 in fnt.keys():
+                    if fi1 != fi2:
+                        f1 = next(it for it in fontspec if it['id'] == int(fi1))
+                        f2 = next(it for it in fontspec if it['id'] == int(fi2))
+                        if (f2['id'] not in f1['same_line']):
+                            f1['same_line'].append(f2['id'])
+                            f2['same_line'].append(f1['id'])
+            fnt = {}
+            fnt[txt['font']] = len(li.strip())
+        else:
+            if (is_ind_exp(txt['text'])):
+                li = "%s%s" % (li, txt['text'].strip())
+            else:
+                li = "%s %s" % (li, txt['text'].strip())
+            if (fnt.get(txt['font']) is None):
+                fnt[txt['font']] = len(txt['text'].strip())
+            else:
+                fnt[txt['font']] += len(txt['text'].strip())
+        page_num = txt['page']
+    return lines
+
+# +--------------------------------------------------------------+
+# |                        guess_fonts                           |
+# +--------------------------------------------------------------+
+# Tries to guess fontspec of each line into blocks list.
+# It calculates the levenshtein distance with every segment of the same page
+# and assigns the best matching score's font.
+def guess_fonts(blocks, segments, fontspec):
+    lines = get_lines(segments, fontspec)
+    ndx_lines = [0,] # Indexation des indices de line par numéro de page
+    for ndx in range(1, len(lines)):
+        if (lines[ndx-1]['page'] != lines[ndx]['page']):
+            ndx_lines.append(ndx)
+    ndx_lines.append(len(lines))
+
+    for f in fontspec:
+        f['nb_lines'] = 0
+        f['dist_sum'] = 0
+        #f['block_pos_sum'] = 0
+
+    for bl in blocks:
+        for l in bl['lines']:
+            if (len(l['text']) > 0):
+                min_dist = len(l['text'])
+                min_score = 1.0
+                font_sel = -1
+                line_no = -1
+                for i in range(ndx_lines[bl['page']-1], ndx_lines[bl['page']]):
+                    if (len(lines[i]['text']) > 0):
+                        d = levenshtein(l['text'], lines[i]['text'])
+                        if (d == 0):
+                            min_dist = 0
+                            min_score = 0.0
+                            font_sel = lines[i]['most_used_font']
+                            line_no = i
+                            break;
+                        score = float(d) / float(max(len(l['text']), len(lines[i]['text'])))
+                        if (score <= SIMILARITY_THRESHOLD):
+                            if (d < min_dist):
+                                min_dist = d
+                                min_score = score
+                                font_sel = lines[i]['most_used_font']
+                                line_no = i
+                l['font'] = font_sel
+                if (font_sel >= 0):
+                  fnt = next(it for it in fontspec if it['id'] == font_sel)
+                  fnt['nb_lines'] +=1
+                  fnt['dist_sum'] += min_dist
+                l['score'] = min_score # For debuggin purpose
+                l['dist'] = min_dist   #    idem.
+                l['line_no'] = line_no # idem. Stores the "similar line" number
+                # print("> %s" % l['text'])
+                # print("  %s" % lines[line_no]['text'])
+                # print("  [%d]" % font_sel)
+                # print("")
+                if (lines[line_no]['nb_fonts'] > 1):
+                    l['flags'] |= MANY_FONTS
+
+# +--------------------------------------------------------------+
+# |                    replace_block_fonts                       |
+# +--------------------------------------------------------------+
+# Adds a 'short_font' attribute to lines which gives another font value which
+# doesn't care about style (bold, …).
+# RK: def_size is default_font_size, used to mark SMALL_FONT flag.
+def replace_block_fonts(blocks, fontspec, def_size):
+    for i in range(0, len(fontspec) - 1):
+        for j in range(i+1, len(fontspec)):
+          if (fontspec[j].get('replaceWith') is None):
+            if (fontspec[j]['id'] in fontspec[i]['same_line']):
+                if fontspec[i].get('replaceWith') is None:
+                    fontspec[j]['replaceWith'] = fontspec[i]['id']
+                else:
+                    fontspec[j]['replaceWith'] = fontspec[i]['replaceWith']
+    for bl in blocks:
+        for l in bl['lines']:
+            if (l['font'] < 0):
+                f = None
+            else:
+                f = next(it for it in fontspec if it['id'] == l['font'])
+            if (f is None) or (f.get('replaceWith') is None):
+                l['short_font'] = l['font']
+            else:
+                l['short_font'] = f.get('replaceWith')
+            if (f is not None):
+                f = next(it for it in fontspec if it['id'] == l['short_font'])
+                if (f['size'] < def_size):
+                    l['flags'] |= SMALL_FONT
+                if (f['size'] == def_size):
+                    l['flags'] |= DEFAULT_FONT_SIZE
+
+
+# +--------------------------------------------------------------+
+# |                      guess_structure                         |
+# +--------------------------------------------------------------+
+def guess_structure(blocks, fontspec,
+  remove_flags = SMALL_FONT | PAGE_BOTTOM | IS_BULLET):
+    t = [] # A list used here and there
+    n = [] # Another one
+
+    # Search for the most used font
+    # Here, t will be used to count the number of cars of each font.
+    #   and n will be used to store the maximum line size for each font.
+    for i in range(len(fontspec)):
+        t.append(0)
+        n.append(0)
+    nb_max = -1
+    ndx_most_used = -1
+    for bl in blocks:
+        for l in bl['lines']:
+            if (l['short_font'] >= 0) and ((l['flags'] & remove_flags) == FLAG_NONE):
+                lon = len(l['text'].strip())
+                t[l['short_font']] += lon
+                if lon > n[l['short_font']]: n[l['short_font']] = lon
+                if (t[l['short_font']] > nb_max):
+                    nb_max = t[l['short_font']]
+                    ndx_most_used = l['short_font']
+    b = [nb <= TITLE_MIN_CHAR for nb in n]
+
+    ### ndx_most_used is the most used font number.
+    ### b[font_number] is True if the font seems used for bullets.
+
+    t = [] # We'll use it to list the fonts succession
+    n = [] # Used to count the number of lines
+    for bl in blocks:
+        for l in bl['lines']:
+            if (l['flags'] & remove_flags) == FLAG_NONE:
+                if t == []:
+                    t.append(l['short_font'])
+                    n.append(1)
+                else:
+                    if (t[-1] != l['short_font']):
+                        t.append(l['short_font'])
+                        n.append(1)
+                    else:
+                        n[-1] += 1
+
+    f = {} # Will contain used font numbers and number of occurences in t
+    for i,j in zip(t,n):
+        if i not in f.keys():
+            f[i] = {'nb': 1, 'nl':j, 'maxl': j,
+                    'is_bullet': b[i], 'flags': FLAG_NONE}
+        else:
+            f[i]['nb'] += 1
+            f[i]['nl'] += j
+            if (j > f[i]['maxl']):
+                f[i]['maxl'] = j
+
+    for i in f.keys():
+        f[i]['isnt_title'] = (f[i]['maxl'] > TITLE_MAX_LINES)
+
+    # Replace short_font for lines considered as bullets (or text styling).
+    last_bullet_lines = []
+    for bl in blocks:
+        for l in bl['lines']:
+            if (l['flags'] & remove_flags) == FLAG_NONE:
+                if f[l['short_font']]['is_bullet']:
+                    last_bullet_lines.append(l)
+                else:
+                    if (len(last_bullet_lines) > 0):
+                        for last in last_bullet_lines:
+                            last['short_font'] = l['short_font']
+                        last_bullet_lines = []
+    if (len(last_bullet_lines) > 0):
+        for last in last_bullet_lines:
+            last['short_font'] = ndx_most_used
+
+    # n and b won't be used anymore I think. So they're free
+
+    # Rebuild the font succession list (is not optimized but is the safest)
+    t = []
+    for bl in blocks:
+        for l in bl['lines']:
+            if (l['flags'] & remove_flags) == FLAG_NONE:
+                if t == []: t.append(l['short_font'])
+                else:
+                    if (t[-1] != l['short_font']):
+                        t.append(l['short_font'])
+
+    b = [] # We'll do a 2d table with b[i][j] = number of transitions
+           # from fonti to fontj (will be a tree of font transitions)
+    for i in range(len(fontspec)+1): # Consider len+1 to have font number -1
+        b.append([0 for j in range(len(fontspec)+1)])
+    for i in range(len(t)-1):
+        j = i+1
+        if not f[t[i]]['isnt_title']:
+            b[t[i]][t[j]] += 1
+
+    # Should we do this : ? &&&&&&&&&&&&&&&&&&&& A tester
+    # Un moyen de s'assurer que tout sera parcouru...
+    # Signifie qu'on ne finit pas sur un titre.
+    f[t[-1]]['isnt_title'] = True
+
+    # Create a deep attribute in f which contains distance from leaves
+    for k,v in f.items():
+        if v['isnt_title']:
+            v['deep'] = 0
+            v['nb_transitions'] = 999999999
+        else: v['deep'] = None
+
+    # Algo : dans le tableau b, on parcourt les colonnes (j) pour les fontes qui ont un deep.
+    #        Un indice (i) de ligne pour lequel la valeur b[i][j] est non nulle signifie
+    #        que la fonte i
+    #        précède la fonte j b[i][j] fois.
+    # Rq : Une colonne vide pour un indice dont la ligne est non-vide est une racine
+    #      Une ligne vide pour un indice dont la colonne est non-vide est une feuille
+    # On répète tant qu'on change des valeurs (c'est pas optimisé mais crotte,
+    # le tableau n'est pas si grand)
+    has_changed = True
+    deep_max = 0
+    while has_changed:
+        has_changed = False
+        for k,v in f.items():
+            if v['deep'] is not None:
+                for i in range(-1,len(b)-1):
+                    if b[i][k] != 0:
+                        if f[i]['deep'] is None:
+                            if (b[i][k] <= NB_SUCCESSION_FOR_SAME):
+                                f[i]['deep'] = v['deep']
+                                f[i]['nb_transitions'] = b[i][k]
+                            else:
+                                f[i]['deep'] = v['deep'] + 1
+                                f[i]['nb_transitions'] = b[i][k]
+                            if f[i]['deep'] > deep_max:
+                                deep_max = f[i]['deep']
+                            has_changed = True
+                            if (fontspec[i]['size'] < fontspec[k]['size']):
+                                f[i]['flags'] |= TITLE_SMALLER_THAN_SUBTITLE
+                        elif f[i]['nb_transitions'] < b[i][k]:
+                            f[i]['deep'] = v['deep'] + 1
+                            f[i]['nb_transitions'] = b[i][k]
+                            has_changed = True
+                            if (fontspec[i]['size'] < fontspec[k]['size']):
+                                f[i]['flags'] |= TITLE_SMALLER_THAN_SUBTITLE
+
+    # Reverse deepness value, to make it distance from root
+    for v in f.values():
+        if (v['deep'] is not None):
+            v['deep'] = deep_max - v['deep']
+
+    # Add deep in blocks lines
+    for bl in blocks:
+        for l in bl['lines']:
+            if (l['flags'] & remove_flags) == FLAG_NONE:
+                l['deep'] = f[l['short_font']]['deep']
+                if ((f[l['short_font']]['flags']) & TITLE_SMALLER_THAN_SUBTITLE != 0):
+                   l['flags'] |= TITLE_SMALLER_THAN_SUBTITLE
+            else:
+                l['deep'] = deep_max
+
+
+
+
+
+
+
+# +--------------------------------------------------------------+
+# |                      print_block_list                        |
+# +--------------------------------------------------------------+
+def print_block_list(t, remove_flags = FLAG_NONE):
+    last_page = -1
+    deep_max = -1
+    for bl in t:
+        for l in bl['lines']:
+            if (l.get('deep') is not None):
+                if deep_max < l['deep']: deep_max = l['deep']
+    if deep_max > 10: deep_max = 10
+    ttl = "#############"
+    last_deep = -1
+
+    for block in t:
+        if (block['page'] != last_page):
+            if (last_page > 0):
+                print("")
+            last_page = block['page']
+            print("________________________________")
+            print("*page %d*" % last_page)
+
+        print("")
+
+        for l in block['lines']:
+            if (l['flags'] & remove_flags) == FLAG_NONE:
+                pre = ''
+                post = '  '
+                if (l.get('deep') is None):
+                    pre = '!! '
+                    last_deep = -1
+                else:
+                    if (l['flags'] & SMALL_FONT) != 0:
+                        pre = "> %s" % pre
+                    #if (len(l['text']) > 20) and \
+                    #   len(re.sub(r'\w','', l['text']).strip()) > 5:
+                    #    post = "%s  " % post
+                    if (l['flags'] & TITLE_SMALLER_THAN_SUBTITLE) != 0 and \
+                       (l['flags'] & (DEFAULT_FONT_SIZE | SMALL_FONT)) != 0:
+                        pre = "%s**" % (pre)
+                        post = "**%s" % post
+                    elif l['deep'] < deep_max:
+                        pre = "%s%s " % (pre, ttl[0:(l['deep']+1)])
+                    last_deep = l['deep']
+                print("%s%s%s" % (pre, l['text'], post))
+
+
+# +--------------------------------------------------------------+
+# |                           main                               |
+# +--------------------------------------------------------------+
+if (len(sys.argv) < 1):
+    print("-U-> Usage : python pdf2blocks.py <fichier_pdf>")
+    sys.exit(-1)
+
+blocks = get_pdftotext(sys.argv[1])
+p2h = get_pdftohtml(sys.argv[1])
+fontspec = p2h['fonts']
+segments = p2h['segments']
+
+default_font_size = get_default_font_size(fontspec)
+# mark_small_fonts(blocks, default_font_size)
+mark_page_bottom(blocks)
+guess_fonts(blocks, segments, fontspec)
+replace_block_fonts(blocks, fontspec, default_font_size)
+guess_structure(blocks, fontspec)
+print_block_list(blocks, PAGE_BOTTOM | IS_BULLET)