import path from "path"; import { join } from "path"; import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas"; import { getMealInfo, getNutritionInfo, NameToEmoji, removeNutritionInfo } from "./meal"; // 이 함수의 내용은 제공되지 않았으므로 그대로 둡니다. import { getAllSchedules } from "./schedule"; import { isVTS } from "./vts"; GlobalFonts.registerFromPath('./template/Pretendard-Bold.ttf', 'Pretendard Bold') GlobalFonts.registerFromPath('./template/NotoColorEmoji-Regular.ttf', 'NotoColorEmoji Regular') export class CreateImage { static async PostMeal(MLSV_YMD: string): Promise<{ [key: string]: any } | undefined> { const mealInfo = await getMealInfo(MLSV_YMD); const img = await loadImage(path.join("./template/skeleton.png")); const canvas = createCanvas(img.width, img.height); const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); ctx.font = "56px Pretendard Bold"; ctx.fillStyle = "white"; ctx.textAlign = "left"; const lines = removeNutritionInfo(mealInfo.meal).split("\n").reverse(); let emojis = (await NameToEmoji(lines.toString())).split(","); if (lines.length !== emojis.length) { console.error("Error: Emojis and lines count mismatch retrying..."); return this.PostMeal(MLSV_YMD); } for (let i = 0; i < lines.length; i++) { const emoji = emojis[i]; const text = lines[i]; ctx.font = "50px NotoColorEmoji Regular"; ctx.fillText(emoji ?? "", 75, 930 - i * 60); ctx.font = "56px Pretendard Bold"; ctx.fillText(text ?? "", 75 + 80, 930 - i * 60); } ctx.font = "24px Pretendard Bold"; ctx.textAlign = "right"; ctx.fillText( `${MLSV_YMD.slice(0, 4)}년 ${MLSV_YMD.slice(4, 6)}월 ${MLSV_YMD.slice( 6, 8 )}일`, 945, 110 ); ctx.fillStyle = "#89CAFF"; ctx.fillText(mealInfo.kcal, 945, 220); if (await isVTS(MLSV_YMD)) { ctx.fillStyle = "#CDAD94"; ctx.fillText("with V.T.S.", 952.5, 245); } // VTS 관련 코드 주석 처리 const outPath = path.join("./temp", `${MLSV_YMD}.png`); // 경로 수정: "./temp/" -> "./temp" const buffer = canvas.toBuffer("image/png"); try { await Bun.write(outPath, buffer); console.log("🍲 | Meal Info Image Saved"); } catch (error) { console.error("Error saving meal info image:", error); } finally { const meals = removeNutritionInfo(mealInfo.meal).split("\n"); const nutritionInfo = getNutritionInfo(mealInfo.meal); const retrunValue: { [key: string]: any } = {}; for (let i = 0; i < meals.length; i++) { const meal = (meals[i] ?? "").trim(); retrunValue[meal] = nutritionInfo[i] || []; } return retrunValue; } } static async PostSchedule() { const today = new Date(); const year = today.getFullYear(); const month = (today.getMonth() + 1).toString().padStart(2, "0"); // 일정 데이터 불러오기 const allSchedules = await getAllSchedules(); const events = allSchedules[month] || []; // 이미지 로드 const image = await loadImage(join("./template/skeleton_schoolevent.png")); const canvas = createCanvas(image.width, image.height); const ctx = canvas.getContext("2d"); // 배경 그리기 ctx.drawImage(image, 0, 0); // 폰트 설정 const eventFont = "48px Pretendard Bold"; const detailFont = "24px Pretendard Bold"; ctx.font = eventFont; ctx.fillStyle = "white"; ctx.textAlign = "left"; // 행사 내용 그리기 let yPosition = 930; for (const event of events.slice().reverse()) { ctx.fillText(`${event.date} : ${event.desc}`, 75, yPosition); yPosition -= 60; } // 상단 월 정보 ctx.font = detailFont; ctx.textAlign = "right"; ctx.fillText(`${year}년 ${parseInt(month)}월`, 945, 110); // 파일 저장 const outputFilePath = join("./", `temp/schedule-${year}-${month}.png`); const buffer = canvas.toBuffer("image/png"); require("fs").writeFileSync(outputFilePath, buffer); console.log("📅 | School Event Image Saved"); return outputFilePath; } static async ConvertToStory(filePath: string) { const inPath = path.join("./",`${filePath}`); let img; try { img = await loadImage(inPath); } catch (error) { console.error(`Error loading image for story: ${inPath}`, error); // Post 메서드가 먼저 호출되어 이미지가 생성되었는지 확인 필요 // 또는 에러 처리를 통해 사용자에게 알림 return; } const width = img.width; const height = img.height; const aspectRatio = 9 / 16; const currentRatio = width / height; let newWidth = width; let newHeight = height; if (currentRatio < aspectRatio) { newWidth = Math.floor(height * aspectRatio); } else { newHeight = Math.floor(width / aspectRatio); } const canvas = createCanvas(newWidth, newHeight); const ctx = canvas.getContext("2d"); ctx.fillStyle = "#0A0A0A"; // 배경색 ctx.fillRect(0, 0, newWidth, newHeight); // 원본 이미지를 새 캔버스 중앙에 맞추어 약간 작게(85%) 그립니다. const resizedWidth = Math.floor(width * 0.85); const resizedHeight = Math.floor(height * 0.85); const offsetX = Math.floor((newWidth - resizedWidth) / 2); const offsetY = Math.floor((newHeight - resizedHeight) / 2); ctx.drawImage(img, offsetX, offsetY, resizedWidth, resizedHeight); const extIndex = filePath.lastIndexOf("."); const baseName = extIndex !== -1 ? filePath.substring(0, extIndex) : filePath; const outPath = path.join("./", `${baseName}-story.png`); const buffer = canvas.toBuffer("image/png"); try { await Bun.write(outPath, buffer); console.log("🔄️ | Story Convert Success"); } catch (error) { console.error("Error saving story info image:", error); } } }