mulmocast 2.1.27 → 2.1.29

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.
@@ -182,6 +182,7 @@ export declare const mediaSourceMermaidSchema: z.ZodDiscriminatedUnion<[z.ZodObj
182
182
  export declare const mulmoMarkdownMediaSchema: z.ZodObject<{
183
183
  type: z.ZodLiteral<"markdown">;
184
184
  markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
185
+ style: z.ZodOptional<z.ZodString>;
185
186
  }, z.core.$strict>;
186
187
  export declare const mulmoImageMediaSchema: z.ZodObject<{
187
188
  type: z.ZodLiteral<"image">;
@@ -271,6 +272,7 @@ export declare const mulmoVisionMediaSchema: z.ZodObject<{
271
272
  export declare const mulmoImageAssetSchema: z.ZodUnion<readonly [z.ZodObject<{
272
273
  type: z.ZodLiteral<"markdown">;
273
274
  markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
275
+ style: z.ZodOptional<z.ZodString>;
274
276
  }, z.core.$strict>, z.ZodObject<{
275
277
  type: z.ZodLiteral<"web">;
276
278
  url: z.ZodURL;
@@ -797,6 +799,7 @@ export declare const mulmoBeatSchema: z.ZodObject<{
797
799
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
798
800
  type: z.ZodLiteral<"markdown">;
799
801
  markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
802
+ style: z.ZodOptional<z.ZodString>;
800
803
  }, z.core.$strict>, z.ZodObject<{
801
804
  type: z.ZodLiteral<"web">;
802
805
  url: z.ZodURL;
@@ -1981,6 +1984,7 @@ export declare const mulmoScriptSchema: z.ZodObject<{
1981
1984
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
1982
1985
  type: z.ZodLiteral<"markdown">;
1983
1986
  markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
1987
+ style: z.ZodOptional<z.ZodString>;
1984
1988
  }, z.core.$strict>, z.ZodObject<{
1985
1989
  type: z.ZodLiteral<"web">;
1986
1990
  url: z.ZodURL;
@@ -2869,6 +2873,7 @@ export declare const mulmoStudioSchema: z.ZodObject<{
2869
2873
  image: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
2870
2874
  type: z.ZodLiteral<"markdown">;
2871
2875
  markdown: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
2876
+ style: z.ZodOptional<z.ZodString>;
2872
2877
  }, z.core.$strict>, z.ZodObject<{
2873
2878
  type: z.ZodLiteral<"web">;
2874
2879
  url: z.ZodURL;
@@ -79,6 +79,7 @@ export const mulmoMarkdownMediaSchema = z
79
79
  .object({
80
80
  type: z.literal("markdown"),
81
81
  markdown: stringOrStringArray,
82
+ style: z.string().optional(),
82
83
  })
83
84
  .strict();
84
85
  const mulmoWebMediaSchema = z
@@ -275,6 +275,7 @@ export declare const createStudioData: (_mulmoScript: MulmoScript, fileName: str
275
275
  } | {
276
276
  type: "markdown";
277
277
  markdown: string | string[];
278
+ style?: string | undefined;
278
279
  } | {
279
280
  type: "web";
280
281
  url: string;
@@ -931,6 +932,7 @@ export declare const initializeContextFromFiles: (files: FileObject, raiseError:
931
932
  } | {
932
933
  type: "markdown";
933
934
  markdown: string | string[];
935
+ style?: string | undefined;
934
936
  } | {
935
937
  type: "web";
936
938
  url: string;
@@ -0,0 +1,3 @@
1
+ export declare const renderHTMLToImage: (html: string, outputPath: string, width: number, height: number, isMermaid?: boolean, omitBackground?: boolean) => Promise<void>;
2
+ export declare const renderMarkdownToImage: (markdown: string, style: string, outputPath: string, width: number, height: number) => Promise<void>;
3
+ export declare const interpolate: (template: string, data: Record<string, string>) => string;
@@ -0,0 +1,50 @@
1
+ import { marked } from "marked";
2
+ import puppeteer from "puppeteer";
3
+ const isCI = process.env.CI === "true";
4
+ export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false) => {
5
+ // Use Puppeteer to render HTML to an image
6
+ const browser = await puppeteer.launch({
7
+ args: isCI ? ["--no-sandbox"] : [],
8
+ });
9
+ const page = await browser.newPage();
10
+ // Set the page content to the HTML generated from the Markdown
11
+ await page.setContent(html);
12
+ // Adjust page settings if needed (like width, height, etc.)
13
+ await page.setViewport({ width, height });
14
+ // height:100% ensures background fills viewport; only reset html, let body styles come from custom CSS
15
+ await page.addStyleTag({ content: "html{height:100%;margin:0;padding:0;overflow:hidden}" });
16
+ if (isMermaid) {
17
+ await page.waitForFunction(() => {
18
+ const element = document.querySelector(".mermaid");
19
+ return element && element.dataset.ready === "true";
20
+ }, { timeout: 20000 });
21
+ }
22
+ // Wait for Chart.js to finish rendering if this is a chart
23
+ if (html.includes("data-chart-ready")) {
24
+ await page.waitForFunction(() => {
25
+ const canvas = document.querySelector("canvas[data-chart-ready='true']");
26
+ return !!canvas;
27
+ }, { timeout: 20000 });
28
+ }
29
+ // Measure the size of the page and scale the page to the width and height
30
+ await page.evaluate(({ viewportWidth, viewportHeight }) => {
31
+ const docElement = document.documentElement;
32
+ const scrollWidth = Math.max(docElement.scrollWidth, document.body.scrollWidth || 0);
33
+ const scrollHeight = Math.max(docElement.scrollHeight, document.body.scrollHeight || 0);
34
+ const scale = Math.min(viewportWidth / (scrollWidth || viewportWidth), viewportHeight / (scrollHeight || viewportHeight), 1);
35
+ docElement.style.overflow = "hidden";
36
+ document.body.style.zoom = String(scale);
37
+ }, { viewportWidth: width, viewportHeight: height });
38
+ // Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
39
+ await page.screenshot({ path: outputPath, omitBackground });
40
+ await browser.close();
41
+ };
42
+ export const renderMarkdownToImage = async (markdown, style, outputPath, width, height) => {
43
+ const header = `<head><style>${style}</style></head>`;
44
+ const body = await marked(markdown);
45
+ const html = `<html>${header}<body>${body}</body></html>`;
46
+ await renderHTMLToImage(html, outputPath, width, height);
47
+ };
48
+ export const interpolate = (template, data) => {
49
+ return template.replace(/\$\{(.*?)\}/g, (_, key) => data[key.trim()] ?? "");
50
+ };
@@ -1,5 +1,5 @@
1
1
  import { getHTMLFile } from "../file.js";
2
- import { renderHTMLToImage, interpolate } from "../markdown.js";
2
+ import { renderHTMLToImage, interpolate } from "../html_render.js";
3
3
  import { parrotingImagePath } from "./utils.js";
4
4
  import nodeProcess from "node:process";
5
5
  export const imageType = "chart";
@@ -1,5 +1,5 @@
1
1
  import { getHTMLFile } from "../file.js";
2
- import { renderHTMLToImage, interpolate } from "../markdown.js";
2
+ import { renderHTMLToImage, interpolate } from "../html_render.js";
3
3
  import { parrotingImagePath } from "./utils.js";
4
4
  export const imageType = "html_tailwind";
5
5
  const processHtmlTailwind = async (params) => {
@@ -1,5 +1,6 @@
1
- import { renderMarkdownToImage } from "../markdown.js";
1
+ import { renderMarkdownToImage } from "../html_render.js";
2
2
  import { parrotingImagePath } from "./utils.js";
3
+ import { getMarkdownStyle } from "../../data/markdownStyles.js";
3
4
  import { marked } from "marked";
4
5
  export const imageType = "markdown";
5
6
  const processMarkdown = async (params) => {
@@ -7,7 +8,11 @@ const processMarkdown = async (params) => {
7
8
  if (!beat.image || beat.image.type !== imageType)
8
9
  return;
9
10
  const markdown = dumpMarkdown(params) ?? "";
10
- await renderMarkdownToImage(markdown, textSlideStyle, imagePath, canvasSize.width, canvasSize.height);
11
+ // Use custom style if specified, otherwise use default textSlideStyle
12
+ const styleName = beat.image.style;
13
+ const customStyle = styleName ? getMarkdownStyle(styleName) : undefined;
14
+ const style = customStyle ? customStyle.css : textSlideStyle;
15
+ await renderMarkdownToImage(markdown, style, imagePath, canvasSize.width, canvasSize.height);
11
16
  return imagePath;
12
17
  };
13
18
  const dumpMarkdown = (params) => {
@@ -1,6 +1,6 @@
1
1
  import { MulmoMediaSourceMethods } from "../../methods/index.js";
2
2
  import { getHTMLFile } from "../file.js";
3
- import { renderHTMLToImage, interpolate } from "../markdown.js";
3
+ import { renderHTMLToImage, interpolate } from "../html_render.js";
4
4
  import { parrotingImagePath } from "./utils.js";
5
5
  import nodeProcess from "node:process";
6
6
  export const imageType = "mermaid";
@@ -1,4 +1,4 @@
1
- import { renderMarkdownToImage } from "../markdown.js";
1
+ import { renderMarkdownToImage } from "../html_render.js";
2
2
  import { parrotingImagePath } from "./utils.js";
3
3
  import { marked } from "marked";
4
4
  export const imageType = "textSlide";
@@ -1,113 +1,42 @@
1
1
  import { marked } from "marked";
2
2
  import puppeteer from "puppeteer";
3
3
  const isCI = process.env.CI === "true";
4
- const reuseBrowser = process.env.MULMO_PUPPETEER_REUSE !== "0";
5
- const browserLaunchArgs = isCI ? ["--no-sandbox"] : [];
6
- // Shared browser to avoid spawning a new Chromium per render.
7
- let sharedBrowserPromise = null;
8
- let sharedBrowserRefs = 0;
9
- let sharedBrowserCloseTimer = null;
10
- // Acquire a browser instance; reuse a shared one when enabled.
11
- const acquireBrowser = async () => {
12
- if (!reuseBrowser) {
13
- return await puppeteer.launch({ args: browserLaunchArgs });
14
- }
15
- sharedBrowserRefs += 1;
16
- if (sharedBrowserCloseTimer) {
17
- clearTimeout(sharedBrowserCloseTimer);
18
- sharedBrowserCloseTimer = null;
19
- }
20
- if (!sharedBrowserPromise) {
21
- sharedBrowserPromise = puppeteer.launch({ args: browserLaunchArgs });
22
- }
23
- const currentPromise = sharedBrowserPromise;
24
- try {
25
- return await currentPromise;
26
- }
27
- catch (error) {
28
- if (sharedBrowserPromise === currentPromise) {
29
- sharedBrowserPromise = null;
30
- }
31
- sharedBrowserRefs = Math.max(0, sharedBrowserRefs - 1);
32
- throw error;
33
- }
34
- };
35
- // Release the browser; close only after a short idle window.
36
- const releaseBrowser = async (browser) => {
37
- if (!reuseBrowser) {
38
- await browser.close().catch(() => { });
39
- return;
40
- }
41
- sharedBrowserRefs = Math.max(0, sharedBrowserRefs - 1);
42
- if (sharedBrowserRefs > 0 || !sharedBrowserPromise) {
43
- return;
44
- }
45
- // Delay close to allow back-to-back renders to reuse the browser.
46
- sharedBrowserCloseTimer = setTimeout(async () => {
47
- const current = sharedBrowserPromise;
48
- sharedBrowserPromise = null;
49
- sharedBrowserCloseTimer = null;
50
- if (current) {
51
- await (await current).close().catch(() => { });
52
- }
53
- }, 300);
54
- };
55
- // Wait for a single animation frame to let canvas paints settle.
56
- const waitForNextFrame = async (page) => {
57
- await page.evaluate(() => new Promise((resolve) => {
58
- requestAnimationFrame(() => resolve());
59
- }));
60
- };
61
4
  export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false) => {
62
- // Charts are rendered in a dedicated browser to avoid shared-page timing issues.
63
- const useSharedBrowser = reuseBrowser && !html.includes("data-chart-ready");
64
- const browser = useSharedBrowser ? await acquireBrowser() : await puppeteer.launch({ args: browserLaunchArgs });
65
- let page = null;
66
- try {
67
- page = await browser.newPage();
68
- // Adjust page settings if needed (like width, height, etc.)
69
- await page.setViewport({ width, height });
70
- // Set the page content to the HTML generated from the Markdown
71
- await page.setContent(html, { waitUntil: "domcontentloaded" });
72
- await page.addStyleTag({ content: "html,body{margin:0;padding:0;overflow:hidden}" });
73
- if (isMermaid) {
74
- await page.waitForFunction(() => {
75
- const element = document.querySelector(".mermaid");
76
- return element && element.dataset.ready === "true";
77
- }, { timeout: 20000 });
78
- }
79
- if (html.includes("data-chart-ready")) {
80
- await page.waitForFunction(() => {
81
- const canvas = document.querySelector("canvas[data-chart-ready='true']");
82
- return !!canvas;
83
- }, { timeout: 20000 });
84
- // Give the browser a couple of frames to paint the canvas.
85
- await waitForNextFrame(page);
86
- await waitForNextFrame(page);
87
- }
88
- // Measure the size of the page and scale the page to the width and height
89
- await page.evaluate(({ vw, vh }) => {
90
- const documentElement = document.documentElement;
91
- const scrollWidth = Math.max(documentElement.scrollWidth, document.body.scrollWidth || 0);
92
- const scrollHeight = Math.max(documentElement.scrollHeight, document.body.scrollHeight || 0);
93
- const scale = Math.min(vw / (scrollWidth || vw), vh / (scrollHeight || vh), 1); // <=1 で縮小のみ
94
- documentElement.style.overflow = "hidden";
95
- document.body.style.zoom = String(scale);
96
- }, { vw: width, vh: height });
97
- // Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
98
- await page.screenshot({ path: outputPath, omitBackground });
99
- }
100
- finally {
101
- if (page) {
102
- await page.close().catch(() => { });
103
- }
104
- if (useSharedBrowser) {
105
- await releaseBrowser(browser);
106
- }
107
- else {
108
- await browser.close().catch(() => { });
109
- }
110
- }
5
+ // Use Puppeteer to render HTML to an image
6
+ const browser = await puppeteer.launch({
7
+ args: isCI ? ["--no-sandbox"] : [],
8
+ });
9
+ const page = await browser.newPage();
10
+ // Set the page content to the HTML generated from the Markdown
11
+ await page.setContent(html);
12
+ // Adjust page settings if needed (like width, height, etc.)
13
+ await page.setViewport({ width, height });
14
+ await page.addStyleTag({ content: "html,body{margin:0;padding:0;overflow:hidden}" });
15
+ if (isMermaid) {
16
+ await page.waitForFunction(() => {
17
+ const el = document.querySelector(".mermaid");
18
+ return el && el.dataset.ready === "true";
19
+ }, { timeout: 20000 });
20
+ }
21
+ // Wait for Chart.js to finish rendering if this is a chart
22
+ if (html.includes("data-chart-ready")) {
23
+ await page.waitForFunction(() => {
24
+ const canvas = document.querySelector("canvas[data-chart-ready='true']");
25
+ return !!canvas;
26
+ }, { timeout: 20000 });
27
+ }
28
+ // Measure the size of the page and scale the page to the width and height
29
+ await page.evaluate(({ vw, vh }) => {
30
+ const de = document.documentElement;
31
+ const sw = Math.max(de.scrollWidth, document.body.scrollWidth || 0);
32
+ const sh = Math.max(de.scrollHeight, document.body.scrollHeight || 0);
33
+ const scale = Math.min(vw / (sw || vw), vh / (sh || vh), 1); // <=1 で縮小のみ
34
+ de.style.overflow = "hidden";
35
+ document.body.style.zoom = String(scale);
36
+ }, { vw: width, vh: height });
37
+ // Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
38
+ await page.screenshot({ path: outputPath, omitBackground });
39
+ await browser.close();
111
40
  };
112
41
  export const renderMarkdownToImage = async (markdown, style, outputPath, width, height) => {
113
42
  const header = `<head><style>${style}</style></head>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.1.27",
3
+ "version": "2.1.29",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",