전자서명 기능이 포함된 계약서 화면을 만들 때, 서명 영역 외에 본문 텍스트를 캔버스에 정확히 배치하는 작업이 필요하다. 이 글에서는 위챗 미니프로그램의 Canvas API를 활용해 여러 줄의 텍스트를 그리고, 자동 줄바꿈과 정렬을 처리하는 방법을 소개한다.
화면 구성
캔버스 요소와 테스트 버튼을 간단히 배치한다.
<canvas id='textCanvas' canvas-id='textCanvas' style='width:{{canvasW}}px;height:{{canvasH}}px;'></canvas>
<button bindtap="renderText">텍스트 그리기</button>
데이터 준비
문서 내용을 객체 배열로 정의한다. 각 항목은 텍스트, 굵기, 정렬 정보를 갖는다.
Page({
data: {
canvasW: 414,
canvasH: 750,
signImg: '/images/sign_placeholder.png',
imgW: 100,
imgH: 30
},
renderText() {
const paragraphs = [
{ content: "위임장", weight: "bold", align: "center" },
{ content: "위임인: [***]", weight: "normal", align: "left" },
{ content: "주민등록번호: [4***********X]", weight: "normal", align: "left" },
{ content: "주소: [서울시 *****]", weight: "normal", align: "left" },
{ content: "수임인: ***********************", weight: "normal", align: "left" },
{ content: "본인 [***]은(는) [****기관]과 관련된 업무를 개인 사정으로 직접 처리할 수 없어, 아래 수임인을 대리인으로 선임하여 해당 업무를 위임합니다. 위임 기간은 본 위임장 서명일로부터 위임 사무 완료 시까지이며, 위임 사항 및 권한 범위는 다음과 같습니다.", weight: "normal", align: "left" },
{ content: "1. ㅇㅇ 업무 관련 서류 접수 및 처리", weight: "normal", align: "left" },
{ content: "2. ㅇㅇ 기관 방문 및 설명", weight: "normal", align: "left" },
{ content: "3. 필요 서류 발급 신청", weight: "normal", align: "left" },
{ content: "4. 위임 사무 관련 각종 문서 서명", weight: "normal", align: "left" },
{ content: "5. 수임인의 복대리인 선임 동의", weight: "normal", align: "left" },
{ content: "위 사항을 확인하고 동의합니다.", weight: "normal", align: "left" },
{ content: "위임인: ", weight: "normal", align: "right" },
{ content: "2025년 11월 10일", weight: "normal", align: "right" }
];
this.drawParagraphs(paragraphs);
}
});
핵심 로직: 캔버스에 텍스트 그리기
기기 너비에 맞춰 rpx 단위를 계산하고, 줄 높이와 좌우 여백을 설정한다. 텍스트는 breakLines 함수로 자동 줄바꿈 처리된 후 그려진다.
drawParagraphs(list) {
const sysInfo = wx.getSystemInfoSync();
const canvasWidth = sysInfo.windowWidth;
const rpxRatio = (canvasWidth / 375).toFixed(2);
const canvasHeight = sysInfo.windowHeight - rpxRatio * 59;
const ctx = wx.createCanvasContext('textCanvas');
const lineHeight = rpxRatio * 30;
const padLeft = rpxRatio * 12;
const padRight = rpxRatio * 12;
let currentY = rpxRatio * 20;
list.forEach(item => {
const fontWeight = item.weight === 'bold' ? 'bold' : 'normal';
const font = `${fontWeight} ${Math.round(rpxRatio * 14)}px PingFangSC-Regular`;
const maxWidth = canvasWidth - padLeft - padRight;
const lines = this.breakLines(ctx, item.content, maxWidth, font);
let xPos;
if (item.align === 'left') {
xPos = padLeft;
} else if (item.align === 'center') {
const textWidth = ctx.measureText(item.content).width;
xPos = (canvasWidth - padLeft - padRight - textWidth) * 0.5 + padLeft;
} else if (item.align === 'right') {
const textWidth = ctx.measureText(item.content).width;
xPos = canvasWidth - padRight - textWidth;
}
ctx.font = font;
ctx.fillStyle = '#000';
lines.forEach(line => {
currentY += lineHeight;
ctx.fillText(line, xPos, currentY);
});
});
// 서명 이미지 배치 (추후 실제 서명으로 교체)
const signSrc = this.data.signImg;
if (signSrc) {
ctx.drawImage(
signSrc,
canvasWidth - padRight - this.data.imgW,
currentY - lineHeight - this.data.imgH * 0.7,
this.data.imgW,
this.data.imgH
);
}
ctx.draw();
}
줄바꿈 계산
measureText를 사용해 주어진 너비에서 잘릴 지점을 이진 탐색으로 찾고, 문자열을 여러 줄로 분할한다.
findBreakIndex(text, maxWidth, context) {
let low = 0;
let high = text.length - 1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
const currentWidth = context.measureText(text.substring(0, mid)).width;
const nextWidth = context.measureText(text.substring(0, mid + 1)).width;
if (currentWidth <= maxWidth && nextWidth > maxWidth) {
return mid;
}
if (currentWidth < maxWidth) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
},
breakLines(context, text, maxWidth, font) {
const result = [];
if (font) context.font = font;
const segments = text.split('\r\n');
segments.forEach(segment => {
let remaining = segment;
let breakPoint;
while ((breakPoint = this.findBreakIndex(remaining, maxWidth, context)) !== -1) {
result.push(remaining.substring(0, breakPoint));
remaining = remaining.substring(breakPoint);
}
if (remaining) result.push(remaining);
});
return result;
}
위 코드는 breakLines에서 개행 문자(\r\n)도 처리하므로, 미리 줄바꿈이 포함된 텍스트도 정확히 분할된다. 모든 텍스트가 그려진 후 하단에 서명 이미지가 배치될 위치를 계산해 미리 채워 넣었다. 실제 서명 기능과는 별도로, 이 구조를 통해 계약서 본문을 캔버스 위에 완성할 수 있다.