java查找PDF关键字坐标 并且标记出来

java查找PDF关键字坐标 并且标记出来

导入POM依赖

<!--使用itextpdf,比pdfbox简单些-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13.2</version>
</dependency>
<!--必须加入这个,否则中文会乱码或为空-->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

代码

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.PdfTextExtractor;
import com.itextpdf.text.pdf.parser.TextExtractionStrategy;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import com.itextpdf.text.pdf.parser.Vector;

import lombok.Data;

public class PdfMatchKeyword {

    /**
     * 用于供外部类调用获取关键字所在PDF文件坐标
     * @param filepath
     * @param keyWords
     * @return
     */
    public static List<KeyWordPosition> getKeyWordsByPath(String filepath, String keyWords) {
    	List<KeyWordPosition> matchItems = null;
        try{
            PdfReader pdfReader = new PdfReader(filepath);
            matchItems = getKeyWords(pdfReader, keyWords);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return matchItems;
    }

    /**
     * 获取关键字所在PDF坐标
     * @param pdfReader
     * @param keyWords
     * @return
     */
    private static List<KeyWordPosition> getKeyWords(PdfReader pdfReader, String keyWords) {
        int page = 0;
        
        List<KeyWordPosition> matchItems = new ArrayList<>();
        try{
            int pageNum = pdfReader.getNumberOfPages();
            StringBuilder allText = null;
            
            //遍历页
            for (page = 1; page <= pageNum; page++) {
            	//只记录当页的所有内容,需要记录全部页放在循环外面
            	List<ItemPosition> allItems = new ArrayList<>();
            	//扫描内容
            	MyTextExtractionStrategy myTextExtractionStrategy = new MyTextExtractionStrategy(allItems, page);
            	PdfTextExtractor.getTextFromPage(pdfReader, page, myTextExtractionStrategy);
                //当页的文字内容,用于关键词匹配
                allText = new StringBuilder();
                //一个字一个字的遍历
                for (int i=0; i<allItems.size(); i++) {
                	ItemPosition item = allItems.get(i);
                    allText.append(item.getText());
                    //关键字存在连续多个块中
                    if(allText.indexOf(keyWords) != -1) {
                    	KeyWordPosition keyWordPosition = new KeyWordPosition();
                    	//记录关键词每个字的位置,只记录开始结束标记时会有问题
                    	List<ItemPosition> listItem = new ArrayList<>();
                    	for(int j=i-keyWords.length()+1; j<=i; j++) {
                    		listItem.add(allItems.get(j));
                    	}
                    	keyWordPosition.setListItem(listItem);
                    	keyWordPosition.setText(keyWords);
                    	
                    	matchItems.add(keyWordPosition);
                    	allText.setLength(0);
                    }
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return matchItems;
    }


    /**
     * 添加矩形标记
     * @param oldPath
     * @param newPath
     * @param matchItems 关键词
     * @param color 标记颜色
     * @param lineWidth 线条粗细
     * @param padding 边框内边距
     * @throws DocumentException
     * @throws IOException
     */
    public static void andRectangleMark(String oldPath, String newPath, List<KeyWordPosition> matchItems, BaseColor color, int lineWidth, int padding) throws DocumentException, IOException{
    	// 待加水印的文件
        PdfReader reader = new PdfReader(oldPath);
        // 加完水印的文件
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(newPath));

        PdfContentByte content;

        // 设置字体
        // 循环对每页插入水印
        for (KeyWordPosition keyWordPosition:matchItems)
        {
        	//一个关键词的所有字坐标
        	List<ItemPosition> oneKeywordItems = keyWordPosition.getListItem();
        	for(int i=0; i<oneKeywordItems.size(); i++) {
        		ItemPosition item = oneKeywordItems.get(i);
        		ItemPosition preItem = i==0?null:oneKeywordItems.get(i-1);
        		// 水印的起始
                content = stamper.getOverContent(item.getPage());
                // 开始写入水印
                content.setLineWidth(lineWidth);
                content.setColorStroke(color);
                
                //底线
                content.moveTo(item.getRectangle().getLeft()-padding, item.getRectangle().getBottom()-padding);
                content.lineTo(item.getRectangle().getRight()+padding, item.getRectangle().getBottom()-padding);
                if(i!=0 && preItem!=null && (preItem.getRectangle().getBottom()-padding)==(item.getRectangle().getBottom()-padding) && (preItem.getRectangle().getRight()+padding)!=(item.getRectangle().getLeft()-padding)) {
                	content.moveTo(preItem.getRectangle().getRight()+padding, preItem.getRectangle().getBottom()-padding);
                	content.lineTo(item.getRectangle().getLeft()-padding, item.getRectangle().getBottom()-padding);
                }
                //上线
                content.moveTo(item.getRectangle().getLeft()-padding, item.getRectangle().getTop()+padding);
                content.lineTo(item.getRectangle().getRight()+padding, item.getRectangle().getTop()+padding);
                if(i!=0 && preItem!=null && (preItem.getRectangle().getTop()+padding)==(item.getRectangle().getTop()+padding) && (preItem.getRectangle().getRight()+padding)!=(item.getRectangle().getLeft()-padding)) {
                	content.moveTo(preItem.getRectangle().getRight()+padding, preItem.getRectangle().getTop()+padding);
                	content.lineTo(item.getRectangle().getLeft()-padding, item.getRectangle().getTop()+padding);
                }
                
                //左线
                if(i==0) {
                	content.moveTo(item.getRectangle().getLeft()-padding, item.getRectangle().getBottom()-padding);
                    content.lineTo(item.getRectangle().getLeft()-padding, item.getRectangle().getTop()+padding);
                }
                //右线
                if(i==(oneKeywordItems.size()-1)) {
                	content.moveTo(item.getRectangle().getRight()+padding, item.getRectangle().getBottom()-padding);
                    content.lineTo(item.getRectangle().getRight()+padding, item.getRectangle().getTop()+padding);
                }
                
                content.stroke();
        	}
        }
        stamper.close();
    }

    public static void main(String[] args) throws Exception {
    	String keyword = "移动机器人";
    	String sourcePdf = "C:\\Users\\chenyang-054\\Desktop\\3388.pdf";
    	String watermarkPdf = "C:\\Users\\chenyang-054\\Desktop\\3388-mark.pdf";
    	
    	Long start = System.currentTimeMillis();
    	System.out.println("开始扫描....");
    	List<KeyWordPosition> matchItems = getKeyWordsByPath(sourcePdf, keyword);
    	System.out.println("扫描结束["+(System.currentTimeMillis()-start)+"ms],共找到关键字["+keyword+"]出现["+matchItems.size()+"]次");
    	
    	start = System.currentTimeMillis();
    	System.out.println("开始添加标记....");
    	andRectangleMark(sourcePdf
        		, watermarkPdf
        		, matchItems
        		, BaseColor.RED
        		, 2
        		, 2);
    	System.out.println("标记添加完成["+(System.currentTimeMillis()-start)+"ms]");
    }
}

/**
 * @ClassName: ItemPosition
 * @Description: 字体的位置信息
 * @author chenyang-054
 * @date 2021-04-09 09:14:36
 */
@Data
class ItemPosition{
	private Integer page;
	private String text;
	//这个字的矩形坐标
	private Rectangle rectangle;
}

/**
 * @ClassName: KeyWordPosition
 * @Description: 需要高亮显示的关键字坐标信息
 * @author chenyang-054
 * @date 2021-04-09 11:28:56
 */
@Data
class KeyWordPosition{
	private String text;
	private List<ItemPosition> listItem;
}

/**
 * @ClassName: MyTextExtractionStrategy
 * @Description: 记录所有位置+字体信息,这种方式获取坐标信息和字体信息方便一点
 * @author chenyang-054
 * @date 2021-04-09 11:00:31
 */
class MyTextExtractionStrategy implements TextExtractionStrategy{

	private List<ItemPosition> positions;
	private Integer page;
	
	public MyTextExtractionStrategy() {}
	
	public MyTextExtractionStrategy(List<ItemPosition> positions, Integer page) {
		this.positions = positions;
		this.page = page;
	}
	
	@Override
	public void beginTextBlock() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void renderText(TextRenderInfo renderInfo) {
		ItemPosition ItemPosition = new ItemPosition();
		Vector bottomLeftPoint = renderInfo.getDescentLine().getStartPoint();
		Vector topRightPoint = renderInfo.getAscentLine().getEndPoint();
		//记录矩形坐标
		Rectangle rectangle = new Rectangle(bottomLeftPoint.get(Vector.I1), bottomLeftPoint.get(Vector.I2),
	            topRightPoint.get(Vector.I1), topRightPoint.get(Vector.I2));
		ItemPosition.setPage(page);
		ItemPosition.setRectangle(rectangle);
		ItemPosition.setText(renderInfo.getText());
		positions.add(ItemPosition);
	}

	@Override
	public void endTextBlock() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void renderImage(ImageRenderInfo renderInfo) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public String getResultantText() {
		// TODO Auto-generated method stub
		return null;
	}
}

关键点说明

  1. itextpdf中坐标系为第一象限,原点在每页的左下角Alt

  2. TextExtractionStrategy ,自定义MyTextExtractionStrategy,通过renderText方法可以获取位置和字体信息;

  3. 每页分开判断,此代码例子中,关键词判断通过每页内容字符串.indexOf来判断的,如果关键词正好在上下页就判断不了了,可以继续改进,把allText和allItems放到页循环外就可以了;

  4. 关键词检索,通过indexOf来判断很简单,如需多个关键词或敏感词库之类的,需要自己拓展算法和词库或其他规则;

  5. 标记功能,不论是后台画还是前端画矩形圈,不能只通过关键词的开始位置结束位置来画,因为会碰到换行/换页/有空格的情况,最好通过关键词的每个字的坐标去画矩形。否则会变成这样(图中关键词是:斯坦福研究院)在这里插入图片描述

  6. 其他都写在注释里了,直接运行就可以了。

要获取 PDF 关键字坐标,可以使用 Apache PDFBox 库。以下是获取 PDF 关键字坐标的示例代码: ```java import java.io.File; import java.io.IOException; import java.util.List; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup.MarkupTypeEnum; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationTextMarkup.QuadPoints; public class PDFKeywordCoordinates { public static void main(String[] args) throws IOException { String filePath = "example.pdf"; String keyword = "example"; PDDocument document = PDDocument.load(new File(filePath)); List<PDPage> pages = document.getDocumentCatalog().getAllPages(); for (PDPage page : pages) { List<PDAnnotation> annotations = page.getAnnotations(); for (PDAnnotation annotation : annotations) { if (annotation instanceof PDAnnotationTextMarkup) { PDAnnotationTextMarkup markup = (PDAnnotationTextMarkup) annotation; if (markup.getMarkupType() == MarkupTypeEnum.HIGHLIGHT) { COSDictionary dict = markup.getCOSObject(); COSArray quadPointsArray = (COSArray) dict.getDictionaryObject(COSName.getPDFName("QuadPoints")); for (int i = 0; i < quadPointsArray.size(); i += 8) { float x1 = quadPointsArray.getFloat(i); float y1 = quadPointsArray.getFloat(i + 1); float x2 = quadPointsArray.getFloat(i + 2); float y2 = quadPointsArray.getFloat(i + 3); float x3 = quadPointsArray.getFloat(i + 4); float y3 = quadPointsArray.getFloat(i + 5); float x4 = quadPointsArray.getFloat(i + 6); float y4 = quadPointsArray.getFloat(i + 7); if (containsKeyword(page, keyword, x1, y1, x2, y2, x3, y3, x4, y4)) { System.out.println("Keyword '" + keyword + "' found on page " + (pages.indexOf(page) + 1) + " at (" + x1 + "," + y1 + ") (" + x2 + "," + y2 + ") (" + x3 + "," + y3 + ") (" + x4 + "," + y4 + ")"); } } } } } } document.close(); } private static boolean containsKeyword(PDPage page, String keyword, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) throws IOException { StringBuilder sb = new StringBuilder(); sb.append("q\n"); sb.append(x1).append(' ').append(y1).append(" m\n"); sb.append(x2).append(' ').append(y2).append(" l\n"); sb.append(x3).append(' ').append(y3).append(" l\n"); sb.append(x4).append(' ').append(y4).append(" l\n"); sb.append("h\n"); sb.append("W* n\n"); sb.append("BT\n"); sb.append("/Helv 12 Tf\n"); sb.append("0 g\n"); sb.append("1 0 0 1 ").append(x1).append(' ').append(y1).append(" Tm\n"); sb.append("(").append(keyword).append(") Tj\n"); sb.append("ET\n"); sb.append("Q\n"); return page.getContents().stream().anyMatch(content -> content.getString().contains(sb.toString())); } } ``` 在此示例代码中,我们首先加载 PDF 文件并获取所有页面。然后,我们遍历每个页面的所有注释,并查找类型为“高亮”的注释。对于每个高亮注释,我们获取该注释的四个顶点坐标,并检查关键字是否包含在其中。如果找到匹配项,则打印关键字坐标
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值