From 3d7eb040abb48bb06d316c993eb10fdbafd9a4f0 Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 17 Feb 2025 21:26:18 +0100 Subject: [PATCH 01/18] Fix: Replace pull_request with pull_request_target in SonarQube Workflow for Fork Analysis (#2977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes Please provide a summary of the changes, including: This update changes the workflow trigger for SonarQube from using the `pull_request` event to `pull_request_target` for the "main" branch. By doing so, the workflow runs in the context of the base repository, ensuring that the required secrets (like `SONAR_TOKEN`) are available during execution—even when analyzing code from forked repositories. This change enables full Sonar analysis for PRs from forks while being mindful of potential security implications. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --- .github/workflows/sonarqube.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index c06707ac..a2b4ccbc 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -1,21 +1,22 @@ +name: Run Sonarqube + on: push: branches: - master - pull_request: - branches: [ "main" ] + pull_request_target: + branches: + - main workflow_dispatch: permissions: pull-requests: read actions: read -name: Run Sonarqube + jobs: sonarqube: runs-on: ubuntu-latest steps: - - - name: Harden Runner uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 with: From 27fc5e9a9edd068e354b0b3a30392362308eba32 Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 17 Feb 2025 22:16:51 +0100 Subject: [PATCH 02/18] Enable Java Formatting - Use `build` code formatting in VS Code IDE (#2978) # Description of Changes Please provide a summary of the changes, including: This PR updates the VSCode settings by making the following changes: - Changed the value of `editor.wordSegmenterLocales` from `null` to an empty string (`""`). This adjustment helps to avoid potential issues with locale detection in the editor. - Added new Java formatting settings: - Enabled Java formatting with `"java.format.enabled": true` - Set the formatting profile to `"GoogleStyle"` - Specified the Google Java formatting version as `"1.25.2"` These changes improve consistency and enforce coding standards in the development environment by aligning Java code formatting with the Google style guide. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --- .vscode/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c8ef9888..b7ac1b3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,7 +40,7 @@ "files.insertFinalNewline": false }, "diffEditor.maxComputationTime": 0, - "editor.wordSegmenterLocales": null, + "editor.wordSegmenterLocales": "", "editor.guides.bracketPairs": "active", "editor.guides.bracketPairsHorizontal": "active", "files.insertFinalNewline": true, @@ -50,6 +50,9 @@ "editor.stickyScroll.enabled": false, "editor.minimap.enabled": false, "editor.formatOnSave": true, + "java.format.enabled": true, + "java.format.settings.profile": "GoogleStyle", + "java.format.settings.google.version": "1.25.2", "java.format.settings.google.mode": "jar-file", "java.format.settings.google.extra": "--aosp --skip-sorting-imports" } From 4b40a0460e6a95053352856aea18de788b0f31ef Mon Sep 17 00:00:00 2001 From: bendem Date: Tue, 18 Feb 2025 01:05:26 +0100 Subject: [PATCH 03/18] =?UTF-8?q?Unify=20translations=20for=20"r=C3=A9dige?= =?UTF-8?q?r"=20and=20"caviarder"=20with=20"censurer"=20(#2972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Rédiger" is a literal translation, it doesn't have that meaning in french. "Caviarder" is not that common. "Censurer", imo will be more widely understood by french speakers. I left the tags so it's still easy to find, not matter which term you use in search. # Description of Changes Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --- src/main/resources/messages_fr_FR.properties | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/resources/messages_fr_FR.properties b/src/main/resources/messages_fr_FR.properties index e935e06f..1fca29ab 100644 --- a/src/main/resources/messages_fr_FR.properties +++ b/src/main/resources/messages_fr_FR.properties @@ -485,13 +485,13 @@ home.showJS.title=Afficher le JavaScript home.showJS.desc=Recherche et affiche tout JavaScript injecté dans un PDF. showJS.tags=JS -home.autoRedact.title=Caviarder automatiquement -home.autoRedact.desc=Caviardez automatiquement les informations sensibles d'un PDF. -autoRedact.tags=caviarder,redact,auto +home.autoRedact.title=Censure automatique +home.autoRedact.desc=Censurer automatiquement les informations sensibles d'un PDF. +autoRedact.tags=caviarder,rédiger,censurer,redact,auto -home.redact.title=Rédaction manuelle -home.redact.desc=Rédiger un PDF en fonction de texte sélectionné, formes dessinées et/ou des pages sélectionnées. -redact.tags=Redact,Hide,black out,black,marker,hidden,manual +home.redact.title=Censure manuelle +home.redact.desc=Censurer un PDF en fonction de texte sélectionné, formes dessinées et/ou des pages sélectionnées. +redact.tags=Redact,Hide,black out,black,marker,hidden,manual,caviarder,rédiger,censurer home.tableExtraxt.title=PDF en CSV home.tableExtraxt.desc=Extrait les tableaux d'un PDF et les transforme en CSV. From 68e8a0174c0e299cfd0e97939b40428fdc4d294c Mon Sep 17 00:00:00 2001 From: Wn0304 <86523415+Null-wn@users.noreply.github.com> Date: Tue, 18 Feb 2025 08:08:05 +0800 Subject: [PATCH 04/18] Translation localization (#2969) # Description of Changes Please provide a summary of the changes, including: - What was changed messages_zh_CN.properties - Why the change was made Try to translate the rest of the Chinese information - Any challenges encountered No Closes #(issue_number) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> --- src/main/resources/messages_zh_CN.properties | 208 +++++++++---------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/src/main/resources/messages_zh_CN.properties b/src/main/resources/messages_zh_CN.properties index 8d700596..a900dcb7 100644 --- a/src/main/resources/messages_zh_CN.properties +++ b/src/main/resources/messages_zh_CN.properties @@ -76,11 +76,11 @@ donate=捐款 color=颜色 sponsor=赞助 info=信息 -pro=Pro -page=Page -pages=Pages +pro=专业版 +page=页面 +pages=页码 loading=加载中... -addToDoc=Add to Document +addToDoc=添加至文件 reset=重置 apply=应用 @@ -120,19 +120,19 @@ pipelineOptions.validateButton=验证 ######################## enterpriseEdition.button=升级到 Pro 版本 enterpriseEdition.warning=此功能仅适用于 Pro 版本 -enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features. -enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro +enterpriseEdition.yamlAdvert=Stirling PDF Pro支持YAML配置文件和其他SSO功能。 +enterpriseEdition.ssoAdvert=寻找更多的用户管理功能?查看Stirling PDF Pro ################# # Analytics # ################# -analytics.title=Do you want make Stirling PDF better? -analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents. -analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better. -analytics.enable=Enable analytics -analytics.disable=Disable analytics -analytics.settings=You can change the settings for analytics in the config/settings.yml file +analytics.title=你想协助改善Stirling PDF吗 +analytics.paragraph1=Stirling PDF有选择性分析功能,可以帮助我们改进产品。我们不跟踪任何个人信息或文件内容。 +analytics.paragraph2=请考虑启用分析来帮助Stirling-PDF的发展,并让我们更好地了解我们的用户。 +analytics.enable=启用分析功能 +analytics.disable=禁用分析功能 +analytics.settings=您可以在 config/settings.yml 文件中变更分析功能的设定 ############# # NAVBAR # @@ -240,17 +240,17 @@ database.creationDate=创建时间 database.fileSize=文件大小 database.deleteBackupFile=删除备份文件 database.importBackupFile=导入备份文件 -database.createBackupFile=Create Backup File +database.createBackupFile=创建备份文件 database.downloadBackupFile=下载备份文件 database.info_1=导入数据时,确保结构正确至关重要。如果您不确定自己在做什么,请寻求专业人士的建议和支持。结构错误会导致应用程序故障,甚至完全无法运行应用程序。 database.info_2=上传文件时,文件名并不重要。上传后,文件名将重命名为 backup_user_yyyyMMddHHmm.sql,以确保命名规范的一致性。 database.submit=导入备份 database.importIntoDatabaseSuccessed=导入数据库成功 -database.backupCreated=Database backup successful +database.backupCreated=数据库备份成功 database.fileNotFound=未找到文件 database.fileNullOrEmpty=文件不能为空 database.failedImportFile=导入文件失败 -database.notSupported=This function is not available for your database connection. +database.notSupported=此功能不适用于您的数据库连接。 session.expired=您的会话已过期。请刷新页面并重试。 session.refreshPage=刷新页面 @@ -267,8 +267,8 @@ home.viewPdf.desc=浏览、注释、添加文本或图像 viewPdf.tags=浏览、阅读、注释、文本、图像 home.setFavorites=编辑收藏夹 -home.hideFavorites=Hide Favourites -home.showFavorites=Show Favourites +home.hideFavorites=隐藏收藏夹 +home.showFavorites=显示收藏夹 home.legacyHomepage=使用旧版主页 home.newHomePage=试用新版主页! home.alphabetical=按字母顺序 @@ -462,9 +462,9 @@ home.MarkdownToPDF.title=Markdown 转 PDF home.MarkdownToPDF.desc=将任何 Markdown 文件转换为 PDF MarkdownToPDF.tags=标记、网页内容、转换、转换 -home.PDFToMarkdown.title=PDF to Markdown -home.PDFToMarkdown.desc=Converts any PDF to Markdown -PDFToMarkdown.tags=markup,web-content,transformation,convert,md +home.PDFToMarkdown.title=PDF 转 Markdown +home.PDFToMarkdown.desc=将任何pdf文件转换为Markdown文件 +PDFToMarkdown.tags=标记,网页内容,转换,转档,md home.getPdfInfo.title=获取 PDF 的所有信息 home.getPdfInfo.desc=获取 PDF 的所有可能的信息 @@ -489,9 +489,9 @@ home.autoRedact.title=自动删除 home.autoRedact.desc=根据输入文本自动删除(覆盖)PDF 中的文本 autoRedact.tags=脱敏、隐藏、涂黑、标记、不可见 -home.redact.title=Manual Redaction -home.redact.desc=Redacts a PDF based on selected text, drawn shapes and/or selected page(s) -redact.tags=Redact,Hide,black out,black,marker,hidden,manual +home.redact.title=手动修订 +home.redact.desc=根据选定的文本、绘制的形状和/或选定的页面编辑PDF +redact.tags=涂改,隐藏,涂黑,黑色,标记,遮蔽,手动 home.tableExtraxt.title=PDF 转 CSV home.tableExtraxt.desc=从 PDF 中提取表格并将其转换为 CSV @@ -542,19 +542,19 @@ replace-color.title=替换-反转-颜色 replace-color.header=替换-反转 PDF 颜色 home.replaceColorPdf.title=替换和反转颜色 home.replaceColorPdf.desc=替换 PDF 中文本和背景的颜色,并将PDF全色反转以减小文件大小 -replaceColorPdf.tags=Replace Color,Page operations,Back end,server side +replaceColorPdf.tags=更换颜色,页面操作,后端,服务器端 replace-color.selectText.1=替换或反转颜色选项 -replace-color.selectText.2=Default(Default high contrast colors) -replace-color.selectText.3=Custom(Customized colors) -replace-color.selectText.4=Full-Invert(Invert all colors) -replace-color.selectText.5=High contrast color options -replace-color.selectText.6=white text on black background -replace-color.selectText.7=Black text on white background -replace-color.selectText.8=Yellow text on black background -replace-color.selectText.9=Green text on black background -replace-color.selectText.10=Choose text Color -replace-color.selectText.11=Choose background Color -replace-color.submit=Replace +replace-color.selectText.2=默认(默认高对比度颜色) +replace-color.selectText.3=定制(定制的颜色) +replace-color.selectText.4=全反转(反转所有颜色) +replace-color.selectText.5=高对比度颜色选项 +replace-color.selectText.6=黑底白字 +replace-color.selectText.7=白底黑字 +replace-color.selectText.8=黑底黄字 +replace-color.selectText.9=黑底绿字 +replace-color.selectText.10=选择文本颜色 +replace-color.selectText.11=选择背景颜色 +replace-color.submit=取代 @@ -599,31 +599,31 @@ autoRedact.convertPDFToImageLabel=将PDF转换为PDF-Image(用于删除方框 autoRedact.submitButton=提交 #redact -redact.title=Manual Redaction -redact.header=Manual Redaction -redact.submit=Redact -redact.textBasedRedaction=Text based Redaction -redact.pageBasedRedaction=Page-based Redaction -redact.convertPDFToImageLabel=Convert PDF to PDF-Image (Used to remove text behind the box) -redact.pageRedactionNumbers.title=Pages -redact.pageRedactionNumbers.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1) -redact.redactionColor.title=Redaction Color -redact.export=Export -redact.upload=Upload -redact.boxRedaction=Box draw redaction -redact.zoom=Zoom -redact.zoomIn=Zoom in -redact.zoomOut=Zoom out -redact.nextPage=Next Page -redact.previousPage=Previous Page -redact.toggleSidebar=Toggle Sidebar -redact.showThumbnails=Show Thumbnails -redact.showDocumentOutline=Show Document Outline (double-click to expand/collapse all items) -redact.showAttatchments=Show Attachments -redact.showLayers=Show Layers (double-click to reset all layers to the default state) -redact.colourPicker=Colour Picker -redact.findCurrentOutlineItem=Find current outline item -redact.applyChanges=Apply Changes +redact.title=手动纠正 +redact.header=手动纠正 +redact.submit=纠正 +redact.textBasedRedaction=基于文本的纠正 +redact.pageBasedRedaction=基于页面的纠正 +redact.convertPDFToImageLabel=将PDF转换为PDF图像(用于删除框后的文本) +redact.pageRedactionNumbers.title=页面 +redact.pageRedactionNumbers.placeholder=(例如 1,2,8 或 4,7,12-16 或 2n-1) +redact.redactionColor.title=编辑颜色 +redact.export=导出 +redact.upload=上传 +redact.boxRedaction=框选区域涂黑 +redact.zoom=缩放 +redact.zoomIn=放大 +redact.zoomOut=缩小 +redact.nextPage=下一页 +redact.previousPage=上一页 +redact.toggleSidebar=切换侧边栏 +redact.showThumbnails=显示缩略图 +redact.showDocumentOutline=显示文档大纲(双击展开/折叠所有项目) +redact.showAttatchments=显示附件 +redact.showLayers=显示图层(双击将所有图层重置为默认状态) +redact.colourPicker=颜色选择器 +redact.findCurrentOutlineItem=查找当前大纲项目 +redact.applyChanges=应用 #showJS showJS.title=显示 JavaScript @@ -661,9 +661,9 @@ MarkdownToPDF.credit=此服务使用 WeasyPrint 进行文件转换。 #pdf-to-markdown -PDFToMarkdown.title=PDF To Markdown -PDFToMarkdown.header=PDF To Markdown -PDFToMarkdown.submit=Convert +PDFToMarkdown.title=PDF转Markdown +PDFToMarkdown.header=PDF转Markdown +PDFToMarkdown.submit=转换 #url-to-pdf @@ -872,7 +872,7 @@ sign.add=添加 sign.saved=已保存签名 sign.save=保存签名 sign.personalSigs=个人签名 -sign.sharedSigs=Shared Signatures +sign.sharedSigs=共享签名 sign.noSavedSigs=未找到已保存的签名 sign.addToAll=添加到所有页面 sign.delete=删除 @@ -880,7 +880,7 @@ sign.first=首页 sign.last=末页 sign.next=下一页 sign.previous=上一页 -sign.maintainRatio=Toggle maintain aspect ratio +sign.maintainRatio=切换保持长宽比 #repair @@ -1328,18 +1328,18 @@ splitByChapters.header=按章节拆分 PDF splitByChapters.bookmarkLevel=书签级别 splitByChapters.includeMetadata=包含元数据 splitByChapters.allowDuplicates=允许重复 -splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure. -splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.). -splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF. -splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs. +splitByChapters.desc.1=此工具根据章节结构将PDF文件拆分为多个PDF。 +splitByChapters.desc.2=书签级别:选择用于拆分的书签级别(0表示顶级,1表示二级等)。 +splitByChapters.desc.3=包含元数据:如果选中,原始PDF的元数据将包含在每个拆分的PDF中。 +splitByChapters.desc.4=允许重复:如果选中,允许同一页面上的多个书签创建单独的PDF。 splitByChapters.submit=拆分 PDF #File Chooser fileChooser.click=单击 fileChooser.or=或 fileChooser.dragAndDrop=拖放文件 -fileChooser.dragAndDropPDF=Drag & Drop PDF file -fileChooser.dragAndDropImage=Drag & Drop Image file +fileChooser.dragAndDropPDF=拖放PDF文件 +fileChooser.dragAndDropImage=拖放图片文件 fileChooser.hoveredDragAndDrop=拖放文件到此处 #release notes @@ -1350,36 +1350,36 @@ releases.current.version=当前版本 releases.note=版本说明仅提供英文版本 #Validate Signature -validateSignature.title=Validate PDF Signatures -validateSignature.header=Validate Digital Signatures -validateSignature.selectPDF=Select signed PDF file -validateSignature.submit=Validate Signatures -validateSignature.results=Validation Results -validateSignature.status=Status -validateSignature.signer=Signer -validateSignature.date=Date -validateSignature.reason=Reason -validateSignature.location=Location -validateSignature.noSignatures=No digital signatures found in this document -validateSignature.status.valid=Valid -validateSignature.status.invalid=Invalid -validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity -validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified -validateSignature.cert.expired=Certificate has expired -validateSignature.cert.revoked=Certificate has been revoked -validateSignature.signature.info=Signature Information -validateSignature.signature=Signature -validateSignature.signature.mathValid=Signature is mathematically valid BUT: -validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional) -validateSignature.cert.info=Certificate Details -validateSignature.cert.issuer=Issuer -validateSignature.cert.subject=Subject -validateSignature.cert.serialNumber=Serial Number -validateSignature.cert.validFrom=Valid From -validateSignature.cert.validUntil=Valid Until -validateSignature.cert.algorithm=Algorithm -validateSignature.cert.keySize=Key Size -validateSignature.cert.version=Version -validateSignature.cert.keyUsage=Key Usage -validateSignature.cert.selfSigned=Self-Signed -validateSignature.cert.bits=bits +validateSignature.title=验证pdf签名 +validateSignature.header=验证数字签名 +validateSignature.selectPDF=选择已签名的pdf文件 +validateSignature.submit=验证签名 +validateSignature.results=验证结果 +validateSignature.status=状态 +validateSignature.signer=签署者 +validateSignature.date=日期 +validateSignature.reason=原因 +validateSignature.location=位置 +validateSignature.noSignatures=此文件中未找到电子签名 +validateSignature.status.valid=有效 +validateSignature.status.invalid=无效 +validateSignature.chain.invalid=证书链验证失败 - 无法验证签名者的身份 +validateSignature.trust.invalid=证书不在信任存储区中 - 无法验证来源 +validateSignature.cert.expired=凭证已过期 +validateSignature.cert.revoked=凭证已被撤销 +validateSignature.signature.info=签名信息 +validateSignature.signature=签名 +validateSignature.signature.mathValid=签名在数学上有效,但: +validateSignature.selectCustomCert=X.509 自签名证书(可选) +validateSignature.cert.info=凭证信息 +validateSignature.cert.issuer=发行者 +validateSignature.cert.subject=主题 +validateSignature.cert.serialNumber=序列号 +validateSignature.cert.validFrom=有效期自 +validateSignature.cert.validUntil=有效期至 +validateSignature.cert.algorithm=算法 +validateSignature.cert.keySize=密钥长度 +validateSignature.cert.version=版本 +validateSignature.cert.keyUsage=密钥用途 +validateSignature.cert.selfSigned=自签名 +validateSignature.cert.bits=比特 From d34c44ed7bbf60d85f96744f99cd38a38911daa3 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:57:56 +0000 Subject: [PATCH 05/18] [Test PR] Desktop fix and unoconv to unoserver (#2971) # Description of Changes This pull request includes several updates to the Docker configuration and Java application UI scaling. The changes enhance environment variable management, dependency installation, and UI responsiveness to different screen sizes. ### Docker Configuration Updates: * Added new environment variables `STIRLING_PDF_DESKTOP_UI`, `PYTHONPATH`, `UNO_PATH`, and `URE_BOOTSTRAP` to `Dockerfile` and `Dockerfile.fat` to improve the configuration and integration of the LibreOffice environment. [[1]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557L38-R46) [[2]](diffhunk://#diff-571631582b988e88c52c86960cc083b0b8fa63cf88f056f26e9e684195221c27L40-R49) * Updated the `CMD` instruction in `Dockerfile` and `Dockerfile.fat` to run both the Java application and `unoserver` simultaneously. [[1]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557L87-R96) [[2]](diffhunk://#diff-571631582b988e88c52c86960cc083b0b8fa63cf88f056f26e9e684195221c27L87-R100) * Modified the `RUN` instruction to include additional Python dependencies and setup a virtual environment. [[1]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557L68-R81) [[2]](diffhunk://#diff-571631582b988e88c52c86960cc083b0b8fa63cf88f056f26e9e684195221c27R72-R86) ### Workflow Enhancements: * Added `STIRLING_PDF_DESKTOP_UI` environment variable to the GitHub Actions workflows (`PR-Demo-Comment.yml` and `push-docker.yml`) to ensure consistent environment settings. [[1]](diffhunk://#diff-145fe5c0ed8c24e4673c9ad39800dd171a2d0a2e8050497cff980fc7e3a3df0dR106) [[2]](diffhunk://#diff-76056236de05155107f6a660f1e3956059e37338011b8f0e72188afcb9b17b6fR41) ### Java Application UI Scaling: * Introduced `UIScaling` utility to dynamically adjust the size of UI components based on screen resolution in `DesktopBrowser` and `LoadingWindow` classes. [[1]](diffhunk://#diff-dff83b0fe53cba8ee80dc8cee96b9c2bfec612ec1f2c636ebdf22dedb36671e8L218-R219) [[2]](diffhunk://#diff-dff83b0fe53cba8ee80dc8cee96b9c2bfec612ec1f2c636ebdf22dedb36671e8L267-R270) [[3]](diffhunk://#diff-3e287daf297213b698b3c94d6e6ed4aae139d570ba6b115da459d72b5c36c42fL44-R64) [[4]](diffhunk://#diff-3e287daf297213b698b3c94d6e6ed4aae139d570ba6b115da459d72b5c36c42fL86-R102) * Improved the loading of icons by using the `UIScaling` utility for better visual quality. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: pixeebot[bot] <104101892+pixeebot[bot]@users.noreply.github.com> Co-authored-by: a --- .github/workflows/PR-Demo-Comment.yml | 1 + .github/workflows/push-docker.yml | 1 + Dockerfile | 71 ++++--- Dockerfile.fat | 30 ++- build.gradle | 24 ++- .../software/SPDF/UI/impl/DesktopBrowser.java | 7 +- .../software/SPDF/UI/impl/LoadingWindow.java | 195 +++++++++++++++++- .../SPDF/config/ExternalAppDepConfig.java | 8 +- .../api/RearrangePagesPDFController.java | 41 +++- .../converters/ConvertOfficeController.java | 12 +- .../api/converters/ConvertWebsiteToPDF.java | 2 +- .../api/converters/ExtractCSVController.java | 106 +++++++--- .../software/SPDF/model/SortTypes.java | 2 + .../SPDF/model/api/PDFWithPageNums.java | 9 +- .../SPDF/model/api/extract/PDFFilePage.java | 15 -- .../api/general/RearrangePagesRequest.java | 2 + .../model/api/misc/AddPageNumbersRequest.java | 38 +++- .../software/SPDF/utils/FileToPdf.java | 2 +- .../software/SPDF/utils/GeneralUtils.java | 3 +- .../software/SPDF/utils/UIScaling.java | 67 ++++++ src/main/resources/messages_en_GB.properties | 1 + .../resources/static/js/pages/pdf-to-csv.js | 8 +- .../templates/convert/pdf-to-csv.html | 2 +- .../resources/templates/pdf-organizer.html | 3 +- testing/cucumber/exampleFiles/tables.pdf | Bin 0 -> 1755 bytes testing/cucumber/features/external.feature | 15 ++ testing/test.sh | 2 +- 27 files changed, 540 insertions(+), 127 deletions(-) delete mode 100644 src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java create mode 100644 src/main/java/stirling/software/SPDF/utils/UIScaling.java create mode 100644 testing/cucumber/exampleFiles/tables.pdf diff --git a/.github/workflows/PR-Demo-Comment.yml b/.github/workflows/PR-Demo-Comment.yml index c248d657..188eac2a 100644 --- a/.github/workflows/PR-Demo-Comment.yml +++ b/.github/workflows/PR-Demo-Comment.yml @@ -103,6 +103,7 @@ jobs: run: ./gradlew clean build env: DOCKER_ENABLE_SECURITY: false + STIRLING_PDF_DESKTOP_UI: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index a336fcd0..4b603e96 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -38,6 +38,7 @@ jobs: run: ./gradlew clean build env: DOCKER_ENABLE_SECURITY: false + STIRLING_PDF_DESKTOP_UI: false - name: Install cosign if: github.ref == 'refs/heads/master' diff --git a/Dockerfile b/Dockerfile index 45e28df3..ccb8408a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,47 +35,56 @@ ENV DOCKER_ENABLE_SECURITY=false \ HOME=/home/stirlingpdfuser \ PUID=1000 \ PGID=1000 \ - UMASK=022 + UMASK=022 \ + PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \ + UNO_PATH=/usr/lib/libreoffice/program \ + URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc # JDK for app -RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ - echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ +RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ + echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ apk upgrade --no-cache -a && \ apk add --no-cache \ - ca-certificates \ - tzdata \ - tini \ - bash \ - curl \ - qpdf \ - shadow \ - su-exec \ - openssl \ - openssl-dev \ - openjdk21-jre \ -# Doc conversion - gcompat \ - libc6-compat \ - libreoffice \ -# pdftohtml - poppler-utils \ -# OCR MY PDF (unpaper for descew and other advanced features) - tesseract-ocr-data-eng \ -# CV - py3-opencv \ -# python3/pip - python3 \ - py3-pip && \ -# uno unoconv and HTML - pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \ + ca-certificates \ + tzdata \ + tini \ + bash \ + curl \ + qpdf \ + shadow \ + su-exec \ + openssl \ + openssl-dev \ + openjdk21-jre \ + # Doc conversion + gcompat \ + libc6-compat \ + libreoffice \ + # pdftohtml + poppler-utils \ + # OCR MY PDF (unpaper for descew and other advanced features) + tesseract-ocr-data-eng \ + # CV + py3-opencv \ + python3 \ + py3-pip \ + py3-pillow@testing \ + py3-pdf2image@testing && \ + python3 -m venv /opt/venv && \ + export PATH="/opt/venv/bin:$PATH" && \ + pip install --upgrade pip && \ + pip install --no-cache-dir --upgrade unoserver weasyprint && \ + ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \ mv /usr/share/tessdata /usr/share/tessdata-original && \ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ fc-cache -f -v && \ chmod +x /scripts/* && \ chmod +x /scripts/init.sh && \ -# User permissions + # User permissions addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ chown stirlingpdfuser:stirlingpdfgroup /app.jar @@ -84,4 +93,4 @@ EXPOSE 8080/tcp # Set user and run command ENTRYPOINT ["tini", "--", "/scripts/init.sh"] -CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] +CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"] \ No newline at end of file diff --git a/Dockerfile.fat b/Dockerfile.fat index 5ff49aab..c7b3e203 100644 --- a/Dockerfile.fat +++ b/Dockerfile.fat @@ -9,6 +9,7 @@ COPY . . # Build the application with DOCKER_ENABLE_SECURITY=false RUN DOCKER_ENABLE_SECURITY=true \ +STIRLING_PDF_DESKTOP_UI=false \ ./gradlew clean build # Main stage @@ -37,12 +38,15 @@ ENV DOCKER_ENABLE_SECURITY=false \ PGID=1000 \ UMASK=022 \ FAT_DOCKER=true \ - INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false + INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \ + PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \ + UNO_PATH=/usr/lib/libreoffice/program \ + URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc # JDK for app -RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ - echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ +RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \ + echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \ echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \ apk upgrade --no-cache -a && \ apk add --no-cache \ @@ -65,14 +69,21 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et # OCR MY PDF (unpaper for descew and other advanced featues) qpdf \ tesseract-ocr-data-eng \ + font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \ # CV py3-opencv \ -# python3/pip - python3 \ - py3-pip && \ -# uno unoconv and HTML - pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \ + python3 \ + py3-pip \ + py3-pillow@testing \ + py3-pdf2image@testing && \ + python3 -m venv /opt/venv && \ + export PATH="/opt/venv/bin:$PATH" && \ + pip install --upgrade pip && \ + pip install --no-cache-dir --upgrade unoserver weasyprint && \ + ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \ + ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \ mv /usr/share/tessdata /usr/share/tessdata-original && \ mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \ fc-cache -f -v && \ @@ -84,7 +95,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et chown stirlingpdfuser:stirlingpdfgroup /app.jar EXPOSE 8080/tcp - # Set user and run command ENTRYPOINT ["tini", "--", "/scripts/init.sh"] -CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"] +CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 0.0.0.0"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4f0d2d57..182e91f4 100644 --- a/build.gradle +++ b/build.gradle @@ -15,9 +15,8 @@ plugins { import com.github.jk1.license.render.* ext { - springBootVersion = "3.4.1" + springBootVersion = "3.4.2" pdfboxVersion = "3.0.4" - logbackVersion = "1.5.7" imageioVersion = "3.12.0" lombokVersion = "1.18.36" bouncycastleVersion = "1.80" @@ -26,7 +25,7 @@ ext { } group = "stirling.software" -version = "0.41.0" +version = "0.42.0" java { // 17 is lowest but we support and recommend 21 @@ -294,14 +293,27 @@ configurations.all { } dependencies { + //tmp for security bumps + implementation 'ch.qos.logback:logback-core:1.5.15' + implementation 'ch.qos.logback:logback-classic:1.5.15' + + + // Exclude vulnerable BouncyCastle version used in tableau + configurations.all { + exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15on' + exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on' + exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on' + } + if (System.getenv("STIRLING_PDF_DESKTOP_UI") != "false") { + implementation 'org.apache.commons:commons-compress:1.26.0' implementation "me.friwi:jcefmaven:127.3.1" implementation "org.openjfx:javafx-controls:21" implementation "org.openjfx:javafx-swing:21" } //security updates - implementation "org.springframework:spring-webmvc:6.2.2" + implementation "org.springframework:spring-webmvc:6.2.3" implementation("io.github.pixee:java-security-toolkit:1.2.1") @@ -320,8 +332,8 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion" implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion" - implementation "org.springframework.session:spring-session-core:$springBootVersion" - implementation "org.springframework:spring-jdbc:6.2.2" + implementation "org.springframework.session:spring-session-core:3.4.1" + implementation "org.springframework:spring-jdbc:6.2.3" implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5' // Don't upgrade h2database diff --git a/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java b/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java index ae5f76fd..b221d019 100644 --- a/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java +++ b/src/main/java/stirling/software/SPDF/UI/impl/DesktopBrowser.java @@ -41,6 +41,7 @@ import me.friwi.jcefmaven.MavenCefAppHandlerAdapter; import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler; import stirling.software.SPDF.UI.WebBrowser; import stirling.software.SPDF.config.InstallationPathConfig; +import stirling.software.SPDF.utils.UIScaling; @Component @Slf4j @@ -215,7 +216,7 @@ public class DesktopBrowser implements WebBrowser { } }); - frame.setSize(1280, 768); + frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800)); frame.setLocationRelativeTo(null); loadIcon(); @@ -264,7 +265,9 @@ public class DesktopBrowser implements WebBrowser { frame.setOpacity(1.0f); frame.setUndecorated(false); frame.pack(); - frame.setSize(1280, 800); + frame.setSize( + UIScaling.scaleWidth(1280), + UIScaling.scaleHeight(800)); frame.setLocationRelativeTo(null); log.debug("Frame reconfigured"); diff --git a/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java b/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java index d6c0d27a..b4479be3 100644 --- a/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java +++ b/src/main/java/stirling/software/SPDF/UI/impl/LoadingWindow.java @@ -1,12 +1,20 @@ package stirling.software.SPDF.UI.impl; import java.awt.*; +import java.io.BufferedReader; import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; import javax.swing.*; +import io.github.pixee.security.BoundedLineReader; + import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.utils.UIScaling; @Slf4j public class LoadingWindow extends JDialog { @@ -16,6 +24,13 @@ public class LoadingWindow extends JDialog { private final JLabel brandLabel; private long startTime; + private Timer stuckTimer; + private long stuckThreshold = 4000; + private long timeAt90Percent = -1; + private volatile Process explorerProcess; + private static final boolean IS_WINDOWS = + System.getProperty("os.name").toLowerCase().contains("win"); + public LoadingWindow(Frame parent, String initialUrl) { super(parent, "Initializing Stirling-PDF", true); startTime = System.currentTimeMillis(); @@ -41,12 +56,12 @@ public class LoadingWindow extends JDialog { if (is != null) { Image img = ImageIO.read(is); if (img != null) { - Image scaledImg = img.getScaledInstance(48, 48, Image.SCALE_SMOOTH); + Image scaledImg = UIScaling.scaleIcon(img, 48, 48); JLabel iconLabel = new JLabel(new ImageIcon(scaledImg)); iconLabel.setHorizontalAlignment(SwingConstants.CENTER); gbc.gridy = 0; mainPanel.add(iconLabel, gbc); - log.debug("Icon loaded and scaled successfully"); + log.info("Icon loaded and scaled successfully"); } } } @@ -83,7 +98,8 @@ public class LoadingWindow extends JDialog { setUndecorated(false); // Set size and position - setSize(400, 200); + setSize(UIScaling.scaleWidth(400), UIScaling.scaleHeight(200)); + setLocationRelativeTo(parent); setAlwaysOnTop(true); setProgress(0); @@ -94,6 +110,163 @@ public class LoadingWindow extends JDialog { System.currentTimeMillis() - startTime); } + private void checkAndRefreshExplorer() { + if (!IS_WINDOWS) { + return; + } + if (timeAt90Percent == -1) { + timeAt90Percent = System.currentTimeMillis(); + stuckTimer = + new Timer( + 1000, + e -> { + long currentTime = System.currentTimeMillis(); + if (currentTime - timeAt90Percent > stuckThreshold) { + try { + log.debug( + "Attempting Windows explorer refresh due to 90% stuck state"); + String currentDir = System.getProperty("user.dir"); + + // Store current explorer PIDs before we start new one + Set existingPids = new HashSet<>(); + ProcessBuilder listExplorer = + new ProcessBuilder( + "cmd", + "/c", + "wmic", + "process", + "where", + "name='explorer.exe'", + "get", + "ProcessId", + "/format:csv"); + Process process = listExplorer.start(); + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + process.getInputStream())); + String line; + while ((line = + BoundedLineReader.readLine( + reader, 5_000_000)) + != null) { + if (line.matches(".*\\d+.*")) { // Contains numbers + String[] parts = line.trim().split(","); + if (parts.length >= 2) { + existingPids.add( + parts[parts.length - 1].trim()); + } + } + } + process.waitFor(2, TimeUnit.SECONDS); + + // Start new explorer + ProcessBuilder pb = + new ProcessBuilder( + "cmd", + "/c", + "start", + "/min", + "/b", + "explorer.exe", + currentDir); + pb.redirectErrorStream(true); + explorerProcess = pb.start(); + + // Schedule cleanup + Timer cleanupTimer = + new Timer( + 2000, + cleanup -> { + try { + // Find new explorer processes + ProcessBuilder findNewExplorer = + new ProcessBuilder( + "cmd", + "/c", + "wmic", + "process", + "where", + "name='explorer.exe'", + "get", + "ProcessId", + "/format:csv"); + Process newProcess = + findNewExplorer.start(); + BufferedReader newReader = + new BufferedReader( + new InputStreamReader( + newProcess + .getInputStream())); + String newLine; + while ((newLine = + BoundedLineReader + .readLine( + newReader, + 5_000_000)) + != null) { + if (newLine.matches( + ".*\\d+.*")) { + String[] parts = + newLine.trim() + .split(","); + if (parts.length >= 2) { + String pid = + parts[ + parts.length + - 1] + .trim(); + if (!existingPids + .contains( + pid)) { + log.debug( + "Found new explorer.exe with PID: " + + pid); + ProcessBuilder + killProcess = + new ProcessBuilder( + "taskkill", + "/PID", + pid, + "/F"); + killProcess + .redirectErrorStream( + true); + Process killResult = + killProcess + .start(); + killResult.waitFor( + 2, + TimeUnit + .SECONDS); + log.debug( + "Explorer process terminated: " + + pid); + } + } + } + } + newProcess.waitFor( + 2, TimeUnit.SECONDS); + } catch (Exception ex) { + log.error( + "Error cleaning up Windows explorer process", + ex); + } + }); + cleanupTimer.setRepeats(false); + cleanupTimer.start(); + stuckTimer.stop(); + } catch (Exception ex) { + log.error("Error refreshing Windows explorer", ex); + } + } + }); + stuckTimer.setRepeats(true); + stuckTimer.start(); + } + } + public void setProgress(final int progress) { SwingUtilities.invokeLater( () -> { @@ -115,11 +288,23 @@ public class LoadingWindow extends JDialog { // Add thread state logging Thread currentThread = Thread.currentThread(); - log.debug( + log.info( "Current thread state - Name: {}, State: {}, Priority: {}", currentThread.getName(), currentThread.getState(), currentThread.getPriority()); + + if (validProgress >= 90 && validProgress < 95) { + checkAndRefreshExplorer(); + } else { + // Reset the timer if we move past 95% + if (validProgress >= 95) { + if (stuckTimer != null) { + stuckTimer.stop(); + } + timeAt90Percent = -1; + } + } } progressBar.setValue(validProgress); @@ -145,7 +330,7 @@ public class LoadingWindow extends JDialog { statusLabel.setText(validStatus); // Log UI state when status changes - log.debug( + log.info( "UI State - Window visible: {}, Progress: {}%, Status: {}", isVisible(), progressBar.getValue(), validStatus); diff --git a/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java b/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java index 9d1ac1fc..886244ff 100644 --- a/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java +++ b/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java @@ -21,9 +21,9 @@ public class ExternalAppDepConfig { { put("soffice", List.of("LibreOffice")); - put("weasyprint", List.of("Weasyprint")); + put("/opt/venv/bin/weasyprint", List.of("Weasyprint")); put("pdftohtml", List.of("Pdftohtml")); - put("unoconv", List.of("Unoconv")); + put("/opt/venv/bin/unoconvert", List.of("Unoconv")); put("qpdf", List.of("qpdf")); put("tesseract", List.of("tesseract")); } @@ -101,9 +101,9 @@ public class ExternalAppDepConfig { checkDependencyAndDisableGroup("tesseract"); checkDependencyAndDisableGroup("soffice"); checkDependencyAndDisableGroup("qpdf"); - checkDependencyAndDisableGroup("weasyprint"); + checkDependencyAndDisableGroup("/opt/venv/bin/weasyprint"); checkDependencyAndDisableGroup("pdftohtml"); - checkDependencyAndDisableGroup("unoconv"); + checkDependencyAndDisableGroup("/opt/venv/bin/unoconvert"); // Special handling for Python/OpenCV dependencies boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python"); if (!pythonAvailable) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index 77601b5d..35f6cf07 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -174,7 +174,38 @@ public class RearrangePagesPDFController { return newPageOrderZeroBased; } - private List processSortTypes(String sortTypes, int totalPages) { + private List duplicate(int totalPages, String pageOrder) { + List newPageOrder = new ArrayList<>(); + int duplicateCount; + + try { + // Parse the duplicate count from pageOrder + duplicateCount = + pageOrder != null && !pageOrder.isEmpty() + ? Integer.parseInt(pageOrder.trim()) + : 2; // Default to 2 if not specified + } catch (NumberFormatException e) { + log.error("Invalid duplicate count specified", e); + duplicateCount = 2; // Default to 2 if invalid input + } + + // Validate duplicate count + if (duplicateCount < 1) { + duplicateCount = 2; // Default to 2 if invalid input + } + + // For each page in the document + for (int pageNum = 0; pageNum < totalPages; pageNum++) { + // Add the current page index duplicateCount times + for (int dupCount = 0; dupCount < duplicateCount; dupCount++) { + newPageOrder.add(pageNum); + } + } + + return newPageOrder; + } + + private List processSortTypes(String sortTypes, int totalPages, String pageOrder) { try { SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase()); switch (mode) { @@ -196,6 +227,8 @@ public class RearrangePagesPDFController { return removeLast(totalPages); case REMOVE_FIRST_AND_LAST: return removeFirstAndLast(totalPages); + case DUPLICATE: + return duplicate(totalPages, pageOrder); default: throw new IllegalArgumentException("Unsupported custom mode"); } @@ -223,8 +256,10 @@ public class RearrangePagesPDFController { String[] pageOrderArr = pageOrder != null ? pageOrder.split(",") : new String[0]; int totalPages = document.getNumberOfPages(); List newPageOrder; - if (sortType != null && sortType.length() > 0) { - newPageOrder = processSortTypes(sortType, totalPages); + if (sortType != null + && sortType.length() > 0 + && !"custom".equals(sortType.toLowerCase())) { + newPageOrder = processSortTypes(sortType, totalPages, pageOrder); } else { newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false); } 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 50a251e4..b7bb699c 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 @@ -61,13 +61,13 @@ public class ConvertOfficeController { List command = new ArrayList<>( Arrays.asList( - "unoconv", - "-vvv", - "-f", + "/opt/venv/bin/unoconvert", + "--port", + "2003", + "--convert-to", "pdf", - "-o", - tempOutputFile.toString(), - tempInputFile.toString())); + tempInputFile.toString(), + tempOutputFile.toString())); ProcessExecutorResult returnCode = ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) .runCommandWithOutputHandling(command); 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 d6ae1a47..dccc4dff 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 @@ -65,7 +65,7 @@ public class ConvertWebsiteToPDF { // Prepare the WeasyPrint command List command = new ArrayList<>(); - command.add("weasyprint"); + command.add("/opt/venv/bin/weasyprint"); command.add(URL); command.add(tempOutputFile.toString()); diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractCSVController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractCSVController.java index f0a4c267..7f8e7087 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractCSVController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ExtractCSVController.java @@ -1,7 +1,14 @@ package stirling.software.SPDF.controller.api.converters; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.QuoteMode; @@ -18,18 +25,18 @@ import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; - -import stirling.software.SPDF.model.api.extract.PDFFilePage; +import lombok.extern.slf4j.Slf4j; +import stirling.software.SPDF.model.api.PDFWithPageNums; import stirling.software.SPDF.pdf.FlexibleCSVWriter; import technology.tabula.ObjectExtractor; import technology.tabula.Page; import technology.tabula.Table; import technology.tabula.extractors.SpreadsheetExtractionAlgorithm; -import technology.tabula.writers.Writer; @RestController @RequestMapping("/api/v1/convert") @Tag(name = "Convert", description = "Convert APIs") +@Slf4j public class ExtractCSVController { @PostMapping(value = "/pdf/csv", consumes = "multipart/form-data") @@ -37,31 +44,80 @@ public class ExtractCSVController { summary = "Extracts a CSV document from a PDF", description = "This operation takes an input PDF file and returns CSV file of whole page. Input:PDF Output:CSV Type:SISO") - public ResponseEntity PdfToCsv(@ModelAttribute PDFFilePage form) throws Exception { - StringWriter writer = new StringWriter(); + public ResponseEntity pdfToCsv(@ModelAttribute PDFWithPageNums form) throws Exception { + String baseName = getBaseName(form.getFileInput().getOriginalFilename()); + List csvEntries = new ArrayList<>(); + try (PDDocument document = Loader.loadPDF(form.getFileInput().getBytes())) { - CSVFormat format = - CSVFormat.EXCEL.builder().setEscape('"').setQuoteMode(QuoteMode.ALL).build(); - Writer csvWriter = new FlexibleCSVWriter(format); + List pages = form.getPageNumbersList(document, true); SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm(); - try (ObjectExtractor extractor = new ObjectExtractor(document)) { - Page page = extractor.extract(form.getPageId()); - List tables = sea.extract(page); - csvWriter.write(writer, tables); + CSVFormat format = CSVFormat.EXCEL.builder() + .setEscape('"') + .setQuoteMode(QuoteMode.ALL) + .build(); + + for (int pageNum : pages) { + try (ObjectExtractor extractor = new ObjectExtractor(document)) { + log.info("{}",pageNum); + Page page = extractor.extract(pageNum); + List
tables = sea.extract(page); + + for (int i = 0; i < tables.size(); i++) { + StringWriter sw = new StringWriter(); + FlexibleCSVWriter csvWriter = new FlexibleCSVWriter(format); + csvWriter.write(sw, Collections.singletonList(tables.get(i))); + + String entryName = generateEntryName(baseName, pageNum, i + 1); + csvEntries.add(new CsvEntry(entryName, sw.toString())); + } + } + } + + if (csvEntries.isEmpty()) { + return ResponseEntity.noContent().build(); + } else if (csvEntries.size() == 1) { + return createCsvResponse(csvEntries.get(0), baseName); + } else { + return createZipResponse(csvEntries, baseName); } } - - HttpHeaders headers = new HttpHeaders(); - headers.setContentDisposition( - ContentDisposition.builder("attachment") - .filename( - form.getFileInput() - .getOriginalFilename() - .replaceFirst("[.][^.]+$", "") - + "_extracted.csv") - .build()); - headers.setContentType(MediaType.parseMediaType("text/csv")); - - return ResponseEntity.ok().headers(headers).body(writer.toString()); } + + private ResponseEntity createZipResponse(List entries, String baseName) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ZipOutputStream zipOut = new ZipOutputStream(baos)) { + for (CsvEntry entry : entries) { + ZipEntry zipEntry = new ZipEntry(entry.filename()); + zipOut.putNextEntry(zipEntry); + zipOut.write(entry.content().getBytes(StandardCharsets.UTF_8)); + zipOut.closeEntry(); + } + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentDisposition(ContentDisposition.builder("attachment") + .filename(baseName + "_extracted.zip").build()); + headers.setContentType(MediaType.parseMediaType("application/zip")); + + return ResponseEntity.ok().headers(headers).body(baos.toByteArray()); + } + + private ResponseEntity createCsvResponse(CsvEntry entry, String baseName) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentDisposition(ContentDisposition.builder("attachment") + .filename(baseName + "_extracted.csv").build()); + headers.setContentType(MediaType.parseMediaType("text/csv")); + + return ResponseEntity.ok().headers(headers).body(entry.content()); + } + + private String generateEntryName(String baseName, int pageNum, int tableIndex) { + return String.format("%s_p%d_t%d.csv", baseName, pageNum, tableIndex); + } + + private String getBaseName(String filename) { + return filename.replaceFirst("[.][^.]+$", ""); + } + + private record CsvEntry(String filename, String content) {} } diff --git a/src/main/java/stirling/software/SPDF/model/SortTypes.java b/src/main/java/stirling/software/SPDF/model/SortTypes.java index a7a699b8..14d12b5a 100644 --- a/src/main/java/stirling/software/SPDF/model/SortTypes.java +++ b/src/main/java/stirling/software/SPDF/model/SortTypes.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.model; public enum SortTypes { + CUSTOM, REVERSE_ORDER, DUPLEX_SORT, BOOKLET_SORT, @@ -10,4 +11,5 @@ public enum SortTypes { REMOVE_FIRST, REMOVE_LAST, REMOVE_FIRST_AND_LAST, + DUPLICATE } diff --git a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java index 4eaabe87..1e7ae22a 100644 --- a/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java +++ b/src/main/java/stirling/software/SPDF/model/api/PDFWithPageNums.java @@ -8,6 +8,7 @@ import org.apache.pdfbox.pdmodel.PDDocument; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Data; import lombok.EqualsAndHashCode; @@ -25,7 +26,9 @@ public class PDFWithPageNums extends PDFFile { description = "The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the" + " format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a" - + " constant (e.g., '2n+1', '3n', '6n-5')\"") + + " constant (e.g., '2n+1', '3n', '6n-5')\"", + defaultValue = "all", + requiredMode = RequiredMode.NOT_REQUIRED) private String pageNumbers; @Hidden @@ -41,9 +44,9 @@ public class PDFWithPageNums extends PDFFile { } @Hidden - public List getPageNumbersList(PDDocument doc, boolean zeroCount) { + public List getPageNumbersList(PDDocument doc, boolean oneBased) { int pageCount = 0; pageCount = doc.getNumberOfPages(); - return GeneralUtils.parsePageList(pageNumbers, pageCount, zeroCount); + return GeneralUtils.parsePageList(pageNumbers, pageCount, oneBased); } } diff --git a/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java b/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java deleted file mode 100644 index faf955c6..00000000 --- a/src/main/java/stirling/software/SPDF/model/api/extract/PDFFilePage.java +++ /dev/null @@ -1,15 +0,0 @@ -package stirling.software.SPDF.model.api.extract; - -import io.swagger.v3.oas.annotations.media.Schema; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import stirling.software.SPDF.model.api.PDFFile; - -@Data -@EqualsAndHashCode(callSuper = true) -public class PDFFilePage extends PDFFile { - - @Schema(description = "Number of chosen page", type = "number") - private int pageId; -} diff --git a/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java b/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java index 7ba2d84c..7cda530e 100644 --- a/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/general/RearrangePagesRequest.java @@ -15,6 +15,8 @@ public class RearrangePagesRequest extends PDFWithPageNums { implementation = SortTypes.class, description = "The custom mode for page rearrangement. Valid values are:\n" + + "CUSTOM: Uses order defined in PageNums " + + "DUPLICATE: Duplicate pages n times (if Page order defined as 4, then duplicates each page 4 times)" + "REVERSE_ORDER: Reverses the order of all pages.\n" + "DUPLEX_SORT: Sorts pages as if all fronts were scanned then all backs in reverse (1, n, 2, n-1, ...). " + "BOOKLET_SORT: Arranges pages for booklet printing (last, first, second, second last, ...).\n" diff --git a/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java b/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java index 9f3b5266..4776ddc0 100644 --- a/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java +++ b/src/main/java/stirling/software/SPDF/model/api/misc/AddPageNumbersRequest.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.model.api.misc; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Data; import lombok.EqualsAndHashCode; @@ -11,24 +12,49 @@ import stirling.software.SPDF.model.api.PDFWithPageNums; public class AddPageNumbersRequest extends PDFWithPageNums { @Schema( - description = "Custom margin: small/medium/large", - allowableValues = {"small", "medium", "large"}) + description = "Custom margin: small/medium/large/x-large", + allowableValues = {"small", "medium", "large", "x-large"}, + defaultValue = "medium", + requiredMode = RequiredMode.NOT_REQUIRED) private String customMargin; + @Schema( + description = "Font size for page numbers", + minimum = "1", + requiredMode = RequiredMode.REQUIRED) private float fontSize; + + @Schema( + description = "Font type for page numbers", + allowableValues = {"helvetica", "courier", "times"}, + requiredMode = RequiredMode.REQUIRED) private String fontType; - @Schema(description = "Position: 1 of 9 positions", minimum = "1", maximum = "9") + @Schema( + description = + "Position: 1-9 representing positions on the page (1=top-left, 5=center, 9=bottom-right)", + minimum = "1", + maximum = "9", + requiredMode = RequiredMode.REQUIRED) private int position; - @Schema(description = "Starting number", minimum = "1") + @Schema( + description = "Starting number for page numbering", + minimum = "1", + requiredMode = RequiredMode.REQUIRED) private int startingNumber; - @Schema(description = "Which pages to number, default all") + @Schema( + description = "Which pages to number (e.g. '1,3-5,7' or 'all')", + defaultValue = "all", + requiredMode = RequiredMode.NOT_REQUIRED) private String pagesToNumber; @Schema( description = - "Custom text: defaults to just number but can have things like \"Page {n} of {p}\"") + "Custom text pattern. Available variables: {n}=current page number, {total}=total pages, {filename}=original filename", + example = "Page {n} of {total}", + defaultValue = "{n}", + requiredMode = RequiredMode.NOT_REQUIRED) private String customText; } diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index e46bcb16..c28cda6e 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -50,7 +50,7 @@ public class FileToPdf { List command = new ArrayList<>(); if (!htmlFormatsInstalled) { - command.add("weasyprint"); + command.add("/opt/venv/bin/weasyprint"); command.add("-e"); command.add("utf-8"); command.add("-v"); diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 15d866e2..ea4b235b 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -210,8 +210,7 @@ public class GeneralUtils { result.addAll(handlePart(page, totalPages, offset)); } } - return new ArrayList<>( - new java.util.LinkedHashSet<>(result)); // Remove duplicates and maintain order + return result; } public static List evaluateNFunc(String expression, int maxValue) { diff --git a/src/main/java/stirling/software/SPDF/utils/UIScaling.java b/src/main/java/stirling/software/SPDF/utils/UIScaling.java new file mode 100644 index 00000000..fe1364c9 --- /dev/null +++ b/src/main/java/stirling/software/SPDF/utils/UIScaling.java @@ -0,0 +1,67 @@ +package stirling.software.SPDF.utils; + +import java.awt.*; + +import javax.swing.*; + +public class UIScaling { + private static final double BASE_RESOLUTION_WIDTH = 1920.0; + private static final double BASE_RESOLUTION_HEIGHT = 1080.0; + + public static double getWidthScaleFactor() { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + return screenSize.getWidth() / BASE_RESOLUTION_WIDTH; + } + + public static double getHeightScaleFactor() { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + return screenSize.getHeight() / BASE_RESOLUTION_HEIGHT; + } + + public static int scaleWidth(int value) { + return (int) Math.round(value * getWidthScaleFactor()); + } + + public static int scaleHeight(int value) { + return (int) Math.round(value * getHeightScaleFactor()); + } + + public static Dimension scale(Dimension dim) { + return new Dimension(scaleWidth(dim.width), scaleHeight(dim.height)); + } + + public static Insets scale(Insets insets) { + return new Insets( + scaleHeight(insets.top), + scaleWidth(insets.left), + scaleHeight(insets.bottom), + scaleWidth(insets.right)); + } + + public static Font scaleFont(Font font) { + // For fonts, we'll use the smaller scale factor to ensure readability + double scaleFactor = Math.min(getWidthScaleFactor(), getHeightScaleFactor()); + return font.deriveFont((float) (font.getSize() * scaleFactor)); + } + + // Utility method for aspect ratio aware icon scaling + public static Image scaleIcon(Image icon, int targetWidth, int targetHeight) { + if (icon == null) return null; + + double widthScale = getWidthScaleFactor(); + double heightScale = getHeightScaleFactor(); + + int scaledWidth = (int) Math.round(targetWidth * widthScale); + int scaledHeight = (int) Math.round(targetHeight * heightScale); + + // Maintain aspect ratio for icons + double aspectRatio = (double) icon.getWidth(null) / icon.getHeight(null); + if (scaledWidth / scaledHeight > aspectRatio) { + scaledWidth = (int) (scaledHeight * aspectRatio); + } else { + scaledHeight = (int) (scaledWidth / aspectRatio); + } + + return icon.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH); + } +} diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index 6e6b234b..3f7320f8 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -992,6 +992,7 @@ pdfOrganiser.mode.7=Remove First pdfOrganiser.mode.8=Remove Last pdfOrganiser.mode.9=Remove First and Last pdfOrganiser.mode.10=Odd-Even Merge +pdfOrganiser.mode.11=Duplicate all pages pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1) diff --git a/src/main/resources/static/js/pages/pdf-to-csv.js b/src/main/resources/static/js/pages/pdf-to-csv.js index 6be3c2ed..9a06aac5 100644 --- a/src/main/resources/static/js/pages/pdf-to-csv.js +++ b/src/main/resources/static/js/pages/pdf-to-csv.js @@ -17,7 +17,7 @@ let fileInput = document.getElementById('fileInput-input'); let file; let pdfDoc = null; -let pageId = document.getElementById('pageId'); +let pageNumbers = document.getElementById('pageNumbers'); let currentPage = 1; let totalPages = 0; @@ -31,7 +31,7 @@ let timeId = null; // timeout id for resizing canvases event btn1Object.addEventListener('click', function (e) { if (currentPage !== 1) { currentPage = currentPage - 1; - pageId.value = currentPage; + pageNumbers.value = currentPage; if (file.type === 'application/pdf') { let reader = new FileReader(); @@ -52,7 +52,7 @@ btn1Object.addEventListener('click', function (e) { btn2Object.addEventListener('click', function (e) { if (currentPage !== totalPages) { currentPage = currentPage + 1; - pageId.value = currentPage; + pageNumbers.value = currentPage; if (file.type === 'application/pdf') { let reader = new FileReader(); @@ -81,7 +81,7 @@ function renderPageFromFile(file) { totalPages = pdf.numPages; renderPage(currentPage); }); - pageId.value = currentPage; + pageNumbers.value = currentPage; }; reader.readAsArrayBuffer(file); document.getElementById('pagination-button-container').style.display = 'flex'; diff --git a/src/main/resources/templates/convert/pdf-to-csv.html b/src/main/resources/templates/convert/pdf-to-csv.html index b9e27b72..3272766a 100644 --- a/src/main/resources/templates/convert/pdf-to-csv.html +++ b/src/main/resources/templates/convert/pdf-to-csv.html @@ -17,7 +17,7 @@
- +
diff --git a/src/main/resources/templates/pdf-organizer.html b/src/main/resources/templates/pdf-organizer.html index 8d698869..746a6baf 100644 --- a/src/main/resources/templates/pdf-organizer.html +++ b/src/main/resources/templates/pdf-organizer.html @@ -36,6 +36,7 @@ + @@ -51,7 +52,7 @@