Merge pull request #459 from Artem-ka-create/issue-372-pdfbox

Issue 372 pdfbox
This commit is contained in:
Anthony Stirling 2023-11-05 19:49:14 +00:00 committed by GitHub
commit 9d052b310f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1002 additions and 235 deletions

View file

@ -81,10 +81,12 @@ dependencies {
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio // https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4' implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
implementation 'commons-io:commons-io:2.13.0' implementation 'commons-io:commons-io:2.13.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
//general PDF //general PDF
// https://mvnrepository.com/artifact/com.opencsv/opencsv
implementation group: 'com.opencsv', name: 'opencsv', version: '5.7.1'
implementation 'org.apache.pdfbox:pdfbox:2.0.29' implementation 'org.apache.pdfbox:pdfbox:2.0.29'
implementation 'org.apache.pdfbox:xmpbox:2.0.29' implementation 'org.apache.pdfbox:xmpbox:2.0.29'
implementation 'org.bouncycastle:bcprov-jdk15on:1.70' implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
@ -100,9 +102,9 @@ dependencies {
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
compileOnly 'org.projectlombok:lombok:1.18.28' compileOnly 'org.projectlombok:lombok:1.18.28'
annotationProcessor 'org.projectlombok:lombok:1.18.28' annotationProcessor 'org.projectlombok:lombok:1.18.28'
} }
task writeVersion { task writeVersion {
def propsFile = file('src/main/resources/version.properties') def propsFile = file('src/main/resources/version.properties')
def props = new Properties() def props = new Properties()

View file

@ -0,0 +1,120 @@
package stirling.software.SPDF.controller.api;
import com.opencsv.CSVWriter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import stirling.software.SPDF.controller.api.strippers.PDFTableStripper;
import stirling.software.SPDF.model.api.extract.PDFFilePage;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/api/v1/extract/pdf-to-csv")
@Tag(name = "General", description = "General APIs")
public class ExtractController {
private static final Logger logger = LoggerFactory.getLogger(CropController.class);
@PostMapping(consumes = "multipart/form-data")
@Operation(summary = "Extracts a PDF document to csv", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO")
public ResponseEntity<String> PdfToCsv(@ModelAttribute PDFFilePage form)
throws IOException {
ArrayList<String> tableData = new ArrayList<>();
int columnsCount = 0;
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(form.getFileInput().getBytes()))) {
final double res = 72; // PDF units are at 72 DPI
PDFTableStripper stripper = new PDFTableStripper();
stripper.setSortByPosition(true);
stripper.setRegion(new Rectangle((int) Math.round(1.0 * res), (int) Math.round(1 * res), (int) Math.round(6 * res), (int) Math.round(9.0 * res)));
PDPage pdPage = document.getPage(form.getPageId() - 1);
stripper.extractTable(pdPage);
columnsCount = stripper.getColumns();
for (int c = 0; c < columnsCount; ++c) {
for(int r=0; r<stripper.getRows(); ++r) {
tableData.add(stripper.getText(r, c));
}
}
}
ArrayList<String> notEmptyColumns = new ArrayList<>();
for (String item: tableData) {
if(!item.trim().isEmpty()){
notEmptyColumns.add(item);
}else{
columnsCount--;
}
}
List<String> fullTable = notEmptyColumns.stream().map((entity)->
entity.replace('\n',' ').replace('\r',' ').trim().replaceAll("\\s{2,}", "|")).toList();
int rowsCount = fullTable.get(0).split("\\|").length;
ArrayList<String> headersList = getTableHeaders(columnsCount,fullTable);
ArrayList<String> recordList = getRecordsList(rowsCount,fullTable);
StringWriter writer = new StringWriter();
try (CSVWriter csvWriter = new CSVWriter(writer)) {
csvWriter.writeNext(headersList.toArray(new String[0]));
for (String record : recordList) {
csvWriter.writeNext(record.split("\\|"));
}
}
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(ContentDisposition.builder("attachment").filename(form.getFileInput().getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_extracted.csv").build());
headers.setContentType(MediaType.parseMediaType("text/csv"));
return ResponseEntity.ok()
.headers(headers)
.body(writer.toString());
}
private ArrayList<String> getRecordsList( int rowsCounts ,List<String> items){
ArrayList<String> recordsList = new ArrayList<>();
for (int b=1; b<rowsCounts;b++) {
StringBuilder strbldr = new StringBuilder();
for (int i=0;i<items.size();i++){
String[] parts = items.get(i).split("\\|");
strbldr.append(parts[b]);
if (i!= items.size()-1){
strbldr.append("|");
}
}
recordsList.add(strbldr.toString());
}
return recordsList;
}
private ArrayList<String> getTableHeaders(int columnsCount, List<String> items){
ArrayList<String> resultList = new ArrayList<>();
for (int i=0;i<columnsCount;i++){
String[] parts = items.get(i).split("\\|");
resultList.add(parts[0]);
}
return resultList;
}
}

View file

@ -0,0 +1,354 @@
package stirling.software.SPDF.controller.api.strippers;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType3Font;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.PDFTextStripperByArea;
import org.apache.pdfbox.text.TextPosition;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;
import java.util.*;
/**
*
* Class to extract tabular data from a PDF.
* Works by making a first pass of the page to group all nearby text items
* together, and then inferring a 2D grid from these regions. Each table cell
* is then extracted using a PDFTextStripperByArea object.
*
* Works best when
* headers are included in the detected region, to ensure representative text
* in every column.
*
* Based upon DrawPrintTextLocations PDFBox example
* (https://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/util/DrawPrintTextLocations.java)
*
* @author Beldaz
*/
public class PDFTableStripper extends PDFTextStripper
{
/**
* This will print the documents data, for each table cell.
*
* @param args The command line arguments.
*
* @throws IOException If there is an error parsing the document.
*/
/*
* Used in methods derived from DrawPrintTextLocations
*/
private AffineTransform flipAT;
private AffineTransform rotateAT;
/**
* Regions updated by calls to writeString
*/
private Set<Rectangle2D> boxes;
// Border to allow when finding intersections
private double dx = 1.0; // This value works for me, feel free to tweak (or add setter)
private double dy = 0.000; // Rows of text tend to overlap, so need to extend
/**
* Region in which to find table (otherwise whole page)
*/
private Rectangle2D regionArea;
/**
* Number of rows in inferred table
*/
private int nRows=0;
/**
* Number of columns in inferred table
*/
private int nCols=0;
/**
* This is the object that does the text extraction
*/
private PDFTextStripperByArea regionStripper;
/**
* 1D intervals - used for calculateTableRegions()
* @author Beldaz
*
*/
public static class Interval {
double start;
double end;
public Interval(double start, double end) {
this.start=start; this.end = end;
}
public void add(Interval col) {
if(col.start<start)
start = col.start;
if(col.end>end)
end = col.end;
}
public static void addTo(Interval x, LinkedList<Interval> columns) {
int p = 0;
Iterator<Interval> it = columns.iterator();
// Find where x should go
while(it.hasNext()) {
Interval col = it.next();
if(x.end>=col.start) {
if(x.start<=col.end) { // overlaps
x.add(col);
it.remove();
}
break;
}
++p;
}
while(it.hasNext()) {
Interval col = it.next();
if(x.start>col.end)
break;
x.add(col);
it.remove();
}
columns.add(p, x);
}
}
/**
* Instantiate a new PDFTableStripper object.
*
* @param document
* @throws IOException If there is an error loading the properties.
*/
public PDFTableStripper() throws IOException
{
super.setShouldSeparateByBeads(false);
regionStripper = new PDFTextStripperByArea();
regionStripper.setSortByPosition( true );
}
/**
* Define the region to group text by.
*
* @param rect The rectangle area to retrieve the text from.
*/
public void setRegion(Rectangle2D rect )
{
regionArea = rect;
}
public int getRows()
{
return nRows;
}
public int getColumns()
{
return nCols;
}
/**
* Get the text for the region, this should be called after extractTable().
*
* @return The text that was identified in that region.
*/
public String getText(int row, int col)
{
return regionStripper.getTextForRegion("el"+col+"x"+row);
}
public void extractTable(PDPage pdPage) throws IOException
{
setStartPage(getCurrentPageNo());
setEndPage(getCurrentPageNo());
boxes = new HashSet<Rectangle2D>();
// flip y-axis
flipAT = new AffineTransform();
flipAT.translate(0, pdPage.getBBox().getHeight());
flipAT.scale(1, -1);
// page may be rotated
rotateAT = new AffineTransform();
int rotation = pdPage.getRotation();
if (rotation != 0)
{
PDRectangle mediaBox = pdPage.getMediaBox();
switch (rotation)
{
case 90:
rotateAT.translate(mediaBox.getHeight(), 0);
break;
case 270:
rotateAT.translate(0, mediaBox.getWidth());
break;
case 180:
rotateAT.translate(mediaBox.getWidth(), mediaBox.getHeight());
break;
default:
break;
}
rotateAT.rotate(Math.toRadians(rotation));
}
// Trigger processing of the document so that writeString is called.
try (Writer dummy = new OutputStreamWriter(new ByteArrayOutputStream())) {
super.output = dummy;
super.processPage(pdPage);
}
Rectangle2D[][] regions = calculateTableRegions();
// System.err.println("Drawing " + nCols + "x" + nRows + "="+ nRows*nCols + " regions");
for(int i=0; i<nCols; ++i) {
for(int j=0; j<nRows; ++j) {
final Rectangle2D region = regions[i][j];
regionStripper.addRegion("el"+i+"x"+j, region);
}
}
regionStripper.extractRegions(pdPage);
}
/**
* Infer a rectangular grid of regions from the boxes field.
*
* @return 2D array of table regions (as Rectangle2D objects). Note that
* some of these regions may have no content.
*/
private Rectangle2D[][] calculateTableRegions() {
// Build up a list of all table regions, based upon the populated
// regions of boxes field. Treats the horizontal and vertical extents
// of each box as distinct
LinkedList<Interval> columns = new LinkedList<Interval>();
LinkedList<Interval> rows = new LinkedList<Interval>();
for(Rectangle2D box: boxes) {
Interval x = new Interval(box.getMinX(), box.getMaxX());
Interval y = new Interval(box.getMinY(), box.getMaxY());
Interval.addTo(x, columns);
Interval.addTo(y, rows);
}
nRows = rows.size();
nCols = columns.size();
Rectangle2D[][] regions = new Rectangle2D[nCols][nRows];
int i=0;
// Label regions from top left, rather than the transformed orientation
for(Interval column: columns) {
int j=0;
for(Interval row: rows) {
regions[nCols-i-1][nRows-j-1] = new Rectangle2D.Double(column.start, row.start, column.end - column.start, row.end - row.start);
++j;
}
++i;
}
return regions;
}
/**
* Register each character's bounding box, updating boxes field to maintain
* a list of all distinct groups of characters.
*
* Overrides the default functionality of PDFTextStripper.
* Most of this is taken from DrawPrintTextLocations.java, with extra steps
* at end of main loop
*/
@Override
protected void writeString(String string, List<TextPosition> textPositions) throws IOException
{
for (TextPosition text : textPositions)
{
// glyph space -> user space
// note: text.getTextMatrix() is *not* the Text Matrix, it's the Text Rendering Matrix
AffineTransform at = text.getTextMatrix().createAffineTransform();
PDFont font = text.getFont();
BoundingBox bbox = font.getBoundingBox();
// advance width, bbox height (glyph space)
float xadvance = font.getWidth(text.getCharacterCodes()[0]); // todo: should iterate all chars
Rectangle2D.Float rect = new Rectangle2D.Float(0, bbox.getLowerLeftY(), xadvance, bbox.getHeight());
if (font instanceof PDType3Font)
{
// bbox and font matrix are unscaled
at.concatenate(font.getFontMatrix().createAffineTransform());
}
else
{
// bbox and font matrix are already scaled to 1000
at.scale(1/1000f, 1/1000f);
}
Shape s = at.createTransformedShape(rect);
s = flipAT.createTransformedShape(s);
s = rotateAT.createTransformedShape(s);
//
// Merge character's bounding box with boxes field
//
Rectangle2D bounds = s.getBounds2D();
// Pad sides to detect almost touching boxes
Rectangle2D hitbox = bounds.getBounds2D();
hitbox.add(bounds.getMinX() - dx , bounds.getMinY() - dy);
hitbox.add(bounds.getMaxX() + dx , bounds.getMaxY() + dy);
// Find all overlapping boxes
List<Rectangle2D> intersectList = new ArrayList<Rectangle2D>();
for(Rectangle2D box: boxes) {
if(box.intersects(hitbox)) {
intersectList.add(box);
}
}
// Combine all touching boxes and update
// (NOTE: Potentially this could leave some overlapping boxes un-merged,
// but it's sufficient for now and get's fixed up in calculateTableRegions)
for(Rectangle2D box: intersectList) {
bounds.add(box);
boxes.remove(box);
}
boxes.add(bounds);
}
}
/**
* This method does nothing in this derived class, because beads and regions are incompatible. Beads are
* ignored when stripping by area.
*
* @param aShouldSeparateByBeads The new grouping of beads.
*/
@Override
public final void setShouldSeparateByBeads(boolean aShouldSeparateByBeads)
{
}
/**
* Adapted from PDFTextStripperByArea
* {@inheritDoc}
*/
@Override
protected void processTextPosition( TextPosition text )
{
if(regionArea!=null && !regionArea.contains( text.getX(), text.getY() ) ) {
// skip character
} else {
super.processTextPosition( text );
}
}
}

View file

@ -99,6 +99,14 @@ public class ConverterWebController {
return modelAndView; return modelAndView;
} }
@GetMapping("/pdf-to-csv")
@Hidden
public ModelAndView pdfToCSV() {
ModelAndView modelAndView = new ModelAndView("convert/pdf-to-csv");
modelAndView.addObject("currentPage", "pdf-to-csv");
return modelAndView;
}
@GetMapping("/pdf-to-pdfa") @GetMapping("/pdf-to-pdfa")
@Hidden @Hidden

View file

@ -0,0 +1,18 @@
package stirling.software.SPDF.model.api.extract;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import stirling.software.SPDF.model.api.PDFFile;
@Data
@EqualsAndHashCode(callSuper=true)
public class PDFFilePage extends PDFFile {
@Schema(description = "Number of chosen page", type = "number")
private int pageId;
}

View file

@ -823,3 +823,8 @@ PDFToXML.title=تحويل PDF إلى XML
PDFToXML.header=تحويل PDF إلى XML PDFToXML.header=تحويل PDF إلى XML
PDFToXML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات. PDFToXML.credit=تستخدم هذه الخدمة LibreOffice لتحويل الملفات.
PDFToXML.submit=تحويل PDFToXML.submit=تحويل
#PDFToCSV
PDFToCSV.title= PDF ??? CSV
PDFToCSV.header=PDF ??? CSV
PDFToCSV.submit=??????

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF към XML
PDFToXML.header=PDF към XML PDFToXML.header=PDF към XML
PDFToXML.credit=Тази услуга използва LibreOffice за преобразуване на файлове. PDFToXML.credit=Тази услуга използва LibreOffice за преобразуване на файлове.
PDFToXML.submit=Преобразуване PDFToXML.submit=Преобразуване
#PDFToCSV
PDFToCSV.title=PDF ??? CSV
PDFToCSV.header=PDF ??? CSV
PDFToCSV.submit=????????

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF a XML
PDFToXML.header=PDF a XML PDFToXML.header=PDF a XML
PDFToXML.credit=Utilitza LibreOffice per a la conversió d'Arxius. PDFToXML.credit=Utilitza LibreOffice per a la conversió d'Arxius.
PDFToXML.submit=Converteix PDFToXML.submit=Converteix
#PDFToCSV
PDFToCSV.title=PDF a CSV
PDFToCSV.header=PDF a CSV
PDFToCSV.submit=Extracte

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF in XML
PDFToXML.header=PDF in XML PDFToXML.header=PDF in XML
PDFToXML.credit=Dieser Dienst verwendet LibreOffice für die Dateikonvertierung. PDFToXML.credit=Dieser Dienst verwendet LibreOffice für die Dateikonvertierung.
PDFToXML.submit=Konvertieren PDFToXML.submit=Konvertieren
#PDFToCSV
PDFToCSV.title=PDF zu CSV
PDFToCSV.header=PDF zu CSV
PDFToCSV.submit=Extrakt

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF \u03C3\u03B5 XML
PDFToXML.header=PDF \u03C3\u03B5 XML PDFToXML.header=PDF \u03C3\u03B5 XML
PDFToXML.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD. PDFToXML.credit=\u0391\u03C5\u03C4\u03AE \u03B7 \u03C5\u03C0\u03B7\u03C1\u03B5\u03C3\u03AF\u03B1 \u03C7\u03C1\u03B7\u03C3\u03B9\u03BC\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF LibreOffice \u03B3\u03B9\u03B1 \u03C4\u03B7 \u03BC\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE \u03C4\u03C9\u03BD \u03B1\u03C1\u03C7\u03B5\u03AF\u03C9\u03BD.
PDFToXML.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE PDFToXML.submit=\u039C\u03B5\u03C4\u03B1\u03C4\u03C1\u03BF\u03C0\u03AE
#PDFToCSV
PDFToCSV.title=PDF ?? CSV
PDFToCSV.header=PDF ?? CSV
PDFToCSV.submit=?????????

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF to XML
PDFToXML.header=PDF to XML PDFToXML.header=PDF to XML
PDFToXML.credit=This service uses LibreOffice for file conversion. PDFToXML.credit=This service uses LibreOffice for file conversion.
PDFToXML.submit=Convert PDFToXML.submit=Convert
#PDFToCSV
PDFToCSV.title=PDF to CSV
PDFToCSV.header=PDF to CSV
PDFToCSV.submit=Extract

View file

@ -93,7 +93,6 @@ account.accountSettings=Account Settings
account.adminSettings=Admin Settings - View and Add Users account.adminSettings=Admin Settings - View and Add Users
account.userControlSettings=User Control Settings account.userControlSettings=User Control Settings
account.changeUsername=Change Username account.changeUsername=Change Username
account.changeUsername=Change Username
account.password=Confirmation Password account.password=Confirmation Password
account.oldPassword=Old password account.oldPassword=Old password
account.newPassword=New Password account.newPassword=New Password
@ -334,7 +333,12 @@ showJS.tags=JS
home.autoRedact.title=Auto Redact home.autoRedact.title=Auto Redact
home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text home.autoRedact.desc=Auto Redacts(Blacks out) text in a PDF based on input text
showJS.tags=JS autoRedact.tags=JS
home.tableExtraxt.title=Table Extraction
home.tableExtraxt.desc=Table Extraction from PDF to CSV
tableExtraxt.tags=CSV
########################### ###########################
# # # #
@ -773,7 +777,6 @@ changeMetadata.keywords=Keywords:
changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss): changeMetadata.modDate=Modification Date (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Producer: changeMetadata.producer=Producer:
changeMetadata.subject=Subject: changeMetadata.subject=Subject:
changeMetadata.title=Title:
changeMetadata.trapped=Trapped: changeMetadata.trapped=Trapped:
changeMetadata.selectText.4=Other Metadata: changeMetadata.selectText.4=Other Metadata:
changeMetadata.selectText.5=Add Custom Metadata Entry changeMetadata.selectText.5=Add Custom Metadata Entry
@ -823,3 +826,8 @@ PDFToXML.title=PDF to XML
PDFToXML.header=PDF to XML PDFToXML.header=PDF to XML
PDFToXML.credit=This service uses LibreOffice for file conversion. PDFToXML.credit=This service uses LibreOffice for file conversion.
PDFToXML.submit=Convert PDFToXML.submit=Convert
#PDFToCSV
PDFToCSV.title=PDF to CSV
PDFToCSV.header=PDF to CSV
PDFToCSV.submit=Extract

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF a XML
PDFToXML.header=PDF a XML PDFToXML.header=PDF a XML
PDFToXML.credit=Este servicio utiliza LibreOffice para la conversión de archivos PDFToXML.credit=Este servicio utiliza LibreOffice para la conversión de archivos
PDFToXML.submit=Convertir PDFToXML.submit=Convertir
#PDFToCSV
PDFToCSV.title=PDF a CSV
PDFToCSV.header=PDF a CSV
PDFToCSV.submit=Extracto

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDFa XML bihurtu
PDFToXML.header=PDFa XML bihurtu PDFToXML.header=PDFa XML bihurtu
PDFToXML.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko PDFToXML.credit=Zerbitzu honek LibreOffice erabiltzen du fitxategiak bihurtzeko
PDFToXML.submit=Bihurtu PDFToXML.submit=Bihurtu
#PDFToCSV
PDFToCSV.title=PDF a CSV
PDFToCSV.header=PDF a CSV
PDFToCSV.submit=Extracto

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF en XML
PDFToXML.header=PDF en XML PDFToXML.header=PDF en XML
PDFToXML.credit=Ce service utilise LibreOffice pour la conversion de fichiers. PDFToXML.credit=Ce service utilise LibreOffice pour la conversion de fichiers.
PDFToXML.submit=Convertir PDFToXML.submit=Convertir
#PDFToCSV
PDFToCSV.title=PDF en CSV
PDFToCSV.header=PDF en CSV
PDFToCSV.submit=Extrait

View file

@ -823,3 +823,8 @@ PDFToXML.title=Da PDF a XML
PDFToXML.header=Da PDF a XML PDFToXML.header=Da PDF a XML
PDFToXML.credit=Questo servizio utilizza LibreOffice per la conversione. PDFToXML.credit=Questo servizio utilizza LibreOffice per la conversione.
PDFToXML.submit=Converti PDFToXML.submit=Converti
#PDFToCSV
PDFToCSV.title=Da PDF a CSV
PDFToCSV.header=Da PDF a CSV
PDFToCSV.submit=Estratto

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDFをXMLに変換
PDFToXML.header=PDFをXMLに変換 PDFToXML.header=PDFをXMLに変換
PDFToXML.credit=本サービスはファイル変換にLibreOfficeを使用しています。 PDFToXML.credit=本サービスはファイル変換にLibreOfficeを使用しています。
PDFToXML.submit=変換 PDFToXML.submit=変換
#PDFToCSV
PDFToCSV.title=PDF??CSV?
PDFToCSV.header=PDF??CSV?
PDFToCSV.submit=????

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF to XML
PDFToXML.header=PDF를 XML로 변환 PDFToXML.header=PDF를 XML로 변환
PDFToXML.credit=이 서비스는 파일 변환을 위해 LibreOffice를 사용합니다. PDFToXML.credit=이 서비스는 파일 변환을 위해 LibreOffice를 사용합니다.
PDFToXML.submit=변환 PDFToXML.submit=변환
#PDFToCSV
PDFToCSV.title=PDF? CSV?
PDFToCSV.header=PDF? CSV?
PDFToCSV.submit=??

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF naar XML
PDFToXML.header=PDF naar XML PDFToXML.header=PDF naar XML
PDFToXML.credit=Deze service gebruikt LibreOffice voor bestandsconversie. PDFToXML.credit=Deze service gebruikt LibreOffice voor bestandsconversie.
PDFToXML.submit=Converteren PDFToXML.submit=Converteren
#PDFToCSV
PDFToCSV.title=PDF naar CSV
PDFToCSV.header=PDF naar CSV
PDFToCSV.submit=Extract

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF na XML
PDFToXML.header=PDF na XML PDFToXML.header=PDF na XML
PDFToXML.credit=Ta usługa używa LibreOffice do konwersji plików. PDFToXML.credit=Ta usługa używa LibreOffice do konwersji plików.
PDFToXML.submit=Konwertuj PDFToXML.submit=Konwertuj
#PDFToCSV
PDFToCSV.title=PDF na CSV
PDFToCSV.header=PDF na CSV
PDFToCSV.submit=Wyci?g

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF para XML
PDFToXML.header=PDF para XML PDFToXML.header=PDF para XML
PDFToXML.credit=Este serviço usa o LibreOffice para Conversão de Arquivos. PDFToXML.credit=Este serviço usa o LibreOffice para Conversão de Arquivos.
PDFToXML.submit=Converter PDFToXML.submit=Converter
#PDFToCSV
PDFToCSV.title=PDF para CSV
PDFToCSV.header=PDF para CSV
PDFToCSV.submit=Eztennañ

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF către XML
PDFToXML.header=PDF către XML PDFToXML.header=PDF către XML
PDFToXML.credit=Acest serviciu utilizează LibreOffice pentru conversia fișierului. PDFToXML.credit=Acest serviciu utilizează LibreOffice pentru conversia fișierului.
PDFToXML.submit=Convert PDFToXML.submit=Convert
#PDFToCSV
PDFToCSV.title=PDF în CSV
PDFToCSV.header=PDF în CSV
PDFToCSV.submit=Extrage

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF в XML
PDFToXML.header=PDF в XML PDFToXML.header=PDF в XML
PDFToXML.credit=Этот сервис использует LibreOffice для преобразования файлов. PDFToXML.credit=Этот сервис использует LibreOffice для преобразования файлов.
PDFToXML.submit=Конвертировать PDFToXML.submit=Конвертировать
#PDFToCSV
PDFToCSV.title=PDF ? CSV
PDFToCSV.header=PDF ? CSV
PDFToCSV.submit=???????

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF till XML
PDFToXML.header=PDF till XML PDFToXML.header=PDF till XML
PDFToXML.credit=Denna tjänst använder LibreOffice för filkonvertering. PDFToXML.credit=Denna tjänst använder LibreOffice för filkonvertering.
PDFToXML.submit=Konvertera PDFToXML.submit=Konvertera
#PDFToCSV
PDFToCSV.title=PDF till CSV
PDFToCSV.header=PDF till CSV
PDFToCSV.submit=Navvit

View file

@ -823,3 +823,8 @@ PDFToXML.title=PDF To XML
PDFToXML.header=将PDF转换为XML PDFToXML.header=将PDF转换为XML
PDFToXML.credit=此服务使用LibreOffice进行文件转换。 PDFToXML.credit=此服务使用LibreOffice进行文件转换。
PDFToXML.submit=转换 PDFToXML.submit=转换
#PDFToCSV
PDFToCSV.title=PDF ? CSV
PDFToCSV.header=PDF ? CSV
PDFToCSV.submit=??

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V304H176c-35.3 0-64 28.7-64 64V512H64c-35.3 0-64-28.7-64-64V64zm384 64H256V0L384 128zM200 352h16c22.1 0 40 17.9 40 40v8c0 8.8-7.2 16-16 16s-16-7.2-16-16v-8c0-4.4-3.6-8-8-8H200c-4.4 0-8 3.6-8 8v80c0 4.4 3.6 8 8 8h16c4.4 0 8-3.6 8-8v-8c0-8.8 7.2-16 16-16s16 7.2 16 16v8c0 22.1-17.9 40-40 40H200c-22.1 0-40-17.9-40-40V392c0-22.1 17.9-40 40-40zm133.1 0H368c8.8 0 16 7.2 16 16s-7.2 16-16 16H333.1c-7.2 0-13.1 5.9-13.1 13.1c0 5.2 3 9.9 7.8 12l37.4 16.6c16.3 7.2 26.8 23.4 26.8 41.2c0 24.9-20.2 45.1-45.1 45.1H304c-8.8 0-16-7.2-16-16s7.2-16 16-16h42.9c7.2 0 13.1-5.9 13.1-13.1c0-5.2-3-9.9-7.8-12l-37.4-16.6c-16.3-7.2-26.8-23.4-26.8-41.2c0-24.9 20.2-45.1 45.1-45.1zm98.9 0c8.8 0 16 7.2 16 16v31.6c0 23 5.5 45.6 16 66c10.5-20.3 16-42.9 16-66V368c0-8.8 7.2-16 16-16s16 7.2 16 16v31.6c0 34.7-10.3 68.7-29.6 97.6l-5.1 7.7c-3 4.5-8 7.1-13.3 7.1s-10.3-2.7-13.3-7.1l-5.1-7.7c-19.3-28.9-29.6-62.9-29.6-97.6V368c0-8.8 7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,158 @@
<!DOCTYPE html>
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{PDFToCSV.title})}"></th:block>
<body>
<div id="page-container">
<div id="content-wrap">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{PDFToCSV.header}"></h2>
<form id="PDFToCSVForm" th:action="@{api/v1/extract/pdf-to-csv}" method="post" enctype="multipart/form-data">
<input id="pageId" type="hidden" name="pageId" />
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<button type="submit" class="btn btn-primary" th:text="#{PDFToCSV.submit}"></button>
</form>
<p id="instruction-text" style="margin: 0; display: none">Choose page to extract table</p>
<div style="position: relative; display: inline-block;">
<div>
<div style="display:none ;margin: 3px;position: absolute;top: 0;width: 120px;justify-content:space-between;z-index: 10" id="pagination-button-container">
<button id='previous-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> < </button>
<button id='next-page-btn' style='opacity: 80% ; width: 50px; height: 30px; display: flex;align-items: center;justify-content: center; background: grey; color: #ffffff; ;border: none;outline: none; border-radius: 4px;'> > </button>
</div>
<canvas id="crop-pdf-canvas" style="position: absolute; top: 0; left: 0; z-index: 1;"></canvas>
</div>
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2;"></canvas>
</div>
<script>
let pdfCanvas = document.getElementById('crop-pdf-canvas');
let overlayCanvas = document.getElementById('overlayCanvas');
// let paginationBtnContainer = ;
let context = pdfCanvas.getContext('2d');
let btn1Object = document.getElementById('previous-page-btn');
let btn2Object = document.getElementById('next-page-btn');
overlayCanvas.width = pdfCanvas.width;
overlayCanvas.height = pdfCanvas.height;
let fileInput = document.getElementById('fileInput-input');
let file;
let pdfDoc = null;
let pageId = document.getElementById('pageId');
let currentPage = 1;
let totalPages = 0;
let startX = 0;
let startY = 0;
let rectWidth = 0;
let rectHeight = 0;
btn1Object.addEventListener('click',function (e){
if (currentPage !== 1) {
currentPage = currentPage - 1;
pageId.value = currentPage;
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function (ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
});
btn2Object.addEventListener('click',function (e){
if (currentPage !== totalPages){
currentPage=currentPage+1;
pageId.value = currentPage;
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function(ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
};
reader.readAsArrayBuffer(file);
}
}
});
fileInput.addEventListener('change', function(e) {
file = e.target.files[0];
if (file.type === 'application/pdf') {
let reader = new FileReader();
reader.onload = function(ev) {
let typedArray = new Uint8Array(reader.result);
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js'
pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
pdfDoc = pdf;
totalPages = pdf.numPages;
renderPage(currentPage);
});
pageId.value = currentPage;
};
reader.readAsArrayBuffer(file);
document.getElementById("pagination-button-container").style.display="flex";
document.getElementById("instruction-text").style.display="block";
}
});
function renderPage(pageNumber) {
pdfDoc.getPage(pageNumber).then(function(page) {
let viewport = page.getViewport({ scale: 1.0 });
pdfCanvas.width = viewport.width;
pdfCanvas.height = viewport.height;
overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
overlayCanvas.height = viewport.height;
let renderContext = { canvasContext: context, viewport: viewport };
page.render(renderContext);
pdfCanvas.classList.add("shadow-canvas");
});
}
</script>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>

View file

@ -105,6 +105,8 @@
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div> <div th:replace="~{fragments/navbarEntry :: navbarEntry ('cert-sign', 'images/award.svg', 'home.certSign.title', 'home.certSign.desc', 'certSign.tags')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div> <div th:replace="~{fragments/navbarEntry :: navbarEntry ('sanitize-pdf', 'images/sanitize.svg', 'home.sanitizePdf.title', 'home.sanitizePdf.desc', 'sanitizePdf.tags')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-redact', 'images/eraser-fill.svg', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags')}"></div> <div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-redact', 'images/eraser-fill.svg', 'home.autoRedact.title', 'home.autoRedact.desc', 'autoRedact.tags')}"></div>
<div th:replace="~{fragments/navbarEntry :: navbarEntry ('auto-extract', 'images/eraser-fill.svg', 'home.tableExtraxt.title', 'home.tableExtraxt.desc', 'tableExtraxt.tags')}"></div>
</div> </div>
</li> </li>

View file

@ -93,6 +93,7 @@
<div th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', svgPath='images/js.svg')}"></div> <div th:replace="~{fragments/card :: card(id='show-javascript', cardTitle=#{home.showJS.title}, cardText=#{home.showJS.desc}, cardLink='show-javascript', svgPath='images/js.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', svgPath='images/eraser-fill.svg')}"></div> <div th:replace="~{fragments/card :: card(id='auto-redact', cardTitle=#{home.autoRedact.title}, cardText=#{home.autoRedact.desc}, cardLink='auto-redact', svgPath='images/eraser-fill.svg')}"></div>
<div th:replace="~{fragments/card :: card(id='pdf-to-csv', cardTitle=#{home.tableExtraxt.title}, cardText=#{home.tableExtraxt.desc}, cardLink='pdf-to-csv', svgPath='images/pdf-csv.svg')}"></div>