주요글: 도커 시작하기
반응형
PDF 출판시 발생하는 한글 깨짐 문제를 처리하는 방법을 설명한다.

한글 출력시 폰트 깨짐 문제

코쿤 2.0을 이용하여 XML 문서를 PDF로 변환하여 출판하고자 하는 경우 한국의 개발자들이 부딪히는 문제가 바로 한글이 올바르게 출력되지 않는다는 점이다. 먼저 가장 간단한 예인 "Hello World!"의 우리말 버전 "안녕하세요!"를 PDF로 변환하여 출력해주는 예제를 작성해보자. 먼저 XML 데이터는 다음과 같다. (한글이 포함되어 있다.)

    <?xml version="1.0" encoding="euc-kr"?>
    
    <page>
     <title>안녕하세요?</title>
     <content>
      <para>이 페이지는 나의 첫번째 코쿤 페이지입니다!!</para>
     </content>
    </page>

위 XML 파일은 코쿤 배포판의 cocoon.war에 포함되어 있는 /samples/hello-world/content/xml/hello-page.xml 파일을 수정한 것이다.

이 XML 파일을 FO 형식으로 변환해줄 XSL 파일은 다음과 같다. 이 파일 역시 코쿤 배포판에 포함되어 있는 /samples/hello-world/style/xsl/simple-page2fo.xsl 파일의 일부 내용(폰트 부분)을 변경한 것이다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:fo="http://www.w3.org/1999/XSL/Format">
    
      <xsl:template match="/">
       <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
       
        <fo:layout-master-set>
         <fo:simple-page-master master-name="page"
                      page-height="29.7cm" 
                      page-width="21cm"
                      margin-top="1cm" 
                      margin-bottom="2cm" 
                      margin-left="2.5cm" 
                      margin-right="2.5cm">
           <fo:region-before extent="3cm"/>
           <fo:region-body margin-top="3cm"/>
           <fo:region-after extent="1.5cm"/>
         </fo:simple-page-master>
    
         <fo:page-sequence-master master-name="all">
           <fo:repeatable-page-master-alternatives>
         <fo:conditional-page-master-reference
                master-reference="page" page-position="first"/>
           </fo:repeatable-page-master-alternatives>
         </fo:page-sequence-master>
        </fo:layout-master-set>
    
        <fo:page-sequence master-reference="all">
          <fo:static-content flow-name="xsl-region-after">
        <fo:block text-align="center" 
                  font-size="10pt" 
                  <b>font-family="새굴림" </b>
              line-height="14pt">페이지 <fo:page-number/></fo:block>
          </fo:static-content> 
            
          <fo:flow flow-name="xsl-region-body">
            <xsl:apply-templates/>
          </fo:flow>
        </fo:page-sequence>
       </fo:root>
      </xsl:template>
    
      <xsl:template match="title">
        <fo:block font-size="36pt" <b>font-family="새굴림"</b>
            space-before.optimum="24pt"
            text-align="center"><xsl:apply-templates/></fo:block>
      </xsl:template>
    
      <xsl:template match="para">
        <fo:block font-size="12pt" <b>font-family="새굴림"</b>
            space-before.optimum="12pt"
            text-align="center"><xsl:apply-templates/></fo:block>
      </xsl:template>
    </xsl:stylesheet>

코쿤 예제에는 이미 hello-page.xml과 simple-page2fo.xsl을 사용하여 PDF로 변환해주도록 사이트맵에 설정되어 있으므로, http://host:8080/cocoon/samples/hello-world/hello.pdf를 웹브라우저의 주소란에 입력하면 PDF 변환 결과를 볼 수 있다. 한글 폰트 처리를 해주지 않은 상태에서 실행하게 되면 다음과 같이 한글 부분이 '#'으로 출력될 것이다.


이처럼 한글이 올바르게 출력되지 않는 이유는 PDF가 기본적으로 지원하는 폰트에 자주 사용하는 한글 폰트인 "굴림", "돋움", "바탕체" 등이 포함되어 있지 않기 때문이다. 따라서 PDF 출판시 한글이 올바르게 출력되도록 하기 위해서는 한글 폰트와 관련된 별도의 처리를 해 주어야 하는데, 폰트 처리를 하기 위해서는 http://xml.apache.org/fop/download.html에서 최신 FOP(Formatting Object Processor)를 다운로드 받아야 한다.

FOP 바이너리 버전을 다운로드 받아서 압축을 F:\fop-0.20.5rc2(필자는 현재 최신 버전인 0.20.5 rc2 버전을 사용하였다)에 풀었다고 가정하고서 이후 과정을 설명하도록 하겠다. 먼저 폰트 처리를 하기 전에 다음과 같은 jar 파일을 CLASSPATH에 추가하도록 하자. (참고로 버전을 나타내는 숫자는 다운로드 받은 FOP 버전에 따라 다를 수 있으며, 이 글에서는 0.20.5 rc2 버전을 기준으로 설명한다.)

  • F:\fop-0.20.5rc2\build\fop.jar
  • F:\fop-0.20.5rc2\lib\avalon-framework-cvs-20020806.jar
  • F:\fop-0.20.5rc2\lib\batik.jar
  • F:\fop-0.20.5rc2\lib\xalan-2.4.1.jar
  • F:\fop-0.20.5rc2\lib\xercesImpl-2.2.1.jar
  • F:\fop-0.20.5rc2\lib\xml-apis.jar
TTF 및 TTC 파일의 폰트 추출

c:\WINDOWS\Fonts (또는 c:\WINNT\Fonts) 디렉토리를 보면 현재 시스템에 설치되어 있는 폰트 파일이 저장되어 있는 것을 알 수 있다. 이 폰트 파일에는 확장자가 .TTF인 것과 .TTC인 것이 있는데, 확장자가 .TTF인 것은 한개의 트루타입 폰트를 저장하고 있는 폰트 파일이며 .TTC인 것은 여러 개의 트루타입 폰트를 저장하고 있는 폰트 파일이다. 이 글에서는 먼저 TTF 파일로부터 폰트 정보를 추출하는 것에 대해서 살펴보도록 하겠다.

TTF 파일로부터 폰트 메트릭스 정보 생성하기

TTF 파일로부터 폰트 정보를 추출하여 PDF 변환시 사용되는 XML 파일로 변환하려면 다음과 같이 org.apache.fop.fonts.apps.TTFReader 클래스를 사용하면 된다.

    java org.apache.fop.fonts.apps.TTFReader -fn "새굴림" C:\WINNT\Fontsngulim.ttf ngulim.xml
    
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\ngulim.ttf...
    
    Number of glyphs in font: 23181
    Postscript format 3
    Glyph 65535 out of range: 23181
    Glyph 65535 out of range: 23181
    Creating xml font file...
    
    Creating CID encoded metrics
    Writing xml font file ngulim.xml...
    
    This font contains no embedding license restrictions

ngulim.ttf 파일은 '새굴림' 폰트 파일인데 위와 같이 TTFReader 클래스를 실행하면 ngulim.xml 파일에 '새굴림' 폰트에 대한 폰트 메트릭스 정보가 저장된다. 이렇게 생성된 폰트 메트릭스 파일은 FO XML 문서를 PDF로 변환할 때 폰트로 사용될 수 있게 된다.

TTC 파일로부터 폰트 메트릭스 정보 생성하기

TTC는 True Type Collection을 의미하는 것으로 TTC 폰트 파일은 한개 이상의 폰트를 저장하고 있다. TTFReader 클래스를 사용하면 TTC 폰트 파일에 저장되어 있는 폰트 목록을 볼 수 있는데, 앞에서 TTF 폰트로부터 폰트 메트릭스를 생성할 때와 똑같이 하면 된다.

    java org.apache.fop.fonts.apps.TTFReader C:\WINNT\Fonts\gulim.ttc gulim.xml
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\gulim.ttc...
    
    This is a TrueType collection file with4 fonts
    Containing the following fonts:
     G u l i m     G u l i m C h e     D o t u m     D o t u m C h e    java.io.IOException: Failed to read font
            at org.apache.fop.fonts.TTFFile.readFont(TTFFile.java:372)
            at org.apache.fop.fonts.apps.TTFReader.loadTTF(TTFReader.java:178)
            at org.apache.fop.fonts.apps.TTFReader.main(TTFReader.java:140)

여기서 폰트 이름 부분을 굵게 표시하였다. (에러는 4개의 폰트 중 폰트 메트릭스를 표시할 폰트를 지정하지 않아서 발생하는 것이다.) 출력된 폰트 목록중에서 폰트를 추출하려면 TTFReader 클래스를 실행할 때 다음과 같이 -ttcname 옵션을 추가해야 한다.

    java org.apache.fop.fonts.apps.TTFReader -ttcname "Gulim"     C:\WINNT\Fonts\gulim.ttc gulim.xml
    
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\gulim.ttc...
    
    This is a TrueType collection file with4 fonts
    Containing the following fonts:
     G u l i m
     G u l i m C h e
     D o t u m
     D o t u m C h e
    java.io.IOException: Failed to read font
            at org.apache.fop.fonts.TTFFile.readFont(TTFFile.java:372)
            at org.apache.fop.fonts.apps.TTFReader.loadTTF(TTFReader.java:178)
            at org.apache.fop.fonts.apps.TTFReader.main(TTFReader.java:140)

위의 결과를 보고서 약간 의아해할 것이다. -ttcname 옵션을 추가하나 하지 않나 실행 결과가 똑같기 때문이다. 이렇게 실행결과가 똑같은 이유는 -ttcname 옵션에 전달한 폰트 이름을 TTFReader 클래스가 올바르게 변환해주지 않기 때문이다. 앞에서 폰트 목록을 추출한 폰트의 이름을 보면 "Gulim"이 아니라 " G u l i m"인 것을 알 수 있는데, 이렇게 출력된 이유는 TTFReader 클래스가 유니코드로 된 폰트명 "Gulim"을 출력했기 때문이다.

따라서 명령행에서 -ttcname 옵션으로 폰트명을 전달할 때도 유니코드로 전달해야 TTFReader가 올바르게 추출할 폰트를 찾는데, 명령행에서 유니코드로 폰트명을 입력한다는 건 여간 귀찮은 일이 아니다. 그래서 필자는 다음과 같은 보조 클래스를 작성했다.

    import org.apache.fop.fonts.apps.TTFReader;
    
    public class FontReaderUtil {
    
        public static void main(String[] args) throws Exception {
            for (int i = 0 ; i < args.length ; i++) {
                if (args[i].compareTo("-ttcname") == 0) {
                    // 폰트이름을 유니코드에 해당하는 바이트 배열로 변환하여 전달
                    byte[] fontNameByte = args[i+1].getBytes("UTF-16BE");
                    String fontName = new String(fontNameByte);
                    args[i+1] = fontName;
                    System.out.println("FontName="+fontName);
                }
            }
            TTFReader.main(args);
        }
    }
    

이제 FontReaderUtil 클래스를 사용하여 gulim.ttc 파일로부터 "Gulim" 폰트에 해당하는 폰트 메트릭스를 생성할 수 있다.

    java FontReaderUtil -ttcname "Gulim" -fn "굴림" 
    C:\WINNT\Fonts\gulim.ttc gulim.xml (위의 줄과 같은 줄에 입력)
    
    FontName= G u l i m
    TTF Reader v1.1.1
    
    Reading C:\WINNT\Fonts\gulim.ttc...
    
    This is a TrueType collection file with4 fonts
    Containing the following fonts:
    *  G u l i m     G u l i m C h e
     D o t u m
     D o t u m C h e
    Number of glyphs in font: 40194
    Postscript format 3
    Glyph 65535 out of range: 40194
    Glyph 65535 out of range: 40194
    Creating xml font file...
    
    Creating CID encoded metrics
    Writing xml font file gulim.xml...
    
    This font contains no embedding license restrictions
    

폰트 설정 파일 작성 및 출판하기

폰트 파일(TTC나 TTF)로부터 폰트 메트릭스 파일을 생성했다면 fo2pdf Serializer가 사용할 폰트 설정 파일을 작성하면 된다. 예를 들어, 앞에서 작성한 폰트 메트릭스 파일들이 f:\fop-fonts 디렉토리에 저장되어 있다고 할 경우 다음과 같이 폰트 설정 파일을 작성하면 된다.

    <?xml version="1.0" encoding="euc-kr"?>
    
    <configuration>
      <fonts>
        <font metrics-file="F:/fop-fonts/ngulim.xml" 
              kerning="yes" embed-file="C:/WINNT/Fonts/NGULIM.TTF">
          <font-triplet name="새굴림" style="normal" weight="normal"/>
        </font>
        <font metrics-file="F:/fop-fonts/gulim.xml" 
              kerning="yes" embed-file="C:/WINNT/Fonts/GULIM.TTC">
          <font-triplet name="굴림" style="normal" weight="normal"/>
        </font>
        <font metrics-file="F:/fop-fonts/gulimche.xml" 
              kerning="yes" embed-file="C:/WINNT/Fonts/GULIM.TTC">
          <font-triplet name="굴림체" style="normal" weight="normal"/>
        </font>
      </fonts>
    </configuration>

위 파일을 f:\fop-fonts\ 디렉토리에 font-config.xml로 저장했다면, 이제 마지막으로 남은 일은 코쿤의 사이트맵 파일에서 fo2pdf Serializer를 명시한 부분에 다음과 같이 user-config 태그를 사용하여 폰트 설정 정보를 지정해주는 것이다.

    <map:serializer
            mime-type="application/pdf" 
            name="fo2pdf"
            src="org.apache.cocoon.serialization.FOPSerializer">
        <b><user-config src="F:/fop-fonts/font-config.xml"/></b>
    </map:serializer>

이제 남은 일은 XML을 FO 형태로 변환해주는 XSL 파일을 작성하고 사이트맵에 URL을 매핑시켜주는 것이다. XSL 파일을 작성할 때는 다음과 같이 font-family 속성을 사용하여 사용할 폰트를 지정해주면 된다. (CSS 및 FO에 대한 자세한 설명은 관련링크를 참고하기 바란다.)

    <fo:block text-align="center" font-size="10pt" <b>font-family="새굴림"</b>
        line-height="14pt">페이지 <fo:page-number/></fo:block>

이제 톰캣을 재가동한 후 다시 URL을 요청해보자. (캐시에 저장된 내용이 보여질 수 있으니 리로드해서 보도록 하자.) 그럼, 다음과 같이 한글이 올바르게 출력되는 것을 확인할 수 있을 것이다.



해결해야 할 문제점

아직 필자가 해결하지 못한 문제가 있는데, 그것은 바로 TTC 폰트집합에 포함되어 있는 글꼴을 사용하지 못한다는 것이다. FontReaderUtil 클래스를 작성하여 TTC에 포함되어 있는 폰트에 대한 폰트 메트릭스 정보를 추출하는데까지는 성공했는데, 실제로 그 폰트 메트릭스 정보를 참조해도 해당 글꼴을 사용하지 못하고 출력 결과에는 한글이 '#'으로 출력되었다. (즉, gulim.ttc 파일에 포함되어 있는 '굴림'이나 '굴림체'를 사용할 경우 한글이 '#'으로 출력된다.) 이 문제를 해결하게 되면 다음에 해결방안을 올리도록 하겠다.

관련링크:

+ Recent posts