Tistory View

Android Develop/Font

안드로이드 Paint getTextBounds와 정렬

God Dangchy What should I do? 2019. 8. 31. 12:38

이 글을 읽기전에 우선 다음의 포스트를 읽고 오세요.

 

https://jamssoft.tistory.com/141

 

안드로이드 FontMetrics

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

jamssoft.tistory.com

 

 

실제 글자를 Canvas를 통해서 출력하기 전에 출력될 텍스트의 크기를 알아 내야 할 필요가 있다. 안드로이드의 Paint는 이 것을 Paint 인스턴스의 getTextBounds로 정보를 얻을 수 있다.

 

다음은 폰트의 크기를 100px로 놓고 "jklmnOPq"라는 text를 getTextBounds한 예제이다.

Paint myPaint = new Paint( Paint.ANTI_ALIAS_FLAG );
String text = "jklmnOPq";
Rect rc = new Rect();
myPaint.setTextSize(100.0f);
myPaint.getTextBounds( text, 0, text.length(), rc );


// [jklmnOPq] font size=100 L=  -4 B=  22 R= 430 T= -77 // aling left
// [jklmnOPq] font size=100 L=  -4 B=  22 R= 430 T= -77 // align center

left값이 음수값으로 "j"글자 때문에 발생하게 되는 데, 이 때문에 출력의 X위치를 바꿔줘야 한다.

top값은 음수값으로 "jklmnOPq"모든 글자중에 가장 큰 글자(가장 위로 솟구친)의 ascent값일 것이다.

bottom값은 "j"나"q"의 밑꼬리 값을 것이다.

 

따라서 이 글자("jklmnOPq")를 정확히 감싸는 Bitmap을 만들경우 글자를 찍기 시작하는 X값과 Baseline값은 (4,77)을 기준으로 찍어야 한다. 또한 Align과는 상관없이 left일 경우로 계산되어 나오기 때문에 실제 출력을 할 경우에는 Align은 Left로 놓고 글자를 찍어야 한다.

 

다음의 예제는 실제 위의 "jklmnOPq"를 정확히 감싸는 크기에 출력한 예제이다.

 

 

[예제] 찍을 위치를 계산한 예제

String text   = "jklmnOPq";
Rect   rc     = new Rect();
Paint  paint  = new Paint( Paint.ANTI_ALIAS_FLAG );
Bitmap bmp    = null;
Canvas canvas = null;

paint.setTextSize(100.0f);
paint.getTextBounds( text, 0, text.length(), rc );

bmp = Bitmap.CreateBitmap.createBitmap( rc.width(), rc.height(), Bitmap.Config.ARGB_8888 );
canvas = new Canvas( bmp );

paint.setStrokeWidth( strokeWidth );
paint.setColor( 0xff000000 );
canvas.drawText( text, -rc.left, -rc.top, myPaint ); // x, y값을 계산 해줘야 한다.

 

X점이 0일경우 밀림이 발생한다.

 

 

bold와 getTextBounds

font파일은 bold용 서체를 따로 만들어 두었다. 서체를 만드는 사람이 가장 잘 bold를 표현할 수 있게 일일이 bold용 서체를 넣어 놓았다는 것이다. 즉, font파일이 있으면 두껍게 만드는 것을 계산하는 방식이 아니고, 미리 만들어둔 bold용 서체를 이용한다는 것이다. 따라서 getTextBounds를 사용하는 방법은 위에서 언급한 방식을 그대로 사용하면 된다.

서체가 bold를 지원하지 않는 경우도 있다. 이런 경우 2가지를 방법이 있다.

 

1. Paint.FAKE_BOLD_TEXT_FLAG

     Paint객체를 만들 때 이 플래그를 넘겨주거나, setFakeBoldText함수를 이용

2. setStrokeWidth를 이용

 

대부분의 bold는 폭과 높이가 커진다. 특히 폭이 많이 커지는 데, FAKE_BOLD_TEXT_FLAG를 사용할 경우 getTextBounds도 같이 알아서 커지기에 그냥 위에서 언급한 방법을 쓰면 된다.

 

bold를 지정하면 알아서 getTextBounds의 값이 커진다.

setStrokeWidth를 이용하여 bold를 처리할 경우는 거의 없지만, getTextBounds와의 관계를 구하는 주제기 때문에 언급을 한다. setStorkeWidth를 사용하여 글자를 뚱뚱하게 만들어도 getTextBounds에는 영향을 주지 않는다. 따라서 setStorkWidth를 사용할 경우는 계산을 해서 더 늘려 주어야 한다. 주로 글자에 Outline을 그릴 때 필요하다.

 

storkWidth는 getTextBounds와 상관이 없다.

 

볼드 적용법

1. paint.setTypeface(Typeface.DEFAULT_BOLD);
2. paint.setFakeBoldText(true);

 

setStrokeWidth와 getTextBounds

위에서 언급한 바와 같이 setStorkeWidth를 늘린다고 하여 getTextBounds의 값이 알아서 늘어나지를 않는다. 따라서 이 것을 계산을 해서 처리를 해줘야 한다.

 

 

상하 좌우에 strokeWidth의 반을 더해주면 된다. 물론 LineCap, Meter와도 관련이 있지만 이 정도의 오차는 지금은 무시한다. 처음 Paint객체를 생성할 경우, strokeWidth는 "0"값이고, style를 Paint.Style.FILL형태이기에 getStrokeWidth를 이용하여 값을 계산해 낼 수 있다.

 

또한 storkeWidth로 인해 늘어난 만큼 첫글자의 X값도 그 만큼 추가 해 줘야 하고 baseline값 또한 추가 해줘야 한다.

필요한 공식은 다음과 같다.

 

Bounds의 크기

width  : bounds.width() + paint.getStrokeWidth();

height : bounds.height() + paint.getStrokeWidth();

 

첫 글자를 찍을 X값 : (-bounds.left) + getStrokeWidth() / 2.0F

첫 글자를 찍을 Y값 : (-bounds.top) + getStrokeWidth() / 2.0F

 

0.5px~2.0px정도의 오차는 있겠지만 이 정도는 무시한다.

 

텍스트의 Outline(아웃라인)그리기

주로 배경색과 글자색이 겹치는 경우 배경색이 겹쳐서 글자가 보이지 않는 경우가 있다. 언제나 글자를 보이게 하기위해 strokewidth를 이용한 text와 그냥 text를 두번 그려 다른 색깔로 적용을 한다. 이런 상황때문에 getTextBounds를 이용한 크기를 다시 계산을 해주는 내용을 위에 언급한 것이다.

 

 

final float strokeWidth = 5.0f;

Paint myPaint = new Paint( Paint.ANTI_ALIAS_FLAG );
String text   = "jklmnOPq";
Rect rc       = new Rect();

myPaint.setTextSize(100.0f);
myPaint.getTextBounds( text, 0, text.length(), rc );

// draw outline of text
myPaint.setStyle(Paint.Style.FILL_AND_STROKE );
myPaint.setStrokeWidth( strokeWidth );
myPaint.setColor( 0xff8000ff );
canvas.drawText( text, ( -rc.left ) + strokeWidth / 2.0f, (-rc.top) + strokeWidth / 2.0f, myPaint );

// draw text
myPaint.setStyle(Paint.Style.FILL );
myPaint.setColor( 0xff000000 );
canvas.drawText( text, ( -rc.left ) + strokeWidth / 2.0f, (-rc.top) + strokeWidth / 2.0f, myPaint );

 

결과물

 

가로정렬과 세로정렬

이제 getTextBounds를 이용하여 차지하는 크기계산법을 익혔으니, 실제 정렬을 해보자.

 

가로 정렬

가로 정렬은 Paint객체가 setTextAlign함수로 바로 지원을 해준다. 따라서 특별한 계산을 요구하지는 않는다.

 

파리미터는 다음과 같이 3개지가 있다.

Paint.Align.LEFT, Paint.Align.CENTER, Paint.Align.RIGHT

이 파리미터에 따라 drawText함수의 x값을 기준점으로 정렬을 하게 된다.

 

정렬의 3가지

paint.setTextAlign( Paint.Align.CENTER );

세로정렬

새로로 정렬을 할 경우는 baseline을 계산을 해야 한다. [안드로이드 FontMetrics]편에서도 언급했듯이, baseline을 가운데로 잡을 수가 없기 때문에, 계산을 해서 내려줘야 한다.

세로 정렬 baseline 계산

위의 그림에 따라 세로정렬을 위한 계산식은 다음과 같다.

 

y1 = ( H - h ) / 2
y2 = -ascent
baseline = y1 + y2 = (H-h)/2 - ascent

 

세로정렬을 함수로 만들어 두었다.

// offsetX, Y : parent left-top corner
private static void DrawTextOnCenterOfParent( String text, Canvas canvas, Paint paint, float parentW, float parentH, float textSize, int textColor, float offsetX, float offsetY )
{
    float drawX, drawY;

    // save old values
    float       oldTextSize = paint.getTextSize();
    Paint.Align oldAlign    = paint.getTextAlign();
    int         oldColor    = paint.getColor();
    Rect        rcBound     = new Rect();



    paint.setTextSize( textSize );
    paint.getTextBounds( text, 0, text.length(), rcBound );


    // compute position
    drawX = offsetX + parentW / 2.0f; // horizontal center
    drawY = offsetY + ( parentH - rcBound.height() ) / 2.0f - rcBound.top; // baseline

    paint.setTextAlign( Paint.Align.CENTER );
    paint.setColor( textColor );
    canvas.drawText( text, drawX, drawY, paint );

    // restore old values
    paint.setTextSize( oldTextSize );
    paint.setTextAlign( oldAlign );
    paint.setColor( oldColor );
}

 

Replies
Reply Write