기술 문서

PDFlibPas의 Delphi 하이픈 처리와 균형 잡힌 텍스트 열

PDFlibPas는 DrawTextFlowColumns로 보관된 문단을 1개에서 64개까지의 동일한 너비 열로 흘려보내며, SetTextFlowLanguageSetTextFlowHyphenation을 호출하면 제한된 언어 인식 하이픈 처리로 줄을 나눕니다. 아홉 개 언어가 지원되며, 언어는 플로우마다 설정하는 대신 문서 Catalog의 /Lang 값에서 상속받을 수도 있습니다

두 기능 모두 같은 이유로 존재합니다. 좁은 열은 순진한 줄바꿈이 조판처럼 보이기를 멈추고 버그 보고서처럼 보이기 시작하는 지점입니다

양쪽 정렬된 텍스트는 왜 좁은 열에서 무너지는가?

양쪽 정렬은 남는 공간을 한 줄의 단어 사이 간격에 분배하며, 남는 양은 무엇이 들어맞는지에 달려 있기 때문입니다. 넓은 측정값(measure)에서는 남는 공간이 작아서 눈에 전혀 띄지 않습니다. 너비를 절반으로 줄이면, 들어맞지 않는 긴 단어 하나가 다음 줄로 밀려나면서 그 앞의 단어들이 그 공간 전부를 흡수하게 됩니다. 이런 줄이 연속으로 세 번 나오면 조판가들이 리버(river)라고 부르는 수직 흰 통로가 생기고, 독자는 왜 그런지 모른 채 따라가기 어려운 텍스트로 이를 경험하게 됩니다

하이픈 처리는 단어 안쪽에서의 줄바꿈을 허용함으로써 증상이 아니라 원인을 고칩니다. 독일어와 네덜란드어의 복합어는 이를 타협 불가능하게 만듭니다. 60밀리미터 열 안의 24자짜리 명사는 줄바꿈 지점 없이는 좋은 결과가 나올 수 없습니다. 영어는 그 부재를 더 잘 견디는데, 이것이 영어 우선 제품이 독일 고객이 처음 돌려볼 때 무너지는 레이아웃 코드를 종종 출시하는 이유입니다

어떤 언어가 지원되며, 언어는 어디서 오는가?

하이픈 처리는 영어, 독일어, 네덜란드어, 프랑스어, 스페인어, 이탈리아어, 포르투갈어, 러시아어, 터키어를 포괄합니다. SetTextFlowLanguage로 플로우마다 명시적으로 설정하거나, 태그되어 접근 가능한 문서라면 이미 지니고 있을 문서 Catalog의 /Lang 항목에서 상속받도록 두십시오

이 상속은 재정의하기보다는 활용할 가치가 있습니다. Catalog에서 언어를 선언하는 문서는 스크린 리더, 검색 색인기, 하이픈 처리에 같은 사실을 한 곳에서 알려주는 것이며, 사실은 한 곳에 살아야 마땅합니다. 접근 가능한 PDF를 위한 자동 태깅에서 설명하는 대로 이미 태그된 출력을 생성하고 있다면, 언어 항목은 이미 설정되어 있고 플로우는 그저 그것을 따르기만 하면 됩니다

var
  Lib: TPDFlib;
  Flow, Drawn: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.AddTrueTypeFont('Georgia', 1);
    Lib.SetTextSize(10.5);

    Flow := Lib.NewTextFlow(ArticleBody);
    try
      Lib.SetTextFlowLanguage(Flow, 'de');
      // 활성화, 줄바꿈 앞에 최소 3자, 뒤에 3자
      Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);
      Lib.SetTextFlowMinLines(Flow, 2);   // 외톨이 줄이 남지 않도록

      repeat
        // 480pt 영역에 3열, 18pt 거터, 균형 처리
        Drawn := Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);
        if (Drawn = 0) or (Lib.TextFlowFinished(Flow) = 1) then
          Break;
        Lib.NewPage;
      until False;
    finally
      Lib.ReleaseTextFlow(Flow);
    end;

    Lib.SaveToFile('newsletter.pdf');
  finally
    Lib.Free;
  end;
end;

MinPrefix와 MinSuffix는 검증이 아니라 타이포그래피다

활성화 플래그 뒤의 두 정수는 줄바꿈 앞뒤로 남아야 하는 최소 문자 수를 설정합니다. 3과 3은 대부분의 하우스 스타일이 받아들이는 보수적인 기본값입니다. 2와 2는 줄바꿈 기회를 더 많이 만들어내지만 눈에 띄게 못생긴 결과를 낳는데, 줄 끝에 매달린 두 글자짜리 조각은 오타처럼 읽히기 때문입니다

글자가 커서 각 조각이 시각적으로 두드러지는 경우에는 최솟값을 올리고, 열이 정말로 좁아서 깔끔함보다 빡빡한 측정값이 더 중요하다고 판단했을 때만 낮추십시오. 이는 기술적 결정이라기보다는 하우스 스타일의 결정이며, 바로 그래서 상수가 아니라 매개변수로 되어 있습니다

여기서 "균형"은 실제로 무엇을 뜻하는가?

Balance 매개변수는 문단이 끝나는 지점에서만 동작을 바꿉니다. 균형 처리를 켜면, 남은 내용이 전부 영역 안에 들어맞을 때 열들이 정확히 같은 줄 수로 줄어드는데, 이것이 마지막 페이지가 꽉 찬 열 두 개와 외로운 줄 하나만 담은 세 번째 열을 보여주는 것을 막아줍니다. 문단이 다 들어맞지 않으면 모든 열은 전체 높이를 유지하여 페이지가 담을 수 있는 만큼의 텍스트를 담고 나머지는 다음 페이지로 이어집니다

이 비대칭성은 연속적인 문서에 대한 올바른 기본값입니다. 흐르는 기사 중간에 균형을 맞추면 어차피 열이 꽉 차 있으므로 아무도 보지 못할 장식적 효과를 위해 모든 페이지에서 수직 공간을 낭비하게 될 것입니다. 눈이 실제로 알아채는 곳은 끝부분이며, 바로 그곳에 균형 처리가 적용됩니다

줄바꿈은 단어 전체를 측정한다

줄바꿈 알고리즘은 문자 너비를 누적하는 대신 완전한 단어를 측정하며, URL이나 등록 번호처럼 줄에 전혀 들어맞지 않는 지나치게 큰 토큰을 위해 제한된 탐색을 남겨 둡니다. 이는 일반적인 경우는 빠르게, 병적인 경우는 유계로 유지하며, 그 반대가 아닙니다

임의(discretionary) 소프트 하이픈과 자동 하이픈은 그것이 표시하는 줄바꿈이 실제로 선택된 줄바꿈일 때만 렌더링됩니다. 당연해 보이지만 고전적인 결함입니다. 순진한 구현은 측정하면서 하이픈 문자를 써넣고, 줄바꿈 위치가 옮겨지면 하이픈은 줄 한가운데 남겨진 채 뒤에 남습니다. 단어 안에 떠도는 하이픈만큼 텍스트 엔진이 고장 난 것처럼 보이게 만드는 것은 없습니다

var
  Lib: TPDFlib;
  Flow, Needed: Integer;
begin
  // 아무것도 그리기 전에 레이아웃을 결정
  Flow := Lib.NewTextFlow(ArticleBody);
  try
    Lib.SetTextFlowLanguage(Flow, 'fr');
    Lib.SetTextFlowHyphenation(Flow, 1, 3, 3);

    // 열 하나 너비에서 나머지 문단에 필요한 줄 수
    Needed := Lib.MeasureTextFlow(Flow, 148);
    if Needed > 3 * LinesPerColumn then
      UseTwoPageSpread
    else
      UseSinglePage;

    Lib.DrawTextFlowColumns(Flow, 72, 720, 480, 620, 3, 18, 1);

    if Lib.TextFlowFinished(Flow) <> 1 then
      CarryOver(Lib.GetTextFlowRemaining(Flow));
  finally
    Lib.ReleaseTextFlow(Flow);
  end;
end;

상자 전체에 걸쳐 폰트 설정을 동일하게 유지하라

모든 플로우 기반 레이아웃을 지배하는 규칙이 하나 있으며, 직설적으로 말해둘 가치가 있습니다. DrawTextFlow, DrawTextFlowColumns, MeasureTextFlow는 모두 호출되는 순간 선택되어 있는 폰트를 사용해 줄을 나눕니다. 같은 플로우의 두 상자 사이에서 폰트나 크기를 바꾸거나, 다시 선택하지 않고 새 페이지를 시작하면, 두 번째 상자는 첫 번째가 측정한 것과 다르게 나뉩니다

이 증상은 간헐적으로 보인다는 바로 그 이유 때문에 골치 아픕니다. 1페이지에서는 들어맞던 텍스트가 2페이지에서는 넘치거나, 측정된 줄 수가 실제로 그려진 것과 어긋납니다. 루프 전에 폰트를 한 번 선택하고, NewPage 이후마다 다시 선택하면 플로우는 제대로 동작합니다. 같은 문단 안에 여러 문자 체계가 섞여 있을 때는, CJK와 이모지 텍스트를 위한 자동 폰트 폴백에서 설명하는 해법이 측정과 드로잉 모두에 적용되므로, 폴백 런 전반에 걸쳐서도 너비가 일관되게 유지됩니다

플로우가 헤더, 푸터, 데이터 기반 블록들 사이의 한 요소인 보고서 레이아웃이라면, 데이터셋 리포트 엔진의 구성 패턴이 열 플로우와 깔끔하게 결합됩니다. 먼저 측정하고, 고정된 요소(furniture)를 배치한 다음, 남은 영역이 무엇이든 그것을 플로우에 넘기십시오

PDFlibPas는 Delphi, C++Builder, Lazarus용 PDF 라이브러리이며, TextFlow의 전체 생명주기, 즉 생성, 드로잉, 측정, 검사, 되감기, 해제까지 DLL과 ActiveX 인터페이스를 통해서도 노출됩니다. 전체 문서는 PDFlibPas Delphi PDF 라이브러리 페이지에 있습니다