Leonel FOFOU
2026-07-28 32450e295200b2bc7896518ff74161ff26772e2d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.megatim.manuel.core.convert;
 
import javax.imageio.ImageIO;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
/** Outils partagés par tous les convertisseurs : nommage, échappement, écriture HTML et images. */
public abstract class AbstractHtmlConverter implements DocumentConverter {
 
    /** Reconnaît "Heading 2", "heading2", "Titre 3", "Título 1", "..._niveau_3"... */
    private static final Pattern HEADING =
            Pattern.compile("(?i).*?(?:heading|titre|t[ií]tulo|niveau)[\\s_]*([1-6]).*");
 
    protected final Path outputDir;
 
    protected AbstractHtmlConverter(Path outputDir) {
        this.outputDir = outputDir;
    }
 
    /** Niveau de titre déduit d'un nom/identifiant de style Word, ou 0 si ce n'est pas un titre. */
    protected static int matchHeading(String s) {
        if (s == null) return 0;
        Matcher m = HEADING.matcher(s);
        if (m.matches()) return Integer.parseInt(m.group(1));
        if (s.equalsIgnoreCase("Title") || s.equalsIgnoreCase("Titre")) return 1;
        return 0;
    }
 
    protected static String esc(String s) {
        if (s == null) return "";
        return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;");
    }
 
    protected static String stripExt(String n) {
        int i = n.lastIndexOf('.');
        return i > 0 ? n.substring(0, i) : n;
    }
 
    protected static String sanitize(String n) {
        return n.replaceAll("[^A-Za-z0-9_-]", "_");
    }
 
    /** Base de nom unique et ordonnée, ex. "03_mon_manuel". */
    protected String safeBase(Path source, int order) {
        return String.format("%02d_%s", order, sanitize(stripExt(source.getFileName().toString())));
    }
 
    protected String wrapHtml(String title, String body) {
        return "<!DOCTYPE html>\n<html lang=\"fr\"><head>\n"
                + "<meta charset=\"UTF-8\"/>\n"
                + "<title>" + esc(title) + "</title>\n"
                + "<link rel=\"stylesheet\" href=\"styles.css\"/>\n"
                + "</head>\n<body>\n" + body + "\n</body></html>";
    }
 
    protected void writeHtml(String fileName, String title, String body) throws IOException {
        Files.write(outputDir.resolve(fileName), wrapHtml(title, body).getBytes(StandardCharsets.UTF_8));
    }
 
    /** Écrit une image binaire (extraite d'un document) et renvoie son chemin relatif. */
    protected String writeImageBytes(String safe, int idx, String ext, byte[] data) throws IOException {
        String name = "images/" + safe + "_img" + idx + "." + ext;
        Path target = outputDir.resolve(name);
        Files.createDirectories(target.getParent());
        Files.write(target, data);
        return name;
    }
 
    /** Écrit une image PNG (rendu d'une diapositive) et renvoie son chemin relatif. */
    protected String writeImagePng(String safe, int idx, BufferedImage img) throws IOException {
        String name = "images/" + safe + "_img" + idx + ".png";
        Path target = outputDir.resolve(name);
        Files.createDirectories(target.getParent());
        try (OutputStream os = Files.newOutputStream(target)) {
            ImageIO.write(img, "png", os);
        }
        return name;
    }
 
    /** Réglages de rendu de qualité pour la peinture des diapositives. */
    protected static void applyQualityHints(Graphics2D g) {
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    }
}