Skip to content

Commit fd89189

Browse files
committed
add fonts on docker
1 parent 76e266d commit fd89189

File tree

2 files changed

+83
-7
lines changed

2 files changed

+83
-7
lines changed

Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ RUN --mount=type=cache,target=/root/.npm npm run build
1111
# --- Release Stage ---
1212
FROM node:22-alpine AS release
1313

14+
# Install necessary tools and Japanese fonts
15+
RUN apk add --no-cache \
16+
fontconfig \
17+
font-noto-cjk \
18+
ttf-freefont \
19+
curl \
20+
unzip \
21+
chromium \
22+
nss
23+
24+
# Download and install IPAex fonts for better Japanese support
25+
RUN curl -L "https://moji.or.jp/wp-content/ipafont/IPAexfont/IPAexfont00401.zip" -o font.zip && \
26+
unzip font.zip && \
27+
mkdir -p /usr/share/fonts/truetype/ipaex && \
28+
cp IPAexfont00401/*.ttf /usr/share/fonts/truetype/ipaex/ && \
29+
rm -rf IPAexfont00401 font.zip
30+
31+
# Update font cache
32+
RUN fc-cache -f -v
33+
34+
# Set Puppeteer to use system Chromium
35+
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
36+
1437
# Set up a non-root user ('appuser'/'appgroup') to avoid running as root - good security practice!
1538
# (-S is the Alpine option for a system user/group, suitable here)
1639
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
@@ -19,6 +42,7 @@ RUN addgroup -S appgroup && adduser -S appuser -G appgroup
1942
COPY --from=builder /app/dist /app/dist
2043
COPY --from=builder /app/package.json /app/package.json
2144
COPY --from=builder /app/package-lock.json /app/package-lock.json
45+
COPY --from=builder /app/richmenu-template /app/richmenu-template
2246

2347
ENV NODE_ENV=production
2448

src/tools/createRichMenu.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,11 @@ export default class CreateRichMenu extends AbstractTool {
106106
richMenuImagePath,
107107
});
108108
} catch (error) {
109+
console.error("Rich menu creation error:", error);
109110
return createErrorResponse(
110111
JSON.stringify({
111-
error,
112+
error: error instanceof Error ? error.message : String(error),
113+
stack: error instanceof Error ? error.stack : undefined,
112114
createRichMenuResponse,
113115
setImageResponse,
114116
setDefaultResponse,
@@ -128,6 +130,9 @@ async function generateRichMenuImage(
128130
templateNo: number,
129131
actions: messagingApi.Action[],
130132
): Promise<string> {
133+
console.log(
134+
`Generating rich menu image for template ${templateNo} with ${actions.length} actions`,
135+
);
131136
// Flow:
132137
// 1. Read the Markdown template
133138
// 2. Convert Markdown to HTML using Marp
@@ -143,9 +148,12 @@ async function generateRichMenuImage(
143148
// 1. Read the Markdown template
144149
const srcPath = path.join(
145150
serverPath,
146-
`richmenu-templates/template-0${templateNo}.md`,
151+
`richmenu-template/template-0${templateNo}.md`,
147152
);
153+
console.log(`Reading template from: ${srcPath}`);
154+
console.log(`Server path: ${serverPath}`);
148155
let content = await fsp.readFile(srcPath, "utf8");
156+
console.log(`Template content length: ${content.length}`);
149157
for (let index = 0; index < actions.length; index++) {
150158
const pattern = new RegExp(`<h3>item0${index + 1}</h3>`, "g");
151159
content = content.replace(pattern, `<h3>${actions[index].label}</h3>`);
@@ -155,11 +163,19 @@ async function generateRichMenuImage(
155163
const marp = new Marp();
156164
const { html, css } = marp.render(content);
157165

158-
// 3. Save the HTML as a temporary file
166+
// 3. Save the HTML as a temporary file with Japanese font support
159167
const htmlContent = `
160-
<html>
168+
<!doctype html>
169+
<html lang="ja">
161170
<head>
162-
<style>${css}</style>
171+
<meta charset="UTF-8">
172+
<style>
173+
${css}
174+
* {
175+
font-family: 'IPAexGothic', 'IPAexMincho', 'Noto Sans CJK JP', 'Noto Sans JP', 'Yu Gothic UI', 'Yu Gothic', 'Meiryo UI', 'Meiryo', 'MS UI Gothic', sans-serif !important;
176+
}
177+
html, body { margin: 0; padding: 0; }
178+
</style>
163179
</head>
164180
<body>${html}</body>
165181
</html>
@@ -170,19 +186,55 @@ async function generateRichMenuImage(
170186
);
171187
await fsp.writeFile(tempHtmlPath, htmlContent, "utf8");
172188

173-
// 4. Use puppeteer to convert HTML to PNG
174-
const browser = await puppeteer.launch();
189+
// 4. Use puppeteer to convert HTML to PNG with Docker-compatible settings
190+
console.log(
191+
`Launching Puppeteer with executable: ${process.env.PUPPETEER_EXECUTABLE_PATH || "default"}`,
192+
);
193+
const browser = await puppeteer.launch({
194+
headless: true,
195+
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
196+
args: [
197+
"--no-sandbox",
198+
"--disable-setuid-sandbox",
199+
"--disable-dev-shm-usage",
200+
"--disable-gpu",
201+
"--disable-web-security",
202+
"--disable-features=VizDisplayCompositor",
203+
"--no-first-run",
204+
"--no-default-browser-check",
205+
"--disable-default-apps",
206+
"--disable-extensions",
207+
],
208+
});
209+
console.log("Puppeteer browser launched successfully");
175210
const page = await browser.newPage();
176211
await page.setViewport({ width: RICHMENU_WIDTH, height: RICHMENU_HEIGHT });
177212
await page.goto(`file://${tempHtmlPath}`, {
178213
waitUntil: "networkidle0",
179214
});
215+
216+
// Wait for fonts to load
217+
await page.evaluate(() => document.fonts.ready);
218+
await new Promise(resolve => setTimeout(resolve, 2000));
219+
220+
console.log(`Taking screenshot to: ${richMenuImagePath}`);
180221
await page.screenshot({
181222
path: richMenuImagePath as `${string}.png`,
182223
clip: { x: 0, y: 0, width: RICHMENU_WIDTH, height: RICHMENU_HEIGHT },
183224
});
225+
console.log("Screenshot taken successfully");
184226
await browser.close();
185227

228+
// Save image to output directory
229+
const outputPath = path.join("/tmp", path.basename(richMenuImagePath));
230+
231+
try {
232+
await fsp.copyFile(richMenuImagePath, outputPath);
233+
console.log(`Rich menu image saved to: ${outputPath}`);
234+
} catch (error) {
235+
console.warn(`Failed to save image to output directory: ${error}`);
236+
}
237+
186238
// 5. Delete the temporary HTML file
187239
await fsp.unlink(tempHtmlPath);
188240

0 commit comments

Comments
 (0)