Tistory View

Android Develop/Font

안드로이드 FontMetrics

God Dangchy What should I do? 2019. 8. 25. 23:30

안드로이드 Canvas에 글자를 쓰려면 우선 서체가 어떻게 생겼는지를 알아야 한다. 다행히도 안드로이드는 이 부분을 Paint class를 통하여 바로 처리할 수 있게 되어 있다. 실제 이 것을 지원해 주지 않는다면 폰트를 다루는 상황에서 상상만해도 끔찍한 일이다.

 

글자를 찍기위해서는 서체의 기준선들을 알아야 정확히 찍을 수가 있다. 이 기준선들은 다음과 같다.

 

Font 기준선

 

가장 중요한 선은 baseline(세로 기준점:y)이다, 이 baseline을 기준점으로 글자는 찍히게 되며, ascent, descent, top, bottom값은 이 baseline으로 얼마나 떨어져있는 지를 나타낸다.

 

이 기준선들은 android.android.graphics.Paint.FontMetrics의 멤버변수들로 구성되어 있다.

 

public static class FontMetrics {

    public float top;            // 서체내의 글자중에 가장 높은 값
    public float ascent;        //
    public float descent;
    public float bottom;       // 서체내의 글자중에 가장 낮은 값
    public float leading;
}

 

 

이 멤버변수들 중에는 baseline과 line height(줄간격)값은 없다.

baseline은 어디에 찍을 것인지 프로그램을 짜는 사람이 정하는 마음대로 정하는 값이라 없는 것이고,

line height 값은 다른 식으로 구해야 한다.(뒤에 나온다)

 

getFontMetrics

이 기준선들의 값을 알아내려면 paint instance의 getFontMetrics(..) 함수를 이용하면 된다.

이 함수는 2가지가 있는 데, 한개는 파라미터로 FontMetrics instance를 넘겨주는 방식이고 또 하나는 return값으로 FontMetrics instance를 받는 방법이다.

// android.android.graphics.Paint
1. public float getFontMetrics( FontMetrics metrics );  // 파라미터로 넘겨 받는 방식
2. public FontMetrics getFontMetrics(); // return 값으로 받는 방식

 

이 둘의 결과로 처리되는 FontMetrics는 같은 결과를 갖게 되지만, 필자는 [파라미터로 넘겨 받는 방식]을 추천한다. 보통 이런 그리기 작업은 여러면 수행되는 경우가 많아 실행될 때마다 새로운 객체를 생성하는 부하를 줄일 수 있기 때문이다. 또한 1번 방식의 함수의 리턴값이 바로 line height값이다.(주1)

 

line height값은 폰트가 정해주는 값으로 이 폰트를 이용하여 글자를 여러 줄로 그려야 할 경우 최적의 값을 나타낸다. 최적의 값이라는 표현을 쓰는 이유는 다음줄의 출력할 위치 또한 개발자가 그냥 정하면 되는 값이기 때문이다.

 

 

어떤 폰트인지는 모르겠지만, 폰트크기를 100.0px로 설정한 후 위의 값들을 보면

 

ascent   : -92.82px

descent : 23.58px

top      : -105.62px

bot       : 27.10px

line height : 116.41px

와 같이 들어가 있다.

 

 

baseline을 기준으로 상대적 좌표값이기 때문에 보통 ascent와 top는 보통 음수값을 갖는다. 꼭 음수값은 아닐 수 있다.

line height값은 보통 (descent - ascent)와 같은 값을 갖고 있지만, 이 것 또한 꼭 이 값이라고 할 수 없으므로 getFontMetrics의 리턴값을 이용해야 한다.

 

 

실제 출력하기

단순히 글자를 출력하려면 Canvas의 drawText함수를 이용하면 된다.

public void drawText( char[] text, int index, int count, float x, float y, Paint paint)

public void drawText( String text, float x, float y, Paint paint)

public void drawText( String text, int start, int end, float x, float y, Paint paint)

public void drawText( CharSequence text, int start, int end, float x, float y, Paint paint)

 

4가지나 overload되어 있지만 실제 사용성을 편하게 해주는 것일 뿐 같은 역할을 한다.

 

파라미터

paint값은 null이 될 수 없다. 서체의 정보는 Canvas에 있지않고 Paint에 있기 때문에 paint값은 꼭 넘겨 줘야 한다.

 

y값은 baseline을 의미한다. 글자는 baseline을 기준으로 그리기 때문이다. 다른 말로 상단으로 알고 출력을 해보면 다음과 같은 참사가 나타난다.

켄버스의 y값이 0을 기준으로하면 발생하는 참사

 

그래서 출력을 할 때는 일정부분 baseline값을 아래로 내려 줘야 하는 데, 필자는 주로 line height값을 쓴다.

 

 

다음의 추가적인 함수들은 그리고자 하는 글자들의 위치정보값을 알아 낼 수 있다.

 

measureText

float paint.measureText(String text)

출력하고자하는 글자들의 전체 폭을 구하는 함수

서체내의 글자들은 폭이 제각각이라 단순히 글자수에 일정한 폭을 곱하여 계산해서는 안된다. 일일이 글자마다 폭을 더해줘야 하는 데, 이 작업을 해주는 함수다.

 

 

getTextWidths

각각의 글자의 폭을 구하는 함수

paint.getTextWidths( String text, float[] widths );

 

만약 출력할 글자들의 각각의 폭이 필요하다면, 위의 함수를 사용하면 된다. 이 함수의 결과로 나오는 폭은 정확히 폭이 아니다.

 

내부에서 글자를 출력할 때, 임의로 정한 x값과 baseline을 기준으로 한 글자를 찍는다. 다음 글자를 찍으려면 방금 찍은 글자의 폭만큼 오른 쪽으로 이동 후 다음 글자를 찍는 방식일텐데, 이 얼마나 오른쪽으로 이동을 해야 되는 지를 나타내는 값이다. 글자 모양에 따른 글자를 감싸는 등의 폭이 아니고, 다음 글자를 찍어야할 위치라는 뜻이다. 다음 그림의 "Advance"를 나타내는 값이 들어있다.

 

Advance를 나타낸다

 

 

TrueType font내의 Bitmap font

예전 화면의 해상도가 그리 좋지 않던 시절에는 글자를 화면에 찍으면 뿌였게 보였다. Antialias가 적용이 되면 뿌였게 되었고, 적용되지 않으면 못생긴 글자가 나와 둘 다 알아보기 힘든 상황이있다.

이 상황을 해결하고자 TrueType폰트는 작은크기의 글자들을 미리 Bitmap으로 일일이 만들어 같이 포함되어 있다.

Paint객체를 만들 때 다음과 같이 하면 이 Bitmap폰트가 적용된다(필자가 테스트해보지는 않았다.)

Paint paint = new Paint( Paint.EMBEDDED_BITMAP_TEXT_FLAG );

 

 


퍼포먼스 노트

글자를 그리는 작업은 일일이 계산을 통해서 그리기 때문에 부하가 많이 드는 작업이다. 실제 hardware엔진을 이용하지 않는 다면 그리기 작업 중에서 가장 큰 부하를 주는 작업이니, 일단 코드가 잘 동작하면 최적화 작업을 따로 해주어야 할 경우도 많다. 실제 내부에서는 수 많은 수학공식으로 그려지게 된다. 

 

 

더 세부적인 작업이 필요하다면

만약 Android에서 지원하지 않는 기능을 써야 한다면, NDK를 이용하여 FreeType2 라이브러리를 컴파일해서 넣은 후 사용해야 한다. 실제 Paint class는 웬만한 것은 다 지원을 해주니, 크게 쓸 일은 없었다. 필자는 맨날 구글을 욕하면서 프로그래밍을 하지만, 이 것은 칭찬해주고 싶다.

 


주1 :

    getFontSpacing() 함수와 같은 결과를 보여주고 getFontSpacing함수의 내부에서 getFontMetrics함수를 쓴다.

 

Replies
Reply Write