안은결

Velog Readme Stats

서로 다른 글자 너비를 측정해 Velog 태그에 꼭 맞춘 SVG 카드

Velog Readme Stats latest result for eungyeole
[![Velog's GitHub stats](https://velog-readme-stats.vercel.app/api?name=eungyeole&tag=github)](https://velog-readme-stats.vercel.app/api/redirect?name=eungyeole&tag=github)

SVG는 CSS처럼 자유롭지 않다

HTML에서는 텍스트에 padding을 주고 inline-flex를 사용하면 브라우저가 배경의 너비와 다음 요소의 위치를 계산합니다. 서버에서 SVG 문자열을 만들 때는 이 흐름을 그대로 사용할 수 없습니다. <text>와 <rect>는 서로의 크기를 모르고, 다음 태그도 앞 태그의 뒤에 자동으로 놓이지 않습니다.

CSS, TypeScript, Frontend Development처럼 길이가 다른 태그를 그리려면 세 값을 직접 정해야 합니다. 배경의 너비, 텍스트의 중심, 다음 태그의 시작 위치입니다. 세 값의 기준은 모두 실제 글자 너비입니다.

아래에서 태그를 바꿔보세요. 글자가 달라질 때 배경의 너비도 함께 바뀝니다.

TypeScript

text width 58.0px

화면 밖에서 글자를 먼저 잰다

SVG는 텍스트를 그리기 전에 실제 너비를 알려주지 않습니다. 서버에서 Canvas로 글자를 먼저 측정하기 위해 OffscreenCanvas 방식을 사용하기로 했습니다.

Node.js에서는 @napi-rs/canvas로 화면에 표시되지 않는 Canvas를 만들 수 있습니다. 같은 글꼴과 크기를 설정한 뒤 measureText를 호출하면 SVG를 배치하는 데 필요한 글자 너비를 얻을 수 있습니다.

function getTextWidth(text: string, fontSize: number) {
  const context = canvas.createCanvas(1, 1).getContext("2d");
  context.font = `${fontSize}px ${fontFamily}`;

  return context.measureText(text).width;
}

Canvas는 결과물에 나타나지 않습니다. SVG를 만들기 전에 글자 너비를 구하는 측정 도구로만 사용합니다.

하나의 너비로 세 곳을 맞춘다

측정한 글자 너비를 기준으로 태그의 전체 너비를 구합니다. 같은 값으로 배경을 그리고, 텍스트를 가운데에 놓고, 다음 태그의 시작점을 옮깁니다.

const width = measureTag(tag);

renderTag({
  width,
  textX: width / 2,
  x: previousX,
});

previousX += width + 6;

width / 2와 text-anchor="middle"이 텍스트를 pill의 가운데에 놓습니다. 배경과 중심과 다음 위치가 같은 너비에서 시작하므로 따로 밀리지 않습니다.

카드의 경계에서 멈춘다

현재 시작점에 새 너비를 더하면 태그의 오른쪽 끝을 알 수 있습니다. 카드 안쪽 영역인 400px을 넘으면 그리지 않습니다.

if (previousX + width > 400) return;

태그를 중간에서 자르지 않고 온전한 pill만 남기는 기준입니다.

글자 너비를 먼저 구하면 SVG에서도 태그의 배경, 중심, 다음 위치를 하나의 규칙으로 만들 수 있습니다.