grep

Engineering

서비스 개발하다 오픈소스 기여한 썰 (1)

NHN

2022년 4월 20일

원문에서 보기 ↗

안녕하세요. 아이엠티처팀 신승엽입니다.

오늘은 서비스 개발을 하다가 오픈소스에 기여한 썰을 풀어보려고 합니다.

바로학습AI

먼저 개발한 서비스에 대해 소개하겠습니다. 이번에는 "바로학습AI"라는 서비스를 개발했습니다. 바로학습AI는 기초학습능력 진단 후 부족한 부분에 대한 처방 학습으로 기초학습 능력을 키우는 것을 콘셉트로 개발한 서비스입니다.

1차로 우선 학교 선생님들을 대상으로 오픈하였고 국어, 수학, 영어 과목의 학습지를 자동으로 생성해 줍니다.

어떤 문제를 해결해야 했는가?

각 학습지는 인쇄할 수도 있고, PDF로 다운로드가 가능합니다. 또한 미리 보기 페이지를 가지고 있습니다.

그림1.png 그림 1. 학습지 미리 보기 페이지

문제 1) 웹에서 인쇄 미리 보기를 보여주어야 하며 A4 용지에 정확히 인쇄해 내어야 한다.

웹 브라우저는 인쇄 기능을 기본적으로 가지고 있고, 웹 페이지를 인쇄해 주는 것 자체는 문제가 되지 않습니다. 문제는 바로 디자이너가 의도한 대로 정확한 여백을 이용하여 정확히 쪽 바꿈이 되며 인쇄되게 하기는 어렵다는 점입니다. 보통 아래와 같이 인쇄를 위한 페이지 설정을 공지하는 경우가 많습니다.

그림2.png 그림 2. T World의 B2B 사이트 공지 내용

우리도 이렇게 할 수도 있겠지만, 사용자가 이런 내용에 대해서는 신경 쓰지 않고 인쇄할 수 있는 방법은 없을까 고민이 되었습니다.

문제 2) 학습지를 PDF로 다운로드할 수 있어야 한다.

위에서 본 미리 보기 페이지에 있는 학습지가 그 모습 그대로 PDF로 다운로드를 할 수 있어야 한다는 요구사항을 받았습니다. 보통은 웹 페이지와 PDF를 별도로 제공하는 식으로 문제를 해결할 수 있습니다.

이전에는 PDF 파일의 생성이 필요할 경우 pdfmake라는 라이브러리(http://pdfmake.org/)를 이용하여 PDF를 만들어주고 있었습니다. 라이브러리 홈페이지를 참고하시면 아시겠지만, 콘텐츠를 배열로 정의하여 손쉽게(?) PDF 파일을 생성할 수 있습니다. 손쉽게(?)에 물음표가 들어간 이유는 디자이너가 디자인해 준 예쁜 모습으로 PDF를 만들어내기 위해서는 꽤 많은 노력이 들어가기 때문입니다. 이태리서.. 아니, 판교에서 PDF 장인이 한 땀 한 땀 PDF를 찍어내는 코드를 짜고 있었죠.

그림3.png 그림 3. 판교의 어느 PDF 장인(필자 아님)이 한 땀 한 땀 만든 PDF

미리 보기 페이지 때문에 이미 학습지는 웹 페이지로 만들어지며, 익숙한 HTML랑 CSS로 마크업 할 수 있습니다. 이렇게 만든 웹 페이지로부터 곧바로 PDF를 만들어낼 수 있다면 참 좋겠다는 생각이 들었습니다.

어떻게 문제를 해결했는가?

문제 1) 웹에서 인쇄 미리 보기를 보여주어야 하며 A4 용지에 정확히 인쇄해 내어야 한다.

종이 매체를 위한 CSS 스펙

종이라는 매체는 스크린과는 다른 특성을 가지고 있습니다. 스크린은 하나의 연속된 영역을 가지고 있지만 종이는 여러 개의 분리된 영역을 가지고 있기 때문에 쪽 바꿈이 일어납니다. 이런 종이 매체를 위한 CSS 표준이 아래와 같이 4가지 있습니다.

@page 룰을 통해서 용지의 여백, 크기 등을 지정할 수 있습니다. 그리고 @bottom-center와 같은 룰을 통해 여백의 콘텐츠(쪽수 등)도 정의할 수 있습니다.

@page {
    size: A4;
    margin: 53px 52px 119px 52px;

    @bottom-left {
        content: element(pageNum);
    }
}

paged.js

위에 살펴본 종이 매체를 위한 CSS 스펙은 현재 working draft 상태로 각 브라우저에 지원이 되거나 일부만 지원이 되며, 결과물도 약간씩 다른 상태라고 합니다. paged.js(https://www.pagedjs.org/)는 HTML로 PDF를 만들어낼 수 있는 라이브러리입니다. 문제 2까지 해결을 할 수 있을 것 같군요.

종이 매체를 위한 CSS 스펙을 브라우저 상에 구현해 주어 서로 다른 브라우저에서도 동일한 결과로 보이고, 브라우저가 지원하지 않는 스펙까지 지원합니다. 마치 jQuery 같네요. paged.js의 미리 보기 모듈을 사용하여 학습지 미리 보기 와 인쇄를 모두 해결하였습니다. 🎉

그림4.png 그림 4. 용지에 딱 맞아떨어지는 학습지

문제 2) 학습지를 PDF로 다운로드할 수 있어야 한다.

paged.js 소개에 paged.js는 HTML에서 PDF를 만들 수 있는 라이브러리라고 소개하고 있습니다. 문제 2도 손쉽게 해결 될지도 모르겠습니다.

그림5.png 그림 5. paged.js에서 PDF 만드는 방법에 대한 설명

아니! 브라우저에서 직접 인쇄 대화 상자를 열어서 PDF로 저장하라고 하는군요! 다른 방법을 찾아보아야 합니다. 안 그러면 다시 "한 땀 한 땀 PDF 깎아내는 판교 장인"이 되어야 합니다.

실마리

구글링을 해보면 HTML을 PDF로 만들어주는 방법에 대한 글을 많이 찾을 수 있습니다. 해당 글들이 공통적으로 제시하고 있는 방법은 html2canvas 라이브러리를 이용하여 HTML을 이미지로 변환하고, 이어서 jsPDF 라이브러리를 이용하여 해당 이미지를 PDF (0,0) 좌표에 삽입하는 것입니다.

그림6.png 그림 6. 이미지로 변환하여 PDF 생성하는 과정

하지만 이 방법은 몇 가지 문제가 있습니다.

1. 첫 페이지만 생성된다.

전체 페이지를 이미지로 변환하여 이미지로 추가해 본 결과, 쪽 바꿈 처리를 해주지 않기 때문에, 직접 페이지 별로 이미지를 하나씩 만들어 PDF에 페이지를 추가하며 삽입해야 합니다.

2. 비트맵 이미지로 추가되기 때문에 품질이 나빠진다.

이미지로 추가되기 때문에 글씨들 또한 모두 비트맵 이미지이며, 확대 시 글씨가 깨져 보이고, 해당 PDF를 인쇄하여도 자연스럽지 못한 결과를 보여주었습니다. 이 부분은 저 스스로 서비스의 퀄리티 부분으로 타협할 수 없었습니다.

그림8.png 그림 7. 이미지로 추가한 PDF를 확대한 모습

그러던 중 Dooray! Project에서 PDF 내보내기 기능을 보게 되었습니다.

그림9.png 그림 8. 두레이 프로젝트의 업무 PDF로 저장하기 기능

PDF로 저장한 업무 내용은 아무리 확대해도 글씨가 깨지지 않고, 페이지 쪽 바꿈이 자연스럽게 일어나는 제가 원하던 바로 그 상태 그대로였습니다. (지금 고민하고 있는 문제가 있다면 어쩌면 주변 동료가 이미 해결한 문제일 수도 있습니다.)

그림10.png 그림 9. 두레이 프로젝트에서 받은 PDF

NHN Dooray에 있는 옛 동료에게 연락을 해봅니다. 근데 의외의 대답을 듣게 되었는데요. 지금까지 제가 구글링해서 찾은 접근법에서 사용한 라이브러리를 그대로 사용했다는 것입니다. 나중에 알고 보니 jsPDF는 이미 html2canvas를 활용하여 HTML을 곧바로 PDF로 만들어주는 모듈을 제공하고 있었습니다! (아... 구글 주도 개발론으로 접근했던 저를 반성해 봅니다. 라이브러리 홈페이지의 설명을 조금만 봐도 바로 찾을 수 있었어요)

그림11.png 그림 10. 호우! 고마워요!

문제 2를 본격적으로 해결해 보자

jsPDF(https://github.com/MrRio/jsPDF)는 클라이언트 사이드에서 PDF 파일을 생성할 수 있는 라이브러리입니다. html2canvas는 웹 페이지를 이미지로 변환할 수 있는 라이브러리입니다.

jsPDF는 PDF를 생성하는 핵심 기능 외에 여러 가지 방식으로 사용할 수 있는 모듈을 제공해 주는데요. 그중 하나가 바로 html 모듈입니다. html 모듈에서는 html2canvas에 jsPDF의 canvas 모듈을 넘겨주어 html2canvas는 캔버스에 결과를 그리게 되지만, PDF에 결과를 그리게 되는 것입니다.

jsPDFAPI.html = function(src, options) {
    ...
    // 별도로 지정하지 않으면 jsPDF의 캔버스 모듈을 html2canvas의 옵션으로 사용한다.
    options.html2canvas.canvas = options.html2canvas.canvas || this.canvas; 
    ...
}

코드 1. jsPDF/src/moudles/html.js

Canvas.prototype.getContext = function(contextType, contextAttributes) {
    ...
    // getContext('2d') 호출 시 jsPDF의 context2d 모듈을 넘긴다.
    return this.pdf.context2d; 
}

코드 2. jsPDF/src/modules/canvas.js

Object.defineProperty(this, "fillStyle", {
  get: function() {
    return this.ctx.fillStyle;
  },
  set: function(value) {
    ...

    // context2d 인터페이스를
    // PDF 만드는데 쓸 수 있게 구현해 놓았다.
    this.pdf.setFillColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); 
    this.pdf.setTextColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); 
  }
});

코드 3. jsPDF/src/modules/context2d.js

그림12.png 그림 11. HTML을 PDF로 만드는 개념도

신나는 마음으로 적용을 해봅니다. 그리고 결과를 확인해 봅니다.

그림13.png 그림 12. 응, 안 돼. 한 번에 되게 해줄 생각 없어. 돌아가.

PDF에 점선이 표현되지 않아요 ㅠㅠ

다음에 계속...