Browse Source

add FontUtil

Looly 5 years ago
parent
commit
73b2f75de2

+ 1 - 0
CHANGELOG.md

@@ -16,6 +16,7 @@
 * 【core   】     BeanValuePovider转换失败时,返回原数据,而非null
 * 【core   】     支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee)
 * 【core   】     MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee)
+* 【core   】     增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee)
 
 ### Bug修复
 * 【core   】     修复SimpleCache死锁问题(issue#I1HOKB@Gitee)

+ 123 - 0
hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java

@@ -0,0 +1,123 @@
+package cn.hutool.core.img;
+
+import cn.hutool.core.exceptions.UtilException;
+import cn.hutool.core.io.IORuntimeException;
+import sun.font.FontDesignMetrics;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.awt.FontMetrics;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * AWT中字体相关工具类
+ *
+ * @author looly
+ * @since 5.3.6
+ */
+public class FontUtil {
+
+	/**
+	 * 创建默认字体
+	 *
+	 * @return 默认字体
+	 */
+	public static Font createFont() {
+		return new Font(null);
+	}
+
+	/**
+	 * 创建SansSerif字体
+	 *
+	 * @param size 字体大小
+	 * @return 字体
+	 */
+	public static Font createSansSerifFont(int size) {
+		return createFont(Font.SANS_SERIF, size);
+	}
+
+	/**
+	 * 创建指定名称的字体
+	 *
+	 * @param name 字体名称
+	 * @param size 字体大小
+	 * @return 字体
+	 */
+	public static Font createFont(String name, int size) {
+		return new Font(name, Font.PLAIN, size);
+	}
+
+	/**
+	 * 根据文件创建字体<br>
+	 * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT}
+	 *
+	 * @param fontFile 字体文件
+	 * @return {@link Font}
+	 */
+	public static Font createFont(File fontFile) {
+		try {
+			return Font.createFont(Font.TRUETYPE_FONT, fontFile);
+		} catch (FontFormatException e) {
+			// True Type字体无效时使用Type1字体
+			try {
+				return Font.createFont(Font.TYPE1_FONT, fontFile);
+			} catch (Exception e1) {
+				throw new UtilException(e);
+			}
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+	}
+
+	/**
+	 * 根据文件创建字体<br>
+	 * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT}
+	 *
+	 * @param fontStream 字体流
+	 * @return {@link Font}
+	 */
+	public static Font createFont(InputStream fontStream) {
+		try {
+			return Font.createFont(Font.TRUETYPE_FONT, fontStream);
+		} catch (FontFormatException e) {
+			// True Type字体无效时使用Type1字体
+			try {
+				return Font.createFont(Font.TYPE1_FONT, fontStream);
+			} catch (Exception e1) {
+				throw new UtilException(e1);
+			}
+		} catch (IOException e) {
+			throw new IORuntimeException(e);
+		}
+	}
+
+	/**
+	 * 获得字体对应字符串的长宽信息
+	 *
+	 * @param font 字体
+	 * @param str  字符串
+	 * @return 长宽信息
+	 */
+	public static Dimension getDimension(Font font, String str) {
+		final FontMetrics metrics = FontDesignMetrics.getMetrics(font);
+		return getDimension(FontDesignMetrics.getMetrics(font), str);
+	}
+
+	/**
+	 * 获得字体对应字符串的长宽信息
+	 *
+	 * @param metrics {@link FontMetrics}
+	 * @param str  字符串
+	 * @return 长宽信息
+	 */
+	public static Dimension getDimension(FontMetrics metrics, String str) {
+		final int width = metrics.stringWidth(str);
+		final int height = metrics.getAscent() - metrics.getLeading() - metrics.getDescent();
+
+		return new Dimension(width, height);
+	}
+
+}

+ 118 - 18
hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java

@@ -1,16 +1,23 @@
 package cn.hutool.core.img;
 
+import cn.hutool.core.util.ObjectUtil;
+
+import java.awt.AlphaComposite;
 import java.awt.Color;
+import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
 
 /**
  * {@link Graphics}相关工具类
- * 
+ *
  * @author looly
  * @since 4.5.2
  */
@@ -18,7 +25,7 @@ public class GraphicsUtil {
 
 	/**
 	 * 创建{@link Graphics2D}
-	 * 
+	 *
 	 * @param image {@link BufferedImage}
 	 * @param color {@link Color}背景颜色以及当前画笔颜色,{@code null}表示不设置背景色
 	 * @return {@link Graphics2D}
@@ -26,8 +33,8 @@ public class GraphicsUtil {
 	 */
 	public static Graphics2D createGraphics(BufferedImage image, Color color) {
 		final Graphics2D g = image.createGraphics();
-		
-		if(null != color) {
+
+		if (null != color) {
 			// 填充背景
 			g.setColor(color);
 			g.fillRect(0, 0, image.getWidth(), image.getHeight());
@@ -39,8 +46,8 @@ public class GraphicsUtil {
 	/**
 	 * 获取文字居中高度的Y坐标(距离上边距距离)<br>
 	 * 此方法依赖FontMetrics,如果获取失败,默认为背景高度的1/3
-	 * 
-	 * @param g {@link Graphics2D}画笔
+	 *
+	 * @param g                {@link Graphics2D}画笔
 	 * @param backgroundHeight 背景高度
 	 * @return 最小高度,-1表示无法获取
 	 * @since 4.5.17
@@ -64,11 +71,11 @@ public class GraphicsUtil {
 
 	/**
 	 * 绘制字符串,使用随机颜色,默认抗锯齿
-	 * 
-	 * @param g {@link Graphics}画笔
-	 * @param str 字符串
-	 * @param font 字体
-	 * @param width 字符串总宽度
+	 *
+	 * @param g      {@link Graphics}画笔
+	 * @param str    字符串
+	 * @param font   字体
+	 * @param width  字符串总宽度
 	 * @param height 字符串背景高度
 	 * @return 画笔对象
 	 * @since 4.5.10
@@ -79,12 +86,12 @@ public class GraphicsUtil {
 
 	/**
 	 * 绘制字符串,默认抗锯齿
-	 * 
-	 * @param g {@link Graphics}画笔
-	 * @param str 字符串
-	 * @param font 字体
-	 * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机)
-	 * @param width 字符串背景的宽度
+	 *
+	 * @param g      {@link Graphics}画笔
+	 * @param str    字符串
+	 * @param font   字体
+	 * @param color  字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机)
+	 * @param width  字符串背景的宽度
 	 * @param height 字符串背景的高度
 	 * @return 画笔对象
 	 * @since 4.5.10
@@ -98,7 +105,7 @@ public class GraphicsUtil {
 		g.setFont(font);
 
 		// 文字高度(必须在设置字体后调用)
-		int midY = GraphicsUtil.getCenterY(g, height);
+		int midY = getCenterY(g, height);
 		if (null != color) {
 			g.setColor(color);
 		}
@@ -115,4 +122,97 @@ public class GraphicsUtil {
 		return g;
 	}
 
+	/**
+	 * 绘制字符串,默认抗锯齿。<br>
+	 * 此方法定义一个矩形区域和坐标,文字基于这个区域中间偏移x,y绘制。
+	 *
+	 * @param g         {@link Graphics}画笔
+	 * @param str       字符串
+	 * @param font      字体,字体大小决定了在背景中绘制的大小
+	 * @param color     字体颜色,{@code null} 表示使用黑色
+	 * @param rectangle 字符串绘制坐标和大小,此对象定义了绘制字符串的区域大小和偏移位置
+	 * @return 画笔对象
+	 * @since 4.5.10
+	 */
+	public static Graphics drawString(Graphics g, String str, Font font, Color color, Rectangle rectangle) {
+		// 背景长宽
+		final int backgroundWidth = rectangle.width;
+		final int backgroundHeight = rectangle.height;
+
+		//获取字符串本身的长宽
+		Dimension dimension;
+		try {
+			dimension = FontUtil.getDimension(g.getFontMetrics(font), str);
+		} catch (Exception e) {
+			// 此处报告bug某些情况下会抛出IndexOutOfBoundsException,在此做容错处理
+			dimension = new Dimension(backgroundWidth / 3, backgroundHeight / 3);
+		}
+
+		rectangle.setSize(dimension.width, dimension.height);
+		final Point point = ImgUtil.getPointBaseCentre(rectangle, backgroundWidth, backgroundHeight);
+
+		return drawString(g, str, font, color, point);
+	}
+
+	/**
+	 * 绘制字符串,默认抗锯齿
+	 *
+	 * @param g     {@link Graphics}画笔
+	 * @param str   字符串
+	 * @param font  字体,字体大小决定了在背景中绘制的大小
+	 * @param color 字体颜色,{@code null} 表示使用黑色
+	 * @param point 绘制字符串的位置坐标
+	 * @return 画笔对象
+	 * @since 5.3.6
+	 */
+	public static Graphics drawString(Graphics g, String str, Font font, Color color, Point point) {
+		// 抗锯齿
+		if (g instanceof Graphics2D) {
+			((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+		}
+
+		g.setFont(font);
+		g.setColor(ObjectUtil.defaultIfNull(color, Color.BLACK));
+		g.drawString(str, point.x, point.y);
+
+		return g;
+	}
+
+	/**
+	 * 绘制图片
+	 *
+	 * @param g         画笔
+	 * @param img       要绘制的图片
+	 * @param point 绘制的位置,基于左上角
+	 * @return 画笔对象
+	 */
+	public static Graphics drawImg(Graphics g, Image img, Point point) {
+		return drawImg(g, img,
+				new Rectangle(point.x, point.y, img.getWidth(null), img.getHeight(null)));
+	}
+
+	/**
+	 * 绘制图片
+	 *
+	 * @param g         画笔
+	 * @param img       要绘制的图片
+	 * @param rectangle 矩形对象,表示矩形区域的x,y,width,height,,基于左上角
+	 * @return 画笔对象
+	 */
+	public static Graphics drawImg(Graphics g, Image img, Rectangle rectangle) {
+		g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图
+		return g;
+	}
+
+	/**
+	 * 设置画笔透明度
+	 *
+	 * @param g 画笔
+	 * @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
+	 * @return 画笔
+	 */
+	public static Graphics2D setAlpha(Graphics2D g, float alpha){
+		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+		return g;
+	}
 }

+ 29 - 19
hutool-core/src/main/java/cn/hutool/core/img/Img.java

@@ -15,9 +15,9 @@ import javax.imageio.stream.ImageOutputStream;
 import java.awt.AlphaComposite;
 import java.awt.Color;
 import java.awt.Font;
-import java.awt.FontMetrics;
 import java.awt.Graphics2D;
 import java.awt.Image;
+import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Toolkit;
@@ -145,13 +145,13 @@ public class Img implements Serializable {
 	/**
 	 * 构造
 	 *
-	 * @param srcImage 来源图片
+	 * @param srcImage        来源图片
 	 * @param targetImageType 目标图片类型
 	 * @since 5.0.7
 	 */
 	public Img(BufferedImage srcImage, String targetImageType) {
 		this.srcImage = srcImage;
-		if(null == targetImageType){
+		if (null == targetImageType) {
 			targetImageType = ImgUtil.IMAGE_TYPE_JPG;
 		}
 		this.targetImageType = targetImageType;
@@ -312,7 +312,7 @@ public class Img implements Serializable {
 		Graphics2D g = image.createGraphics();
 
 		// 设置背景
-		if(null != fixedColor){
+		if (null != fixedColor) {
 			g.setBackground(fixedColor);
 			g.clearRect(0, 0, width, height);
 		}
@@ -450,20 +450,22 @@ public class Img implements Serializable {
 
 		if (null == font) {
 			// 默认字体
-			font = new Font("Courier", Font.PLAIN, (int) (targetImage.getHeight() * 0.75));
+			font = FontUtil.createSansSerifFont((int) (targetImage.getHeight() * 0.75));
 		}
-
-		// 抗锯齿
-		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-		g.setColor(color);
-		g.setFont(font);
 		// 透明度
 		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
-		// 在指定坐标绘制水印文字
-		final FontMetrics metrics = g.getFontMetrics(font);
-		final int textLength = metrics.stringWidth(pressText);
-		final int textHeight = metrics.getAscent() - metrics.getLeading() - metrics.getDescent();
-		g.drawString(pressText, Math.abs(targetImage.getWidth() - textLength) / 2 + x, Math.abs(targetImage.getHeight() + textHeight) / 2 + y);
+
+		// 绘制
+		if (positionBaseCentre) {
+			// 基于中心绘制
+			GraphicsUtil.drawString(g, pressText, font, color,
+					new Rectangle(x, y, targetImage.getWidth(), targetImage.getHeight()));
+		} else {
+			// 基于左上角绘制
+			GraphicsUtil.drawString(g, pressText, font, color,
+					new Point(x, y));
+		}
+
 		g.dispose();
 		this.targetImage = targetImage;
 
@@ -497,7 +499,6 @@ public class Img implements Serializable {
 	public Img pressImage(Image pressImg, Rectangle rectangle, float alpha) {
 		final Image targetImg = getValidSrcImg();
 
-		fixRectangle(rectangle, targetImg.getWidth(null), targetImg.getHeight(null));
 		this.targetImage = draw(ImgUtil.toBufferedImage(targetImg), pressImg, rectangle, alpha);
 		return this;
 	}
@@ -619,12 +620,21 @@ public class Img implements Serializable {
 	 * @param backgroundImg 背景图片
 	 * @param img           要绘制的图片
 	 * @param rectangle     矩形对象,表示矩形区域的x,y,width,height,x,y从背景图片中心计算
+	 * @param alpha         透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
 	 * @return 绘制后的背景
 	 */
-	private static BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) {
+	private BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) {
 		final Graphics2D g = backgroundImg.createGraphics();
-		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
-		g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图
+		GraphicsUtil.setAlpha(g, alpha);
+
+		Point point;
+		if (positionBaseCentre) {
+			point = ImgUtil.getPointBaseCentre(rectangle, backgroundImg.getWidth(), backgroundImg.getHeight());
+		} else {
+			point = new Point(rectangle.x, rectangle.y);
+		}
+		GraphicsUtil.drawImg(g, img, point);
+
 		g.dispose();
 		return backgroundImg;
 	}

+ 19 - 26
hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java

@@ -2,7 +2,6 @@ package cn.hutool.core.img;
 
 import cn.hutool.core.codec.Base64;
 import cn.hutool.core.convert.Convert;
-import cn.hutool.core.exceptions.UtilException;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.IORuntimeException;
 import cn.hutool.core.io.IoUtil;
@@ -25,10 +24,10 @@ import javax.imageio.stream.ImageInputStream;
 import javax.imageio.stream.ImageOutputStream;
 import java.awt.Color;
 import java.awt.Font;
-import java.awt.FontFormatException;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Image;
+import java.awt.Point;
 import java.awt.Rectangle;
 import java.awt.font.FontRenderContext;
 import java.awt.geom.AffineTransform;
@@ -1377,18 +1376,7 @@ public class ImgUtil {
 	 * @since 3.0.9
 	 */
 	public static Font createFont(File fontFile) {
-		try {
-			return Font.createFont(Font.TRUETYPE_FONT, fontFile);
-		} catch (FontFormatException e) {
-			// True Type字体无效时使用Type1字体
-			try {
-				return Font.createFont(Font.TYPE1_FONT, fontFile);
-			} catch (Exception e1) {
-				throw new UtilException(e);
-			}
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		}
+		return FontUtil.createFont(fontFile);
 	}
 
 	/**
@@ -1400,18 +1388,7 @@ public class ImgUtil {
 	 * @since 3.0.9
 	 */
 	public static Font createFont(InputStream fontStream) {
-		try {
-			return Font.createFont(Font.TRUETYPE_FONT, fontStream);
-		} catch (FontFormatException e) {
-			// True Type字体无效时使用Type1字体
-			try {
-				return Font.createFont(Font.TYPE1_FONT, fontStream);
-			} catch (Exception e1) {
-				throw new UtilException(e1);
-			}
-		} catch (IOException e) {
-			throw new IORuntimeException(e);
-		}
+		return FontUtil.createFont(fontStream);
 	}
 
 	/**
@@ -1917,4 +1894,20 @@ public class ImgUtil {
 		}
 		return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
 	}
+
+	/**
+	 * 获得修正后的矩形坐标位置,变为以背景中心为基准坐标(即x,y == 0,0时,处于背景正中)
+	 *
+	 * @param rectangle  矩形
+	 * @param backgroundWidth  参考宽(背景宽)
+	 * @param backgroundHeight  参考高(背景高)
+	 * @return 修正后的{@link Point}
+	 * @since 5.3.6
+	 */
+	public static Point getPointBaseCentre(Rectangle rectangle, int backgroundWidth, int backgroundHeight) {
+		return new Point(
+				rectangle.x + (Math.abs(backgroundWidth - rectangle.width) / 2), //
+				rectangle.y + (Math.abs(backgroundHeight - rectangle.height) / 2)//
+		);
+	}
 }