grep

Engineering

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

NHN

2022년 4월 28일

원문에서 보기 ↗

지난 이야기

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

문제 5) PDF에 둥근 테두리 점선이 표현되지 않는다.

그림18.png 그림 18. 아니, 저기요? 저한테 왜 이러세요?

아.. 산 넘어 산이에요. 점선 테두리가 가능하게 했더니 둥근 + 점선인 테두리는 안 되네요. 제가 찾아서 적용했던 PR 내용은 둥글지 않은 테두리만 지원을 하는 것이었습니다. 이미 개발 일정의 과반이 지나가고 있습니다. 이제 다시 PDF 장인으로 돌아가기에도 늦은 것 같습니다. 전면 돌파를 선택합니다.

html2canvas의 박스 모델 렌더링 방법

html2canvas의 소스코드를 읽어봅니다. html2canvas는 이미지로 변경하고 싶은 엘리먼트를 순회하면서 엘리먼트의 위치, 스타일 등을 수집해서 캔버스에 그립니다. 그중에 박스 모델을 구현하고 있는 부분에 대해서 알아보았습니다. 네 방향의 각 테두리 별로 포인트 4군데의 위치를 계산하여 테두리 영역을 위한 path를 생성하고 색을 칠하는 것으로 테두리를 구현하고 있는 것을 확인할 수 있었습니다.

그림19.png 그림 19. html2canvas에서 테두리를 그리는 법

제가 찾아 적용했던 PR은 이렇게 구한 path의 중간 지점을 구해 dash line을 그어주는 식으로 구현이 되어 있었습니다.

그림20.png 그림 20. 찾아낸 PR에서 dashed line을 구현한 방법

둥근 테두리인 경우는 좀 더 복잡해지는데요. 둥근 테두리 부분은 베지에 곡선을 이용하여 path를 구성하게 되어 있습니다. 양 끝이 모두 둥근 경우 포인트는 총 12개를 사용하게 됩니다.

그림 21.png 그림 21. html2canvas에서 둥근 테두리 path는 베지에 곡선으로 이루어져 있다

즉, 제가 찾아 적용했던 PR의 코드는 둥근 테두리에 대한 대응이 되어 있지 않았고, 베지에 곡선을 위한 포인트를 잘못 사용하여 아래와 같이 잘못된 결과를 보여주게 된 것입니다.

그림22.png 그림 22. 약간 삐뚤고, 두께도 맞지 않게 그려진다. 무엇보다 둥글게 표현되지 않는다.

브라우저는 테두리를 어떻게 그리고 있을까?

이쯤 되니 직접 제대로 구현을 하는 것이 낫겠다는 생각이 들었습니다. 그리고, 실제 브라우저에서는 어떻게 테두리를 그려주고 있는지 궁금해졌습니다. 웹킷과 크로미움의 소스코드를 찾아 무작정 검색해 봤습니다. 관심 있는 부분은 dashed와 dotted 스타일을 그리는 부분입니다.

그림23.png 그림 23. 브라우저에서 스타일, 두께, 둥근 테두리 속성에 따른 다양한 테두리 표현들(jsFiddle)

웹킷의 소스를 보면 dashed 스타일은 테두리 바깥 path를 따라 본래 두께의 2 * 1.1 배 만큼의 점선을 그려준 후에 테두리 path로 크리핑 마스크 처리하는 것을 확인할 수 있습니다. 2배를 곱하는 이유는 테두리의 바깥 모서리를 따라 선을 그어주기 때문입니다. 2배로 해야 본래 테두리 영역까지 커버되겠죠. 바깥으로 나간 절반은 마스킹 됩니다. 1.1의 추가 배수를 곱해주는 이유는 2배보다 살짝 넘어가게 해서 경계 부분에 안티-알리어싱을 적용하기 위함입니다. (소스코드 참고)

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

dashed

이제 브라우저에서 테두리를 어떻게 그리고 있는지 아이디어를 얻었으니 구현을 해봅니다. 다행히 html2canvas는 테두리 path를 구하는 것까지는 구현이 되어 있는 상태입니다. 여기에 위 아이디어와 같이 dashed line을 그리고 클리핑 마스킹을 해주면 됩니다. (그 외 자잘한 처리도 해주었습니다)

그림24.png 그림 24. dashed 스타일을 구현한 방법

dotted

Dotted 스타일의 경우도 다시 새롭게 코드를 작성해 주었는데요. 위의 dashed 방식을 활용하여 dotted도 구현이 가능하였습니다. 다만, dotted 같은 경우 동그라미로 그려주어야 하기 때문에 추가로 테두리 영역의 가운데를 지나가는 path를 구해주어야 했습니다.

그리고 dashed line을 그어주는 것은 동일하지만, line cap을 round로, dash 길이는 0, gap은 본래 gap에 테두리 두께(동그라미의 지름) 만큼 추가해 주어 구현할 수 있었습니다. 참고로 line cap은 선 끝의 모양을 정의하는 속성입니다. round로 설정하면 선 끝이 둥글게 됩니다. dash 길이는 0이므로 line cap 속성 없이 그린다면 점선이 나타나지 않을 것입니다. 하지만 선 끝을 둥글게 처리해 주었기 때문에 아무것도 없는 선에 양 끝이 둥글게 처리되어 원이 되는 것이죠.

최종적으로 클리핑 마스크 처리하는 것도 동일합니다. 그림 23의 세 번째 줄 박스들을 보면 각 사이드의 테두리가 각자의 영역으로 마스킹 되어 있는 것을 확인할 수 있습니다.

그림25.png 그림 25. dotted 스타일을 구현한 방법

dotted, dashed의 간격 결정하기

이전 PR의 코드를 적용하여 만든 PDF를 보면 점선에 약간 어색한 부분을 찾을 수 있습니다. 바로 이음매 부분이 자연스럽지 않다는 것인데요. 이유는 바로 고정된 간격을 사용하고 있기 때문입니다. 그렇다 보니 어떤 길이에서는 끝부분이 공백 부분이 되어 비어 있게 되는 것이죠.

그림26.png 그림 26. 끝부분이 어색하다

반면에 브라우저에서는 길이에 따라 항상 양 끝에는 점선이 존재하고 그 사이 간격을 적절하게 조정하고 있습니다. 그리고 점선으로 표현하기 짧은 길이인 경우 아예 실선으로 보이기도 합니다.

그림27.png 그림 27. 브라우저는 길이에 따라 점선의 간격을 적절히 조정한다(소스코드 참고)

점선이 양 끝에 항상 존재하고 적절하게 간격을 조절하는 방법에 대한 아이디어는 테두리에 점선이 몇 개 들어가는지 계산한 후에 나머지 공백의 간격을 균등하게 다시 계산하는 것입니다. 그 과정에서 좀 더 자연스럽게 표현하기 위한 몇 가지 방법이 추가됩니다. 자세한 건 아래 그림을 봐주세요.

그림28.png 그림 28. 적절한 dash, gap을 구하는 방법

double

Double 스타일은 테두리 두께를 3 분등하여 가운데를 비우고 양끝이 채워지는 스타일입니다. 그래서 테두리 두께가 3px이 되지 않으면 solid와 동일하게 동작합니다. dashed랑 dotted 구현한 거에 비하면 이거는 매우 간단히 구현 가능합니다. 그래서, double 스타일은 서비스 개발할 때 필요하지는 않지만 하는 김에 기능을 추가해 주기로 합니다. ^^

테두리를 3 분등 할 수 있게 추가로 2개의 path를 더 구합니다. 이후에 바깥쪽과 안쪽의 닫힌 path를 만들어주어 색을 칠하기만 하면 됩니다.

그림29.png 그림 29. double 스타일을 구현한 방법

html2canvas이 10년간 해결 못한 기능을 기여해 보자

html2canvas는 github의 tag 페이지를 확인해보았을 때 0.3.0이 2011년 9월 11일 릴리즈된 것을 확인할 수 있었습니다. 즉, 10년 동안 solid를 제외한 border-style을 지원하지 못하고 있었습니다.

서비스 개발을 위해서 시작한 것이지만 이것도 기여를 해봅니다. 기여 규칙에 따라 코드 포매팅, 테스트 추가 등을 하고 본문을 작성한 후 PR(https://github.com/niklasvh/html2canvas/pull/2531)을 올렸습니다. html2canvas는 개인이 관리하는 오픈소스로 보입니다. PR을 올린 후 한 달이 넘게 반응이 없습니다. 그 사이 서비스는 오픈을 합니다.

그리고, 이제 저도 기여했던 것을 잊어갈 무렵, 드디어 PR이 머지 되었습니다. 그리고, 1.0.0 정식 버전을 릴리스 하더라고요. 그러더니 최근 1년 동안 거의 액티비티가 없었는데 갑자기 열심히 릴리스 하기 시작하더군요. (제 PR에 누군가가 이제 이 프로젝트는 방치된 거 같다고까지도 댓글을 남겼어요)

제 기여가 뭔가 자극제가 된 걸까요?

그림30.png 그림 30. 1.0.0 릴리즈 후 열 일 하시는 중

그래서 문제 2를 해결하기 위한 여정은 끝났는가?

학습지를 PDF로 다운로드할 수 있어야 한다는 요구사항을 맞추기 위한 여정은 이렇게 끝납니다. 이제 HTML을 기반으로 PDF를 생성해낼 수 있고, dotted도, dashed도, 둥근 테두리와 조합된 것도 PDF로 만들어낼 수 있습니다.

그림 31.png 그림 31. 자연스러운 dotted와 dashed 표현!

이로써 요구사항으로 받았던 2가지 문제(미리 보기를 보여주어야 하며 정확히 인쇄해야 한다, 학습지를 PDF로 다운로드할 수 있어야 한다)를 모두 해결하였습니다. 🎉

마무리

바로학습AI 서비스에서 학습지는 랜덤하게 생성되지만 한번 생성한 학습지에 대한 데이터는 변하지 않는 값입니다. 그렇다면, PDF와 같은 리소스 또한 한번 만들어 놓고 해당 리소스를 계속 활용하는 방법도 있을 것입니다.

하지만, 이렇게 했을 경우 한번 만들어진 학습지의 내용에 오류가 있어 수정하게 되었다면 관련된 리소스를 모두 갱신하는 번거로움이 발생할 수도 있을 것입니다. 또한 파일을 관리하기 위한 추가적인 설계가 필요하며, 한정된 서버 자원을 PDF 생성하는데, 파일을 제공하는 데 사용하여야 합니다.

이 프로젝트에서 PDF의 장수는 최대 2장이므로, 새로운 시도를 해볼 수 있었던 것 같습니다. 이렇게 하는 것이 다른 프로젝트에서는 적합하지 않을 수도 있겠죠? 하지만 간단한 PDF 생성이 필요한 부분이 있다면 한번 참고해 보시면 어떨까요?

그리고 이번 프로젝트는 구성원들끼리 뭔가 합이 착착 잘 맞고 일정이 늘어지는 것도 없이 매끄럽게 진행되었고, 진행 과정에서 무려 총 3번(jsPDF 1번, html2canvas 2번 - 언급 안 한 PR이 하나 더 있어요-)의 오픈소스 기여를 하게 되어서 개인적으로는 매우 보람을 느꼈던 프로젝트였네요.

긴 글 읽어 주셔서 감사합니다.