2014년 1월 29일 수요일

java awt 에서 문자열 출력

GUI를 위한 프로그램을 구성할 때 언제나 좌표와 크기 같은 것을 제어해 주어야 하기 때문에 번거 롭다는 생각이 듭니다.

특히 문자열을 출력할 때 그러한 경향이 더 나타나는 것 같습니다.

java.awt.Graphics 또는 Graphics2D class drawString 메소드는 문자열을 출력할 수 있는 메소드 입니다.

기본적인 drawString method 원형은 drawString(String str, int x, int y) 입니다.
여기서 잠깐 살펴보아야 할 부분은  x, y가 FontMetrics 의 baseline 을 기준으로 하고 있는 점입니다.

FontMetrics 객체는 Graphics 객체의 getFontMetrics() 메소드로 가져올 수 있습니다.

FontMetrics의 baseline은 영문자의 예로 Pig 라는 단어가 있을 때 대문자 P와 소문자 i 으 하단을 의미한다고 보시면 됩니다. g 는 baseline 아래에 약간의 영역에 출력하고 있습니다.
Font 에서 보면 baseline을 기준으로 그 위를 getAscent() ,아래를 getDescent() 메소드로 얻어 올 수 있습니다. 하나더 getLeading() 이라고 하여 line 사이의 값을 가져 올 수 있습니다.   하지만 api 에 언급되어 있듯 어떤 문자는 위에서 가져온 값과 달리 표기 될 수도 있습니다.  대략적인 값으로 이해 하면 될 것 같습니다.

그럼 일반적인 메소드 처럼 원하는 위치에 출력하고 싶다면 일반적으로 다음과 같은 방식을 쓸 수 있습니다.
String str = "msg";
int x   = 0;
int y   = 0;
int fontHeight = ((Graphics)gr).getFontMetrics().getHeight();

getHeight() 메소드는 일반적으로 ( getAscent() + getDescent() + getLeading() ) 의 크기 입니다.

y += fontHeight;

글꼴, style, 크기 는 Font class 를 통해 변경할 수 있습니다.
Font 는 현재 폰트로 부터 유도해서 만들 수도 있고 새롭게 생성해서 설정 할 수도 있습니다.
예를 들어 현재 폰트에서 크기와 스타일을 변경 하고 싶다면 다음과 같이 할 수 있습니다.
(gr 은 Graphics or Graphics2D 객체 )

int styleType = Font.BOLD | Font.ITALIC;
float pointSize = 12.0f;
Font defFont = gr.getFont();
Font derivedFont = defFont.deriveFont(styleType, pointSize);
gr.setFont(derivedFont);

만약 영역을 정해서 수직 수평 정렬을 하고 싶다면 앞서 언급한 내용을 이용 하시면 됩니다.
시작점인 x,y 가 있고 크기가 clipWidth, clipHeight 라고 한다면
String msg = "Test Program";

FontMetrics fm = gr.getFontMetrics();
int ftSize = fm.stringWidth(msg);

int nx = x +  (clipWidth-ftSize)/2;
int ny = (y-clipHeight+((clipHeight-(fm.getAscent()))/2));

gr.drawString(msg, nx,y);

여기서 getAscent() 를 이용하는 이유는 Font의 위치를 설정하기 위해서는 baseline 위의 영역만을 대상으로 하는게 중앙정렬처럼 보이기 때문입니다.

만약 수직 수평 정렬을 변경하고 싶다면 위의 nx, ny 값 계산을 약간만 변형하면 됩니다.

정렬은 위의 방식으로 할 수 있다고 해도 크기를 제한해서 크기가 넘어가면 안보이게 하고 싶다면, 위의 계산을 변형해서 해도 되지만, clip을 이용하는게 더 직관적일 것 같습니다.

위의 예에서 본다면
먼저 gr.setClip((x-1), ny, clipWidth, clipHeight) 를 구성 하시면 됩니다.
그 다음 gr.drawString(msg,nx,y); 를 호출한다면 영역을 넘어서는 문자는 출력되지 않습니다.
x-1을 한 이유는 clip 영역에 line을 그릴 경우 글자와 겹치는 부분을 피하기 위함 입니다. 정렬등의 계산에서는 그 보정치를 적용해 주시면 될 것 같습니다.

혹시 x,y의 포지션은 그대로 두고 클립영역을 기준으로 그 영역에서 중앙값을 조정하시려면
clip 영역은 단순하게 new Rectangle(x,y-clipHeight, clipWidth, chlipHeight) 의 사각형을 만들고, 다만 drawString의 x,y position 을 변경해 주시면 됩니다.
위에서 계산한 것을 예를 들면
x = nx;
y -= ((clipHeight-(fm.getAscent()))/2);
drawString(msg,x,y);

경우에 따라 문자열을 다양한 각도로 출력하는 경우가 발생할 수 있습니다.
그럴 때는 여러방법이 있겠지만, 다음의 두가지 방법중 편한 방법으로 각도를 조정할 수 있습니다.

먼저 Font 를 생성할 때 rotate 된 Font 를 사용하는 방법입니다.
일반적인 각도입니다.

double degree = 45.0;
AffineTransform affine = new AffineTransform();
affine.rotate(Math.toRadians(degree));
Font   derivedFont = gr.getFont().deriveFont(affine);
gr.setFont(derivedFont);
gr.drawString(msg,x,y);

다음은 Graphics 객체 자체를 rotate 시키는 방법입니다.
gr.rotate(Math.toRadians(degree), x, y);
gr.drawString(msg,x,y);

이 방법은 그리는 객체 전체를 움직인 것이라서 그후 출력되는 모든 상은 변경된 내용을 기준으로 하게 됩니다.

문자열의 품질은
gr.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
gr.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
gr.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
등을 적용하여 확인해 보실 수 있습니다.



java awt 혹은 swing 을 이용해서 문자열을 출력할 때 사용할 수 있는 방법을 간략히 살펴 보았습니다.

변경된 내용은 Graphics 객체에 계속 영향을 미치기 때문에 다른 그림 혹은 문자를 출력할 때에도 설정 내용이 적용됩니다.   그렇기 때문에 혹시 원래대로 환원시킬려면 변경전 상태를 저장하고 있다가, 다 출력후 초기 상태로 돌려 놓는 것이 필요할 것 같습니다.














댓글 1개: