같은 한 쪽짜리 LaTeX 문서를 컴파일하면 DVI는 956바이트, 그것을 dvips로 변환한 PostScript는 169,769바이트가 됩니다. 178배입니다. 늘어난 분량 가운데 새로운 정보는 한 바이트도 없습니다. 차이는 오직 하나, DVI는 아홉 개의 글꼴을 이름으로 부를 뿐이지만 PostScript는 그 아홉 개를 통째로 품고 있다는 점입니다. 이 사실 하나가 DVI라는 형식의 성격도, PostScript가 오랫동안 중간 기착지였던 이유도, 오늘날 PDF로 가는 세 경로의 차이도 거의 다 설명해 줍니다.
DVI 파일 안에 실제로 들어 있는 것: 상자와 글꼴 이름
DVI에 들어 있는 것은 위치와 글꼴 참조뿐이며, 글자 모양은 하나도 없습니다. TeX Live에 딸려 오는 dvitype으로 읽어 보면 그 사정이 그대로 드러납니다. fntdef1 27: cmr10은 27번 글꼴을 cmr10으로 삼겠다는 선언일 뿐이고, setchar49는 지금 선택된 글꼴의 49번 문자를 여기 놓으라는 명령일 뿐입니다. 49는 문자 코드로 숫자 1입니다. 사이에 낀 push, down4, right3은 현재 위치를 옮기는 조작입니다. 즉 DVI는 “어디에, 어느 글꼴의, 몇 번 문자를 놓을지”를 늘어놓은 지시서이며, 그 문자가 실제로 어떻게 생겼는지는 전혀 알지 못합니다.
dvitype -output-level=4 -page-start='*' doc.dvi
# numerator/denominator=25400000/473628672
# magnification=1000; 0.00006334 pixels per DVI unit
# Postamble starts at byte 723.
# Font 27: cmr10---loaded at size 655360 DVI units
# Font 37: cmbx12 scaled 1200---loaded at size 943718 DVI units
# (this font is magnified 120%)
# 145: fntdef1 37: cmbx12
# 167: fntnum37 current font is cmbx12
# 168: setchar49 h:=4063232+530841=4594073, hh:=291이것은 “DVI가 작은 이유”인 동시에 “DVI만으로는 볼 수 없는 이유”이기도 합니다. 956바이트짜리 파일에 strings를 걸면 읽히는 것은 본문의 낱말과 cmr10, cmmi7, cmsy10, cmex10 같은 글꼴 이름의 나열뿐입니다. 그래서 DVI를 건네받은 쪽은 같은 이름의 글꼴을 자기 환경에서 찾지 못하면 그 쪽을 재현할 수 없습니다. 1980년대에 노린 것이 바로 이 분업이었습니다. 조판 결과는 장치에 의존하지 않는(device-independent) 중간 형식에서 멈추고, 실제 종이나 화면에 맞추는 일은 뒤에 오는 드라이버에 맡기는 것입니다. DVI라는 이름 자체가 그 설계 방침입니다.
| DVI에 있음 / 없음 | 내용 |
|---|---|
setchar / put | 문자 번호 지정. 글자 모양 자체는 없습니다 |
fntdef / fntnum | 글꼴을 이름・디자인 크기・체크섬으로 참조할 뿐입니다 |
push / pop / down / right | 상자를 쌓는 좌표 조작. 조판 결과 그 자체입니다 |
xxx (\special) | DVI가 표현하지 못하는 지시의 탈출구. 내용은 드라이버가 해석합니다 |
glyph outlines | 없습니다. 그래서 글꼴이 없으면 표시할 수 없습니다 |
color, paper size | DVI 자체에는 없습니다. 둘 다 \special로 실려 갑니다 |
DVI는 왜 모든 것을 1/65536 포인트로 재는가
답은 dvitype 출력 맨 앞에 그대로 적혀 있습니다. numerator/denominator=25400000/473628672의 분모 473628672는 65536 × 7227이고, 7227포인트는 정확히 100인치입니다. 즉 DVI의 1단위는 1/65536 포인트이며, 이는 TeX이 내부에서 쓰는 정수 단위인 스케일드 포인트(sp) 와 같습니다. 그래서 앞의 cmr10---loaded at size 655360 DVI units는 10 × 65536, 곧 10pt를 그렇게 적은 것일 뿐이고, cmbx12 scaled 1200이 알리는 943718은 12pt × 1.2 = 14.4pt에 65536을 곱한 값입니다. TeX은 부동소수점을 전혀 쓰지 않고 이 정수만으로 판면을 짭니다. 같은 원고가 어느 기계에서든 1 sp 단위까지 같은 결과가 되는 이유가 여기에 있습니다.
이 형식을 설계한 사람은 커누스의 학생이던 David R. Fuchs로, 1979년의 일입니다. 규격은 1982년 10월 TUGboat 제3권 2호에 「The format of TeX’s DVI files」로 발표되었습니다. 40년이 넘은 지금도 doc.dvi의 첫 바이트는 247(pre 명령), 그다음 바이트는 2(DVI 식별 번호) 그대로입니다. 호환성이 끊기지 않은 것은 이 형식이 장치에 대해 아무것도 가정하지 않았기 때문입니다. 당시 상정했던 출력 기계는 진작 사라졌지만, 아무것도 상정하지 않은 부분만은 남았습니다.
PostScript가 오랫동안 중계지였던 이유: \special 안을 들여다보기
DVI는 장치에 의존하지 않는 대신 색도, 이미지도, 회전도 표현하지 못합니다. 그래서 Fuchs는 탈출구를 딱 하나 마련했습니다. 바로 \special{...}로, 내용은 해석하지 않고 드라이버에 그대로 넘깁니다. 그리고 이 탈출구를 가장 먼저 채운 것이 PostScript였습니다. \usepackage{graphicx}를 불러 \rotatebox{30}{rotated}라 쓰고 latex으로 DVI를 만든 뒤 들여다보면, 날것의 PostScript가 그대로 들어 있습니다. DVI는 그것이 PostScript라는 사실조차 모릅니다. 그저 문자열을 나를 뿐입니다.
# what latex actually wrote into the DVI for \rotatebox and \textcolor
dvitype -output-level=4 -page-start='*' spec.dvi | grep xxx
# 88: xxx 'header=l3backend-dvips.pro'
# 116: xxx 'papersize=614.295pt,794.96999pt'
# 247: xxx 'color push rgb 1 0 0'
# 347: xxx 'ps: gsave currentpoint currentpoint translate
# 30 neg rotate neg exch neg exch translate'
# 449: xxx 'ps: currentpoint grestore moveto'그다음부터는 외길이었습니다. DVI의 탈출구가 PostScript로 채워진 이상, 그 DVI를 끝까지 해석할 수 있는 것은 PostScript를 이해하는 장치뿐입니다. 1980년대 중반 Adobe의 PostScript를 얹은 레이저 프린터가 사실상의 표준이 되자, dvips가 DVI를 PostScript로 옮기는 경로가 자리를 잡았습니다. 옮기면서 dvips는 글꼴을 끼워 넣습니다. 생성된 .ps 앞머리에는 %%DocumentFonts: CMBX12 CMR10 CMEX10 CMSY7 CMR7 CMMI10 CMMI7 CMR5 CMSY10라는 줄이 있고, 본문에는 그 아홉 개 각각의 %%BeginFont: 블록이 이어집니다. 이 페이지 첫머리의 956바이트와 169,769바이트의 차이는 바로 이 아홉입니다.
PostScript는 무대에서 물러났지만 지문은 곳곳에 남아 있습니다. DVI에서 곧장 PDF를 만드는 dvipdfmx 실행 파일 안에는 인라인 PostScript를 실행하는 작은 인터프리터가 들어 있고, Stack not empty after execution of inline PostScript code. 라는 진단 메시지까지 갖추고 있습니다. 앞의 ps: 특수 명령이 이 경로에서도 실제 회전으로 반영되는 이유가 그것입니다. --help에는 -D template PS->PDF conversion command line template [none] 항목도 있어, 감당하지 못하는 PostScript는 Ghostscript 같은 외부 프로그램에 넘기도록 설계되어 있습니다. 나아가 이미지 크기를 기록하는 .xbb 파일의 내용은 %%BoundingBox: 0 0 8 8 — PostScript의 주석 표기 그대로입니다.
오늘날의 세 경로와 각각의 대가
서양어라면 pdflatex 등으로 곧장 PDF, 일본어라면 (u)platex → dvipdfmx, PSTricks나 오래된 EPS 뭉치를 안고 있다면 latex → dvips → ps2pdf. 실무의 갈림길은 이것이 전부입니다. 다만 “같은 문서니 같은 PDF가 나온다”는 말은 사실이 아닙니다. 이 페이지 첫머리의 문서를 세 경로로 각각 PDF로 만들자 direct.pdf는 85,509바이트, viadvi.pdf는 14,693바이트, viaps.pdf는 17,851바이트가 되었습니다. pdffonts로 들여다보면 이유가 곧 드러납니다. pdfTeX은 Type 1 그대로 심고, dvipdfmx와 Ghostscript는 압축 형식인 Type 1C로 바꾸어 심기 때문입니다. 지면은 똑같아 보이는데 용량만 여섯 배 차이가 납니다.
| 경로 | 거치는 형식 | 유리한 상황 |
|---|---|---|
pdflatex / lualatex | .tex → PDF | 한 단계로 끝납니다. 서양어의 기본 |
xelatex | .tex → XDV → PDF | 시스템 글꼴. XDV 단계는 숨겨져 있을 뿐입니다 |
dvipdfmx | .tex → DVI → PDF | 일본어((u)pLaTeX)의 정석. PNG・JPEG도 바로 넣을 수 있습니다 |
dvips + ps2pdf | .tex → DVI → PS → PDF | PSTricks, EPS 자료 일습, PostScript를 전제한 입고 |
# 1. straight to PDF
pdflatex doc.tex
# 2. via DVI (the Japanese route)
uplatex doc.tex && dvipdfmx doc
# doc.dvi -> doc.pdf
# [1]
# 14692 bytes written
# 3. via PostScript
latex doc.tex && dvips doc -o doc.ps && ps2pdf doc.ps
# in practice latexmk drives all three for youXeLaTeX은 정말로 DVI를 건너뛰는가
건너뛰지 않습니다. xelatex -no-pdf doc.tex을 돌리면 Output written on doc.xdv (1 page, 2476 bytes). 가 나오고 doc.xdv가 남습니다. 그 첫 바이트를 보면 247 — doc.dvi와 똑같은 pre 명령입니다. 다른 것은 다음 한 바이트뿐으로, DVI는 2인데 XDV는 7입니다. 즉 XeTeX이 쓰는 것은 확장 DVI(XDV) 이고, 그것을 PDF로 만드는 것은 xdvipdfmx입니다. TeX Live에 딸린 README에도 “In the installation, dvipdfmx is a symlink to xdvipdfmx.” 라고 적혀 있고, 실제로 dvipdfmx도 extractbb도 같은 실행 파일을 가리킵니다. “PDF를 직접 출력한다”는 말은 DVI 단계를 감췄다는 뜻이지, 없앴다는 뜻이 아닙니다.
xelatex -no-pdf doc.tex
# Output written on doc.xdv (1 page, 2476 bytes).
# same container, different id byte:
# doc.dvi byte 0 = 247 (pre) byte 1 = 2
# doc.xdv byte 0 = 247 (pre) byte 1 = 7
xdvipdfmx doc.xdv # this is what xelatex runs for youCannot determine size of graphic ... (no BoundingBox) 가 나오면
DVI 경로에서 PNG나 JPEG를 넣으려는 사람이 가장 먼저 부딪치는 벽입니다. 메시지 전문은 ! LaTeX Error: Cannot determine size of graphic in sample.png (no BoundingBox). 이고, 원인은 이미지도 dvipdfmx도 아니라 graphicx의 드라이버 설정에 있습니다. latex은 PDF를 만들지 않으므로 이미지 치수를 스스로 읽을 능력이 없습니다. 기본 드라이버인 dvips는 PostScript의 %%BoundingBox 줄만 읽을 수 있고 PNG 헤더는 읽지 못합니다. 해결책은 둘입니다. \usepackage[dvipdfmx]{graphicx}처럼 드라이버를 명시하거나, extractbb sample.png를 먼저 돌려 %%BoundingBox: 0 0 8 8 이 적힌 .xbb 파일을 만들어 두는 것입니다. pdflatex에서 이 오류가 나지 않는 것은 pdfTeX 자체가 PNG 헤더를 읽을 수 있기 때문일 뿐입니다.
# ! LaTeX Error: Cannot determine size of graphic in sample.png (no BoundingBox).
# fix 1 - name the driver in the preamble:
# \usepackage[dvipdfmx]{graphicx}
# fix 2 - write the bounding box out first:
extractbb sample.png
cat sample.xbb
# %%Title: sample.png
# %%Creator: extractbb 20240305
# %%BoundingBox: 0 0 8 8
# %%HiResBoundingBox: 0.000000 0.000000 8.000000 8.000000정리하면 판단의 순서는 이렇습니다. 먼저 출력 형식이 아니라 언어와 패키지로 정합니다. 일본어라면 (u)platex → dvipdfmx, PSTricks가 있으면 PostScript 경유, 그 밖에는 곧장 PDF입니다. 다음으로 이미지 형식을 맞춥니다. DVI 경로라면 PDF나 EPS, 또는 extractbb를 거친 PNG・JPEG입니다. 마지막으로 드라이버 이름을 명시합니다. graphicx에도 hyperref에도 [dvipdfmx]를 적어 두면, 훗날 그 문서를 다른 경로로 빌드하는 사람이 같은 함정을 밟지 않습니다. 그동안에도 DVI 쪽은 자신이 무슨 색의 어떤 그림을 나르고 있는지 여전히 알지 못합니다.