@imontene/mdrender 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +89 -29
- package/dist/assets/density-compact.css +74 -0
- package/dist/assets/theme.css +34 -0
- package/dist/assets.js +5 -1
- package/dist/cli.js +43 -1
- package/dist/convert.d.ts +12 -1
- package/dist/convert.js +61 -7
- package/dist/doctor.d.ts +30 -0
- package/dist/doctor.js +278 -0
- package/dist/markdown/code-blocks.d.ts +9 -0
- package/dist/markdown/code-blocks.js +21 -0
- package/dist/markdown/env.d.ts +2 -0
- package/dist/markdown/index.js +5 -0
- package/dist/markdown/keep-with-heading.d.ts +8 -0
- package/dist/markdown/keep-with-heading.js +44 -0
- package/dist/markdown/mermaid.js +3 -1
- package/dist/options.d.ts +16 -0
- package/dist/options.js +30 -0
- package/dist/render/browser.d.ts +2 -0
- package/dist/render/browser.js +4 -0
- package/dist/render/mermaid-fit.d.ts +81 -0
- package/dist/render/mermaid-fit.js +135 -0
- package/dist/render/mermaid.d.ts +40 -6
- package/dist/render/mermaid.js +101 -24
- package/dist/render/page.d.ts +5 -0
- package/dist/render/page.js +6 -0
- package/dist/render/template.d.ts +5 -1
- package/dist/render/template.js +12 -2
- package/dist/run.js +6 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Cambios
|
|
2
2
|
|
|
3
|
+
## 2.2.0 — 2026-10-15
|
|
4
|
+
|
|
5
|
+
- **Autoajuste de diagramas de flujo Mermaid** (`flowchart`/`graph`): cada diagrama se dibuja
|
|
6
|
+
con varios anchos de ajuste de etiquetas y se elige el que imprime el texto más grande (a igual
|
|
7
|
+
legibilidad, el más bajo), nunca más pequeño que tal como está escrito. Si sigue por debajo del
|
|
8
|
+
75 % de su tamaño, se prueba el otro eje (`LR` → `TB`, `TD` → `LR`) y se gira si gana al menos
|
|
9
|
+
15 puntos. En el banco de pruebas, la escala media del texto pasa de 0,80 a 0,98 y ningún
|
|
10
|
+
diagrama queda por debajo de 0,6.
|
|
11
|
+
- `--mermaid-flip never` (`mermaidFlip`) conserva la dirección; `--no-mermaid-autofit`
|
|
12
|
+
(`mermaidAutofit: false`) lo desactiva. Se respetan los diagramas con `%%{init}%%` o
|
|
13
|
+
`config:` propios.
|
|
14
|
+
- `-v` informa la elección por diagrama; aviso si un diagrama se imprime por debajo del 50 %.
|
|
15
|
+
- Un título seguido de un diagrama o una imagen ya no queda solo al pie de una página: pasa con
|
|
16
|
+
ellos a la siguiente.
|
|
17
|
+
- `npm run bench:mermaid [-- archivo.md]` compara los diagramas de un documento con y sin
|
|
18
|
+
autoajuste.
|
|
19
|
+
|
|
20
|
+
## 2.1.0 — 2026-10-15
|
|
21
|
+
|
|
22
|
+
- `mdrender doctor`: revisa instalación (versión, Node.js, `PATH`), navegador (cuál y por qué,
|
|
23
|
+
arranque y versión, sandbox), una conversión real de prueba, el `mdrender.config.json`
|
|
24
|
+
aplicado (errores, opciones mal escritas, valores efectivos), la caché de `setup` y el proxy.
|
|
25
|
+
`--quick`, `--json`; código 1 si encuentra un problema.
|
|
26
|
+
- Mejor uso de la página:
|
|
27
|
+
- Los bloques de código largos se parten entre páginas (al menos 4 líneas a cada lado) en
|
|
28
|
+
vez de saltar enteros y dejar hasta media página en blanco; los de hasta 12 líneas siguen
|
|
29
|
+
juntos.
|
|
30
|
+
- `--density compact` (o `density: compact`): interlineado y espacios más ajustados.
|
|
31
|
+
- `--font-size <tamaño>` (o `fontSize`): tamaño del texto, por defecto 10.5pt.
|
|
32
|
+
- Mermaid usa el tamaño de letra del documento (antes 16 px) y los diagramas de secuencia no
|
|
33
|
+
repiten los actores al pie.
|
|
34
|
+
- Imágenes y diagramas más altos que la página se reducen para caber en una sola.
|
|
35
|
+
|
|
3
36
|
## 2.0.0 — 2026-10-13
|
|
4
37
|
|
|
5
38
|
- Márgenes por lado: `--margin-top`, `--margin-right`, `--margin-bottom`, `--margin-left`
|
package/README.md
CHANGED
|
@@ -89,14 +89,39 @@ mdrender docs/ -o pdf/ --watch
|
|
|
89
89
|
# Vista previa en el navegador que se recarga sola al guardar
|
|
90
90
|
mdrender preview documento.md
|
|
91
91
|
|
|
92
|
+
# Más contenido por página
|
|
93
|
+
mdrender documento.md --density compact --font-size 10pt
|
|
94
|
+
|
|
92
95
|
# Tema oscuro, o un CSS propio que reemplaza el tema
|
|
93
96
|
mdrender documento.md --theme dark
|
|
94
97
|
mdrender documento.md --theme mi-tema.css
|
|
95
98
|
|
|
96
99
|
# Descargar un navegador si no tienes Chrome, Edge ni Chromium
|
|
97
100
|
mdrender setup
|
|
101
|
+
|
|
102
|
+
# Diagnóstico: instalación, navegador, configuración y una conversión de prueba
|
|
103
|
+
mdrender doctor
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Si algo no funciona: `mdrender doctor`
|
|
107
|
+
|
|
108
|
+
Revisa la versión y el Node.js, si `mdrender` está en el `PATH`, qué navegador se usa (y por
|
|
109
|
+
qué), lo arranca y convierte un documento de prueba con una ecuación y un diagrama, e indica
|
|
110
|
+
qué `mdrender.config.json` se aplica, con sus opciones mal escritas y los valores efectivos.
|
|
111
|
+
Cada problema viene con su solución sugerida; termina con código 1 si encuentra alguno.
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
[ok] mdrender 2.0.0, npm package (/usr/lib/node_modules/@imontene/mdrender)
|
|
115
|
+
[ok] Node.js 22.23.3
|
|
116
|
+
[warn] PATH the mdrender command is not in any PATH folder
|
|
117
|
+
-> add its folder to PATH (for npm: the output of `npm prefix -g`…)
|
|
118
|
+
[ok] Browser C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
|
|
119
|
+
[ok] Test conversion PDF with an equation and a Mermaid diagram in 1840 ms
|
|
98
120
|
```
|
|
99
121
|
|
|
122
|
+
`--quick` omite el arranque del navegador y la conversión; `--json` produce el informe en JSON
|
|
123
|
+
para compartirlo.
|
|
124
|
+
|
|
100
125
|
En Windows (PowerShell o CMD) los comandos son idénticos:
|
|
101
126
|
|
|
102
127
|
```powershell
|
|
@@ -109,35 +134,39 @@ equipo); `--port` fija el puerto y `--no-open` no abre el navegador.
|
|
|
109
134
|
|
|
110
135
|
### Opciones
|
|
111
136
|
|
|
112
|
-
| Opción
|
|
113
|
-
|
|
|
114
|
-
| `-o, --output <ruta>`
|
|
115
|
-
| `-f, --format <pdf\|html>`
|
|
116
|
-
| `--page-size <tamaño>`
|
|
117
|
-
| `--margin <valor>`
|
|
118
|
-
| `--margin-top <medida>`
|
|
119
|
-
| `--margin-right <medida>`
|
|
120
|
-
| `--margin-bottom <medida>`
|
|
121
|
-
| `--margin-left <medida>`
|
|
122
|
-
| `--orientation <orientación>`
|
|
123
|
-
| `--landscape`
|
|
124
|
-
| `--toc`
|
|
125
|
-
| `--toc-depth <n>`
|
|
126
|
-
| `--header <texto>`
|
|
127
|
-
| `--footer <texto>`
|
|
128
|
-
| `--no-page-numbers`
|
|
129
|
-
| `--
|
|
130
|
-
| `--
|
|
131
|
-
| `--
|
|
132
|
-
| `--
|
|
133
|
-
| `--
|
|
134
|
-
| `--
|
|
135
|
-
| `--no-
|
|
136
|
-
| `--
|
|
137
|
-
| `--
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
137
|
+
| Opción | Descripción | Por defecto |
|
|
138
|
+
| ------------------------------ | -------------------------------------------------------------------- | ------------------ |
|
|
139
|
+
| `-o, --output <ruta>` | Archivo de salida, o carpeta con varios archivos | junto al `.md` |
|
|
140
|
+
| `-f, --format <pdf\|html>` | Formato de salida | `pdf` |
|
|
141
|
+
| `--page-size <tamaño>` | `Letter`, `A4`, `Legal` (también `A3`, `A5`, `Tabloid`) | `Letter` |
|
|
142
|
+
| `--margin <valor>` | Las cuatro márgenes como en CSS: `10mm`, `15mm 20mm`… | `10mm` |
|
|
143
|
+
| `--margin-top <medida>` | Margen superior, p. ej. `15mm` (prevalece sobre `--margin`) | `10mm` |
|
|
144
|
+
| `--margin-right <medida>` | Margen derecho | `10mm` |
|
|
145
|
+
| `--margin-bottom <medida>` | Margen inferior | `10mm` |
|
|
146
|
+
| `--margin-left <medida>` | Margen izquierdo | `10mm` |
|
|
147
|
+
| `--orientation <orientación>` | `portrait` (vertical) o `landscape` (horizontal) | `portrait` |
|
|
148
|
+
| `--landscape` | Atajo de `--orientation landscape` | — |
|
|
149
|
+
| `--toc` | Tabla de contenidos al inicio (o donde pongas `[[toc]]`) | — |
|
|
150
|
+
| `--toc-depth <n>` | Nivel de título más profundo en la tabla de contenidos | `3` |
|
|
151
|
+
| `--header <texto>` | Encabezado de página (ver abajo) | — |
|
|
152
|
+
| `--footer <texto>` | Pie de página (ver abajo) | `{page} / {pages}` |
|
|
153
|
+
| `--no-page-numbers` | Sin el pie con números de página | — |
|
|
154
|
+
| `--density <densidad>` | `normal` o `compact` (menos espacio entre líneas y bloques) | `normal` |
|
|
155
|
+
| `--font-size <tamaño>` | Tamaño del texto en `pt` o `px`, de 6 a 24 pt; p. ej. `10pt` | `10.5pt` |
|
|
156
|
+
| `--theme <light\|dark\|css>` | `light`, `dark` o un `.css` que reemplaza el tema | `light` |
|
|
157
|
+
| `--css <archivo.css>` | CSS adicional (se puede repetir) | — |
|
|
158
|
+
| `--mermaid-theme <tema>` | `default`, `neutral`, `dark`, `forest`, `base` | `neutral` / `dark` |
|
|
159
|
+
| `--mermaid-flip <auto\|never>` | Si el autoajuste puede girar un diagrama de flujo de eje (ver abajo) | `auto` |
|
|
160
|
+
| `--no-mermaid-autofit` | Dibujar los diagramas de flujo tal como están escritos | — |
|
|
161
|
+
| `--lang <código>` | Idioma del documento; `en` para inglés (`pt`, `fr`, `de`…) | `es` |
|
|
162
|
+
| `--title <texto>` | Título del documento (muestra un bloque de título) | primer `# título` |
|
|
163
|
+
| `--config <archivo>` | Archivo de configuración | el más cercano |
|
|
164
|
+
| `--no-config` | Ignorar `mdrender.config.json` | — |
|
|
165
|
+
| `--browser <ruta>` | Ruta a Chrome/Edge/Chromium (o variable `MDRENDER_BROWSER`) | autodetección |
|
|
166
|
+
| `--allow-remote` | Permitir que el PDF descargue imágenes/CSS de internet | bloqueado |
|
|
167
|
+
| `-w, --watch` | Regenerar al guardar | — |
|
|
168
|
+
| `-q, --quiet` | Mostrar solo advertencias y errores | — |
|
|
169
|
+
| `-v, --verbose` | Mostrar también configuración, navegador y tiempos | — |
|
|
141
170
|
|
|
142
171
|
**Página**: por defecto hoja **Letter vertical con márgenes de 10 mm**. Las medidas aceptan
|
|
143
172
|
`mm`, `cm`, `in` o `px`. `--margin` fija las cuatro márgenes a la vez y `--margin-top`,
|
|
@@ -146,6 +175,16 @@ equipo); `--port` fija el puerto y `--no-open` no abre el navegador.
|
|
|
146
175
|
El encabezado y el pie se dibujan dentro de las márgenes superior e inferior: con menos de
|
|
147
176
|
10 mm pueden tocar el texto y mdrender lo advierte.
|
|
148
177
|
|
|
178
|
+
**Aprovechar la página**: `--density compact` reduce interlineado, espacios entre párrafos y
|
|
179
|
+
títulos, y `--font-size 10pt` achica el texto (los diagramas Mermaid siguen el mismo tamaño).
|
|
180
|
+
Juntos ahorran entre un 15 y un 25 % de páginas. Además, siempre:
|
|
181
|
+
|
|
182
|
+
- Los bloques de código largos continúan en la página siguiente (con al menos 4 líneas a cada
|
|
183
|
+
lado del corte) en lugar de saltar enteros y dejar un hueco; los de hasta 12 líneas no se
|
|
184
|
+
parten.
|
|
185
|
+
- Las imágenes y diagramas más altos que la página se reducen para caber en una sola.
|
|
186
|
+
- Los diagramas de secuencia no repiten los actores al pie.
|
|
187
|
+
|
|
149
188
|
**Encabezado y pie**: texto con marcadores `{page}`, `{pages}`, `{title}`, `{author}` y
|
|
150
189
|
`{date}`; `|` separa columnas izquierda, centro y derecha. Ejemplos:
|
|
151
190
|
`--header "{title} | | {date}"`, `--footer "Confidencial | {page} de {pages}"`.
|
|
@@ -176,6 +215,8 @@ header: "{title} | | {date}"
|
|
|
176
215
|
"pageSize": "Letter",
|
|
177
216
|
"margin": "15mm",
|
|
178
217
|
"marginLeft": "25mm",
|
|
218
|
+
"density": "compact",
|
|
219
|
+
"fontSize": "10pt",
|
|
179
220
|
"css": "estilos/empresa.css"
|
|
180
221
|
}
|
|
181
222
|
```
|
|
@@ -198,6 +239,25 @@ fecha). Las rutas de `css` y `theme` son relativas al archivo donde aparecen.
|
|
|
198
239
|
- Si una fórmula o diagrama tiene un error, el documento se genera igual con una caja roja en
|
|
199
240
|
su lugar, se informa `error: archivo.md:LÍNEA: mensaje` y el comando termina con código `2`.
|
|
200
241
|
|
|
242
|
+
**Autoajuste de diagramas de flujo**: en el PDF, un diagrama más ancho que la columna o más alto
|
|
243
|
+
que la página se reduce entero, y su texto puede quedar diminuto. Por eso mdrender dibuja cada
|
|
244
|
+
`flowchart`/`graph` con varios anchos de ajuste de las etiquetas y se queda con el que imprime el
|
|
245
|
+
texto más grande; a igual legibilidad, el que ocupa menos alto. Nunca lo imprime más pequeño que
|
|
246
|
+
tal como está escrito.
|
|
247
|
+
|
|
248
|
+
- Si el diagrama sigue por debajo del 75 % de su tamaño, prueba además el otro eje (`LR` → `TB`,
|
|
249
|
+
o `TD` → `LR`) y lo gira si gana al menos 15 puntos. Una cadena horizontal de 8 cajas, por
|
|
250
|
+
ejemplo, pasa de imprimirse al 37 % a su tamaño real. `--mermaid-flip never` (o
|
|
251
|
+
`mermaidFlip: never`) conserva siempre la dirección escrita.
|
|
252
|
+
- No se tocan los diagramas con configuración propia (`%%{init: …}%%` o `config:` en su front
|
|
253
|
+
matter) ni los de otros tipos (secuencia, torta, clases…).
|
|
254
|
+
- `-v` informa la elección de cada diagrama; si alguno queda por debajo del 50 %, mdrender avisa:
|
|
255
|
+
conviene partirlo o acortar sus etiquetas.
|
|
256
|
+
- `--no-mermaid-autofit` (o `mermaidAutofit: false`) lo desactiva.
|
|
257
|
+
|
|
258
|
+
Un título seguido de un diagrama o una imagen pasa con ellos a la página siguiente cuando no
|
|
259
|
+
caben, en lugar de quedar solo al pie de la anterior.
|
|
260
|
+
|
|
201
261
|
## Más Markdown
|
|
202
262
|
|
|
203
263
|
- **Alertas** estilo GitHub: `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/* mdrender compact density (--density compact): loaded after the theme, tightens vertical spacing. */
|
|
2
|
+
|
|
3
|
+
body {
|
|
4
|
+
line-height: 1.5;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
p,
|
|
8
|
+
ul,
|
|
9
|
+
ol,
|
|
10
|
+
blockquote,
|
|
11
|
+
pre,
|
|
12
|
+
table,
|
|
13
|
+
dl {
|
|
14
|
+
margin-bottom: 0.8em;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
h1,
|
|
18
|
+
h2,
|
|
19
|
+
h3,
|
|
20
|
+
h4,
|
|
21
|
+
h5,
|
|
22
|
+
h6 {
|
|
23
|
+
margin: 1.3em 0 0.5em;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
h1 {
|
|
27
|
+
font-size: 1.9em;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
h2 {
|
|
31
|
+
font-size: 1.4em;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
h3 {
|
|
35
|
+
font-size: 1.18em;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
li + li {
|
|
39
|
+
margin-top: 0.15em;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pre {
|
|
43
|
+
padding: 0.7em 0.9em;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
th,
|
|
47
|
+
td {
|
|
48
|
+
padding: 0.35em 0.7em;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.math-display {
|
|
52
|
+
margin: 0.8em 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.mermaid-diagram {
|
|
56
|
+
margin: 0.9em 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.doc-header {
|
|
60
|
+
margin-bottom: 1.5em;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.toc {
|
|
64
|
+
margin-bottom: 1.5em;
|
|
65
|
+
padding: 0.8em 1.2em;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
hr {
|
|
69
|
+
margin: 1.5em 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
hr.footnotes-sep {
|
|
73
|
+
margin: 1.8em 0 0.8em;
|
|
74
|
+
}
|
package/dist/assets/theme.css
CHANGED
|
@@ -243,6 +243,14 @@ pre {
|
|
|
243
243
|
background: var(--color-subtle-bg);
|
|
244
244
|
white-space: pre-wrap;
|
|
245
245
|
overflow-wrap: anywhere;
|
|
246
|
+
/* Long listings may continue on the next page, keeping a few lines on each side. */
|
|
247
|
+
break-inside: auto;
|
|
248
|
+
orphans: 4;
|
|
249
|
+
widows: 4;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Short blocks (marked by mdrender) stay on one page. */
|
|
253
|
+
pre.keep-together {
|
|
246
254
|
break-inside: avoid;
|
|
247
255
|
}
|
|
248
256
|
|
|
@@ -335,6 +343,32 @@ div.math-error {
|
|
|
335
343
|
height: auto;
|
|
336
344
|
}
|
|
337
345
|
|
|
346
|
+
/*
|
|
347
|
+
* In the PDF, images and diagrams are scaled down to fit the page height (mdrender sets
|
|
348
|
+
* --page-content-height), so they are never cut across pages.
|
|
349
|
+
*/
|
|
350
|
+
@media print {
|
|
351
|
+
img,
|
|
352
|
+
.mermaid-diagram svg {
|
|
353
|
+
max-height: calc(var(--page-content-height) - 6mm);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Next to its heading: leave room for it (HEADING_ALLOWANCE_EM in render/mermaid.ts). */
|
|
357
|
+
.keep-with-heading img,
|
|
358
|
+
.keep-with-heading .mermaid-diagram svg {
|
|
359
|
+
max-height: calc(var(--page-content-height) - 6mm - 5rem);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* A heading and the diagram or image right after it go to the same page. */
|
|
364
|
+
.keep-with-heading {
|
|
365
|
+
break-inside: avoid;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.markdown-body > .keep-with-heading:first-child > :first-child {
|
|
369
|
+
margin-top: 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
338
372
|
/* Mermaid's HTML labels are <p>/<span> inside <foreignObject>: keep document styles out. */
|
|
339
373
|
.mermaid-diagram foreignObject p {
|
|
340
374
|
margin: 0;
|
package/dist/assets.js
CHANGED
|
@@ -85,7 +85,11 @@ export const MONO_STYLESHEETS = [
|
|
|
85
85
|
];
|
|
86
86
|
export const KATEX_CSS = "katex/dist/katex.min.css";
|
|
87
87
|
export const MERMAID_JS = "mermaid/dist/mermaid.min.js";
|
|
88
|
-
export const THEMES = [
|
|
88
|
+
export const THEMES = [
|
|
89
|
+
"mdrender/assets/theme.css",
|
|
90
|
+
"mdrender/assets/theme-dark.css",
|
|
91
|
+
"mdrender/assets/density-compact.css",
|
|
92
|
+
];
|
|
89
93
|
export const PACKAGE_JSON = "mdrender/package.json";
|
|
90
94
|
/** Fonts referenced by a stylesheet as url(...woff2), as asset keys. */
|
|
91
95
|
export function woff2References(cssKey, css) {
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command, CommanderError, Option } from "commander";
|
|
2
2
|
import { setup } from "./browser/setup.js";
|
|
3
|
+
import { formatReport, runDoctor } from "./doctor.js";
|
|
3
4
|
import { MdRenderError } from "./errors.js";
|
|
4
5
|
import { ExitCode } from "./exit-codes.js";
|
|
5
6
|
import { expandInputs, planOutputs } from "./inputs.js";
|
|
@@ -24,9 +25,13 @@ const DOCUMENT_FLAGS = [
|
|
|
24
25
|
"header",
|
|
25
26
|
"footer",
|
|
26
27
|
"pageNumbers",
|
|
28
|
+
"density",
|
|
29
|
+
"fontSize",
|
|
27
30
|
"css",
|
|
28
31
|
"theme",
|
|
29
32
|
"mermaidTheme",
|
|
33
|
+
"mermaidAutofit",
|
|
34
|
+
"mermaidFlip",
|
|
30
35
|
"lang",
|
|
31
36
|
"title",
|
|
32
37
|
];
|
|
@@ -52,9 +57,13 @@ function addDocumentOptions(command) {
|
|
|
52
57
|
.option("--header <text>", 'page header, e.g. "{title} | | {date}"')
|
|
53
58
|
.option("--footer <text>", 'page footer (default: "{page} / {pages}")')
|
|
54
59
|
.option("--no-page-numbers", "no default page-number footer")
|
|
60
|
+
.option("--density <density>", "normal or compact (tighter spacing) (default: normal)")
|
|
61
|
+
.option("--font-size <size>", "text size, e.g. 10pt or 11pt (default: 10.5pt)")
|
|
55
62
|
.option("--theme <theme>", "light, dark, or a .css file replacing the theme (default: light)")
|
|
56
63
|
.option("--css <file>", "extra stylesheet (repeatable)", collect)
|
|
57
64
|
.option("--mermaid-theme <theme>", `Mermaid theme: ${MERMAID_THEMES.join(", ")} (default: neutral; dark with --theme dark)`)
|
|
65
|
+
.option("--no-mermaid-autofit", "draw flowcharts exactly as written (no layout search)")
|
|
66
|
+
.option("--mermaid-flip <mode>", "auto: turn a flowchart to the other axis if its text prints too small; never (default: auto)")
|
|
58
67
|
.option("--lang <tag>", "document language, e.g. es or en (default: es)")
|
|
59
68
|
.option("--title <text>", "document title (shown as a title block)")
|
|
60
69
|
.option("--config <file>", "config file (default: nearest mdrender.config.json)")
|
|
@@ -80,6 +89,7 @@ function buildRenderProgram(io) {
|
|
|
80
89
|
Commands:
|
|
81
90
|
mdrender preview <file> live HTML preview in the browser (see: mdrender preview --help)
|
|
82
91
|
mdrender setup download a headless Chrome if no browser is installed
|
|
92
|
+
mdrender doctor check the installation, browser and configuration
|
|
83
93
|
|
|
84
94
|
Header/footer placeholders: {page} {pages} {title} {author} {date}; "|" separates
|
|
85
95
|
left | center | right columns.
|
|
@@ -105,7 +115,11 @@ function cliOverrides(program) {
|
|
|
105
115
|
if (program.getOptionValueSource(key) === "cli")
|
|
106
116
|
raw[key] = program.getOptionValue(key);
|
|
107
117
|
}
|
|
108
|
-
return normalizeOptions(raw, "command line", process.cwd(), (key) => key === "pageNumbers"
|
|
118
|
+
return normalizeOptions(raw, "command line", process.cwd(), (key) => key === "pageNumbers"
|
|
119
|
+
? "--no-page-numbers"
|
|
120
|
+
: key === "mermaidAutofit"
|
|
121
|
+
? "--no-mermaid-autofit"
|
|
122
|
+
: toFlag(key));
|
|
109
123
|
}
|
|
110
124
|
function settings(program, options) {
|
|
111
125
|
return {
|
|
@@ -143,6 +157,32 @@ async function runSetup(argv, io) {
|
|
|
143
157
|
return parsed;
|
|
144
158
|
return setup({ io, force: Boolean(program.opts().force) });
|
|
145
159
|
}
|
|
160
|
+
function buildDoctorProgram(io) {
|
|
161
|
+
const program = new Command()
|
|
162
|
+
.name("mdrender doctor")
|
|
163
|
+
.description("Check the installation, browser and configuration, and try a real conversion.\n" +
|
|
164
|
+
"Exits with 1 when a problem is found.")
|
|
165
|
+
.option("--browser <path>", "check this browser instead of the detected one")
|
|
166
|
+
.option("--config <file>", "check this config file (default: nearest mdrender.config.json)")
|
|
167
|
+
.option("--no-config", "ignore mdrender.config.json files")
|
|
168
|
+
.option("--quick", "skip starting the browser and the test conversion")
|
|
169
|
+
.option("--json", "print the report as JSON");
|
|
170
|
+
return configure(program, io);
|
|
171
|
+
}
|
|
172
|
+
async function runDoctorCommand(argv, io) {
|
|
173
|
+
const program = buildDoctorProgram(io);
|
|
174
|
+
const parsed = await parse(program, argv);
|
|
175
|
+
if (parsed !== undefined)
|
|
176
|
+
return parsed;
|
|
177
|
+
const options = program.opts();
|
|
178
|
+
const report = await runDoctor({
|
|
179
|
+
browser: options.browser,
|
|
180
|
+
config: options.config,
|
|
181
|
+
render: !options.quick,
|
|
182
|
+
});
|
|
183
|
+
io.out(options.json ? `${JSON.stringify(report, null, 2)}\n` : formatReport(report));
|
|
184
|
+
return report.ok ? ExitCode.Ok : ExitCode.Usage;
|
|
185
|
+
}
|
|
146
186
|
async function runPreview(argv, io, signal) {
|
|
147
187
|
const program = buildPreviewProgram(io);
|
|
148
188
|
const parsed = await parse(program, argv);
|
|
@@ -192,6 +232,8 @@ export async function main(argv, io = defaultIo, { signal = new AbortController(
|
|
|
192
232
|
return await runPreview(argv.slice(1), io, signal);
|
|
193
233
|
if (argv[0] === "setup")
|
|
194
234
|
return await runSetup(argv.slice(1), io);
|
|
235
|
+
if (argv[0] === "doctor")
|
|
236
|
+
return await runDoctorCommand(argv.slice(1), io);
|
|
195
237
|
return await runRender(argv, io, signal);
|
|
196
238
|
}
|
|
197
239
|
catch (error) {
|
package/dist/convert.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Diagnostic } from "./diagnostics.js";
|
|
|
2
2
|
import { type DocumentOptions, type Margins, type MermaidTheme } from "./options.js";
|
|
3
3
|
import type { OutputFormat } from "./paths.js";
|
|
4
4
|
import { BrowserSession } from "./render/browser.js";
|
|
5
|
+
import { type DiagramLayout } from "./render/mermaid.js";
|
|
5
6
|
/** Settings shared by every file of a run (batch, watch or preview). */
|
|
6
7
|
export interface RenderSettings {
|
|
7
8
|
/** Explicit browser executable. */
|
|
@@ -30,6 +31,8 @@ export interface ConvertResult {
|
|
|
30
31
|
output: string;
|
|
31
32
|
/** Problems found while rendering; the output is still written, with errors shown inline. */
|
|
32
33
|
diagnostics: Diagnostic[];
|
|
34
|
+
/** Details for --verbose, such as the layout chosen for each Mermaid flowchart. */
|
|
35
|
+
notes: Note[];
|
|
33
36
|
/** The effective options after merging config file, front matter and command line. */
|
|
34
37
|
options: DocumentOptions;
|
|
35
38
|
/** Config file that was applied, if any. */
|
|
@@ -59,12 +62,20 @@ export declare class BrowserPool {
|
|
|
59
62
|
*/
|
|
60
63
|
export declare const MIN_HEADER_FOOTER_MARGIN = "10mm";
|
|
61
64
|
export declare function headerFooterSpaceWarnings(margin: Margins, hasHeader: boolean, hasFooter: boolean): Diagnostic[];
|
|
65
|
+
export interface Note {
|
|
66
|
+
line?: number;
|
|
67
|
+
message: string;
|
|
68
|
+
}
|
|
69
|
+
/** "Mermaid autofit: wrap 300, turned LR→TB, scale 0.33 → 1.00, height 81 → 903 px". */
|
|
70
|
+
export declare function describeLayout(layout: DiagramLayout): string;
|
|
62
71
|
/** Mermaid theme: explicit choice, else dark diagrams for the dark page theme. */
|
|
63
72
|
export declare function effectiveMermaidTheme(options: DocumentOptions): MermaidTheme;
|
|
64
73
|
/**
|
|
65
74
|
* Render a Markdown file to a complete HTML document. A browser is only started (through
|
|
66
75
|
* `browsers`) when the document has Mermaid diagrams.
|
|
67
76
|
*/
|
|
68
|
-
export declare function renderDocument(inputPath: string, settings: RenderSettings, browsers: BrowserPool
|
|
77
|
+
export declare function renderDocument(inputPath: string, settings: RenderSettings, browsers: BrowserPool,
|
|
78
|
+
/** Printed on pages (PDF): diagrams are fitted to the page height, not only the width. */
|
|
79
|
+
paged?: boolean): Promise<RenderedDocument>;
|
|
69
80
|
/** Read a Markdown file and write it as a self-contained HTML document or a PDF. */
|
|
70
81
|
export declare function convertFile(convert: ConvertOptions): Promise<ConvertResult>;
|
package/dist/convert.js
CHANGED
|
@@ -7,12 +7,13 @@ import { extractFrontMatter } from "./frontmatter.js";
|
|
|
7
7
|
import { strings } from "./i18n.js";
|
|
8
8
|
import { renderMarkdown } from "./markdown/index.js";
|
|
9
9
|
import { buildToc, tocPlaceholder } from "./markdown/toc.js";
|
|
10
|
-
import { mergeOptions, normalizeOptions, OptionsError, resolveMargins, } from "./options.js";
|
|
10
|
+
import { DEFAULT_FONT_SIZE, fontSizePx, mergeOptions, normalizeOptions, OptionsError, resolveMargins, } from "./options.js";
|
|
11
11
|
import { BrowserSession } from "./render/browser.js";
|
|
12
12
|
import { DEFAULT_FOOTER, headerFooterTemplate } from "./render/header-footer.js";
|
|
13
13
|
import { fillMermaid, renderMermaid } from "./render/mermaid.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { SMALL_SCALE } from "./render/mermaid-fit.js";
|
|
15
|
+
import { contentHeightPx, contentWidthPx, lengthToPx } from "./render/page.js";
|
|
16
|
+
import { buildHtmlDocument, builtinThemeCss, densityCss } from "./render/template.js";
|
|
16
17
|
import { writeFileAtomic } from "./write.js";
|
|
17
18
|
/** Starts one browser on first use and keeps it for later conversions. */
|
|
18
19
|
export class BrowserPool {
|
|
@@ -78,6 +79,27 @@ export function headerFooterSpaceWarnings(margin, hasHeader, hasFooter) {
|
|
|
78
79
|
}
|
|
79
80
|
return warnings;
|
|
80
81
|
}
|
|
82
|
+
/** Font size, if set, and the page's text height (used to fit images and diagrams). */
|
|
83
|
+
function layoutCss(options, margin) {
|
|
84
|
+
const height = contentHeightPx(options.pageSize, options.orientation === "landscape", margin);
|
|
85
|
+
const rules = [`:root { --page-content-height: ${height}px; }`];
|
|
86
|
+
if (options.fontSize)
|
|
87
|
+
rules.push(`html { font-size: ${options.fontSize}; }`);
|
|
88
|
+
return rules.join("\n");
|
|
89
|
+
}
|
|
90
|
+
const fixed2 = (n) => n.toFixed(2);
|
|
91
|
+
/** "Mermaid autofit: wrap 300, turned LR→TB, scale 0.33 → 1.00, height 81 → 903 px". */
|
|
92
|
+
export function describeLayout(layout) {
|
|
93
|
+
const changes = [
|
|
94
|
+
layout.wrap === undefined ? undefined : `wrap ${layout.wrap}`,
|
|
95
|
+
layout.turned ? `turned ${layout.turned}` : undefined,
|
|
96
|
+
].filter(Boolean);
|
|
97
|
+
const scale = `scale ${fixed2(layout.baseScale)} → ${fixed2(layout.scale)}`;
|
|
98
|
+
const height = `height ${layout.baseHeight} → ${layout.height} px`;
|
|
99
|
+
return changes.length === 0
|
|
100
|
+
? `Mermaid autofit: kept as written (scale ${fixed2(layout.scale)}, ${layout.tried} layouts tried)`
|
|
101
|
+
: `Mermaid autofit: ${changes.join(", ")}, ${scale}, ${height} (${layout.tried} layouts tried)`;
|
|
102
|
+
}
|
|
81
103
|
/** Mermaid theme: explicit choice, else dark diagrams for the dark page theme. */
|
|
82
104
|
export function effectiveMermaidTheme(options) {
|
|
83
105
|
return options.mermaidTheme ?? (options.theme === "dark" ? "dark" : "neutral");
|
|
@@ -86,7 +108,9 @@ export function effectiveMermaidTheme(options) {
|
|
|
86
108
|
* Render a Markdown file to a complete HTML document. A browser is only started (through
|
|
87
109
|
* `browsers`) when the document has Mermaid diagrams.
|
|
88
110
|
*/
|
|
89
|
-
export async function renderDocument(inputPath, settings, browsers
|
|
111
|
+
export async function renderDocument(inputPath, settings, browsers,
|
|
112
|
+
/** Printed on pages (PDF): diagrams are fitted to the page height, not only the width. */
|
|
113
|
+
paged = false) {
|
|
90
114
|
const input = path.resolve(inputPath);
|
|
91
115
|
const baseDir = path.dirname(input);
|
|
92
116
|
const source = await readFile(input, "utf8");
|
|
@@ -106,6 +130,7 @@ export async function renderDocument(inputPath, settings, browsers) {
|
|
|
106
130
|
...config.warnings.map((message) => ({ severity: "warning", message })),
|
|
107
131
|
...md.diagnostics,
|
|
108
132
|
];
|
|
133
|
+
const notes = [];
|
|
109
134
|
const title = options.title ?? md.title ?? path.parse(input).name;
|
|
110
135
|
// Table of contents: at the [[toc]] marker, or at the start with --toc.
|
|
111
136
|
let body = md.html;
|
|
@@ -121,10 +146,37 @@ export async function renderDocument(inputPath, settings, browsers) {
|
|
|
121
146
|
const session = await browsers.get();
|
|
122
147
|
const width = contentWidthPx(options.pageSize, options.orientation === "landscape", margin);
|
|
123
148
|
const theme = effectiveMermaidTheme(options);
|
|
124
|
-
const
|
|
149
|
+
const fontPx = fontSizePx(options.fontSize ?? DEFAULT_FONT_SIZE);
|
|
150
|
+
const landscape = options.orientation === "landscape";
|
|
151
|
+
const results = await renderMermaid(session, md.mermaid, {
|
|
152
|
+
theme,
|
|
153
|
+
widthPx: width,
|
|
154
|
+
fontPx,
|
|
155
|
+
// Same limit as the print stylesheet: the page's text height minus 6 mm.
|
|
156
|
+
pageHeightPx: paged
|
|
157
|
+
? contentHeightPx(options.pageSize, landscape, margin) - lengthToPx("6mm")
|
|
158
|
+
: Infinity,
|
|
159
|
+
autofit: options.mermaidAutofit,
|
|
160
|
+
flip: options.mermaidFlip,
|
|
161
|
+
});
|
|
125
162
|
const filled = fillMermaid(body, md.nonce, md.mermaid, results);
|
|
126
163
|
body = filled.html;
|
|
127
164
|
diagnostics.push(...filled.diagnostics);
|
|
165
|
+
md.mermaid.forEach((block, i) => {
|
|
166
|
+
const result = results[i];
|
|
167
|
+
if (!result || "error" in result)
|
|
168
|
+
return;
|
|
169
|
+
if (result.layout)
|
|
170
|
+
notes.push({ line: block.line, message: describeLayout(result.layout) });
|
|
171
|
+
if (paged && result.scale !== undefined && result.scale < SMALL_SCALE) {
|
|
172
|
+
diagnostics.push({
|
|
173
|
+
severity: "warning",
|
|
174
|
+
line: block.line,
|
|
175
|
+
message: `Mermaid diagram printed at ${Math.round(result.scale * 100)}% of its size; ` +
|
|
176
|
+
"split it or shorten its labels to make it readable",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
128
180
|
}
|
|
129
181
|
const html = buildHtmlDocument({
|
|
130
182
|
title,
|
|
@@ -136,6 +188,7 @@ export async function renderDocument(inputPath, settings, browsers) {
|
|
|
136
188
|
date: options.date,
|
|
137
189
|
showTitle: options.title !== undefined,
|
|
138
190
|
themeCss,
|
|
191
|
+
layoutCss: [...densityCss(options.density), layoutCss(options, margin)],
|
|
139
192
|
extraCss,
|
|
140
193
|
});
|
|
141
194
|
diagnostics.sort((a, b) => (a.line ?? 0) - (b.line ?? 0));
|
|
@@ -144,14 +197,14 @@ export async function renderDocument(inputPath, settings, browsers) {
|
|
|
144
197
|
...(options.theme.endsWith(".css") ? [options.theme] : []),
|
|
145
198
|
...options.css,
|
|
146
199
|
];
|
|
147
|
-
return { html, title, diagnostics, options, configFile, dependencies };
|
|
200
|
+
return { html, title, diagnostics, notes, options, configFile, dependencies };
|
|
148
201
|
}
|
|
149
202
|
/** Read a Markdown file and write it as a self-contained HTML document or a PDF. */
|
|
150
203
|
export async function convertFile(convert) {
|
|
151
204
|
const output = path.resolve(convert.output);
|
|
152
205
|
const browsers = convert.browsers ?? new BrowserPool(convert.browser);
|
|
153
206
|
try {
|
|
154
|
-
const doc = await renderDocument(convert.input, convert, browsers);
|
|
207
|
+
const doc = await renderDocument(convert.input, convert, browsers, convert.format === "pdf");
|
|
155
208
|
const { options } = doc;
|
|
156
209
|
if (convert.format === "pdf") {
|
|
157
210
|
const margin = resolveMargins(options);
|
|
@@ -185,6 +238,7 @@ export async function convertFile(convert) {
|
|
|
185
238
|
return {
|
|
186
239
|
output,
|
|
187
240
|
diagnostics: doc.diagnostics,
|
|
241
|
+
notes: doc.notes,
|
|
188
242
|
options,
|
|
189
243
|
configFile: doc.configFile,
|
|
190
244
|
dependencies: doc.dependencies,
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type CheckStatus = "ok" | "warn" | "fail" | "info";
|
|
2
|
+
export interface Check {
|
|
3
|
+
name: string;
|
|
4
|
+
status: CheckStatus;
|
|
5
|
+
detail: string;
|
|
6
|
+
/** What to do about a warning or failure. */
|
|
7
|
+
hint?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DoctorOptions {
|
|
10
|
+
/** Explicit browser executable (as with --browser). */
|
|
11
|
+
browser?: string;
|
|
12
|
+
/** Config file to check; undefined: nearest to `cwd`; false: none. */
|
|
13
|
+
config?: string | false;
|
|
14
|
+
/** Start the browser and convert a sample document (the slow part). Default: true. */
|
|
15
|
+
render?: boolean;
|
|
16
|
+
cwd?: string;
|
|
17
|
+
env?: Record<string, string | undefined>;
|
|
18
|
+
platform?: NodeJS.Platform;
|
|
19
|
+
}
|
|
20
|
+
export interface DoctorReport {
|
|
21
|
+
version: string;
|
|
22
|
+
checks: Check[];
|
|
23
|
+
ok: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Where `mdrender` resolves on the PATH, like `which`/`where`. */
|
|
26
|
+
export declare function findOnPath(env: Record<string, string | undefined>, platform?: NodeJS.Platform): string | undefined;
|
|
27
|
+
/** Inspect the installation, browser and configuration, and try a real conversion. */
|
|
28
|
+
export declare function runDoctor(options?: DoctorOptions): Promise<DoctorReport>;
|
|
29
|
+
/** Plain-text report; ASCII markers so it reads well in any Windows console. */
|
|
30
|
+
export declare function formatReport(report: DoctorReport): string;
|