推荐一个开源的字帖生成器,Java语言开发,小学生家长建议收藏

查理谈科技 2024-05-11 02:58:20

家里有一个一年级的小学生,最近老师总是在家长群里强调汉字的识字、默写, 没办法在家里默默给孩子练习, 觉得需要一个字帖, 把每节课的生字都打印上面, 让孩子先练习一遍。

田字格

于是就在网上寻找, 发现生成字帖的网站有很多, 但是很多是需要付费的, 大部分是生成免费, 但是打印、下载时候,就开始转向会员页面, 打印和下载大部分都是收费的, 烦!仔细研究了会, 发现在线字帖, 里面的水还是挺深的, 技术没啥, 但是涉及到字体部分, 就非常麻烦, 这里面的坑非常多, 字体的授权、商用, 很多时候都涉及到侵权问题, 不收费的话是很难应对类似方正那种专业公司的法务部门的。

没找个开源的吧, 还真找到了一个xxy-copybook, 作者是 radium0028, 中文名称是“玩技术的雷哥”, github 地址: https://github.com/radium0028/xxy-copybook

在这里先感谢一下雷哥的付出!雷哥的b站地址https://www.bilibili.com/video/BV1K94y197Lz, 看起来好像是大前端的专家,大家有空可以去支持。

一、项目整体介绍

这个项目的主要功能就是生成田字格样式的字帖, 可以方便小朋友练习, 生成的田字格字帖是这样的:

田字格

这个项目比较贴心地支持A4纸, 基本上拿过来就能直接打印和使用, 我在这个项目的基础上,简单修改了田字格的样式为红色,看起来更符合常见的字帖。

这里面比较核心的就是田字格样式的生成,以及字体两个方面。

二、田字格是怎么生成的

我对于如何生成田字格和汉字, 之前并没有特别的研究, 在这个项目里我学到的, 也是比较佩服的是, 作者(雷哥)没有使用什么现成的库, 而是直接使用Java 2D的Graphics2D API, 用Java 桌面环境和图片类硬生生的画出来的, 这个让我佩服。 现在很多Java 开发者, 对于Web 开发用的比较深入, 但是对于Java Awt 桌面开发和Graphics2D API知之甚少, 甚至有些人压根没听过, 这里佩服雷哥。

首先来看一下项目的整体结构:

简单理解的话,这里面最重要的两个类,AbstractCellDecorator和AbstractCell, 一个是田字格的装饰类(线条、线条样式等), 一个是田字格本身的填充类(田字格里的汉字、字体等), 理解了这两个类, 基本上就能明白这个田字格字帖的产生过程了:

在上面的buildCopyBook 方法中, 整体展示了一个田字格页面的组成: 头部、尾部和田字格, 页面的主体是田字格组成的行, 这个行里又有很多个单个的田字格组成:

List<RowData> rowDataList = abstractCopybookBuilder.createRow(textAbstractCell, pinyinAbstractCell);List<BufferedImage> rowImage = abstractCopybookBuilder.builderRow(rowDataList);

一个田字格的行数据是这么组成的,在BaseCopybook类的createRow方法里:

RowData rowData = new RowData();// 每行几个字 * 当前行索引 + 当前循环到第几个行中的字(也就是列)int wordIndex = rowTextNum * i + z;if (this.copybookData.getWordList().size() <= wordIndex) { break;}String word = this.copybookData.getWordList().get(wordIndex);// log.debug("准备画文字:{}", word);//开始画一行的文字for (int j = 0; j < fullWordNum; j++) { // log.debug("画'{}'的完整文字:", word); //完整文字 CellText cellText = new CellText(textAbstractCell, this.templateBean.getFont(), this.templateBean.getTextColor()); cellText.setText(word); if (this.templateBean.isShowPinyin()) { CellPinyin pinyinCell1 = new CellPinyin(pinyinAbstractCell, this.templateBean.getPinyinFont() , this.templateBean.getPinyinColor()); pinyinCell1.setText(this.copybookData.getPinyinList().get(wordIndex)); rowData.push(cellText, pinyinCell1); } else { rowData.push(cellText); }}

可以看出, 在BaseCopybook#createRow中, 最主要的代码是

//完整文字CellText cellText = new CellText(textAbstractCell, this.templateBean.getFont(), this.templateBean.getTextColor());cellText.setText(word);

至于拼音和描红文字,基本是一样的,这里不展开。

那么一个田字格具体是怎么产生的呢?

这就是abstractCopybookBuilder.builderRow方法驱动的:

//先画一个行的底图,依然是透明底的。 BufferedImage finalRowImage = ImageUtil.createImage(columnWidth, columnHeight); CollUtil.forEach(rowData, (row, index) -> { //每行里有多少列 int columnCount = row.columnCount(); //创建一行的画板 BufferedImage image = ImageUtil.copyImage(finalRowImage); Graphics2D g = image.createGraphics(); //循环列数 for (int i = 0; i < columnCount; i++) { AbstractCell[] abstractCells = row.pull(i); if (abstractCells != null) { AbstractCell text = abstractCells[0]; if (text == null) { continue; } int offsetPinyinX = this.pinyinCellWidth + this.templateBean.getCellMarginLeft(); int offsetTextX = this.textCellWidth + this.templateBean.getCellMarginLeft(); if (this.templateBean.isShowPinyin() && abstractCells.length >= 2) { //有拼音格,先画拼音,再画文字 if (abstractCells[1] != null) { g.drawImage(abstractCells[1].draw(), offsetPinyinX * i, this.templateBean.getCellMarginTop(), null); } g.drawImage(text.draw(), offsetTextX * i, this.pinyinCellHeight + this.templateBean.getCellMarginTop(), null); } else if (abstractCells.length >= 1) { g.drawImage(text.draw(), offsetTextX * i, this.templateBean.getCellMarginTop(), null); } } } g.dispose(); resultImage.add(image);

这里最主要的代码, 就是这个逐个田字格的绘制:

g.drawImage(text.draw(), offsetTextX * i,this.templateBean.getCellMarginTop(), null);

田字格怎么画呢?这个drawLine方法绘制田字格的线:

protected void drawLine(Graphics2D graphics2d) { //默认虚线 BasicStroke basicStroke = Optional.ofNullable(this.basicStroke).orElse(StrokeForCell.DOTTED_LINE); graphics2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graphics2d.setStroke(basicStroke); Color color = Optional.ofNullable(this.color).orElse(abstractCell.getColor()); graphics2d.setColor(color); Integer width = Optional.ofNullable(this.width).orElse(abstractCell.getWidth()); Integer height = Optional.ofNullable(this.height).orElse(abstractCell.getHeight());// log.debug("CellLineTian,width:{},height:{}",width,height); graphics2d.drawLine(0, height / 2, width, height / 2); graphics2d.drawLine(width / 2, 0, width / 2, height); }

是比较简单的:

public BufferedImage draw() { Integer width = Optional.ofNullable(this.width).orElse(TemplateSize.CELL_WIDTH.getValue()); Integer height = Optional.ofNullable(this.height).orElse(TemplateSize.CELL_HEIGHT.getValue()); logger.debug("绘制田字格(ConreteCell),width:{},height:{}",width,height); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); Graphics2D g = image.createGraphics(); //透明背景 image = g.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT); g.dispose(); return image;}

看明白田字格的绘制方法之后, 再看整体的类的结构,就会更明白些:

从UML 类图来分析, 有些类的结构还是需要调整, 但是瑕不掩瑜, 这个项目整体上是实现了这个功能的。

三、如何使用字体

理解了如何产生田字格的逻辑外, 另外一个问题是, 使用什么字体,以及什么字库来显示这个汉字。

我在这里选用的是楷体, 看起来跟平常的练习册的显示没有什么大的区别, 但是如果换用其他的字体,显示就不一样。

田字格

例如,我这里换用仿宋:

首先来看一个本地执行的视频,从IDEA启动:

可以看出, 字体的显示是截然不同的。

如何添加和使用字体呢?这里Github的源代码库中没有介绍, 我的办法是, 放到resources\fonts目录, 然后在代码中注册这些字体:

我在网络上一些免费字体网站下载了一些, 感觉靠谱的还是阿里巴巴开源的普惠体:

当然,对于小学生练字而言, 还是老老实实找个楷体吧, 操作系统里一般默认自带,后缀名一般是ttf 或者otf, 复制到fonts 目录即可。

复制到项目之后,想要正确使用, 还需要正确的font 名称, 这里用注册字体的方法来得到font的真正名字:

Font font = Font.createFont(Font.PLAIN, fontFile);logger.debug("registering font {}", font);ge.registerFont(font);

例如下面的font 信息就是我从log 中找到的, 拿到font name ,复制到项目代码中即可:

registering font java.awt.Font[family=Alibaba Sans,name=Alibaba Sans Italic,style=plain,size=1]

2024-05-10 01:36:31.052 [main] DEBUG i.g.r.x.YiNianJiXia.lambda$null$0(YiNianJiXia.java:58) registering font java.awt.Font[family=FZKai-Z03S,name=FZKai-Z03S,style=plain,size=1]

2024-05-10 01:36:31.053 [main] DEBUG i.g.r.x.YiNianJiXia.lambda$null$0(YiNianJiXia.java:58) registering font java.awt.Font[family=Alibaba Sans Light,name=Alibaba Sans Light,style=plain,size=1]

下面展示一个完整的产生字帖的代码:

/** * 快速的创建一个字帖模板。 */@Testvoid construct() { //需要些的字 String text = "绿遍山原白满川子规声里雨如烟"; //字体名字 String fontName = "方正仿宋-简体"; CopybookTemplate.CopybookTemplateBuilder copybookTemplateBuilder = CopybookTemplate.builder() .emptyCellNum(2) .textLineStroke(StrokeForCell.LINE) //单元格使用一个边框+田字格样式。 .textCellLineStyle(CollUtil.toList(LineStyle.BORDER, LineStyle.TIAN)) //.textCellLineStyle(CollUtil.toList(LineStyle.BORDER, LineStyle.PINYIN3CELL)) ; //给边框格一个加粗的边线 copybookTemplateBuilder.textLineStrokeMap(MapUtil .builder(LineStyle.BORDER.getValue(), StrokeForCell.LINE_BOLD) .build()); Font font = new Font(fontName, Font.PLAIN, 140); copybookTemplateBuilder.font(font); //设置模板数据 CopybookTemplate copybookTemplate = copybookTemplateBuilder.pagePadding(new Integer[]{10,10,10,200}).build(); CopybookData copybookData = CopybookData.builder() .author("Radium") .wordList(CollUtil.toList(text.split(""))) .build(); BaseCopybook baseCopybook = new BaseCopybook(copybookTemplate, copybookData); CopybookDirector director = new CopybookDirector(baseCopybook); try { Copybook construct = director.buildCopybook(); BufferedImage bufferedImage = construct.exportFirstImage(); //输出图像 ByteArrayOutputStream output = new ByteArrayOutputStream(); ImageIO.write(bufferedImage, "png", output); File constructFile = new File(outputPath+"construct.png"); FileUtil.writeBytes(output.toByteArray(), constructFile); logger.debug("Write file to " + constructFile.getAbsolutePath()); Desktop.getDesktop().open(constructFile); } catch (Exception e) { e.printStackTrace(); }}

总结

本文介绍了开源项目xxy-copybook, 一个能产生田字格字帖的开源项目, 家里有小朋友的家长,建议收藏和使用。

附带一些我生成的人教版语文,一年级下册的几个单元的字帖:



0 阅读:0

查理谈科技

简介:感谢大家的关注