Step By Step(Java 2D图形篇<四>)
12. 字体:
1) 通过之前介绍的图形环境(GraphicsEnvironment)工具类获取当前系统支持的所有字体。
1 public class MyTest {
2 public static void main(String[] args) {
3 String[] fontFamilies = GraphicsEnvironment.getLocalGraphicsEnvironment()
4 .getAvailableFontFamilyNames();
5 for (String fn : fontFamilies)
6 System.out.println(fn);
7 Font[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
8 for (Font f : fonts) {
9 System.out.print(f.getFontName() + " : ");
10 System.out.print(f.getFamily() + " : ");
11 System.out.print(f.getName());
12 System.out.println();
13 }
14 }
15 }
2) 动态加载TTF字体文件。
该示例同时加载了一种字体的两种形式:正常和粗体,以便于观察渲染后的结果。
1 public class MyTest extends JPanel {
2 private Font font;
3 private Font boldFont;
4
5 MyTest() {
6 InputStream fin = null;
7 InputStream finBold = null;
8 try {
9 fin = new FileInputStream("D:/arial.ttf");
10 finBold = new FileInputStream("D:/arialbd.ttf");
11 font = Font.createFont(Font.TRUETYPE_FONT, fin).deriveFont(50.0f);
12 boldFont = Font.createFont(Font.TRUETYPE_FONT, finBold).deriveFont(50.0f);
13 } catch (Exception e) {
14 } finally {
15 try {
16 fin.close();
17 finBold.close();
18 } catch (IOException e) {
19 }
20 }
21 }
22 public void paintComponent(Graphics g) {
23 super.paintComponent(g);
24 Graphics2D g2 = (Graphics2D) g;
25 g2.setFont(font);
26 g2.drawString("Normal Hello", 100, 100);
27 g2.setFont(boldFont);
28 g2.drawString("Bold Hello", 100, 300);
29 }
30 public static void main(String[] args) {
31 JFrame frame = new JFrame();
32 frame.setTitle("Load TTF From File");
33 frame.addWindowListener(new WindowAdapter() {
34 public void windowClosing(WindowEvent e) {
35 System.exit(0);
36 }
37 });
38 frame.setContentPane(new MyTest());
39 frame.setSize(600, 400);
40 frame.setVisible(true);
41 }
42 }
3) 绘制指定字体的文本信息:
在我们的日常开发中,基于文本信息的应用更多的是输出日志、给出提示信息和填充字符类型的数据库字段等,即便是在Swing的控件中显示这些文字提示时, 也是通过控件提供的属性修改器方法来协助完成的。那么Swing的控件又是如何做到的呢?试想一下,JButton上的文字是如何绘制的?
1 public class MyTest extends JPanel {
2 public static void main(String args[]) {
3 JFrame f = new JFrame("Strings");
4 f.setSize(360, 300);
5 f.add(new MyTest());
6 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
7 f.setLocationRelativeTo(null);
8 f.setVisible(true);
9 }
10 public MyTest() {
11 setBackground(Color.white);
12 setForeground(Color.white);
13 }
14 @Override
15 public void paintComponent(Graphics g) {
16 super.paintComponent(g);
17 g.setColor(Color.red);
18 paintString(g);
19 }
20 //绘制文本信息
21 private void paintString(Graphics g) {
22 //1. 生成待绘制文本信息的字体对象
23 Font fi = new Font("SansSerif", Font.BOLD + Font.ITALIC, 18);
24 //2. 获取该字体对象的metadata信息。
25 FontMetrics fm = g.getFontMetrics(fi);
26 String s1 = "Hello World";
27 //根据字体的metadata信息获取带绘制字符串的宽度,
28 //不同的字体,相同字体不同的字号,或者其他属性的不同
29 //都将导致带绘制字符串的宽度不同。
30 int width1 = fm.stringWidth(s1);
31 //根据当前窗体的size和字符串的宽度与高度来计算绘制的起始坐标。
32 Dimension d = getSize();
33 int cx = (d.width - width1) / 2;
34 int cy = (d.height - fm.getHeight()) / 2 + fm.getAscent();
35 g.setFont(fi);
36 //绘制字符串
37 g.drawString(s1, cx, cy);
38 }
39 }
4) 绘制带有纹理的文本信息。
1 public class MyTest extends JPanel {
2 public void paintComponent(Graphics g) {
3 super.paintComponent(g);
4 Graphics2D g2 = (Graphics2D) g;
5 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
6 g2.setFont(new Font("Times New Roman", Font.PLAIN, 72));
7 String s = "Hello World";
8 float x = 20, y = 100;
9 BufferedImage bi = getTextureImage();
10 Rectangle r = new Rectangle(0, 0, bi.getWidth(), bi.getHeight());
11 TexturePaint tp = new TexturePaint(bi, r);
12 g2.setPaint(tp);
13 g2.drawString(s, x, y);
14 }
15 //构造一个正方形图形,该正方形被切成四个部分,每个部分不同的颜色。
16 private BufferedImage getTextureImage() {
17 int size = 8;
18 BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
19 Graphics2D g2 = bi.createGraphics();
20 g2.setPaint(Color.red);
21 g2.fillRect(0, 0, size / 2, size / 2);
22 g2.setPaint(Color.yellow);
23 g2.fillRect(size / 2, 0, size, size / 2);
24 g2.setPaint(Color.green);
25 g2.fillRect(0, size / 2, size / 2, size);
26 g2.setPaint(Color.blue);
27 g2.fillRect(size / 2, size / 2, size, size);
28 return bi;
29 }
30 public static void main(String[] args) {
31 JFrame frame = new JFrame();
32 frame.setTitle("Paint Font with Texture");
33 frame.addWindowListener(new WindowAdapter() {
34 public void windowClosing(WindowEvent e) {
35 System.exit(0);
36 }
37 });
38 frame.setContentPane(new MyTest());
39 frame.setSize(800,200);
40 frame.setVisible(true);
41 }
42 }
5) 绘制文本图形对象的轮廓
1 public class MyTest extends JPanel {
2 private int width = 600;
3 private int height = 400;
4 private Font f = new Font("Helvetica", 1, 60);
5 private String s = "Hello World";
6 public void paintComponent(Graphics g) {
7 super.paintComponent(g);
8 Graphics2D g2 = (Graphics2D) g;
9 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING
10 , RenderingHints.VALUE_ANTIALIAS_ON);
11 //1. 根据字体、字符串和Graphics的字体渲染上下文对象获取文本布局对象
12 TextLayout textTl = new TextLayout(s, f, g2.getFontRenderContext());
13 //2. 通过文本布局对象取得字符串的outline(以Shape的方式获得)
14 Shape outline = textTl.getOutline(null);
15 //3. 获取字符串图形对象outline的边界矩形
16 Rectangle outlineBounds = outline.getBounds();
17 //4. 像绘制其他普通形状一样绘制outline
18 AffineTransform transform = new AffineTransform();
19 transform = g2.getTransform();
20 transform.translate(width / 2 - (outlineBounds.width / 2),
21 height / 2 + (outlineBounds.height / 2));
22 g2.transform(transform);
23 g2.setColor(Color.blue);
24 g2.draw(outline);
25 g2.setClip(outline);
26 }
27 public static void main(String[] args) {
28 JFrame frame = new JFrame();
29 frame.setTitle("Load TTF From File");
30 frame.addWindowListener(new WindowAdapter() {
31 public void windowClosing(WindowEvent e) {
32 System.exit(0);
33 }
34 });
35 frame.setContentPane(new MyTest());
36 frame.setSize(600, 400);
37 frame.setVisible(true);
38 }
39 }
6) 通过TextAttribute设置字体的属性,如下划线、背景色和前景色等:
1 public class MyTest extends JPanel {
2 public void paintComponent(Graphics g) {
3 super.paintComponent(g);
4 Graphics2D g2 = (Graphics2D) g;
5 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
6 Font font = new Font("Serif", Font.PLAIN, 40);
7 AttributedString as1 = new AttributedString("1234567890");
8 as1.addAttribute(TextAttribute.FONT, font);
9 g2.drawString(as1.getIterator(), 15, 60);
10 //通过最后两个参数可以给字符串中的子字符串添加属性,如果不指定则应用于所有字符。
11 //加下划线
12 as1.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON,3,4);
13 g2.drawString(as1.getIterator(), 15, 120);
14 //加背景色
15 as1.addAttribute(TextAttribute.BACKGROUND, Color.LIGHT_GRAY);
16 g2.drawString(as1.getIterator(), 15, 180);
17 //加横线
18 as1.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
19 g2.drawString(as1.getIterator(), 15, 240);
20 //加前景色
21 as1.addAttribute(TextAttribute.FOREGROUND, Color.red);
22 g2.drawString(as1.getIterator(), 15, 300);
23 }
24 public static void main(String[] args) {
25 JFrame frame = new JFrame();
26 frame.setTitle("Set Font Attribute with TextAttribute");
27 frame.addWindowListener(new WindowAdapter() {
28 public void windowClosing(WindowEvent e) {
29 System.exit(0);
30 }
31 });
32 frame.setContentPane(new MyTest());
33 frame.setSize(800, 500);
34 frame.setVisible(true);
35 }
36 }
7) 字体属性(FontMetrics)的常用参量:
Baseline: 字体的基线,如g.drawString("Hello",x,BaselineY)
getAscent(): 字体的上差值,在所有的数字和字符中,其最高点到基线的距离
getDescent(): 字体的下差值,在所有的数字和字符中,其最低点到基线的距离
getLeading(): 当前行的Descend到下一行Ascend之间的距离
getHeigth(): 字体的高度 = Ascent + Descent + Leading.
下面的示例代码在运行后,可以显示的给出各个参量所表示的具体范围。
1 public class MyTest extends JPanel {
2 public void paintComponent(Graphics g) {
3 super.paintComponent(g);
4 Graphics2D g2 = (Graphics2D) g;
5 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING
6 , RenderingHints.VALUE_ANTIALIAS_ON);
7 g2.setFont(new Font("SansSerif", 0, 200));
8 FontMetrics fm = g2.getFontMetrics();
9 int baseline = 300;
10 g2.drawString("By", 200,baseline);
11 g2.setColor(Color.red);
12 g2.setFont(new Font("SansSerif", 0, 15));
13 //可以看出Height = Ascent + Descent + Leading
14 int ascent = fm.getAscent();
15 int descent = fm.getDescent();
16 int leading = fm.getLeading();
17 int height = fm.getHeight();
18 System.out.printf("Height = %d\tAscent = %d\tDescent = %d\tLeading = %d"
19 ,height,ascent,descent,leading);
20 //在文字上绘制每一个常量,用一条横线予以标注
21 g2.drawLine(100, baseline, 500, baseline);
22 g2.drawLine(100, baseline - ascent, 500, baseline - ascent);
23 g2.drawLine(100, baseline + descent, 500, baseline + descent);
24 g2.drawLine(100, baseline - ascent - leading, 500, baseline - ascent - leading);
25 //指出基线的位置,和其他常量作用的范围。
26 g2.drawString("Baseline",510,baseline);
27 g2.drawLine(480, baseline - ascent, 480, baseline);
28 //绘出Ascent的范围
29 g2.drawString("Ascent", 510, baseline - ascent/2);
30 g2.drawLine(480, baseline, 480, baseline + descent);
31 //绘出Descent的范围
32 g2.drawString("Descent", 510, baseline + descent/2);
33 //绘出Leading的范围
34 g2.drawLine(480, baseline - ascent, 480, baseline - ascent - leading);
35 g2.drawString("Leading", 510, baseline - ascent - leading/2);
36 //绘出Height的范围
37 g2.drawLine(100,baseline - ascent - leading,100, baseline + descent);
38 g2.drawString("Height", 30, baseline - ascent - leading + height/2);
39 }
40 public static void main(String[] args) {
41 JFrame frame = new JFrame();
42 frame.setTitle("Show Font's Metrics Info");
43 frame.addWindowListener(new WindowAdapter() {
44 public void windowClosing(WindowEvent e) {
45 System.exit(0);
46 }
47 });
48 frame.setContentPane(new MyTest());
49 frame.setSize(800, 500);
50 frame.setVisible(true);
51 }
52 }
8) 通过FontMetrics获取单个字符在指定字体上的宽度和高度,再逐个绘制每个字符。
1 public class MyTest extends JPanel {
2 public void paintComponent(Graphics g) {
3 super.paintComponent(g);
4 Graphics2D g2 = (Graphics2D) g;
5 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
6 Font f = new Font("Serif",0, 100);
7 g.setFont(f);
8 FontMetrics fm = getFontMetrics(f);
9 String s = "HelloWorld";
10 int x = 5;
11 int y = 5;
12
13 for (int i = 0; i < s.length(); i++) {
14 char c = s.charAt(i);
15 int h = fm.getHeight();
16 int w = fm.charWidth(c);
17 System.out.printf("Letter = %c\t Width = %d\t Height = %d\n",c,w,h);
18 g.drawRect(x, y, w, h);
19 g.drawString(String.valueOf(c), x, y + h);
20 x = x + w;
21 }
22 }
23 public static void main(String[] args) {
24 JFrame frame = new JFrame();
25 frame.setTitle("Draw Char one by one");
26 frame.addWindowListener(new WindowAdapter() {
27 public void windowClosing(WindowEvent e) {
28 System.exit(0);
29 }
30 });
31 frame.setContentPane(new MyTest());
32 frame.setSize(600, 400);
33 frame.setVisible(true);
34 }
35 }
9) 通过TextLayout和LineBreakMeasurer对象绘制多行文本:如果当前的文本数据是由很多的字符构成,同时当前Graphics设置的字体尺寸也比较大,那么我们就无法在一行内显示所有的文本,超出显示组件宽度的文本将不会在屏幕上被渲染出来。为了让所有的文本都能够在当前组件中显示,我们就需要在渲染即将超出屏幕宽度之前,折行显示后面的文本信息。这里一个比较关键的技术点是我们如何获知在一定宽度内可以显示的字符数量。在Java 2D中我们可以通过TextLayout和LineBreakMeasurer来帮助我们完成这样的工作。
1 public class MyTest extends JPanel {
2 public void paintComponent(Graphics g) {
3 super.paintComponent(g);
4 Graphics2D g2 = (Graphics2D) g;
5 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING
6 , RenderingHints.VALUE_ANTIALIAS_ON);
7 String s = "A guide to architecting, designing, and building distributed " +
8 "applications with Windows Communication Foundation Windows " +
9 "Communication Foundation is the .NET technology that is used " +
10 "to build service-oriented applications, exchange messages in " +
11 "various communication scenarios, and run workflows.";
12 //1. 用多行字符串构造AttributedString,并设置初始化字体
13 AttributedString as = new AttributedString(s);
14 as.addAttribute(TextAttribute.FONT, new Font("serif", Font.PLAIN, 40));
15 //2. 获取文本和属性的迭代器
16 AttributedCharacterIterator aci = as.getIterator();
17 //3. 通过迭代器和Graphics的字体渲染上下文获取断行测量器
18 //该对象可以根据迭代器返回的文本信息和FontMetrics等属性值,再结合当前
19 //Graphics的渲染上下文计算出每行可以显示的文本。
20 LineBreakMeasurer lbm = new LineBreakMeasurer(aci,g2.getFontRenderContext());
21 //4. 结合JPanel的内边属性,计算每行显示的起始x坐标和宽度
22 Insets insets = getInsets();
23 float wrappingWidth = getSize().width - insets.left - insets.right;
24 float x = insets.left;
25 float y = insets.top;
26 //5. 开始计算每行可以显示的字符数量同时渲染本行的字符串。
27 //getPosition()返回当前正在渲染的字符在整个字符串中的索引值。
28 while (lbm.getPosition() < aci.getEndIndex()) {
29 //5.1 LineBreakMeasurer基于参数传入的宽度,并结合字体的Metrics,
30 //获取当前行应该渲染的文本布局对象。
31 TextLayout textLayout = lbm.nextLayout(wrappingWidth);
32 //5.2 y + ascent得到的是基线y坐标,并从该点开始绘制本行的文本
33 y += textLayout.getAscent();
34 textLayout.draw(g2, x, y);
35 //5.3 baseline + descent + leading得到下一行的ascent的y坐标
36 y += textLayout.getDescent() + textLayout.getLeading();
37 //5.4 将baseline的x坐标回归到行起点
38 x = insets.left;
39 }
40 }
41 public static void main(String[] args) {
42 JFrame frame = new JFrame();
43 frame.setTitle("Show String with Multiline.");
44 frame.addWindowListener(new WindowAdapter() {
45 public void windowClosing(WindowEvent e) {
46 System.exit(0);
47 }
48 });
49 frame.setContentPane(new MyTest());
50 frame.setSize(800, 500);
51 frame.setVisible(true);
52 }
53 }
10) 一个相对复杂而又步骤清晰的示例代码。
通过该示例,可以更好的理解之前介绍的Java 2D 技巧和本小节的字符绘制知识。
1 public class MyTest extends JPanel {
2 static final int WIDTH = 800, HEIGHT = 375;
3 public void paintComponent(Graphics g) {
4 super.paintComponent(g);
5 Graphics2D g2 = (Graphics2D) g;
6 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING
7 , RenderingHints.VALUE_ANTIALIAS_ON);
8 // 1. 通过渐变绘制整个背景--一个从左上角到右下角的渐变
9 g2.setPaint(new GradientPaint(0, 0, new Color(150, 0, 0)
10 , WIDTH, HEIGHT, new Color(200, 200, 255)));
11 g.fillRect(0, 0, WIDTH, HEIGHT);
12 // 2. 用渐变绘制宽度为15的边框--这里的渐变是通过Alpha值的变化体现出来的。
13 g2.setPaint(new GradientPaint(0, 0, new Color(0, 150, 0), 20, 20
14 , new Color(0, 150, 0, 0), true));
15 g2.setStroke(new BasicStroke(15));
16 g2.drawRect(25, 25, WIDTH - 50, HEIGHT - 50);
17 // 3. 基于Font对象绘制字符,为了显示方便我们需要一个放大的Font
18 Font font = new Font("Serif", Font.BOLD, 10); // a basic font
19 Font bigfont = font.deriveFont(AffineTransform.getScaleInstance(30.0, 30.0));
20 // 4. 基于这个放大的大字体(bigFont)和Graphics2D的字体渲染上下文,
21 // 将字符串"JAV"分解,再基于每个字符的outline生成Shape对象,这样,
22 // Graphics2D就可以想渲染普通形状一样渲染字体信息了。
23 GlyphVector gv = bigfont.createGlyphVector(g2.getFontRenderContext(), "JAV");
24 // 5. jshape 对应 J,ashape 对应 A,vshape 对应 V
25 Shape jshape = gv.getGlyphOutline(0);
26 Shape ashape = gv.getGlyphOutline(1);
27 Shape vshape = gv.getGlyphOutline(2);
28 // 6. 将字体阴影的颜色设置为半透的黑色
29 g2.setStroke(new BasicStroke(5.0f));
30 Paint shadowPaint = new Color(0, 0, 0, 100);
31 // 7. 阴影的光线是斜射的,因此这里需要做切变变化来模拟,再将阴影的Height缩小一倍。
32 AffineTransform shadowTransform = AffineTransform.getShearInstance(-1.0, 0.0);
33 shadowTransform.scale(1.0, 0.5);
34 g2.translate(65, 270);
35 // 8. 设置阴影的背景色
36 g2.setPaint(shadowPaint);
37 // 9. 调整阴影的绘制坐标,使他比原始图形的BASELINE下降一点儿来模拟投影的效果。
38 g2.translate(15, 20);
39 // 10.基于原始字符图形J和变换对象创建阴影图形对象,并绘制到Graphics中
40 g2.fill(shadowTransform.createTransformedShape(jshape));
41 // 11. 调整回原始图形J的BASELINE原点坐标
42 g2.translate(-15, -20); // Undo the translation above
43 // 12. 和绘制普通图形一样绘制基于字符生成的Shape对象,既可以填充也可以只是绘制边框。
44 g2.setPaint(Color.blue);
45 g2.fill(jshape);
46 g2.setPaint(Color.black);
47 g2.draw(jshape);
48 // 13. 绘制原始字符图形A和他的阴影,处理方式和上面J的完全一致。
49 g2.translate(75, 0);
50 g2.setPaint(shadowPaint);
51 g2.fill(shadowTransform.createTransformedShape(ashape));
52 g2.setPaint(new Color(0, 255, 0, 125));
53 g2.fill(ashape);
54 g2.setPaint(Color.black);
55 g2.draw(ashape);
56 // 14. 绘制字符V的阴影。
57 g2.translate(75, 0);
58 g2.setPaint(shadowPaint);
59 g2.fill(shadowTransform.createTransformedShape(vshape));
60 // 15. 通过纹理绘制字符V的原图形。
61 BufferedImage tile = new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
62 Graphics2D tg = tile.createGraphics();
63 // 15.1 纹理的背景为pink
64 tg.setColor(Color.pink);
65 tg.fillRect(0, 0, 50, 50);
66 // 15.2 纹理上绘制渐变的椭圆
67 tg.setPaint(new GradientPaint(40, 0, Color.green, 0, 40, Color.gray));
68 tg.fillOval(5, 5, 40, 40);
69 tg.dispose();
70 g2.setPaint(new TexturePaint(tile, new Rectangle(0, 0, 50, 50)));
71 g2.fill(vshape);
72 g2.setPaint(Color.black);
73 g2.draw(vshape);
74 }
75 public static void main(String[] args) {
76 JFrame frame = new JFrame();
77 frame.setTitle("A Good Example");
78 frame.addWindowListener(new WindowAdapter() {
79 public void windowClosing(WindowEvent e) {
80 System.exit(0);
81 }
82 });
83 frame.setContentPane(new MyTest());
84 frame.setSize(WIDTH, HEIGHT);
85 frame.setVisible(true);
86 }
87 }