3P by GN⁺ | ★ favorite | 댓글 3개
  • Dropflow는 기초 CSS 표준인 inline, block, float, positioning, 향후 table을 탐구하기 위해 만든 CSS 레이아웃 엔진이며, flexbox와 grid는 대상이 아님
  • Node와 node-canvas로 백엔드에서 PDF·이미지 생성에 쓸 수 있고, 브라우저에서는 canvas에 줄바꿈이 있는 리치 텍스트를 렌더링할 수 있음
  • 텍스트 레이아웃은 양방향·RTL 텍스트, 그래핌 단위 폰트 폴백, 색이 있는 발음 구별 기호, OpenType/TrueType 폰트 등록, HarfBuzz 기반 shaping을 지원함
  • CSS 지원 범위는 30개 이상 속성으로, float, clear, display: inline-block, position: relative, overflow, z-index, zoom 등이 동작하고 table, absolute, fixed, transform 등은 계획 단계임
  • 최고 성능은 HTML/CSS 파싱을 건너뛰는 hyperscript API와 재사용 가능한 style 객체에서 나오며, 2019 MacBook Pro 기준 500개 넘는 문단의 The Little Prince를 HTML에서 이미지로 160ms 미만에 변환함

Dropflow가 다루는 CSS 레이아웃 범위

  • Dropflow는 CSS 레이아웃 엔진으로, foundational CSS standards의 범위를 탐구하기 위해 만들어짐
    • 대상은 inline, block, float, positioning, 향후 tables임
    • flexbox와 grid는 대상이 아님
  • 고품질 텍스트 레이아웃 구현을 갖추고 있으며, 세계 여러 언어를 표시할 수 있음
  • 사용처는 두 가지로 제시됨
    • Node와 node-canvas를 이용해 백엔드에서 PDF나 이미지 생성
    • 브라우저에서 canvas에 줄바꿈되는 리치 텍스트 렌더링

주요 기능

  • 30개 이상 CSS 속성을 지원하며, float처럼 복잡한 속성도 포함함
  • 텍스트 관련 기능이 넓음
    • 양방향 텍스트와 RTL 텍스트
    • 그래핌 단위 폰트 폴백
    • 색이 있는 발음 구별 기호
    • 바람직한 줄바꿈 처리, 예를 들어 시작 padding을 다음 줄로 가져감
    • 최적화된 shaping
  • 입력 방식은 HTML/CSS뿐 아니라, 스타일을 객체로 넘기는 hyperscript h() API도 지원함
  • OpenType/TrueType 버퍼는 등록 가능하며, 등록이 필수임
  • <img>는 JPEG, BMP, PNG, GIF를 지원하지만 백엔드별 지원은 다를 수 있음
  • 상속·캐스케이드된 스타일은 두 번 계산하지 않음
  • 타입이 완비되어 있고 테스트가 많으며 빠른 동작을 목표로 함

CSS 지원 상태

  • Inline formatting에서 동작하는 항목에는 color, direction, font-family, font-size, font-stretch, font-style, font-weight, line-height, text-align, vertical-align, white-space, word-break, overflow-wrap, word-wrap, word-spacing 등이 있음
  • Inline formatting에서 font-variant, letter-spacing, tab-size, text-decoration, unicode-bidi계획됨 상태임
  • Block formatting에서는 clearfloat가 동작함
    • writing-modehorizontal-tb, vertical-lr, vertical-rl은 부분 구현 상태임
    • BFC에는 구현됐지만 IFC에는 아직 구현되지 않음
  • Boxes and positioning에서는 여러 박스·배치 속성이 동작함
    • background-clip, background-color, border-color, border-style, border-width
    • top, right, bottom, left
    • box-sizing
    • display: block, inline, inline-block, flow-root, none
    • height, margin, padding, overflow, width, z-index, zoom
    • position: relative
  • Boxes and positioning에서 display: table, min/max-height, min/max-width, position: absolute, position: fixed, transform계획됨 상태임

기본 사용 흐름

  • Dropflow는 브라우저와 같은 방식으로 상속·계산된 스타일이 있는 DOM을 기반으로 동작함
  • 일반적인 흐름은 다음과 같음
    • 레이아웃 전에 FontFace로 폰트 등록
    • flow.style()로 스타일 객체 생성
    • flow.h()로 DOM 생성
    • flow.dom()으로 렌더링할 트리 준비
    • renderToCanvas()로 canvas 전체에 레이아웃과 페인트 수행
  • HTML API도 제공하지만, 성능이 중요하지 않거나 학습 목적일 때만 권장됨
    • 파싱은 추가 시간을 만들고 번들 크기를 크게 늘림
    • HTML 파싱은 @fb55 덕분에 빠르다고 적혀 있음
    • 현재는 style HTML 속성만 지원하며 class는 아직 동작하지 않음

성능 특성

  • 성능은 최상위 목표이며, 정확성 다음으로 중요함
  • 예제 기준 성능 수치가 제시됨
    • 여러 inline span과 다른 폰트가 있는 8개 문단을 HTML에서 이미지로 변환하는 데 2019 MacBook Pro에서 9ms, 2012 MacBook Pro에서 13ms
    • 500개 넘는 문단의 The Little Prince를 HTML에서 이미지로 변환하는 데 2019 MacBook Pro에서 160ms 미만, 2012 MacBook Pro에서 250ms 미만
    • 10글자 단어를 생성하고 레이아웃만 수행하는 데 2019 MacBook Pro에서 25µs 미만, 2012 MacBook Pro에서 50µs 미만
  • 가장 빠른 성능은 hyperscript API로 DOM을 직접 만들고 일반적인 HTML/CSS 파싱 단계를 건너뛸 때 얻을 수 있음
  • style 객체를 재사용하면 이점을 더 많이 얻을 수 있음
  • 다른 너비에서 reflow하는 작업은 레이아웃 트리를 다시 만드는 것보다 빠름

API 구성

  • 기본 단계는 두 가지임
    • 폰트 등록
    • Hyperscript API나 Parse API로 DOM 생성
  • 단순 사용에서는 DOM을 canvas에 바로 렌더링할 수 있음
    • renderToCanvas(el, canvas)는 canvas의 width와 height를 viewport 크기로 사용해 전체 레이아웃을 렌더링함
  • 더 낮은 수준 API를 쓰면 레이아웃을 보존할 수 있음
    • 의존 리소스 로드
    • DOM의 layout 생성
    • layout reflow
    • HTML5 canvas 같은 대상에 paint
  • 이 방식은 다른 크기로 reflow하거나, 보이지 않는 레이아웃을 페인트하지 않거나, intrinsics를 얻는 데 사용할 수 있음

폰트 처리

  • Dropflow 프로그램의 첫 단계는 CSS font 속성으로 선택될 폰트를 등록하는 것임
  • Dropflow는 시스템 폰트를 검색하지 않으므로, 최소 한 번 FontFace를 만들고 추가해야 함
  • 폰트 등록 API는 CSS Font Loading API의 부분집합을 구현하고, 비표준 loadSync 메서드를 추가함
  • 서버 측에서는 file:/// URL을 readFileSync로 동기 로드할 수 있음
  • ArrayBuffer는 브라우저처럼 생성자에서 즉시 로드됨
  • registerNotoFonts는 모든 Noto Sans 폰트 패밀리를 등록함
    • 폰트는 FontSource가 배포하고 jsDelivr가 호스팅함
    • Noto Sans 폰트가 200개를 넘고 CJK 폰트의 unicodeRange 문자열이 커서 큰 import임
    • 브라우저 프로덕션 사용에서는 개별 폰트를 등록하는 편이 더 낫다고 되어 있음
    • Latin에는 italic 폰트가 등록되고, 모든 스크립트에는 normal 400과 bold 700이 등록됨
  • 중국어·한국어·일본어는 공통 Unicode 코드 포인트를 공유하지만 문자가 다르게 렌더링될 수 있으므로, 가능하면 특정 언어용 폰트를 쓰는 편이 더 좋음

레이아웃, reflow, paint

  • layout(el)box tree, fragmentation tree, glyph를 포함하는 layout을 만듦
    • box tree는 DOM tree와 대체로 대응하지만, 익명 텍스트 콘텐츠 때문에 더 많은 box가 생기거나 display: none 때문에 더 적을 수 있음
  • reflow(layout, width = 640, height = 480)는 box를 배치하고 텍스트를 줄로 나눠 paint 가능한 상태로 만듦
    • block box의 margin collapsing
    • HarfBuzz에 텍스트 전달
    • 폰트 폴백 반복
    • 줄바꿈과 break point에 따른 reshaping
    • float 배치와 clear 처리
    • direction과 텍스트 방향에 따른 shaped text span과 background 배치
    • float, inline-block, absolute 콘텐츠의 intrinsics 계산
    • normal flow 이후 position 처리
  • Paint 대상은 현재 Canvas와 SVG를 지원함
    • paintToCanvas는 브라우저 canvas, node-canvas, 유사한 표준 준수 context에 paint함
    • paintToSvg는 SVG 문자열을 만들고, FontFace에 넘긴 URL을 참조하는 @font-face 규칙을 포함함
    • paintToSvgElements는 기존 SVG 안에 그릴 때 쓰도록 <svg>@font-face 규칙을 추가하지 않음
    • paintToHtml은 절대 위치 요소의 flat list를 생성하며, 사용을 권장하지는 않지만 개발 중 유용할 수 있음

DOM API와 환경 훅

  • Hyperscript와 Parse API에서 얻은 루트 HTMLElement는 browser의 querySelector 계열처럼 tag name, id, class로 요소를 찾는 메서드를 제공함
    • query(selector)는 하나의 HTMLElement 또는 null을 반환함
    • queryAll(selector)HTMLElement[]를 반환함
  • HTMLElement는 연결된 render box를 가질 수 있음
    • 일반적으로 하나지만, inline과 block 콘텐츠가 섞인 경우 여러 개일 수 있음
    • BlockContainer는 absolute positioned element, floated element, inline-block, block-level element에 생성됨
    • ReplacedBox는 이미지에 생성됨
  • Dropflow는 다양한 환경에 맞게 조정할 수 있도록 설계됨
    • 브라우저에서는 fetch로 폰트와 이미지를 로드하고, 폰트 버퍼를 document.fonts에 등록함
    • Nodejs에서는 fs.readFileSync로 폰트를 동기 로드할 수 있음
    • canvas 백엔드와 node-canvas가 있으면 node-canvasregisterFont를 호출함
    • node-canvas는 font buffer를 지원하지 않으므로 file:// URL을 사용해야 함
  • @napi-rs/canvasskia-canvas를 쓰려면 flow.environment.registerFont를 해당 폰트 등록 API에 연결하는 몇 줄의 코드가 필요함
  • 환경에는 6개 훅이 있음
    • wasmLocator
    • registerFont
    • resolveUrl
    • resolveUrlSync
    • createDecodedImage
    • destroyDecodedImage

HarfBuzz 기반 텍스트 shaping

  • Glyph layout은 WebAssembly로 컴파일된 HarfBuzz가 수행함
  • measureText API로 텍스트 span 위치를 잡는 방식으로는 얻기 어려운 정확성을 목표로 함
  • 예로 Google Sheets에서 "AV""V"만 다른 색으로 칠하면 kerning이 사라져 글자가 원래보다 멀어짐
    • 두 번의 measureTextfillText 호출이 글자별로 발생하면서 contextual glyph advance가 사라지기 때문임
  • Dropflow는 색이 바뀌는 지점이 아니라 더 거친 shaping boundary에서 HarfBuzz를 사용해 폰트를 더 정확히 지원함
  • WebAssembly로 컴파일된 HarfBuzz는 CanvasRenderingContext2DmeasureText와 비슷한 성능 지표를 낼 수 있음
    • measureText만큼 빠르지는 않지만 크게 느리지는 않다고 되어 있음
    • 둘 다 텍스트 레이아웃 스택의 지배적인 병목은 아니라고 되어 있음
  • measureText 기반 텍스트 레이아웃은 빠르게 동작하려면 word cache가 필요하며, GSuite 앱이 이 방식을 쓴다고 되어 있음
    • word cache는 공백을 가로지르는 효과가 있는 폰트를 지원하지 못함
    • 그런 폰트를 지원하려면 문단의 break index에서 이진 검색이 필요하고, 전체 문단을 HarfBuzz에 넘기는 것보다 훨씬 느리다고 되어 있음
    • 색이 있는 발음 구별 기호는 measureText로는 불가능함

의존 프로젝트

  • Dropflow는 package.json 의존성이 없지만, 여러 프로젝트의 작업을 활용함
  • JavaScript 의존성은 프로젝트에 체크인되어 있으며, 초점 유지와 dependency-of-dependency 문제를 피하기 위해 다양한 정도로 수정됨
  • 주요 프로젝트는 다음과 같음

댓글과 토론

원 제목은 "Show HN: Dropflow, a CSS layout engine for node or <canvas>" 이거였군요. 지금은 "GN⁺: HN 소개: 노드 또는 <canvas>용 CSS 레이아웃 엔진, Dropflow</canvas>"로 들어가있네요.

제목에 태그가 있으니 상세페이지 제목부분이 깨지는군요..ㅎㅎ escape를..

Hacker News 의견들
  • 요즘 백엔드에서 예쁜 PDF를 만드는 기본 방식은 헤드리스 브라우저를 띄우고 브라우저 API로 HTML/CSS를 PDF로 바꾸는 것인데, 서버에서 브라우저 인스턴스를 돌리고 큰 작업량에 맞게 확장하는 비용이 꽤 큼
    이건 판을 바꾸는 도구라서, 이제 브라우저 오버헤드 없이도 HTML/CSS로 PDF를 디자인하고 생성할 수 있음

    • 웹이 여기까지 온 게 놀라움. 예쁜 PDF 문서를 만드는 최선의 방법이 서버에서 웹 브라우저를 그대로 실행하는 것이라니, 90년대나 2000년대에는 상상하기 어려웠을 듯
    • 브라우저를 쓰면 생성된 PDF가 벡터와 폰트를 사용한다는 장점이 있음. 반면 Canvas 기반은 PDF 안에서 대부분 이미지가 될 텐데, 대부분의 용도에서는 큰 문제는 아닐 수 있음
    • https://ekoopmans.github.io/html2pdf.js/를 써봤고 꽤 잘 동작했음
    • 조금 헷갈림. 사이드 프로젝트 백엔드에서 PDF 생성을 위해 Prawn 라이브러리를 꽤 오래 써왔음: https://github.com/prawnpdf/prawn
      다만 내가 생성하는 PDF는 확실히 예쁘지는 않아서, 그게 차이일 수도 있음
    • 몇몇 클라이언트를 위해 PDF 렌더러를 만들어왔는데, PDF에서 가장 큰 요구사항은 접근성이었음
      모두 ADA 준수가 필요해서, Canvas 렌더러로 바꾸기는 어려움. 그러면 접근성이 사라지기 때문임
  • 정말 좋아 보임. 이전 직장에서 몰입형 온라인 학습 플랫폼을 개발했는데, Oculus Quest 2로 국방부 인력의 외국어 교육을 했고 WebXR, Three.js 등을 사용했음
    Unity3D는 한 번으로 충분했고 앱스토어 심사를 거치고 싶지도 않았음. 자체 기기군이 있어서 괜찮았음
    가장 큰 난제 중 하나는 교재 콘텐츠 워크플로를 만드는 일이었고, 거의 혼자 해야 했음. 실제 언어 강사들이 PowerPoint로 PDF를 만들고, 내가 만든 전용 편집기로 PDF를 콘텐츠 데이터베이스에 올려 훈련 환경에 배치한 뒤, PDFJS로 Canvas 요소에 렌더링해서 3D 사각형의 텍스처로 썼음
    이런 도구가 있었다면 사람들이 PowerPoint로 우회해서 자료를 만들 필요가 없었을 것임. PowerPoint 방식은 Photoshop으로 이미지를 만들게 했던 이전 시도보다 워크플로 속도를 크게 개선했지만, 앱 안에 표지판 편집기를 만들 수 있었다면 “환경에서 어떻게 보일지 추측 → PDF로 내보내기 → DB 업로드 → 실제 모습 확인”의 반복을 없애 더 좋아졌을 것임
    어차피 제품이 아니라 서비스 판매만 아는 사업개발팀이나 마케팅팀이 없었으니 큰 의미는 없었겠지만

  • 내 프로젝트 https://htwins.net/scale2와 SVG 또는 Canvas를 쓰는 다른 작업에 이런 걸 찾고 있었음

    • 이걸 어떻게 만들었는지 제작기를 꼭 읽어보고 싶음
  • Flexbox로 고생한다면, 반응형 레이아웃을 만들 때 여러 속성에 신경 쓰지 않도록 과정을 단순화해주는 도구를 쓸 수 있음: https://flexboxcss.com

    • 실제로 본 사이트 중 뉴모픽 디자인을 쓰는 첫 사례임. 미감이 좋고, 실전에서 본 적은 없었음
  • 훌륭함. 이런 도구는 브라우저 렌더링 엔진이라는 마법 상자를 이해 가능하게 만들어주므로 매우 중요함
    HTML과 CSS 렌더링에 대해 완전한 기계 판독 가능한 명세를 만들 수 있다면 렌더러를 생성할 수 있을 것임. 브라우저별 특이 동작은 그 위의 확장으로 둘 수 있음. https://github.com/tawesoft/html5spec 같은 것이 실제 엔진에 쓰이는 형태가 되면 좋겠음

    • 이걸 보니 Ladybird가 처음부터 작성 중인 브라우저라는 점이 떠올랐고, 실제로 명세에 버그가 없는지 확인하는 데도 꽤 유용했음
  • 요즘 궁금해하던 것과 꽤 가까움. CSS와 SVG를 그래픽 및 UI 라이브러리 위의 추상화 계층으로 쓸 수 있을지 생각하고 있었음
    node-canvas는 처음 들었는데, 그중 그리기 부분을 채워주는 듯함. 이 도구는 UI 라이브러리에서 내가 필요한 전부인 레이아웃 부분을 맡을 수 있어 보임
    CSS 구현이 얼마나 어려웠을지도 궁금함. 꽤 복잡하다고 들었음

    • CSS로 네이티브 그래픽 라이브러리를 대상으로 삼는 Sciter라는 다른 프로젝트도 있음: https://sciter.com
      CSS 구현은 어려웠지만, 가장 큰 장벽은 지식이 잘 드러나 있지 않다는 점이었음
      가장 어려운 부분은 텍스트 레이아웃임. 글리프를 다루고 RTL을 위해 역방향으로 순회하는 일은 머리가 아프고, 줄바꿈도 정말 복잡해짐. 필요한 지식이 한곳에 정리되어 있지 않아서 더 난해함
      초기에 블록 레이아웃을 끝낸 뒤 몇 년 동안은 주당 몇 시간씩만 작업하면서 텍스트 셰이핑과 항목화의 세부 사항, 해야 할 것과 피해야 할 것을 배워야 했음. 많은 부분은 Pango [1] 소스 코드를 읽으며 배웠고, 나머지는 Google 검색으로 조합했음
      그 외에는 W3C 명세가 거의 모든 것을 다룸. CSS2 표준 [2]는 지금까지 읽은 것 중 가장 아름다운 문서 중 하나였음. 내부적으로 일관되고 간결하며, 수년간의 숙고와 시행착오가 반영된 결과임. CSS3도 훌륭하지만 CSS2가 모든 것의 기반임
      [1] https://gitlab.gnome.org/GNOME/pango/
      [2] https://www.w3.org/TR/CSS22/
  • 세상에 큰 기여임. “누군가 $X를 해야 하는데”라고 모두 생각하지만 아무도 하지 않았던 전형적인 사례처럼 보임
    레이아웃에 CSS를 쓰는 걸 좋아하는 입장에서 요즘은 주로 Flexbox와 Grid에 의존하고 있음. 아직 지원하지 않는 건 충분히 이해되는데, 언젠가 지원할 계획이 있는지 궁금함. 있다면 다른 사람들이 어떻게 도울 수 있을까?

  • 정말 대단함. HTML을 프로그래밍 방식으로 PNG로 바꾸는 일이 얼마나 어려운지 대부분 잘 모를 것 같음
    Node와 브라우저의 차이, 또는 HTML과 Canvas의 차이 때문에 수많은 작은 문제에 부딪히게 됨

  • 유용해 보임. 먼저 CSS를 이해하고 그 위에 레이아웃 엔진을 만드는 데 얼마나 많은 작업이 필요한지 상상하기 어려움