Skip to content

Commit

Permalink
add: create image with title
Browse files Browse the repository at this point in the history
  • Loading branch information
pbc1017 committed Mar 13, 2024
1 parent 2c1c80c commit 956c23c
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 53 deletions.
57 changes: 57 additions & 0 deletions src/common/interfaces/ITimetable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export namespace ITimetable {
export interface IClasstime {
id: number;
day: number;
begin: Date;
end: Date;
type: string;
building_id: string | null;
building_full_name: string | null;
building_full_name_en: string | null;
room_name: string | null;
unit_time: number | null;
lecture_id: number | null;
// Additional properties as needed
}

export interface ILecture {
id: number;
code: string;
old_code: string;
year: number;
semester: number;
department_id: number;
class_no: string;
title: string;
title_en: string;
type: string;
type_en: string;
audience: number;
credit: number;
title_en_no_space: string;
title_no_space: string;
num_classes: number;
num_labs: number;
credit_au: number;
limit: number;
num_people: number | null; // Allow num_people to be null
is_english: boolean;
deleted: boolean;
course_id: number;
grade_sum: number;
load_sum: number;
speech_sum: number;
grade: number;
load: number;
speech: number;
review_total_weight: number;
class_title: string | null;
class_title_en: string | null;
common_title: string | null;
common_title_en: string | null;
subject_classtime: IClasstime[];
// Additional properties as needed
}

// Other interfaces as needed...
}
1 change: 1 addition & 0 deletions src/common/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './IAuth';
export * from './ICourse';
export * from './IFeed';
export * from './ITimetable';
8 changes: 5 additions & 3 deletions src/modules/share/share.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ShareService } from './share.service';
import { TimetableRepository } from 'src/prisma/repositories/timetable.repository';
import { Response } from 'express';

@Controller('share')
@Controller('/api/share')
export class ShareController {
constructor(
private readonly shareService: ShareService,
Expand All @@ -29,14 +29,16 @@ export class ShareController {
@Res() res: Response,
) {
try {
const imageStream = await this.shareService.createTimetableImage(
const imageBuffer = await this.shareService.createTimetableImage(
timetableId,
year,
semester,
language,
user,
);
res.setHeader('Content-Type', 'image/png');
imageStream.pipe(res);
// imageStream.pipe(res);
res.send(imageBuffer);
} catch (error) {
throw new HttpException(
'An error occurred while generating the timetable image',
Expand Down
3 changes: 2 additions & 1 deletion src/modules/share/share.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { ShareController } from './share.controller';
import { ShareService } from './share.service';
import { TimetablesModule } from '../timetables/timetables.module';
import { PrismaModule } from 'src/prisma/prisma.module';
import { SemestersModule } from '../semesters/semesters.module';

@Module({
imports: [PrismaModule, TimetablesModule],
imports: [PrismaModule, TimetablesModule, SemestersModule],
controllers: [ShareController],
providers: [ShareService],
})
Expand Down
181 changes: 132 additions & 49 deletions src/modules/share/share.service.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,46 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { timetable_timetable_lectures } from '@prisma/client';
import { session_userprofile, subject_semester } from '@prisma/client';
import { CanvasRenderingContext2D } from 'canvas';
import { createCanvas, loadImage, registerFont } from 'canvas';
import { join } from 'path';
import { PrismaService } from 'src/prisma/prisma.service';
import { ITimetable } from 'src/common/interfaces';
import { TimetableRepository } from 'src/prisma/repositories/timetable.repository';
import { SemesterRepository } from 'src/prisma/repositories/semester.repository';
import { SemestersService } from '../semesters/semesters.service';

@Injectable()
export class ShareService {
private readonly TIMETABLE_CELL_COLORS = ['#color1', '#color2'];
private readonly file_path =
process.env.NODE_ENV === 'development'
? 'static/'
: '/var/www/otlplus/static/';
process.env.NODE_ENV === 'local' ? 'static/' : '/var/www/otlplus/static/';

constructor(
private readonly prismaService: PrismaService,
private readonly timetableRepository: TimetableRepository,
private readonly semesterRepository: SemesterRepository,
) {
registerFont(join(this.file_path, 'fonts/NotoSansKR-Regular.otf'), {
family: 'NotoSansKR',
});
registerFont(join(this.file_path, 'fonts/NotoSansKR-Regular.otf'), {
family: 'NotoSansKR',
});
}

private getTimetableType(lectures: any[]): 'FIVE_DAYS' | 'SEVEN_DAYS' {
return lectures.some((lecture: { classtimes: { day: number }[] }) =>
lecture.classtimes.some(
(classtime: { day: number }) => classtime.day >= 5,
),
)
? 'SEVEN_DAYS'
: 'FIVE_DAYS';
private getSemesterName(
semester: subject_semester,
language: string = 'kr',
): string {
const seasons = language.includes('en')
? ['spring', 'summer', 'fall', 'winter']
: ['봄', '여름', '가을', '겨울'];

const seasonName = seasons[semester.semester - 1];
return `${semester.year} ${seasonName}`;
}

private drawRoundedRectangle(
ctx: {
fillStyle: string;
beginPath: () => void;
moveTo: (arg0: number, arg1: number) => void;
lineTo: (arg0: number, arg1: number) => void;
quadraticCurveTo: (
arg0: number,
arg1: number,
arg2: number,
arg3: number,
) => void;
closePath: () => void;
fill: () => void;
},
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
Expand Down Expand Up @@ -98,56 +92,145 @@ export class ShareService {
}

private drawTextbox(
ctx: {
fillStyle: string;
font: string;
fillText: (arg0: string, arg1: number, arg2: number) => void;
},
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
text: string,
font: string,
fontSize: number,
color: string,
align?: 'right' | 'left',
) {
const lines = this.sliceTextToFitWidth(text, width, font, fontSize);
ctx.fillStyle = color;
ctx.font = `${fontSize}px '${font}'`;
lines.forEach((line, i) => {
ctx.fillText(line, x, y + fontSize * (i + 1));
});
ctx.textAlign = align ? align : 'left';
if (align == 'right') ctx.fillText(text, x, y);
else {
lines.forEach((line, i) => {
ctx.fillText(line, x, y + fontSize * (i + 1));
});
}
}

async createTimetableImage(
private getTimetableType(lectures: ITimetable.ILecture[]): '5days' | '7days' {
return lectures.some((lecture) =>
lecture.subject_classtime.some((classtime) => classtime.day >= 5),
)
? '7days'
: '5days';
}

// Make sure to adjust other methods that use lectures to match the type
private async getTimetableEntries(
timetableId: number,
year: number,
semester: number,
language: string,
) {
): Promise<ITimetable.ILecture[]> {
const timetableDetails =
await this.timetableRepository.getLecturesWithClassTimes(timetableId);
if (!timetableDetails) {
throw new HttpException('No such timetable', HttpStatus.NOT_FOUND);
}
return timetableDetails.map((detail) => detail.subject_lecture);
}

async createTimetableImage(
timetableId: number,
year: number,
semester: number,
language: string,
user: any,
): Promise<Buffer> {
const TIMETABLE_CELL_COLORS = [
'#F2CECE',
'#F4B3AE',
'#F2BCA0',
'#F0D3AB',
'#F1E1A9',
'#f4f2b3',
'#dbf4be',
'#beedd7',
'#b7e2de',
'#c9eaf4',
'#B4D3ED',
'#B9C5ED',
'#CCC6ED',
'#D8C1F0',
'#EBCAEF',
'#f4badb',
];
const semesterFontSize = 30;
const tileFontSize = 24;

const lectures = timetableDetails;
const lectures = await this.getTimetableEntries(timetableId);
const timetableType = this.getTimetableType(lectures);
const imageTemplatePath = join(
this.file_path,
`img/Image_template_${timetableType}.png`,
);
const image = await loadImage(imageTemplatePath);
const canvas = createCanvas(image.width, image.height);

const baseImage = await loadImage(imageTemplatePath);
const canvas = createCanvas(baseImage.width, baseImage.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(baseImage, 0, 0);

// Draw the base image
ctx.drawImage(image, 0, 0, image.width, image.height);
const semesterObject = await this.semesterRepository.findSemester(
year,
semester,
);
if (!semesterObject) {
throw new HttpException('Semester not found', HttpStatus.NOT_FOUND);
}
const isEnglish = language && language.includes('en');
const semesterName = this.getSemesterName(
semesterObject,
isEnglish ? 'en' : 'kr',
);
this.drawTextbox(
ctx,
timetableType === '5days' ? 952 : 952 + 350,
78,
200,
semesterName,
'NotoSansKR',
semesterFontSize,
'#CCCCCC',
'right',
);

// Continue with drawing logic, similar to the Python example provided
// ...
lectures.forEach((lecture) => {
const color = TIMETABLE_CELL_COLORS[lecture.course_id % 16];
lecture.subject_classtime.forEach((classtime) => {
const { day, begin, end } = classtime;
const beginNumber = begin.getUTCHours() * 60 + begin.getUTCMinutes();
const endNumber = end.getUTCHours() * 60 + end.getUTCMinutes();
console.log(day, begin, end, beginNumber, endNumber);

const [x, y, width, height] = [
178 * day + 76,
(beginNumber * 4) / 3 - 486,
178 - 7,
((endNumber - beginNumber) * 4) / 3 - 7,
];

// const [x, y, width, height] = [100, 100, 200, 60]; // Placeholder values

this.drawRoundedRectangle(ctx, x, y, width, height, 10, color);
this.drawTextbox(
ctx,
x + 10,
y + 10,
width - 20,
lecture.title,
'NotoSansKR',
tileFontSize,
'#000',
);
});
});

return canvas.createPNGStream();
// Return the image as a buffer
return canvas.toBuffer();
// return canvas.createPNGStream();
}
}
14 changes: 14 additions & 0 deletions src/prisma/repositories/semester.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,18 @@ export class SemesterRepository {
},
});
}

async findSemester(
year: number,
semester: number,
): Promise<subject_semester | null> {
return await this.prisma.subject_semester.findUnique({
where: {
year_semester: {
year: year,
semester: semester,
},
},
});
}
}
Binary file added static/fonts/NanumBarunGothic.ttf
Binary file not shown.
Binary file added static/fonts/NotoSansKR-Regular.otf
Binary file not shown.
Binary file added static/img/Image_template_5days.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/img/Image_template_7days.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 956c23c

Please sign in to comment.