最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Java實現(xiàn)轉(zhuǎn)換掃描的文檔為可搜索的PDF

 更新時間:2023年12月05日 14:17:41   作者:xulihang  
這篇文章主要為大家詳細介紹了如何使用Java實現(xiàn)轉(zhuǎn)換掃描的文檔為可搜索的PDF,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

在上一篇文章中,我們編寫了一個JavaFX的演示程序,使用Dynamsoft Service的REST API掃描文檔。該演示程序可以通過TWAIN、WIA、SANE和ICA等協(xié)議用文檔掃描儀掃描文檔,并使用PDFBox將文檔保存到PDF文件中。

在本文中,我們將擴展它的功能,讓它能將掃描的文檔轉(zhuǎn)換為可搜索的PDF。

打開可搜索的PDF時,我們可以直接選擇文本和搜索關(guān)鍵字。生成可搜索的PDF對于文檔索引或管理系統(tǒng)非常有用。

如果PDF是用InDesign和Word等工具生成的,那么它的文本已經(jīng)可以搜索了。但是,如果PDF包含掃描的圖像,我們需要添加額外的文本覆蓋層,使其可搜索。

掃描文檔的OCR

我們首先需要用OCR得到掃描圖像中的文字和其位置信息。有各種OCR引擎或API服務(wù)可以調(diào)用。這里,我們使用OCRSpace的免費OCR API。

1.創(chuàng)建相關(guān)定義。

TextLine.class

public class TextLine {
    public double left;
    public double top;
    public double width;
    public double height;
    public String text;

    public TextLine(double left, double top, double width, double height, String text) {
        this.left = left;
        this.top = top;
        this.width = width;
        this.height = height;
        this.text = text;
    }
}

OCRResult.class

public class OCRResult {
    public ArrayList<TextLine> lines = new ArrayList<TextLine>();
}

2.創(chuàng)建一個OCRSpace類,該類提供從base64編碼圖像獲取OCR結(jié)果的靜態(tài)方法。

public class OCRSpace {
    public static String key = "";
    public static String lang = "eng";
    /**
     * Get OCR result from a base64-encoded image in JPEG format
     *
     * @param base64 - base64-encoded image
     *
     */
    public static OCRResult detect(String base64) throws IOException {
        OCRResult result = new OCRResult();
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(120, TimeUnit.SECONDS)
                .build();
        RequestBody requestBody=new FormBody.Builder()
                .add("apikey",key)
                .add("language",lang)
                .add("base64Image","data:image/jpeg;base64,"+base64.trim())
                .add("isOverlayRequired","true")
                .build();

        Request httpRequest = new Request.Builder()
                .url("https://api.ocr.space/parse/image")
                .post(requestBody)
                .build();
        try (Response response = client.newCall(httpRequest).execute()) {
            try {
                String json = response.body().string();
                parse(json,result);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    private static void parse(String json,OCRResult ocrResult) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String,Object> body = objectMapper.readValue(json,new TypeReference<Map<String,Object>>() {});
        List<Map<String,Object>> parsedResults = (List<Map<String, Object>>) body.get("ParsedResults");
        for (Map<String,Object> parsedResult:parsedResults) {
            Map<String,Object> textOverlay = (Map<String, Object>) parsedResult.get("TextOverlay");
            List<Map<String,Object>> lines = (List<Map<String, Object>>) textOverlay.get("Lines");
            for (Map<String,Object> line:lines) {
                TextLine textLine = parseAsTextLine(line);
                ocrResult.lines.add(textLine);
            }
        }
    }

    private static TextLine parseAsTextLine(Map<String,Object> line){
        String lineText = (String) line.get("LineText");
        List<Map<String,Object>> words = (List<Map<String, Object>>) line.get("Words");
        int minX = (int)((double) words.get(0).get("Left"));
        int minY = (int)((double) words.get(0).get("Top"));
        int maxX = 0;
        int maxY = 0;
        for (Map<String,Object> word:words) {
            int x = (int)((double) word.get("Left"));
            int y = (int)((double) word.get("Top"));
            int width = (int)((double) word.get("Width"));
            int height = (int)((double) word.get("Height"));
            minX = Math.min(minX,x);
            minY = Math.min(minY,y);
            maxX = Math.max(maxX,x+width);
            maxY = Math.max(maxY,y+height);
        }
        return new TextLine(minX,minY,maxX - minX,maxY-minY,lineText);
    }
}

使用OKHttp用于HTTP請求,Jackson用于處理JSON。

將文本覆蓋層添加到PDF頁面中

創(chuàng)建SearchablePDFCreator類以提供相關(guān)方法。

public class SearchablePDFCreator {}

添加addTextOverlay方法,將文本覆蓋層添加到現(xiàn)有的PDF頁面。

/**
 * Add text overlay to an existing PDF page
 * @param contentStream - PDF content stream
 * @param result - OCR result
 * @param pageHeight - Height of the image
 * @param pdFont - Specify a font for evaluation of the position
 * @param percent - image's height / page's height
 */
public static void addTextOverlay(PDPageContentStream contentStream,OCRResult result, double pageHeight, PDFont pdFont,double percent) throws IOException {
    PDFont font = pdFont;
    contentStream.setFont(font, 16);
    contentStream.setRenderingMode(RenderingMode.NEITHER);
    for (int i = 0; i <result.lines.size() ; i++) {
        TextLine line = result.lines.get(i);
        FontInfo fi = calculateFontSize(font,line.text, (float) (line.width * percent), (float) (line.height * percent));
        contentStream.beginText();
        contentStream.setFont(font, fi.fontSize);
        contentStream.newLineAtOffset((float) (line.left * percent), (float) ((pageHeight - line.top - line.height) * percent));
        contentStream.showText(line.text);
        contentStream.endText();
    }
}

private static FontInfo calculateFontSize(PDFont font, String text, float bbWidth, float bbHeight) throws IOException {
    int fontSize = 17;
    float textWidth = font.getStringWidth(text) / 1000 * fontSize;
    float textHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;

    if(textWidth > bbWidth){
        while(textWidth > bbWidth){
            fontSize -= 1;
            textWidth = font.getStringWidth(text) / 1000 * fontSize;
            textHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;
        }
    }
    else if(textWidth < bbWidth){
        while(textWidth < bbWidth){
            fontSize += 1;
            textWidth = font.getStringWidth(text) / 1000 * fontSize;
            textHeight = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;
        }
    }

    FontInfo fi = new FontInfo();
    fi.fontSize = fontSize;
    fi.textHeight = textHeight;
    fi.textWidth = textWidth;

    return fi;
}

字體大小是根據(jù)指定的字體和文字行寬度自動計算的。

添加addPage方法,可以將文本覆蓋層與圖像一起添加為文檔的新頁面。

public static void addPage(byte[] imageBytes,OCRResult result, PDDocument document,int pageIndex,PDFont pdFont) throws IOException {
    ByteArrayInputStream in = new ByteArrayInputStream(imageBytes);
    BufferedImage bi = ImageIO.read(in);
    // Create a new PDF page
    PDRectangle rect = new PDRectangle((float) bi.getWidth(),(float) bi.getHeight());
    PDPage page = new PDPage(rect);
    document.addPage(page);
    PDPageContentStream contentStream = new PDPageContentStream(document, page);
    PDImageXObject image
            = PDImageXObject.createFromByteArray(document,imageBytes,String.valueOf(pageIndex));
    contentStream.drawImage(image, 0, 0);
    addTextOverlay(contentStream,result,bi.getHeight(),pdFont);
    contentStream.close();
}

讓我們檢查一下添加結(jié)果。

使用RenderingMode.NEITHER將使文本圖層不可見。我們可以把這一行注釋掉,以看到添加上去的文字。以下是覆蓋了文本的PDF文件的一個區(qū)域。我們可以看到文本與圖像基本吻合。

將掃描的文檔圖像保存為可搜索的PDF

接下來,我們可以嘗試使用我們剛剛編寫的類從圖像文件創(chuàng)建一個可搜索的PDF。

File image = new File("F://WebTWAINImage.jpg");
byte[] byteArray = new byte[(int) image.length()];
try (FileInputStream inputStream = new FileInputStream(image)) {
    inputStream.read(byteArray);
} catch (IOException e) {
    throw new RuntimeException(e);
}
String base64 = Base64.getEncoder().encodeToString(byteArray);
OCRSpace.key = "your key";
OCRResult result = OCRSpace.detect(base64);
PDDocument document = new PDDocument();
SearchablePDFCreator.addPage(byteArray,result,document,0);
document.save(new File("F://output.pdf"));
document.close();

使用Searchable PDF Creator加強JavaFX演示程序

編輯pom.xml,將庫添加為依賴項。

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>com.github.tony-xlh</groupId>
        <artifactId>searchablePDF4j</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

添加復(fù)選框以在UI中啟用可搜索PDF的生成。

如果選中該復(fù)選框,則通過添加文本覆蓋層來生成可搜索的PDF。

 PDDocument document = new PDDocument();
 int index = 0;
 for (DocumentImage di: documentListView.getItems()) {
     index = index + 1;
     ImageView imageView = di.imageView;
     PDRectangle rect = new PDRectangle((float) imageView.getImage().getWidth(),(float) imageView.getImage().getHeight());
     System.out.println(rect);
     PDPage page = new PDPage(rect);
     document.addPage(page);
     PDPageContentStream contentStream = new PDPageContentStream(document, page);
     PDImageXObject image
             = PDImageXObject.createFromByteArray(document,di.image,String.valueOf(index));
     contentStream.drawImage(image, 0, 0);
+    if (searchablePDFCheckBox.isSelected()) {
+        String base64 = Base64.getEncoder().encodeToString(di.image);
+        OCRSpace.key = "your key";
+        OCRResult result = OCRSpace.detect(base64);
+        SearchablePDFCreator.addTextOverlay(contentStream,result,image.getHeight());
+    }
     contentStream.close();
 }
 document.save(fileToSave.getAbsolutePath());
 document.close();

到此這篇關(guān)于使用Java實現(xiàn)轉(zhuǎn)換掃描的文檔為可搜索的PDF的文章就介紹到這了,更多相關(guān)Java文檔轉(zhuǎn)換為PDF內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

  • Java中處理I/O操作的不同方式

    Java中處理I/O操作的不同方式

    BIO、NIO和AIO是Java中處理I/O操作的三種不同方式,它們分別代表阻塞I/O、非阻塞I/O和異步I/O,本文我們結(jié)合代碼進行一個綜合演示,代碼由于是偽代碼,可能存在不足,僅供大家參考
    2024-02-02
  • springboot實現(xiàn)多文件上傳功能

    springboot實現(xiàn)多文件上傳功能

    這篇文章主要為大家詳細介紹了springboot實現(xiàn)多文件上傳功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • Java輸入/輸出流體系詳解

    Java輸入/輸出流體系詳解

    這篇文章主要介紹了Java輸入/輸出流體系詳解,涉及字節(jié)流和字符流,輸入輸出體系,轉(zhuǎn)換流,以及文件的讀寫等相關(guān)內(nèi)容,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • Java實現(xiàn)的權(quán)重算法(按權(quán)重展現(xiàn)廣告)

    Java實現(xiàn)的權(quán)重算法(按權(quán)重展現(xiàn)廣告)

    這篇文章主要介紹了Java實現(xiàn)的權(quán)重算法(按權(quán)重展現(xiàn)廣告),本文講解了算法實現(xiàn)原理和實現(xiàn)代碼,需要的朋友可以參考下
    2015-04-04
  • Java中的ThreadLocal線程變量詳解

    Java中的ThreadLocal線程變量詳解

    這篇文章主要介紹了Java中的ThreadLocal線程變量詳解,ThreadLocal叫做線程變量,意思是在ThreadLocal中填充的變量屬于當(dāng)前線程,該變量對其他線程而言是隔離的,它是用來提供線程內(nèi)部的局部變量,需要的朋友可以參考下
    2024-01-01
  • RocketMQ 延時級別配置方式

    RocketMQ 延時級別配置方式

    這篇文章主要介紹了RocketMQ 延時級別配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 一文解讀java.nio.ByteBuffer

    一文解讀java.nio.ByteBuffer

    這篇文章主要介紹了java.nio.ByteBuffer的用法解讀,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • mybatisplus如何在xml的連表查詢中使用queryWrapper

    mybatisplus如何在xml的連表查詢中使用queryWrapper

    這篇文章主要介紹了mybatisplus如何在xml的連表查詢中使用queryWrapper,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java基礎(chǔ)之堆內(nèi)存溢出的解決

    Java基礎(chǔ)之堆內(nèi)存溢出的解決

    這篇文章主要介紹了Java基礎(chǔ)之堆內(nèi)存溢出的解決,文中有非常詳細的圖文示例及代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-05-05
  • 最新評論

    宁海县| 天门市| 朝阳区| 南开区| 林甸县| 和顺县| 孙吴县| 琼中| 太白县| 彭泽县| 来宾市| 益阳市| 施甸县| 阳江市| 宝鸡市| 西乌| 建湖县| 洛阳市| 荥阳市| 灌阳县| 抚顺市| 多伦县| 武清区| 山东省| 政和县| 余姚市| 江华| 隆回县| 甘泉县| 名山县| 龙南县| 陇南市| 织金县| 白山市| 峨眉山市| 二手房| 洛南县| 昌黎县| 麻江县| 东乌| 黄龙县|