PDF 에 출처 링크 한 줄 넣기, 끄는 길까지 만드는 법

브라우저에서 만든 PDF 마지막 쪽에 클릭되는 출처 링크 한 줄을 넣고, 끄는 스위치와 유입 측정까지 순서대로 따라 할 수 있어요.

셀루모2026.10.18 · 14분 읽기
목차

내가 만든 도구로 사람들이 PDF 를 만들어 가는데, 그 파일이 어디서 왔는지 아무도 모른다면 아쉽죠. 파일은 메일과 메신저를 타고 여러 사람 손을 거치는데, 도구 이름은 어디에도 남지 않아요.

이 글은 PDF 마지막 쪽 아래에 작은 출처 한 줄과 클릭되는 링크를 넣는 방법을 정리해요. 넣는 것만큼 중요한 것, 곧 사용자가 한 번에 끌 수 있게 하는 길과 그 링크로 들어온 방문을 세는 법까지 함께 다뤄요. 브라우저 안에서 pdf-lib 라는 라이브러리로 PDF 를 만드는 경우를 기준으로 해요.

혹시 이런 고민 있으신가요?

  • 사용자가 내려받아 보낸 PDF 가 입소문이 되길 바라지만, 파일에는 우리 도구의 흔적이 전혀 없어요.
  • 워터마크(파일 위에 겹쳐 찍는 표시)를 넣자니 계약서 같은 서류에 큼직한 로고가 박히는 게 사용자에게 미안해요.
  • 출처 줄을 넣었다고 해도 정말 방문으로 이어지는지 확인할 방법이 없어요.

이 글을 읽으면

  • 서류 본문을 건드리지 않고 마지막 쪽 아래 여백에만 들어가는 출처 줄을 직접 그릴 수 있어요.
  • 글자만이 아니라 눌러서 열리는 링크로 만드는 방법을 알게 돼요. 회전된 쪽에서도 똑바로 보이게 하는 계산도 포함해요.
  • 기본값을 켜 둘지 꺼 둘지 정하는 기준과, 링크로 들어온 방문을 다른 유입과 구분해서 세는 방법을 정할 수 있어요.

1단계. 어디에, 얼마나 작게 넣을지 정하기

먼저 줄을 어디에 둘지부터 정해요. 이 결정이 사용자의 신뢰를 좌우해요. 계약서나 동의서처럼 조심스러운 서류에 낯선 문구가 들어가면 사용자는 파일을 믿지 못하게 될 수 있어요.

그래서 두 가지 원칙을 세웠어요.

  • 서류 본문이 아니라 마지막 쪽 아래 여백에만 둬요. 본문 위에 겹쳐 그리지 않으니 서류의 내용이 달라지지 않아요.
  • 한 줄만 둬요. 로고도, 큰 글씨도, 여러 쪽에 반복되는 문구도 넣지 않아요.

크기도 숫자로 정해 두면 흔들리지 않아요. 저희는 글자 크기를 7포인트(pt), 쪽 가장자리에서 줄의 바닥선까지를 16포인트로 잡았어요. 포인트는 PDF 가 쓰는 길이 단위로, 1포인트는 약 0.35mm 예요. 7포인트면 읽을 수는 있지만 눈에 띄지 않을 만큼 작아요. 독자가 자기 서류에 맞춰 바꿀 값은 이 두 숫자예요. 작게 할수록 거슬리지 않지만, 너무 작으면 사람이 링크가 있는 줄도 모르고 지나쳐요.

문구는 짧은 한 줄로 해요. 형식은 이래요.

TEXT
Made with 서비스이름  ·  서비스주소

가운데점 양쪽에 공백을 둘씩 둬서 이름과 주소가 떨어져 보이게 했어요. 주소는 줄에 보이는 글이고, 눌렀을 때 열리는 실제 주소는 따로 있어요. 이 부분은 3단계에서 다뤄요.

TIP

문구를 정하기 전에 내 서류 중 가장 빽빽한 것을 열어 보세요. 마지막 쪽 아래까지 글이 차 있는 서류에서도 줄이 읽히는지 보면 크기와 여백을 정하기 쉬워요.

한 가지 한계도 미리 정해 둬요. 마지막 쪽 아래 여백까지 서류의 글이 차 있으면 출처 줄이 그 글과 겹칠 수 있어요. 저희는 이것을 자동으로 감지하지 않기로 했어요. 해법은 감지가 아니라 사용자가 줄을 끌 수 있게 하는 것이고, 그 이야기는 4단계에서 해요.

2단계. 글꼴은 PDF 기본 글꼴 하나로 끝내기

출처 줄을 영어로 둔 이유가 여기 있어요. PDF 에는 글꼴을 파일 안에 싣는 방법과, 모든 뷰어에 이미 들어 있는 기본 글꼴을 쓰는 방법이 있어요. 한글을 쓰려면 글꼴 파일을 실어야 하는데, 한글 글꼴은 크기가 커서 파일이 무거워져요. 영문 기본 글꼴인 헬베티카는 싣지 않고 이름만 가리키면 되니 파일이 거의 커지지 않아요.

pdf-lib 에서는 이렇게 불러요.

TS
import { PDFDocument, StandardFonts, rgb } from "pdf-lib" const doc = await PDFDocument.load(sourceBytes)const font = await doc.embedFont(StandardFonts.Helvetica)const page = doc.getPage(doc.getPageCount() - 1) // 마지막 쪽const width = font.widthOfTextAtSize(creditText, 7) // 7pt 일 때 줄의 너비

주의할 점이 하나 있어요. 기본 글꼴은 한글을 그리지 못해요. 줄에 한글이 한 글자라도 섞이면 그리는 단계에서 오류가 나요. 그래서 출처 줄은 영문과 숫자, 그리고 기본 글꼴이 가진 기호만 써요. 위에서 쓴 가운데점(·)은 이 글꼴이 그릴 수 있는 문자라서 괜찮아요. 새 기호를 넣고 싶다면 먼저 한 번 그려 보고 오류가 나는지 확인하세요.

줄의 너비를 미리 재는 이유는 다음 단계에서 가운데 정렬과 링크 영역을 계산하기 때문이에요.

만약 출처 줄까지 한글로 하고 싶다면 한글 글꼴을 파일에 실어야 해요. 그러면 파일 크기가 커지고 글꼴을 필요한 글자만 줄이는 작업에서 숨은 손상이 생길 수 있어요. 한 줄을 위해 그 비용을 치를 가치가 있는지 따져 보세요. 저희는 PDF 에 박히는 줄은 영어로 두고, 사용자가 보는 화면의 안내는 모두 한국어로 쓰는 쪽을 골랐어요.

3단계. 눌리는 링크로 만들기

PDF 에 글자를 그리는 것만으로는 링크가 되지 않아요. 글자 위에 링크 주석(annotation)을 따로 올려야 해요. 주석은 쪽 위에 겹쳐 놓는 투명한 상자인데, 상자를 누르면 지정한 동작이 일어나요. 웹 페이지에서 글자를 a 태그로 감싸는 것과 같은 역할이에요.

링크 주석에 필요한 것은 네 가지예요. 주석의 종류(Link), 눌리는 영역(Rect), 테두리(없음), 눌렀을 때의 동작(주소 열기)이에요.

TS
const link = doc.context.register(  doc.context.obj({    Type: "Annot",    Subtype: "Link",    Rect: [x - 2, y - 2, x + width + 2, y + 7 + 2], // 글자보다 조금 크게    Border: [0, 0, 0], // 테두리 없음    A: { Type: "Action", S: "URI", URI: PDFString.of(linkUrl) },  }),) const annots = page.node.Annots()if (annots) annots.push(link)else page.node.set(PDFName.of("Annots"), doc.context.obj([link]))

마지막 네 줄이 놓치기 쉬워요. 쪽에 이미 다른 주석이 있으면 목록에 덧붙이고, 없으면 새 목록을 만들어요. 이미 있는 목록을 새 목록으로 덮어쓰면 원래 서류의 링크와 양식 칸이 사라질 수 있어요.

눌리는 영역을 글자보다 위아래, 좌우로 2포인트씩 넓게 잡은 것은 손가락이나 마우스가 살짝 빗나가도 눌리게 하려는 거예요.

링크의 실제 주소에는 출처를 나타내는 값을 붙여요. 이것이 6단계의 측정과 이어지는 부분이에요. 아래는 형식이에요.

TEXT
https://서비스주소/?utm_source=pdf&utm_medium=footer&utm_campaign=made_with

utm 값은 유입이 어디서 왔는지를 알려 주는 꼬리표예요. 이 값에 사용자의 이름이나 파일 이름은 절대 넣지 않아요. 파일을 받은 사람이 링크를 누르면 서버 기록과 분석 도구에 그 주소가 그대로 남기 때문이에요.

PDF 한 장에 출처 줄과 링크가 들어가고 방문으로 이어지는 흐름출처 줄은 글자와 링크 주석 두 겹으로 들어가요

4단계. 쪽이 돌아가 있어도 똑바로 보이게 계산하기

여기서 많은 사람이 막혀요. 스캔한 서류나 가로로 만든 서류는 쪽에 회전 값(/Rotate)이 들어 있는 경우가 있어요. 뷰어는 이 값만큼 쪽을 돌려서 보여 줘요. 이럴 때 "쪽 아래 가운데" 라고 좌표를 계산하면 화면에서는 옆면 가장자리에 세로로 누운 글자가 나타나요.

원리는 이래요. 사용자가 화면에서 보는 아래쪽 가장자리가 PDF 내부 좌표에서는 어느 쪽 가장자리인지를 회전 값으로 가려내는 거예요.

쪽 회전 값화면에서 보이는 아래쪽줄을 그릴 때 글자를 돌리는 각도
0도내부 좌표의 아래쪽 가장자리0도
90도내부 좌표의 오른쪽 가장자리90도
180도내부 좌표의 위쪽 가장자리180도
270도내부 좌표의 왼쪽 가장자리270도

글자는 쪽이 돌아간 만큼 같이 돌려서 그려요. 그러면 뷰어가 쪽을 돌릴 때 글자도 함께 돌아가 결국 똑바로 선 한 줄이 돼요. 눌리는 영역도 같은 방식으로 돌려서 계산해야 해요. 글자만 돌리고 눌리는 영역을 안 돌리면 글자 위치와 링크 위치가 어긋나요.

또 하나, 기준이 되는 상자도 중요해요. 쪽에는 화면에 실제로 보이는 영역을 정하는 상자(CropBox)가 따로 있을 수 있어요. 쪽 전체 크기가 아니라 이 상자를 기준으로 계산해야 여백 안에 들어와요. 상자가 없으면 pdf-lib 가 쪽 전체 상자를 돌려줘요.

이 계산은 pdf-lib 를 부르는 코드와 분리해서 순수한 함수로 만들어 두면 좋아요. 입력은 상자의 위치와 크기, 회전 값, 글자의 너비이고 출력은 시작 좌표, 글자 회전각, 눌리는 영역이에요. PDF 를 만들지 않고도 네 가지 회전에서 값이 맞는지 시험할 수 있기 때문이에요.

TIP

시험 파일은 회전 값 0, 90, 180, 270 짜리 쪽을 각각 하나씩 준비하세요. 계산 시험은 통과했는데 실제 뷰어에서 누워 보이는 일이 이 방법으로 잡혀요.

참고

저희는 브라우저의 PDF 뷰어, 미리보기, 아크로뱃에서 링크가 열린다고 알고 있지만 모든 뷰어에서 시험하지는 않았어요. 어떤 뷰어에서 링크가 열리지 않더라도 출처 줄 글자는 남아요. 링크가 안 열려도 서류가 망가지지 않는 구조가 중요해요.

5단계. 줄을 끌 수 있는 길을 같은 화면에 두기

출처 줄을 넣을 때 가장 신경 쓸 부분이에요. 사용자가 모르는 문구가 계약서에 박히면 신뢰 문제가 돼요. 그래서 세 가지를 지켜요.

  • 기본값은 켜 두되, 내려받는 화면에서 바로 끌 수 있는 스위치를 둬요. 로그인도 결제도 요구하지 않아요.
  • 스위치에는 무엇이 어디에 들어가는지 설명해요. 저희 화면의 문구는 "문서 맨 아래에 출처 한 줄 넣기" 와 "마지막 쪽 아래 여백에 작게 들어가요. 언제든 끌 수 있어요." 같은 뜻이에요.
  • 사용자의 선택은 브라우저에 기억해요. 한 번 끈 사람이 다음번에 또 끄지 않아도 돼요.

기억하는 부분은 브라우저의 localStorage 를 써요. 서버에 저장하지 않으니 가입이 필요 없고, 기억이 필요한 값도 켬과 끔 두 가지뿐이에요.

TS
const KEY = "credit-line" function readEnabled(): boolean {  try {    return window.localStorage.getItem(KEY) !== "off"  } catch {    return true // 저장소가 막혀 있으면 기본값 그대로  }} function saveEnabled(value: boolean) {  try {    window.localStorage.setItem(KEY, value ? "on" : "off")  } catch {    // 저장이 막혀도 이번 방문에는 선택이 적용돼요  }}

try 로 감싼 이유는 사생활 보호 모드나 쿠키 차단 상태에서 저장소를 읽기만 해도 오류가 날 수 있기 때문이에요. 이때는 기본값을 그대로 쓰고, 이번 방문에서만 선택을 적용해요.

스위치를 끈 상태에서는 PDF 를 만드는 함수에 출처 정보를 아예 넘기지 않아요. 합성 함수의 마지막 인자를 선택값으로 두고, 값이 없으면 줄도 링크도 그리지 않게 해요. 이렇게 하면 "끄면 정말 아무것도 안 들어가요" 를 코드 구조로 보장하게 돼요.

TS
export async function composePdf(  source: Uint8Array,  /* ...다른 인자... */  credit?: { text: string; url: string },) {  // ...본문 합성...  if (credit) await drawCredit(doc, credit)  return doc.save()}

또 하나 중요한 점은 실패의 범위예요. 출처 줄을 그리다 오류가 나도 사용자가 원한 서류는 그대로 나와야 해요. 출처 줄은 덤이니까요. 그래서 줄을 그리는 함수 안에서 오류를 잡아 기록만 남기고, 줄이 없는 PDF 를 그대로 돌려줘요.

TIP

시험 두 개는 꼭 넣으세요. 하나는 출처 정보를 주면 마지막 쪽에만 글자와 링크가 하나씩 생기는지, 다른 하나는 출처 정보를 주지 않으면 줄도 링크도 없는지 확인하는 시험이에요.

그리고 기본값을 정하는 기준을 미리 적어 둬요. 저희는 첫 4주 동안 PDF 를 내려받은 방문 가운데 줄을 끈 비율이 절반을 넘으면 기본값을 꺼짐으로 뒤집고, 줄은 공유 화면의 선택지로만 두기로 했어요. 이 기준은 정해 둔 것일 뿐 결과는 아직 나오지 않았어요. 기준을 미리 적어 두는 이유는 숫자가 나온 뒤에 기준을 정하면 보고 싶은 쪽으로 해석하게 되기 때문이에요.

6단계. 링크로 들어온 방문을 따로 세기

링크를 넣었다면 그 링크가 일하는지 봐야 해요. 방문 분석 도구 Umami 는 주소에 붙은 utm 값을 읽어 유입 화면에서 출처별로 나눠 보여 줘요. 그래서 3단계에서 붙인 꼬리표만 지키면 별도의 코드가 필요 없어요.

꼬리표는 경로마다 다르게 붙여요. 같은 도구에서 나가는 링크라도 어디서 눌렀는지 구분하려는 거예요.

링크 위치utm_sourceutm_mediumutm_campaign
PDF 마지막 쪽의 출처 줄pdffootermade_with
내려받은 뒤 공유 링크sharelinkafter_download

여기서 두 번째 줄은 내려받은 뒤 화면에 두는 공유 줄이에요. 저희는 내려받기가 끝난 모달 아래에 "주변에 PDF 서명이 필요한 분이 있나요?" 라는 문구와 함께 링크 복사 버튼을 두고, 시스템 공유 시트를 쓸 수 있는 기기에서만 공유하기 버튼을 보여 줘요. 시스템 공유 시트가 있는지는 브라우저가 알려 줘요.

TS
const canShare = typeof navigator.share === "function" if (canShare) {  await navigator.share({    title: "공유 시트 제목",    text: "공유 시트 본문",    url: shareLink, // utm_source=share 가 붙은 주소  })}

공유 시트는 사용자가 창을 닫으면 오류를 던져요. 이것은 오류가 아니라 사용자의 선택이니 조용히 넘기세요.

클릭 수를 세는 방식에서 흔한 실수가 하나 있어요. 스위치를 켜고 끈 횟수, 복사 버튼과 공유 버튼이 눌린 횟수 같은 클릭은 제품의 핵심 가설을 시험하는 숫자가 아니에요. 그래서 이런 클릭은 분석 도구의 사용자 지정 이벤트로만 세고, 제품 이벤트 테이블에는 넣지 않았어요. 모든 클릭을 제품 이벤트로 보내면 이벤트를 받는 곳의 제한을 금방 채워서 정작 중요한 이벤트를 놓쳐요. 가설의 숫자는 하나, 곧 출처 줄과 공유로 들어온 방문이에요. 그 숫자는 유입 화면이 이미 세고 있으니 같은 것을 두 번 세지 않아요.

참고

어떤 환경에서는 링크를 누른 방문이 유입 정보 없이 직접 방문으로 잡힐 수 있어요. utm 값이 주소에 있으면 대부분 구분되지만, 숫자를 절대값으로 믿지 말고 흐름을 보는 용도로 쓰세요.

자주 묻는 질문

Q1. 워터마크와 출처 줄은 뭐가 달라요?

워터마크는 보통 쪽 위에 크게 겹쳐 찍어서 서류 내용을 가리거나 읽기 불편하게 만들어요. 출처 줄은 마지막 쪽 아래 여백에 한 줄만 작게 두고, 사용자가 끌 수 있는 선택지가 있다는 점이 달라요. 사진이나 도장 이미지에 워터마크를 넣는 건 서명 같은 신뢰가 중요한 서류에서는 특히 피하는 게 좋아요.

Q2. 줄을 켜 둔 게 사용자에게 불편하지 않을까요?

불편할 수 있어서 끄는 길을 같은 화면에 둔 거예요. 끄는 비율을 세어 두고 절반을 넘으면 기본값을 뒤집겠다는 기준을 정해 둔 것도 같은 이유예요. 신뢰가 깨지면 한 줄이 주는 유입보다 잃는 게 더 커요.

Q3. 법적으로 문제가 없나요?

서류에 도구의 이름을 한 줄 남기는 것이 곧바로 문제가 되지는 않아요. 다만 계약서처럼 내용이 중요한 서류에 사용자가 모르는 문구가 들어가는 건 사용자의 신뢰 문제가 될 수 있어요. 그래서 문구를 짧게 하고, 본문이 아닌 여백에만 두고, 끌 수 있게 하는 세 가지를 같이 지켜야 해요. 구체적인 법률 판단이 필요하면 전문가에게 확인하세요.

정리

  • 출처 줄은 서류 본문이 아니라 마지막 쪽 아래 여백에, 한 줄만 작게 넣어요.
  • 영문 기본 글꼴을 쓰면 파일이 커지지 않지만, 줄에 한글을 섞을 수 없어요.
  • 글자만으로는 링크가 되지 않으니 링크 주석을 따로 올리고, 이미 있는 주석 목록은 덮어쓰지 말고 덧붙여요.
  • 쪽 회전 값과 보이는 영역 상자를 반영해서 위치를 계산해야 돌아간 쪽에서도 똑바로 보여요.
  • 끄는 스위치, 기억하는 선택, 줄이 실패해도 서류는 나오는 구조, 끈 비율의 기준을 한 세트로 만들어요.
  • 링크에 출처별 utm 꼬리표를 붙이면 유입 화면에서 바로 나눠 볼 수 있어요.

사용자가 만든 서류에 도구의 흔적을 남기는 일은 넣는 기술보다 신뢰를 지키는 설계가 더 어려워요. 저희 서명 서비스는 이미지 서명을 PDF 에 붙이고 내려받는 흐름 끝에서 이 출처 줄과 끄는 스위치를 함께 제공해요.

이어서 읽기 좋은 글