OpenPDF的学习文档(第三章·字体样式和插入图片)
字体样式
- 根据PDF规范,在所有的PDF阅读器和编辑器中(包括浏览器Chrome,Edge等等),都内置了14种字体。
- 当使用这14种字体时,不用将字体嵌入到PDF中。
- 运行代码可以查看有哪几种字体家族和具体的字体名称。
-
// 获取已注册的字体家族 Set<String> registeredFamilies = FontFactory.getRegisteredFamilies(); log.info("registeredFamilies:{}", registeredFamilies); // 获取已注册的字体名称 Set<String> registeredFonts = FontFactory.getRegisteredFonts(); log.info("registeredFonts:{}", registeredFonts); - 五大类5字体家族,共14种字体:
helvetica无衬线字体 适合标题和现代排版- helvetica(常规)
- helvetica-bold(粗体)
- helvetica-oblique(斜体)
- helvetica-boldoblique(粗斜体)
courier等宽字体 展示代码或模拟打字机效果- courier(常规)
- courier-bold(粗体)
- courier-oblique(斜体)
- courier-boldoblique(粗斜体)
times-roman衬线字体 经典正文排版- times-roman(常规)
- times-bold(粗体)
- times-italic(斜体)
- times-bolditalic(粗斜体)
symbol数学与希腊字母符号字体zapfdingbats图标与装饰符号字体
- 在OpenPDF中,
time家族和times-roman家族是同一个家族。 - 在OpenPDF中,还另外拓展出了两种字体样式:
Font.UNDERLINE带下划线和Font.STRIKETHRU带删除线。 -
@Test public void font() throws IOException { Document document = new Document(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PdfWriter.getInstance(document, outputStream); document.open(); // 创建通用的分割线对象 Paragraph linePara = new Paragraph(); LineSeparator lineSeparator = new LineSeparator(1f, 100f, Color.GRAY, Element.ALIGN_CENTER, 0f); // linePara.setSpacingBefore(1f); // 线上方间距 linePara.setSpacingAfter(20f); // 线下方间距 linePara.add(lineSeparator); // 测试文本 String text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; String greece= "αβγδεζηθικλμνξοπρςστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΣΤΥΦΧΨΩ1234567890 My Name Is J.C"; document.add(new Paragraph(text, new Font(Font.HELVETICA, 12, Font.NORMAL, Color.BLACK))); // 常规 document.add(new Paragraph(text, new Font(Font.COURIER, 12, Font.NORMAL, Color.BLACK))); // 常规 document.add(new Paragraph(text, new Font(Font.TIMES_ROMAN, 12, Font.NORMAL, Color.BLACK))); // 常规 document.add(linePara); document.add(new Paragraph(text, new Font(Font.HELVETICA, 12, Font.BOLD, Color.BLACK))); // 粗体 document.add(new Paragraph(text, new Font(Font.COURIER, 12, Font.BOLD, Color.BLACK))); // 粗体 document.add(new Paragraph(text, new Font(Font.TIMES_ROMAN, 12, Font.BOLD, Color.BLACK))); // 粗体 document.add(linePara); document.add(new Paragraph(text, new Font(Font.HELVETICA, 12, Font.ITALIC, Color.BLACK))); // 斜体 document.add(new Paragraph(text, new Font(Font.COURIER, 12, Font.ITALIC, Color.BLACK))); // 斜体 document.add(new Paragraph(text, new Font(Font.TIMES_ROMAN, 12, Font.ITALIC, Color.BLACK))); // 斜体 document.add(linePara); document.add(new Paragraph(text, new Font(Font.HELVETICA, 12, Font.BOLDITALIC, Color.BLACK))); // 粗斜体 document.add(new Paragraph(text, new Font(Font.COURIER, 12, Font.BOLDITALIC, Color.BLACK))); // 粗斜体 document.add(new Paragraph(text, new Font(Font.TIMES_ROMAN, 12, Font.BOLDITALIC, Color.BLACK))); // 粗斜体 document.add(linePara); document.add(new Paragraph(text, new Font(Font.HELVETICA, 12, Font.UNDERLINE, Color.BLACK))); // 带下划线 document.add(new Paragraph(text, new Font(Font.COURIER, 12, Font.UNDERLINE, Color.BLACK))); // 带下划线 document.add(new Paragraph(text, new Font(Font.TIMES_ROMAN, 12, Font.UNDERLINE, Color.BLACK))); // 带下划线 document.add(linePara); document.add(new Paragraph(text, new Font(Font.HELVETICA, 12, Font.STRIKETHRU, Color.BLACK)));// 带删除线 document.add(new Paragraph(text, new Font(Font.COURIER, 12, Font.STRIKETHRU, Color.BLACK)));// 带删除线 document.add(new Paragraph(text, new Font(Font.TIMES_ROMAN, 12, Font.STRIKETHRU, Color.BLACK)));// 带删除线 document.add(linePara); // 这里是坑点,要想使用symbol字体不能这样写 // 虽然能显示,但是是以其他类型的字体渲染到PDF中的。 document.add(new Paragraph(greece, new Font(Font.SYMBOL, 12, Font.NORMAL, Color.BLACK))); // 得这样写 document.add(new Paragraph("abg", new Font(Font.SYMBOL, 12, Font.NORMAL, Color.BLACK))); document.add(new Paragraph("abg", new Font(Font.SYMBOL, 12, Font.UNDERLINE, Color.BLACK))); document.add(new Paragraph("abg", new Font(Font.SYMBOL, 12, Font.STRIKETHRU, Color.BLACK))); Font helvetica = new Font(Font.HELVETICA, 12, Font.NORMAL, Color.BLACK); document.add(SymbolsUtils.createPhraseWithSymbols(greece, helvetica)); // 同样的,使用zapfdingbats字体也不能直接写符号,只能写对应的映射 document.add(new Paragraph("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", new Font(Font.ZAPFDINGBATS, 12, Font.NORMAL, Color.BLACK))); // 在同一个自然段中写入不同字体的文字 Paragraph paragraph = new Paragraph(); paragraph.add(new Chunk("HELVETICA", new Font(Font.HELVETICA))); paragraph.add(new Chunk("COURIER", new Font(Font.COURIER))); paragraph.add(new Chunk("TIMES_ROMAN", new Font(Font.TIMES_ROMAN))); document.add(paragraph); // FontSelector document.close(); Files.write(Paths.get("C:/TMP/font.pdf"), outputStream.toByteArray()); } - 查看代码发现有一个坑点,如果想用
symbol字体,针对希腊文字αβγδεζ不能直接写αβγδεζ。- 方法一: 找到
symbol字体 希腊字母映射在英文字母对应的表。比如英文字母abg对应αβγ - 方法二: 循环使用
SpecialSymbol.get(c, defaultFont)方法,如果是希腊字母转换为对应的英文字母,否则使用defaultFont渲染文字。
- 方法一: 找到
symbol字体映射表zapfdingbats字体映射表- 还需要
ASCII码表,比如想输出✌,查看映射表发现是32+12=44,在ASCII码表中找到44对应的是逗号, document.add(new Paragraph(",", new Font(Font.ZAPFDINGBATS, 12, Font.NORMAL, Color.BLACK)));![image]()
- 当然也可以在一个自然段中写入不同字体,不同字号,不同样式,不同颜色的文字,使用
Chunk即可。 FontSelector是一个很现代化的类,它的作用是根据注册完成的字体,根据顺序来渲染文字。- 注意!!! 在使用
FontSelector的addFont()方法时,一定要注意先后顺序!!! -
// 伪代码 FontSelector fontSelector = new FontSelector(); fontSelector.addFont(new Font("英文字体A")); fontSelector.addFont(new Font("日文字体B")); fontSelector.addFont(new Font("中文字体C")); Phrase phrase = fontSelector.process("Hello World 你好世界 こんにちは世界"); paragraph.add(phrase); - 这里先注册的
英文字体A,后注册的日文字体B,最后注册的中文字体C。当OpenPDF渲染这个文本时。会对文本一个字符一个字符进行尝试。 H能不能用英文字体A渲染?能就继续下一个字符e,不能则看看能不能用日文字体B渲染;以此类推。- 当所有注册的字体都不能渲染上去,FontSelector 有一个托底注册的字体。
FontFactory.register("font-fallback/LiberationSans-Regular.ttf", "sans"); - 当托底注册的字体也不能正常渲染,那么这个字符就不再渲染。
- 注意!!! 在使用
文字排版(竖向)
-
// 书写方式为竖向 Font font = FontFactory.getFont("STSong-Light", "UniGB-UCS2-V", BaseFont.NOT_EMBEDDED, 14); Document document = new Document(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PdfWriter writer = PdfWriter.getInstance(document, outputStream); document.open(); // 关键点 PdfContentByte cb = writer.getDirectContent(); VerticalTextUtils v = new VerticalTextUtils(cb, 500f, 700f, 300f, 5, 24f); v.setPhrase(new Phrase("白日依山尽,黄河入海流。", font)); v.setPhrase(new Phrase("欲穷千里目,更上一层楼。", font)); document.close(); Files.write(Paths.get("C:/TMP/verticalText.pdf"), outputStream.toByteArray()); -
public class VerticalTextUtils { private final VerticalText vt; /** * 组装 VerticalText 的必要参数 * @param cb PdfContentByte * @param startX 从纸张的左下角算起的磅数(横向) * @param startY 从纸张的左下角算起的磅数(竖向) * @param height 竖向文本从上到下最大高度 * @param maxLines 竖向文本最大列数 * @param leading 列间距 */ public VerticalTextUtils(PdfContentByte cb, float startX, float startY, float height, int maxLines, float leading) { vt = new VerticalText(cb); vt.setVerticalLayout(startX, startY, height, maxLines, leading); } /** * 竖向文本(字体需要支持竖向,并且字体的 encoding 属性要设置为 {@link BaseFont#IDENTITY_V}) * @param phrase Phrase */ public void setPhrase(Phrase phrase) { this.vt.addText(phrase); this.vt.go(); } } ![image]()
文字连写
这个内容还有bug,没有验证完毕。
字体注册
- 我们先看看OpenPDF自带的东西。
- 打开
org.openpdf.text.pdf.fonts这个包,我们能看到许多的.afm文件.cmap文件和.properties文件.afm:代表PDF规范的14种字体。.properties:代表字体的高矮胖瘦,字体度量数据。cjkfonts.properties文件规定了 支持的中日韩字体(字体家族)和编码(书写方式)- 举例说明
# STSong-Light 是字体家族 # Adobe-GB1-UCS2_UniGB-UCS2-H_UniGB-UCS2-V_ 是书写方式,使用下划线分隔 # ST = 华文 SinoType、Song = 宋体、Light=细体 # Adobe-GB1-UCS2 = 使用中国国家标准(GB2312/GB18030)字符集 # UniGB-UCS2-H = 横向书写 # UniGB-UCS2-V = 竖向书写 STSong-Light=Adobe-GB1-UCS2_UniGB-UCS2-H_UniGB-UCS2-V_ # MSung-Light = 蒙纳宋体 # Adobe-CNS1-UCS2 = 台湾/香港繁体字符集(Big5 扩展) # UniCNS-UCS2-H = 横向书写 # UniCNS-UCS2-V = 竖向书写 MSung-Light=Adobe-CNS1-UCS2_UniCNS-UCS2-H_UniCNS-UCS2-V_ - 所以知道了字体家族和书写方式,我们就可以创建
Font字体对象了。 -
// 如果出现乱码,排版错误,则替换为BaseFont.EMBEDDED Font font = FontFactory.getFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, 14); - 如果阅读器没有自带CJK字体,会请求操作系统;如果操作系统也没有,就会有豆腐块的出现。
- 所以可以使用上文的
FontSelector,来构建一个能输出CJK + 英文 + 希腊字母 + 符号标签的程序。 - 如果OpenPDF自带的字体都不喜欢,有以下解决办法:
-
<!-- 安装 OpenPDF Fonts Extras --> <dependency> <groupId>com.github.librepdf</groupId> <artifactId>openpdf-fonts-extra</artifactId> <version>3.0.5</version> </dependency> - 它包含了
Liberation家族的LiberationMono等宽、LiberationSans无衬线、LiberationSerif有衬线 -
Font font = Liberation.MONO.create(10); -
// 不要轻易使用,建议先看看源码。 FontFactory.registerDirectories(); - 在网上下载其他字体,比如思源字体,Google Noto字体等等。
-
BaseFont bf = BaseFont.createFont( "fonts/SourceHanSansSC-Regular.ttf", BaseFont.IDENTITY_V, // 竖排 CMap BaseFont.EMBEDDED // 完整嵌入字体 ); Font font = new Font(bf, 14);
-
.cmap:主要是由Adobe对应的字符标识符CID(Character ID)- 在OpenPDF中,需要计算文本的换行、文字在页面上的坐标位置以及水平/竖向推进量。就是豆腐块的大小。
插入图片
- OpenPDF支持插入jpg、gif、png、bmp、wmf、tif这些类型的图片
- gif的图片只会截取第一帧,不会播放动画。
-
document.add(new Paragraph("A picture of my dog: otsoe.jpg")); Image jpg = Image.getInstanceFromClasspath("images/otsoe.jpg"); document.add(jpg); document.add(new Paragraph("getacro.gif")); Image gif = Image.getInstanceFromClasspath("images/getacro.gif"); document.add(gif); document.add(new Paragraph("git.gif")); Image git = Image.getInstanceFromClasspath("images/git.gif"); git.scaleToFit(80f, 80f); document.add(git); document.add(new Paragraph("pngnow.png")); Image png = Image.getInstanceFromClasspath("images/pngnow.png"); document.add(png); document.add(new Paragraph("grayscaled.png")); Image grayscaledPng = Image.getInstanceFromClasspath("images/grayscaled.png"); document.add(grayscaledPng); document.add(new Paragraph("iText.bmp")); Image bmp = Image.getInstanceFromClasspath("images/iText.bmp"); document.add(bmp); // 声称支持,但是渲染不出来 document.add(new Paragraph("iText.wmf")); Image wmf = Image.getInstanceFromClasspath("images/iText.wmf"); document.add(wmf); document.add(new Paragraph("iText.tif")); Image tif = Image.getInstanceFromClasspath("images/iText.tif"); document.add(tif); - 图片缩放
scalePercent(float percent)根据百分比来缩放图片scaleToFit(float fitWidth, float fitHeight)按照比例压缩图片scaleAbsolute(float newWidth, float newHeight)强制指定图片尺寸
- 对齐方式
setAlignment(int alignment)默认是左对齐Image.ALIGN_LEFT左对齐Image.ALIGN_CENTER居中Image.ALIGN_RIGHT右对齐-
document.add(new Paragraph("Alignment: left")); Image git = Image.getInstanceFromClasspath("images/git.gif"); git.scalePercent(30f); git.setAlignment(Image.ALIGN_LEFT); document.add(git); document.add(new Paragraph("Alignment: center")); git.setAlignment(Image.ALIGN_CENTER); document.add(git); document.add(new Paragraph("Alignment: right")); git.setAlignment(Image.ALIGN_RIGHT); document.add(git); ![image]()
Image.TEXTWRAP文字环绕方式,组合使用:image.setAlignment(Image.LEFT | Image.TEXTWRAP);-
document.add(new Paragraph("Alignment: left and textwrap")); Image git = Image.getInstanceFromClasspath("images/git.gif"); git.setAlignment(Image.ALIGN_LEFT | Image.TEXTWRAP); document.add(git); document.add(new Paragraph(longText)); document.add(new Paragraph("Alignment: right and textwrap")); git.setAlignment(Image.ALIGN_RIGHT | Image.TEXTWRAP); document.add(git); document.add(new Paragraph(longText)); ![image]()
-
Image.UNDERLYING将图片置于底层,组合使用:image.setAlignment(Image.RIGHT | Image.UNDERLYING);-
document.add(new Paragraph("Alignment: left and underlying")); git.setAlignment(Image.ALIGN_CENTER | Image.UNDERLYING); document.add(git); document.add(new Paragraph(longText)); document.add(new Paragraph("Alignment: right and underlying")); git.setAlignment(Image.ALIGN_RIGHT | Image.UNDERLYING); document.add(git); document.add(new Paragraph(longText)); ![image]()
-
- 二维码与条形码
- 这里只介绍BarCode128和QrCode,其它类型的条形码和二维码大同小异(关键是看业务用哪个)。
- OpenPDF原生支持BarCode128,不用引入其他包。
-
/** * 创建 BarCode128 条形码 * @param writer writer * @param code 条形码内容 * @return Image */ public static Image barcode128(PdfWriter writer, String code){ Barcode qrCode = new Barcode128(); qrCode.setCode(code); // 第一个Color.BLACK是条形码的颜色 // 第二个Color.BLACK是条形码下文字的颜色 return qrCode.createImageWithBarcode(writer.getDirectContent(), Color.BLACK, Color.BLACK); }
-
- QrCode需要引入第三方包
- 这里使用Google的zxing来示例
-
<dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.5.4</version> </dependency> -
// 设置关键属性 private static final Map<EncodeHintType, Object> HINTS = new HashMap<>(); { { HINTS.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 设置字符编码 HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 设置为30%的纠错方式 HINTS.put(EncodeHintType.MARGIN, 1); // 二维码边距 } } /** * 生成二维码 * @param content 二维码内容 * @param size 大小(像素) * @return BufferedImage */ public BufferedImage createQRCode(String content, int size) throws WriterException { QRCodeWriter writer = new QRCodeWriter(); BitMatrix matrix = writer.encode( content, BarcodeFormat.QR_CODE, size, size, HINTS ); return MatrixToImageWriter.toBufferedImage(matrix); } /** * 生成二维码(中心带图片) * @param content 二维码内容 * @param size 像素 * @param logoStream logo 图像 * @return BufferedImage */ public BufferedImage createQRCode(String content, int size, InputStream logoStream) throws WriterException, IOException { QRCodeWriter writer = new QRCodeWriter(); BitMatrix matrix = writer.encode( content, BarcodeFormat.QR_CODE, size, size, HINTS ); // 转换为 BufferedImage BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(matrix); // 读取图片 BufferedImage logo = ImageIO.read(logoStream); int qrWidth = qrImage.getWidth(); int qrHeight = qrImage.getHeight(); // 设置 Logo 尺寸:约为二维码的 1/5 到 1/4 int logoSize = Math.min(qrWidth, qrHeight) / 3; int logoX = (qrWidth - logoSize) / 2; int logoY = (qrHeight - logoSize) / 2; // 创建新图像(避免修改原图) BufferedImage combined = new BufferedImage(qrWidth, qrHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g = combined.createGraphics(); // 先画二维码 g.drawImage(qrImage, 0, 0, null); // 再画缩放后的 Logo(保持比例) g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(logo, logoX, logoY, logoSize, logoSize, null); g.dispose(); return combined; }






浙公网安备 33010602011771号