From 21de6c6520bdad2b6254a04494e7b97e51ad8307 Mon Sep 17 00:00:00 2001 From: NeilJared Date: Wed, 26 Jul 2023 12:17:20 +0200 Subject: [PATCH 01/21] Update messages_es_ES.properties partial translation update, will continue by line 348 --- src/main/resources/messages_es_ES.properties | 142 +++++++++---------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index 56e92fdb..f750be96 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -6,7 +6,7 @@ language.direction=ltr pdfPrompt=Seleccionar PDF(s) multiPdfPrompt=Seleccionar PDFs (2+) -multiPdfDropPrompt=Selecciona (o arrastra y suelta) todos los PDFs que quieras +multiPdfDropPrompt=Seleccione (o arrastre y suelte) todos los PDFs que quiera imgPrompt=Seleccionar Imagen(es) genericSubmit=Enviar processTimeWarning=Advertencia: este proceso puede tardar hasta un minuto dependiendo del tamaño del archivo @@ -19,17 +19,17 @@ save=Guardar close=Cerrar filesSelected=archivos seleccionados noFavourites=No se agregaron favoritos -bored=¿Aburrido de esperar? +bored=¿Cansado de esperar? alphabet=Alfabeto downloadPdf=Descargar PDF text=Texto font=Fuente -selectFillter=-- Select -- +selectFillter=-- Seleccionar -- pageNum=Número de página -sizes.small=Small -sizes.medium=Medium -sizes.large=Large -sizes.x-large=X-Large +sizes.small=Paqueño +sizes.medium=Mediano +sizes.large=Grande +sizes.x-large=Extra grande error.pdfPassword=El documento PDF está protegido con contraseña y no se ha proporcionado o es incorrecta @@ -58,30 +58,30 @@ settings.zipThreshold=Ficheros ZIP cuando excede el número de ficheros descarga ############# # HOME-PAGE # ############# -home.desc=Tu ventanilla única autohospedada para todas tus necesidades PDF +home.desc=Su ventanilla única autohospedada para todas tus necesidades PDF home.multiTool.title=Multi-herramienta PDF home.multiTool.desc=Combinar, rotar, reorganizar y eliminar páginas -multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side +multiTool.tags=Multi-herramienta,Multi-operación,Interfaz de usuario,Arrastrar con un click,front end,lado del client home.merge.title=Unir home.merge.desc=Unir fácilmente múltiples PDFs en uno -merge.tags=merge,Page operations,Back end,server side +merge.tags=Unir,Operaciones de página,Back end,lado del servidor home.split.title=Dividir home.split.desc=Dividir PDFs en múltiples documentos ########################## ### TODO: Translate ### ########################## -split.tags=Page operations,divide,Multi Page,cut,server side +split.tags=Operaciones de página,dividir,Multi-página,cortar,lado del servidor home.rotate.title=Rotar -home.rotate.desc=Rotar fácilmente tus PDFs +home.rotate.desc=Rotar fácilmente sus PDFs ########################## ### TODO: Translate ### ########################## -rotate.tags=server side +rotate.tags=lado del servidor home.imageToPdf.title=Imagen a PDF @@ -89,43 +89,43 @@ home.imageToPdf.desc=Convertir una imagen (PNG, JPEG, GIF) a PDF ########################## ### TODO: Translate ### ########################## -imageToPdf.tags=conversion,img,jpg,picture,photo +imageToPdf.tags=conversión,img,jpg,imagen,fotografía home.pdfToImage.title=PDF a Imagen home.pdfToImage.desc=Convertir un PDF a una imagen (PNG, JPEG, GIF) ########################## ### TODO: Translate ### ########################## -pdfToImage.tags=conversion,img,jpg,picture,photo +pdfToImage.tags=conversión,img,jpg,imagen,fotografía home.pdfOrganiser.title=Organizador home.pdfOrganiser.desc=Eliminar/Reorganizar páginas en cualquier orden ########################## ### TODO: Translate ### ########################## -pdfOrganiser.tags=duplex,even,odd,sort,move +pdfOrganiser.tags=doble cara,pares,impares,ordenar,mover home.addImage.title=Agregar imagen al PDF -home.addImage.desc=Agregar una imagen en una ubicación establecida en el PDF (trabajo en progreso) +home.addImage.desc=Agregar una imagen en una ubicación establecida en el PDF (en desarrollo) ########################## ### TODO: Translate ### ########################## -addImage.tags=img,jpg,picture,photo +addImage.tags=img,jpg,imagen,fotografía home.watermark.title=Añadir marca de agua home.watermark.desc=Añadir una marca de agua predefinida al documento PDF ########################## ### TODO: Translate ### ########################## -watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo +watermark.tags=Texto,repetir,etiquetar,propietario,copyight,marca comercial,img,jpg,imagen,fotografía home.permissions.title=Cambiar permisos home.permissions.desc=Cambiar los permisos del documento PDF ########################## ### TODO: Translate ### ########################## -permissions.tags=read,write,edit,print +permissions.tags=leer,escribir,editar,imprimir home.removePages.title=Eliminar @@ -133,28 +133,28 @@ home.removePages.desc=Eliminar páginas no deseadas del documento PDF ########################## ### TODO: Translate ### ########################## -removePages.tags=Remove pages,delete pages +removePages.tags=Borrar páginas,eliminar páginas home.addPassword.title=Añadir contraseña home.addPassword.desc=Encriptar el documento PDF con una contraseña ########################## ### TODO: Translate ### ########################## -addPassword.tags=secure,security +addPassword.tags=seguro,seguridad home.removePassword.title=Eliminar contraseña home.removePassword.desc=Eliminar la contraseña del documento PDF ########################## ### TODO: Translate ### ########################## -removePassword.tags=secure,Decrypt,security,unpassword,delete password +removePassword.tags=seguro,Desencriptar,seguridad,quitar contraseña,eliminar contraseña home.compressPdfs.title=Comprimir home.compressPdfs.desc=Comprimir PDFs para reducir el tamaño del fichero ########################## ### TODO: Translate ### ########################## -compressPdfs.tags=squish,small,tiny +compressPdfs.tags=aplastar,pequeño,diminuto home.changeMetadata.title=Cambiar metadatos @@ -162,21 +162,21 @@ home.changeMetadata.desc=Cambiar/Eliminar/Añadir metadatos al documento PDF ########################## ### TODO: Translate ### ########################## -changeMetadata.tags==Title,author,date,creation,time,publisher,producer,stats +changeMetadata.tags==Título,autor,fecha,creación,hora,editorial,productor,estadísticas home.fileToPDF.title=Convertir fichero a PDF home.fileToPDF.desc=Convertir casi cualquier archivo a PDF (DOCX, PNG, XLS, PPT, TXT y más) ########################## ### TODO: Translate ### ########################## -fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint +fileToPDF.tags=transformación,formato,documento,imagen,diapositiva,texto,conversión,office,docs,word,excel,powerpoint -home.ocr.title=Ejecutar OCR en PDF y/o escaneos de limpieza -home.ocr.desc=Escaneos de limpieza y detectar texto de imágenes dentro de un PDF y volver a agregarlo como texto +home.ocr.title=Ejecutar OCR en PDF y/o tareas de limpieza +home.ocr.desc=Tareas de limpieza y detectar texto en imágenes dentro de un PDF y volver a incrustarlo como texto ########################## ### TODO: Translate ### ########################## -ocr.tags=recognition,text,image,scan,read,identify,detection,editable +ocr.tags=reconocimiento,texto,imagen,escanear,leer,identificar,detección,editable home.extractImages.title=Extraer imágenes @@ -184,42 +184,42 @@ home.extractImages.desc=Extraer todas las imágenes de un PDF y guardarlas en ZI ########################## ### TODO: Translate ### ########################## -extractImages.tags=picture,photo,save,archive,zip,capture,grab +extractImages.tags=imagen,fotografía,guardar,archivo,zip,capturar,coger home.pdfToPDFA.title=Convertir PDF a PDF/A home.pdfToPDFA.desc=Convertir PDF a PDF/A para almacenamiento a largo plazo ########################## ### TODO: Translate ### ########################## -pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation +pdfToPDFA.tags=archivo,largo plazo,estándar,conversión,almacewnamiento,conservación home.PDFToWord.title=PDF a Word home.PDFToWord.desc=Convertir formatos PDF a Word (DOC, DOCX y ODT) ########################## ### TODO: Translate ### ########################## -PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile +PDFToWord.tags=doc,docx,odt,word,transformación,formato,conversión,office,microsoft,archivo del documento home.PDFToPresentation.title=PDF a presentación home.PDFToPresentation.desc=Convertir PDF a formatos de presentación (PPT, PPTX y ODP) ########################## ### TODO: Translate ### ########################## -PDFToPresentation.tags=slides,show,office,microsoft +PDFToPresentation.tags=diapositivas,mostrar,office,microsoft home.PDFToText.title=PDF a TXT o RTF home.PDFToText.desc=Convertir PDF a formato TXT o RTF ########################## ### TODO: Translate ### ########################## -PDFToText.tags=richformat,richtextformat,rich text format +PDFToText.tags=formato enriquecido,formato de texto enriquecido,formato de texto enriquecido home.PDFToHTML.title=PDF a HTML home.PDFToHTML.desc=Convertir PDF a formato HTML ########################## ### TODO: Translate ### ########################## -PDFToHTML.tags=web content,browser friendly +PDFToHTML.tags=contenido web,amigable para navegador home.PDFToXML.title=PDF a XML @@ -227,132 +227,132 @@ home.PDFToXML.desc=Convertir PDF a formato XML ########################## ### TODO: Translate ### ########################## -PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert +PDFToXML.tags=extracción de datos,contenido estructurado,interopersabilidad,transformación,convertir home.ScannerImageSplit.title=Detectar/Dividir fotos escaneadas home.ScannerImageSplit.desc=Dividir varias fotos dentro de una foto/PDF ########################## ### TODO: Translate ### ########################## -ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize +ScannerImageSplit.tags=separar,auto-detectar,escaneos,multi-foto,organizar home.sign.title=Firmar home.sign.desc=Añadir firma a PDF mediante dibujo, texto o imagen ########################## ### TODO: Translate ### ########################## -sign.tags=authorize,initials,drawn-signature,text-sign,image-signature +sign.tags=autorizar,iniciales,firma manuscrita,texto de firma,imagen de firma home.flatten.title=Aplanar home.flatten.desc=Eliminar todos los elementos y formularios interactivos de un PDF ########################## ### TODO: Translate ### ########################## -flatten.tags=static,deactivate,non-interactive,streamline +flatten.tags=estática,desactivar,no interactiva,etiqueta dinámica home.repair.title=Reparar home.repair.desc=Intentar reparar un PDF corrupto/roto ########################## ### TODO: Translate ### ########################## -repair.tags=fix,restore,correction,recover +repair.tags=reparar,restaurar,corregir,recuperar home.removeBlanks.title=Eliminar páginas en blanco home.removeBlanks.desc=Detectar y eliminar páginas en blanco de un documento ########################## ### TODO: Translate ### ########################## -removeBlanks.tags=cleanup,streamline,non-content,organize +removeBlanks.tags=limpieza,dinámica,sin contenido,organizar home.compare.title=Comparar home.compare.desc=Comparar y mostrar las diferencias entre 2 documentos PDF ########################## ### TODO: Translate ### ########################## -compare.tags=differentiate,contrast,changes,analysis +compare.tags=diferenciar,contrastar,cambios,análisis home.certSign.title=Firmar con certificado home.certSign.desc=Firmar un PDF con un Certificado/Clave (PEM/P12) ########################## ### TODO: Translate ### ########################## -certSign.tags=authenticate,PEM,P12,official,encrypt +certSign.tags=autentificar,PEM,P12,oficial,encriptar home.pageLayout.title=Diseño de varias páginas home.pageLayout.desc=Unir varias páginas de un documento PDF en una sola página ########################## ### TODO: Translate ### ########################## -pageLayout.tags=merge,composite,single-view,organize +pageLayout.tags=unir,compuesto,vista única,organizar home.scalePages.title=Escalar/ajustar tamaño de página home.scalePages.desc=Escalar/cambiar el tamaño de una pagina y/o su contenido ########################## ### TODO: Translate ### ########################## -scalePages.tags=resize,modify,dimension,adapt +scalePages.tags=cambiar tamaño,modificar,dimensionar,adaptar -home.pipeline.title=Pipeline (Advanced) -home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts +home.pipeline.title=Secuencia (Avanzado) +home.pipeline.desc=Ejecutar varias tareas a PDFs definiendo una secuencia de comandos ########################## ### TODO: Translate ### ########################## -pipeline.tags=automate,sequence,scripted,batch-process +pipeline.tags=automatizar,secuencia,con script,proceso por lotes -home.add-page-numbers.title=Add Page Numbers -home.add-page-numbers.desc=Add Page numbers throughout a document in a set location +home.add-page-numbers.title=Aádir números de página +home.add-page-numbers.desc=Aádir números de página en un documento en una ubicación concreta ########################## ### TODO: Translate ### ########################## -add-page-numbers.tags=paginate,label,organize,index +add-page-numbers.tags=paginar,etiquetar,organizar,indexar -home.auto-rename.title=Auto Rename PDF File -home.auto-rename.desc=Auto renames a PDF file based on its detected header +home.auto-rename.title=Auto renombrar archivo PDF +home.auto-rename.desc=Auto renormbrar un archivo PDF según su encabezamiento detecetado ########################## ### TODO: Translate ### ########################## -auto-rename.tags=auto-detect,header-based,organize,relabel +auto-rename.tags=auto-detectar,basado en el encabezamiento,organizar,re-etiquetar -home.adjust-contrast.title=Adjust Colors/Contrast -home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF +home.adjust-contrast.title=Ajustar Color/Contraste +home.adjust-contrast.desc=Ajustar Contraste, Saturación y Brillo de un PDF ########################## ### TODO: Translate ### ########################## -adjust-contrast.tags=color-correction,tune,modify,enhance +adjust-contrast.tags=corrección de color,sintonizar color,modificar,mejorar -home.crop.title=Crop PDF -home.crop.desc=Crop a PDF to reduce its size (maintains text!) +home.crop.title=Recortar PDF +home.crop.desc=Recortar un PDF para reducir su tamaño (¡conservando el texto!) ########################## ### TODO: Translate ### ########################## -crop.tags=trim,shrink,edit,shape +crop.tags=recortar,contraer,editar,forma -home.autoSplitPDF.title=Auto Split Pages -home.autoSplitPDF.desc=Auto Split Scanned PDF with physical scanned page splitter QR Code +home.autoSplitPDF.title=Auto Dividir Páginas +home.autoSplitPDF.desc=Auto Dividir PDF escaneado con código QR divsor de página escaneada físicamente ########################## ### TODO: Translate ### ########################## -autoSplitPDF.tags=QR-based,separate,scan-segment,organize +autoSplitPDF.tags=Marcado por QR,separar,segmento de escaneo,organizar -home.sanitizePdf.title=Sanitize -home.sanitizePdf.desc=Remove scripts and other elements from PDF files +home.sanitizePdf.title=Desinfectar +home.sanitizePdf.desc=Eliminar scripts y otros elementos de los archivos PDF ########################## ### TODO: Translate ### ########################## -sanitizePdf.tags=clean,secure,safe,remove-threats +sanitizePdf.tags=limpiar,asegurar,seguro,quitar amenazas ########################## ### TODO: Translate ### ########################## -home.URLToPDF.title=URL/Website To PDF -home.URLToPDF.desc=Converts any http(s)URL to PDF -URLToPDF.tags=web-capture,save-page,web-to-doc,archive +home.URLToPDF.title=URL/Página web a PDF +home.URLToPDF.desc=Convierte cualquier dirección http(s) a PDF +URLToPDF.tags=captura web,guardar página,web-a-doc,archivo ########################## ### TODO: Translate ### ########################## -home.HTMLToPDF.title=HTML to PDF -home.HTMLToPDF.desc=Converts any HTML file or zip to PDF +home.HTMLToPDF.title=HTML a PDF +home.HTMLToPDF.desc=Convierte cualquier archivo HTML o ZIP a PDF HTMLToPDF.tags=markup,web-content,transformation,convert From 4d438142209c16dbf7ac1f15947dd6648016b14a Mon Sep 17 00:00:00 2001 From: NeilJared Date: Thu, 27 Jul 2023 21:20:30 +0200 Subject: [PATCH 02/21] Update messages_es_ES.properties ES translation completed and updated to v.0.11.2 --- src/main/resources/messages_es_ES.properties | 104 +++++++++---------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index f750be96..dbdc0a17 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -353,7 +353,7 @@ URLToPDF.tags=captura web,guardar página,web-a-doc,archivo ########################## home.HTMLToPDF.title=HTML a PDF home.HTMLToPDF.desc=Convierte cualquier archivo HTML o ZIP a PDF -HTMLToPDF.tags=markup,web-content,transformation,convert +HTMLToPDF.tags=margen,contenido web,transformación,convertir ########################### @@ -362,76 +362,76 @@ HTMLToPDF.tags=markup,web-content,transformation,convert # # ########################### #url-to-pdf -URLToPDF.title=URL To PDF -URLToPDF.header=URL To PDF -URLToPDF.submit=Convert -URLToPDF.credit=Uses WeasyPrint +URLToPDF.title=URL a PDF +URLToPDF.header=URL a PDF +URLToPDF.submit=Convertir +URLToPDF.credit=Utiliza WeasyPrint #html-to-pdf -HTMLToPDF.title=HTML To PDF -HTMLToPDF.header=HTML To PDF -HTMLToPDF.help=Accepts HTML files and ZIPs containing html/css/images etc required -HTMLToPDF.submit=Convert -HTMLToPDF.credit=Uses WeasyPrint +HTMLToPDF.title=HTML a PDF +HTMLToPDF.header=HTML a PDF +HTMLToPDF.help=Acepta archivos HTML y ZIPs conteniendo los html/css/imágenes etc requeridas +HTMLToPDF.submit=Convertir +HTMLToPDF.credit=Utiliza WeasyPrint #sanitizePDF -sanitizePDF.title=Sanitize PDF -sanitizePDF.header=Sanitize a PDF file -sanitizePDF.selectText.1=Remove JavaScript actions -sanitizePDF.selectText.2=Remove embedded files -sanitizePDF.selectText.3=Remove metadata -sanitizePDF.selectText.4=Remove links -sanitizePDF.selectText.5=Remove fonts -sanitizePDF.submit=Sanitize PDF +sanitizePDF.title=Desinfectar archivo PDF +sanitizePDF.header=Desinfectar un archivo PDF +sanitizePDF.selectText.1=Eliminar acciones JavaScript +sanitizePDF.selectText.2=Eliminar archivos incrustados +sanitizePDF.selectText.3=Eliminar metadatos +sanitizePDF.selectText.4=Eliminar enlaces +sanitizePDF.selectText.5=Eliminar fuentes +sanitizePDF.submit=Desinfectar PDF #addPageNumbers -addPageNumbers.title=Add Page Numbers -addPageNumbers.header=Add Page Numbers -addPageNumbers.selectText.1=Select PDF file: -addPageNumbers.selectText.2=Margin Size -addPageNumbers.selectText.3=Position -addPageNumbers.selectText.4=Starting Number -addPageNumbers.selectText.5=Pages to Number -addPageNumbers.selectText.6=Custom Text -addPageNumbers.submit=Add Page Numbers +addPageNumbers.title=Añadir Números de Página +addPageNumbers.header=Añadir Números de Página +addPageNumbers.selectText.1=Seleccionar archivo PDF: +addPageNumbers.selectText.2=Tamaño del margen +addPageNumbers.selectText.3=Posición +addPageNumbers.selectText.4=Número de inicio +addPageNumbers.selectText.5=Páginas a numerar +addPageNumbers.selectText.6=Texto personalizado +addPageNumbers.submit=Añadir Números de Página #auto-rename -auto-rename.title=Auto Rename -auto-rename.header=Auto Rename PDF -auto-rename.submit=Auto Rename +auto-rename.title=Auto Renombrar +auto-rename.header=Auto Renombrar PDF +auto-rename.submit=Auto Renombrar #adjustContrast -adjustContrast.title=Adjust Contrast -adjustContrast.header=Adjust Contrast -adjustContrast.contrast=Contrast: -adjustContrast.brightness=Brightness: -adjustContrast.saturation=Saturation: -adjustContrast.download=Download +adjustContrast.title=Ajustar Contraste +adjustContrast.header=Ajustar Contraste +adjustContrast.contrast=Contraste: +adjustContrast.brightness=Brillo: +adjustContrast.saturation=Saturación: +adjustContrast.download=Descargar #crop -crop.title=Crop -crop.header=Crop Image -crop.submit=Submit +crop.title=Recortar +crop.header=Recortar Imagen +crop.submit=Entregar #autoSplitPDF -autoSplitPDF.title=Auto Split PDF -autoSplitPDF.header=Auto Split PDF -autoSplitPDF.description=Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed. -autoSplitPDF.selectText.1=Print out some divider sheets from below (Black and white is fine). -autoSplitPDF.selectText.2=Scan all your documents at once by inserting the divider sheet between them. -autoSplitPDF.selectText.3=Upload the single large scanned PDF file and let Stirling PDF handle the rest. -autoSplitPDF.selectText.4=Divider pages are automatically detected and removed, guaranteeing a neat final document. -autoSplitPDF.formPrompt=Submit PDF containing Stirling-PDF Page dividers: -autoSplitPDF.dividerDownload1=Download 'Auto Splitter Divider (minimal).pdf' -autoSplitPDF.dividerDownload2=Download 'Auto Splitter Divider (with instructions).pdf' -autoSplitPDF.submit=Submit +autoSplitPDF.title=Auto Dividir PDF +autoSplitPDF.header=Auto Dividir PDF +autoSplitPDF.description=Imprimir, Insertar, Escanear, cargar, y déjenos sepsrar automáticamente sus documentos. No se necesita clasificación manual. +autoSplitPDF.selectText.1=Imprimir algunas hojas divisorias desde la parte inferior (Blanco y negro está bien). +autoSplitPDF.selectText.2=Escanee todos sus documentos a la vez insertando la hoja divisoria entre ellos. +autoSplitPDF.selectText.3=Cargue un único archivo PDF escaneado de gran tamaño y deje que Stirling PDF se encargue del resto. +autoSplitPDF.selectText.4=Las páginas divisorias son automáticamente detectadas y eliminadas, garantizando un buen documento final. +autoSplitPDF.formPrompt=Entregar PDF conteniendo divisores de página de Stirling-PDF: +autoSplitPDF.dividerDownload1=Descargar 'Auto Splitter Divider (mínima).pdf' +autoSplitPDF.dividerDownload2=Descargar 'Auto Splitter Divider (con instrucciones).pdf' +autoSplitPDF.submit=Entregar #pipeline @@ -455,13 +455,13 @@ scalePages.submit=Entregar #certSign certSign.title=Firma de certificado -certSign.header=Firmar un PDF con su certificado (Trabajo en progreso) +certSign.header=Firmar un PDF con su certificado (en desarrollo) certSign.selectPDF=Seleccione un archivo PDF para firmar: certSign.selectKey=Seleccione su archivo de clave privada (formato PKCS#8, podría ser .pem o .der): certSign.selectCert=Seleccione su archivo de certificado (formato X.509, podría ser .pem o .der): certSign.selectP12=Seleccione su archivo de almacén de claves PKCS#12 (.p12 o .pfx) (Opcional, si se proporciona, debe contener su clave privada y certificado): certSign.certType=Tipo de certificado -certSign.password=Ingrese su almacén de claves o contraseña de clave privada (si corresponde): +certSign.password=Introduzca su almacén de claves o contraseña de clave privada (si corresponde): certSign.showSig=Mostrar firma certSign.reason=Razón certSign.location=Ubicación From b5b4636e569fd0153d697a2249187e1c8136489f Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 29 Jul 2023 13:53:30 +0100 Subject: [PATCH 03/21] changes to script executor and init --- scripts/init.sh | 12 +++++++ .../api/converters/ConvertHtmlToPDF.java | 3 +- .../converters/ConvertOfficeController.java | 7 ++-- .../api/converters/ConvertPDFToPDFA.java | 3 +- .../api/converters/ConvertWebsiteToPDF.java | 3 +- .../api/other/BlankPageController.java | 5 +-- .../api/other/CompressController.java | 3 +- .../other/ExtractImageScansController.java | 3 +- .../controller/api/other/OCRController.java | 11 ++++-- .../api/other/RepairController.java | 3 +- .../api/security/PasswordController.java | 24 ++++++------- .../controller/web/SecurityWebController.java | 2 ++ .../software/SPDF/utils/PDFToFile.java | 4 ++- .../software/SPDF/utils/ProcessExecutor.java | 34 ++++++++++++++++--- 14 files changed, 85 insertions(+), 32 deletions(-) diff --git a/scripts/init.sh b/scripts/init.sh index b45bf45f..99ff2226 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -5,5 +5,17 @@ echo "Copying original files without overwriting existing files" mkdir -p /usr/share/tesseract-ocr cp -rn /usr/share/tesseract-ocr-original/* /usr/share/tesseract-ocr +# Check if TESSERACT_LANGS environment variable is set and is not empty +if [[ -n "$TESSERACT_LANGS" ]]; then + # Convert comma-separated values to a space-separated list + LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ') + + # Install each language pack + for LANG in $LANGS; do + apt-get install -y "tesseract-ocr-$LANG" + done +fi + + # Run the main command exec "$@" \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index 2d792ee3..a5878b04 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -59,7 +60,7 @@ public class ConvertHtmlToPDF { command.add("weasyprint"); command.add(tempInputFile.toString()); command.add(tempOutputFile.toString()); - int returnCode = 0; + ProcessExecutorResult returnCode; if (originalFilename.endsWith(".zip")) { returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) .runCommandWithOutputHandling(command, tempInputFile.getParent().toFile()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java index 79be9e2e..3b9f278f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java @@ -19,6 +19,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -41,7 +42,7 @@ public class ConvertOfficeController { // Run the LibreOffice command List command = new ArrayList<>(Arrays.asList("unoconv", "-vvv", "-f", "pdf", "-o", tempOutputFile.toString(), tempInputFile.toString())); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); // Read the converted PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); @@ -62,10 +63,10 @@ public class ConvertOfficeController { summary = "Convert a file to a PDF using LibreOffice", description = "This endpoint converts a given file to a PDF using LibreOffice API Input:Any Output:PDF Type:SISO" ) - public ResponseEntity processPdfWithOCR( + public ResponseEntity processFileToPDF( @RequestPart(required = true, value = "fileInput") @Parameter( - description = "The input file to be converted to a PDF file using OCR", + description = "The input file to be converted to a PDF file using LibreOffice", required = true ) MultipartFile inputFile diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index 4ff2b4f2..6a99090b 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -16,6 +16,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -49,7 +50,7 @@ public class ConvertPDFToPDFA { command.add(tempInputFile.toString()); command.add(tempOutputFile.toString()); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); // Read the optimized PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java index 9167a6e4..57cf8f7f 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java @@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -49,7 +50,7 @@ public class ConvertWebsiteToPDF { command.add(URL); command.add(tempOutputFile.toString()); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT).runCommandWithOutputHandling(command); // Read the optimized PDF file pdfBytes = Files.readAllBytes(tempOutputFile); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java b/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java index 6ed76edb..4f505b73 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/BlankPageController.java @@ -31,6 +31,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.PdfUtils; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -86,10 +87,10 @@ public class BlankPageController { List command = new ArrayList<>(Arrays.asList("python3", System.getProperty("user.dir") + "/scripts/detect-blank-pages.py", tempFile.toString() ,"--threshold", String.valueOf(threshold), "--white_percent", String.valueOf(whitePercent))); // Run CLI command - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command); // does contain data - if (returnCode == 0) { + if (returnCode.getRc() == 0) { System.out.println("page " + pageIndex + " has image which is not blank"); pagesToKeepIndex.add(pageIndex); } else { diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java index 42ab6a41..381a6821 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/CompressController.java @@ -34,6 +34,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -116,7 +117,7 @@ public class CompressController { command.add("-sOutputFile=" + tempOutputFile.toString()); command.add(tempInputFile.toString()); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); // Check if file size is within expected size or not auto mode so instantly finish long outputFileSize = Files.size(tempOutputFile); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java index f9ac6761..55ff446c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/ExtractImageScansController.java @@ -33,6 +33,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -117,7 +118,7 @@ public class ExtractImageScansController { // Run CLI command - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV).runCommandWithOutputHandling(command); // Read the output photos in temp directory List tempOutputFiles = Files.list(tempDir).sorted().collect(Collectors.toList()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java index c3c323f5..d6009c8a 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/OCRController.java @@ -29,6 +29,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -141,8 +142,12 @@ public class OCRController { command.addAll(Arrays.asList("--language", languageOption, tempInputFile.toString(), tempOutputFile.toString())); // Run CLI command - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); - + ProcessExecutorResult result = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + if(result.getRc() != 0 && result.getMessages().contains("multiprocessing/synchronize.py") && result.getMessages().contains("OSError: [Errno 38] Function not implemented")) { + command.add("--jobs"); + command.add("1"); + result = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF).runCommandWithOutputHandling(command); + } @@ -153,7 +158,7 @@ public class OCRController { List gsCommand = Arrays.asList("gs", "-sDEVICE=pdfwrite", "-dFILTERIMAGE", "-o", tempPdfWithoutImages.toString(), tempOutputFile.toString()); - int gsReturnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(gsCommand); + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(gsCommand); tempOutputFile = tempPdfWithoutImages; } // Read the OCR processed PDF file diff --git a/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java b/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java index 536f8c89..52644080 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/other/RepairController.java @@ -18,6 +18,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import stirling.software.SPDF.utils.ProcessExecutor; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.WebResponseUtils; @RestController @@ -51,7 +52,7 @@ public class RepairController { command.add(tempInputFile.toString()); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT).runCommandWithOutputHandling(command); // Read the optimized PDF file byte[] pdfBytes = Files.readAllBytes(tempOutputFile); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index fa6df6ef..b3afecf1 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -35,7 +35,7 @@ public class PasswordController { @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file from which the password should be removed", required = true) MultipartFile fileInput, - @RequestParam(name = "password") + @RequestPart(name = "password") @Parameter(description = "The password of the PDF file", required = true) String password) throws IOException { PDDocument document = PDDocument.load(fileInput.getBytes(), password); @@ -52,37 +52,37 @@ public class PasswordController { @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to which the password should be added", required = true) MultipartFile fileInput, - @RequestParam(defaultValue = "", name = "ownerPassword") + @RequestPart(value = "", name = "ownerPassword") @Parameter(description = "The owner password to be added to the PDF file (Restricts what can be done with the document once it is opened)") String ownerPassword, - @RequestParam(defaultValue = "", name = "password") + @RequestPart( name = "password") @Parameter(description = "The password to be added to the PDF file (Restricts the opening of the document itself.)") String password, - @RequestParam(defaultValue = "128", name = "keyLength") + @RequestPart( name = "keyLength") @Parameter(description = "The length of the encryption key", schema = @Schema(allowableValues = {"40", "128", "256"})) int keyLength, - @RequestParam(defaultValue = "false", name = "canAssembleDocument") + @RequestPart( name = "canAssembleDocument") @Parameter(description = "Whether the document assembly is allowed", example = "false") boolean canAssembleDocument, - @RequestParam(defaultValue = "false", name = "canExtractContent") + @RequestPart( name = "canExtractContent") @Parameter(description = "Whether content extraction for accessibility is allowed", example = "false") boolean canExtractContent, - @RequestParam(defaultValue = "false", name = "canExtractForAccessibility") + @RequestPart( name = "canExtractForAccessibility") @Parameter(description = "Whether content extraction for accessibility is allowed", example = "false") boolean canExtractForAccessibility, - @RequestParam(defaultValue = "false", name = "canFillInForm") + @RequestPart( name = "canFillInForm") @Parameter(description = "Whether form filling is allowed", example = "false") boolean canFillInForm, - @RequestParam(defaultValue = "false", name = "canModify") + @RequestPart( name = "canModify") @Parameter(description = "Whether the document modification is allowed", example = "false") boolean canModify, - @RequestParam(defaultValue = "false", name = "canModifyAnnotations") + @RequestPart( name = "canModifyAnnotations") @Parameter(description = "Whether modification of annotations is allowed", example = "false") boolean canModifyAnnotations, - @RequestParam(defaultValue = "false", name = "canPrint") + @RequestPart(name = "canPrint") @Parameter(description = "Whether printing of the document is allowed", example = "false") boolean canPrint, - @RequestParam(defaultValue = "false", name = "canPrintFaithful") + @RequestPart( name = "canPrintFaithful") @Parameter(description = "Whether faithful printing is allowed", example = "false") boolean canPrintFaithful ) throws IOException { diff --git a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java index fe176f62..66de400e 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java @@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; @Controller @Tag(name = "Security", description = "Security APIs") public class SecurityWebController { + + @GetMapping("/add-password") @Hidden public String addPasswordForm(Model model) { diff --git a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java index ffe1d93d..af658f79 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java @@ -20,6 +20,8 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; + public class PDFToFile { public ResponseEntity processPdfToOfficeFormat(MultipartFile inputFile, String outputFormat, String libreOfficeFilter) throws IOException, InterruptedException { @@ -53,7 +55,7 @@ public class PDFToFile { // Run the LibreOffice command List command = new ArrayList<>( Arrays.asList("soffice", "--infilter=" + libreOfficeFilter, "--convert-to", outputFormat, "--outdir", tempOutputDir.toString(), tempInputFile.toString())); - int returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); + ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE).runCommandWithOutputHandling(command); // Get output files List outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles()); diff --git a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java index f2a7ed55..fe5c6717 100644 --- a/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java +++ b/src/main/java/stirling/software/SPDF/utils/ProcessExecutor.java @@ -37,11 +37,12 @@ public class ProcessExecutor { private ProcessExecutor(int semaphoreLimit) { this.semaphore = new Semaphore(semaphoreLimit); } - public int runCommandWithOutputHandling(List command) throws IOException, InterruptedException { + public ProcessExecutorResult runCommandWithOutputHandling(List command) throws IOException, InterruptedException { return runCommandWithOutputHandling(command, null); } - public int runCommandWithOutputHandling(List command, File workingDirectory) throws IOException, InterruptedException { + public ProcessExecutorResult runCommandWithOutputHandling(List command, File workingDirectory) throws IOException, InterruptedException { int exitCode = 1; + String messages = ""; semaphore.acquire(); try { @@ -89,14 +90,16 @@ public class ProcessExecutor { // Wait for the reader threads to finish errorReaderThread.join(); outputReaderThread.join(); - + if (outputLines.size() > 0) { String outputMessage = String.join("\n", outputLines); + messages += outputMessage; System.out.println("Command output:\n" + outputMessage); } if (errorLines.size() > 0) { String errorMessage = String.join("\n", errorLines); + messages += errorMessage; System.out.println("Command error output:\n" + errorMessage); if (exitCode != 0) { throw new IOException("Command process failed with exit code " + exitCode + ". Error message: " + errorMessage); @@ -105,7 +108,28 @@ public class ProcessExecutor { } finally { semaphore.release(); } - return exitCode; + return new ProcessExecutorResult(exitCode, messages); + } + public class ProcessExecutorResult{ + int rc; + String messages; + public ProcessExecutorResult(int rc, String messages) { + this.rc = rc; + this.messages = messages; + } + public int getRc() { + return rc; + } + public void setRc(int rc) { + this.rc = rc; + } + public String getMessages() { + return messages; + } + public void setMessages(String messages) { + this.messages = messages; + } + + } - } From 0732ffa76e6c2a2af8287918f953acdd4c5fa4f9 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sat, 29 Jul 2023 14:31:09 +0100 Subject: [PATCH 04/21] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8e69fb91..f88f9873 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ [![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/paypalme/froodleplex) [![Github Sponser](https://img.shields.io/badge/Github%20Sponsor-yellow?style=flat&logo=github)](https://github.com/sponsors/Frooodle) +[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Frooodle/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af) + This is a powerful locally hosted web based PDF manipulation tool using docker that allows you to perform various operations on PDF files, such as splitting merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application started as a 100% ChatGPT-made application and has evolved to include a wide range of features to handle all your PDF needs. Stirling PDF makes no outbound calls for any record keeping or tracking. From 6b618f3abe85e8786d1514d2e816c7b2eaf70407 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 30 Jul 2023 11:31:46 +0100 Subject: [PATCH 05/21] Watermark fixes --- .../api/security/WatermarkController.java | 4 ++-- src/main/resources/application.properties | 5 ++++- src/main/resources/static/js/downloader.js | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index a8271207..f5655391 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -44,8 +44,8 @@ public class WatermarkController { @Operation(summary = "Add watermark to a PDF file", description = "This endpoint adds a watermark to a given PDF file. Users can specify the watermark type (text or image), rotation, opacity, width spacer, and height spacer. Input:PDF Output:PDF Type:SISO") public ResponseEntity addWatermark( @RequestPart(required = true, value = "fileInput") @Parameter(description = "The input PDF file to add a watermark") MultipartFile pdfFile, - @RequestPart(required = true) @Parameter(description = "The watermark type (text or image)") String watermarkType, - @RequestPart(required = false) @Parameter(description = "The watermark text") String watermarkText, + @RequestParam(required = true) @Parameter(description = "The watermark type (text or image)") String watermarkType, + @RequestParam(required = false) @Parameter(description = "The watermark text") String watermarkText, @RequestPart(required = false) @Parameter(description = "The watermark image") MultipartFile watermarkImage, @RequestParam(defaultValue = "roman", name = "alphabet") @Parameter(description = "The selected alphabet", diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 06e4edec..c4118342 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,7 +15,10 @@ server.error.whitelabel.enabled=false server.error.include-stacktrace=always server.error.include-exception=true server.error.include-message=always -\ + +#logging.level.org.springframework.web=DEBUG + + server.servlet.session.tracking-modes=cookie server.servlet.context-path=${APP_ROOT_PATH:/} diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index e9abdf38..59434264 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -14,6 +14,13 @@ $(document).ready(function() { const url = this.action; const files = $('#fileInput-input')[0].files; const formData = new FormData(this); + + // Remove empty file entries + for (let [key, value] of formData.entries()) { + if (value instanceof File && !value.name) { + formData.delete(key); + } + } const override = $('#override').val() || ''; const originalButtonText = $('#submitBtn').text(); $('#submitBtn').text('Processing...'); @@ -159,6 +166,21 @@ async function submitMultiPdfForm(url, files) { let formData = new FormData($('form')[0]); formData.delete('fileInput'); + for (let [key, value] of formData.entries()) { + console.log(key, value); + } + + // Remove empty file entries + for (let [key, value] of formData.entries()) { + if (value instanceof File && !value.name) { + formData.delete(key); + } + } + console.log("## AFTER ## ") + for (let [key, value] of formData.entries()) { + console.log(key, value); + } + const CONCURRENCY_LIMIT = 8; const chunks = []; for (let i = 0; i < Array.from(files).length; i += CONCURRENCY_LIMIT) { From 52a7885f3c73a4f2d0cb4446289f312de278c17e Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 30 Jul 2023 14:43:34 +0100 Subject: [PATCH 06/21] all inf --- .../controller/api/security/PDFExtractor.java | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java b/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java new file mode 100644 index 00000000..4113691d --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java @@ -0,0 +1,335 @@ +package stirling.software.SPDF.controller.api.security; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDDocumentInformation; +import com.itextpdf.kernel.pdf.PdfObject; +import com.itextpdf.forms.PdfAcroForm; +import com.itextpdf.forms.fields.PdfFormField; +import com.itextpdf.kernel.geom.Rectangle; +import com.itextpdf.kernel.pdf.PdfArray; +import com.itextpdf.kernel.pdf.PdfCatalog; +import com.itextpdf.kernel.pdf.PdfDictionary; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfDocumentInfo; +import com.itextpdf.kernel.pdf.PdfEncryption; +import com.itextpdf.kernel.pdf.PdfReader; +import com.itextpdf.kernel.pdf.PdfResources; +import com.itextpdf.kernel.pdf.PdfStream; +import com.itextpdf.kernel.pdf.PdfName; +import com.itextpdf.kernel.pdf.PdfViewerPreferences; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.kernel.pdf.annot.PdfAnnotation; +import com.itextpdf.kernel.pdf.annot.PdfFileAttachmentAnnotation; +import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation; +import com.itextpdf.kernel.pdf.layer.PdfLayer; +import com.itextpdf.kernel.pdf.layer.PdfOCProperties; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.pdfbox.text.PDFTextStripper; +import java.io.File; +import java.io.FileWriter; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.Map; + +public class PDFExtractor { + public static void main(String[] args) { + try { + PDDocument pdfBoxDoc = PDDocument.load(new File("path_to_pdf.pdf")); + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode jsonOutput = objectMapper.createObjectNode(); + + // Metadata using PDFBox + PDDocumentInformation info = pdfBoxDoc.getDocumentInformation(); + ObjectNode metadata = objectMapper.createObjectNode(); + metadata.put("Title", info.getTitle()); + metadata.put("Author", info.getAuthor()); + metadata.put("Subject", info.getSubject()); + metadata.put("Keywords", info.getKeywords()); + metadata.put("Producer", info.getProducer()); + metadata.put("Creator", info.getCreator()); + metadata.put("CreationDate", formatDate(info.getCreationDate())); + metadata.put("ModificationDate", formatDate(info.getModificationDate())); + metadata.put("Trapped", info.getTrapped()); + jsonOutput.set("Metadata", metadata); + + // Document Information using PDFBox + ObjectNode docInfoNode = objectMapper.createObjectNode(); + docInfoNode.put("Number of pages", pdfBoxDoc.getNumberOfPages()); + docInfoNode.put("PDF version", pdfBoxDoc.getVersion()); + ; + + // Page Mode using iText7 + PdfDocument itextDoc = new PdfDocument(new PdfReader("path_to_pdf.pdf")); + PdfCatalog catalog = itextDoc.getCatalog(); + PdfName pageMode = catalog.getPdfObject().getAsName(PdfName.PageMode); + + ObjectNode itextDocInfo = objectMapper.createObjectNode(); + docInfoNode.put("Page Mode", getPageModeDescription(pageMode));; + + jsonOutput.set("Document Information", docInfoNode); + + for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { + ObjectNode pageInfo = objectMapper.createObjectNode(); + + // Page-level Information + Rectangle pageSize = itextDoc.getPage(pageNum).getPageSize(); + pageInfo.put("Width", pageSize.getWidth()); + pageInfo.put("Height", pageSize.getHeight()); + pageInfo.put("Rotation", itextDoc.getPage(pageNum).getRotation()); + + // Boxes + pageInfo.put("MediaBox", itextDoc.getPage(pageNum).getMediaBox().toString()); + pageInfo.put("CropBox", itextDoc.getPage(pageNum).getCropBox().toString()); + pageInfo.put("BleedBox", itextDoc.getPage(pageNum).getBleedBox().toString()); + pageInfo.put("TrimBox", itextDoc.getPage(pageNum).getTrimBox().toString()); + pageInfo.put("ArtBox", itextDoc.getPage(pageNum).getArtBox().toString()); + + // Content Extraction + PDFTextStripper textStripper = new PDFTextStripper(); + textStripper.setStartPage(pageNum -1); + textStripper.setEndPage(pageNum - 1); + String pageText = textStripper.getText(pdfBoxDoc); + + pageInfo.put("Text Characters Count", pageText.length()); // + + // Annotations + ArrayNode annotationsArray = objectMapper.createArrayNode(); + List annotations = itextDoc.getPage(pageNum).getAnnotations(); + for (PdfAnnotation annotation : annotations) { + ObjectNode annotationNode = objectMapper.createObjectNode(); + annotationNode.put("Subtype", annotation.getSubtype().toString()); + annotationNode.put("Contents", annotation.getContents().getValue()); + annotationsArray.add(annotationNode); + } + pageInfo.set("Annotations", annotationsArray); + + // Images (simplified) + // This part is non-trivial as images can be embedded in multiple ways in a PDF. + // Here is a basic structure to recognize image XObjects on a page. + ArrayNode imagesArray = objectMapper.createArrayNode(); + PdfResources resources = itextDoc.getPage(pageNum).getResources(); + for (PdfName name : resources.getResourceNames()) { + PdfObject obj = resources.getResource(name); + if (obj instanceof PdfStream) { + PdfStream stream = (PdfStream) obj; + if (PdfName.Image.equals(stream.getAsName(PdfName.Subtype))) { + ObjectNode imageNode = objectMapper.createObjectNode(); + imageNode.put("Width", stream.getAsNumber(PdfName.Width).intValue()); + imageNode.put("Height", stream.getAsNumber(PdfName.Height).intValue()); + PdfObject colorSpace = stream.get(PdfName.ColorSpace); + if (colorSpace != null) { + imageNode.put("ColorSpace", colorSpace.toString()); + } + imagesArray.add(imageNode); + } + } + } + pageInfo.set("Images", imagesArray); + + // Links + ArrayNode linksArray = objectMapper.createArrayNode(); + for (PdfAnnotation annotation : annotations) { + if (annotation instanceof PdfLinkAnnotation) { + PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation; + ObjectNode linkNode = objectMapper.createObjectNode(); + linkNode.put("URI", linkAnnotation.getAction().toString()); // Basic, might not work for all links + linksArray.add(linkNode); + } + } + pageInfo.set("Links", linksArray); + + //Fonts + ArrayNode fontsArray = objectMapper.createArrayNode(); + PdfDictionary fontDicts = resources.getResource(PdfName.Font); + if (fontDicts != null) { + for (PdfName key : fontDicts.keySet()) { + PdfDictionary font = fontDicts.getAsDictionary(key); + ObjectNode fontNode = objectMapper.createObjectNode(); + fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString()); + + // Font Subtype (e.g., Type1, TrueType) + if (font.containsKey(PdfName.Subtype)) { + fontNode.put("Subtype", font.getAsName(PdfName.Subtype).toString()); + } + + // Font Descriptor + PdfDictionary fontDescriptor = font.getAsDictionary(PdfName.FontDescriptor); + if (fontDescriptor != null) { + // Italic Angle + if (fontDescriptor.containsKey(PdfName.ItalicAngle)) { + fontNode.put("ItalicAngle", fontDescriptor.getAsNumber(PdfName.ItalicAngle).floatValue()); + } + + // Flags (e.g., italic, bold) + if (fontDescriptor.containsKey(PdfName.Flags)) { + int flags = fontDescriptor.getAsNumber(PdfName.Flags).intValue(); + fontNode.put("IsItalic", (flags & 64) != 0); + fontNode.put("IsBold", (flags & 1) != 0); + } + } + + fontsArray.add(fontNode); + } + } + pageInfo.set("Fonts", fontsArray); + + + + + // Access resources dictionary + PdfDictionary resourcesDict = itextDoc.getPage(pageNum).getResources().getPdfObject(); + + // Color Spaces & ICC Profiles + ArrayNode colorSpacesArray = objectMapper.createArrayNode(); + PdfDictionary colorSpaces = resourcesDict.getAsDictionary(PdfName.ColorSpace); + if (colorSpaces != null) { + for (PdfName name : colorSpaces.keySet()) { + PdfObject colorSpaceObject = colorSpaces.get(name); + if (colorSpaceObject instanceof PdfArray) { + PdfArray colorSpaceArray = (PdfArray) colorSpaceObject; + if (colorSpaceArray.size() > 1 && colorSpaceArray.get(0) instanceof PdfName && PdfName.ICCBased.equals(colorSpaceArray.get(0))) { + ObjectNode iccProfileNode = objectMapper.createObjectNode(); + PdfStream iccStream = (PdfStream) colorSpaceArray.get(1); + byte[] iccData = iccStream.getBytes(); + // TODO: Further decode and analyze the ICC data if needed + iccProfileNode.put("ICC Profile Length", iccData.length); + colorSpacesArray.add(iccProfileNode); + } + } + } + } + pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray); + + // Other XObjects + ArrayNode xObjectsArray = objectMapper.createArrayNode(); + PdfDictionary xObjects = resourcesDict.getAsDictionary(PdfName.XObject); + if (xObjects != null) { + for (PdfName name : xObjects.keySet()) { + PdfStream xObjectStream = xObjects.getAsStream(name); + ObjectNode xObjectNode = objectMapper.createObjectNode(); + xObjectNode.put("Type", xObjectStream.getAsName(PdfName.Subtype).toString()); + // TODO: Extract further details depending on the XObject type + xObjectsArray.add(xObjectNode); + } + } + pageInfo.set("XObjects", xObjectsArray); + + jsonOutput.set("Page " + pageNum, pageInfo); + } + + PdfAcroForm acroForm = PdfAcroForm.getAcroForm(itextDoc, false); + if (acroForm != null) { + ObjectNode formFieldsNode = objectMapper.createObjectNode(); + for (Map.Entry entry : acroForm.getFormFields().entrySet()) { + formFieldsNode.put(entry.getKey(), entry.getValue().getValueAsString()); + } + jsonOutput.set("FormFields", formFieldsNode); + } + + + + //TODO bookmarks here + + + + + //embeed files TODO size + PdfDictionary embeddedFiles = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) + .getAsDictionary(PdfName.EmbeddedFiles); + if (embeddedFiles != null) { + ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); + PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names); + for (int i = 0; i < namesArray.size(); i += 2) { + ObjectNode embeddedFileNode = objectMapper.createObjectNode(); + embeddedFileNode.put("Name", namesArray.getAsString(i).toString()); + // Add other details if required + embeddedFilesArray.add(embeddedFileNode); + } + jsonOutput.set("EmbeddedFiles", embeddedFilesArray); + } + + + //attachments TODO size + ArrayNode attachmentsArray = objectMapper.createArrayNode(); + for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { + for (PdfAnnotation annotation : itextDoc.getPage(pageNum).getAnnotations()) { + if (annotation instanceof PdfFileAttachmentAnnotation) { + ObjectNode attachmentNode = objectMapper.createObjectNode(); + attachmentNode.put("Name", ((PdfFileAttachmentAnnotation) annotation).getName().toString()); + attachmentNode.put("Description", annotation.getContents().getValue()); + attachmentsArray.add(attachmentNode); + } + } + } + jsonOutput.set("Attachments", attachmentsArray); + + //Javascript + PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); + if (namesDict != null) { + PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); + if (javascriptDict != null) { + ArrayNode javascriptArray = objectMapper.createArrayNode(); + PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); + for (int i = 0; i < namesArray.size(); i += 2) { + ObjectNode jsNode = objectMapper.createObjectNode(); + jsNode.put("JS Name", namesArray.getAsString(i).toString()); + jsNode.put("JS Code", namesArray.getAsString(i + 1).toString()); + javascriptArray.add(jsNode); + } + jsonOutput.set("JavaScripts", javascriptArray); + } + } + + + //TODO size + PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false); + if (ocProperties != null) { + ArrayNode layersArray = objectMapper.createArrayNode(); + for (PdfLayer layer : ocProperties.getLayers()) { + ObjectNode layerNode = objectMapper.createObjectNode(); + layerNode.put("Name", layer.getPdfObject().getAsString(PdfName.Name).toString()); + layersArray.add(layerNode); + } + jsonOutput.set("Layers", layersArray); + } + + + //TODO Security + + + + + + + // Digital Signatures using iText7 TODO + + + // Save JSON to file + try (FileWriter file = new FileWriter("output.json")) { + file.write(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput)); + file.flush(); + } + + pdfBoxDoc.close(); + itextDoc.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static String formatDate(Calendar calendar) { + if (calendar != null) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.format(calendar.getTime()); + } else { + return null; + } + } + + private static String getPageModeDescription(PdfName pageMode) { + return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown"; + } +} From 0da9c62ef84560cbab58b1a5a0bdd55aa3c96ab8 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 30 Jul 2023 21:56:09 +0100 Subject: [PATCH 07/21] all info --- .../controller/api/security/PDFExtractor.java | 675 ++++++++++++++---- .../controller/web/SecurityWebController.java | 7 + .../templates/security/get-info-on-pdf.html | 33 + 3 files changed, 592 insertions(+), 123 deletions(-) create mode 100644 src/main/resources/templates/security/get-info-on-pdf.html diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java b/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java index 4113691d..2cd429b4 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java @@ -1,8 +1,23 @@ package stirling.software.SPDF.controller.api.security; +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.cos.COSString; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode; +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot; +import org.apache.pdfbox.pdmodel.encryption.PDEncryption; +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; +import org.apache.pdfbox.pdmodel.interactive.form.PDField; + import com.itextpdf.kernel.pdf.PdfObject; +import com.itextpdf.kernel.pdf.PdfOutline; import com.itextpdf.forms.PdfAcroForm; import com.itextpdf.forms.fields.PdfFormField; import com.itextpdf.kernel.geom.Rectangle; @@ -15,29 +30,64 @@ import com.itextpdf.kernel.pdf.PdfEncryption; import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfResources; import com.itextpdf.kernel.pdf.PdfStream; +import com.itextpdf.kernel.pdf.PdfString; import com.itextpdf.kernel.pdf.PdfName; import com.itextpdf.kernel.pdf.PdfViewerPreferences; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.annot.PdfAnnotation; import com.itextpdf.kernel.pdf.annot.PdfFileAttachmentAnnotation; import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation; +import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation; import com.itextpdf.kernel.pdf.layer.PdfLayer; import com.itextpdf.kernel.pdf.layer.PdfOCProperties; +import com.itextpdf.kernel.xmp.XMPException; +import com.itextpdf.kernel.xmp.XMPMeta; +import com.itextpdf.kernel.xmp.XMPMetaFactory; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.utils.WebResponseUtils; + import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.pdfbox.text.PDFTextStripper; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + import java.io.File; +import java.util.HashMap; import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.List; import java.util.Map; - +import java.util.Set; +import java.util.HashSet; +@RestController +@Tag(name = "Security", description = "Security APIs") public class PDFExtractor { - public static void main(String[] args) { - try { - PDDocument pdfBoxDoc = PDDocument.load(new File("path_to_pdf.pdf")); + + static ObjectMapper objectMapper = new ObjectMapper(); + + @PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf") + @Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO") + public ResponseEntity getPdfInfo( + @RequestPart(required = true, value = "fileInput") + @Parameter(description = "The input PDF file to get info on", required = true) MultipartFile inputFile) + throws IOException { + + try ( + PDDocument pdfBoxDoc = PDDocument.load(inputFile.getInputStream()); + PdfDocument itextDoc = new PdfDocument(new PdfReader(inputFile.getInputStream())) + ) { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode jsonOutput = objectMapper.createObjectNode(); @@ -55,22 +105,256 @@ public class PDFExtractor { metadata.put("Trapped", info.getTrapped()); jsonOutput.set("Metadata", metadata); + + + // Total file size of the PDF + long fileSizeInBytes = inputFile.getSize(); + jsonOutput.put("FileSizeInBytes", fileSizeInBytes); + + // Number of words, paragraphs, and images in the entire document + String fullText = new PDFTextStripper().getText(pdfBoxDoc); + String[] words = fullText.split("\\s+"); + int wordCount = words.length; + int paragraphCount = fullText.split("\r\n|\r|\n").length; + jsonOutput.put("WordCount", wordCount); + jsonOutput.put("ParagraphCount", paragraphCount); + // Number of characters in the entire document (including spaces and special characters) + int charCount = fullText.length(); + jsonOutput.put("CharacterCount", charCount); + + + // Initialize the flags and types + boolean hasCompression = false; + String compressionType = "None"; + + // Check for object streams + for (int i = 1; i <= itextDoc.getNumberOfPdfObjects(); i++) { + PdfObject obj = itextDoc.getPdfObject(i); + if (obj != null && obj.isStream() && ((PdfStream) obj).get(PdfName.Type) == PdfName.ObjStm) { + hasCompression = true; + compressionType = "Object Streams"; + break; + } + } + + // If not compressed using object streams, check for compressed Xref tables + if (!hasCompression && itextDoc.getReader().hasRebuiltXref()) { + hasCompression = true; + compressionType = "Compressed Xref or Rebuilt Xref"; + } + jsonOutput.put("Compression", hasCompression); + if(hasCompression) + jsonOutput.put("CompressionType", compressionType); + + String language = pdfBoxDoc.getDocumentCatalog().getLanguage(); + jsonOutput.put("Language", language); + // Document Information using PDFBox ObjectNode docInfoNode = objectMapper.createObjectNode(); docInfoNode.put("Number of pages", pdfBoxDoc.getNumberOfPages()); docInfoNode.put("PDF version", pdfBoxDoc.getVersion()); - ; + // Page Mode using iText7 - PdfDocument itextDoc = new PdfDocument(new PdfReader("path_to_pdf.pdf")); PdfCatalog catalog = itextDoc.getCatalog(); PdfName pageMode = catalog.getPdfObject().getAsName(PdfName.PageMode); - ObjectNode itextDocInfo = objectMapper.createObjectNode(); + + + + PdfAcroForm acroForm = PdfAcroForm.getAcroForm(itextDoc, false); + ObjectNode formFieldsNode = objectMapper.createObjectNode(); + if (acroForm != null) { + for (Map.Entry entry : acroForm.getFormFields().entrySet()) { + formFieldsNode.put(entry.getKey(), entry.getValue().getValueAsString()); + } + } + jsonOutput.set("FormFields", formFieldsNode); + + + + + + //embeed files TODO size + ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); + if(itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) != null) + { + PdfDictionary embeddedFiles = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) + .getAsDictionary(PdfName.EmbeddedFiles); + if (embeddedFiles != null) { + + PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names); + for (int i = 0; i < namesArray.size(); i += 2) { + ObjectNode embeddedFileNode = objectMapper.createObjectNode(); + embeddedFileNode.put("Name", namesArray.getAsString(i).toString()); + // Add other details if required + embeddedFilesArray.add(embeddedFileNode); + } + + } + } + jsonOutput.set("EmbeddedFiles", embeddedFilesArray); + + //attachments TODO size + ArrayNode attachmentsArray = objectMapper.createArrayNode(); + for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { + for (PdfAnnotation annotation : itextDoc.getPage(pageNum).getAnnotations()) { + if (annotation instanceof PdfFileAttachmentAnnotation) { + ObjectNode attachmentNode = objectMapper.createObjectNode(); + attachmentNode.put("Name", ((PdfFileAttachmentAnnotation) annotation).getName().toString()); + attachmentNode.put("Description", annotation.getContents().getValue()); + attachmentsArray.add(attachmentNode); + } + } + } + jsonOutput.set("Attachments", attachmentsArray); + + //Javascript + PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); + ArrayNode javascriptArray = objectMapper.createArrayNode(); + if (namesDict != null) { + PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); + if (javascriptDict != null) { + + PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); + for (int i = 0; i < namesArray.size(); i += 2) { + ObjectNode jsNode = objectMapper.createObjectNode(); + jsNode.put("JS Name", namesArray.getAsString(i).toString()); + jsNode.put("JS Code", namesArray.getAsString(i + 1).toString()); + javascriptArray.add(jsNode); + } + + } + } + jsonOutput.set("JavaScript", javascriptArray); + + //TODO size + PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false); + ArrayNode layersArray = objectMapper.createArrayNode(); + if (ocProperties != null) { + + for (PdfLayer layer : ocProperties.getLayers()) { + ObjectNode layerNode = objectMapper.createObjectNode(); + layerNode.put("Name", layer.getPdfObject().getAsString(PdfName.Name).toString()); + layersArray.add(layerNode); + } + + } + jsonOutput.set("Layers", layersArray); + + //TODO Security + + + + + + + // Digital Signatures using iText7 TODO + + + + + PDAcroForm pdAcroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm(); + ArrayNode formFieldsArray2 = objectMapper.createArrayNode(); + if (pdAcroForm != null) { + + for (PDField field : pdAcroForm.getFields()) { + ObjectNode fieldNode = objectMapper.createObjectNode(); + fieldNode.put("FieldName", field.getFullyQualifiedName()); + fieldNode.put("FieldType", field.getFieldType()); + // Add more attributes as needed... + formFieldsArray2.add(fieldNode); + } + + } + jsonOutput.set("FormFields", formFieldsArray2); + + + PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot(); + ArrayNode structureTreeArray; + try { + if(structureTreeRoot != null) { + structureTreeArray = exploreStructureTree(structureTreeRoot.getKids()); + jsonOutput.set("StructureTree", structureTreeArray); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + + + + + + + boolean isPdfACompliant = checkOutputIntent(itextDoc, "PDF/A"); + boolean isPdfXCompliant = checkOutputIntent(itextDoc, "PDF/X"); + boolean isPdfECompliant = checkForStandard(itextDoc, "PDF/E"); + boolean isPdfVTCompliant = checkForStandard(itextDoc, "PDF/VT"); + boolean isPdfUACompliant = checkForStandard(itextDoc, "PDF/UA"); + boolean isPdfBCompliant = checkForStandard(itextDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard. + boolean isPdfSECCompliant = checkForStandard(itextDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021. + + ObjectNode compliancy = objectMapper.createObjectNode(); + compliancy.put("IsPDF/ACompliant", isPdfACompliant); + compliancy.put("IsPDF/XCompliant", isPdfXCompliant); + compliancy.put("IsPDF/ECompliant", isPdfECompliant); + compliancy.put("IsPDF/VTCompliant", isPdfVTCompliant); + compliancy.put("IsPDF/UACompliant", isPdfUACompliant); + compliancy.put("IsPDF/BCompliant", isPdfBCompliant); + compliancy.put("IsPDF/SECCompliant", isPdfSECCompliant); + + jsonOutput.set("Compliancy", compliancy); + + + + + ArrayNode bookmarksArray = objectMapper.createArrayNode(); + PdfOutline root = itextDoc.getOutlines(false); + if (root != null) { + for (PdfOutline child : root.getAllChildren()) { + addOutlinesToArray(child, bookmarksArray); + } + } + jsonOutput.set("Bookmarks/Outline/TOC", bookmarksArray); + + String xmpString = null; + try { + byte[] xmpBytes = itextDoc.getXmpMetadata(); + if (xmpBytes != null) { + XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(xmpBytes); + xmpString = xmpMeta.dumpObject(); + + } + } catch (XMPException e) { + e.printStackTrace(); + } + jsonOutput.put("XMPMetadata", xmpString); + + + + ObjectNode encryptionNode = objectMapper.createObjectNode(); + if (pdfBoxDoc.isEncrypted()) { + encryptionNode.put("IsEncrypted", true); + + // Retrieve encryption details using getEncryption() + PDEncryption encryption = pdfBoxDoc.getEncryption(); + encryptionNode.put("EncryptionAlgorithm", encryption.getFilter()); + encryptionNode.put("KeyLength", encryption.getLength()); + encryptionNode.put("Permissions", pdfBoxDoc.getCurrentAccessPermission().toString()); + + // Add other encryption-related properties as needed + } else { + encryptionNode.put("IsEncrypted", false); + } + jsonOutput.set("Encryption", encryptionNode); + docInfoNode.put("Page Mode", getPageModeDescription(pageMode));; jsonOutput.set("Document Information", docInfoNode); - + ObjectNode pageInfoParent = objectMapper.createObjectNode(); for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { ObjectNode pageInfo = objectMapper.createObjectNode(); @@ -79,7 +363,9 @@ public class PDFExtractor { pageInfo.put("Width", pageSize.getWidth()); pageInfo.put("Height", pageSize.getHeight()); pageInfo.put("Rotation", itextDoc.getPage(pageNum).getRotation()); - + pageInfo.put("Page Orientation", getPageOrientation(pageSize.getWidth(),pageSize.getHeight())); + pageInfo.put("Standard Size", getPageSize(pageSize.getWidth(),pageSize.getHeight())); + // Boxes pageInfo.put("MediaBox", itextDoc.getPage(pageNum).getMediaBox().toString()); pageInfo.put("CropBox", itextDoc.getPage(pageNum).getCropBox().toString()); @@ -98,14 +384,25 @@ public class PDFExtractor { // Annotations ArrayNode annotationsArray = objectMapper.createArrayNode(); List annotations = itextDoc.getPage(pageNum).getAnnotations(); - for (PdfAnnotation annotation : annotations) { - ObjectNode annotationNode = objectMapper.createObjectNode(); - annotationNode.put("Subtype", annotation.getSubtype().toString()); - annotationNode.put("Contents", annotation.getContents().getValue()); - annotationsArray.add(annotationNode); - } - pageInfo.set("Annotations", annotationsArray); + int subtypeCount = 0; + int contentsCount = 0; + + for (PdfAnnotation annotation : annotations) { + if(annotation.getSubtype() != null) { + subtypeCount++; // Increase subtype count + } + if(annotation.getContents() != null) { + contentsCount++; // Increase contents count + } + } + + ObjectNode annotationsObject = objectMapper.createObjectNode(); + annotationsObject.put("AnnotationsCount", annotations.size()); + annotationsObject.put("SubtypeCount", subtypeCount); + annotationsObject.put("ContentsCount", contentsCount); + pageInfo.set("Annotations", annotationsObject); + // Images (simplified) // This part is non-trivial as images can be embedded in multiple ways in a PDF. // Here is a basic structure to recognize image XObjects on a page. @@ -129,32 +426,62 @@ public class PDFExtractor { } pageInfo.set("Images", imagesArray); + // Links ArrayNode linksArray = objectMapper.createArrayNode(); + Set uniqueURIs = new HashSet<>(); // To store unique URIs + for (PdfAnnotation annotation : annotations) { if (annotation instanceof PdfLinkAnnotation) { PdfLinkAnnotation linkAnnotation = (PdfLinkAnnotation) annotation; - ObjectNode linkNode = objectMapper.createObjectNode(); - linkNode.put("URI", linkAnnotation.getAction().toString()); // Basic, might not work for all links - linksArray.add(linkNode); + String uri = linkAnnotation.getAction().toString(); + uniqueURIs.add(uri); // Add to set to ensure uniqueness } } + + // Add unique URIs to linksArray + for (String uri : uniqueURIs) { + ObjectNode linkNode = objectMapper.createObjectNode(); + linkNode.put("URI", uri); + linksArray.add(linkNode); + } pageInfo.set("Links", linksArray); //Fonts ArrayNode fontsArray = objectMapper.createArrayNode(); PdfDictionary fontDicts = resources.getResource(PdfName.Font); + Set uniqueSubtypes = new HashSet<>(); // To store unique subtypes + + if (fontDicts != null) { for (PdfName key : fontDicts.keySet()) { + ObjectNode fontNode = objectMapper.createObjectNode(); // Create a new font node for each font PdfDictionary font = fontDicts.getAsDictionary(key); - ObjectNode fontNode = objectMapper.createObjectNode(); - fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString()); + boolean isEmbedded = font.containsKey(PdfName.FontFile) || + font.containsKey(PdfName.FontFile2) || + font.containsKey(PdfName.FontFile3); + fontNode.put("IsEmbedded", isEmbedded); + + + if (font.containsKey(PdfName.Encoding)) { + String encoding = font.getAsName(PdfName.Encoding).toString(); + fontNode.put("Encoding", encoding); + } + + + + if(font.getAsString(PdfName.BaseFont) != null) + fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString()); + + String subtype = null; // Font Subtype (e.g., Type1, TrueType) if (font.containsKey(PdfName.Subtype)) { - fontNode.put("Subtype", font.getAsName(PdfName.Subtype).toString()); + subtype = font.getAsName(PdfName.Subtype).toString(); + uniqueSubtypes.add(subtype); // Add to set to ensure uniqueness } - + fontNode.put("Subtype", subtype); + // Font Descriptor PdfDictionary fontDescriptor = font.getAsDictionary(PdfName.FontDescriptor); if (fontDescriptor != null) { @@ -166,14 +493,53 @@ public class PDFExtractor { // Flags (e.g., italic, bold) if (fontDescriptor.containsKey(PdfName.Flags)) { int flags = fontDescriptor.getAsNumber(PdfName.Flags).intValue(); - fontNode.put("IsItalic", (flags & 64) != 0); - fontNode.put("IsBold", (flags & 1) != 0); + fontNode.put("IsItalic", (flags & 64) != 0); // Existing italic flag + fontNode.put("IsBold", (flags & 1 << 16) != 0); // Existing bold flag + fontNode.put("IsFixedPitch", (flags & 1) != 0); + fontNode.put("IsSerif", (flags & 2) != 0); + fontNode.put("IsSymbolic", (flags & 4) != 0); + fontNode.put("IsScript", (flags & 8) != 0); + fontNode.put("IsNonsymbolic", (flags & 16) != 0); + } + + if (fontDescriptor.containsKey(PdfName.FontFamily)) { + String fontFamily = fontDescriptor.getAsString(PdfName.FontFamily).toString(); + fontNode.put("FontFamily", fontFamily); } - } - fontsArray.add(fontNode); + if (fontDescriptor.containsKey(PdfName.FontStretch)) { + String fontStretch = fontDescriptor.getAsName(PdfName.FontStretch).toString(); + fontNode.put("FontStretch", fontStretch); + } + + if (fontDescriptor != null && fontDescriptor.containsKey(PdfName.FontBBox)) { + PdfArray bbox = fontDescriptor.getAsArray(PdfName.FontBBox); + fontNode.put("FontBoundingBox", bbox.toString()); + } + if (fontDescriptor != null && fontDescriptor.containsKey(PdfName.FontWeight)) { + float fontWeight = fontDescriptor.getAsNumber(PdfName.FontWeight).floatValue(); + fontNode.put("FontWeight", fontWeight); + } + + } + if (font.containsKey(PdfName.ToUnicode)) { + PdfStream toUnicodeStream = font.getAsStream(PdfName.ToUnicode); + // Handle the stream as needed, maybe extract some details or just note its existence + fontNode.put("HasToUnicodeMap", true); + } + if (fontNode.size() > 0) { + fontsArray.add(fontNode); // Add each font node to fontsArray + } } } + + // Add unique subtypes to fontsArray + ArrayNode subtypesArray = objectMapper.createArrayNode(); + for (String subtype : uniqueSubtypes) { + subtypesArray.add(subtype); + } + pageInfo.set("FontSubtypes", subtypesArray); // Changed from Fonts to FontSubtypes + pageInfo.set("Fonts", fontsArray); @@ -204,123 +570,186 @@ public class PDFExtractor { pageInfo.set("Color Spaces & ICC Profiles", colorSpacesArray); // Other XObjects - ArrayNode xObjectsArray = objectMapper.createArrayNode(); + Map xObjectCountMap = new HashMap<>(); // To store the count for each type PdfDictionary xObjects = resourcesDict.getAsDictionary(PdfName.XObject); if (xObjects != null) { for (PdfName name : xObjects.keySet()) { PdfStream xObjectStream = xObjects.getAsStream(name); - ObjectNode xObjectNode = objectMapper.createObjectNode(); - xObjectNode.put("Type", xObjectStream.getAsName(PdfName.Subtype).toString()); - // TODO: Extract further details depending on the XObject type - xObjectsArray.add(xObjectNode); + String xObjectType = xObjectStream.getAsName(PdfName.Subtype).toString(); + + // Increment the count for this type in the map + xObjectCountMap.put(xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1); } } - pageInfo.set("XObjects", xObjectsArray); - jsonOutput.set("Page " + pageNum, pageInfo); - } - - PdfAcroForm acroForm = PdfAcroForm.getAcroForm(itextDoc, false); - if (acroForm != null) { - ObjectNode formFieldsNode = objectMapper.createObjectNode(); - for (Map.Entry entry : acroForm.getFormFields().entrySet()) { - formFieldsNode.put(entry.getKey(), entry.getValue().getValueAsString()); + // Add the count map to pageInfo (or wherever you want to store it) + ObjectNode xObjectCountNode = objectMapper.createObjectNode(); + for (Map.Entry entry : xObjectCountMap.entrySet()) { + xObjectCountNode.put(entry.getKey(), entry.getValue()); } - jsonOutput.set("FormFields", formFieldsNode); - } + pageInfo.set("XObjectCounts", xObjectCountNode); + + - - - //TODO bookmarks here - - - - - //embeed files TODO size - PdfDictionary embeddedFiles = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names) - .getAsDictionary(PdfName.EmbeddedFiles); - if (embeddedFiles != null) { - ArrayNode embeddedFilesArray = objectMapper.createArrayNode(); - PdfArray namesArray = embeddedFiles.getAsArray(PdfName.Names); - for (int i = 0; i < namesArray.size(); i += 2) { - ObjectNode embeddedFileNode = objectMapper.createObjectNode(); - embeddedFileNode.put("Name", namesArray.getAsString(i).toString()); - // Add other details if required - embeddedFilesArray.add(embeddedFileNode); - } - jsonOutput.set("EmbeddedFiles", embeddedFilesArray); - } - - - //attachments TODO size - ArrayNode attachmentsArray = objectMapper.createArrayNode(); - for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { - for (PdfAnnotation annotation : itextDoc.getPage(pageNum).getAnnotations()) { - if (annotation instanceof PdfFileAttachmentAnnotation) { - ObjectNode attachmentNode = objectMapper.createObjectNode(); - attachmentNode.put("Name", ((PdfFileAttachmentAnnotation) annotation).getName().toString()); - attachmentNode.put("Description", annotation.getContents().getValue()); - attachmentsArray.add(attachmentNode); + ArrayNode multimediaArray = objectMapper.createArrayNode(); + for (PdfAnnotation annotation : annotations) { + if (PdfName.RichMedia.equals(annotation.getSubtype())) { + ObjectNode multimediaNode = objectMapper.createObjectNode(); + // Extract details from the dictionary as needed + multimediaArray.add(multimediaNode); } } + pageInfo.set("Multimedia", multimediaArray); + + + + pageInfoParent.set("Page " + pageNum, pageInfo); } - jsonOutput.set("Attachments", attachmentsArray); - - //Javascript - PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); - if (namesDict != null) { - PdfDictionary javascriptDict = namesDict.getAsDictionary(PdfName.JavaScript); - if (javascriptDict != null) { - ArrayNode javascriptArray = objectMapper.createArrayNode(); - PdfArray namesArray = javascriptDict.getAsArray(PdfName.Names); - for (int i = 0; i < namesArray.size(); i += 2) { - ObjectNode jsNode = objectMapper.createObjectNode(); - jsNode.put("JS Name", namesArray.getAsString(i).toString()); - jsNode.put("JS Code", namesArray.getAsString(i + 1).toString()); - javascriptArray.add(jsNode); - } - jsonOutput.set("JavaScripts", javascriptArray); - } - } - - //TODO size - PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false); - if (ocProperties != null) { - ArrayNode layersArray = objectMapper.createArrayNode(); - for (PdfLayer layer : ocProperties.getLayers()) { - ObjectNode layerNode = objectMapper.createObjectNode(); - layerNode.put("Name", layer.getPdfObject().getAsString(PdfName.Name).toString()); - layersArray.add(layerNode); - } - jsonOutput.set("Layers", layersArray); - } - + jsonOutput.set("Per Page Info", pageInfoParent); - //TODO Security - - - - - - - // Digital Signatures using iText7 TODO // Save JSON to file - try (FileWriter file = new FileWriter("output.json")) { - file.write(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput)); - file.flush(); - } - - pdfBoxDoc.close(); - itextDoc.close(); + String jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput); + + + + return WebResponseUtils.bytesToWebResponse(jsonString.getBytes(StandardCharsets.UTF_8), "response.json", MediaType.APPLICATION_JSON); + } catch (Exception e) { e.printStackTrace(); } + return null; } - private static String formatDate(Calendar calendar) { + private static void addOutlinesToArray(PdfOutline outline, ArrayNode arrayNode) { + if (outline == null) return; + ObjectNode outlineNode = objectMapper.createObjectNode(); + outlineNode.put("Title", outline.getTitle()); + // You can add other properties if needed + arrayNode.add(outlineNode); + + for (PdfOutline child : outline.getAllChildren()) { + addOutlinesToArray(child, arrayNode); + } + } + public String getPageOrientation(double width, double height) { + if (width > height) { + return "Landscape"; + } else if (height > width) { + return "Portrait"; + } else { + return "Square"; + } + } + public String getPageSize(double width, double height) { + // Common aspect ratios used for standard paper sizes + double[] aspectRatios = {4.0 / 3.0, 3.0 / 2.0, Math.sqrt(2.0), 16.0 / 9.0}; + + // Check if the page matches any common aspect ratio + for (double aspectRatio : aspectRatios) { + if (isCloseToAspectRatio(width, height, aspectRatio)) { + return "Standard"; + } + } + + // If not a standard aspect ratio, consider it as a custom size + return "Custom"; + } + private boolean isCloseToAspectRatio(double width, double height, double aspectRatio) { + // Calculate the aspect ratio of the page + double pageAspectRatio = width / height; + + // Compare the page aspect ratio with the common aspect ratio within a threshold + return Math.abs(pageAspectRatio - aspectRatio) <= 0.05; + } + + public boolean checkForStandard(PdfDocument document, String standardKeyword) { + // Check Output Intents + boolean foundInOutputIntents = checkOutputIntent(document, standardKeyword); + if (foundInOutputIntents) return true; + + // Check XMP Metadata (rudimentary) + try { + byte[] metadataBytes = document.getXmpMetadata(); + if (metadataBytes != null) { + XMPMeta xmpMeta = XMPMetaFactory.parseFromBuffer(metadataBytes); + String xmpString = xmpMeta.dumpObject(); + if (xmpString.contains(standardKeyword)) { + return true; + } + } + } catch (XMPException e) { + e.printStackTrace(); + } + + return false; + } + + + public boolean checkOutputIntent(PdfDocument document, String standard) { + PdfArray outputIntents = document.getCatalog().getPdfObject().getAsArray(PdfName.OutputIntents); + if (outputIntents != null && !outputIntents.isEmpty()) { + for (int i = 0; i < outputIntents.size(); i++) { + PdfDictionary outputIntentDict = outputIntents.getAsDictionary(i); + if (outputIntentDict != null) { + PdfString s = outputIntentDict.getAsString(PdfName.S); + if (s != null && s.toString().contains(standard)) { + return true; + } + } + } + } + return false; + } + + public ArrayNode exploreStructureTree(List nodes) { + ArrayNode elementsArray = objectMapper.createArrayNode(); + if (nodes != null) { + for (Object obj : nodes) { + if (obj instanceof PDStructureNode) { + PDStructureNode node = (PDStructureNode) obj; + ObjectNode elementNode = objectMapper.createObjectNode(); + + if (node instanceof PDStructureElement) { + PDStructureElement structureElement = (PDStructureElement) node; + elementNode.put("Type", structureElement.getStructureType()); + elementNode.put("Content", getContent(structureElement)); + + // Recursively explore child elements + ArrayNode childElements = exploreStructureTree(structureElement.getKids()); + if (childElements.size() > 0) { + elementNode.set("Children", childElements); + } + } + elementsArray.add(elementNode); + } + } + } + return elementsArray; + } + + + public String getContent(PDStructureElement structureElement) { + StringBuilder contentBuilder = new StringBuilder(); + + for (Object item : structureElement.getKids()) { + if (item instanceof COSString) { + COSString cosString = (COSString) item; + contentBuilder.append(cosString.getString()); + } else if (item instanceof PDStructureElement) { + // For simplicity, we're handling only COSString and PDStructureElement here + // but a more comprehensive method would handle other types too + contentBuilder.append(getContent((PDStructureElement) item)); + } + } + + return contentBuilder.toString(); + } + + + private String formatDate(Calendar calendar) { if (calendar != null) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(calendar.getTime()); @@ -329,7 +758,7 @@ public class PDFExtractor { } } - private static String getPageModeDescription(PdfName pageMode) { + private String getPageModeDescription(PdfName pageMode) { return pageMode != null ? pageMode.toString().replaceFirst("/", "") : "Unknown"; } } diff --git a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java index 66de400e..3857cc9e 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/SecurityWebController.java @@ -52,4 +52,11 @@ public class SecurityWebController { model.addAttribute("currentPage", "sanitize-pdf"); return "security/sanitize-pdf"; } + + @GetMapping("/get-info-on-pdf") + @Hidden + public String getInfo(Model model) { + model.addAttribute("currentPage", "get-info-on-pdf"); + return "security/get-info-on-pdf"; + } } diff --git a/src/main/resources/templates/security/get-info-on-pdf.html b/src/main/resources/templates/security/get-info-on-pdf.html new file mode 100644 index 00000000..36a0bb6e --- /dev/null +++ b/src/main/resources/templates/security/get-info-on-pdf.html @@ -0,0 +1,33 @@ + + + + + + + + +
+
+
+

+
+
+
+

+

+

+
+
+ + +
+ +
+
+
+ +
+
+
+ + \ No newline at end of file From 77411e94a48bc35a35a316c334ac1a1b664d1424 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Tue, 1 Aug 2023 00:03:13 +0100 Subject: [PATCH 08/21] new features --- build.gradle | 3 +- .../api/ToSinglePageController.java | 86 +++++++++++++++++ .../api/converters/ConvertHtmlToPDF.java | 81 ++-------------- .../api/converters/ConvertMarkdownToPdf.java | 52 ++++++++++ .../web/ConverterWebController.java | 9 +- .../controller/web/GeneralWebController.java | 14 +++ .../software/SPDF/utils/FileToPdf.java | 95 +++++++++++++++++++ .../software/SPDF/utils/WebResponseUtils.java | 17 ++++ src/main/resources/messages_en_GB.properties | 19 ++++ src/main/resources/static/images/extract.svg | 3 + src/main/resources/static/images/info.svg | 4 + src/main/resources/static/images/markdown.svg | 3 + .../resources/static/images/single-page.svg | 4 + .../resources/templates/auto-split-pdf.html | 2 +- .../templates/convert/markdown-to-pdf.html | 30 ++++++ .../resources/templates/extract-page.html | 33 +++++++ src/main/resources/templates/home.html | 7 ++ .../templates/pdf-to-single-page.html | 29 ++++++ 18 files changed, 413 insertions(+), 78 deletions(-) create mode 100644 src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java create mode 100644 src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java create mode 100644 src/main/java/stirling/software/SPDF/utils/FileToPdf.java create mode 100644 src/main/resources/static/images/extract.svg create mode 100644 src/main/resources/static/images/info.svg create mode 100644 src/main/resources/static/images/markdown.svg create mode 100644 src/main/resources/static/images/single-page.svg create mode 100644 src/main/resources/templates/convert/markdown-to-pdf.html create mode 100644 src/main/resources/templates/extract-page.html create mode 100644 src/main/resources/templates/pdf-to-single-page.html diff --git a/build.gradle b/build.gradle index 688bee00..e851d9a5 100644 --- a/build.gradle +++ b/build.gradle @@ -61,8 +61,9 @@ dependencies { implementation 'com.itextpdf:itext7-core:7.2.5' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-core' - implementation group: 'com.google.zxing', name: 'core', version: '3.5.1' + // https://mvnrepository.com/artifact/org.commonmark/commonmark + implementation 'org.commonmark:commonmark:0.21.0' developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java new file mode 100644 index 00000000..2c249b85 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/ToSinglePageController.java @@ -0,0 +1,86 @@ +package stirling.software.SPDF.controller.api; + +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import com.itextpdf.kernel.pdf.*; +import com.itextpdf.kernel.pdf.xobject.PdfFormXObject; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.geom.Rectangle; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Image; +import java.util.ArrayList; +import java.util.List; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageTree; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.utils.WebResponseUtils; +import org.apache.pdfbox.pdmodel.*; +import org.apache.pdfbox.multipdf.PDFMergerUtility; +@RestController +@Tag(name = "General", description = "General APIs") +public class ToSinglePageController { + + private static final Logger logger = LoggerFactory.getLogger(ToSinglePageController.class); + + + @PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page") + @Operation( + summary = "Convert a multi-page PDF into a single long page PDF", + description = "This endpoint converts a multi-page PDF document into a single paged PDF document. The width of the single page will be same as the input's width, but the height will be the sum of all the pages' heights. Input:PDF Output:PDF Type:SISO" + ) + public ResponseEntity pdfToSinglePage( + @RequestPart(required = true, value = "fileInput") + @Parameter(description = "The input multi-page PDF file to be converted into a single page", required = true) + MultipartFile file) throws IOException { + + PdfReader reader = new PdfReader(file.getInputStream()); + PdfDocument sourceDocument = new PdfDocument(reader); + + float totalHeight = 0; + float width = 0; + + for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) { + Rectangle pageSize = sourceDocument.getPage(i).getPageSize(); + totalHeight += pageSize.getHeight(); + if(width < pageSize.getWidth()) + width = pageSize.getWidth(); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfWriter writer = new PdfWriter(baos); + PdfDocument newDocument = new PdfDocument(writer); + PageSize newPageSize = new PageSize(width, totalHeight); + newDocument.addNewPage(newPageSize); + + Document layoutDoc = new Document(newDocument); + float yOffset = totalHeight; + + for (int i = 1; i <= sourceDocument.getNumberOfPages(); i++) { + PdfFormXObject pageCopy = sourceDocument.getPage(i).copyAsFormXObject(newDocument); + Image copiedPage = new Image(pageCopy); + copiedPage.setFixedPosition(0, yOffset - sourceDocument.getPage(i).getPageSize().getHeight()); + yOffset -= sourceDocument.getPage(i).getPageSize().getHeight(); + layoutDoc.add(copiedPage); + } + + layoutDoc.close(); + sourceDocument.close(); + + byte[] result = baos.toByteArray(); + return WebResponseUtils.bytesToWebResponse(result, file.getOriginalFilename().replaceFirst("[.][^.]+$", "") + "_singlePage.pdf"); + } +} \ No newline at end of file diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java index a5878b04..e054d7f0 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertHtmlToPDF.java @@ -19,6 +19,7 @@ import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.utils.FileToPdf; import stirling.software.SPDF.utils.GeneralUtils; import stirling.software.SPDF.utils.ProcessExecutor; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; @@ -44,87 +45,17 @@ public class ConvertHtmlToPDF { String originalFilename = fileInput.getOriginalFilename(); if (originalFilename == null || (!originalFilename.endsWith(".html") && !originalFilename.endsWith(".zip"))) { throw new IllegalArgumentException("File must be either .html or .zip format."); - } - Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - Path tempInputFile = null; - byte[] pdfBytes; - try { - if (originalFilename.endsWith(".html")) { - tempInputFile = Files.createTempFile("input_", ".html"); - Files.write(tempInputFile, fileInput.getBytes()); - } else { - tempInputFile = unzipAndGetMainHtml(fileInput); - } - - List command = new ArrayList<>(); - command.add("weasyprint"); - command.add(tempInputFile.toString()); - command.add(tempOutputFile.toString()); - ProcessExecutorResult returnCode; - if (originalFilename.endsWith(".zip")) { - returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) - .runCommandWithOutputHandling(command, tempInputFile.getParent().toFile()); - } else { - - returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) - .runCommandWithOutputHandling(command); - } - - pdfBytes = Files.readAllBytes(tempOutputFile); - } finally { - // Clean up temporary files - Files.delete(tempOutputFile); - Files.delete(tempInputFile); - - if (originalFilename.endsWith(".zip")) { - GeneralUtils.deleteDirectory(tempInputFile.getParent()); - } - } + }byte[] pdfBytes = FileToPdf.convertHtmlToPdf( fileInput.getBytes(), originalFilename); + String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } - - - private Path unzipAndGetMainHtml(MultipartFile zipFile) throws IOException { - Path tempDirectory = Files.createTempDirectory("unzipped_"); - try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(zipFile.getBytes()))) { - ZipEntry entry = zipIn.getNextEntry(); - while (entry != null) { - Path filePath = tempDirectory.resolve(entry.getName()); - if (entry.isDirectory()) { - Files.createDirectories(filePath); // Explicitly create the directory structure - } else { - Files.createDirectories(filePath.getParent()); // Create parent directories if they don't exist - Files.copy(zipIn, filePath); - } - zipIn.closeEntry(); - entry = zipIn.getNextEntry(); - } - } - - //search for the main HTML file. - try (Stream walk = Files.walk(tempDirectory)) { - List htmlFiles = walk.filter(file -> file.toString().endsWith(".html")) - .collect(Collectors.toList()); - - if (htmlFiles.isEmpty()) { - throw new IOException("No HTML files found in the unzipped directory."); - } - - // Prioritize 'index.html' if it exists, otherwise use the first .html file - for (Path htmlFile : htmlFiles) { - if (htmlFile.getFileName().toString().equals("index.html")) { - return htmlFile; - } - } - - return htmlFiles.get(0); - } - } + - + } diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java new file mode 100644 index 00000000..c1bc1b73 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdf.java @@ -0,0 +1,52 @@ +package stirling.software.SPDF.controller.api.converters; + +import java.io.IOException; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import stirling.software.SPDF.utils.FileToPdf; +import stirling.software.SPDF.utils.WebResponseUtils; + +@RestController +@Tag(name = "Convert", description = "Convert APIs") +public class ConvertMarkdownToPdf { + + @PostMapping(consumes = "multipart/form-data", value = "/markdown-to-pdf") + @Operation( + summary = "Convert a Markdown file to PDF", + description = "This endpoint takes a Markdown file input, converts it to HTML, and then to PDF format." + ) + public ResponseEntity markdownToPdf( + @RequestPart(required = true, value = "fileInput") MultipartFile fileInput) + throws IOException, InterruptedException { + + if (fileInput == null) { + throw new IllegalArgumentException("Please provide a Markdown file for conversion."); + } + + String originalFilename = fileInput.getOriginalFilename(); + if (originalFilename == null || !originalFilename.endsWith(".md")) { + throw new IllegalArgumentException("File must be in .md format."); + } + + // Convert Markdown to HTML using CommonMark + Parser parser = Parser.builder().build(); + Node document = parser.parse(new String(fileInput.getBytes())); + HtmlRenderer renderer = HtmlRenderer.builder().build(); + String htmlContent = renderer.render(document); + + byte[] pdfBytes = FileToPdf.convertHtmlToPdf(htmlContent.getBytes(), "converted.html"); + + String outputFilename = originalFilename.replaceFirst("[.][^.]+$", "") + ".pdf"; // Remove file extension and append .pdf + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } +} diff --git a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java index 90429f1a..76e7be8f 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/ConverterWebController.java @@ -25,7 +25,14 @@ public class ConverterWebController { model.addAttribute("currentPage", "html-to-pdf"); return "convert/html-to-pdf"; } - + @GetMapping("/markdown-to-pdf") + @Hidden + public String convertMarkdownToPdfForm(Model model) { + model.addAttribute("currentPage", "markdown-to-pdf"); + return "convert/markdown-to-pdf"; + } + + @GetMapping("/url-to-pdf") @Hidden public String convertURLToPdfForm(Model model) { diff --git a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java index 75d67401..4d6e991a 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/GeneralWebController.java @@ -97,6 +97,20 @@ public class GeneralWebController { return "pdf-organizer"; } + @GetMapping("/extract-page") + @Hidden + public String extractPages(Model model) { + model.addAttribute("currentPage", "extract-page"); + return "extract-page"; + } + + @GetMapping("/pdf-to-single-page") + @Hidden + public String pdfToSinglePage(Model model) { + model.addAttribute("currentPage", "pdf-to-single-page"); + return "pdf-to-single-page"; + } + @GetMapping("/rotate-pdf") @Hidden public String rotatePdfForm(Model model) { diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java new file mode 100644 index 00000000..9515a3ac --- /dev/null +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -0,0 +1,95 @@ +package stirling.software.SPDF.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; + +public class FileToPdf { + public static byte[] convertHtmlToPdf(byte[] fileBytes, String fileName) throws IOException, InterruptedException { + + Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + Path tempInputFile = null; + byte[] pdfBytes; + try { + if (fileName.endsWith(".html")) { + tempInputFile = Files.createTempFile("input_", ".html"); + Files.write(tempInputFile, fileBytes); + } else { + tempInputFile = unzipAndGetMainHtml(fileBytes); + } + + List command = new ArrayList<>(); + command.add("weasyprint"); + command.add(tempInputFile.toString()); + command.add(tempOutputFile.toString()); + ProcessExecutorResult returnCode; + if (fileName.endsWith(".zip")) { + returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) + .runCommandWithOutputHandling(command, tempInputFile.getParent().toFile()); + } else { + + returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT) + .runCommandWithOutputHandling(command); + } + + pdfBytes = Files.readAllBytes(tempOutputFile); + } finally { + // Clean up temporary files + Files.delete(tempOutputFile); + Files.delete(tempInputFile); + + if (fileName.endsWith(".zip")) { + GeneralUtils.deleteDirectory(tempInputFile.getParent()); + } + } + + return pdfBytes; + } + + + private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException { + Path tempDirectory = Files.createTempDirectory("unzipped_"); + try (ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(fileBytes))) { + ZipEntry entry = zipIn.getNextEntry(); + while (entry != null) { + Path filePath = tempDirectory.resolve(entry.getName()); + if (entry.isDirectory()) { + Files.createDirectories(filePath); // Explicitly create the directory structure + } else { + Files.createDirectories(filePath.getParent()); // Create parent directories if they don't exist + Files.copy(zipIn, filePath); + } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); + } + } + + //search for the main HTML file. + try (Stream walk = Files.walk(tempDirectory)) { + List htmlFiles = walk.filter(file -> file.toString().endsWith(".html")) + .collect(Collectors.toList()); + + if (htmlFiles.isEmpty()) { + throw new IOException("No HTML files found in the unzipped directory."); + } + + // Prioritize 'index.html' if it exists, otherwise use the first .html file + for (Path htmlFile : htmlFiles) { + if (htmlFile.getFileName().toString().equals("index.html")) { + return htmlFile; + } + } + + return htmlFiles.get(0); + } + } +} diff --git a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java index 59c0b056..09a395ba 100644 --- a/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/WebResponseUtils.java @@ -12,6 +12,9 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; + public class WebResponseUtils { public static ResponseEntity boasToWebResponse(ByteArrayOutputStream baos, String docName) throws IOException { @@ -57,5 +60,19 @@ public class WebResponseUtils { return boasToWebResponse(baos, docName); } + + public static ResponseEntity pdfDocToWebResponse(PdfDocument document, String docName) throws IOException { + + // Open Byte Array and save document to it + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfWriter writer = new PdfWriter(baos); + PdfDocument newDocument = new PdfDocument(writer); + + document.copyPagesTo(1, document.getNumberOfPages(), newDocument); + newDocument.close(); + + return boasToWebResponse(baos, docName); + } + } diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 0d881b65..807e8581 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -236,6 +236,25 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert +home.MarkdownToPDF.title=Markdown to PDF +home.MarkdownToPDF.desc=Converts any Markdown fileto PDF +MarkdownToPDF.tags=markup,web-content,transformation,convert + + +home.getPdfInfo.title=Get ALL Info on PDF +home.getPdfInfo.desc=Grabs any and all information possible on PDFs +getPdfInfo.tags=infomation,data,stats,statistics + + +home.extractPage.title=Extract page(s) +home.extractPage.desc=Extracts select pages from PDF +extractPage.tags=extract + + +home.PdfToSinglePage.title=PDF to Single Large Page +home.PdfToSinglePage.desc=Merges all PDF pages into one large single page +PdfToSinglePage.tags=single page + ########################### # # # WEB PAGES # diff --git a/src/main/resources/static/images/extract.svg b/src/main/resources/static/images/extract.svg new file mode 100644 index 00000000..d21f03eb --- /dev/null +++ b/src/main/resources/static/images/extract.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/static/images/info.svg b/src/main/resources/static/images/info.svg new file mode 100644 index 00000000..8f48f86c --- /dev/null +++ b/src/main/resources/static/images/info.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/static/images/markdown.svg b/src/main/resources/static/images/markdown.svg new file mode 100644 index 00000000..ca5cd597 --- /dev/null +++ b/src/main/resources/static/images/markdown.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/static/images/single-page.svg b/src/main/resources/static/images/single-page.svg new file mode 100644 index 00000000..4f57d79b --- /dev/null +++ b/src/main/resources/static/images/single-page.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/auto-split-pdf.html b/src/main/resources/templates/auto-split-pdf.html index 4f3045e0..d6f68fe1 100644 --- a/src/main/resources/templates/auto-split-pdf.html +++ b/src/main/resources/templates/auto-split-pdf.html @@ -22,7 +22,7 @@
  • -
    +

    diff --git a/src/main/resources/templates/convert/markdown-to-pdf.html b/src/main/resources/templates/convert/markdown-to-pdf.html new file mode 100644 index 00000000..4637d129 --- /dev/null +++ b/src/main/resources/templates/convert/markdown-to-pdf.html @@ -0,0 +1,30 @@ + + + + + + +
    +
    +
    +

    +
    +
    +
    +

    + +
    +
    + + + +

    +

    +
    +
    +
    +
    +
    +
    + + diff --git a/src/main/resources/templates/extract-page.html b/src/main/resources/templates/extract-page.html new file mode 100644 index 00000000..0a5fb158 --- /dev/null +++ b/src/main/resources/templates/extract-page.html @@ -0,0 +1,33 @@ + + + + + + + +
    +
    +
    +

    +
    +
    +
    +

    +
    +
    + +
    + + +
    + + +
    +
    +
    +
    +
    +
    +
    + + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html index 8cbb6ca7..c5c8a0da 100644 --- a/src/main/resources/templates/home.html +++ b/src/main/resources/templates/home.html @@ -84,6 +84,13 @@
    +
    +
    +
    +
    + + +
    diff --git a/src/main/resources/templates/pdf-to-single-page.html b/src/main/resources/templates/pdf-to-single-page.html new file mode 100644 index 00000000..1f5b64c6 --- /dev/null +++ b/src/main/resources/templates/pdf-to-single-page.html @@ -0,0 +1,29 @@ + + + + + + + +
    +
    +
    +

    +
    +
    +
    +

    +
    +

    +
    + +
    +
    +
    +
    + +
    +
    +
    + + \ No newline at end of file From 96f05cd5186394b1633f6902bc8ce485f425f476 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 2 Aug 2023 22:49:43 +0100 Subject: [PATCH 09/21] get info changes --- .../{PDFExtractor.java => GetInfoOnPDF.java} | 175 +++++++++--------- src/main/resources/messages_en_GB.properties | 2 +- src/main/resources/static/js/fileInput.js | 59 +++--- .../resources/templates/fragments/common.html | 12 +- .../resources/templates/other/compare.html | 4 +- .../templates/security/get-info-on-pdf.html | 100 +++++++++- 6 files changed, 226 insertions(+), 126 deletions(-) rename src/main/java/stirling/software/SPDF/controller/api/security/{PDFExtractor.java => GetInfoOnPDF.java} (89%) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java similarity index 89% rename from src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java rename to src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index 2cd429b4..a167cb74 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PDFExtractor.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -73,7 +73,7 @@ import java.util.Set; import java.util.HashSet; @RestController @Tag(name = "Security", description = "Security APIs") -public class PDFExtractor { +public class GetInfoOnPDF { static ObjectMapper objectMapper = new ObjectMapper(); @@ -94,6 +94,13 @@ public class PDFExtractor { // Metadata using PDFBox PDDocumentInformation info = pdfBoxDoc.getDocumentInformation(); ObjectNode metadata = objectMapper.createObjectNode(); + ObjectNode basicInfo = objectMapper.createObjectNode(); + ObjectNode docInfoNode = objectMapper.createObjectNode(); + ObjectNode compliancy = objectMapper.createObjectNode(); + ObjectNode encryption = objectMapper.createObjectNode(); + ObjectNode other = objectMapper.createObjectNode(); + + metadata.put("Title", info.getTitle()); metadata.put("Author", info.getAuthor()); metadata.put("Subject", info.getSubject()); @@ -102,25 +109,25 @@ public class PDFExtractor { metadata.put("Creator", info.getCreator()); metadata.put("CreationDate", formatDate(info.getCreationDate())); metadata.put("ModificationDate", formatDate(info.getModificationDate())); - metadata.put("Trapped", info.getTrapped()); jsonOutput.set("Metadata", metadata); - + + // Total file size of the PDF long fileSizeInBytes = inputFile.getSize(); - jsonOutput.put("FileSizeInBytes", fileSizeInBytes); + basicInfo.put("FileSizeInBytes", fileSizeInBytes); // Number of words, paragraphs, and images in the entire document String fullText = new PDFTextStripper().getText(pdfBoxDoc); String[] words = fullText.split("\\s+"); int wordCount = words.length; int paragraphCount = fullText.split("\r\n|\r|\n").length; - jsonOutput.put("WordCount", wordCount); - jsonOutput.put("ParagraphCount", paragraphCount); + basicInfo.put("WordCount", wordCount); + basicInfo.put("ParagraphCount", paragraphCount); // Number of characters in the entire document (including spaces and special characters) int charCount = fullText.length(); - jsonOutput.put("CharacterCount", charCount); + basicInfo.put("CharacterCount", charCount); // Initialize the flags and types @@ -142,22 +149,24 @@ public class PDFExtractor { hasCompression = true; compressionType = "Compressed Xref or Rebuilt Xref"; } - jsonOutput.put("Compression", hasCompression); + basicInfo.put("Compression", hasCompression); if(hasCompression) - jsonOutput.put("CompressionType", compressionType); + basicInfo.put("CompressionType", compressionType); String language = pdfBoxDoc.getDocumentCatalog().getLanguage(); - jsonOutput.put("Language", language); + basicInfo.put("Language", language); + basicInfo.put("Number of pages", pdfBoxDoc.getNumberOfPages()); - // Document Information using PDFBox - ObjectNode docInfoNode = objectMapper.createObjectNode(); - docInfoNode.put("Number of pages", pdfBoxDoc.getNumberOfPages()); - docInfoNode.put("PDF version", pdfBoxDoc.getVersion()); - // Page Mode using iText7 PdfCatalog catalog = itextDoc.getCatalog(); PdfName pageMode = catalog.getPdfObject().getAsName(PdfName.PageMode); + + // Document Information using PDFBox + docInfoNode.put("PDF version", pdfBoxDoc.getVersion()); + docInfoNode.put("Trapped", info.getTrapped()); + docInfoNode.put("Page Mode", getPageModeDescription(pageMode));; + @@ -193,7 +202,7 @@ public class PDFExtractor { } } - jsonOutput.set("EmbeddedFiles", embeddedFilesArray); + other.set("EmbeddedFiles", embeddedFilesArray); //attachments TODO size ArrayNode attachmentsArray = objectMapper.createArrayNode(); @@ -207,7 +216,7 @@ public class PDFExtractor { } } } - jsonOutput.set("Attachments", attachmentsArray); + other.set("Attachments", attachmentsArray); //Javascript PdfDictionary namesDict = itextDoc.getCatalog().getPdfObject().getAsDictionary(PdfName.Names); @@ -226,7 +235,7 @@ public class PDFExtractor { } } - jsonOutput.set("JavaScript", javascriptArray); + other.set("JavaScript", javascriptArray); //TODO size PdfOCProperties ocProperties = itextDoc.getCatalog().getOCProperties(false); @@ -240,7 +249,7 @@ public class PDFExtractor { } } - jsonOutput.set("Layers", layersArray); + other.set("Layers", layersArray); //TODO Security @@ -267,7 +276,7 @@ public class PDFExtractor { } } - jsonOutput.set("FormFields", formFieldsArray2); + jsonOutput.set("FormFields2", formFieldsArray2); PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot(); @@ -275,19 +284,13 @@ public class PDFExtractor { try { if(structureTreeRoot != null) { structureTreeArray = exploreStructureTree(structureTreeRoot.getKids()); - jsonOutput.set("StructureTree", structureTreeArray); + other.set("StructureTree", structureTreeArray); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } - - - - - - boolean isPdfACompliant = checkOutputIntent(itextDoc, "PDF/A"); boolean isPdfXCompliant = checkOutputIntent(itextDoc, "PDF/X"); @@ -297,7 +300,6 @@ public class PDFExtractor { boolean isPdfBCompliant = checkForStandard(itextDoc, "PDF/B"); // If you want to check for PDF/Broadcast, though this isn't an official ISO standard. boolean isPdfSECCompliant = checkForStandard(itextDoc, "PDF/SEC"); // This might not be effective since PDF/SEC was under development in 2021. - ObjectNode compliancy = objectMapper.createObjectNode(); compliancy.put("IsPDF/ACompliant", isPdfACompliant); compliancy.put("IsPDF/XCompliant", isPdfXCompliant); compliancy.put("IsPDF/ECompliant", isPdfECompliant); @@ -306,7 +308,6 @@ public class PDFExtractor { compliancy.put("IsPDF/BCompliant", isPdfBCompliant); compliancy.put("IsPDF/SECCompliant", isPdfSECCompliant); - jsonOutput.set("Compliancy", compliancy); @@ -318,7 +319,7 @@ public class PDFExtractor { addOutlinesToArray(child, bookmarksArray); } } - jsonOutput.set("Bookmarks/Outline/TOC", bookmarksArray); + other.set("Bookmarks/Outline/TOC", bookmarksArray); String xmpString = null; try { @@ -331,29 +332,27 @@ public class PDFExtractor { } catch (XMPException e) { e.printStackTrace(); } - jsonOutput.put("XMPMetadata", xmpString); + other.put("XMPMetadata", xmpString); - ObjectNode encryptionNode = objectMapper.createObjectNode(); if (pdfBoxDoc.isEncrypted()) { - encryptionNode.put("IsEncrypted", true); + encryption.put("IsEncrypted", true); // Retrieve encryption details using getEncryption() - PDEncryption encryption = pdfBoxDoc.getEncryption(); - encryptionNode.put("EncryptionAlgorithm", encryption.getFilter()); - encryptionNode.put("KeyLength", encryption.getLength()); - encryptionNode.put("Permissions", pdfBoxDoc.getCurrentAccessPermission().toString()); + PDEncryption pdfEncryption = pdfBoxDoc.getEncryption(); + encryption.put("EncryptionAlgorithm", pdfEncryption.getFilter()); + encryption.put("KeyLength", pdfEncryption.getLength()); + encryption.put("Permissions", pdfBoxDoc.getCurrentAccessPermission().toString()); // Add other encryption-related properties as needed } else { - encryptionNode.put("IsEncrypted", false); + encryption.put("IsEncrypted", false); } - jsonOutput.set("Encryption", encryptionNode); - docInfoNode.put("Page Mode", getPageModeDescription(pageMode));; + + - jsonOutput.set("Document Information", docInfoNode); ObjectNode pageInfoParent = objectMapper.createObjectNode(); for (int pageNum = 1; pageNum <= itextDoc.getNumberOfPages(); pageNum++) { ObjectNode pageInfo = objectMapper.createObjectNode(); @@ -382,7 +381,6 @@ public class PDFExtractor { pageInfo.put("Text Characters Count", pageText.length()); // // Annotations - ArrayNode annotationsArray = objectMapper.createArrayNode(); List annotations = itextDoc.getPage(pageNum).getAnnotations(); int subtypeCount = 0; @@ -447,61 +445,57 @@ public class PDFExtractor { } pageInfo.set("Links", linksArray); - //Fonts + // Fonts ArrayNode fontsArray = objectMapper.createArrayNode(); PdfDictionary fontDicts = resources.getResource(PdfName.Font); - Set uniqueSubtypes = new HashSet<>(); // To store unique subtypes + Set uniqueSubtypes = new HashSet<>(); // To store unique subtypes + + // Map to store unique fonts and their counts + Map uniqueFontsMap = new HashMap<>(); - if (fontDicts != null) { for (PdfName key : fontDicts.keySet()) { - ObjectNode fontNode = objectMapper.createObjectNode(); // Create a new font node for each font + ObjectNode fontNode = objectMapper.createObjectNode(); // Create a new font node for each font PdfDictionary font = fontDicts.getAsDictionary(key); - - boolean isEmbedded = font.containsKey(PdfName.FontFile) || - font.containsKey(PdfName.FontFile2) || + + boolean isEmbedded = font.containsKey(PdfName.FontFile) || + font.containsKey(PdfName.FontFile2) || font.containsKey(PdfName.FontFile3); - fontNode.put("IsEmbedded", isEmbedded); - - - if (font.containsKey(PdfName.Encoding)) { - String encoding = font.getAsName(PdfName.Encoding).toString(); - fontNode.put("Encoding", encoding); - } - - - - if(font.getAsString(PdfName.BaseFont) != null) + fontNode.put("IsEmbedded", isEmbedded); + + if (font.containsKey(PdfName.Encoding)) { + String encoding = font.getAsName(PdfName.Encoding).toString(); + fontNode.put("Encoding", encoding); + } + + if (font.getAsString(PdfName.BaseFont) != null) { fontNode.put("Name", font.getAsString(PdfName.BaseFont).toString()); - + } + String subtype = null; - // Font Subtype (e.g., Type1, TrueType) if (font.containsKey(PdfName.Subtype)) { subtype = font.getAsName(PdfName.Subtype).toString(); uniqueSubtypes.add(subtype); // Add to set to ensure uniqueness } fontNode.put("Subtype", subtype); - - // Font Descriptor + PdfDictionary fontDescriptor = font.getAsDictionary(PdfName.FontDescriptor); if (fontDescriptor != null) { - // Italic Angle if (fontDescriptor.containsKey(PdfName.ItalicAngle)) { fontNode.put("ItalicAngle", fontDescriptor.getAsNumber(PdfName.ItalicAngle).floatValue()); } - - // Flags (e.g., italic, bold) + if (fontDescriptor.containsKey(PdfName.Flags)) { int flags = fontDescriptor.getAsNumber(PdfName.Flags).intValue(); - fontNode.put("IsItalic", (flags & 64) != 0); // Existing italic flag - fontNode.put("IsBold", (flags & 1 << 16) != 0); // Existing bold flag + fontNode.put("IsItalic", (flags & 64) != 0); + fontNode.put("IsBold", (flags & 1 << 16) != 0); fontNode.put("IsFixedPitch", (flags & 1) != 0); fontNode.put("IsSerif", (flags & 2) != 0); fontNode.put("IsSymbolic", (flags & 4) != 0); fontNode.put("IsScript", (flags & 8) != 0); fontNode.put("IsNonsymbolic", (flags & 16) != 0); } - + if (fontDescriptor.containsKey(PdfName.FontFamily)) { String fontFamily = fontDescriptor.getAsString(PdfName.FontFamily).toString(); fontNode.put("FontFamily", fontFamily); @@ -511,34 +505,43 @@ public class PDFExtractor { String fontStretch = fontDescriptor.getAsName(PdfName.FontStretch).toString(); fontNode.put("FontStretch", fontStretch); } - - if (fontDescriptor != null && fontDescriptor.containsKey(PdfName.FontBBox)) { + + if (fontDescriptor.containsKey(PdfName.FontBBox)) { PdfArray bbox = fontDescriptor.getAsArray(PdfName.FontBBox); fontNode.put("FontBoundingBox", bbox.toString()); } - if (fontDescriptor != null && fontDescriptor.containsKey(PdfName.FontWeight)) { + + if (fontDescriptor.containsKey(PdfName.FontWeight)) { float fontWeight = fontDescriptor.getAsNumber(PdfName.FontWeight).floatValue(); fontNode.put("FontWeight", fontWeight); } - } + if (font.containsKey(PdfName.ToUnicode)) { - PdfStream toUnicodeStream = font.getAsStream(PdfName.ToUnicode); - // Handle the stream as needed, maybe extract some details or just note its existence fontNode.put("HasToUnicodeMap", true); } + if (fontNode.size() > 0) { - fontsArray.add(fontNode); // Add each font node to fontsArray + // Create a unique key for this font node based on its attributes + String uniqueKey = fontNode.toString(); + + // Increment count if this font exists, or initialize it if new + if (uniqueFontsMap.containsKey(uniqueKey)) { + ObjectNode existingFontNode = uniqueFontsMap.get(uniqueKey); + int count = existingFontNode.get("Count").asInt() + 1; + existingFontNode.put("Count", count); + } else { + fontNode.put("Count", 1); + uniqueFontsMap.put(uniqueKey, fontNode); + } } } } - // Add unique subtypes to fontsArray - ArrayNode subtypesArray = objectMapper.createArrayNode(); - for (String subtype : uniqueSubtypes) { - subtypesArray.add(subtype); + // Add unique font entries to fontsArray + for (ObjectNode uniqueFontNode : uniqueFontsMap.values()) { + fontsArray.add(uniqueFontNode); } - pageInfo.set("FontSubtypes", subtypesArray); // Changed from Fonts to FontSubtypes pageInfo.set("Fonts", fontsArray); @@ -605,8 +608,14 @@ public class PDFExtractor { pageInfoParent.set("Page " + pageNum, pageInfo); } + - jsonOutput.set("Per Page Info", pageInfoParent); + jsonOutput.set("BasicInfo", basicInfo); + jsonOutput.set("DocumentInfo", docInfoNode); + jsonOutput.set("Compliancy", compliancy); + jsonOutput.set("Encryption", encryption); + jsonOutput.set("Other", other); + jsonOutput.set("PerPageInfo", pageInfoParent); diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 807e8581..5ae0c5a0 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -237,7 +237,7 @@ HTMLToPDF.tags=markup,web-content,transformation,convert home.MarkdownToPDF.title=Markdown to PDF -home.MarkdownToPDF.desc=Converts any Markdown fileto PDF +home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 94b5294f..0842462e 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -1,12 +1,18 @@ document.addEventListener('DOMContentLoaded', function() { + document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput); +}); +function setupFileInput(chooser) { + const elementId = chooser.getAttribute('data-element-id'); + const filesSelected = chooser.getAttribute('data-files-selected'); + const pdfPrompt = chooser.getAttribute('data-pdf-prompt'); + let overlay; let dragCounter = 0; const dragenterListener = function() { dragCounter++; if (!overlay) { - // Create and show the overlay overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = 0; @@ -28,7 +34,6 @@ document.addEventListener('DOMContentLoaded', function() { const dragleaveListener = function() { dragCounter--; if (dragCounter === 0) { - // Hide and remove the overlay if (overlay) { overlay.remove(); overlay = null; @@ -40,24 +45,19 @@ document.addEventListener('DOMContentLoaded', function() { const dt = e.dataTransfer; const files = dt.files; - // Access the file input element and assign dropped files - const fileInput = document.getElementById(elementID); + const fileInput = document.getElementById(elementId); fileInput.files = files; - // Hide and remove the overlay if (overlay) { overlay.remove(); overlay = null; } - // Reset drag counter dragCounter = 0; - //handleFileInputChange(fileInput); fileInput.dispatchEvent(new Event('change', { bubbles: true })); }; - // Prevent default behavior for drag events ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { document.body.addEventListener(eventName, preventDefaults, false); }); @@ -69,29 +69,26 @@ document.addEventListener('DOMContentLoaded', function() { document.body.addEventListener('dragenter', dragenterListener); document.body.addEventListener('dragleave', dragleaveListener); - // Add drop event listener document.body.addEventListener('drop', dropListener); -}); + $("#" + elementId).on("change", function() { + handleFileInputChange(this); + }); -$("#"+elementID).on("change", function() { - handleFileInputChange(this); -}); - - -function handleFileInputChange(inputElement) { - const files = $(inputElement).get(0).files; - const fileNames = Array.from(files).map(f => f.name); - const selectedFilesContainer = $(inputElement).siblings(".selected-files"); - selectedFilesContainer.empty(); - fileNames.forEach(fileName => { - selectedFilesContainer.append("
    " + fileName + "
    "); - }); - if (fileNames.length === 1) { - $(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]); - } else if (fileNames.length > 1) { - $(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " " + filesSelected); - } else { - $(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt); - } -} \ No newline at end of file + function handleFileInputChange(inputElement) { + const files = $(inputElement).get(0).files; + const fileNames = Array.from(files).map(f => f.name); + const selectedFilesContainer = $(inputElement).siblings(".selected-files"); + selectedFilesContainer.empty(); + fileNames.forEach(fileName => { + selectedFilesContainer.append("
    " + fileName + "
    "); + }); + if (fileNames.length === 1) { + $(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames[0]); + } else if (fileNames.length > 1) { + $(inputElement).siblings(".custom-file-label").addClass("selected").html(fileNames.length + " " + filesSelected); + } else { + $(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt); + } + } +} diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index eb079166..42477113 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -98,7 +98,10 @@ -
    +
    @@ -114,12 +117,7 @@
    - - + diff --git a/src/main/resources/templates/other/compare.html b/src/main/resources/templates/other/compare.html index 7c06062a..aeb83aee 100644 --- a/src/main/resources/templates/other/compare.html +++ b/src/main/resources/templates/other/compare.html @@ -15,8 +15,8 @@

    -
    -
    +
    +
    diff --git a/src/main/resources/templates/security/get-info-on-pdf.html b/src/main/resources/templates/security/get-info-on-pdf.html index 36a0bb6e..dc93186c 100644 --- a/src/main/resources/templates/security/get-info-on-pdf.html +++ b/src/main/resources/templates/security/get-info-on-pdf.html @@ -15,13 +15,109 @@

    -

    -
    + +

    +
    + +
    + +
    + + + Download JSON +
    +
    From b07437dbfa6907179917b505a6fcbe020fa88a97 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 2 Aug 2023 23:03:35 +0100 Subject: [PATCH 10/21] get info DONE! --- .../controller/api/security/GetInfoOnPDF.java | 18 +---------------- .../templates/security/get-info-on-pdf.html | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java index a167cb74..fea57764 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/GetInfoOnPDF.java @@ -260,23 +260,7 @@ public class GetInfoOnPDF { // Digital Signatures using iText7 TODO - - - - PDAcroForm pdAcroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm(); - ArrayNode formFieldsArray2 = objectMapper.createArrayNode(); - if (pdAcroForm != null) { - - for (PDField field : pdAcroForm.getFields()) { - ObjectNode fieldNode = objectMapper.createObjectNode(); - fieldNode.put("FieldName", field.getFullyQualifiedName()); - fieldNode.put("FieldType", field.getFieldType()); - // Add more attributes as needed... - formFieldsArray2.add(fieldNode); - } - - } - jsonOutput.set("FormFields2", formFieldsArray2); + PDStructureTreeRoot structureTreeRoot = pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot(); diff --git a/src/main/resources/templates/security/get-info-on-pdf.html b/src/main/resources/templates/security/get-info-on-pdf.html index dc93186c..a77b5ae6 100644 --- a/src/main/resources/templates/security/get-info-on-pdf.html +++ b/src/main/resources/templates/security/get-info-on-pdf.html @@ -5,7 +5,6 @@ -
    @@ -71,14 +70,17 @@ function renderJsonSection(key, value, depth = 0) { + // Replace spaces and other non-alphanumeric characters with underscores for valid IDs + let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key; + let output = `
    -
    +
    `; // Check if the value is an object and has children - if (value && typeof value === 'object' && Object.keys(value).length) { + if (value && typeof value === 'object' && (Object.keys(value).length || Array.isArray(value))) { output += ` - `; } else { @@ -89,7 +91,7 @@ output += `
    -
    `; +
    `; // Check if the value is a nested object if (typeof value === 'object' && !Array.isArray(value)) { @@ -99,9 +101,13 @@ } output += '
    '; } else if (typeof value === 'object' && Array.isArray(value) && value.length) { // Array values + output += '
    '; value.forEach((val, index) => { - output += renderJsonSection(index, val, depth + 1); + // For arrays, we're going to make the displayed key more descriptive. + const arrayKey = `${key}[${index}]`; + output += renderJsonSection(arrayKey, val, depth + 1); }); + output += '
    '; } output += '
    '; @@ -109,6 +115,8 @@ return output; } + + From 724fb4bf8fbce5958580ddff1e820a355f7b9019 Mon Sep 17 00:00:00 2001 From: jordy Date: Sat, 5 Aug 2023 17:36:05 +0200 Subject: [PATCH 11/21] add fileInput widget to multiSelect --- .../static/js/multitool/PdfContainer.js | 22 +++--- .../static/js/multitool/fileInput.js | 69 +++++++++++++++++++ src/main/resources/templates/multi-tool.html | 5 +- 3 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 src/main/resources/static/js/multitool/fileInput.js diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index bd150ac6..45c92be1 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -10,6 +10,7 @@ class PdfContainer { this.pagesContainerWrapper = document.getElementById(wrapperId); this.movePageTo = this.movePageTo.bind(this); this.addPdfs = this.addPdfs.bind(this); + this.addPdfsFromFiles = this.addPdfsFromFiles.bind(this); this.rotateElement = this.rotateElement.bind(this); this.rotateAll = this.rotateAll.bind(this); this.exportPdf = this.exportPdf.bind(this); @@ -57,22 +58,25 @@ class PdfContainer { input.type = 'file'; input.multiple = true; input.setAttribute("accept", "application/pdf"); - input.onchange = async(e) => { const files = e.target.files; - this.fileName = files[0].name; - for (var i=0; i < files.length; i++) { - await this.addPdfFile(files[i], nextSiblingElement); - } - - document.querySelectorAll(".enable-on-file").forEach(element => { - element.disabled = false; - }); + this.addPdfsFromFiles(files, nextSiblingElement); } input.click(); } + async addPdfsFromFiles(files, nextSiblingElement) { + this.fileName = files[0].name; + for (var i=0; i < files.length; i++) { + await this.addPdfFile(files[i], nextSiblingElement); + } + + document.querySelectorAll(".enable-on-file").forEach(element => { + element.disabled = false; + }); + } + rotateElement(element, deg) { var lastTransform = element.style.rotate; if (!lastTransform) { diff --git a/src/main/resources/static/js/multitool/fileInput.js b/src/main/resources/static/js/multitool/fileInput.js new file mode 100644 index 00000000..1a76bd48 --- /dev/null +++ b/src/main/resources/static/js/multitool/fileInput.js @@ -0,0 +1,69 @@ +const addFileDragListener = (callback) => { + let overlay; + let dragCounter = 0; + + const dragenterListener = function() { + dragCounter++; + if (!overlay) { + // Create and show the overlay + overlay = document.createElement('div'); + overlay.style.position = 'fixed'; + overlay.style.top = 0; + overlay.style.left = 0; + overlay.style.width = '100%'; + overlay.style.height = '100%'; + overlay.style.background = 'rgba(0, 0, 0, 0.5)'; + overlay.style.color = '#fff'; + overlay.style.zIndex = '1000'; + overlay.style.display = 'flex'; + overlay.style.alignItems = 'center'; + overlay.style.justifyContent = 'center'; + overlay.style.pointerEvents = 'none'; + overlay.innerHTML = '

    Drop files anywhere to upload

    '; + document.getElementById('content-wrap').appendChild(overlay); + } + }; + + const dragleaveListener = function() { + dragCounter--; + if (dragCounter === 0) { + // Hide and remove the overlay + if (overlay) { + overlay.remove(); + overlay = null; + } + } + }; + + const dropListener = function(e) { + + const dt = e.dataTransfer; + const files = dt.files; + callback(files).catch((err) => { + console.error(err); + //maybe + }).finally(() => { + if (overlay) { + overlay.remove(); + overlay = null; + } + }); + }; + + // Prevent default behavior for drag events + ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { + document.body.addEventListener(eventName, preventDefaults, false); + }); + + function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); + } + + document.body.addEventListener('dragenter', dragenterListener); + document.body.addEventListener('dragleave', dragleaveListener); + // Add drop event listener + document.body.addEventListener('drop', dropListener); +} + +export default addFileDragListener; \ No newline at end of file diff --git a/src/main/resources/templates/multi-tool.html b/src/main/resources/templates/multi-tool.html index 50fadcca..0b1d7b8a 100644 --- a/src/main/resources/templates/multi-tool.html +++ b/src/main/resources/templates/multi-tool.html @@ -65,7 +65,7 @@ import scrollDivHorizontally from "./js/multitool/horizontalScroll.js"; import ImageHighlighter from "./js/multitool/ImageHighlighter.js"; import PdfActionsManager from './js/multitool/PdfActionsManager.js'; - + import addFileInputListener from './js/multitool/fileInput.js'; // enables drag and drop const dragDropManager = new DragDropManager('drag-container', 'pages-container'); // enables image highlight on click @@ -86,6 +86,9 @@ pdfActionsManager, ] ) + addFileInputListener(async (files) => { + pdfContainer.addPdfsFromFiles(files); + }); + +
    +
    +
    + +
    +
    + + + \ No newline at end of file diff --git a/src/main/resources/templates/security/get-info-on-pdf.html b/src/main/resources/templates/security/get-info-on-pdf.html index 193056e4..06cc1052 100644 --- a/src/main/resources/templates/security/get-info-on-pdf.html +++ b/src/main/resources/templates/security/get-info-on-pdf.html @@ -73,46 +73,45 @@ function renderJsonSection(key, value, depth = 0) { - // Replace spaces and other non-alphanumeric characters with underscores for valid IDs let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key; let output = `
    `; - // Check if the value is an object and has children - if (value && typeof value === 'object') { - // For arrays and non-array objects - if (Array.isArray(value) && value.length === 0) { - output += `${key}: Empty array`; - } else if (!Array.isArray(value) && Object.keys(value).length === 0) { - output += `${key}: Empty object`; - } else { - output += ` - `; - } - } else { - // For simple key-value pairs - output += `${key}: ${value}`; - } - + if (key === 'XMPMetadata' && typeof value === "string") { + output += ``; + } else if (value && typeof value === 'object') { + if (Array.isArray(value) && value.length === 0) { + output += `${key}: Empty array`; + } else if (!Array.isArray(value) && Object.keys(value).length === 0) { + output += `${key}: Empty object`; + } else { + output += ``; + } + } else { + output += `${key}: ${value}`; + } output += ` -
    -
    -
    `; + +
    +
    `; - // Check if the value is a nested object - if (value && typeof value === 'object' && !Array.isArray(value)) { + if (key === 'XMPMetadata' && typeof value === "string") { + output += `
    ${escapeHTML(value)}
    `; + } else if (value && typeof value === 'object' && !Array.isArray(value)) { output += '
    '; if (Object.keys(value).length) { for (const subKey in value) { output += renderJsonSection(subKey, value[subKey], depth + 1); } } else { - output += '

    Empty object

    '; + output += '

    Empty

    '; } output += '
    '; } else if (value && typeof value === 'object' && Array.isArray(value)) { @@ -123,7 +122,7 @@ output += renderJsonSection(arrayKey, val, depth + 1); }); } else { - output += '

    Empty array

    '; + output += '

    Empty

    '; } output += '
    '; } @@ -132,6 +131,14 @@ return output; } + + + function escapeHTML(s) { + if(s) + return s.replace(/&/g, '&').replace(//g, '>'); + + return null; + }
    From 5d3ee7755a5387d3bbe594d3f04b567549f54605 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 6 Aug 2023 21:57:35 +0100 Subject: [PATCH 18/21] show js --- src/main/resources/messages_ar_AR.properties | 41 ++++++++------------ src/main/resources/messages_ca_CA.properties | 41 ++++++++------------ src/main/resources/messages_de_DE.properties | 41 ++++++++------------ src/main/resources/messages_es_ES.properties | 41 ++++++++------------ src/main/resources/messages_eu_ES.properties | 41 ++++++++------------ src/main/resources/messages_fr_FR.properties | 41 ++++++++------------ src/main/resources/messages_it_IT.properties | 41 ++++++++------------ src/main/resources/messages_ja_JP.properties | 41 ++++++++------------ src/main/resources/messages_ko_KR.properties | 41 ++++++++------------ src/main/resources/messages_pl_PL.properties | 41 ++++++++------------ src/main/resources/messages_pt_BR.properties | 41 ++++++++------------ src/main/resources/messages_ro_RO.properties | 41 ++++++++------------ src/main/resources/messages_ru_RU.properties | 41 ++++++++------------ src/main/resources/messages_sv_SE.properties | 41 ++++++++------------ src/main/resources/messages_zh_CN.properties | 41 ++++++++------------ 15 files changed, 240 insertions(+), 375 deletions(-) diff --git a/src/main/resources/messages_ar_AR.properties b/src/main/resources/messages_ar_AR.properties index 9ec04294..53a0fa44 100644 --- a/src/main/resources/messages_ar_AR.properties +++ b/src/main/resources/messages_ar_AR.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_ca_CA.properties b/src/main/resources/messages_ca_CA.properties index 938a6f2a..6d653a9b 100644 --- a/src/main/resources/messages_ca_CA.properties +++ b/src/main/resources/messages_ca_CA.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_de_DE.properties b/src/main/resources/messages_de_DE.properties index d36cc592..1b5691bf 100644 --- a/src/main/resources/messages_de_DE.properties +++ b/src/main/resources/messages_de_DE.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_es_ES.properties b/src/main/resources/messages_es_ES.properties index b7721482..6f31beec 100644 --- a/src/main/resources/messages_es_ES.properties +++ b/src/main/resources/messages_es_ES.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Convierte cualquier archivo HTML o ZIP a PDF HTMLToPDF.tags=margen,contenido web,transformación,convertir -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_eu_ES.properties b/src/main/resources/messages_eu_ES.properties index 817df519..c1e6eba0 100644 --- a/src/main/resources/messages_eu_ES.properties +++ b/src/main/resources/messages_eu_ES.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index cc93ca80..b26278f5 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_it_IT.properties b/src/main/resources/messages_it_IT.properties index 4f8ecb27..4e5cf304 100644 --- a/src/main/resources/messages_it_IT.properties +++ b/src/main/resources/messages_it_IT.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_ja_JP.properties b/src/main/resources/messages_ja_JP.properties index 3a7d2c06..4e49c578 100644 --- a/src/main/resources/messages_ja_JP.properties +++ b/src/main/resources/messages_ja_JP.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_ko_KR.properties b/src/main/resources/messages_ko_KR.properties index 88e311db..4c5a1e8c 100644 --- a/src/main/resources/messages_ko_KR.properties +++ b/src/main/resources/messages_ko_KR.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties index 82de60dd..2e04e80a 100644 --- a/src/main/resources/messages_pl_PL.properties +++ b/src/main/resources/messages_pl_PL.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_pt_BR.properties b/src/main/resources/messages_pt_BR.properties index af851716..e615c730 100644 --- a/src/main/resources/messages_pt_BR.properties +++ b/src/main/resources/messages_pt_BR.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_ro_RO.properties b/src/main/resources/messages_ro_RO.properties index 0e2a1602..11a5c090 100644 --- a/src/main/resources/messages_ro_RO.properties +++ b/src/main/resources/messages_ro_RO.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_ru_RU.properties b/src/main/resources/messages_ru_RU.properties index f64c0c59..89be5d64 100644 --- a/src/main/resources/messages_ru_RU.properties +++ b/src/main/resources/messages_ru_RU.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_sv_SE.properties b/src/main/resources/messages_sv_SE.properties index 6687fcf6..06637e86 100644 --- a/src/main/resources/messages_sv_SE.properties +++ b/src/main/resources/messages_sv_SE.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 0c557eb7..7ece8479 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -236,67 +236,61 @@ home.HTMLToPDF.desc=Converts any HTML file or zip to PDF HTMLToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.MarkdownToPDF.title=Markdown to PDF home.MarkdownToPDF.desc=Converts any Markdown file to PDF MarkdownToPDF.tags=markup,web-content,transformation,convert -########################## -### TODO: Translate ### -########################## home.getPdfInfo.title=Get ALL Info on PDF home.getPdfInfo.desc=Grabs any and all information possible on PDFs getPdfInfo.tags=infomation,data,stats,statistics -########################## -### TODO: Translate ### -########################## home.extractPage.title=Extract page(s) home.extractPage.desc=Extracts select pages from PDF extractPage.tags=extract -########################## -### TODO: Translate ### -########################## home.PdfToSinglePage.title=PDF to Single Large Page home.PdfToSinglePage.desc=Merges all PDF pages into one large single page PdfToSinglePage.tags=single page + +########################## +### TODO: Translate ### +########################## +home.showJS.title=Show Javascript +home.showJS.desc=Searches and displays any JS injected into a PDF +showJS.tags=JS + ########################### # # # WEB PAGES # # # ########################### - - - -#pdfToSinglePage +#showJS ########################## ### TODO: Translate ### ########################## +showJS.title=Show Javascript +showJS.header=Show Javascript +showJS.downloadJS=Download Javascript +showJS.submit=Show + + +#pdfToSinglePage pdfToSinglePage.title=PDF To Single Page pdfToSinglePage.header=PDF To Single Page pdfToSinglePage.submit=Convert To Single Page #pageExtracter -########################## -### TODO: Translate ### -########################## pageExtracter.title=Extract Pages pageExtracter.header=Extract Pages pageExtracter.submit=Extract #getPdfInfo -########################## -### TODO: Translate ### -########################## getPdfInfo.title=Get Info on PDF getPdfInfo.header=Get Info on PDF getPdfInfo.submit=Get Info @@ -304,9 +298,6 @@ getPdfInfo.downloadJson=Download JSON #markdown-to-pdf -########################## -### TODO: Translate ### -########################## MarkdownToPDF.title=Markdown To PDF MarkdownToPDF.header=Markdown To PDF MarkdownToPDF.submit=Convert From 9cb4d8e088e09b7b53364f4e2b5c7c04513c4764 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 6 Aug 2023 21:58:55 +0100 Subject: [PATCH 19/21] remove copy fonts from LITE --- Dockerfile-lite | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Dockerfile-lite b/Dockerfile-lite index eb92e487..d3968a2a 100644 --- a/Dockerfile-lite +++ b/Dockerfile-lite @@ -10,12 +10,6 @@ RUN apt-get update && \ unoconv && \ rm -rf /var/lib/apt/lists/* -#Install fonts -RUN mkdir /usr/share/fonts/opentype/noto/ -COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/ -COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/ -RUN fc-cache -f -v - # Copy the application JAR file COPY build/libs/*.jar app.jar From 4a579c00cea49dfeb8fc00381265b77668a53c7e Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 6 Aug 2023 22:14:37 +0100 Subject: [PATCH 20/21] docs --- Endpoint-groups.md | 7 ++++++- Version-groups.md | 7 ++++++- .../software/SPDF/config/EndpointConfiguration.java | 13 +++++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Endpoint-groups.md b/Endpoint-groups.md index e6401396..9c7f3ae6 100644 --- a/Endpoint-groups.md +++ b/Endpoint-groups.md @@ -3,9 +3,11 @@ | adjust-contrast | ✔️ | | | | | | | | | | ✔️ | | auto-split-pdf | ✔️ | | | | | | | | | ✔️ | | | crop | ✔️ | | | | | | | | | ✔️ | | +| extract-page | ✔️ | | | | | | | | | ✔️ | | | merge-pdfs | ✔️ | | | | | | | | | ✔️ | | | multi-page-layout | ✔️ | | | | | | | | | ✔️ | | | pdf-organizer | ✔️ | | | | | | | | | ✔️ | ✔️ | +| pdf-to-single-page | ✔️ | | | | | | | | | ✔️ | | | remove-pages | ✔️ | | | | | | | | | ✔️ | | | rotate-pdf | ✔️ | | | | | | | | | ✔️ | | | scale-pages | ✔️ | | | | | | | | | ✔️ | | @@ -15,6 +17,7 @@ | pdf-to-html | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-img | | ✔️ | | | | | | | | ✔️ | | | pdf-to-pdfa | | ✔️ | | | ✔️ | | | | ✔️ | | | +| pdf-to-markdown | | ✔️ | | | | | | | | ✔️ | | | pdf-to-presentation | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-text | | ✔️ | | | ✔️ | | | ✔️ | | | | | pdf-to-word | | ✔️ | | | ✔️ | | | ✔️ | | | | @@ -34,8 +37,10 @@ | compress-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | extract-image-scans | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | extract-images | | | | ✔️ | | | | | | ✔️ | | -| flatten | | | | ✔️ | | | | | | | | +| flatten | | | | ✔️ | | | | | | | ✔️ | +| get-info-on-pdf | | | | ✔️ | | | | | | ✔️ | | | ocr-pdf | | | | ✔️ | ✔️ | | | | ✔️ | | | | remove-blanks | | | | ✔️ | ✔️ | ✔️ | ✔️ | | | | | | repair | | | | ✔️ | ✔️ | | | ✔️ | | | | +| show-javascript | | | | ✔️ | | | | | | | ✔️ | | sign | | | | ✔️ | | | | | | | ✔️ | \ No newline at end of file diff --git a/Version-groups.md b/Version-groups.md index 557cadb2..90020579 100644 --- a/Version-groups.md +++ b/Version-groups.md @@ -15,6 +15,7 @@ Operation | Ultra-Lite | Lite | Full --------------------|------------|------|----- add-page-numbers | ✔️ | ✔️ | ✔️ add-password | ✔️ | ✔️ | ✔️ +add-image | ✔️ | ✔️ | ✔️ add-watermark | ✔️ | ✔️ | ✔️ adjust-contrast | ✔️ | ✔️ | ✔️ auto-split-pdf | ✔️ | ✔️ | ✔️ @@ -24,21 +25,25 @@ crop | ✔️ | ✔️ | ✔️ change-metadata | ✔️ | ✔️ | ✔️ change-permissions | ✔️ | ✔️ | ✔️ compare | ✔️ | ✔️ | ✔️ +extract-page | ✔️ | ✔️ | ✔️ extract-images | ✔️ | ✔️ | ✔️ flatten | ✔️ | ✔️ | ✔️ +get-info-on-pdf | ✔️ | ✔️ | ✔️ img-to-pdf | ✔️ | ✔️ | ✔️ +markdown-to-pdf | ✔️ | ✔️ | ✔️ merge-pdfs | ✔️ | ✔️ | ✔️ multi-page-layout | ✔️ | ✔️ | ✔️ pdf-organizer | ✔️ | ✔️ | ✔️ pdf-to-img | ✔️ | ✔️ | ✔️ +pdf-to-single-page | ✔️ | ✔️ | ✔️ remove-pages | ✔️ | ✔️ | ✔️ remove-password | ✔️ | ✔️ | ✔️ rotate-pdf | ✔️ | ✔️ | ✔️ sanitize-pdf | ✔️ | ✔️ | ✔️ scale-pages | ✔️ | ✔️ | ✔️ sign | ✔️ | ✔️ | ✔️ +show-javascript | ✔️ | ✔️ | ✔️ split-pdfs | ✔️ | ✔️ | ✔️ -add-image | ✔️ | ✔️ | ✔️ file-to-pdf | | ✔️ | ✔️ pdf-to-html | | ✔️ | ✔️ pdf-to-presentation | | ✔️ | ✔️ diff --git a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 322de0e2..11f7b8c8 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -71,6 +71,8 @@ public class EndpointConfiguration { addEndpointToGroup("PageOps", "adjust-contrast"); addEndpointToGroup("PageOps", "crop"); addEndpointToGroup("PageOps", "auto-split-pdf"); + addEndpointToGroup("PageOps", "extract-page"); + addEndpointToGroup("PageOps", "pdf-to-single-page"); // Adding endpoints to "Convert" group addEndpointToGroup("Convert", "pdf-to-img"); @@ -85,6 +87,7 @@ public class EndpointConfiguration { addEndpointToGroup("Convert", "pdf-to-xml"); addEndpointToGroup("Convert", "html-to-pdf"); addEndpointToGroup("Convert", "url-to-pdf"); + addEndpointToGroup("Convert", "markdown-to-pdf"); // Adding endpoints to "Security" group addEndpointToGroup("Security", "add-password"); @@ -94,7 +97,7 @@ public class EndpointConfiguration { addEndpointToGroup("Security", "cert-sign"); addEndpointToGroup("Security", "sanitize-pdf"); - + // Adding endpoints to "Other" group addEndpointToGroup("Other", "ocr-pdf"); addEndpointToGroup("Other", "add-image"); @@ -109,7 +112,8 @@ public class EndpointConfiguration { addEndpointToGroup("Other", "compare"); addEndpointToGroup("Other", "add-page-numbers"); addEndpointToGroup("Other", "auto-rename"); - + addEndpointToGroup("Other", "get-info-on-pdf"); + addEndpointToGroup("Other", "show-javascript"); @@ -180,6 +184,11 @@ public class EndpointConfiguration { addEndpointToGroup("Java", "auto-split-pdf"); addEndpointToGroup("Java", "sanitize-pdf"); addEndpointToGroup("Java", "crop"); + addEndpointToGroup("Java", "get-info-on-pdf"); + addEndpointToGroup("Java", "extract-page"); + addEndpointToGroup("Java", "pdf-to-single-page"); + addEndpointToGroup("Java", "markdown-to-pdf"); + addEndpointToGroup("Java", "show-javascript"); //Javascript addEndpointToGroup("Javascript", "pdf-organizer"); From 891f9e225245110b965bf4986dce63f997f124c6 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Tue, 8 Aug 2023 19:55:18 +0100 Subject: [PATCH 21/21] api changes --- README.md | 1 + build.gradle | 2 +- .../controller/web/MetricsController.java | 212 +++++++++++++++--- 3 files changed, 188 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index f88f9873..9b7365f3 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ Using the same method you can also change - Disable and remove endpoints and functionality from Stirling-PDF. Currently the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma seperated lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image to pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Frooodle/Stirling-PDF/blob/main/groups.md) - Change the max file size allowed through the server with the environment variable MAX_FILE_SIZE. default 2000MB - Customise static files such as app logo by placing files in the /customFiles/static/ directory. Example to customise app logo is placing a /customFiles/static/favicon.svg to override current SVG. This can be used to change any images/icons/css/fonts/js etc in Stirling-PDF +- Enable/Disable metric api endpoints with ENABLE_API_METRICS. Default enabled ## API For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation diff --git a/build.gradle b/build.gradle index 832cbc2d..dcc53c3e 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'stirling.software' -version = '0.12.1' +version = '0.12.2' sourceCompatibility = '17' repositories { diff --git a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java index 70235df3..ab18b1b5 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/MetricsController.java @@ -1,8 +1,16 @@ package stirling.software.SPDF.controller.web; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -14,6 +22,8 @@ import io.micrometer.core.instrument.MeterRegistry; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import stirling.software.SPDF.config.StartupApplicationListener; @RestController @RequestMapping("/api/v1") @@ -22,6 +32,20 @@ public class MetricsController { private final MeterRegistry meterRegistry; + private boolean isEndpointEnabled; + + @PostConstruct + public void init() { + String isEndpointEnabled = System.getProperty("ENABLE_API_METRICS"); + if (isEndpointEnabled == null) { + isEndpointEnabled = System.getenv("ENABLE_API_METRICS"); + if (isEndpointEnabled == null) { + isEndpointEnabled = "true"; + } + } + this.isEndpointEnabled = "true".equalsIgnoreCase(isEndpointEnabled); + } + public MetricsController(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @@ -29,18 +53,25 @@ public class MetricsController { @GetMapping("/status") @Operation(summary = "Application status and version", description = "This endpoint returns the status of the application and its version number.") - public Map getStatus() { + public ResponseEntity getStatus() { + if (!isEndpointEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + Map status = new HashMap<>(); status.put("status", "UP"); status.put("version", getClass().getPackage().getImplementationVersion()); - return status; + return ResponseEntity.ok(status); } @GetMapping("/loads") @Operation(summary = "GET request count", description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.") - public Double getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { - try { + public ResponseEntity getPageLoads(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { + if (!isEndpointEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + try { double count = 0.0; @@ -68,36 +99,165 @@ public class MetricsController { } } - return count; + return ResponseEntity.ok(count); } catch (Exception e) { - return -1.0; + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } + @GetMapping("/loads/all") + @Operation(summary = "GET requests count for all endpoints", + description = "This endpoint returns the count of GET requests for each endpoint.") + public ResponseEntity getAllEndpointLoads() { + if (!isEndpointEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + try { + Map counts = new HashMap<>(); + + for (Meter meter : meterRegistry.getMeters()) { + if (meter.getId().getName().equals("http.requests")) { + String method = meter.getId().getTag("method"); + if (method != null && method.equals("GET")) { + String uri = meter.getId().getTag("uri"); + if (uri != null) { + double currentCount = counts.getOrDefault(uri, 0.0); + if (meter instanceof Counter) { + currentCount += ((Counter) meter).count(); + } + counts.put(uri, currentCount); + } + } + } + } + + List results = counts.entrySet().stream() + .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) + .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) + .collect(Collectors.toList()); + + return ResponseEntity.ok(results); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + public class EndpointCount { + private String endpoint; + private double count; + + public EndpointCount(String endpoint, double count) { + this.endpoint = endpoint; + this.count = count; + } + public String getEndpoint() { + return endpoint; + } + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + public double getCount() { + return count; + } + public void setCount(double count) { + this.count = count; + } + + } + + @GetMapping("/requests") @Operation(summary = "POST request count", description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.") - public Double getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { - try { - Counter counter; - if (endpoint.isPresent() && !endpoint.get().isBlank()) { - if(!endpoint.get().startsWith("/")) { - endpoint = Optional.of("/" + endpoint.get()); - } - - System.out.println("loads " + endpoint.get() + " vs " + meterRegistry.get("http.requests").tags("uri", endpoint.get()).toString()); - counter = meterRegistry.get("http.requests") - .tags("method", "POST", "uri", endpoint.get()).counter(); - } else { - counter = meterRegistry.get("http.requests") - .tags("method", "POST").counter(); - } - return counter.count(); - } catch (Exception e) { - e.printStackTrace(); - return 0.0; + public ResponseEntity getTotalRequests(@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint") Optional endpoint) { + if (!isEndpointEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + try { + double count = 0.0; + + for (Meter meter : meterRegistry.getMeters()) { + if (meter.getId().getName().equals("http.requests")) { + String method = meter.getId().getTag("method"); + if (method != null && method.equals("POST")) { + if (endpoint.isPresent() && !endpoint.get().isBlank()) { + if (!endpoint.get().startsWith("/")) { + endpoint = Optional.of("/" + endpoint.get()); + } + if (endpoint.get().equals(meter.getId().getTag("uri"))) { + if (meter instanceof Counter) { + count += ((Counter) meter).count(); + } + } + } else { + if (meter instanceof Counter) { + count += ((Counter) meter).count(); + } + } + } + } + } + return ResponseEntity.ok(count); + } catch (Exception e) { + return ResponseEntity.ok(-1); } - } + + @GetMapping("/requests/all") + @Operation(summary = "POST requests count for all endpoints", + description = "This endpoint returns the count of POST requests for each endpoint.") + public ResponseEntity getAllPostRequests() { + if (!isEndpointEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + try { + Map counts = new HashMap<>(); + + for (Meter meter : meterRegistry.getMeters()) { + if (meter.getId().getName().equals("http.requests")) { + String method = meter.getId().getTag("method"); + if (method != null && method.equals("POST")) { + String uri = meter.getId().getTag("uri"); + if (uri != null) { + double currentCount = counts.getOrDefault(uri, 0.0); + if (meter instanceof Counter) { + currentCount += ((Counter) meter).count(); + } + counts.put(uri, currentCount); + } + } + } + } + + List results = counts.entrySet().stream() + .map(entry -> new EndpointCount(entry.getKey(), entry.getValue())) + .sorted(Comparator.comparing(EndpointCount::getCount).reversed()) + .collect(Collectors.toList()); + + return ResponseEntity.ok(results); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + + @GetMapping("/uptime") + public ResponseEntity getUptime() { + if (!isEndpointEnabled) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled."); + } + + LocalDateTime now = LocalDateTime.now(); + Duration uptime = Duration.between(StartupApplicationListener.startTime, now); + return ResponseEntity.ok(formatDuration(uptime)); + } + + private String formatDuration(Duration duration) { + long days = duration.toDays(); + long hours = duration.toHoursPart(); + long minutes = duration.toMinutesPart(); + long seconds = duration.toSecondsPart(); + return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds); + } }