PDFium 컴포넌트의 텍스트 셰이핑은 설치형 오브젝트 하나를 거칩니다. ConfigureTextShaper는 모든 셰이핑 진입점이 라우팅되는 셰이퍼를 설치하며, 있던 것은 교체하고 해제합니다. ActiveTextShaper는 설치된 것을 돌려주고 첫 사용 시 플랫폼 기본값을 만듭니다. ActiveTextShaperName은 어느 백엔드가 살아 있는지 보고합니다. ClearTextShaper는 설치를 내려놓아 기본값이 다시 만들어질 수 있게 합니다. Windows의 기본값은 TPdfUniscribeTextShaper입니다. Free Pascal 아래에는 TPdfHarfBuzzTextShaper가 있으며, libharfbuzz를 실행 시점에 바인드해 빠진 라이브러리가 로드 실패가 아니라 보고되는 조건이 되게 합니다
인터페이스는 하나, 일을 완전히 다르게 나누는 두 백엔드입니다. 그 비대칭을 이해하는 것이 이식 경로가 올바르게 셰이핑되고 잘못 위치된 텍스트를 만들지 않게 하는 것입니다
Windows 백엔드는 한 클래스이고 이식용은 세 조각인 이유
Uniscribe는 하나인 척하는 네 개의 API이기 때문입니다. ScriptItemize는 문자열을 스크립트별로 분절해 양방향 레벨을 해석하고, ScriptShape는 문자를 글리프로 매핑하며, ScriptPlace는 전진폭과 오프셋을 계산하고, ScriptLayout은 결과 run들을 시각 순서로 놓습니다. 그 위에 지은 백엔드는 더할 것이 없으므로, Windows 셰이퍼가 단일 메서드를 가진 단일 클래스인 이유가 됩니다
HarfBuzz는 가운데 둘을 커버합니다. 호출자가 이미 방향과 스크립트를 정해 둔 run을 셰이핑하고 위치시키며, 단락이 run으로 어떻게 갈라지는지나 그 run들이 어떤 순서로 나타나는지에 대한 견해가 없습니다. 그래서 이식 백엔드가 나머지를 공급합니다. 양방향 알고리즘이 임베딩 레벨을 해석하고, HarfBuzz Unicode 함수가 텍스트를 스크립트별로 분절하며, run들은 UAX #9 규칙 L2가 만드는 시각 순서로 배치됩니다. 양방향 절반은 자기 유닛이 될 만큼 실질적이며, UAX #9 임베딩 레벨 문서에서 설명합니다
셰이퍼는 폰트를 해석하지 않으며 그것은 의도입니다
Uniscribe는 GDI 디바이스 컨텍스트에서 폰트 바이너리를 읽습니다. 그 이식 가능한 동등물은 없으며, 셰이핑 유닛 안에서 그것을 발명한다는 것은 모든 애플리케이션을 대신해 폰트가 fontconfig에서 오는지, CoreText에서 오는지, 애플리케이션 폰트 폴더에서 오는지, 데이터베이스에서 오는지를 정한다는 뜻입니다. 그래서 HarfBuzz 백엔드는 리졸버를 받습니다. 폰트 이름을 TrueType 또는 OpenType 바이트로 매핑하는 콜백입니다. False를 돌려주면 Windows에서 읽을 수 없는 GDI 폰트가 실패시키는 것과 똑같이 셰이핑 요청이 실패합니다
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// 여러분의 정책: fontconfig, CoreText, 앱 폰트 폴더, 데이터베이스
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// 소유권은 유닛으로 넘어갑니다. 무언가가 텍스트를 셰이핑하기 전에
// 시작 중 한 번만 호출하십시오
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// Delphi에서는 플랫폼 기본값(Uniscribe)이 수요 시 만들어지므로
// 설치가 전혀 필요 없습니다
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
폰트 탐색을 셰이퍼 밖에 두면 서버에서 드러나는 두 번째 이득이 있습니다. 같은 프로세스가 기계에 설치된 것과 무관한 내장 폰트 세트로 셰이핑할 수 있다는 것입니다. 출력이 호스트들 사이에서 바이트 재현 가능해야 할 때 원하는 바로 그것입니다. 컴포넌트는 설치된 폰트를 정말 원하는 경우를 위해 호스트 시스템 폰트 provider도 노출하며, 시스템 폰트 provider 문서에서 다룹니다
결과 레코드는 백엔드 중립이며 클러스터가 그 이유입니다
두 백엔드 모두 같은 TPdfShapedText를 채웁니다. 원본 텍스트, 폰트 이름, 크기, 폰트 바이트, run 배열, 총 폭, 글리프 수, 논리 문자 수입니다. 각 TPdfShapedRun은 원본 텍스트 안에서의 구간, 시각 X 위치, 폭, 양방향 레벨, 오른쪽에서 왼쪽 플래그와 글리프들을 실어 갑니다. 각 TPdfShapedGlyph는 글리프 식별자, 전진폭, X와 Y 오프셋, 그리고 원본 텍스트 안에서 시작과 길이로서 자기가 속한 클러스터를 실어 갑니다
그 클러스터 필드들이 레코드를 단지 정보 제공이 아니라 쓸모 있게 만듭니다. 셰이핑은 일대일 매핑이 아닙니다. 데바나가리 음절이 네 문자에서 하나의 글리프가 되고, 아랍어 합자가 둘을 합치며, 단일 문자가 여러 마크를 만들어 낼 수 있습니다. 클러스터 구간이 없으면 캐럿을 놓거나 클릭을 히트 테스트하거나 선택을 강조할 수 없습니다. 글리프가 어느 문자에 속하는지 말할 수 없기 때문입니다. 그것들이 있으면 산술은 국소적이고 같은 코드가 양쪽 백엔드에서 동작합니다
var
Shaped: TPdfShapedText;
R, G: Integer;
begin
if ShapePdfText(Line, 'Noto Sans Arabic', 14, ptdAuto, Shaped) then
for R := 0 to High(Shaped.Runs) do
begin
// run들은 VisualX가 채워진 시각 순서로 이미 도착합니다
X := Shaped.Runs[R].VisualX;
for G := 0 to High(Shaped.Runs[R].Glyphs) do
begin
EmitGlyph(Shaped.Runs[R].Glyphs[G].GlyphID,
X + Shaped.Runs[R].Glyphs[G].OffsetX,
Shaped.Runs[R].Glyphs[G].OffsetY);
X := X + Shaped.Runs[R].Glyphs[G].Advance;
end;
end;
end;
예산은 옵션 레코드에 속합니다
TPdfTextShapingOptions는 방향과 세 상한, 즉 최대 문자, 최대 글리프, 최대 run을 실으며, 상식적인 값을 채우는 Default 클래스 함수가 있습니다. 상한은 기형 입력에 대한 편집증이 아니라 산술입니다. 셰이핑은 팽창합니다. 공격적인 문맥 치환을 하는 폰트는 입력 문자보다 많은 글리프를 낼 수 있고, 몇 문자마다 스크립트를 번갈아 쓰는 단락은 전환마다 run을 하나씩 냅니다. 둘 다를 극대화하도록 조립된 문서는 소박한 문자열을 큰 할당으로 바꾸며, 신뢰할 수 없는 PDF에서 텍스트를 셰이핑하는 서비스는 기계가 부과하는 한도가 아니라 자기가 고른 한도가 필요합니다
이미 알고 있다면 방향을 자동으로 두는 대신 명시적으로 설정할 가치가 있습니다. 자동은 첫 강한 문자에서 추측하도록 단락 방향 규칙을 적용하는데, 자유 텍스트에는 맞지만 방향이 누군가 친 값의 속성이 아니라 필드의 속성인 양식 필드에는 틀립니다
빌드 의존성이 아니라 실행 시점 바인딩
HarfBuzz 백엔드는 라이브러리를 동적으로 로드합니다. 실질적 결과가 있는 배포 결정입니다. 하나의 바이너리가 HarfBuzz가 있는 기계에서도 없는 기계에서도 돌며, 두 번째 경우에는 시작 실패 대신 축소된 능력을 보고합니다. 다른 개발자들에게 배포되는 라이브러리에는 그것이 유일하게 통하는 배열입니다. PDF 컴포넌트의 모든 소비자에게 필요하지 않을 수도 있는 셰이핑 라이브러리를 구하고 버전을 맞추라고 요구할 수는 없기 때문입니다
호출자 쪽의 대응 규칙은 확인하는 것입니다. 플랫폼에 기본값이 없고 구성된 것도 없으면 ActiveTextShaper는 nil을 돌려주고, 셰이핑 진입점은 그것을 셰이핑 실패가 아니라 사용 불가 셰이퍼로 보고합니다. 둘은 다른 문제이며 다른 메시지를 받을 가치가 있습니다. 하나는 배포 공백이고 다른 하나는 폰트나 텍스트 문제입니다
무언가가 셰이핑하기 전에 한 번 설치합니다
설치는 이전 셰이퍼를 교체하고 해제하므로 반복 호출은 안전하지만 무의미하며, 다른 스레드가 셰이핑하는 동안의 호출은 전혀 안전하지 않습니다. 시작 중에 하십시오. 나중에 플랫폼 기본값으로 폴백해야 한다면 nil을 넘기십시오. 테스트 끝에서 테스트 대역을 되돌리는 방법이기도 합니다
백엔드가 설치되고 나면 측정과 줄바꿈은 두 플랫폼에서 같게 동작합니다. 플랫폼을 직접 부르는 대신 run과 글리프 메트릭을 소비하기 때문입니다. 줄바꿈 모델은 텍스트 측정 및 word wrap 문서에서 설명합니다. 컴포넌트의 지원 플랫폼과 툴체인은 PDFium Delphi component 제품 페이지에 정리되어 있습니다