@imontene/mdrender 2.5.0 → 2.6.1
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 +19 -0
- package/README.md +16 -0
- package/dist/assets/theme-antigravity-dark.css +0 -1
- package/dist/assets/theme-antigravity.css +3 -9
- package/dist/cli.js +4 -0
- package/dist/convert.js +24 -2
- package/dist/doctor.js +2 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.js +3 -0
- package/dist/render/field.d.ts +25 -0
- package/dist/render/field.js +36 -0
- package/dist/render/template.d.ts +2 -0
- package/dist/render/template.js +1 -1
- package/dist/render/watermark.d.ts +24 -0
- package/dist/render/watermark.js +85 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Cambios
|
|
2
2
|
|
|
3
|
+
## 2.6.1 — 2026-10-22
|
|
4
|
+
|
|
5
|
+
- **PDF mucho más livianos con los campos de luz.** Chrome convertía los degradados CSS del
|
|
6
|
+
campo en varias imágenes por página. Ahora el campo es un SVG que queda en el PDF como
|
|
7
|
+
sombreado vectorial, con el mismo aspecto:
|
|
8
|
+
- con `--watermark-field`, el README pasa de 3,0 MB a 0,41 MB (antes unos 290 KB más por
|
|
9
|
+
página, ahora unos 7 KB en total);
|
|
10
|
+
- con el tema Anti-Gravity, el campo junto al título ya no añade ~140 KB a cada documento
|
|
11
|
+
con título (el ejemplo pasa de 332 KB a 195 KB).
|
|
12
|
+
|
|
13
|
+
## 2.6.0 — 2026-10-22
|
|
14
|
+
|
|
15
|
+
- **Marcas de agua** con el degradado Anti-Gravity, solo en el PDF y desactivadas por defecto:
|
|
16
|
+
- `--watermark <texto>` (o `watermark:`): texto en diagonal en cada página, ajustado al
|
|
17
|
+
tamaño y la orientación de la hoja;
|
|
18
|
+
- `--watermark-field` (o `watermarkField: true`): campo de luz en la esquina de cada página.
|
|
19
|
+
|
|
20
|
+
Van detrás del contenido y funcionan con cualquier tema.
|
|
21
|
+
|
|
3
22
|
## 2.5.0 — 2026-10-22
|
|
4
23
|
|
|
5
24
|
- **Nuevo estilo por defecto: Anti-Gravity**, tomado del design system del mismo nombre y
|
package/README.md
CHANGED
|
@@ -156,6 +156,8 @@ equipo); `--port` fija el puerto y `--no-open` no abre el navegador.
|
|
|
156
156
|
| `--no-page-numbers` | Sin el pie con números de página | — |
|
|
157
157
|
| `--font <manrope\|inter>` | Tipografía del texto (ver abajo) | `manrope` |
|
|
158
158
|
| `--no-justify` | Párrafos alineados a la izquierda en el PDF (ver abajo) | justificado |
|
|
159
|
+
| `--watermark <texto>` | Marca de agua en diagonal en cada página del PDF, con el degradado Anti-Gravity | — |
|
|
160
|
+
| `--watermark-field` | Campo de luz con el degradado en la esquina de cada página del PDF | — |
|
|
159
161
|
| `--density <densidad>` | `normal` o `compact` (menos espacio entre líneas y bloques) | `normal` |
|
|
160
162
|
| `--font-size <tamaño>` | Tamaño del texto en `pt` o `px`, de 6 a 24 pt; p. ej. `10pt` | `10.5pt` |
|
|
161
163
|
| `--theme <tema\|css>` | `antigravity`, `antigravity-dark`, `light`, `dark` o un `.css` que reemplaza el tema (ver abajo) | `antigravity` |
|
|
@@ -195,6 +197,20 @@ del mismo nombre y llevado de diapositivas a páginas:
|
|
|
195
197
|
versiones anteriores y `--theme dark` su variante oscura. El design system pide texto alineado
|
|
196
198
|
a la izquierda: con este tema, usa `--no-justify` si quieres seguirlo al pie de la letra.
|
|
197
199
|
|
|
200
|
+
**Marca de agua**: dos marcas con el degradado azul → cian → violeta del design system
|
|
201
|
+
Anti-Gravity, cada una por separado y solo en el PDF:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
mdrender informe.md --watermark "Borrador" # texto en diagonal en cada página
|
|
205
|
+
mdrender informe.md --watermark-field # campo de luz en la esquina de cada página
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
También como `watermark: Borrador` y `watermarkField: true` en el front matter o en
|
|
209
|
+
`mdrender.config.json`. Van muy tenues y detrás del contenido: no tapan el texto, y las
|
|
210
|
+
imágenes, tablas y tarjetas quedan por encima. Según la versión de Chrome, el texto de la marca
|
|
211
|
+
puede formar parte del texto del PDF (aparecer al copiar o buscar). Funcionan con cualquier tema
|
|
212
|
+
y son algo más intensas en los oscuros.
|
|
213
|
+
|
|
198
214
|
**Tipografía**: el texto usa [Manrope](https://fonts.google.com/specimen/Manrope);
|
|
199
215
|
`--font inter` (o `font: inter` en el front matter o en `mdrender.config.json`) vuelve a
|
|
200
216
|
[Inter](https://fonts.google.com/specimen/Inter), la tipografía de las versiones anteriores.
|
|
@@ -16,10 +16,6 @@
|
|
|
16
16
|
--ag-hairline-strong: #c9d0db;
|
|
17
17
|
--ag-accent: #2456d3;
|
|
18
18
|
--ag-accent-soft: #ecf1fd;
|
|
19
|
-
--ag-field-blue: #4f7cff;
|
|
20
|
-
--ag-field-cyan: #22c3e6;
|
|
21
|
-
--ag-field-violet: #8c6cf0;
|
|
22
|
-
--ag-opacity-field: 0.16;
|
|
23
19
|
--ag-shadow-card: 0 1px 2px rgba(15, 20, 25, 0.04), 0 12px 32px rgba(15, 20, 25, 0.06);
|
|
24
20
|
|
|
25
21
|
/* The variables theme.css is written with */
|
|
@@ -152,12 +148,10 @@ h6 {
|
|
|
152
148
|
right: 0;
|
|
153
149
|
bottom: 0;
|
|
154
150
|
width: 36%;
|
|
155
|
-
opacity: var(--ag-opacity-field);
|
|
156
151
|
pointer-events: none;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
radial-gradient(25% 30% at 40% 32%, var(--ag-field-violet), transparent);
|
|
152
|
+
/* An SVG image (render/field.ts) set by mdrender, with the theme's opacity: CSS gradients
|
|
153
|
+
fading to transparent print as heavy bitmaps. */
|
|
154
|
+
background: var(--ag-field-image) center / 100% 100% no-repeat;
|
|
161
155
|
}
|
|
162
156
|
|
|
163
157
|
/* Text */
|
package/dist/cli.js
CHANGED
|
@@ -27,6 +27,8 @@ const DOCUMENT_FLAGS = [
|
|
|
27
27
|
"pageNumbers",
|
|
28
28
|
"font",
|
|
29
29
|
"justify",
|
|
30
|
+
"watermark",
|
|
31
|
+
"watermarkField",
|
|
30
32
|
"density",
|
|
31
33
|
"fontSize",
|
|
32
34
|
"css",
|
|
@@ -63,6 +65,8 @@ function addDocumentOptions(command) {
|
|
|
63
65
|
.option("--no-page-numbers", "no default page-number footer")
|
|
64
66
|
.option("--font <font>", "text typeface: manrope or inter (default: manrope)")
|
|
65
67
|
.option("--no-justify", "align paragraphs left in the PDF instead of justifying them")
|
|
68
|
+
.option("--watermark <text>", "text across every PDF page, in the Anti-Gravity gradient")
|
|
69
|
+
.option("--watermark-field", "a soft field of light in the corner of every PDF page")
|
|
66
70
|
.option("--density <density>", "normal or compact (tighter spacing) (default: normal)")
|
|
67
71
|
.option("--font-size <size>", "text size, e.g. 10pt or 11pt (default: 10.5pt)")
|
|
68
72
|
.option("--theme <theme>", "antigravity, antigravity-dark, light, dark, or a .css file replacing the theme " +
|
package/dist/convert.js
CHANGED
|
@@ -12,8 +12,10 @@ 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
14
|
import { SMALL_SCALE } from "./render/mermaid-fit.js";
|
|
15
|
-
import { headerFooterStyle, mermaidThemeVariables } from "./render/antigravity.js";
|
|
15
|
+
import { headerFooterStyle, isAntigravityTheme, mermaidThemeVariables, } from "./render/antigravity.js";
|
|
16
|
+
import { fieldImage } from "./render/field.js";
|
|
16
17
|
import { FONT_FAMILIES, fontStack } from "./render/fonts.js";
|
|
18
|
+
import { WATERMARK_CSS, watermarkHtml } from "./render/watermark.js";
|
|
17
19
|
import { contentHeightPx, contentWidthPx, lengthToPx, textWidthPx } from "./render/page.js";
|
|
18
20
|
import { buildHtmlDocument, builtinThemeCss, densityCss } from "./render/template.js";
|
|
19
21
|
import { writeFileAtomic } from "./write.js";
|
|
@@ -109,10 +111,24 @@ function layoutCss(options, margin) {
|
|
|
109
111
|
rules.push("h1, h2, h3, h4, h5, h6 { line-height: 1.4; }");
|
|
110
112
|
if (options.justify)
|
|
111
113
|
rules.push(JUSTIFY_CSS);
|
|
114
|
+
if (isAntigravityTheme(options.theme)) {
|
|
115
|
+
rules.push(`:root { --ag-field-image: ${fieldImage(isDarkTheme(options.theme))}; }`);
|
|
116
|
+
}
|
|
112
117
|
if (options.fontSize)
|
|
113
118
|
rules.push(`html { font-size: ${options.fontSize}; }`);
|
|
114
119
|
return rules.join("\n");
|
|
115
120
|
}
|
|
121
|
+
/** The watermarks asked for (PDF only), sized to the page's text area. */
|
|
122
|
+
function watermarks(options, margin) {
|
|
123
|
+
const landscape = options.orientation === "landscape";
|
|
124
|
+
return watermarkHtml({
|
|
125
|
+
text: options.watermark,
|
|
126
|
+
field: options.watermarkField,
|
|
127
|
+
widthPx: textWidthPx(options.pageSize, landscape, margin),
|
|
128
|
+
heightPx: contentHeightPx(options.pageSize, landscape, margin),
|
|
129
|
+
dark: isDarkTheme(options.theme),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
116
132
|
const fixed2 = (n) => n.toFixed(2);
|
|
117
133
|
/** "Mermaid autofit: wrap 300, turned LR→TB, scale 0.33 → 1.00, height 81 → 903 px". */
|
|
118
134
|
export function describeLayout(layout) {
|
|
@@ -215,6 +231,7 @@ paged = false) {
|
|
|
215
231
|
}
|
|
216
232
|
});
|
|
217
233
|
}
|
|
234
|
+
const watermark = paged ? watermarks(options, margin) : "";
|
|
218
235
|
const html = buildHtmlDocument({
|
|
219
236
|
title,
|
|
220
237
|
body,
|
|
@@ -226,7 +243,12 @@ paged = false) {
|
|
|
226
243
|
showTitle: options.title !== undefined,
|
|
227
244
|
themeCss,
|
|
228
245
|
font: options.font,
|
|
229
|
-
|
|
246
|
+
beforeBody: watermark || undefined,
|
|
247
|
+
layoutCss: [
|
|
248
|
+
...densityCss(options.density),
|
|
249
|
+
layoutCss(options, margin),
|
|
250
|
+
...(watermark ? [WATERMARK_CSS] : []),
|
|
251
|
+
],
|
|
230
252
|
extraCss,
|
|
231
253
|
});
|
|
232
254
|
diagnostics.sort((a, b) => (a.line ?? 0) - (b.line ?? 0));
|
package/dist/doctor.js
CHANGED
|
@@ -138,6 +138,8 @@ function settingsCheck(options) {
|
|
|
138
138
|
`${m.left} (top right bottom left), ${options.font} ` +
|
|
139
139
|
`${options.fontSize ?? DEFAULT_FONT_SIZE}, ${options.density} density, ` +
|
|
140
140
|
`${options.justify ? "justified" : "ragged"} paragraphs, ` +
|
|
141
|
+
(options.watermark?.trim() ? `watermark "${options.watermark.trim()}", ` : "") +
|
|
142
|
+
(options.watermarkField ? "watermark field, " : "") +
|
|
141
143
|
`language ${options.lang}, theme ` +
|
|
142
144
|
path.basename(options.theme),
|
|
143
145
|
};
|
package/dist/options.d.ts
CHANGED
|
@@ -49,6 +49,10 @@ export interface DocumentOptions {
|
|
|
49
49
|
mermaidAutofit: boolean;
|
|
50
50
|
/** Whether autofit may turn a flowchart to the other axis (LR ↔ TB) when it reads far better. */
|
|
51
51
|
mermaidFlip: MermaidFlip;
|
|
52
|
+
/** Text set diagonally across every page of the PDF, in the Anti-Gravity gradient. */
|
|
53
|
+
watermark?: string;
|
|
54
|
+
/** A soft field of light (the Anti-Gravity gradient) in the corner of every PDF page. */
|
|
55
|
+
watermarkField: boolean;
|
|
52
56
|
/** Justify paragraphs in the PDF (body, quotes, alerts), breaking long words with hyphens. */
|
|
53
57
|
justify: boolean;
|
|
54
58
|
/** Typeface of the text: Manrope or Inter (code is always JetBrains Mono). */
|
package/dist/options.js
CHANGED
|
@@ -22,6 +22,7 @@ export const DEFAULT_OPTIONS = {
|
|
|
22
22
|
mermaidFlip: "auto",
|
|
23
23
|
font: "manrope",
|
|
24
24
|
justify: true,
|
|
25
|
+
watermarkField: false,
|
|
25
26
|
density: "normal",
|
|
26
27
|
theme: "antigravity",
|
|
27
28
|
css: [],
|
|
@@ -122,6 +123,8 @@ const VALIDATORS = {
|
|
|
122
123
|
mermaidFlip: oneOf(MERMAID_FLIPS),
|
|
123
124
|
font: oneOf(TEXT_FONTS),
|
|
124
125
|
justify: bool,
|
|
126
|
+
watermark: text,
|
|
127
|
+
watermarkField: bool,
|
|
125
128
|
density: oneOf(DENSITIES),
|
|
126
129
|
fontSize,
|
|
127
130
|
theme: (value, describe, baseDir) => {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Anti-Gravity gradient field: three soft lights (field-blue, field-cyan, field-violet),
|
|
3
|
+
* each an ellipse fading to transparent inside its box.
|
|
4
|
+
*
|
|
5
|
+
* Drawn as an SVG image rather than CSS radial-gradient()s: Chrome rasterizes CSS gradients that
|
|
6
|
+
* fade to transparent into several bitmaps per page (about 70 KB each, repeated on every page),
|
|
7
|
+
* while SVG gradients with stop-opacity go into the PDF as small vector shadings.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FIELD_COLORS: {
|
|
10
|
+
blue: string;
|
|
11
|
+
cyan: string;
|
|
12
|
+
violet: string;
|
|
13
|
+
};
|
|
14
|
+
/** Field opacity on light pages and on dark ones (the design system's opacity-field tokens). */
|
|
15
|
+
export declare const FIELD_OPACITY: {
|
|
16
|
+
light: number;
|
|
17
|
+
dark: number;
|
|
18
|
+
};
|
|
19
|
+
/** The field as an SVG stretched over its box (viewBox in %, no aspect ratio kept). */
|
|
20
|
+
export declare function fieldSvg(opacity: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* CSS `url(…)` of the field image. The SVG has only double quotes, which the encoding escapes, so
|
|
23
|
+
* the value is safe inside single quotes, also in a style="…" attribute.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fieldImage(dark: boolean): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Anti-Gravity gradient field: three soft lights (field-blue, field-cyan, field-violet),
|
|
3
|
+
* each an ellipse fading to transparent inside its box.
|
|
4
|
+
*
|
|
5
|
+
* Drawn as an SVG image rather than CSS radial-gradient()s: Chrome rasterizes CSS gradients that
|
|
6
|
+
* fade to transparent into several bitmaps per page (about 70 KB each, repeated on every page),
|
|
7
|
+
* while SVG gradients with stop-opacity go into the PDF as small vector shadings.
|
|
8
|
+
*/
|
|
9
|
+
export const FIELD_COLORS = { blue: "#4f7cff", cyan: "#22c3e6", violet: "#8c6cf0" };
|
|
10
|
+
/** Field opacity on light pages and on dark ones (the design system's opacity-field tokens). */
|
|
11
|
+
export const FIELD_OPACITY = { light: 0.16, dark: 0.22 };
|
|
12
|
+
/** Each light: centre and radii in % of the box (as `radial-gradient(rx ry at cx cy)`). */
|
|
13
|
+
const LIGHTS = [
|
|
14
|
+
{ color: FIELD_COLORS.blue, cx: 58, cy: 45, rx: 28, ry: 38 },
|
|
15
|
+
{ color: FIELD_COLORS.cyan, cx: 78, cy: 58, rx: 16, ry: 26 },
|
|
16
|
+
{ color: FIELD_COLORS.violet, cx: 40, cy: 32, rx: 25, ry: 30 },
|
|
17
|
+
];
|
|
18
|
+
/** The field as an SVG stretched over its box (viewBox in %, no aspect ratio kept). */
|
|
19
|
+
export function fieldSvg(opacity) {
|
|
20
|
+
const gradients = LIGHTS.map(({ color, cx, cy, rx, ry }, i) => `<radialGradient id="f${i}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1" ` +
|
|
21
|
+
`gradientTransform="translate(${cx} ${cy}) scale(${rx} ${ry})">` +
|
|
22
|
+
`<stop offset="0" stop-color="${color}" stop-opacity="${opacity}"/>` +
|
|
23
|
+
`<stop offset="1" stop-color="${color}" stop-opacity="0"/></radialGradient>`).join("");
|
|
24
|
+
const fills = LIGHTS.map((_, i) => `<rect width="100" height="100" fill="url(#f${i})"/>`).join("");
|
|
25
|
+
return (`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="none">` +
|
|
26
|
+
`<defs>${gradients}</defs>${fills}</svg>`);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* CSS `url(…)` of the field image. The SVG has only double quotes, which the encoding escapes, so
|
|
30
|
+
* the value is safe inside single quotes, also in a style="…" attribute.
|
|
31
|
+
*/
|
|
32
|
+
export function fieldImage(dark) {
|
|
33
|
+
const svg = fieldSvg(dark ? FIELD_OPACITY.dark : FIELD_OPACITY.light);
|
|
34
|
+
return `url('data:image/svg+xml,${encodeURIComponent(svg)}')`;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=field.js.map
|
|
@@ -20,6 +20,8 @@ export interface DocumentParts {
|
|
|
20
20
|
themeCss?: string[];
|
|
21
21
|
/** Text font to embed. Defaults to Manrope. */
|
|
22
22
|
font?: TextFont;
|
|
23
|
+
/** HTML placed before the document body (the watermarks). */
|
|
24
|
+
beforeBody?: string;
|
|
23
25
|
/** Layout stylesheets (density, font size, page height), applied after the theme. */
|
|
24
26
|
layoutCss?: string[];
|
|
25
27
|
/** Extra stylesheet contents, applied after the theme and layout. */
|
package/dist/render/template.js
CHANGED
|
@@ -61,7 +61,7 @@ ${author ? `<meta name="author" content="${escapeHtml(author)}">\n` : ""}<title>
|
|
|
61
61
|
${styles.map((css) => `<style>\n${css}\n</style>`).join("\n")}
|
|
62
62
|
</head>
|
|
63
63
|
<body>
|
|
64
|
-
<main class="markdown-body">
|
|
64
|
+
${parts.beforeBody ? parts.beforeBody + "\n" : ""}<main class="markdown-body">
|
|
65
65
|
${parts.showTitle ? titleBlock(parts) + "\n" : ""}${body}
|
|
66
66
|
</main>
|
|
67
67
|
</body>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface WatermarkOptions {
|
|
2
|
+
/** Text set across every page; undefined or blank for none. */
|
|
3
|
+
text?: string;
|
|
4
|
+
/** A field of light in the corner of every page. */
|
|
5
|
+
field: boolean;
|
|
6
|
+
/** Size of the text area in CSS px (the text runs along its diagonal). */
|
|
7
|
+
widthPx: number;
|
|
8
|
+
heightPx: number;
|
|
9
|
+
/** Dark page: the marks are a little stronger, as the design system's dark stage. */
|
|
10
|
+
dark: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Angle (degrees, counter-clockwise) and font size (px) that fit `text` on the diagonal. */
|
|
13
|
+
export declare function diagonalText(text: string, widthPx: number, heightPx: number): {
|
|
14
|
+
angle: number;
|
|
15
|
+
fontSize: number;
|
|
16
|
+
};
|
|
17
|
+
/** HTML of the requested marks, to go before the document body; "" when none is requested. */
|
|
18
|
+
export declare function watermarkHtml(options: WatermarkOptions): string;
|
|
19
|
+
/**
|
|
20
|
+
* Print-only placement: fixed over the text area of every page, under the content (the body
|
|
21
|
+
* becomes a stacking layer above it). Hidden on screen. With the field on every page, the title
|
|
22
|
+
* block's own field (Anti-Gravity) is dropped so the first page does not get two.
|
|
23
|
+
*/
|
|
24
|
+
export declare const WATERMARK_CSS: string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { escapeHtml } from "../html.js";
|
|
2
|
+
import { FIELD_COLORS, fieldImage } from "./field.js";
|
|
3
|
+
/**
|
|
4
|
+
* Watermarks in the Anti-Gravity gradient (field-blue → field-cyan → field-violet), PDF only:
|
|
5
|
+
* a text set diagonally across every page, and a soft field of light in the top-right corner of
|
|
6
|
+
* every page. Both sit behind the content. Chrome repeats a `position: fixed` element on every
|
|
7
|
+
* printed page, at the same place in the text area, so pagination is left alone.
|
|
8
|
+
*/
|
|
9
|
+
const FIELD = FIELD_COLORS;
|
|
10
|
+
/** Angle (degrees, counter-clockwise) and font size (px) that fit `text` on the diagonal. */
|
|
11
|
+
export function diagonalText(text, widthPx, heightPx) {
|
|
12
|
+
const angle = (Math.atan2(heightPx, widthPx) * 180) / Math.PI;
|
|
13
|
+
const diagonal = Math.hypot(widthPx, heightPx);
|
|
14
|
+
// Manrope 600 capitals average about 0.68 em; the text takes ~70% of the diagonal.
|
|
15
|
+
const chars = Math.max(1, [...text].length);
|
|
16
|
+
const fontSize = Math.min(heightPx * 0.2, (diagonal * 0.7) / (chars * 0.68));
|
|
17
|
+
return { angle, fontSize: Math.round(fontSize) };
|
|
18
|
+
}
|
|
19
|
+
function textMark(text, widthPx, heightPx, dark) {
|
|
20
|
+
const { angle, fontSize } = diagonalText(text, widthPx, heightPx);
|
|
21
|
+
// HTML rather than SVG text: Chrome paints the SVG text of a fixed element on the first page
|
|
22
|
+
// only, while HTML is repeated on every page like the rest of the element.
|
|
23
|
+
return `<div class="mdr-watermark-text" style="opacity: ${dark ? 0.14 : 0.1}"><span style="font-size: ${fontSize}px; transform: rotate(${(-angle).toFixed(2)}deg)">${escapeHtml(text.toUpperCase())}</span></div>`;
|
|
24
|
+
}
|
|
25
|
+
function fieldMark(dark) {
|
|
26
|
+
// The same field as the title block's (render/field.ts), as a vector SVG image.
|
|
27
|
+
return `<div class="mdr-watermark-field" style="background-image: ${fieldImage(dark)}"></div>`;
|
|
28
|
+
}
|
|
29
|
+
/** HTML of the requested marks, to go before the document body; "" when none is requested. */
|
|
30
|
+
export function watermarkHtml(options) {
|
|
31
|
+
const text = options.text?.trim();
|
|
32
|
+
if (!text && !options.field)
|
|
33
|
+
return "";
|
|
34
|
+
const parts = [
|
|
35
|
+
options.field ? fieldMark(options.dark) : "",
|
|
36
|
+
text ? textMark(text, options.widthPx, options.heightPx, options.dark) : "",
|
|
37
|
+
].filter(Boolean);
|
|
38
|
+
return `<div class="mdr-watermark" aria-hidden="true">\n${parts.join("\n")}\n</div>`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Print-only placement: fixed over the text area of every page, under the content (the body
|
|
42
|
+
* becomes a stacking layer above it). Hidden on screen. With the field on every page, the title
|
|
43
|
+
* block's own field (Anti-Gravity) is dropped so the first page does not get two.
|
|
44
|
+
*/
|
|
45
|
+
export const WATERMARK_CSS = `.mdr-watermark { display: none; }
|
|
46
|
+
@media print {
|
|
47
|
+
.mdr-watermark {
|
|
48
|
+
display: block;
|
|
49
|
+
position: fixed;
|
|
50
|
+
inset: 0;
|
|
51
|
+
z-index: 0;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
}
|
|
54
|
+
.markdown-body { position: relative; z-index: 1; }
|
|
55
|
+
.mdr-watermark-text {
|
|
56
|
+
position: absolute;
|
|
57
|
+
inset: 0;
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
}
|
|
62
|
+
.mdr-watermark-text span {
|
|
63
|
+
font-family: var(--font-text);
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
letter-spacing: 0.02em;
|
|
66
|
+
line-height: 1;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
color: transparent;
|
|
69
|
+
background: linear-gradient(90deg, ${FIELD.blue}, ${FIELD.cyan}, ${FIELD.violet});
|
|
70
|
+
-webkit-background-clip: text;
|
|
71
|
+
background-clip: text;
|
|
72
|
+
}
|
|
73
|
+
.mdr-watermark-field {
|
|
74
|
+
position: absolute;
|
|
75
|
+
top: 0;
|
|
76
|
+
right: 0;
|
|
77
|
+
width: 40%;
|
|
78
|
+
height: 230px;
|
|
79
|
+
background-position: center;
|
|
80
|
+
background-size: 100% 100%;
|
|
81
|
+
background-repeat: no-repeat;
|
|
82
|
+
}
|
|
83
|
+
.mdr-watermark:has(.mdr-watermark-field) ~ .markdown-body .doc-header::before { display: none; }
|
|
84
|
+
}`;
|
|
85
|
+
//# sourceMappingURL=watermark.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imontene/mdrender",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Command-line app for Windows and Linux that renders Markdown — with LaTeX equations and Mermaid diagrams — to PDF and HTML set in Manrope or Inter",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|