「コード」를 한 번에 한 글자씩, 매 호출을 직전보다 한 엠 아래로 내려 그리며 페이지를 따라 내려가게 하면 다섯 글리프 중 셋이 잘못 나옵니다. 두 모서리 괄호는 가로줄에 앉는 방식 그대로 열린 쪽을 옆으로 향한 채 칼럼에 앉고, 장음 부호 ー는 일본어 독자가 짧은 세로 획을 기대하는 자리에서 칼럼을 가로지르는 납작한 대시가 됩니다. 평범한 가타카나 두 글자인 コ와 ド만이 조판사가 조판한 모양으로 보입니다
줄 전체를 90도 돌리면 괄호와 장음 부호가 어느 정도 우연히 맞아 보이지만 나머지 전부가 깨집니다. 모든 한자와 가나가 옆으로 눕습니다. 세로쓰기는 세 가지를 동시에 합니다. 글자는 세워진 채로 있고, 어드밴스는 옆이 아니라 페이지 아래로 흐르며, 소수의 글자는 다른 그림으로 갈아탑니다. 세 가지를 모두 맞추려면 세로 폼과 세로 메트릭을 지닌 폰트, 그리고 그것을 쓰도록 준비된 PDF 폰트가 필요합니다
PDF Library for Delphi에서 세로쓰기 켜기
PDF Library for Delphi는 세로쓰기를 드로잉 모드로 다룹니다. SetVerticalWritingMode(1)이 이를 켜면 그 뒤의 모든 DrawText 호출이 주어진 지점에서 페이지 아래로 흐르고, SetVerticalWritingMode(0)이 가로 텍스트를 되돌려 줍니다. 세터는 호출 이전에 유효하던 모드를 돌려주고 GetVerticalWritingMode는 현재 모드를 읽으므로, 헬퍼 루틴은 모드를 바꾼 뒤 나중에 발견한 값을 정확히 되돌려 놓을 수 있습니다
두 좌표는 가로 텍스트와 조금 다른 의미를 지닙니다. x 값은 칼럼의 중심선을 가리킵니다. 각 글리프는 수평 중심이 그 선에 걸린 채 매달리므로, 좁은 구두점과 전각 한자가 같은 축 위에 정렬됩니다. y 값은 첫 글자 셀의 꼭대기이고 칼럼은 거기서 아래로 자랍니다. y = 0을 페이지 위쪽 가장자리에 두는 SetOrigin(1) 아래에서는 계산이 종이 위에 페이지를 깔아 놓은 방식 그대로 읽힙니다
var
Lib: TPDFlib;
H: Double;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.AddTrueTypeFont('MS Mincho', 1); // 1 = 페이스를 임베드
Lib.SetTextSize(12);
Lib.SetVerticalWritingMode(1); // 이제 평범한 DrawText가 아래로 흐른다
H := Lib.GetVerticalTextHeight('MS Mincho', 12, '第三章 保守点検');
Lib.DrawText(480, 72, '第三章 保守点検'); // 칼럼은 y = 72에서 시작한다
Lib.SetVerticalWritingMode(0); // 가로로 되돌림
// Origin 1은 y = 0을 위에 두므로 72 + H가 칼럼의 발이고,
// 가로 캡션은 그보다 한 줄 아래에 앉는다
Lib.DrawText(456, 72 + H + 18, 'Chapter 3');
Lib.SaveToFile('manual-ja.pdf');
finally
Lib.Free;
end;
end;
GetVerticalTextHeight는 페이스 이름, 크기, 텍스트를 받아 칼럼 길이를 포인트로 돌려줍니다. 예제에서 72 + H는 칼럼의 발이고 영어 캡션은 그보다 18포인트 아래에서, 칼럼 중심선의 왼쪽 24포인트 지점부터 시작해 짧은 라틴 줄이 대략 칼럼 아래에 앉게 합니다. 이 함수는 그 이름으로 페이스를 열 수 없을 때 0을 돌려주는데, 높이 0 위에 세워진 레이아웃은 캡션을 칼럼 위에 얹어 버리므로 값을 쓰기 전에 검사하십시오
세로 칼럼에서 괄호가 옆으로 눕는 이유는?
그런 글자마다 폰트가 두 개의 그림을 지니고 가로쓰기용이 기본이기 때문입니다. OpenType 페이스는 vert라는 GSUB 기능에 대체 글리프를 나열하며, 세로 조판용으로 이미 회전된 글리프를 실은 페이스에서는 vrt2입니다. 텍스트 줄을 셰이핑하면 가로쓰기 글리프가 돌아오고, 세로 기능을 그 뒤에 글리프 대 글리프로 적용하는 주체가 필요합니다. 모서리 괄호, 괄호, 장음 부호는 칼럼을 따라 흐르는 방향으로 돌아서고, 작은 가나와 、나 。같은 구두점은 자기 네모의 다른 구석으로 옮겨 갑니다
PDF Library for Delphi는 그리기에 쓰이는 페이스에서 그 기능을 읽어 각 글리프를 폰트가 지목하는 세로 폼으로 교체합니다. 페이스가 두 기능을 모두 지니면 vrt2가 이기고 vert는 무시됩니다. 규격은 vrt2가 vert를 대체하도록 의도하며, 둘을 모두 읽으면 어느 룩업이 먼저 답하는지에 따라 한 글자에 서로 다른 두 세로 글리프가 붙을 수 있습니다. 둘 다 없는 페이스는 평범한 글리프를 유지합니다. 우리는 이 결과를 내장 대체 테이블보다 선호합니다. 추측으로 만든 테이블은 흔한 글자는 맞추지만, 설계자가 다르게 취급한 글자를 지닌 페이스를 조용히 덮어쓰기 때문입니다
Windows와 함께 배포되는 사본들의 GSUB 테이블을 덤프해 보면 숫자가 크게 갈립니다. MS Gothic의 세로 기능은 979개 글리프를, Yu Gothic은 2,491개를, SimSun은 72개를 매핑하고, Arial은 전혀 없습니다(정확한 수치는 폰트 버전에 따라 움직입니다). 우연히 가나 몇 개를 담고 있는 라틴 페이스는 모든 괄호와 장음 부호가 가로쓰기 폼인 채로 세워진 칼럼을 그립니다. 폰트 안에 대체해 줄 것이 없기 때문입니다
대체는 측정도 바꿉니다. 세로 폼은 자신이 대체하는 가로쓰기 글리프와 다른 어드밴스를 가질 수 있으므로, GetVerticalTextHeight는 어드밴스를 더하기 전에 같은 대체를 적용합니다. 가로쓰기 폼을 측정하고 세로쓰기 폼을 그리면 칼럼은 괄호 하나당 몇 포인트씩 여러분이 잡아 둔 상자를 넘어섭니다
이 모든 것은 텍스트 자체에는 손대지 않습니다. 뷰어에서 칼럼을 복사하면 「コード」가 돌아옵니다. 각 세로 글리프가 여러분이 넘긴 문자로 매핑되기 때문입니다. 검색과 텍스트 추출은 회전된 표현 폼이 아니라 여러분의 원본 문자열을 봅니다
세로 메트릭이 PDF에 담기는 과정
세로 구간의 각 글자는 폰트 자체의 세로 메트릭이 주는 거리만큼 나아갑니다. 출력을 PDF 인스펙터로 열면 Identity-V 인코딩의 Type0 폰트를 찾을 수 있는데, 그 CIDFont는 사용된 모든 글리프에 W2 항목을 지닙니다. 페이스의 vmtx 테이블에서 온 세로 어드밴스, 칼럼 중심선으로의 오프셋인 글리프 폭의 절반, 그리고 세로 원점입니다. ISO 32000-1 §9.7.4.3이 이 세 숫자를 정의합니다. 세로 메트릭이 없는 페이스에는 W2 배열이 없고, DW2 기본값이 모든 글자를 정확히 한 엠씩 나아가게 합니다
세로 원점은 서드파티 폰트들이 서로 어긋나는 지점입니다. vhea 테이블에는 자명한 원점처럼 보이는 ascender와 descender 필드가 있지만, descender의 부호 관례는 페이스마다 공유되지 않습니다. 아무 폰트 테이블 뷰어로 Windows 사본들의 이 필드를 읽으면 MS Gothic은 +140, Yu Gothic은 +120, 반면 SimSun은 −144, MingLiU는 −500이 나오고, 값은 폰트 버전에 따라 바뀔 수 있습니다. 그 필드에서 유도한 위치는 페이스마다 제각각으로 흘러갑니다. PDF Library for Delphi는 위에 언급한 CJK 페이스들이 일관되게 보고하는 세로 ascender에서 원점을 가져가고, 페이스가 그럴듯한 값을 보고하지 않으면 880유닛, 즉 PDF 규격 자체가 DW2에 주는 기본값으로 폴백합니다
세로 글리프를 손으로 배치하는 데도 자체 함정이 있습니다. 가로쓰기 텍스트는 간격을 TJ 배열 안의 숫자로 맞추는 일이 흔합니다. 세로쓰기 모드에서는 ISO 32000-1 §9.4.4가 그 숫자들을 칼럼 방향에 적용하므로, 가로 간격용으로 계산된 보정은 글자를 페이지의 더 아래로 밀어냅니다. 그래서 콘텐츠 스트림의 세로 구간은 조정 없이 글리프 코드만 나열한 평범한 문자열 하나이며, 간격의 모든 부분은 W2에서 나옵니다
출력을 검증할 때는 딕셔너리가 아니라 잉크를 보십시오. 폰트에서 /Identity-V를 발견한 것은 인코딩이 선언되었다는 증거일 뿐, 텍스트가 실제로 어느 방향으로 흐르는지는 말해 주지 않습니다. 페이지를 렌더링하고 그려진 픽셀의 바운딩 박스를 측정해야 알 수 있습니다. 일곱 글자짜리 구간은 대략 한 글자 폭에 일곱 글자 높이로 나와야 합니다
가로 페이지 위의 세로 구간 하나
DrawVerticalText는 구간 하나를 세로로 그리고 쓰기 모드는 그대로 둡니다. 위치, 폰트 이름, 크기, 텍스트를 인자로 받으므로 현재 폰트 선택도 건드리지 않습니다. 책등 라벨, 인장, 표 옆의 이름 칼럼 하나 — 가로 문서 안의 세로 예외들이며, 그때마다 전역 모드를 켰다 껐다 하는 것은 이 작업이 감당할 이상의 상태 관리입니다
// 가로로만 이뤄진 페이지 안의 세로 구간 하나
if Lib.DrawVerticalText(520, 96, 'MS Mincho', 14, '保守点検記録') = 0 then
begin
// 페이스를 열 수 없었거나 이 텍스트를 설정하지 못했습니다.
// LogLayoutProblem은 여러분 자신의 루틴이지 라이브러리의 일부가 아닙니다
LogLayoutProblem('vertical label on page ' + IntToStr(Lib.SelectedPage));
end;
// 주변의 가로 텍스트는 영향을 받지 않습니다
Lib.DrawText(72, 96, 'Maintenance inspection record');
양방향을 섞는 페이지는 같은 폰트 프로그램에서 만들어진 페이스를 리소스에 두 번 담습니다. 가로 인코딩으로 한 번, Identity-V로 한 번입니다. 각 방향은 자기 메트릭을 유지하므로, 세로 본문과 가로 러닝헤드를 지닌 일본어 서적 페이지나 가로 세부 줄 위에 세로 제목이 놓인 중국어 증서도 양방향 모두 올바른 간격으로 나옵니다
반환 값이야말로 대부분 세로인 문서에서도 DrawVerticalText에 손을 뻗는 이유입니다. 모드 기반 그리기에서 세로로 조판할 수 없는 구간은 버려지는 대신 평범한 가로 방식으로 그려지고, DrawText는 어느 쪽이든 성공을 보고합니다. 본문 텍스트라면 그것이 더 나은 기본값입니다. 엉뚱한 가로 줄은 교정에서 금방 눈에 띄지만 사라진 단락은 그렇지 않습니다. 방향 자체가 존재 이유인 라벨이라면 DrawVerticalText를 쓰고 0은 레이아웃 실패로 취급하십시오
칼럼을 오른쪽에서 왼쪽으로 배치하기
칼럼 순서는 여러분이 정합니다. 일본어와 전통 중국어 세로쓰기는 오른쪽에서 왼쪽으로 칼럼 단위로 읽히므로, 페이지는 오른쪽 여백에서 시작하고 새 칼럼은 이전 칼럼의 왼쪽에 앉습니다. 어느 쓰기 모드 설정도 텍스트만 보고 그것을 추론하지 못하며, 오른쪽으로 제본한 책의 페이지 순서나 후리가나, 각주, 그림 캡션이 어디에 속하는지도 마찬가지입니다
아래 루틴은 문자열을 고정 높이에 맞는 칼럼들로 나누면서, 진행하면서 GetVerticalTextHeight로 측정하고 칼럼당 1.5엠씩 왼쪽으로 이동합니다:
procedure DrawColumns(Lib: TPDFlib; const Face: WideString; Size: Double;
const Text: WideString; Right, Top, BoxH: Double);
var
Col, Trial: WideString;
I: Integer;
X: Double;
begin
// 권장 가드이지 라이브러리 검사가 아닙니다. 높이 0은 페이스를
// 열지 못했다는 뜻이며, 아래 루프는 칼럼을 끊지 못합니다
if Lib.GetVerticalTextHeight(Face, Size, Text) <= 0 then
Exit;
X := Right; // 첫 칼럼은 오른쪽 여백에서
Col := '';
for I := 1 to Length(Text) do // 서러게이트 쌍이 없다고 가정
begin
Trial := Col + Text[I];
if (Col <> '') and (Lib.GetVerticalTextHeight(Face, Size, Trial) > BoxH) then
begin
Lib.DrawVerticalText(X, Top, Face, Size, Col);
X := X - Size * 1.5; // 다음 칼럼은 왼쪽으로
Col := Text[I];
end
else
Col := Trial;
end;
if Col <> '' then
Lib.DrawVerticalText(X, Top, Face, Size, Col);
end;
글자마다 시험 문자열을 다시 측정하는 것은 어드밴스를 합산하는 것보다 느리지만, 대체를 포함해 실제로 그려질 것을 정확히 측정합니다. 이 루프가 하지 않는 것은 금자(禁則) 규칙의 적용입니다. 칼럼 꼭대기의 닫는 괄호나 。, 칼럼 발에 홀로 남은 여는 괄호는 일본어 독자에게 조판 오류이며, 고치려면 그리기 전에 글자 하나를 행바꿈 지점을 넘어 당기거나 밀어야 합니다. 서러게이트 쌍도 같은 주의가 필요합니다. Text[I] 인덱싱은 기본 다국어 평면 밖의 글자를 반으로 가르므로, 텍스트에 그런 글자가 있을 수 있다면 코드 포인트 단위로 진행하십시오
데이터로 조립하는 문서라면 텍스트 검색과 페이지 요소 열거 산책문이 각 페이지에 무엇이 떨어졌는지 읽어 내는 방법을 보여 줍니다. 모든 칼럼이 의도한 글자를 담고 있는지 빠르게 확인하는 길입니다
세로 문서는 어떤 폰트를 써야 할까?
자체 세로 메트릭과 vert 또는 vrt2 기능을 지닌 CJK 페이스입니다. 없다면 글자는 한 엠씩 세워진 채 나아가고 폼 변화는 없습니다. 세로 텍스트가 미묘하게 이상해 보이면 코드보다 폰트를 먼저 살펴보십시오. 페이스의 GSUB 기능과 vhea, vmtx 테이블을 나열해 주는 테이블 뷰어면 그 페이스가 이 일을 할 수 있는지 1분 안에 알려 줍니다
DrawVerticalText와 GetVerticalTextHeight는 설치된 페이스를 Windows를 통해 이름으로 엽니다. 다른 플랫폼용 Lazarus 빌드에서는 세로쓰기가 SetVerticalWritingMode와 DrawText를 거치는데, 이들은 여러분이 로드해 선택한 폰트 프로그램으로 동작하므로 그 폰트가 의도한 CJK 페이스이기만 하면 같은 세로 폼과 메트릭이 적용됩니다
임베딩은 익숙한 규칙과 익숙한 비용을 따릅니다. 온전한 CJK 페이스는 수 메가바이트에 이르므로, 여러분의 기계를 떠나는 문서에는 서브셋팅이 합리적인 기본값입니다. PDF 파일 크기 최적화와 폰트 서브셋팅 노트가 기대치를 다루고, 기존 PDF에 없는 폰트 임베드하기 산책문은 폰트 없이 도착한 세로 문서의 수리 사례를 다룹니다
세로 CJK 쓰기는 PDF Library for Delphi의 나머지 텍스트 함수와 같은 드로잉 API 안에 있습니다. Delphi, C++Builder, Lazarus용 네이티브 Pascal PDF 라이브러리입니다. PDF Library for Delphi 제품 페이지에 폰트와 텍스트 기능 전체가 함께 정리되어 있습니다