mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01:00 
			
		
		
		
	Merge branch 'main' into testStuff
This commit is contained in:
		
						commit
						30ee33002d
					
				
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							@ -1,5 +1,5 @@
 | 
				
			|||||||
blank_issues_enabled: true
 | 
					blank_issues_enabled: true
 | 
				
			||||||
contact_links:
 | 
					contact_links:
 | 
				
			||||||
  - name: 💬 Discord Server
 | 
					  - name: 💬 Discord Server
 | 
				
			||||||
    url: https://discord.gg/Cn8pWhQRxZ
 | 
					    url: https://discord.gg/HYmhKj45pU
 | 
				
			||||||
    about: You can join our Discord server for real time discussion and support
 | 
					    about: You can join our Discord server for real time discussion and support
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# Main stage
 | 
					# Main stage
 | 
				
			||||||
FROM alpine:3.20.3
 | 
					FROM alpine:3.21.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy necessary files
 | 
					# Copy necessary files
 | 
				
			||||||
COPY scripts /scripts
 | 
					COPY scripts /scripts
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@ RUN DOCKER_ENABLE_SECURITY=true \
 | 
				
			|||||||
./gradlew clean build
 | 
					./gradlew clean build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Main stage
 | 
					# Main stage
 | 
				
			||||||
FROM alpine:3.20.3
 | 
					FROM alpine:3.21.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy necessary files
 | 
					# Copy necessary files
 | 
				
			||||||
COPY scripts /scripts
 | 
					COPY scripts /scripts
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
# use alpine
 | 
					# use alpine
 | 
				
			||||||
FROM alpine:3.20.3
 | 
					FROM alpine:3.21.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG VERSION_TAG
 | 
					ARG VERSION_TAG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										48
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								README.md
									
									
									
									
									
								
							@ -2,7 +2,7 @@
 | 
				
			|||||||
<h1 align="center">Stirling-PDF</h1>
 | 
					<h1 align="center">Stirling-PDF</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
 | 
					[](https://hub.docker.com/r/frooodle/s-pdf)
 | 
				
			||||||
[](https://discord.gg/Cn8pWhQRxZ)
 | 
					[](https://discord.gg/HYmhKj45pU)
 | 
				
			||||||
[](https://github.com/Stirling-Tools/Stirling-PDF/)
 | 
					[](https://github.com/Stirling-Tools/Stirling-PDF/)
 | 
				
			||||||
[](https://github.com/Stirling-Tools/stirling-pdf)
 | 
					[](https://github.com/Stirling-Tools/stirling-pdf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -191,44 +191,44 @@ Stirling-PDF currently supports 38 languages!
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Language                                     | Progress                               |
 | 
					| Language                                     | Progress                               |
 | 
				
			||||||
| -------------------------------------------- | -------------------------------------- |
 | 
					| -------------------------------------------- | -------------------------------------- |
 | 
				
			||||||
| Arabic (العربية) (ar_AR)                        |    |
 | 
					| Arabic (العربية) (ar_AR)                        |    |
 | 
				
			||||||
| Azerbaijani (Azərbaycan Dili) (az_AZ)        |    |
 | 
					| Azerbaijani (Azərbaycan Dili) (az_AZ)        |    |
 | 
				
			||||||
| Basque (Euskara) (eu_ES)                     |    |
 | 
					| Basque (Euskara) (eu_ES)                     |    |
 | 
				
			||||||
| Bulgarian (Български) (bg_BG)                |    |
 | 
					| Bulgarian (Български) (bg_BG)                |    |
 | 
				
			||||||
| Catalan (Català) (ca_CA)                     |    |
 | 
					| Catalan (Català) (ca_CA)                     |    |
 | 
				
			||||||
| Croatian (Hrvatski) (hr_HR)                  |    |
 | 
					| Croatian (Hrvatski) (hr_HR)                  |    |
 | 
				
			||||||
| Czech (Česky) (cs_CZ)                        |    |
 | 
					| Czech (Česky) (cs_CZ)                        |    |
 | 
				
			||||||
| Danish (Dansk) (da_DK)                       |    |
 | 
					| Danish (Dansk) (da_DK)                       |    |
 | 
				
			||||||
| Dutch (Nederlands) (nl_NL)                   |    |
 | 
					| Dutch (Nederlands) (nl_NL)                   |    |
 | 
				
			||||||
| English (English) (en_GB)                    |  |
 | 
					| English (English) (en_GB)                    |  |
 | 
				
			||||||
| English (US) (en_US)                         |  |
 | 
					| English (US) (en_US)                         |  |
 | 
				
			||||||
| French (Français) (fr_FR)                    |    |
 | 
					| French (Français) (fr_FR)                    |    |
 | 
				
			||||||
| German (Deutsch) (de_DE)                     |    |
 | 
					| German (Deutsch) (de_DE)                     |    |
 | 
				
			||||||
| Greek (Ελληνικά) (el_GR)                     |    |
 | 
					| Greek (Ελληνικά) (el_GR)                     |    |
 | 
				
			||||||
| Hindi (हिंदी) (hi_IN)                          |    |
 | 
					| Hindi (हिंदी) (hi_IN)                          |    |
 | 
				
			||||||
| Hungarian (Magyar) (hu_HU)                   |    |
 | 
					| Hungarian (Magyar) (hu_HU)                   |    |
 | 
				
			||||||
| Indonesian (Bahasa Indonesia) (id_ID)        |    |
 | 
					| Indonesian (Bahasa Indonesia) (id_ID)        |    |
 | 
				
			||||||
| Irish (Gaeilge) (ga_IE)                      |    |
 | 
					| Irish (Gaeilge) (ga_IE)                      |    |
 | 
				
			||||||
| Italian (Italiano) (it_IT)                   |    |
 | 
					| Italian (Italiano) (it_IT)                   |    |
 | 
				
			||||||
| Japanese (日本語) (ja_JP)                    |    |
 | 
					| Japanese (日本語) (ja_JP)                    |    |
 | 
				
			||||||
| Korean (한국어) (ko_KR)                      |    |
 | 
					| Korean (한국어) (ko_KR)                      |    |
 | 
				
			||||||
| Norwegian (Norsk) (no_NB)                    |    |
 | 
					| Norwegian (Norsk) (no_NB)                    |    |
 | 
				
			||||||
| Persian (فارسی) (fa_IR)                      |    |
 | 
					| Persian (فارسی) (fa_IR)                      |    |
 | 
				
			||||||
| Polish (Polski) (pl_PL)                      |    |
 | 
					| Polish (Polski) (pl_PL)                      |    |
 | 
				
			||||||
| Portuguese (Português) (pt_PT)               |    |
 | 
					| Portuguese (Português) (pt_PT)               |    |
 | 
				
			||||||
| Portuguese Brazilian (Português) (pt_BR)     |    |
 | 
					| Portuguese Brazilian (Português) (pt_BR)     |    |
 | 
				
			||||||
| Romanian (Română) (ro_RO)                    |    |
 | 
					| Romanian (Română) (ro_RO)                    |    |
 | 
				
			||||||
| Russian (Русский) (ru_RU)                    |    |
 | 
					| Russian (Русский) (ru_RU)                    |    |
 | 
				
			||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |    |
 | 
					| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |    |
 | 
				
			||||||
| Simplified Chinese (简体中文) (zh_CN)         |    |
 | 
					| Simplified Chinese (简体中文) (zh_CN)         |    |
 | 
				
			||||||
| Slovakian (Slovensky) (sk_SK)                |    |
 | 
					| Slovakian (Slovensky) (sk_SK)                |    |
 | 
				
			||||||
| Spanish (Español) (es_ES)                    |    |
 | 
					| Spanish (Español) (es_ES)                    |    |
 | 
				
			||||||
| Swedish (Svenska) (sv_SE)                    |    |
 | 
					| Swedish (Svenska) (sv_SE)                    |    |
 | 
				
			||||||
| Thai (ไทย) (th_TH)                           |    |
 | 
					| Thai (ไทย) (th_TH)                           |    |
 | 
				
			||||||
| Traditional Chinese (繁體中文) (zh_TW)        |    |
 | 
					| Traditional Chinese (繁體中文) (zh_TW)        |    |
 | 
				
			||||||
| Turkish (Türkçe) (tr_TR)                     |    |
 | 
					| Turkish (Türkçe) (tr_TR)                     |    |
 | 
				
			||||||
| Ukrainian (Українська) (uk_UA)               |    |
 | 
					| Ukrainian (Українська) (uk_UA)               |    |
 | 
				
			||||||
| Vietnamese (Tiếng Việt) (vi_VN)              |    |
 | 
					| Vietnamese (Tiếng Việt) (vi_VN)              |    |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
 | 
					## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -296,7 +296,7 @@ dependencies {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
 | 
					    if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
 | 
				
			||||||
        implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
 | 
					        implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
 | 
				
			||||||
        implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE"
 | 
					        implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.3.RELEASE"
 | 
				
			||||||
        implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
 | 
					        implementation "org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion"
 | 
				
			||||||
        implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
 | 
					        implementation "org.springframework.boot:spring-boot-starter-oauth2-client:$springBootVersion"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -77,6 +77,11 @@ ignore = [
 | 
				
			|||||||
    'language.direction',
 | 
					    'language.direction',
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[fa_IR]
 | 
				
			||||||
 | 
					ignore = [
 | 
				
			||||||
 | 
					    'language.direction',
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[fr_FR]
 | 
					[fr_FR]
 | 
				
			||||||
ignore = [
 | 
					ignore = [
 | 
				
			||||||
    'AddStampRequest.alphabet',
 | 
					    'AddStampRequest.alphabet',
 | 
				
			||||||
 | 
				
			|||||||
@ -39,10 +39,7 @@ public class PasswordController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostMapping(consumes = "multipart/form-data", value = "/remove-password")
 | 
					    @PostMapping(consumes = "multipart/form-data", value = "/remove-password")
 | 
				
			||||||
    @Operation(
 | 
					    @Operation(summary = "Remove password from a PDF file", description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
 | 
				
			||||||
            summary = "Remove password from a PDF file",
 | 
					 | 
				
			||||||
            description =
 | 
					 | 
				
			||||||
                    "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
 | 
					 | 
				
			||||||
    public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
 | 
					    public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
 | 
				
			||||||
            throws IOException {
 | 
					            throws IOException {
 | 
				
			||||||
        MultipartFile fileInput = request.getFileInput();
 | 
					        MultipartFile fileInput = request.getFileInput();
 | 
				
			||||||
@ -57,10 +54,7 @@ public class PasswordController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @PostMapping(consumes = "multipart/form-data", value = "/add-password")
 | 
					    @PostMapping(consumes = "multipart/form-data", value = "/add-password")
 | 
				
			||||||
    @Operation(
 | 
					    @Operation(summary = "Add password to a PDF file", description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
 | 
				
			||||||
            summary = "Add password to a PDF file",
 | 
					 | 
				
			||||||
            description =
 | 
					 | 
				
			||||||
                    "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
 | 
					 | 
				
			||||||
    public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
 | 
					    public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
 | 
				
			||||||
            throws IOException {
 | 
					            throws IOException {
 | 
				
			||||||
        MultipartFile fileInput = request.getFileInput();
 | 
					        MultipartFile fileInput = request.getFileInput();
 | 
				
			||||||
 | 
				
			|||||||
@ -88,15 +88,45 @@ public class GeneralUtils {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static boolean isURLReachable(String urlStr) {
 | 
					    public static boolean isURLReachable(String urlStr) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            // Parse the URL
 | 
				
			||||||
            URL url = URI.create(urlStr).toURL();
 | 
					            URL url = URI.create(urlStr).toURL();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Allow only http and https protocols
 | 
				
			||||||
 | 
					            String protocol = url.getProtocol();
 | 
				
			||||||
 | 
					            if (!protocol.equals("http") && !protocol.equals("https")) {
 | 
				
			||||||
 | 
					                return false; // Disallow other protocols
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check if the host is a local address
 | 
				
			||||||
 | 
					            String host = url.getHost();
 | 
				
			||||||
 | 
					            if (isLocalAddress(host)) {
 | 
				
			||||||
 | 
					                return false; // Exclude local addresses
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check if the URL is reachable
 | 
				
			||||||
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 | 
					            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 | 
				
			||||||
            connection.setRequestMethod("HEAD");
 | 
					            connection.setRequestMethod("HEAD");
 | 
				
			||||||
 | 
					            // connection.setConnectTimeout(5000); // Set connection timeout
 | 
				
			||||||
 | 
					            // connection.setReadTimeout(5000);    // Set read timeout
 | 
				
			||||||
            int responseCode = connection.getResponseCode();
 | 
					            int responseCode = connection.getResponseCode();
 | 
				
			||||||
            return (200 <= responseCode && responseCode <= 399);
 | 
					            return (200 <= responseCode && responseCode <= 399);
 | 
				
			||||||
        } catch (MalformedURLException e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            return false;
 | 
					            return false; // Return false in case of any exception
 | 
				
			||||||
        } catch (IOException e) {
 | 
					        }
 | 
				
			||||||
            return false;
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static boolean isLocalAddress(String host) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Resolve DNS to IP address
 | 
				
			||||||
 | 
					            InetAddress address = InetAddress.getByName(host);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check for local addresses
 | 
				
			||||||
 | 
					            return address.isAnyLocalAddress() ||  // Matches 0.0.0.0 or similar
 | 
				
			||||||
 | 
					                   address.isLoopbackAddress() || // Matches 127.0.0.1 or ::1
 | 
				
			||||||
 | 
					                   address.isSiteLocalAddress() || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to 172.31.x.x
 | 
				
			||||||
 | 
					                   address.getHostAddress().startsWith("fe80:"); // Matches link-local IPv6 addresses
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            return false; // Return false for invalid or unresolved addresses
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=الصفحات المحددة
 | 
				
			|||||||
multiTool.undo=تراجع
 | 
					multiTool.undo=تراجع
 | 
				
			||||||
multiTool.redo=إعادة إجراء
 | 
					multiTool.redo=إعادة إجراء
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
 | 
					multiTool-advert.message=هذه الميزة متوفرة في <a href="{0}">صفحة الأدوات المتعددة</a> لدينا. اطلع عليها للحصول على واجهة مستخدم محسّنة لكل صفحة وميزات إضافية!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Seçilmiş Səhifə(lər)
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!
 | 
					multiTool-advert.message=Bu xüsusiyyət bizim <a href="{0}">multi-alət səhifə</a>mizdə də mövcuddur. Əlavə xüsusiyyətlər və səhifə-səhifə interfeys üçün sınaqdan keçirin!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Ausgewählte Seite(n)
 | 
				
			|||||||
multiTool.undo=Rückgängig machen
 | 
					multiTool.undo=Rückgängig machen
 | 
				
			||||||
multiTool.redo=Wiederherstellen
 | 
					multiTool.redo=Wiederherstellen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
 | 
					multiTool-advert.message=Diese Funktion ist auch auf unserer <a href="{0}">PDF-Multitool-Seite</a> verfügbar. Probieren Sie sie aus, denn sie bietet eine verbesserte Benutzeroberfläche und zusätzliche Funktionen!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -122,6 +122,7 @@ enterpriseEdition.warning=این ویژگی فقط برای کاربران حر
 | 
				
			|||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایلهای پیکربندی YAML و دیگر ویژگیهای SSO پشتیبانی میکند.
 | 
					enterpriseEdition.yamlAdvert=Stirling PDF Pro از فایلهای پیکربندی YAML و دیگر ویژگیهای SSO پشتیبانی میکند.
 | 
				
			||||||
enterpriseEdition.ssoAdvert=به دنبال ویژگیهای بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید
 | 
					enterpriseEdition.ssoAdvert=به دنبال ویژگیهای بیشتر برای مدیریت کاربران هستید؟ Stirling PDF Pro را بررسی کنید
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#################
 | 
					#################
 | 
				
			||||||
#  Analytics    #
 | 
					#  Analytics    #
 | 
				
			||||||
#################
 | 
					#################
 | 
				
			||||||
@ -515,6 +516,7 @@ home.validateSignature.title=اعتبارسنجی امضای PDF
 | 
				
			|||||||
home.validateSignature.desc=تأیید امضاها و گواهیهای دیجیتال در اسناد PDF
 | 
					home.validateSignature.desc=تأیید امضاها و گواهیهای دیجیتال در اسناد PDF
 | 
				
			||||||
validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهینامه، امضای دیجیتال
 | 
					validateSignature.tags=امضا، تأیید، اعتبارسنجی، PDF، گواهینامه، امضای دیجیتال
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#replace-invert-color
 | 
				
			||||||
replace-color.title=جایگزینی/معکوس کردن رنگ
 | 
					replace-color.title=جایگزینی/معکوس کردن رنگ
 | 
				
			||||||
replace-color.header=جایگزینی/معکوس کردن رنگ PDF
 | 
					replace-color.header=جایگزینی/معکوس کردن رنگ PDF
 | 
				
			||||||
home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ
 | 
					home.replaceColorPdf.title=جایگزینی و معکوس کردن رنگ
 | 
				
			||||||
@ -535,7 +537,6 @@ replace-color.submit=جایگزینی
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
###########################
 | 
					###########################
 | 
				
			||||||
#                         #
 | 
					#                         #
 | 
				
			||||||
#       WEB PAGES         #
 | 
					#       WEB PAGES         #
 | 
				
			||||||
@ -611,6 +612,7 @@ MarkdownToPDF.help=در حال پیشرفت
 | 
				
			|||||||
MarkdownToPDF.credit=از WeasyPrint استفاده میکند
 | 
					MarkdownToPDF.credit=از WeasyPrint استفاده میکند
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#url-to-pdf
 | 
					#url-to-pdf
 | 
				
			||||||
URLToPDF.title=URL به PDF
 | 
					URLToPDF.title=URL به PDF
 | 
				
			||||||
URLToPDF.header=URL به PDF
 | 
					URLToPDF.header=URL به PDF
 | 
				
			||||||
@ -903,7 +905,7 @@ compress.selectText.5=اندازه PDF مورد انتظار (مثلاً ۲۵MB
 | 
				
			|||||||
compress.submit=فشردهسازی
 | 
					compress.submit=فشردهسازی
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Add image
 | 
					#Add image
 | 
				
			||||||
addImage.title=افزودن تصویر
 | 
					addImage.title=افزودن تصویر
 | 
				
			||||||
addImage.header=افزودن تصویر به PDF
 | 
					addImage.header=افزودن تصویر به PDF
 | 
				
			||||||
addImage.everyPage=هر صفحه؟
 | 
					addImage.everyPage=هر صفحه؟
 | 
				
			||||||
@ -911,7 +913,7 @@ addImage.upload=افزودن تصویر
 | 
				
			|||||||
addImage.submit=افزودن تصویر
 | 
					addImage.submit=افزودن تصویر
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Merge
 | 
					#merge
 | 
				
			||||||
merge.title=ادغام
 | 
					merge.title=ادغام
 | 
				
			||||||
merge.header=ادغام چندین PDF (۲+)
 | 
					merge.header=ادغام چندین PDF (۲+)
 | 
				
			||||||
merge.sortByName=مرتبسازی بر اساس نام
 | 
					merge.sortByName=مرتبسازی بر اساس نام
 | 
				
			||||||
@ -920,7 +922,7 @@ merge.removeCertSign=حذف امضای دیجیتال در فایل ادغام
 | 
				
			|||||||
merge.submit=ادغام
 | 
					merge.submit=ادغام
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF Organizer
 | 
					#pdfOrganiser
 | 
				
			||||||
pdfOrganiser.title=سازماندهی صفحات
 | 
					pdfOrganiser.title=سازماندهی صفحات
 | 
				
			||||||
pdfOrganiser.header=سازماندهی صفحات PDF
 | 
					pdfOrganiser.header=سازماندهی صفحات PDF
 | 
				
			||||||
pdfOrganiser.submit=بازآرایی صفحات
 | 
					pdfOrganiser.submit=بازآرایی صفحات
 | 
				
			||||||
@ -938,7 +940,7 @@ pdfOrganiser.mode.10=ادغام فرد-زوج
 | 
				
			|||||||
pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1)
 | 
					pdfOrganiser.placeholder=(مثال: ۱,۳,۲ یا ۴-۸,۲,۱۰-۱۲ یا 2n-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Multi Tool
 | 
					#multiTool
 | 
				
			||||||
multiTool.title=ابزار چندگانه PDF
 | 
					multiTool.title=ابزار چندگانه PDF
 | 
				
			||||||
multiTool.header=ابزار چندگانه PDF
 | 
					multiTool.header=ابزار چندگانه PDF
 | 
				
			||||||
multiTool.uploadPrompts=نام فایل
 | 
					multiTool.uploadPrompts=نام فایل
 | 
				
			||||||
@ -963,14 +965,24 @@ multiTool.dragDropMessage=صفحه(ها) انتخاب شدهاند
 | 
				
			|||||||
multiTool.undo=واگرد
 | 
					multiTool.undo=واگرد
 | 
				
			||||||
multiTool.redo=بازگرداندن
 | 
					multiTool.redo=بازگرداندن
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Multi Tool Advert
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگیهای اضافی بررسی کنید!
 | 
					multiTool-advert.message=این ویژگی همچنین در <a href="{0}">صفحه ابزار چندگانه ما</a> موجود است. برای رابط کاربری صفحه به صفحه پیشرفته و ویژگیهای اضافی بررسی کنید!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# View PDF
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=مشاهده PDF
 | 
					viewPdf.title=مشاهده PDF
 | 
				
			||||||
viewPdf.header=مشاهده PDF
 | 
					viewPdf.header=مشاهده PDF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Page Remover
 | 
					#pageRemover
 | 
				
			||||||
pageRemover.title=حذف صفحات
 | 
					pageRemover.title=حذف صفحات
 | 
				
			||||||
pageRemover.header=حذف صفحات PDF
 | 
					pageRemover.header=حذف صفحات PDF
 | 
				
			||||||
pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید):
 | 
					pageRemover.pagesToDelete=صفحات برای حذف (یک لیست از اعداد صفحه جدا شده با کاما وارد کنید):
 | 
				
			||||||
@ -978,14 +990,14 @@ pageRemover.submit=حذف صفحات
 | 
				
			|||||||
pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰)
 | 
					pageRemover.placeholder=(مثال: ۱,۲,۶ یا ۱-۱۰,۱۵-۳۰)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Rotate
 | 
					#rotate
 | 
				
			||||||
rotate.title=چرخش PDF
 | 
					rotate.title=چرخش PDF
 | 
				
			||||||
rotate.header=چرخش PDF
 | 
					rotate.header=چرخش PDF
 | 
				
			||||||
rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضربهای ۹۰ درجه):
 | 
					rotate.selectAngle=زاویه چرخش را انتخاب کنید (به مضربهای ۹۰ درجه):
 | 
				
			||||||
rotate.submit=چرخش
 | 
					rotate.submit=چرخش
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Split PDFs
 | 
					#split-pdfs
 | 
				
			||||||
split.title=تقسیم PDF
 | 
					split.title=تقسیم PDF
 | 
				
			||||||
split.header=تقسیم PDF
 | 
					split.header=تقسیم PDF
 | 
				
			||||||
split.desc.1=اعدادی که انتخاب میکنید شماره صفحههایی هستند که میخواهید بر روی آنها تقسیم انجام دهید
 | 
					split.desc.1=اعدادی که انتخاب میکنید شماره صفحههایی هستند که میخواهید بر روی آنها تقسیم انجام دهید
 | 
				
			||||||
@ -1014,7 +1026,7 @@ imageToPDF.selectText.4=ادغام در یک PDF واحد
 | 
				
			|||||||
imageToPDF.selectText.5=تبدیل به PDF های جداگانه
 | 
					imageToPDF.selectText.5=تبدیل به PDF های جداگانه
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to Image
 | 
					#pdfToImage
 | 
				
			||||||
pdfToImage.title=PDF به تصویر
 | 
					pdfToImage.title=PDF به تصویر
 | 
				
			||||||
pdfToImage.header=PDF به تصویر
 | 
					pdfToImage.header=PDF به تصویر
 | 
				
			||||||
pdfToImage.selectText=فرمت تصویر
 | 
					pdfToImage.selectText=فرمت تصویر
 | 
				
			||||||
@ -1029,7 +1041,7 @@ pdfToImage.submit=تبدیل
 | 
				
			|||||||
pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
 | 
					pdfToImage.info=پایتون نصب نشده است. برای تبدیل WebP لازم است.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Add Password
 | 
					#addPassword
 | 
				
			||||||
addPassword.title=افزودن گذرواژه
 | 
					addPassword.title=افزودن گذرواژه
 | 
				
			||||||
addPassword.header=افزودن گذرواژه (رمزنگاری)
 | 
					addPassword.header=افزودن گذرواژه (رمزنگاری)
 | 
				
			||||||
addPassword.selectText.1=انتخاب PDF برای رمزنگاری
 | 
					addPassword.selectText.1=انتخاب PDF برای رمزنگاری
 | 
				
			||||||
@ -1051,7 +1063,7 @@ addPassword.selectText.16=محدودیتهای باز شدن خود سند
 | 
				
			|||||||
addPassword.submit=رمزنگاری
 | 
					addPassword.submit=رمزنگاری
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Watermark
 | 
					#watermark
 | 
				
			||||||
watermark.title=افزودن واترمارک
 | 
					watermark.title=افزودن واترمارک
 | 
				
			||||||
watermark.header=افزودن واترمارک
 | 
					watermark.header=افزودن واترمارک
 | 
				
			||||||
watermark.customColor=رنگ متن سفارشی
 | 
					watermark.customColor=رنگ متن سفارشی
 | 
				
			||||||
@ -1070,7 +1082,7 @@ watermark.type.1=متن
 | 
				
			|||||||
watermark.type.2=تصویر
 | 
					watermark.type.2=تصویر
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Change Permissions
 | 
					#Change permissions
 | 
				
			||||||
permissions.title=تغییر مجوزها
 | 
					permissions.title=تغییر مجوزها
 | 
				
			||||||
permissions.header=تغییر مجوزها
 | 
					permissions.header=تغییر مجوزها
 | 
				
			||||||
permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه میشود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید
 | 
					permissions.warning=برای اینکه این مجوزها غیرقابل تغییر باشند، توصیه میشود آنها را با گذرواژه از طریق صفحه افزودن گذرواژه تنظیم کنید
 | 
				
			||||||
@ -1087,7 +1099,7 @@ permissions.selectText.10=جلوگیری از چاپ فرمتهای مختل
 | 
				
			|||||||
permissions.submit=تغییر
 | 
					permissions.submit=تغییر
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Remove Password
 | 
					#remove password
 | 
				
			||||||
removePassword.title=حذف گذرواژه
 | 
					removePassword.title=حذف گذرواژه
 | 
				
			||||||
removePassword.header=حذف گذرواژه (رمزگشایی)
 | 
					removePassword.header=حذف گذرواژه (رمزگشایی)
 | 
				
			||||||
removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید
 | 
					removePassword.selectText.1=PDFی را برای رمزگشایی انتخاب کنید
 | 
				
			||||||
@ -1095,7 +1107,7 @@ removePassword.selectText.2=گذرواژه
 | 
				
			|||||||
removePassword.submit=حذف
 | 
					removePassword.submit=حذف
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Change Metadata
 | 
					#changeMetadata
 | 
				
			||||||
changeMetadata.title=عنوان:
 | 
					changeMetadata.title=عنوان:
 | 
				
			||||||
changeMetadata.header=تغییر متادادهها
 | 
					changeMetadata.header=تغییر متادادهها
 | 
				
			||||||
changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید
 | 
					changeMetadata.selectText.1=لطفاً متغیرهایی که مایل به تغییر آنها هستید را ویرایش کنید
 | 
				
			||||||
@ -1114,7 +1126,7 @@ changeMetadata.selectText.5=افزودن ورودی متاداده سفارشی
 | 
				
			|||||||
changeMetadata.submit=تغییر
 | 
					changeMetadata.submit=تغییر
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to PDF/A
 | 
					#pdfToPDFA
 | 
				
			||||||
pdfToPDFA.title=PDF به PDF/A
 | 
					pdfToPDFA.title=PDF به PDF/A
 | 
				
			||||||
pdfToPDFA.header=PDF به PDF/A
 | 
					pdfToPDFA.header=PDF به PDF/A
 | 
				
			||||||
pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده میکند
 | 
					pdfToPDFA.credit=این سرویس از qpdf برای تبدیل PDF/A استفاده میکند
 | 
				
			||||||
@ -1124,7 +1136,7 @@ pdfToPDFA.outputFormat=فرمت خروجی
 | 
				
			|||||||
pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.
 | 
					pdfToPDFA.pdfWithDigitalSignature=PDF حاوی یک امضای دیجیتال است. این در مرحله بعد حذف خواهد شد.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to Word
 | 
					#PDFToWord
 | 
				
			||||||
PDFToWord.title=PDF به ورد
 | 
					PDFToWord.title=PDF به ورد
 | 
				
			||||||
PDFToWord.header=PDF به ورد
 | 
					PDFToWord.header=PDF به ورد
 | 
				
			||||||
PDFToWord.selectText.1=فرمت فایل خروجی
 | 
					PDFToWord.selectText.1=فرمت فایل خروجی
 | 
				
			||||||
@ -1132,7 +1144,7 @@ PDFToWord.credit=این سرویس از LibreOffice برای تبدیل فایل
 | 
				
			|||||||
PDFToWord.submit=تبدیل
 | 
					PDFToWord.submit=تبدیل
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to Presentation
 | 
					#PDFToPresentation
 | 
				
			||||||
PDFToPresentation.title=PDF به ارائه
 | 
					PDFToPresentation.title=PDF به ارائه
 | 
				
			||||||
PDFToPresentation.header=PDF به ارائه
 | 
					PDFToPresentation.header=PDF به ارائه
 | 
				
			||||||
PDFToPresentation.selectText.1=فرمت فایل خروجی
 | 
					PDFToPresentation.selectText.1=فرمت فایل خروجی
 | 
				
			||||||
@ -1140,7 +1152,7 @@ PDFToPresentation.credit=این سرویس از LibreOffice برای تبدیل
 | 
				
			|||||||
PDFToPresentation.submit=تبدیل
 | 
					PDFToPresentation.submit=تبدیل
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to Text
 | 
					#PDFToText
 | 
				
			||||||
PDFToText.title=PDF به RTF (متن)
 | 
					PDFToText.title=PDF به RTF (متن)
 | 
				
			||||||
PDFToText.header=PDF به RTF (متن)
 | 
					PDFToText.header=PDF به RTF (متن)
 | 
				
			||||||
PDFToText.selectText.1=فرمت فایل خروجی
 | 
					PDFToText.selectText.1=فرمت فایل خروجی
 | 
				
			||||||
@ -1148,27 +1160,26 @@ PDFToText.credit=این سرویس از LibreOffice برای تبدیل فایل
 | 
				
			|||||||
PDFToText.submit=تبدیل
 | 
					PDFToText.submit=تبدیل
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to HTML
 | 
					#PDFToHTML
 | 
				
			||||||
PDFToHTML.title=PDF به HTML
 | 
					PDFToHTML.title=PDF به HTML
 | 
				
			||||||
PDFToHTML.header=PDF به HTML
 | 
					PDFToHTML.header=PDF به HTML
 | 
				
			||||||
PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده میکند.
 | 
					PDFToHTML.credit=این سرویس از pdftohtml برای تبدیل فایل استفاده میکند.
 | 
				
			||||||
PDFToHTML.submit=تبدیل
 | 
					PDFToHTML.submit=تبدیل
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to XML
 | 
					#PDFToXML
 | 
				
			||||||
PDFToXML.title=PDF به XML
 | 
					PDFToXML.title=PDF به XML
 | 
				
			||||||
PDFToXML.header=PDF به XML
 | 
					PDFToXML.header=PDF به XML
 | 
				
			||||||
PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده میکند.
 | 
					PDFToXML.credit=این سرویس از LibreOffice برای تبدیل فایل استفاده میکند.
 | 
				
			||||||
PDFToXML.submit=تبدیل
 | 
					PDFToXML.submit=تبدیل
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# PDF to CSV
 | 
					#PDFToCSV
 | 
				
			||||||
PDFToCSV.title=PDF به CSV
 | 
					PDFToCSV.title=PDF به CSV
 | 
				
			||||||
PDFToCSV.header=PDF به CSV
 | 
					PDFToCSV.header=PDF به CSV
 | 
				
			||||||
PDFToCSV.prompt=صفحهای که میخواهید جدول استخراج شود را انتخاب کنید
 | 
					PDFToCSV.prompt=صفحهای که میخواهید جدول استخراج شود را انتخاب کنید
 | 
				
			||||||
PDFToCSV.submit=استخراج
 | 
					PDFToCSV.submit=استخراج
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#split-by-size-or-count
 | 
				
			||||||
# Split by Size or Count
 | 
					 | 
				
			||||||
split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد
 | 
					split-by-size-or-count.title=تقسیم PDF بر اساس اندازه یا تعداد
 | 
				
			||||||
split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد
 | 
					split-by-size-or-count.header=تقسیم PDF بر اساس اندازه یا تعداد
 | 
				
			||||||
split-by-size-or-count.type.label=انتخاب نوع تقسیم
 | 
					split-by-size-or-count.type.label=انتخاب نوع تقسیم
 | 
				
			||||||
@ -1180,7 +1191,7 @@ split-by-size-or-count.value.placeholder=اندازه را وارد کنید (م
 | 
				
			|||||||
split-by-size-or-count.submit=ارسال
 | 
					split-by-size-or-count.submit=ارسال
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Overlay PDFs
 | 
					#overlay-pdfs
 | 
				
			||||||
overlay-pdfs.header=ترکیب فایلهای PDF
 | 
					overlay-pdfs.header=ترکیب فایلهای PDF
 | 
				
			||||||
overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF
 | 
					overlay-pdfs.baseFile.label=انتخاب فایل پایه PDF
 | 
				
			||||||
overlay-pdfs.overlayFiles.label=انتخاب فایلهای ترکیبی PDF
 | 
					overlay-pdfs.overlayFiles.label=انتخاب فایلهای ترکیبی PDF
 | 
				
			||||||
@ -1196,7 +1207,7 @@ overlay-pdfs.position.background=پسزمینه
 | 
				
			|||||||
overlay-pdfs.submit=ارسال
 | 
					overlay-pdfs.submit=ارسال
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Split by Sections
 | 
					#split-by-sections
 | 
				
			||||||
split-by-sections.title=تقسیم PDF به بخشها
 | 
					split-by-sections.title=تقسیم PDF به بخشها
 | 
				
			||||||
split-by-sections.header=تقسیم PDF به بخشها
 | 
					split-by-sections.header=تقسیم PDF به بخشها
 | 
				
			||||||
split-by-sections.horizontal.label=تقسیمات افقی
 | 
					split-by-sections.horizontal.label=تقسیمات افقی
 | 
				
			||||||
@ -1207,7 +1218,7 @@ split-by-sections.submit=تقسیم PDF
 | 
				
			|||||||
split-by-sections.merge=ادغام به یک PDF
 | 
					split-by-sections.merge=ادغام به یک PDF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Print File
 | 
					#printFile
 | 
				
			||||||
printFile.title=چاپ فایل
 | 
					printFile.title=چاپ فایل
 | 
				
			||||||
printFile.header=چاپ فایل به چاپگر
 | 
					printFile.header=چاپ فایل به چاپگر
 | 
				
			||||||
printFile.selectText.1=انتخاب فایل برای چاپ
 | 
					printFile.selectText.1=انتخاب فایل برای چاپ
 | 
				
			||||||
@ -1215,7 +1226,7 @@ printFile.selectText.2=نام چاپگر را وارد کنید
 | 
				
			|||||||
printFile.submit=چاپ
 | 
					printFile.submit=چاپ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Licenses
 | 
					#licenses
 | 
				
			||||||
licenses.nav=مجوزها
 | 
					licenses.nav=مجوزها
 | 
				
			||||||
licenses.title=مجوزهای شخص ثالث
 | 
					licenses.title=مجوزهای شخص ثالث
 | 
				
			||||||
licenses.header=مجوزهای شخص ثالث
 | 
					licenses.header=مجوزهای شخص ثالث
 | 
				
			||||||
@ -1223,7 +1234,7 @@ licenses.module=ماژول
 | 
				
			|||||||
licenses.version=نسخه
 | 
					licenses.version=نسخه
 | 
				
			||||||
licenses.license=مجوز
 | 
					licenses.license=مجوز
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Survey
 | 
					#survey
 | 
				
			||||||
survey.nav=نظرسنجی
 | 
					survey.nav=نظرسنجی
 | 
				
			||||||
survey.title=نظرسنجی Stirling-PDF
 | 
					survey.title=نظرسنجی Stirling-PDF
 | 
				
			||||||
survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما میخواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم!
 | 
					survey.description=Stirling-PDF هیچ ردیابی ندارد، بنابراین ما میخواهیم از کاربران خود بشنویم تا Stirling-PDF را بهبود دهیم!
 | 
				
			||||||
@ -1235,7 +1246,7 @@ survey.button=شرکت در نظرسنجی
 | 
				
			|||||||
survey.dontShowAgain=دیگر نشان نده
 | 
					survey.dontShowAgain=دیگر نشان نده
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Error
 | 
					#error
 | 
				
			||||||
error.sorry=متأسفیم برای مشکل موجود!
 | 
					error.sorry=متأسفیم برای مشکل موجود!
 | 
				
			||||||
error.needHelp=نیاز به کمک / یافتن مشکلی؟
 | 
					error.needHelp=نیاز به کمک / یافتن مشکلی؟
 | 
				
			||||||
error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. میتوانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید:
 | 
					error.contactTip=اگر هنوز مشکلی دارید، دریغ نکنید که با ما تماس بگیرید. میتوانید یک تیکت در صفحه GitHub ما ارسال کنید یا از طریق Discord با ما تماس بگیرید:
 | 
				
			||||||
@ -1249,14 +1260,13 @@ error.githubSubmit=GitHub - ارسال تیکت
 | 
				
			|||||||
error.discordSubmit=Discord - ارسال پست پشتیبانی
 | 
					error.discordSubmit=Discord - ارسال پست پشتیبانی
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Remove Image
 | 
					#remove-image
 | 
				
			||||||
removeImage.title=حذف تصویر
 | 
					removeImage.title=حذف تصویر
 | 
				
			||||||
removeImage.header=حذف تصویر
 | 
					removeImage.header=حذف تصویر
 | 
				
			||||||
removeImage.removeImage=حذف تصویر
 | 
					removeImage.removeImage=حذف تصویر
 | 
				
			||||||
removeImage.submit=حذف تصویر
 | 
					removeImage.submit=حذف تصویر
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Split by Chapters
 | 
					 | 
				
			||||||
splitByChapters.title=تقسیم PDF بر اساس فصلها
 | 
					splitByChapters.title=تقسیم PDF بر اساس فصلها
 | 
				
			||||||
splitByChapters.header=تقسیم PDF بر اساس فصلها
 | 
					splitByChapters.header=تقسیم PDF بر اساس فصلها
 | 
				
			||||||
splitByChapters.bookmarkLevel=سطح نشانک
 | 
					splitByChapters.bookmarkLevel=سطح نشانک
 | 
				
			||||||
@ -1268,20 +1278,20 @@ splitByChapters.desc.3=شامل متادیتا: اگر انتخاب شده، م
 | 
				
			|||||||
splitByChapters.desc.4=اجازهی تکرار: اگر انتخاب شده باشد، اجازه میدهد نشانکهای متعدد در یک صفحه، فایلهای PDF جداگانه ایجاد کنند.
 | 
					splitByChapters.desc.4=اجازهی تکرار: اگر انتخاب شده باشد، اجازه میدهد نشانکهای متعدد در یک صفحه، فایلهای PDF جداگانه ایجاد کنند.
 | 
				
			||||||
splitByChapters.submit=تقسیم PDF
 | 
					splitByChapters.submit=تقسیم PDF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# File Chooser
 | 
					#File Chooser
 | 
				
			||||||
fileChooser.click=کلیک کنید
 | 
					fileChooser.click=کلیک کنید
 | 
				
			||||||
fileChooser.or=یا
 | 
					fileChooser.or=یا
 | 
				
			||||||
fileChooser.dragAndDrop=بکشید و رها کنید
 | 
					fileChooser.dragAndDrop=بکشید و رها کنید
 | 
				
			||||||
fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
 | 
					fileChooser.hoveredDragAndDrop=فایل(های) خود را اینجا بکشید و رها کنید
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Release Notes
 | 
					#release notes
 | 
				
			||||||
releases.footer=نسخهها
 | 
					releases.footer=نسخهها
 | 
				
			||||||
releases.title=یادداشتهای نسخه
 | 
					releases.title=یادداشتهای نسخه
 | 
				
			||||||
releases.header=یادداشتهای نسخه
 | 
					releases.header=یادداشتهای نسخه
 | 
				
			||||||
releases.current.version=نسخه فعلی
 | 
					releases.current.version=نسخه فعلی
 | 
				
			||||||
releases.note=یادداشتهای نسخه فقط به زبان انگلیسی موجود است
 | 
					releases.note=یادداشتهای نسخه فقط به زبان انگلیسی موجود است
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Validate Signature
 | 
					#Validate Signature
 | 
				
			||||||
validateSignature.title=اعتبارسنجی امضاهای PDF
 | 
					validateSignature.title=اعتبارسنجی امضاهای PDF
 | 
				
			||||||
validateSignature.header=اعتبارسنجی امضای دیجیتال
 | 
					validateSignature.header=اعتبارسنجی امضای دیجیتال
 | 
				
			||||||
validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید
 | 
					validateSignature.selectPDF=فایل PDF امضاشده را انتخاب کنید
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) sélectionnées
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
 | 
					multiTool-advert.message=Cette fonctionnalité est aussi disponible dans la <a href="{0}">page de l'outil multifonction</a>. Allez-y pour une interface page par page améliorée et des fonctionnalités additionnelles !
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Pagina(e) selezionata(e)
 | 
				
			|||||||
multiTool.undo=Annulla
 | 
					multiTool.undo=Annulla
 | 
				
			||||||
multiTool.redo=Rifai
 | 
					multiTool.redo=Rifai
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=Questo file è protetto da password. Inserisci la password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operazione annullata per il PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=Nessuna password fornita per il PDF crittografato: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Riprova con la password corretta.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Password errata o crittografia non supportata per il PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=Si è verificato un errore durante l'elaborazione del file. Riprova..
 | 
				
			||||||
 | 
					decrypt.serverError=Errore del server durante la decrittazione: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrittografato con successo.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
 | 
					multiTool-advert.message=Questa funzione è disponibile anche nella nostra <a href="{0}">pagina multi-strumento</a>. Scoprila per un'interfaccia utente pagina per pagina migliorata e funzionalità aggiuntive!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -56,12 +56,12 @@ userNotFoundMessage=ユーザーが見つかりません。
 | 
				
			|||||||
incorrectPasswordMessage=現在のパスワードが正しくありません。
 | 
					incorrectPasswordMessage=現在のパスワードが正しくありません。
 | 
				
			||||||
usernameExistsMessage=新しいユーザー名はすでに存在します。
 | 
					usernameExistsMessage=新しいユーザー名はすでに存在します。
 | 
				
			||||||
invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
 | 
					invalidUsernameMessage=ユーザー名が無効です。ユーザー名には文字、数字、およびそれに続く特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
 | 
				
			||||||
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
 | 
					invalidPasswordMessage=パスワードは空にすることはできません。また、先頭・末尾にスペースを含めることもできません。
 | 
				
			||||||
confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
 | 
					confirmPasswordErrorMessage=新しいパスワードと新しいパスワードの確認は一致する必要があります。
 | 
				
			||||||
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
 | 
					deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
 | 
				
			||||||
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
 | 
					deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
 | 
				
			||||||
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
 | 
					downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
 | 
				
			||||||
disabledCurrentUserMessage=The current user cannot be disabled
 | 
					disabledCurrentUserMessage=現在のユーザーを無効にすることはできません
 | 
				
			||||||
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
 | 
					downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
 | 
				
			||||||
userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
 | 
					userAlreadyExistsOAuthMessage=ユーザーは既にOAuth2ユーザーとして存在します。
 | 
				
			||||||
userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
 | 
					userAlreadyExistsWebMessage=ユーザーは既にWebユーザーとして存在します。
 | 
				
			||||||
@ -76,12 +76,12 @@ donate=寄付する
 | 
				
			|||||||
color=色
 | 
					color=色
 | 
				
			||||||
sponsor=スポンサー
 | 
					sponsor=スポンサー
 | 
				
			||||||
info=Info
 | 
					info=Info
 | 
				
			||||||
pro=Pro
 | 
					pro=pro
 | 
				
			||||||
page=Page
 | 
					page=ページ
 | 
				
			||||||
pages=Pages
 | 
					pages=ページ
 | 
				
			||||||
loading=Loading...
 | 
					loading=読込中...
 | 
				
			||||||
addToDoc=Add to Document
 | 
					addToDoc=ドキュメントに追加
 | 
				
			||||||
reset=Reset
 | 
					reset=リセット
 | 
				
			||||||
 | 
					
 | 
				
			||||||
legal.privacy=プライバシーポリシー
 | 
					legal.privacy=プライバシーポリシー
 | 
				
			||||||
legal.terms=利用規約
 | 
					legal.terms=利用規約
 | 
				
			||||||
@ -92,7 +92,7 @@ legal.impressum=著作権利者情報
 | 
				
			|||||||
###############
 | 
					###############
 | 
				
			||||||
#   Pipeline  #
 | 
					#   Pipeline  #
 | 
				
			||||||
###############
 | 
					###############
 | 
				
			||||||
pipeline.header=パイプラインメニュー (Alpha)
 | 
					pipeline.header=パイプラインメニュー (Beta)
 | 
				
			||||||
pipeline.uploadButton=カスタムのアップロード
 | 
					pipeline.uploadButton=カスタムのアップロード
 | 
				
			||||||
pipeline.configureButton=設定
 | 
					pipeline.configureButton=設定
 | 
				
			||||||
pipeline.defaultOption=カスタム
 | 
					pipeline.defaultOption=カスタム
 | 
				
			||||||
@ -117,21 +117,21 @@ pipelineOptions.validateButton=検証
 | 
				
			|||||||
########################
 | 
					########################
 | 
				
			||||||
#  ENTERPRISE EDITION  #
 | 
					#  ENTERPRISE EDITION  #
 | 
				
			||||||
########################
 | 
					########################
 | 
				
			||||||
enterpriseEdition.button=Upgrade to Pro
 | 
					enterpriseEdition.button=Proにアップグレード
 | 
				
			||||||
enterpriseEdition.warning=This feature is only available to Pro users.
 | 
					enterpriseEdition.warning=この機能はProユーザーのみが利用できます。
 | 
				
			||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
 | 
					enterpriseEdition.yamlAdvert=Stirling PDF Proは、YAML構成ファイルやその他のSSO機能をサポートしています。
 | 
				
			||||||
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
 | 
					enterpriseEdition.ssoAdvert=より多くのユーザー管理機能をお探しですか? Stirling PDF Proをご覧ください
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#################
 | 
					#################
 | 
				
			||||||
#  Analytics    #
 | 
					#  Analytics    #
 | 
				
			||||||
#################
 | 
					#################
 | 
				
			||||||
analytics.title=Do you want make Stirling PDF better?
 | 
					analytics.title=Stirling PDFをもっと良くしたいですか?
 | 
				
			||||||
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.paragraph1=Stirling PDFでは、製品の改善に役立つ分析機能をオプトインしています。個人情報やファイルの内容を追跡することはありません。
 | 
				
			||||||
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
 | 
					analytics.paragraph2=Stirling-PDFの成長を支援しユーザーをより深く理解できるように分析を有効にすることを検討してください。
 | 
				
			||||||
analytics.enable=Enable analytics
 | 
					analytics.enable=分析を有効にする
 | 
				
			||||||
analytics.disable=Disable analytics
 | 
					analytics.disable=分析を無効にする
 | 
				
			||||||
analytics.settings=You can change the settings for analytics in the config/settings.yml file
 | 
					analytics.settings=config/settings.ymlファイルでアナリティクスの設定を変更できます。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
#  NAVBAR   #
 | 
					#  NAVBAR   #
 | 
				
			||||||
@ -142,14 +142,14 @@ navbar.language=言語
 | 
				
			|||||||
navbar.settings=設定
 | 
					navbar.settings=設定
 | 
				
			||||||
navbar.allTools=ツール
 | 
					navbar.allTools=ツール
 | 
				
			||||||
navbar.multiTool=マルチツール
 | 
					navbar.multiTool=マルチツール
 | 
				
			||||||
navbar.search=Search
 | 
					navbar.search=検索
 | 
				
			||||||
navbar.sections.organize=整理
 | 
					navbar.sections.organize=整理
 | 
				
			||||||
navbar.sections.convertTo=PDFへ変換
 | 
					navbar.sections.convertTo=PDFへ変換
 | 
				
			||||||
navbar.sections.convertFrom=PDFから変換
 | 
					navbar.sections.convertFrom=PDFから変換
 | 
				
			||||||
navbar.sections.security=署名とセキュリティ
 | 
					navbar.sections.security=署名とセキュリティ
 | 
				
			||||||
navbar.sections.advance=アドバンスド
 | 
					navbar.sections.advance=アドバンスド
 | 
				
			||||||
navbar.sections.edit=閲覧と編集
 | 
					navbar.sections.edit=閲覧と編集
 | 
				
			||||||
navbar.sections.popular=Popular
 | 
					navbar.sections.popular=人気
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
#  SETTINGS #
 | 
					#  SETTINGS #
 | 
				
			||||||
@ -208,7 +208,7 @@ adminUserSettings.user=ユーザー
 | 
				
			|||||||
adminUserSettings.addUser=新しいユーザを追加
 | 
					adminUserSettings.addUser=新しいユーザを追加
 | 
				
			||||||
adminUserSettings.deleteUser=ユーザの削除
 | 
					adminUserSettings.deleteUser=ユーザの削除
 | 
				
			||||||
adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
 | 
					adminUserSettings.confirmDeleteUser=ユーザを本当に削除しますか?
 | 
				
			||||||
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
 | 
					adminUserSettings.confirmChangeUserStatus=ユーザーを無効/有効にする必要がありますか?
 | 
				
			||||||
adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
 | 
					adminUserSettings.usernameInfo=ユーザー名には、文字、数字、および次の特殊文字 @._+- のみを含めることができます。または、有効な電子メール アドレスである必要があります。
 | 
				
			||||||
adminUserSettings.roles=役割
 | 
					adminUserSettings.roles=役割
 | 
				
			||||||
adminUserSettings.role=役割
 | 
					adminUserSettings.role=役割
 | 
				
			||||||
@ -247,8 +247,8 @@ database.fileNotFound=ファイルが見つかりません
 | 
				
			|||||||
database.fileNullOrEmpty=ファイルは null または空であってはなりません
 | 
					database.fileNullOrEmpty=ファイルは null または空であってはなりません
 | 
				
			||||||
database.failedImportFile=ファイルのインポートに失敗
 | 
					database.failedImportFile=ファイルのインポートに失敗
 | 
				
			||||||
 | 
					
 | 
				
			||||||
session.expired=Your session has expired. Please refresh the page and try again.
 | 
					session.expired=セッションが期限切れです。ページを更新してもう一度お試しください。
 | 
				
			||||||
session.refreshPage=Refresh Page
 | 
					session.refreshPage=ページを更新
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#############
 | 
					#############
 | 
				
			||||||
# HOME-PAGE #
 | 
					# HOME-PAGE #
 | 
				
			||||||
@ -488,52 +488,52 @@ overlay-pdfs.tags=Overlay
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
home.split-by-sections.title=PDFをセクションで分割
 | 
					home.split-by-sections.title=PDFをセクションで分割
 | 
				
			||||||
home.split-by-sections.desc=PDFの各ページを縦横に分割します。
 | 
					home.split-by-sections.desc=PDFの各ページを縦横に分割します。
 | 
				
			||||||
split-by-sections.tags=Section Split, Divide, Customize
 | 
					split-by-sections.tags=Section Split, Divide, Customize,Customise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
home.AddStampRequest.title=PDFにスタンプを追加
 | 
					home.AddStampRequest.title=PDFにスタンプを追加
 | 
				
			||||||
home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
 | 
					home.AddStampRequest.desc=設定した位置にテキストや画像のスタンプを追加できます
 | 
				
			||||||
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
 | 
					AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize,Customise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
home.PDFToBook.title=PDFを書籍に変換
 | 
					home.PDFToBook.title=PDFを書籍に変換
 | 
				
			||||||
home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
 | 
					home.PDFToBook.desc=calibreを使用してPDFを書籍/コミック形式に変換します
 | 
				
			||||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
 | 
					PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
home.BookToPDF.title=PDFを書籍に変換
 | 
					home.BookToPDF.title=PDFを書籍に変換
 | 
				
			||||||
home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
 | 
					home.BookToPDF.desc=calibreを使用してPDFを書籍/コミック形式に変換します
 | 
				
			||||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
 | 
					BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
home.removeImagePdf.title=画像の削除
 | 
					home.removeImagePdf.title=画像の削除
 | 
				
			||||||
home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
 | 
					home.removeImagePdf.desc=PDFから画像を削除してファイルサイズを小さくします
 | 
				
			||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
 | 
					removeImagePdf.tags=Remove Image,Page operations,Back end,server side
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
home.splitPdfByChapters.title=Split PDF by Chapters
 | 
					home.splitPdfByChapters.title=PDFをチャプターごとに分割
 | 
				
			||||||
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
 | 
					home.splitPdfByChapters.desc=チャプターの構造に基づいてPDFを複数のファイルに分割します
 | 
				
			||||||
splitPdfByChapters.tags=split,chapters,bookmarks,organize
 | 
					splitPdfByChapters.tags=split,chapters,bookmarks,organize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
home.validateSignature.title=Validate PDF Signature
 | 
					home.validateSignature.title=PDF署名の検証
 | 
				
			||||||
home.validateSignature.desc=Verify digital signatures and certificates in PDF documents
 | 
					home.validateSignature.desc=PDF文書のデジタル署名と証明書を検証します
 | 
				
			||||||
validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
 | 
					validateSignature.tags=signature,verify,validate,pdf,certificate,digital signature,Validate Signature,Validate certificate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#replace-invert-color
 | 
					#replace-invert-color
 | 
				
			||||||
replace-color.title=Replace-Invert-Color
 | 
					replace-color.title=色の置換・反転
 | 
				
			||||||
replace-color.header=Replace-Invert Color PDF
 | 
					replace-color.header=PDFの色の置換・反転
 | 
				
			||||||
home.replaceColorPdf.title=Replace and Invert Color
 | 
					home.replaceColorPdf.title=色の置換と反転
 | 
				
			||||||
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
 | 
					home.replaceColorPdf.desc=PDF内のテキストと背景の色を置き換え、PDFのフルカラーを反転してファイルサイズを縮小します。
 | 
				
			||||||
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
 | 
					replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
 | 
				
			||||||
replace-color.selectText.1=Replace or Invert color Options
 | 
					replace-color.selectText.1=色の置換または反転オプション
 | 
				
			||||||
replace-color.selectText.2=Default(Default high contrast colors)
 | 
					replace-color.selectText.2=デフォルト(デフォルトの高コントラスト色)
 | 
				
			||||||
replace-color.selectText.3=Custom(Customized colors)
 | 
					replace-color.selectText.3=カスタム(カスタマイズされた色)
 | 
				
			||||||
replace-color.selectText.4=Full-Invert(Invert all colors)
 | 
					replace-color.selectText.4=フル反転(すべての色を反転)
 | 
				
			||||||
replace-color.selectText.5=High contrast color options
 | 
					replace-color.selectText.5=高コントラストカラーオプション
 | 
				
			||||||
replace-color.selectText.6=white text on black background
 | 
					replace-color.selectText.6=黒背景に白文字
 | 
				
			||||||
replace-color.selectText.7=Black text on white background
 | 
					replace-color.selectText.7=白背景に黒文字
 | 
				
			||||||
replace-color.selectText.8=Yellow text on black background
 | 
					replace-color.selectText.8=黒背景に黄色文字
 | 
				
			||||||
replace-color.selectText.9=Green text on black background
 | 
					replace-color.selectText.9=黒背景に緑文字
 | 
				
			||||||
replace-color.selectText.10=Choose text Color
 | 
					replace-color.selectText.10=テキストの色を選択
 | 
				
			||||||
replace-color.selectText.11=Choose background Color
 | 
					replace-color.selectText.11=背景色を選択
 | 
				
			||||||
replace-color.submit=Replace
 | 
					replace-color.submit=置換
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -560,9 +560,9 @@ login.oauth2AccessDenied=アクセス拒否
 | 
				
			|||||||
login.oauth2InvalidTokenResponse=無効なトークン応答
 | 
					login.oauth2InvalidTokenResponse=無効なトークン応答
 | 
				
			||||||
login.oauth2InvalidIdToken=無効なIDトークン
 | 
					login.oauth2InvalidIdToken=無効なIDトークン
 | 
				
			||||||
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
 | 
					login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
 | 
				
			||||||
login.alreadyLoggedIn=You are already logged in to
 | 
					login.alreadyLoggedIn=すでにログインしています
 | 
				
			||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
 | 
					login.alreadyLoggedIn2=デバイスからログアウトしてもう一度お試しください。
 | 
				
			||||||
login.toManySessions=You have too many active sessions
 | 
					login.toManySessions=アクティブなセッションが多すぎます
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#auto-redact
 | 
					#auto-redact
 | 
				
			||||||
autoRedact.title=自動塗りつぶし
 | 
					autoRedact.title=自動塗りつぶし
 | 
				
			||||||
@ -578,8 +578,8 @@ autoRedact.submitButton=送信
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#showJS
 | 
					#showJS
 | 
				
			||||||
showJS.title=JavaScriptを表示
 | 
					showJS.title=Javascriptを表示
 | 
				
			||||||
showJS.header=JavaScriptを表示
 | 
					showJS.header=Javascriptを表示
 | 
				
			||||||
showJS.downloadJS=Javascriptをダウンロード
 | 
					showJS.downloadJS=Javascriptをダウンロード
 | 
				
			||||||
showJS.submit=表示
 | 
					showJS.submit=表示
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -757,7 +757,7 @@ certSign.showSig=署名を表示
 | 
				
			|||||||
certSign.reason=理由
 | 
					certSign.reason=理由
 | 
				
			||||||
certSign.location=場所
 | 
					certSign.location=場所
 | 
				
			||||||
certSign.name=名前
 | 
					certSign.name=名前
 | 
				
			||||||
certSign.showLogo=Show Logo
 | 
					certSign.showLogo=ロゴを表示
 | 
				
			||||||
certSign.submit=PDFに署名
 | 
					certSign.submit=PDFに署名
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -792,9 +792,9 @@ compare.highlightColor.2=ハイライトカラー 2:
 | 
				
			|||||||
compare.document.1=ドキュメント 1
 | 
					compare.document.1=ドキュメント 1
 | 
				
			||||||
compare.document.2=ドキュメント 2
 | 
					compare.document.2=ドキュメント 2
 | 
				
			||||||
compare.submit=比較
 | 
					compare.submit=比較
 | 
				
			||||||
compare.complex.message=One or both of the provided documents are large files, accuracy of comparison may be reduced
 | 
					compare.complex.message=提供された文書の一方または両方が大きなファイルであるため、比較の精度が低下する可能性があります。
 | 
				
			||||||
compare.large.file.message=One or Both of the provided documents are too large to process
 | 
					compare.large.file.message=提供された文書の1つまたは両方が大きすぎて処理できません
 | 
				
			||||||
compare.no.text.message=One or both of the selected PDFs have no text content. Please choose PDFs with text for comparison.
 | 
					compare.no.text.message=選択したPDFの1つまたは両方にテキストコンテンツがありません。比較するには、テキストを含むPDFを選択してください。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#BookToPDF
 | 
					#BookToPDF
 | 
				
			||||||
BookToPDF.title=書籍やコミックをPDFに変換
 | 
					BookToPDF.title=書籍やコミックをPDFに変換
 | 
				
			||||||
@ -803,8 +803,8 @@ BookToPDF.credit=calibreを使用
 | 
				
			|||||||
BookToPDF.submit=変換
 | 
					BookToPDF.submit=変換
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#PDFToBook
 | 
					#PDFToBook
 | 
				
			||||||
PDFToBook.title=書籍をPDFに変換
 | 
					PDFToBook.title=PDFを書籍に変換
 | 
				
			||||||
PDFToBook.header=書籍をPDFに変換
 | 
					PDFToBook.header=PDFを書籍に変換
 | 
				
			||||||
PDFToBook.selectText.1=フォーマット
 | 
					PDFToBook.selectText.1=フォーマット
 | 
				
			||||||
PDFToBook.credit=calibreを使用
 | 
					PDFToBook.credit=calibreを使用
 | 
				
			||||||
PDFToBook.submit=変換
 | 
					PDFToBook.submit=変換
 | 
				
			||||||
@ -817,17 +817,17 @@ sign.draw=署名を書く
 | 
				
			|||||||
sign.text=テキスト入力
 | 
					sign.text=テキスト入力
 | 
				
			||||||
sign.clear=クリア
 | 
					sign.clear=クリア
 | 
				
			||||||
sign.add=追加
 | 
					sign.add=追加
 | 
				
			||||||
sign.saved=Saved Signatures
 | 
					sign.saved=保存された署名
 | 
				
			||||||
sign.save=Save Signature
 | 
					sign.save=署名を保存
 | 
				
			||||||
sign.personalSigs=Personal Signatures
 | 
					sign.personalSigs=個人署名
 | 
				
			||||||
sign.sharedSigs=Shared Signatures
 | 
					sign.sharedSigs=共有署名
 | 
				
			||||||
sign.noSavedSigs=No saved signatures found
 | 
					sign.noSavedSigs=保存された署名が見つかりません
 | 
				
			||||||
sign.addToAll=Add to all pages
 | 
					sign.addToAll=すべてのページに追加
 | 
				
			||||||
sign.delete=Delete
 | 
					sign.delete=削除
 | 
				
			||||||
sign.first=First page
 | 
					sign.first=最初のページ
 | 
				
			||||||
sign.last=Last page
 | 
					sign.last=最後のページ
 | 
				
			||||||
sign.next=Next page
 | 
					sign.next=次のページ
 | 
				
			||||||
sign.previous=Previous page
 | 
					sign.previous=前のページ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#repair
 | 
					#repair
 | 
				
			||||||
repair.title=修復
 | 
					repair.title=修復
 | 
				
			||||||
@ -944,29 +944,39 @@ pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
 | 
				
			|||||||
multiTool.title=PDFマルチツール
 | 
					multiTool.title=PDFマルチツール
 | 
				
			||||||
multiTool.header=PDFマルチツール
 | 
					multiTool.header=PDFマルチツール
 | 
				
			||||||
multiTool.uploadPrompts=ファイル名
 | 
					multiTool.uploadPrompts=ファイル名
 | 
				
			||||||
multiTool.selectAll=Select All
 | 
					multiTool.selectAll=すべて選択
 | 
				
			||||||
multiTool.deselectAll=Deselect All
 | 
					multiTool.deselectAll=選択を解除
 | 
				
			||||||
multiTool.selectPages=Page Select
 | 
					multiTool.selectPages=ページ選択
 | 
				
			||||||
multiTool.selectedPages=Selected Pages
 | 
					multiTool.selectedPages=選択したページ
 | 
				
			||||||
multiTool.page=Page
 | 
					multiTool.page=ページ
 | 
				
			||||||
multiTool.deleteSelected=Delete Selected
 | 
					multiTool.deleteSelected=選択項目を削除
 | 
				
			||||||
multiTool.downloadAll=Export
 | 
					multiTool.downloadAll=エクスポート
 | 
				
			||||||
multiTool.downloadSelected=Export Selected
 | 
					multiTool.downloadSelected=選択項目をエクスポート
 | 
				
			||||||
 | 
					
 | 
				
			||||||
multiTool.insertPageBreak=Insert Page Break
 | 
					multiTool.insertPageBreak=改ページを挿入
 | 
				
			||||||
multiTool.addFile=Add File
 | 
					multiTool.addFile=ファイルを追加
 | 
				
			||||||
multiTool.rotateLeft=Rotate Left
 | 
					multiTool.rotateLeft=左回転
 | 
				
			||||||
multiTool.rotateRight=Rotate Right
 | 
					multiTool.rotateRight=右回転
 | 
				
			||||||
multiTool.split=Split
 | 
					multiTool.split=分割
 | 
				
			||||||
multiTool.moveLeft=Move Left
 | 
					multiTool.moveLeft=左に移動
 | 
				
			||||||
multiTool.moveRight=Move Right
 | 
					multiTool.moveRight=右に移動
 | 
				
			||||||
multiTool.delete=Delete
 | 
					multiTool.delete=削除
 | 
				
			||||||
multiTool.dragDropMessage=Page(s) Selected
 | 
					multiTool.dragDropMessage=選択されたページ
 | 
				
			||||||
multiTool.undo=Undo
 | 
					multiTool.undo=元に戻す
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=やり直す
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=このファイルはパスワードで保護されています。パスワードを入力してください:
 | 
				
			||||||
 | 
					decrypt.cancelled=PDFの操作がキャンセルされました: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=暗号化されたPDFにパスワードが指定されていません: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=正しいパスワードでもう一度お試しください。
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=PDFのパスワードが正しくないか、暗号化がサポートされていません: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=ファイルの処理中にエラーが発生しました。もう一度お試しください。
 | 
				
			||||||
 | 
					decrypt.serverError=復号化中にサーバーエラーが発生しました: {0}
 | 
				
			||||||
 | 
					decrypt.success=ファイルの暗号化が正常に完了しました。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=この機能は、<a href="{0}">マルチツール</a>でもご利用いただけます。強化されたページごとのUIと追加機能についてはこちらをご覧ください。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#view pdf
 | 
					#view pdf
 | 
				
			||||||
viewPdf.title=PDFを表示
 | 
					viewPdf.title=PDFを表示
 | 
				
			||||||
@ -1122,8 +1132,8 @@ pdfToPDFA.header=PDFをPDF/Aに変換
 | 
				
			|||||||
pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
 | 
					pdfToPDFA.credit=本サービスはPDF/Aの変換にqpdfを使用しています。
 | 
				
			||||||
pdfToPDFA.submit=変換
 | 
					pdfToPDFA.submit=変換
 | 
				
			||||||
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
 | 
					pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
 | 
				
			||||||
pdfToPDFA.outputFormat=Output format
 | 
					pdfToPDFA.outputFormat=出力形式
 | 
				
			||||||
pdfToPDFA.pdfWithDigitalSignature=PDF にはデジタル署名が含まれています。これは次の手順で削除されます。
 | 
					pdfToPDFA.pdfWithDigitalSignature=PDFにはデジタル署名が含まれています。これは次の手順で削除されます。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#PDFToWord
 | 
					#PDFToWord
 | 
				
			||||||
@ -1228,8 +1238,8 @@ licenses.license=ライセンス
 | 
				
			|||||||
survey.nav=アンケート
 | 
					survey.nav=アンケート
 | 
				
			||||||
survey.title=Stirling-PDFのアンケート
 | 
					survey.title=Stirling-PDFのアンケート
 | 
				
			||||||
survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください!
 | 
					survey.description=Stirling-PDFには追跡機能がないため、Stirling-PDFをより良くするために皆様の意見を聞かせてください!
 | 
				
			||||||
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
 | 
					survey.changes=Stirling-PDFは前回の調査から変更されました。詳細についてはこちらのブログ投稿をご覧ください。
 | 
				
			||||||
survey.changes2=With these changes we are getting paid business support and funding
 | 
					survey.changes2=これらの変更により私たちは有償のビジネスサポートと資金援助を受けています
 | 
				
			||||||
survey.please=アンケートにご協力ください!
 | 
					survey.please=アンケートにご協力ください!
 | 
				
			||||||
survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
 | 
					survey.disabled=(アンケートのポップアップは、次の更新では無効になりますが、ページの下部に表示されます。)
 | 
				
			||||||
survey.button=アンケートに答える
 | 
					survey.button=アンケートに答える
 | 
				
			||||||
@ -1257,61 +1267,61 @@ removeImage.removeImage=画像の削除
 | 
				
			|||||||
removeImage.submit=画像を削除
 | 
					removeImage.submit=画像を削除
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
splitByChapters.title=Split PDF by Chapters
 | 
					splitByChapters.title=PDFをチャプターごとに分割
 | 
				
			||||||
splitByChapters.header=Split PDF by Chapters
 | 
					splitByChapters.header=PDFをチャプターごとに分割
 | 
				
			||||||
splitByChapters.bookmarkLevel=Bookmark Level
 | 
					splitByChapters.bookmarkLevel=ブックマークレベル
 | 
				
			||||||
splitByChapters.includeMetadata=Include Metadata
 | 
					splitByChapters.includeMetadata=メタデータを含める
 | 
				
			||||||
splitByChapters.allowDuplicates=Allow Duplicates
 | 
					splitByChapters.allowDuplicates=重複を許可する
 | 
				
			||||||
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
 | 
					splitByChapters.desc.1=このツールは、チャプター構造に基づいてPDFファイルを複数のPDFに分割します。
 | 
				
			||||||
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.2=ブックマークレベル:分割に使用するブックマークのレベルを選択します(最上位レベルの場合は0、第2レベルの場合は1など)。
 | 
				
			||||||
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
 | 
					splitByChapters.desc.3=メタデータを含める:チェックすると、元のPDFのメタデータが各分割PDFに含まれます。
 | 
				
			||||||
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
 | 
					splitByChapters.desc.4=重複を許可:チェックすると同じページ上の複数のブックマークから個別のPDFを作成できます。
 | 
				
			||||||
splitByChapters.submit=Split PDF
 | 
					splitByChapters.submit=PDFを分割
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#File Chooser
 | 
					#File Chooser
 | 
				
			||||||
fileChooser.click=Click
 | 
					fileChooser.click=クリック
 | 
				
			||||||
fileChooser.or=or
 | 
					fileChooser.or=または
 | 
				
			||||||
fileChooser.dragAndDrop=Drag & Drop
 | 
					fileChooser.dragAndDrop=ドラッグ&ドロップ
 | 
				
			||||||
fileChooser.hoveredDragAndDrop=Drag & Drop file(s) here
 | 
					fileChooser.hoveredDragAndDrop=ファイルをここにドラッグ&ドロップ
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#release notes
 | 
					#release notes
 | 
				
			||||||
releases.footer=Releases
 | 
					releases.footer=リリース
 | 
				
			||||||
releases.title=Release Notes
 | 
					releases.title=リリースノート
 | 
				
			||||||
releases.header=Release Notes
 | 
					releases.header=リリースノート
 | 
				
			||||||
releases.current.version=Current Release
 | 
					releases.current.version=現在のリリース
 | 
				
			||||||
releases.note=Release notes are only available in English
 | 
					releases.note=リリースノートは英語でのみで提供されています
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#Validate Signature
 | 
					#Validate Signature
 | 
				
			||||||
validateSignature.title=Validate PDF Signatures
 | 
					validateSignature.title=PDF署名の検証
 | 
				
			||||||
validateSignature.header=Validate Digital Signatures
 | 
					validateSignature.header=デジタル署名の検証
 | 
				
			||||||
validateSignature.selectPDF=Select signed PDF file
 | 
					validateSignature.selectPDF=署名済みPDFファイルを選択
 | 
				
			||||||
validateSignature.submit=Validate Signatures
 | 
					validateSignature.submit=署名の検証
 | 
				
			||||||
validateSignature.results=Validation Results
 | 
					validateSignature.results=検証結果
 | 
				
			||||||
validateSignature.status=Status
 | 
					validateSignature.status=状態
 | 
				
			||||||
validateSignature.signer=Signer
 | 
					validateSignature.signer=署名者
 | 
				
			||||||
validateSignature.date=Date
 | 
					validateSignature.date=日付
 | 
				
			||||||
validateSignature.reason=Reason
 | 
					validateSignature.reason=理由
 | 
				
			||||||
validateSignature.location=Location
 | 
					validateSignature.location=場所
 | 
				
			||||||
validateSignature.noSignatures=No digital signatures found in this document
 | 
					validateSignature.noSignatures=この文書にはデジタル署名が見つかりません
 | 
				
			||||||
validateSignature.status.valid=Valid
 | 
					validateSignature.status.valid=有効
 | 
				
			||||||
validateSignature.status.invalid=Invalid
 | 
					validateSignature.status.invalid=無効
 | 
				
			||||||
validateSignature.chain.invalid=Certificate chain validation failed - cannot verify signer's identity
 | 
					validateSignature.chain.invalid=証明書チェーンの検証に失敗しました - 署名者の身元を確認できません
 | 
				
			||||||
validateSignature.trust.invalid=Certificate not in trust store - source cannot be verified
 | 
					validateSignature.trust.invalid=証明書が信頼ストアにありません - ソースを検証できません
 | 
				
			||||||
validateSignature.cert.expired=Certificate has expired
 | 
					validateSignature.cert.expired=証明書の有効期限が切れています
 | 
				
			||||||
validateSignature.cert.revoked=Certificate has been revoked
 | 
					validateSignature.cert.revoked=証明書は取り消されました
 | 
				
			||||||
validateSignature.signature.info=Signature Information
 | 
					validateSignature.signature.info=署名情報
 | 
				
			||||||
validateSignature.signature=Signature
 | 
					validateSignature.signature=署名
 | 
				
			||||||
validateSignature.signature.mathValid=Signature is mathematically valid BUT:
 | 
					validateSignature.signature.mathValid=署名は数学的には有効ですが:
 | 
				
			||||||
validateSignature.selectCustomCert=Custom Certificate File X.509 (Optional)
 | 
					validateSignature.selectCustomCert=カスタム証明書ファイル X.509 (オプション)
 | 
				
			||||||
validateSignature.cert.info=Certificate Details
 | 
					validateSignature.cert.info=証明書の詳細
 | 
				
			||||||
validateSignature.cert.issuer=Issuer
 | 
					validateSignature.cert.issuer=発行者
 | 
				
			||||||
validateSignature.cert.subject=Subject
 | 
					validateSignature.cert.subject=主題
 | 
				
			||||||
validateSignature.cert.serialNumber=Serial Number
 | 
					validateSignature.cert.serialNumber=シリアルナンバー
 | 
				
			||||||
validateSignature.cert.validFrom=Valid From
 | 
					validateSignature.cert.validFrom=有効開始日
 | 
				
			||||||
validateSignature.cert.validUntil=Valid Until
 | 
					validateSignature.cert.validUntil=有効期限
 | 
				
			||||||
validateSignature.cert.algorithm=Algorithm
 | 
					validateSignature.cert.algorithm=アルゴリズム
 | 
				
			||||||
validateSignature.cert.keySize=Key Size
 | 
					validateSignature.cert.keySize=キーサイズ
 | 
				
			||||||
validateSignature.cert.version=Version
 | 
					validateSignature.cert.version=バージョン
 | 
				
			||||||
validateSignature.cert.keyUsage=Key Usage
 | 
					validateSignature.cert.keyUsage=キーの使用法
 | 
				
			||||||
validateSignature.cert.selfSigned=Self-Signed
 | 
					validateSignature.cert.selfSigned=自己署名
 | 
				
			||||||
validateSignature.cert.bits=bits
 | 
					validateSignature.cert.bits=ビット
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
 | 
				
			|||||||
multiTool.undo=Undo
 | 
					multiTool.undo=Undo
 | 
				
			||||||
multiTool.redo=Redo
 | 
					multiTool.redo=Redo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#decrypt
 | 
				
			||||||
 | 
					decrypt.passwordPrompt=This file is password-protected. Please enter the password:
 | 
				
			||||||
 | 
					decrypt.cancelled=Operation cancelled for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.noPassword=No password provided for encrypted PDF: {0}
 | 
				
			||||||
 | 
					decrypt.invalidPassword=Please try again with the correct password.
 | 
				
			||||||
 | 
					decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0}
 | 
				
			||||||
 | 
					decrypt.unexpectedError=There was an error processing the file. Please try again.
 | 
				
			||||||
 | 
					decrypt.serverError=Server error while decrypting: {0}
 | 
				
			||||||
 | 
					decrypt.success=File decrypted successfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#multiTool-advert
 | 
					#multiTool-advert
 | 
				
			||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
					multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -65,6 +65,7 @@
 | 
				
			|||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
  margin: -20px;
 | 
					  margin: -20px;
 | 
				
			||||||
  padding: 20px;
 | 
					  padding: 20px;
 | 
				
			||||||
 | 
					  box-sizing:content-box;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.feature-group-container.animated-group {
 | 
					.feature-group-container.animated-group {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										116
									
								
								src/main/resources/static/js/DecryptFiles.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/main/resources/static/js/DecryptFiles.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					export class DecryptFile {
 | 
				
			||||||
 | 
					  async decryptFile(file, requiresPassword) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const formData = new FormData();
 | 
				
			||||||
 | 
					      formData.append('fileInput', file);
 | 
				
			||||||
 | 
					      if (requiresPassword) {
 | 
				
			||||||
 | 
					        const password = prompt(`${window.decrypt.passwordPrompt}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (password === null) {
 | 
				
			||||||
 | 
					          // User cancelled
 | 
				
			||||||
 | 
					          console.error(`Password prompt cancelled for PDF: ${file.name}`);
 | 
				
			||||||
 | 
					          return null; // No file to return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!password) {
 | 
				
			||||||
 | 
					          // No password provided
 | 
				
			||||||
 | 
					          console.error(`No password provided for encrypted PDF: ${file.name}`);
 | 
				
			||||||
 | 
					          this.showErrorBanner(
 | 
				
			||||||
 | 
					            `${window.decrypt.noPassword.replace('{0}', file.name)}`,
 | 
				
			||||||
 | 
					            '',
 | 
				
			||||||
 | 
					            `${window.decrypt.unexpectedError}`
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					          return null; // No file to return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formData.append('password', password);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Send decryption request
 | 
				
			||||||
 | 
					      const response = await fetch('/api/v1/security/remove-password', {
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        body: formData,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (response.ok) {
 | 
				
			||||||
 | 
					        const decryptedBlob = await response.blob();
 | 
				
			||||||
 | 
					        this.removeErrorBanner();
 | 
				
			||||||
 | 
					        return new File([decryptedBlob], file.name, {type: 'application/pdf'});
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        const errorText = await response.text();
 | 
				
			||||||
 | 
					        console.error(`${window.decrypt.invalidPassword} ${errorText}`);
 | 
				
			||||||
 | 
					        this.showErrorBanner(
 | 
				
			||||||
 | 
					          `${window.decrypt.invalidPassword}`,
 | 
				
			||||||
 | 
					          errorText,
 | 
				
			||||||
 | 
					          `${window.decrypt.invalidPasswordHeader.replace('{0}', file.name)}`
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return null; // No file to return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      // Handle network or unexpected errors
 | 
				
			||||||
 | 
					      console.error(`Failed to decrypt PDF: ${file.name}`, error);
 | 
				
			||||||
 | 
					      this.showErrorBanner(
 | 
				
			||||||
 | 
					        `${window.decrypt.unexpectedError.replace('{0}', file.name)}`,
 | 
				
			||||||
 | 
					        `${error.message || window.decrypt.unexpectedError}`,
 | 
				
			||||||
 | 
					        error
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return null; // No file to return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async checkFileEncrypted(file) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (file.type !== 'application/pdf') {
 | 
				
			||||||
 | 
					        return {isEncrypted: false, requiresPassword: false};
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					      const arrayBuffer = await file.arrayBuffer();
 | 
				
			||||||
 | 
					      const arrayBufferForPdfLib = arrayBuffer.slice(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const loadingTask = pdfjsLib.getDocument({
 | 
				
			||||||
 | 
					        data: arrayBuffer,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await loadingTask.promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        //Uses PDFLib.PDFDocument to check if unpassworded but encrypted
 | 
				
			||||||
 | 
					        const pdfDoc = await PDFLib.PDFDocument.load(arrayBufferForPdfLib);
 | 
				
			||||||
 | 
					        return {isEncrypted: false, requiresPassword: false};
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        if (error.message.includes('Input document to `PDFDocument.load` is encrypted')) {
 | 
				
			||||||
 | 
					          return {isEncrypted: true, requiresPassword: false};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        console.error('Error checking encryption:', error);
 | 
				
			||||||
 | 
					        throw new Error('Failed to determine if the file is encrypted.');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      if (error.name === 'PasswordException') {
 | 
				
			||||||
 | 
					        if (error.code === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
 | 
				
			||||||
 | 
					          return {isEncrypted: true, requiresPassword: true};
 | 
				
			||||||
 | 
					        } else if (error.code === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
 | 
				
			||||||
 | 
					          return {isEncrypted: true, requiresPassword: false};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      console.error('Error checking encryption:', error);
 | 
				
			||||||
 | 
					      throw new Error('Failed to determine if the file is encrypted.');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  showErrorBanner(message, stackTrace, error) {
 | 
				
			||||||
 | 
					    const errorContainer = document.getElementById('errorContainer');
 | 
				
			||||||
 | 
					    errorContainer.style.display = 'block'; // Display the banner
 | 
				
			||||||
 | 
					    errorContainer.querySelector('.alert-heading').textContent = error;
 | 
				
			||||||
 | 
					    errorContainer.querySelector('p').textContent = message;
 | 
				
			||||||
 | 
					    document.querySelector('#traceContent').textContent = stackTrace;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  removeErrorBanner() {
 | 
				
			||||||
 | 
					    const errorContainer = document.getElementById('errorContainer');
 | 
				
			||||||
 | 
					    errorContainer.style.display = 'none'; // Hide the banner
 | 
				
			||||||
 | 
					    errorContainer.querySelector('.alert-heading').textContent = '';
 | 
				
			||||||
 | 
					    errorContainer.querySelector('p').textContent = '';
 | 
				
			||||||
 | 
					    document.querySelector('#traceContent').textContent = '';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/main/resources/static/js/download.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/main/resources/static/js/download.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					document.getElementById('download-pdf').addEventListener('click', async () => {
 | 
				
			||||||
 | 
					  const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
 | 
				
			||||||
 | 
					  let decryptedFile = modifiedPdf;
 | 
				
			||||||
 | 
					  let isEncrypted = false;
 | 
				
			||||||
 | 
					  let requiresPassword = false;
 | 
				
			||||||
 | 
					  await this.decryptFile
 | 
				
			||||||
 | 
					    .checkFileEncrypted(decryptedFile)
 | 
				
			||||||
 | 
					    .then((result) => {
 | 
				
			||||||
 | 
					      isEncrypted = result.isEncrypted;
 | 
				
			||||||
 | 
					      requiresPassword = result.requiresPassword;
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch((error) => {
 | 
				
			||||||
 | 
					      console.error(error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  if (decryptedFile.type === 'application/pdf' && isEncrypted) {
 | 
				
			||||||
 | 
					    decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword);
 | 
				
			||||||
 | 
					    if (!decryptedFile) {
 | 
				
			||||||
 | 
					      throw new Error('File decryption failed.');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const modifiedPdfBytes = await modifiedPdf.save();
 | 
				
			||||||
 | 
					  const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
 | 
				
			||||||
 | 
					  const link = document.createElement('a');
 | 
				
			||||||
 | 
					  link.href = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					  link.download = originalFileName + '_signed.pdf';
 | 
				
			||||||
 | 
					  link.click();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -42,7 +42,7 @@
 | 
				
			|||||||
      event.preventDefault();
 | 
					      event.preventDefault();
 | 
				
			||||||
      firstErrorOccurred = false;
 | 
					      firstErrorOccurred = false;
 | 
				
			||||||
      const url = this.action;
 | 
					      const url = this.action;
 | 
				
			||||||
      const files = $('#fileInput-input')[0].files;
 | 
					      let files = $('#fileInput-input')[0].files;
 | 
				
			||||||
      const formData = new FormData(this);
 | 
					      const formData = new FormData(this);
 | 
				
			||||||
      const submitButton = document.getElementById('submitBtn');
 | 
					      const submitButton = document.getElementById('submitBtn');
 | 
				
			||||||
      const showGameBtn = document.getElementById('show-game-btn');
 | 
					      const showGameBtn = document.getElementById('show-game-btn');
 | 
				
			||||||
@ -71,6 +71,16 @@
 | 
				
			|||||||
      }, 5000);
 | 
					      }, 5000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
 | 
					        if (!url.includes('remove-password')) {
 | 
				
			||||||
 | 
					          // Check if any PDF files are encrypted and handle decryption if necessary
 | 
				
			||||||
 | 
					          const decryptedFiles = await checkAndDecryptFiles(url, files);
 | 
				
			||||||
 | 
					          files = decryptedFiles;
 | 
				
			||||||
 | 
					          // Append decrypted files to formData
 | 
				
			||||||
 | 
					          decryptedFiles.forEach((file, index) => {
 | 
				
			||||||
 | 
					            formData.set(`fileInput`, file);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        submitButton.textContent = 'Processing...';
 | 
					        submitButton.textContent = 'Processing...';
 | 
				
			||||||
        submitButton.disabled = true;
 | 
					        submitButton.disabled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -133,6 +143,98 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function checkAndDecryptFiles(url, files) {
 | 
				
			||||||
 | 
					    const decryptedFiles = [];
 | 
				
			||||||
 | 
					    pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Extract the base URL
 | 
				
			||||||
 | 
					    const baseUrl = new URL(url);
 | 
				
			||||||
 | 
					    let removePasswordUrl = `${baseUrl.origin}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check if there's a path before /api/
 | 
				
			||||||
 | 
					    const apiIndex = baseUrl.pathname.indexOf('/api/');
 | 
				
			||||||
 | 
					    if (apiIndex > 0) {
 | 
				
			||||||
 | 
					      removePasswordUrl += baseUrl.pathname.substring(0, apiIndex);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Append the new endpoint
 | 
				
			||||||
 | 
					    removePasswordUrl += '/api/v1/security/remove-password';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`Remove password URL: ${removePasswordUrl}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const file of files) {
 | 
				
			||||||
 | 
					      console.log(`Processing file: ${file.name}`);
 | 
				
			||||||
 | 
					      if (file.type !== 'application/pdf') {
 | 
				
			||||||
 | 
					        console.log(`Skipping non-PDF file: ${file.name}`);
 | 
				
			||||||
 | 
					        decryptedFiles.push(file);
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const arrayBuffer = await file.arrayBuffer();
 | 
				
			||||||
 | 
					        const loadingTask = pdfjsLib.getDocument({data: arrayBuffer});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log(`Attempting to load PDF: ${file.name}`);
 | 
				
			||||||
 | 
					        const pdf = await loadingTask.promise;
 | 
				
			||||||
 | 
					        console.log(`File is not encrypted: ${file.name}`);
 | 
				
			||||||
 | 
					        decryptedFiles.push(file); // If no error, file is not encrypted
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        if (error.name === 'PasswordException' && error.code === 1) {
 | 
				
			||||||
 | 
					          console.log(`PDF requires password: ${file.name}`, error);
 | 
				
			||||||
 | 
					          console.log(`Attempting to remove password from PDF: ${file.name} with password.`);
 | 
				
			||||||
 | 
					          const password = prompt(`${window.translations.decrypt.passwordPrompt}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (!password) {
 | 
				
			||||||
 | 
					            console.error(`No password provided for encrypted PDF: ${file.name}`);
 | 
				
			||||||
 | 
					            showErrorBanner(
 | 
				
			||||||
 | 
					              `${window.translations.decrypt.noPassword.replace('{0}', file.name)}`,
 | 
				
			||||||
 | 
					              `${window.translations.decrypt.unexpectedError}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            throw error;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          try {
 | 
				
			||||||
 | 
					            // Prepare FormData for the decryption request
 | 
				
			||||||
 | 
					            const formData = new FormData();
 | 
				
			||||||
 | 
					            formData.append('fileInput', file);
 | 
				
			||||||
 | 
					            formData.append('password', password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Use handleSingleDownload to send the request
 | 
				
			||||||
 | 
					            const decryptionResult = await fetch(removePasswordUrl, {method: 'POST', body: formData});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (decryptionResult && decryptionResult.blob) {
 | 
				
			||||||
 | 
					              const decryptedBlob = await decryptionResult.blob();
 | 
				
			||||||
 | 
					              const decryptedFile = new File([decryptedBlob], file.name, {type: 'application/pdf'});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              /*    // Create a link element to download the file
 | 
				
			||||||
 | 
					            const link = document.createElement('a');
 | 
				
			||||||
 | 
					            link.href = URL.createObjectURL(decryptedBlob);
 | 
				
			||||||
 | 
					            link.download = 'test.pdf';
 | 
				
			||||||
 | 
					            document.body.appendChild(link);
 | 
				
			||||||
 | 
					            link.click();
 | 
				
			||||||
 | 
					            document.body.removeChild(link);
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					              decryptedFiles.push(decryptedFile);
 | 
				
			||||||
 | 
					              console.log(`Successfully decrypted PDF: ${file.name}`);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              throw new Error('Decryption failed: No valid response from server');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } catch (decryptError) {
 | 
				
			||||||
 | 
					            console.error(`Failed to decrypt PDF: ${file.name}`, decryptError);
 | 
				
			||||||
 | 
					            showErrorBanner(
 | 
				
			||||||
 | 
					              `${window.translations.invalidPasswordHeader.replace('{0}', file.name)}`,
 | 
				
			||||||
 | 
					              `${window.translations.invalidPassword}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            throw decryptError;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          console.log(`Error loading PDF: ${file.name}`, error);
 | 
				
			||||||
 | 
					          throw error;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return decryptedFiles;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
 | 
					  async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
 | 
				
			||||||
    const startTime = performance.now();
 | 
					    const startTime = performance.now();
 | 
				
			||||||
    const file = formData.get('fileInput');
 | 
					    const file = formData.get('fileInput');
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
const DraggableUtils = {
 | 
					const DraggableUtils = {
 | 
				
			||||||
  boxDragContainer: document.getElementById("box-drag-container"),
 | 
					  boxDragContainer: document.getElementById('box-drag-container'),
 | 
				
			||||||
  pdfCanvas: document.getElementById("pdf-canvas"),
 | 
					  pdfCanvas: document.getElementById('pdf-canvas'),
 | 
				
			||||||
  nextId: 0,
 | 
					  nextId: 0,
 | 
				
			||||||
  pdfDoc: null,
 | 
					  pdfDoc: null,
 | 
				
			||||||
  pageIndex: 0,
 | 
					  pageIndex: 0,
 | 
				
			||||||
@ -9,19 +9,17 @@ const DraggableUtils = {
 | 
				
			|||||||
  lastInteracted: null,
 | 
					  lastInteracted: null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  init() {
 | 
					  init() {
 | 
				
			||||||
    interact(".draggable-canvas")
 | 
					    interact('.draggable-canvas')
 | 
				
			||||||
      .draggable({
 | 
					      .draggable({
 | 
				
			||||||
        listeners: {
 | 
					        listeners: {
 | 
				
			||||||
          move: (event) => {
 | 
					          move: (event) => {
 | 
				
			||||||
            const target = event.target;
 | 
					            const target = event.target;
 | 
				
			||||||
            const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
 | 
					            const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx;
 | 
				
			||||||
              + event.dx;
 | 
					            const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy;
 | 
				
			||||||
            const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
 | 
					 | 
				
			||||||
              + event.dy;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            target.style.transform = `translate(${x}px, ${y}px)`;
 | 
					            target.style.transform = `translate(${x}px, ${y}px)`;
 | 
				
			||||||
            target.setAttribute("data-bs-x", x);
 | 
					            target.setAttribute('data-bs-x', x);
 | 
				
			||||||
            target.setAttribute("data-bs-y", y);
 | 
					            target.setAttribute('data-bs-y', y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.onInteraction(target);
 | 
					            this.onInteraction(target);
 | 
				
			||||||
            //update the last interacted element
 | 
					            //update the last interacted element
 | 
				
			||||||
@ -30,12 +28,12 @@ const DraggableUtils = {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .resizable({
 | 
					      .resizable({
 | 
				
			||||||
        edges: { left: true, right: true, bottom: true, top: true },
 | 
					        edges: {left: true, right: true, bottom: true, top: true},
 | 
				
			||||||
        listeners: {
 | 
					        listeners: {
 | 
				
			||||||
          move: (event) => {
 | 
					          move: (event) => {
 | 
				
			||||||
            var target = event.target;
 | 
					            var target = event.target;
 | 
				
			||||||
            var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
 | 
					            var x = parseFloat(target.getAttribute('data-bs-x')) || 0;
 | 
				
			||||||
            var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
 | 
					            var y = parseFloat(target.getAttribute('data-bs-y')) || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // check if control key is pressed
 | 
					            // check if control key is pressed
 | 
				
			||||||
            if (event.ctrlKey) {
 | 
					            if (event.ctrlKey) {
 | 
				
			||||||
@ -44,8 +42,7 @@ const DraggableUtils = {
 | 
				
			|||||||
              let width = event.rect.width;
 | 
					              let width = event.rect.width;
 | 
				
			||||||
              let height = event.rect.height;
 | 
					              let height = event.rect.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              if (Math.abs(event.deltaRect.width) >= Math.abs(
 | 
					              if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
 | 
				
			||||||
                event.deltaRect.height)) {
 | 
					 | 
				
			||||||
                height = width / aspectRatio;
 | 
					                height = width / aspectRatio;
 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                width = height * aspectRatio;
 | 
					                width = height * aspectRatio;
 | 
				
			||||||
@ -55,19 +52,18 @@ const DraggableUtils = {
 | 
				
			|||||||
              event.rect.height = height;
 | 
					              event.rect.height = height;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            target.style.width = event.rect.width + "px";
 | 
					            target.style.width = event.rect.width + 'px';
 | 
				
			||||||
            target.style.height = event.rect.height + "px";
 | 
					            target.style.height = event.rect.height + 'px';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // translate when resizing from top or left edges
 | 
					            // translate when resizing from top or left edges
 | 
				
			||||||
            x += event.deltaRect.left;
 | 
					            x += event.deltaRect.left;
 | 
				
			||||||
            y += event.deltaRect.top;
 | 
					            y += event.deltaRect.top;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            target.style.transform = "translate(" + x + "px," + y + "px)";
 | 
					            target.style.transform = 'translate(' + x + 'px,' + y + 'px)';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            target.setAttribute("data-bs-x", x);
 | 
					            target.setAttribute('data-bs-x', x);
 | 
				
			||||||
            target.setAttribute("data-bs-y", y);
 | 
					            target.setAttribute('data-bs-y', y);
 | 
				
			||||||
            target.textContent = Math.round(event.rect.width) + "\u00D7"
 | 
					            target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height);
 | 
				
			||||||
              + Math.round(event.rect.height);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.onInteraction(target);
 | 
					            this.onInteraction(target);
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
@ -75,7 +71,7 @@ const DraggableUtils = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        modifiers: [
 | 
					        modifiers: [
 | 
				
			||||||
          interact.modifiers.restrictSize({
 | 
					          interact.modifiers.restrictSize({
 | 
				
			||||||
            min: { width: 5, height: 5 },
 | 
					            min: {width: 5, height: 5},
 | 
				
			||||||
          }),
 | 
					          }),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        inertia: true,
 | 
					        inertia: true,
 | 
				
			||||||
@ -95,8 +91,8 @@ const DraggableUtils = {
 | 
				
			|||||||
        const stepY = target.offsetHeight * 0.05;
 | 
					        const stepY = target.offsetHeight * 0.05;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get the current x and y coordinates
 | 
					        // Get the current x and y coordinates
 | 
				
			||||||
        let x = (parseFloat(target.getAttribute('data-bs-x')) || 0);
 | 
					        let x = parseFloat(target.getAttribute('data-bs-x')) || 0;
 | 
				
			||||||
        let y = (parseFloat(target.getAttribute('data-bs-y')) || 0);
 | 
					        let y = parseFloat(target.getAttribute('data-bs-y')) || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check which key was pressed and update the coordinates accordingly
 | 
					        // Check which key was pressed and update the coordinates accordingly
 | 
				
			||||||
        switch (event.key) {
 | 
					        switch (event.key) {
 | 
				
			||||||
@ -135,15 +131,15 @@ const DraggableUtils = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createDraggableCanvas() {
 | 
					  createDraggableCanvas() {
 | 
				
			||||||
    const createdCanvas = document.createElement("canvas");
 | 
					    const createdCanvas = document.createElement('canvas');
 | 
				
			||||||
    createdCanvas.id = `draggable-canvas-${this.nextId++}`;
 | 
					    createdCanvas.id = `draggable-canvas-${this.nextId++}`;
 | 
				
			||||||
    createdCanvas.classList.add("draggable-canvas");
 | 
					    createdCanvas.classList.add('draggable-canvas');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const x = 0;
 | 
					    const x = 0;
 | 
				
			||||||
    const y = 20;
 | 
					    const y = 20;
 | 
				
			||||||
    createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
 | 
					    createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
 | 
				
			||||||
    createdCanvas.setAttribute("data-bs-x", x);
 | 
					    createdCanvas.setAttribute('data-bs-x', x);
 | 
				
			||||||
    createdCanvas.setAttribute("data-bs-y", y);
 | 
					    createdCanvas.setAttribute('data-bs-y', y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //Click element in order to enable arrow keys
 | 
					    //Click element in order to enable arrow keys
 | 
				
			||||||
    createdCanvas.addEventListener('click', () => {
 | 
					    createdCanvas.addEventListener('click', () => {
 | 
				
			||||||
@ -186,29 +182,29 @@ const DraggableUtils = {
 | 
				
			|||||||
          newHeight = newHeight * scaleMultiplier;
 | 
					          newHeight = newHeight * scaleMultiplier;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        createdCanvas.style.width = newWidth + "px";
 | 
					        createdCanvas.style.width = newWidth + 'px';
 | 
				
			||||||
        createdCanvas.style.height = newHeight + "px";
 | 
					        createdCanvas.style.height = newHeight + 'px';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var myContext = createdCanvas.getContext("2d");
 | 
					        var myContext = createdCanvas.getContext('2d');
 | 
				
			||||||
        myContext.drawImage(myImage, 0, 0);
 | 
					        myContext.drawImage(myImage, 0, 0);
 | 
				
			||||||
        resolve(createdCanvas);
 | 
					        resolve(createdCanvas);
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  deleteAllDraggableCanvases() {
 | 
					  deleteAllDraggableCanvases() {
 | 
				
			||||||
    this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove());
 | 
					    this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove());
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  async addAllPagesDraggableCanvas(element) {
 | 
					  async addAllPagesDraggableCanvas(element) {
 | 
				
			||||||
    if (element) {
 | 
					    if (element) {
 | 
				
			||||||
      let currentPage = this.pageIndex
 | 
					      let currentPage = this.pageIndex;
 | 
				
			||||||
      if (!this.elementAllPages.includes(element)) {
 | 
					      if (!this.elementAllPages.includes(element)) {
 | 
				
			||||||
        this.elementAllPages.push(element)
 | 
					        this.elementAllPages.push(element);
 | 
				
			||||||
        element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)';
 | 
					        element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)';
 | 
				
			||||||
        let newElement = {
 | 
					        let newElement = {
 | 
				
			||||||
          "element": element,
 | 
					          element: element,
 | 
				
			||||||
          "offsetWidth": element.width,
 | 
					          offsetWidth: element.width,
 | 
				
			||||||
          "offsetHeight": element.height
 | 
					          offsetHeight: element.height,
 | 
				
			||||||
        }
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let pagesMap = this.documentsMap.get(this.pdfDoc);
 | 
					        let pagesMap = this.documentsMap.get(this.pdfDoc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -216,21 +212,20 @@ const DraggableUtils = {
 | 
				
			|||||||
          pagesMap = {};
 | 
					          pagesMap = {};
 | 
				
			||||||
          this.documentsMap.set(this.pdfDoc, pagesMap);
 | 
					          this.documentsMap.set(this.pdfDoc, pagesMap);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let page = this.pageIndex
 | 
					        let page = this.pageIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
 | 
					        for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (pagesMap[`${pageIndex}-offsetWidth`]) {
 | 
					          if (pagesMap[`${pageIndex}-offsetWidth`]) {
 | 
				
			||||||
            if (!pagesMap[pageIndex].includes(newElement)) {
 | 
					            if (!pagesMap[pageIndex].includes(newElement)) {
 | 
				
			||||||
              pagesMap[pageIndex].push(newElement);
 | 
					              pagesMap[pageIndex].push(newElement);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            pagesMap[pageIndex] = []
 | 
					            pagesMap[pageIndex] = [];
 | 
				
			||||||
            pagesMap[pageIndex].push(newElement)
 | 
					            pagesMap[pageIndex].push(newElement);
 | 
				
			||||||
            pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`];
 | 
					            pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`];
 | 
				
			||||||
            pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`];
 | 
					            pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`];
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          await this.goToPage(pageIndex)
 | 
					          await this.goToPage(pageIndex);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        const index = this.elementAllPages.indexOf(element);
 | 
					        const index = this.elementAllPages.indexOf(element);
 | 
				
			||||||
@ -247,17 +242,17 @@ const DraggableUtils = {
 | 
				
			|||||||
        for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
 | 
					        for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
 | 
				
			||||||
          if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) {
 | 
					          if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) {
 | 
				
			||||||
            const pageElements = pagesMap[pageIndex];
 | 
					            const pageElements = pagesMap[pageIndex];
 | 
				
			||||||
            pageElements.forEach(elementPage => {
 | 
					            pageElements.forEach((elementPage) => {
 | 
				
			||||||
              const elementIndex = pageElements.findIndex(elementPage => elementPage['element'].id === element.id);
 | 
					              const elementIndex = pageElements.findIndex((elementPage) => elementPage['element'].id === element.id);
 | 
				
			||||||
              if (elementIndex !== -1) {
 | 
					              if (elementIndex !== -1) {
 | 
				
			||||||
                pageElements.splice(elementIndex, 1);
 | 
					                pageElements.splice(elementIndex, 1);
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          await this.goToPage(pageIndex)
 | 
					          await this.goToPage(pageIndex);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      await this.goToPage(currentPage)
 | 
					      await this.goToPage(currentPage);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  deleteDraggableCanvas(element) {
 | 
					  deleteDraggableCanvas(element) {
 | 
				
			||||||
@ -271,7 +266,7 @@ const DraggableUtils = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  getLastInteracted() {
 | 
					  getLastInteracted() {
 | 
				
			||||||
    return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type");
 | 
					    return this.boxDragContainer.querySelector('.draggable-canvas:last-of-type');
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  storePageContents() {
 | 
					  storePageContents() {
 | 
				
			||||||
@ -280,7 +275,7 @@ const DraggableUtils = {
 | 
				
			|||||||
      pagesMap = {};
 | 
					      pagesMap = {};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const elements = [...this.boxDragContainer.querySelectorAll(".draggable-canvas")];
 | 
					    const elements = [...this.boxDragContainer.querySelectorAll('.draggable-canvas')];
 | 
				
			||||||
    const draggablesData = elements.map((el) => {
 | 
					    const draggablesData = elements.map((el) => {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        element: el,
 | 
					        element: el,
 | 
				
			||||||
@ -291,8 +286,8 @@ const DraggableUtils = {
 | 
				
			|||||||
    elements.forEach((el) => this.boxDragContainer.removeChild(el));
 | 
					    elements.forEach((el) => this.boxDragContainer.removeChild(el));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pagesMap[this.pageIndex] = draggablesData;
 | 
					    pagesMap[this.pageIndex] = draggablesData;
 | 
				
			||||||
    pagesMap[this.pageIndex + "-offsetWidth"] = this.pdfCanvas.offsetWidth;
 | 
					    pagesMap[this.pageIndex + '-offsetWidth'] = this.pdfCanvas.offsetWidth;
 | 
				
			||||||
    pagesMap[this.pageIndex + "-offsetHeight"] = this.pdfCanvas.offsetHeight;
 | 
					    pagesMap[this.pageIndex + '-offsetHeight'] = this.pdfCanvas.offsetHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.documentsMap.set(this.pdfDoc, pagesMap);
 | 
					    this.documentsMap.set(this.pdfDoc, pagesMap);
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@ -329,8 +324,8 @@ const DraggableUtils = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // render the page onto the canvas
 | 
					    // render the page onto the canvas
 | 
				
			||||||
    var renderContext = {
 | 
					    var renderContext = {
 | 
				
			||||||
      canvasContext: this.pdfCanvas.getContext("2d"),
 | 
					      canvasContext: this.pdfCanvas.getContext('2d'),
 | 
				
			||||||
      viewport: page.getViewport({ scale: 1 }),
 | 
					      viewport: page.getViewport({scale: 1}),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    await page.render(renderContext).promise;
 | 
					    await page.render(renderContext).promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -358,7 +353,7 @@ const DraggableUtils = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  parseTransform(element) { },
 | 
					  parseTransform(element) {},
 | 
				
			||||||
  async getOverlayedPdfDocument() {
 | 
					  async getOverlayedPdfDocument() {
 | 
				
			||||||
    const pdfBytes = await this.pdfDoc.getData();
 | 
					    const pdfBytes = await this.pdfDoc.getData();
 | 
				
			||||||
    const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
 | 
					    const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
 | 
				
			||||||
@ -369,7 +364,7 @@ const DraggableUtils = {
 | 
				
			|||||||
    const pagesMap = this.documentsMap.get(this.pdfDoc);
 | 
					    const pagesMap = this.documentsMap.get(this.pdfDoc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let pageIdx in pagesMap) {
 | 
					    for (let pageIdx in pagesMap) {
 | 
				
			||||||
      if (pageIdx.includes("offset")) {
 | 
					      if (pageIdx.includes('offset')) {
 | 
				
			||||||
        continue;
 | 
					        continue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      console.log(typeof pageIdx);
 | 
					      console.log(typeof pageIdx);
 | 
				
			||||||
@ -377,9 +372,8 @@ const DraggableUtils = {
 | 
				
			|||||||
      const page = pdfDocModified.getPage(parseInt(pageIdx));
 | 
					      const page = pdfDocModified.getPage(parseInt(pageIdx));
 | 
				
			||||||
      let draggablesData = pagesMap[pageIdx];
 | 
					      let draggablesData = pagesMap[pageIdx];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
 | 
					      const offsetWidth = pagesMap[pageIdx + '-offsetWidth'];
 | 
				
			||||||
      const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
 | 
					      const offsetHeight = pagesMap[pageIdx + '-offsetHeight'];
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (const draggableData of draggablesData) {
 | 
					      for (const draggableData of draggablesData) {
 | 
				
			||||||
        // embed the draggable canvas
 | 
					        // embed the draggable canvas
 | 
				
			||||||
@ -389,8 +383,8 @@ const DraggableUtils = {
 | 
				
			|||||||
        const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
 | 
					        const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // calculate the position in the pdf document
 | 
					        // calculate the position in the pdf document
 | 
				
			||||||
        const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, "");
 | 
					        const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, '');
 | 
				
			||||||
        const transformComponents = tansform.split(",");
 | 
					        const transformComponents = tansform.split(',');
 | 
				
			||||||
        const draggablePositionPixels = {
 | 
					        const draggablePositionPixels = {
 | 
				
			||||||
          x: parseFloat(transformComponents[0]),
 | 
					          x: parseFloat(transformComponents[0]),
 | 
				
			||||||
          y: parseFloat(transformComponents[1]),
 | 
					          y: parseFloat(transformComponents[1]),
 | 
				
			||||||
@ -429,9 +423,8 @@ const DraggableUtils = {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //Defining the image if the page has a 0-degree angle
 | 
					        //Defining the image if the page has a 0-degree angle
 | 
				
			||||||
        let x = draggablePositionPdf.x
 | 
					        let x = draggablePositionPdf.x;
 | 
				
			||||||
        let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height
 | 
					        let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //Defining the image position if it is at other angles
 | 
					        //Defining the image position if it is at other angles
 | 
				
			||||||
        if (normalizedAngle === 90) {
 | 
					        if (normalizedAngle === 90) {
 | 
				
			||||||
@ -451,7 +444,7 @@ const DraggableUtils = {
 | 
				
			|||||||
          y: y,
 | 
					          y: y,
 | 
				
			||||||
          width: draggablePositionPdf.width,
 | 
					          width: draggablePositionPdf.width,
 | 
				
			||||||
          height: draggablePositionPdf.height,
 | 
					          height: draggablePositionPdf.height,
 | 
				
			||||||
          rotate: rotation
 | 
					          rotate: rotation,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -460,6 +453,6 @@ const DraggableUtils = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.addEventListener("DOMContentLoaded", () => {
 | 
					document.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
  DraggableUtils.init();
 | 
					  DraggableUtils.init();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,19 @@
 | 
				
			|||||||
import FileIconFactory from "./file-icon-factory.js";
 | 
					import FileIconFactory from './file-icon-factory.js';
 | 
				
			||||||
import FileUtils from "./file-utils.js";
 | 
					import FileUtils from './file-utils.js';
 | 
				
			||||||
import UUID from './uuid.js';
 | 
					import UUID from './uuid.js';
 | 
				
			||||||
 | 
					import {DecryptFile} from './DecryptFiles.js';
 | 
				
			||||||
let isScriptExecuted = false;
 | 
					let isScriptExecuted = false;
 | 
				
			||||||
if (!isScriptExecuted) {
 | 
					if (!isScriptExecuted) {
 | 
				
			||||||
  isScriptExecuted = true;
 | 
					  isScriptExecuted = true;
 | 
				
			||||||
  document.addEventListener("DOMContentLoaded", function () {
 | 
					  document.addEventListener('DOMContentLoaded', function () {
 | 
				
			||||||
    document.querySelectorAll(".custom-file-chooser").forEach(setupFileInput);
 | 
					    document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
function setupFileInput(chooser) {
 | 
					function setupFileInput(chooser) {
 | 
				
			||||||
  const elementId = chooser.getAttribute("data-bs-element-id");
 | 
					  const elementId = chooser.getAttribute('data-bs-element-id');
 | 
				
			||||||
  const filesSelected = chooser.getAttribute("data-bs-files-selected");
 | 
					  const filesSelected = chooser.getAttribute('data-bs-files-selected');
 | 
				
			||||||
  const pdfPrompt = chooser.getAttribute("data-bs-pdf-prompt");
 | 
					  const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt');
 | 
				
			||||||
  const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
 | 
					  const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let inputContainer = document.getElementById(inputContainerId);
 | 
					  let inputContainer = document.getElementById(inputContainerId);
 | 
				
			||||||
@ -26,7 +25,7 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
  inputContainer.addEventListener('click', (e) => {
 | 
					  inputContainer.addEventListener('click', (e) => {
 | 
				
			||||||
    let inputBtn = document.getElementById(elementId);
 | 
					    let inputBtn = document.getElementById(elementId);
 | 
				
			||||||
    inputBtn.click();
 | 
					    inputBtn.click();
 | 
				
			||||||
  })
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const dragenterListener = function () {
 | 
					  const dragenterListener = function () {
 | 
				
			||||||
    dragCounter++;
 | 
					    dragCounter++;
 | 
				
			||||||
@ -63,7 +62,7 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
    const files = dt.files;
 | 
					    const files = dt.files;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const fileInput = document.getElementById(elementId);
 | 
					    const fileInput = document.getElementById(elementId);
 | 
				
			||||||
    if (fileInput?.hasAttribute("multiple")) {
 | 
					    if (fileInput?.hasAttribute('multiple')) {
 | 
				
			||||||
      pushFileListTo(files, allFiles);
 | 
					      pushFileListTo(files, allFiles);
 | 
				
			||||||
    } else if (fileInput) {
 | 
					    } else if (fileInput) {
 | 
				
			||||||
      allFiles = [files[0]];
 | 
					      allFiles = [files[0]];
 | 
				
			||||||
@ -78,7 +77,7 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    dragCounter = 0;
 | 
					    dragCounter = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fileInput.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: {source: 'drag-drop'} }));
 | 
					    fileInput.dispatchEvent(new CustomEvent('change', {bubbles: true, detail: {source: 'drag-drop'}}));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function pushFileListTo(fileList, container) {
 | 
					  function pushFileListTo(fileList, container) {
 | 
				
			||||||
@ -87,7 +86,7 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
 | 
					  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => {
 | 
				
			||||||
    document.body.addEventListener(eventName, preventDefaults, false);
 | 
					    document.body.addEventListener(eventName, preventDefaults, false);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -96,37 +95,50 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
    e.stopPropagation();
 | 
					    e.stopPropagation();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  document.body.addEventListener("dragenter", dragenterListener);
 | 
					  document.body.addEventListener('dragenter', dragenterListener);
 | 
				
			||||||
  document.body.addEventListener("dragleave", dragleaveListener);
 | 
					  document.body.addEventListener('dragleave', dragleaveListener);
 | 
				
			||||||
  document.body.addEventListener("drop", dropListener);
 | 
					  document.body.addEventListener('drop', dropListener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $("#" + elementId).on("change", function (e) {
 | 
					  $('#' + elementId).on('change', async function (e) {
 | 
				
			||||||
    let element = e.target;
 | 
					    let element = e.target;
 | 
				
			||||||
    const isDragAndDrop = e.detail?.source == 'drag-drop';
 | 
					    const isDragAndDrop = e.detail?.source == 'drag-drop';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (element instanceof HTMLInputElement && element.hasAttribute("multiple")) {
 | 
					    if (element instanceof HTMLInputElement && element.hasAttribute('multiple')) {
 | 
				
			||||||
      allFiles = isDragAndDrop ? allFiles : [... allFiles, ... element.files];
 | 
					      allFiles = isDragAndDrop ? allFiles : [...allFiles, ...element.files];
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
 | 
					      allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    allFiles = await Promise.all(
 | 
				
			||||||
    allFiles = allFiles.map(file => {
 | 
					      allFiles.map(async (file) => {
 | 
				
			||||||
 | 
					        let decryptedFile = file;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const decryptFile = new DecryptFile();
 | 
				
			||||||
 | 
					          const {isEncrypted, requiresPassword} = await decryptFile.checkFileEncrypted(file);
 | 
				
			||||||
 | 
					          if (file.type === 'application/pdf' && isEncrypted) {
 | 
				
			||||||
 | 
					            decryptedFile = await decryptFile.decryptFile(file, requiresPassword);
 | 
				
			||||||
 | 
					            if (!decryptedFile) throw new Error('File decryption failed.');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          decryptedFile.uniqueId = UUID.uuidv4();
 | 
				
			||||||
 | 
					          return decryptedFile;
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          console.error(`Error decrypting file: ${file.name}`, error);
 | 
				
			||||||
          if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
 | 
					          if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
 | 
				
			||||||
          return file;
 | 
					          return file;
 | 
				
			||||||
    });
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    if (!isDragAndDrop) {
 | 
					    if (!isDragAndDrop) {
 | 
				
			||||||
      let dataTransfer = toDataTransfer(allFiles);
 | 
					      let dataTransfer = toDataTransfer(allFiles);
 | 
				
			||||||
      element.files = dataTransfer.files;
 | 
					      element.files = dataTransfer.files;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handleFileInputChange(this);
 | 
					    handleFileInputChange(this);
 | 
				
			||||||
    this.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
 | 
					    this.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true, detail: {elementId, allFiles}}));
 | 
				
			||||||
});
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function toDataTransfer(files) {
 | 
					  function toDataTransfer(files) {
 | 
				
			||||||
    let dataTransfer = new DataTransfer();
 | 
					    let dataTransfer = new DataTransfer();
 | 
				
			||||||
    files.forEach(file => dataTransfer.items.add(file));
 | 
					    files.forEach((file) => dataTransfer.items.add(file));
 | 
				
			||||||
    return dataTransfer;
 | 
					    return dataTransfer;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -136,7 +148,7 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId}));
 | 
					    const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const selectedFilesContainer = $(inputContainer).siblings(".selected-files");
 | 
					    const selectedFilesContainer = $(inputContainer).siblings('.selected-files');
 | 
				
			||||||
    selectedFilesContainer.empty();
 | 
					    selectedFilesContainer.empty();
 | 
				
			||||||
    filesInfo.forEach((info) => {
 | 
					    filesInfo.forEach((info) => {
 | 
				
			||||||
      let fileContainerClasses = 'small-file-container d-flex flex-column justify-content-center align-items-center';
 | 
					      let fileContainerClasses = 'small-file-container d-flex flex-column justify-content-center align-items-center';
 | 
				
			||||||
@ -167,28 +179,26 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function showOrHideSelectedFilesContainer(files) {
 | 
					  function showOrHideSelectedFilesContainer(files) {
 | 
				
			||||||
    if (files && files.length > 0)
 | 
					    if (files && files.length > 0) chooser.style.setProperty('--selected-files-display', 'flex');
 | 
				
			||||||
      chooser.style.setProperty('--selected-files-display', 'flex');
 | 
					    else chooser.style.setProperty('--selected-files-display', 'none');
 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    chooser.style.setProperty('--selected-files-display', 'none');
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function removeFileListener(e) {
 | 
					  function removeFileListener(e) {
 | 
				
			||||||
    const fileId = (e.target).getAttribute('data-file-id');
 | 
					    const fileId = e.target.getAttribute('data-file-id');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let inputElement = document.getElementById(elementId);
 | 
					    let inputElement = document.getElementById(elementId);
 | 
				
			||||||
    removeFileById(fileId, inputElement);
 | 
					    removeFileById(fileId, inputElement);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    showOrHideSelectedFilesContainer(allFiles);
 | 
					    showOrHideSelectedFilesContainer(allFiles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inputElement.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
 | 
					    inputElement.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true}));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function removeFileById(fileId, inputElement) {
 | 
					  function removeFileById(fileId, inputElement) {
 | 
				
			||||||
    let fileContainer = document.getElementById(fileId);
 | 
					    let fileContainer = document.getElementById(fileId);
 | 
				
			||||||
    fileContainer.remove();
 | 
					    fileContainer.remove();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    allFiles = allFiles.filter(v => v.uniqueId != fileId);
 | 
					    allFiles = allFiles.filter((v) => v.uniqueId != fileId);
 | 
				
			||||||
    let dataTransfer = toDataTransfer(allFiles);
 | 
					    let dataTransfer = toDataTransfer(allFiles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (inputElement) inputElement.files = dataTransfer.files;
 | 
					    if (inputElement) inputElement.files = dataTransfer.files;
 | 
				
			||||||
@ -207,23 +217,19 @@ function setupFileInput(chooser) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function createFileInfoContainer(info) {
 | 
					  function createFileInfoContainer(info) {
 | 
				
			||||||
    let fileInfoContainer = document.createElement("div");
 | 
					    let fileInfoContainer = document.createElement('div');
 | 
				
			||||||
    let fileInfoContainerClasses = 'file-info d-flex flex-column align-items-center justify-content-center';
 | 
					    let fileInfoContainerClasses = 'file-info d-flex flex-column align-items-center justify-content-center';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $(fileInfoContainer).addClass(fileInfoContainerClasses);
 | 
					    $(fileInfoContainer).addClass(fileInfoContainerClasses);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $(fileInfoContainer).append(
 | 
					    $(fileInfoContainer).append(`<div title="${info.name}">${info.name}</div>`);
 | 
				
			||||||
      `<div title="${info.name}">${info.name}</div>`
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    let fileSizeWithUnits = FileUtils.transformFileSize(info.size);
 | 
					    let fileSizeWithUnits = FileUtils.transformFileSize(info.size);
 | 
				
			||||||
    $(fileInfoContainer).append(
 | 
					    $(fileInfoContainer).append(`<div title="${info.size}">${fileSizeWithUnits}</div>`);
 | 
				
			||||||
      `<div title="${info.size}">${fileSizeWithUnits}</div>`
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return fileInfoContainer;
 | 
					    return fileInfoContainer;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //Listen for event of file being removed and the filter it out of the allFiles array
 | 
					  //Listen for event of file being removed and the filter it out of the allFiles array
 | 
				
			||||||
  document.addEventListener("fileRemoved", function (e) {
 | 
					  document.addEventListener('fileRemoved', function (e) {
 | 
				
			||||||
    const fileId = e.detail;
 | 
					    const fileId = e.detail;
 | 
				
			||||||
    let inputElement = document.getElementById(elementId);
 | 
					    let inputElement = document.getElementById(elementId);
 | 
				
			||||||
    removeFileById(fileId, inputElement);
 | 
					    removeFileById(fileId, inputElement);
 | 
				
			||||||
 | 
				
			|||||||
@ -268,7 +268,7 @@ document.addEventListener("DOMContentLoaded", function () {
 | 
				
			|||||||
    const parent = header.parentNode;
 | 
					    const parent = header.parentNode;
 | 
				
			||||||
    const container = header.parentNode.querySelector(".feature-group-container");
 | 
					    const container = header.parentNode.querySelector(".feature-group-container");
 | 
				
			||||||
    if (parent.id !== "groupFavorites") {
 | 
					    if (parent.id !== "groupFavorites") {
 | 
				
			||||||
      container.style.maxHeight = container.clientHeight + "px";
 | 
					      container.style.maxHeight = container.scrollHeight + "px";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    header.onclick = () => {
 | 
					    header.onclick = () => {
 | 
				
			||||||
      expandCollapseToggle(parent);
 | 
					      expandCollapseToggle(parent);
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ import {SplitAllCommand} from './commands/split.js';
 | 
				
			|||||||
import {UndoManager} from './UndoManager.js';
 | 
					import {UndoManager} from './UndoManager.js';
 | 
				
			||||||
import {PageBreakCommand} from './commands/page-break.js';
 | 
					import {PageBreakCommand} from './commands/page-break.js';
 | 
				
			||||||
import {AddFilesCommand} from './commands/add-page.js';
 | 
					import {AddFilesCommand} from './commands/add-page.js';
 | 
				
			||||||
 | 
					import {DecryptFile} from '../DecryptFiles.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PdfContainer {
 | 
					class PdfContainer {
 | 
				
			||||||
  fileName;
 | 
					  fileName;
 | 
				
			||||||
@ -40,6 +41,8 @@ class PdfContainer {
 | 
				
			|||||||
    this.removeAllElements = this.removeAllElements.bind(this);
 | 
					    this.removeAllElements = this.removeAllElements.bind(this);
 | 
				
			||||||
    this.resetPages = this.resetPages.bind(this);
 | 
					    this.resetPages = this.resetPages.bind(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.decryptFile = new DecryptFile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.undoManager = undoManager || new UndoManager();
 | 
					    this.undoManager = undoManager || new UndoManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.pdfAdapters = pdfAdapters;
 | 
					    this.pdfAdapters = pdfAdapters;
 | 
				
			||||||
@ -165,7 +168,6 @@ class PdfContainer {
 | 
				
			|||||||
      input.click();
 | 
					      input.click();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  async addFilesFromFiles(files, nextSiblingElement, pages) {
 | 
					  async addFilesFromFiles(files, nextSiblingElement, pages) {
 | 
				
			||||||
    this.fileName = files[0].name;
 | 
					    this.fileName = files[0].name;
 | 
				
			||||||
    for (var i = 0; i < files.length; i++) {
 | 
					    for (var i = 0; i < files.length; i++) {
 | 
				
			||||||
@ -173,17 +175,37 @@ class PdfContainer {
 | 
				
			|||||||
      let processingTime,
 | 
					      let processingTime,
 | 
				
			||||||
        errorMessage = null,
 | 
					        errorMessage = null,
 | 
				
			||||||
        pageCount = 0;
 | 
					        pageCount = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const file = files[i];
 | 
					        let decryptedFile = files[i];
 | 
				
			||||||
        if (file.type === 'application/pdf') {
 | 
					        let isEncrypted = false;
 | 
				
			||||||
          const {renderer, pdfDocument} = await this.loadFile(file);
 | 
					        let requiresPassword = false;
 | 
				
			||||||
 | 
					        await this.decryptFile
 | 
				
			||||||
 | 
					          .checkFileEncrypted(decryptedFile)
 | 
				
			||||||
 | 
					          .then((result) => {
 | 
				
			||||||
 | 
					            isEncrypted = result.isEncrypted;
 | 
				
			||||||
 | 
					            requiresPassword = result.requiresPassword;
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          .catch((error) => {
 | 
				
			||||||
 | 
					            console.error(error);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					        if (decryptedFile.type === 'application/pdf' && isEncrypted) {
 | 
				
			||||||
 | 
					          decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword);
 | 
				
			||||||
 | 
					          if (!decryptedFile) {
 | 
				
			||||||
 | 
					            throw new Error('File decryption failed.');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (decryptedFile.type === 'application/pdf') {
 | 
				
			||||||
 | 
					          const {renderer, pdfDocument} = await this.loadFile(decryptedFile);
 | 
				
			||||||
          pageCount = renderer.pageCount || 0;
 | 
					          pageCount = renderer.pageCount || 0;
 | 
				
			||||||
          pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages);
 | 
					          pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages);
 | 
				
			||||||
        } else if (file.type.startsWith('image/')) {
 | 
					        } else if (decryptedFile.type.startsWith('image/')) {
 | 
				
			||||||
          pages = await this.addImageFile(file, nextSiblingElement, pages);
 | 
					          pages = await this.addImageFile(decryptedFile, nextSiblingElement, pages);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        processingTime = Date.now() - startTime;
 | 
					        processingTime = Date.now() - startTime;
 | 
				
			||||||
        this.captureFileProcessingEvent(true, file, processingTime, null, pageCount);
 | 
					        this.captureFileProcessingEvent(true, decryptedFile, processingTime, null, pageCount);
 | 
				
			||||||
      } catch (error) {
 | 
					      } catch (error) {
 | 
				
			||||||
        processingTime = Date.now() - startTime;
 | 
					        processingTime = Date.now() - startTime;
 | 
				
			||||||
        errorMessage = error.message || 'Unknown error';
 | 
					        errorMessage = error.message || 'Unknown error';
 | 
				
			||||||
@ -194,6 +216,7 @@ class PdfContainer {
 | 
				
			|||||||
    document.querySelectorAll('.enable-on-file').forEach((element) => {
 | 
					    document.querySelectorAll('.enable-on-file').forEach((element) => {
 | 
				
			||||||
      element.disabled = false;
 | 
					      element.disabled = false;
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return pages;
 | 
					    return pages;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										47
									
								
								src/main/resources/static/js/pages/add-image.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/main/resources/static/js/pages/add-image.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					document.getElementById('download-pdf').addEventListener('click', async () => {
 | 
				
			||||||
 | 
					  const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
 | 
				
			||||||
 | 
					  const modifiedPdfBytes = await modifiedPdf.save();
 | 
				
			||||||
 | 
					  const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
 | 
				
			||||||
 | 
					  const link = document.createElement('a');
 | 
				
			||||||
 | 
					  link.href = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					  link.download = originalFileName + '_addedImage.pdf';
 | 
				
			||||||
 | 
					  link.click();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					let originalFileName = '';
 | 
				
			||||||
 | 
					document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
 | 
				
			||||||
 | 
					  const fileInput = event.target;
 | 
				
			||||||
 | 
					  fileInput.addEventListener('file-input-change', async (e) => {
 | 
				
			||||||
 | 
					    const {allFiles} = e.detail;
 | 
				
			||||||
 | 
					    if (allFiles && allFiles.length > 0) {
 | 
				
			||||||
 | 
					      const file = allFiles[0];
 | 
				
			||||||
 | 
					      originalFileName = file.name.replace(/\.[^/.]+$/, '');
 | 
				
			||||||
 | 
					      const pdfData = await file.arrayBuffer();
 | 
				
			||||||
 | 
					      pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					      const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise;
 | 
				
			||||||
 | 
					      await DraggableUtils.renderPage(pdfDoc, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      document.querySelectorAll('.show-on-file-selected').forEach((el) => {
 | 
				
			||||||
 | 
					        el.style.cssText = '';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
 | 
					  document.querySelectorAll('.show-on-file-selected').forEach((el) => {
 | 
				
			||||||
 | 
					    el.style.cssText = 'display:none !important';
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imageUpload = document.querySelector('input[name=image-upload]');
 | 
				
			||||||
 | 
					imageUpload.addEventListener('change', (e) => {
 | 
				
			||||||
 | 
					  if (!e.target.files) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for (const imageFile of e.target.files) {
 | 
				
			||||||
 | 
					    var reader = new FileReader();
 | 
				
			||||||
 | 
					    reader.readAsDataURL(imageFile);
 | 
				
			||||||
 | 
					    reader.onloadend = function (e) {
 | 
				
			||||||
 | 
					      DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										253
									
								
								src/main/resources/static/js/pages/adjust-contrast.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								src/main/resources/static/js/pages/adjust-contrast.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,253 @@
 | 
				
			|||||||
 | 
					var canvas = document.getElementById('contrast-pdf-canvas');
 | 
				
			||||||
 | 
					var context = canvas.getContext('2d');
 | 
				
			||||||
 | 
					var originalImageData = null;
 | 
				
			||||||
 | 
					var allPages = [];
 | 
				
			||||||
 | 
					var pdfDoc = null;
 | 
				
			||||||
 | 
					var pdf = null; // This is the current PDF document
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function renderPDFAndSaveOriginalImageData(file) {
 | 
				
			||||||
 | 
					  var fileReader = new FileReader();
 | 
				
			||||||
 | 
					  fileReader.onload = async function () {
 | 
				
			||||||
 | 
					    var data = new Uint8Array(this.result);
 | 
				
			||||||
 | 
					    pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					    pdf = await pdfjsLib.getDocument({data: data}).promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Get the number of pages in the PDF
 | 
				
			||||||
 | 
					    var numPages = pdf.numPages;
 | 
				
			||||||
 | 
					    allPages = Array.from({length: numPages}, (_, i) => i + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create a new PDF document
 | 
				
			||||||
 | 
					    pdfDoc = await PDFLib.PDFDocument.create();
 | 
				
			||||||
 | 
					    // Render the first page in the viewer
 | 
				
			||||||
 | 
					    await renderPageAndAdjustImageProperties(1);
 | 
				
			||||||
 | 
					    document.getElementById('sliders-container').style.display = 'block';
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  fileReader.readAsArrayBuffer(file);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This function is now async and returns a promise
 | 
				
			||||||
 | 
					function renderPageAndAdjustImageProperties(pageNum) {
 | 
				
			||||||
 | 
					  return new Promise(async function (resolve, reject) {
 | 
				
			||||||
 | 
					    var page = await pdf.getPage(pageNum);
 | 
				
			||||||
 | 
					    var scale = 1.5;
 | 
				
			||||||
 | 
					    var viewport = page.getViewport({scale: scale});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas.height = viewport.height;
 | 
				
			||||||
 | 
					    canvas.width = viewport.width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var renderContext = {
 | 
				
			||||||
 | 
					      canvasContext: context,
 | 
				
			||||||
 | 
					      viewport: viewport,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var renderTask = page.render(renderContext);
 | 
				
			||||||
 | 
					    renderTask.promise.then(function () {
 | 
				
			||||||
 | 
					      originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
 | 
				
			||||||
 | 
					      adjustImageProperties();
 | 
				
			||||||
 | 
					      resolve();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    canvas.classList.add('fixed-shadow-canvas');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function adjustImageProperties() {
 | 
				
			||||||
 | 
					  var contrast = parseFloat(document.getElementById('contrast-slider').value);
 | 
				
			||||||
 | 
					  var brightness = parseFloat(document.getElementById('brightness-slider').value);
 | 
				
			||||||
 | 
					  var saturation = parseFloat(document.getElementById('saturation-slider').value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  contrast /= 100; // normalize to range [0, 2]
 | 
				
			||||||
 | 
					  brightness /= 100; // normalize to range [0, 2]
 | 
				
			||||||
 | 
					  saturation /= 100; // normalize to range [0, 2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (originalImageData) {
 | 
				
			||||||
 | 
					    var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
 | 
				
			||||||
 | 
					    newImageData.data.set(originalImageData.data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (var i = 0; i < newImageData.data.length; i += 4) {
 | 
				
			||||||
 | 
					      var r = newImageData.data[i];
 | 
				
			||||||
 | 
					      var g = newImageData.data[i + 1];
 | 
				
			||||||
 | 
					      var b = newImageData.data[i + 2];
 | 
				
			||||||
 | 
					      // Adjust contrast
 | 
				
			||||||
 | 
					      r = adjustContrastForPixel(r, contrast);
 | 
				
			||||||
 | 
					      g = adjustContrastForPixel(g, contrast);
 | 
				
			||||||
 | 
					      b = adjustContrastForPixel(b, contrast);
 | 
				
			||||||
 | 
					      // Adjust brightness
 | 
				
			||||||
 | 
					      r = adjustBrightnessForPixel(r, brightness);
 | 
				
			||||||
 | 
					      g = adjustBrightnessForPixel(g, brightness);
 | 
				
			||||||
 | 
					      b = adjustBrightnessForPixel(b, brightness);
 | 
				
			||||||
 | 
					      // Adjust saturation
 | 
				
			||||||
 | 
					      var rgb = adjustSaturationForPixel(r, g, b, saturation);
 | 
				
			||||||
 | 
					      newImageData.data[i] = rgb[0];
 | 
				
			||||||
 | 
					      newImageData.data[i + 1] = rgb[1];
 | 
				
			||||||
 | 
					      newImageData.data[i + 2] = rgb[2];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    context.putImageData(newImageData, 0, 0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function rgbToHsl(r, g, b) {
 | 
				
			||||||
 | 
					  (r /= 255), (g /= 255), (b /= 255);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var max = Math.max(r, g, b),
 | 
				
			||||||
 | 
					    min = Math.min(r, g, b);
 | 
				
			||||||
 | 
					  var h,
 | 
				
			||||||
 | 
					    s,
 | 
				
			||||||
 | 
					    l = (max + min) / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (max === min) {
 | 
				
			||||||
 | 
					    h = s = 0; // achromatic
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    var d = max - min;
 | 
				
			||||||
 | 
					    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (max) {
 | 
				
			||||||
 | 
					      case r:
 | 
				
			||||||
 | 
					        h = (g - b) / d + (g < b ? 6 : 0);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case g:
 | 
				
			||||||
 | 
					        h = (b - r) / d + 2;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      case b:
 | 
				
			||||||
 | 
					        h = (r - g) / d + 4;
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    h /= 6;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [h, s, l];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function hslToRgb(h, s, l) {
 | 
				
			||||||
 | 
					  var r, g, b;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (s === 0) {
 | 
				
			||||||
 | 
					    r = g = b = l; // achromatic
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    var hue2rgb = function hue2rgb(p, q, t) {
 | 
				
			||||||
 | 
					      if (t < 0) t += 1;
 | 
				
			||||||
 | 
					      if (t > 1) t -= 1;
 | 
				
			||||||
 | 
					      if (t < 1 / 6) return p + (q - p) * 6 * t;
 | 
				
			||||||
 | 
					      if (t < 1 / 2) return q;
 | 
				
			||||||
 | 
					      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
 | 
				
			||||||
 | 
					      return p;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
 | 
				
			||||||
 | 
					    var p = 2 * l - q;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    r = hue2rgb(p, q, h + 1 / 3);
 | 
				
			||||||
 | 
					    g = hue2rgb(p, q, h);
 | 
				
			||||||
 | 
					    b = hue2rgb(p, q, h - 1 / 3);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [r * 255, g * 255, b * 255];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function adjustContrastForPixel(pixel, contrast) {
 | 
				
			||||||
 | 
					  // Normalize to range [-0.5, 0.5]
 | 
				
			||||||
 | 
					  var normalized = pixel / 255 - 0.5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Apply contrast
 | 
				
			||||||
 | 
					  normalized *= contrast;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Denormalize back to [0, 255]
 | 
				
			||||||
 | 
					  return (normalized + 0.5) * 255;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function clamp(value, min, max) {
 | 
				
			||||||
 | 
					  return Math.min(Math.max(value, min), max);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function adjustSaturationForPixel(r, g, b, saturation) {
 | 
				
			||||||
 | 
					  var hsl = rgbToHsl(r, g, b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Adjust saturation
 | 
				
			||||||
 | 
					  hsl[1] = clamp(hsl[1] * saturation, 0, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Convert back to RGB
 | 
				
			||||||
 | 
					  var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Return adjusted RGB values
 | 
				
			||||||
 | 
					  return rgb;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function adjustBrightnessForPixel(pixel, brightness) {
 | 
				
			||||||
 | 
					  return Math.max(0, Math.min(255, pixel * brightness));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					let inputFileName = '';
 | 
				
			||||||
 | 
					async function downloadPDF() {
 | 
				
			||||||
 | 
					  for (var i = 0; i < allPages.length; i++) {
 | 
				
			||||||
 | 
					    await renderPageAndAdjustImageProperties(allPages[i]);
 | 
				
			||||||
 | 
					    const pngImageBytes = canvas.toDataURL('image/png');
 | 
				
			||||||
 | 
					    const pngImage = await pdfDoc.embedPng(pngImageBytes);
 | 
				
			||||||
 | 
					    const pngDims = pngImage.scale(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create a blank page matching the dimensions of the image
 | 
				
			||||||
 | 
					    const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Draw the PNG image
 | 
				
			||||||
 | 
					    page.drawImage(pngImage, {
 | 
				
			||||||
 | 
					      x: 0,
 | 
				
			||||||
 | 
					      y: 0,
 | 
				
			||||||
 | 
					      width: pngDims.width,
 | 
				
			||||||
 | 
					      height: pngDims.height,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Serialize the PDFDocument to bytes (a Uint8Array)
 | 
				
			||||||
 | 
					  const pdfBytes = await pdfDoc.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Create a Blob
 | 
				
			||||||
 | 
					  const blob = new Blob([pdfBytes.buffer], {type: 'application/pdf'});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Create download link
 | 
				
			||||||
 | 
					  const downloadLink = document.createElement('a');
 | 
				
			||||||
 | 
					  downloadLink.href = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					  let newFileName = inputFileName ? inputFileName.replace('.pdf', '') : 'download';
 | 
				
			||||||
 | 
					  newFileName += '_adjusted_color.pdf';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  downloadLink.download = newFileName;
 | 
				
			||||||
 | 
					  downloadLink.click();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // After download, reset the viewer and clear stored data
 | 
				
			||||||
 | 
					  allPages = []; // Clear the pages
 | 
				
			||||||
 | 
					  originalImageData = null; // Clear the image data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Go back to page 1 and render it in the viewer
 | 
				
			||||||
 | 
					  if (pdf !== null) {
 | 
				
			||||||
 | 
					    renderPageAndAdjustImageProperties(1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Event listeners
 | 
				
			||||||
 | 
					document.getElementById('fileInput-input').addEventListener('change', function (e) {
 | 
				
			||||||
 | 
					  const fileInput = event.target;
 | 
				
			||||||
 | 
					  fileInput.addEventListener('file-input-change', async (e) => {
 | 
				
			||||||
 | 
					    const {allFiles} = e.detail;
 | 
				
			||||||
 | 
					    if (allFiles && allFiles.length > 0) {
 | 
				
			||||||
 | 
					      const file = allFiles[0];
 | 
				
			||||||
 | 
					      inputFileName = file.name;
 | 
				
			||||||
 | 
					      renderPDFAndSaveOriginalImageData(file);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById('contrast-slider').addEventListener('input', function () {
 | 
				
			||||||
 | 
					  document.getElementById('contrast-val').textContent = this.value;
 | 
				
			||||||
 | 
					  adjustImageProperties();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById('brightness-slider').addEventListener('input', function () {
 | 
				
			||||||
 | 
					  document.getElementById('brightness-val').textContent = this.value;
 | 
				
			||||||
 | 
					  adjustImageProperties();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById('saturation-slider').addEventListener('input', function () {
 | 
				
			||||||
 | 
					  document.getElementById('saturation-val').textContent = this.value;
 | 
				
			||||||
 | 
					  adjustImageProperties();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById('download-button').addEventListener('click', function () {
 | 
				
			||||||
 | 
					  downloadPDF();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										150
									
								
								src/main/resources/static/js/pages/change-metadata.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/main/resources/static/js/pages/change-metadata.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,150 @@
 | 
				
			|||||||
 | 
					const deleteAllCheckbox = document.querySelector('#deleteAll');
 | 
				
			||||||
 | 
					let inputs = document.querySelectorAll('input');
 | 
				
			||||||
 | 
					const customMetadataDiv = document.getElementById('customMetadata');
 | 
				
			||||||
 | 
					const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					deleteAllCheckbox.addEventListener('change', function (event) {
 | 
				
			||||||
 | 
					  inputs.forEach((input) => {
 | 
				
			||||||
 | 
					    // If it's the deleteAllCheckbox or any file input, skip
 | 
				
			||||||
 | 
					    if (input === deleteAllCheckbox || input.type === 'file') {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // Disable or enable based on the checkbox state
 | 
				
			||||||
 | 
					    input.disabled = deleteAllCheckbox.checked;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const customModeCheckbox = document.getElementById('customModeCheckbox');
 | 
				
			||||||
 | 
					const addMetadataBtn = document.getElementById('addMetadataBtn');
 | 
				
			||||||
 | 
					const customMetadataFormContainer = document.getElementById('customMetadataEntries');
 | 
				
			||||||
 | 
					var count = 1;
 | 
				
			||||||
 | 
					const fileInput = document.querySelector('#fileInput-input');
 | 
				
			||||||
 | 
					const authorInput = document.querySelector('#author');
 | 
				
			||||||
 | 
					const creationDateInput = document.querySelector('#creationDate');
 | 
				
			||||||
 | 
					const creatorInput = document.querySelector('#creator');
 | 
				
			||||||
 | 
					const keywordsInput = document.querySelector('#keywords');
 | 
				
			||||||
 | 
					const modificationDateInput = document.querySelector('#modificationDate');
 | 
				
			||||||
 | 
					const producerInput = document.querySelector('#producer');
 | 
				
			||||||
 | 
					const subjectInput = document.querySelector('#subject');
 | 
				
			||||||
 | 
					const titleInput = document.querySelector('#title');
 | 
				
			||||||
 | 
					const trappedInput = document.querySelector('#trapped');
 | 
				
			||||||
 | 
					var lastPDFFileMeta = null;
 | 
				
			||||||
 | 
					var lastPDFFile = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileInput.addEventListener('change', async function () {
 | 
				
			||||||
 | 
					  fileInput.addEventListener('file-input-change', async (e) => {
 | 
				
			||||||
 | 
					    const {allFiles} = e.detail;
 | 
				
			||||||
 | 
					    if (allFiles && allFiles.length > 0) {
 | 
				
			||||||
 | 
					      const file = allFiles[0];
 | 
				
			||||||
 | 
					      while (otherMetadataEntriesDiv.firstChild) {
 | 
				
			||||||
 | 
					        otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      while (customMetadataFormContainer.firstChild) {
 | 
				
			||||||
 | 
					        customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var url = URL.createObjectURL(file);
 | 
				
			||||||
 | 
					      pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					      const pdf = await pdfjsLib.getDocument(url).promise;
 | 
				
			||||||
 | 
					      const pdfMetadata = await pdf.getMetadata();
 | 
				
			||||||
 | 
					      lastPDFFile = pdfMetadata?.info;
 | 
				
			||||||
 | 
					      console.log(pdfMetadata);
 | 
				
			||||||
 | 
					      if (!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
 | 
				
			||||||
 | 
					        customModeCheckbox.disabled = true;
 | 
				
			||||||
 | 
					        customModeCheckbox.checked = false;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        customModeCheckbox.disabled = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      authorInput.value = pdfMetadata?.info?.Author;
 | 
				
			||||||
 | 
					      creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
 | 
				
			||||||
 | 
					      creatorInput.value = pdfMetadata?.info?.Creator;
 | 
				
			||||||
 | 
					      keywordsInput.value = pdfMetadata?.info?.Keywords;
 | 
				
			||||||
 | 
					      modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
 | 
				
			||||||
 | 
					      producerInput.value = pdfMetadata?.info?.Producer;
 | 
				
			||||||
 | 
					      subjectInput.value = pdfMetadata?.info?.Subject;
 | 
				
			||||||
 | 
					      titleInput.value = pdfMetadata?.info?.Title;
 | 
				
			||||||
 | 
					      console.log(pdfMetadata?.info);
 | 
				
			||||||
 | 
					      const trappedValue = pdfMetadata?.info?.Trapped;
 | 
				
			||||||
 | 
					      // Get all options in the select element
 | 
				
			||||||
 | 
					      const options = trappedInput.options;
 | 
				
			||||||
 | 
					      // Loop through all options to find the one with a matching value
 | 
				
			||||||
 | 
					      for (let i = 0; i < options.length; i++) {
 | 
				
			||||||
 | 
					        if (options[i].value === trappedValue) {
 | 
				
			||||||
 | 
					          options[i].selected = true;
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      addExtra();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addMetadataBtn.addEventListener('click', () => {
 | 
				
			||||||
 | 
					  const keyInput = document.createElement('input');
 | 
				
			||||||
 | 
					  keyInput.type = 'text';
 | 
				
			||||||
 | 
					  keyInput.placeholder = 'Key';
 | 
				
			||||||
 | 
					  keyInput.className = 'form-control';
 | 
				
			||||||
 | 
					  keyInput.name = `allRequestParams[customKey${count}]`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const valueInput = document.createElement('input');
 | 
				
			||||||
 | 
					  valueInput.type = 'text';
 | 
				
			||||||
 | 
					  valueInput.placeholder = 'Value';
 | 
				
			||||||
 | 
					  valueInput.className = 'form-control';
 | 
				
			||||||
 | 
					  valueInput.name = `allRequestParams[customValue${count}]`;
 | 
				
			||||||
 | 
					  count = count + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const formGroup = document.createElement('div');
 | 
				
			||||||
 | 
					  formGroup.className = 'mb-3';
 | 
				
			||||||
 | 
					  formGroup.appendChild(keyInput);
 | 
				
			||||||
 | 
					  formGroup.appendChild(valueInput);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  customMetadataFormContainer.appendChild(formGroup);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					function convertDateFormat(dateTimeString) {
 | 
				
			||||||
 | 
					  if (!dateTimeString || dateTimeString.length < 17) {
 | 
				
			||||||
 | 
					    return dateTimeString;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const year = dateTimeString.substring(2, 6);
 | 
				
			||||||
 | 
					  const month = dateTimeString.substring(6, 8);
 | 
				
			||||||
 | 
					  const day = dateTimeString.substring(8, 10);
 | 
				
			||||||
 | 
					  const hour = dateTimeString.substring(10, 12);
 | 
				
			||||||
 | 
					  const minute = dateTimeString.substring(12, 14);
 | 
				
			||||||
 | 
					  const second = dateTimeString.substring(14, 16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return year + '/' + month + '/' + day + ' ' + hour + ':' + minute + ':' + second;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function addExtra() {
 | 
				
			||||||
 | 
					  const event = document.getElementById('customModeCheckbox');
 | 
				
			||||||
 | 
					  if (event.checked && lastPDFFile.Custom != null) {
 | 
				
			||||||
 | 
					    customMetadataDiv.style.display = 'block';
 | 
				
			||||||
 | 
					    for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        key === 'Author' ||
 | 
				
			||||||
 | 
					        key === 'CreationDate' ||
 | 
				
			||||||
 | 
					        key === 'Creator' ||
 | 
				
			||||||
 | 
					        key === 'Keywords' ||
 | 
				
			||||||
 | 
					        key === 'ModDate' ||
 | 
				
			||||||
 | 
					        key === 'Producer' ||
 | 
				
			||||||
 | 
					        key === 'Subject' ||
 | 
				
			||||||
 | 
					        key === 'Title' ||
 | 
				
			||||||
 | 
					        key === 'Trapped'
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const entryDiv = document.createElement('div');
 | 
				
			||||||
 | 
					      entryDiv.className = 'mb-3';
 | 
				
			||||||
 | 
					      entryDiv.innerHTML = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
 | 
				
			||||||
 | 
					      otherMetadataEntriesDiv.appendChild(entryDiv);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    customMetadataDiv.style.display = 'none';
 | 
				
			||||||
 | 
					    while (otherMetadataEntriesDiv.firstChild) {
 | 
				
			||||||
 | 
					      otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					customModeCheckbox.addEventListener('change', (event) => {
 | 
				
			||||||
 | 
					  addExtra();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										159
									
								
								src/main/resources/static/js/pages/crop.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/main/resources/static/js/pages/crop.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,159 @@
 | 
				
			|||||||
 | 
					let pdfCanvas = document.getElementById('cropPdfCanvas');
 | 
				
			||||||
 | 
					let overlayCanvas = document.getElementById('overlayCanvas');
 | 
				
			||||||
 | 
					let canvasesContainer = document.getElementById('canvasesContainer');
 | 
				
			||||||
 | 
					canvasesContainer.style.display = 'none';
 | 
				
			||||||
 | 
					let containerRect = canvasesContainer.getBoundingClientRect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let context = pdfCanvas.getContext('2d');
 | 
				
			||||||
 | 
					let overlayContext = overlayCanvas.getContext('2d');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overlayCanvas.width = pdfCanvas.width;
 | 
				
			||||||
 | 
					overlayCanvas.height = pdfCanvas.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let isDrawing = false; // New flag to check if drawing is ongoing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let cropForm = document.getElementById('cropForm');
 | 
				
			||||||
 | 
					let fileInput = document.getElementById('fileInput-input');
 | 
				
			||||||
 | 
					let xInput = document.getElementById('x');
 | 
				
			||||||
 | 
					let yInput = document.getElementById('y');
 | 
				
			||||||
 | 
					let widthInput = document.getElementById('width');
 | 
				
			||||||
 | 
					let heightInput = document.getElementById('height');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let pdfDoc = null;
 | 
				
			||||||
 | 
					let currentPage = 1;
 | 
				
			||||||
 | 
					let totalPages = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let startX = 0;
 | 
				
			||||||
 | 
					let startY = 0;
 | 
				
			||||||
 | 
					let rectWidth = 0;
 | 
				
			||||||
 | 
					let rectHeight = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let pageScale = 1; // The scale which the pdf page renders
 | 
				
			||||||
 | 
					let timeId = null; // timeout id for resizing canvases event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renderPageFromFile(file) {
 | 
				
			||||||
 | 
					  if (file.type === 'application/pdf') {
 | 
				
			||||||
 | 
					    let reader = new FileReader();
 | 
				
			||||||
 | 
					    reader.onload = function (ev) {
 | 
				
			||||||
 | 
					      let typedArray = new Uint8Array(reader.result);
 | 
				
			||||||
 | 
					      pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					      pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
 | 
				
			||||||
 | 
					        pdfDoc = pdf;
 | 
				
			||||||
 | 
					        totalPages = pdf.numPages;
 | 
				
			||||||
 | 
					        renderPage(currentPage);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    reader.readAsArrayBuffer(file);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.addEventListener('resize', function () {
 | 
				
			||||||
 | 
					  clearTimeout(timeId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  timeId = setTimeout(function () {
 | 
				
			||||||
 | 
					    if (fileInput.files.length == 0) return;
 | 
				
			||||||
 | 
					    let canvasesContainer = document.getElementById('canvasesContainer');
 | 
				
			||||||
 | 
					    let containerRect = canvasesContainer.getBoundingClientRect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pdfCanvas.width = containerRect.width;
 | 
				
			||||||
 | 
					    pdfCanvas.height = containerRect.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overlayCanvas.width = containerRect.width;
 | 
				
			||||||
 | 
					    overlayCanvas.height = containerRect.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let file = fileInput.files[0];
 | 
				
			||||||
 | 
					    renderPageFromFile(file);
 | 
				
			||||||
 | 
					  }, 1000);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileInput.addEventListener('change', function (e) {
 | 
				
			||||||
 | 
					  fileInput.addEventListener('file-input-change', async (e) => {
 | 
				
			||||||
 | 
					    const {allFiles} = e.detail;
 | 
				
			||||||
 | 
					    if (allFiles && allFiles.length > 0) {
 | 
				
			||||||
 | 
					      canvasesContainer.style.display = 'block'; // set for visual purposes
 | 
				
			||||||
 | 
					      let file = allFiles[0];
 | 
				
			||||||
 | 
					      renderPageFromFile(file);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cropForm.addEventListener('submit', function (e) {
 | 
				
			||||||
 | 
					  if (xInput.value == '' && yInput.value == '' && widthInput.value == '' && heightInput.value == '') {
 | 
				
			||||||
 | 
					    // Ορίστε συντεταγμένες για ολόκληρη την επιφάνεια του PDF
 | 
				
			||||||
 | 
					    xInput.value = 0;
 | 
				
			||||||
 | 
					    yInput.value = 0;
 | 
				
			||||||
 | 
					    widthInput.value = containerRect.width;
 | 
				
			||||||
 | 
					    heightInput.value = containerRect.height;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overlayCanvas.addEventListener('mousedown', function (e) {
 | 
				
			||||||
 | 
					  // Clear previously drawn rectangle on the main canvas
 | 
				
			||||||
 | 
					  context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
 | 
				
			||||||
 | 
					  renderPage(currentPage); // Re-render the PDF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Clear the overlay canvas to ensure old drawings are removed
 | 
				
			||||||
 | 
					  overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  startX = e.offsetX;
 | 
				
			||||||
 | 
					  startY = e.offsetY;
 | 
				
			||||||
 | 
					  isDrawing = true;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overlayCanvas.addEventListener('mousemove', function (e) {
 | 
				
			||||||
 | 
					  if (!isDrawing) return;
 | 
				
			||||||
 | 
					  overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rectWidth = e.offsetX - startX;
 | 
				
			||||||
 | 
					  rectHeight = e.offsetY - startY;
 | 
				
			||||||
 | 
					  overlayContext.strokeStyle = 'red';
 | 
				
			||||||
 | 
					  overlayContext.strokeRect(startX, startY, rectWidth, rectHeight);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overlayCanvas.addEventListener('mouseup', function (e) {
 | 
				
			||||||
 | 
					  isDrawing = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rectWidth = e.offsetX - startX;
 | 
				
			||||||
 | 
					  rectHeight = e.offsetY - startY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let flippedY = pdfCanvas.height - e.offsetY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  xInput.value = startX / pageScale;
 | 
				
			||||||
 | 
					  yInput.value = flippedY / pageScale;
 | 
				
			||||||
 | 
					  widthInput.value = rectWidth / pageScale;
 | 
				
			||||||
 | 
					  heightInput.value = rectHeight / pageScale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Draw the final rectangle on the main canvas
 | 
				
			||||||
 | 
					  context.strokeStyle = 'red';
 | 
				
			||||||
 | 
					  context.strokeRect(startX, startY, rectWidth, rectHeight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renderPage(pageNumber) {
 | 
				
			||||||
 | 
					  pdfDoc.getPage(pageNumber).then(function (page) {
 | 
				
			||||||
 | 
					    let canvasesContainer = document.getElementById('canvasesContainer');
 | 
				
			||||||
 | 
					    let containerRect = canvasesContainer.getBoundingClientRect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pageScale = containerRect.width / page.getViewport({scale: 1}).width; // The new scale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let viewport = page.getViewport({scale: containerRect.width / page.getViewport({scale: 1}).width});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvasesContainer.width = viewport.width;
 | 
				
			||||||
 | 
					    canvasesContainer.height = viewport.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pdfCanvas.width = viewport.width;
 | 
				
			||||||
 | 
					    pdfCanvas.height = viewport.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
 | 
				
			||||||
 | 
					    overlayCanvas.height = viewport.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let renderContext = {canvasContext: context, viewport: viewport};
 | 
				
			||||||
 | 
					    page.render(renderContext);
 | 
				
			||||||
 | 
					    pdfCanvas.classList.add('shadow-canvas');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										138
									
								
								src/main/resources/static/js/pages/pdf-to-csv.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/main/resources/static/js/pages/pdf-to-csv.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					let pdfCanvas = document.getElementById('cropPdfCanvas');
 | 
				
			||||||
 | 
					let overlayCanvas = document.getElementById('overlayCanvas');
 | 
				
			||||||
 | 
					let canvasesContainer = document.getElementById('canvasesContainer');
 | 
				
			||||||
 | 
					canvasesContainer.style.display = 'none';
 | 
				
			||||||
 | 
					// let paginationBtnContainer = ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let context = pdfCanvas.getContext('2d');
 | 
				
			||||||
 | 
					let overlayContext = overlayCanvas.getContext('2d');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let btn1Object = document.getElementById('previous-page-btn');
 | 
				
			||||||
 | 
					let btn2Object = document.getElementById('next-page-btn');
 | 
				
			||||||
 | 
					overlayCanvas.width = pdfCanvas.width;
 | 
				
			||||||
 | 
					overlayCanvas.height = pdfCanvas.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let fileInput = document.getElementById('fileInput-input');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let file;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let pdfDoc = null;
 | 
				
			||||||
 | 
					let pageId = document.getElementById('pageId');
 | 
				
			||||||
 | 
					let currentPage = 1;
 | 
				
			||||||
 | 
					let totalPages = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let startX = 0;
 | 
				
			||||||
 | 
					let startY = 0;
 | 
				
			||||||
 | 
					let rectWidth = 0;
 | 
				
			||||||
 | 
					let rectHeight = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let timeId = null; // timeout id for resizing canvases event
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					btn1Object.addEventListener('click', function (e) {
 | 
				
			||||||
 | 
					  if (currentPage !== 1) {
 | 
				
			||||||
 | 
					    currentPage = currentPage - 1;
 | 
				
			||||||
 | 
					    pageId.value = currentPage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (file.type === 'application/pdf') {
 | 
				
			||||||
 | 
					      let reader = new FileReader();
 | 
				
			||||||
 | 
					      reader.onload = function (ev) {
 | 
				
			||||||
 | 
					        let typedArray = new Uint8Array(reader.result);
 | 
				
			||||||
 | 
					        pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					        pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
 | 
				
			||||||
 | 
					          pdfDoc = pdf;
 | 
				
			||||||
 | 
					          totalPages = pdf.numPages;
 | 
				
			||||||
 | 
					          renderPage(currentPage);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      reader.readAsArrayBuffer(file);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					btn2Object.addEventListener('click', function (e) {
 | 
				
			||||||
 | 
					  if (currentPage !== totalPages) {
 | 
				
			||||||
 | 
					    currentPage = currentPage + 1;
 | 
				
			||||||
 | 
					    pageId.value = currentPage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (file.type === 'application/pdf') {
 | 
				
			||||||
 | 
					      let reader = new FileReader();
 | 
				
			||||||
 | 
					      reader.onload = function (ev) {
 | 
				
			||||||
 | 
					        let typedArray = new Uint8Array(reader.result);
 | 
				
			||||||
 | 
					        pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					        pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
 | 
				
			||||||
 | 
					          pdfDoc = pdf;
 | 
				
			||||||
 | 
					          totalPages = pdf.numPages;
 | 
				
			||||||
 | 
					          renderPage(currentPage);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      reader.readAsArrayBuffer(file);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renderPageFromFile(file) {
 | 
				
			||||||
 | 
					  if (file.type === 'application/pdf') {
 | 
				
			||||||
 | 
					    let reader = new FileReader();
 | 
				
			||||||
 | 
					    reader.onload = function (ev) {
 | 
				
			||||||
 | 
					      let typedArray = new Uint8Array(reader.result);
 | 
				
			||||||
 | 
					      pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					      pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
 | 
				
			||||||
 | 
					        pdfDoc = pdf;
 | 
				
			||||||
 | 
					        totalPages = pdf.numPages;
 | 
				
			||||||
 | 
					        renderPage(currentPage);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      pageId.value = currentPage;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    reader.readAsArrayBuffer(file);
 | 
				
			||||||
 | 
					    document.getElementById('pagination-button-container').style.display = 'flex';
 | 
				
			||||||
 | 
					    document.getElementById('instruction-text').style.display = 'block';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.addEventListener('resize', function () {
 | 
				
			||||||
 | 
					  clearTimeout(timeId);
 | 
				
			||||||
 | 
					  timeId = setTimeout(function () {
 | 
				
			||||||
 | 
					    if (fileInput.files.length == 0) return;
 | 
				
			||||||
 | 
					    let canvasesContainer = document.getElementById('canvasesContainer');
 | 
				
			||||||
 | 
					    let containerRect = canvasesContainer.getBoundingClientRect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pdfCanvas.width = containerRect.width;
 | 
				
			||||||
 | 
					    pdfCanvas.height = containerRect.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overlayCanvas.width = containerRect.width;
 | 
				
			||||||
 | 
					    overlayCanvas.height = containerRect.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let file = fileInput.files[0];
 | 
				
			||||||
 | 
					    renderPageFromFile(file);
 | 
				
			||||||
 | 
					  }, 1000);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fileInput.addEventListener('change', function (e) {
 | 
				
			||||||
 | 
					  fileInput.addEventListener('file-input-change', async (e) => {
 | 
				
			||||||
 | 
					    const {allFiles} = e.detail;
 | 
				
			||||||
 | 
					    if (allFiles && allFiles.length > 0) {
 | 
				
			||||||
 | 
					      canvasesContainer.style.display = 'block'; // set for visual purposes
 | 
				
			||||||
 | 
					      file = e.target.files[0];
 | 
				
			||||||
 | 
					      renderPageFromFile(file);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renderPage(pageNumber) {
 | 
				
			||||||
 | 
					  pdfDoc.getPage(pageNumber).then(function (page) {
 | 
				
			||||||
 | 
					    let viewport = page.getViewport({scale: 1.0});
 | 
				
			||||||
 | 
					    pdfCanvas.width = viewport.width;
 | 
				
			||||||
 | 
					    pdfCanvas.height = viewport.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas
 | 
				
			||||||
 | 
					    overlayCanvas.height = viewport.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let renderContext = {canvasContext: context, viewport: viewport};
 | 
				
			||||||
 | 
					    page.render(renderContext);
 | 
				
			||||||
 | 
					    pdfCanvas.classList.add('shadow-canvas');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										213
									
								
								src/main/resources/static/js/pages/sign.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/main/resources/static/js/pages/sign.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,213 @@
 | 
				
			|||||||
 | 
					window.toggleSignatureView = toggleSignatureView;
 | 
				
			||||||
 | 
					window.previewSignature = previewSignature;
 | 
				
			||||||
 | 
					window.addSignatureFromPreview = addSignatureFromPreview;
 | 
				
			||||||
 | 
					window.addDraggableFromPad = addDraggableFromPad;
 | 
				
			||||||
 | 
					window.addDraggableFromText = addDraggableFromText;
 | 
				
			||||||
 | 
					window.goToFirstOrLastPage = goToFirstOrLastPage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let currentPreviewSrc = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function toggleSignatureView() {
 | 
				
			||||||
 | 
					  const gridView = document.getElementById('gridView');
 | 
				
			||||||
 | 
					  const listView = document.getElementById('listView');
 | 
				
			||||||
 | 
					  const gridText = document.querySelector('.grid-view-text');
 | 
				
			||||||
 | 
					  const listText = document.querySelector('.list-view-text');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (gridView.style.display !== 'none') {
 | 
				
			||||||
 | 
					    gridView.style.display = 'none';
 | 
				
			||||||
 | 
					    listView.style.display = 'block';
 | 
				
			||||||
 | 
					    gridText.style.display = 'none';
 | 
				
			||||||
 | 
					    listText.style.display = 'inline';
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    gridView.style.display = 'block';
 | 
				
			||||||
 | 
					    listView.style.display = 'none';
 | 
				
			||||||
 | 
					    gridText.style.display = 'inline';
 | 
				
			||||||
 | 
					    listText.style.display = 'none';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function previewSignature(element) {
 | 
				
			||||||
 | 
					  const src = element.dataset.src;
 | 
				
			||||||
 | 
					  currentPreviewSrc = src;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const filename = element.querySelector('.signature-list-name').textContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const previewImage = document.getElementById('previewImage');
 | 
				
			||||||
 | 
					  const previewFileName = document.getElementById('previewFileName');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  previewImage.src = src;
 | 
				
			||||||
 | 
					  previewFileName.textContent = filename;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
 | 
				
			||||||
 | 
					  modal.show();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function addSignatureFromPreview() {
 | 
				
			||||||
 | 
					  if (currentPreviewSrc) {
 | 
				
			||||||
 | 
					    DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
 | 
				
			||||||
 | 
					    bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let originalFileName = '';
 | 
				
			||||||
 | 
					document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
 | 
				
			||||||
 | 
					  const fileInput = event.target;
 | 
				
			||||||
 | 
					  fileInput.addEventListener('file-input-change', async (e) => {
 | 
				
			||||||
 | 
					    const {allFiles} = e.detail;
 | 
				
			||||||
 | 
					    if (allFiles && allFiles.length > 0) {
 | 
				
			||||||
 | 
					      const file = allFiles[0];
 | 
				
			||||||
 | 
					      originalFileName = file.name.replace(/\.[^/.]+$/, '');
 | 
				
			||||||
 | 
					      const pdfData = await file.arrayBuffer();
 | 
				
			||||||
 | 
					      pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
				
			||||||
 | 
					      const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise;
 | 
				
			||||||
 | 
					      await DraggableUtils.renderPage(pdfDoc, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      document.querySelectorAll('.show-on-file-selected').forEach((el) => {
 | 
				
			||||||
 | 
					        el.style.cssText = '';
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.addEventListener('DOMContentLoaded', () => {
 | 
				
			||||||
 | 
					  document.querySelectorAll('.show-on-file-selected').forEach((el) => {
 | 
				
			||||||
 | 
					    el.style.cssText = 'display:none !important';
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imageUpload = document.querySelector('input[name=image-upload]');
 | 
				
			||||||
 | 
					imageUpload.addEventListener('change', (e) => {
 | 
				
			||||||
 | 
					  if (!e.target.files) return;
 | 
				
			||||||
 | 
					  for (const imageFile of e.target.files) {
 | 
				
			||||||
 | 
					    var reader = new FileReader();
 | 
				
			||||||
 | 
					    reader.readAsDataURL(imageFile);
 | 
				
			||||||
 | 
					    reader.onloadend = function (e) {
 | 
				
			||||||
 | 
					      DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
 | 
				
			||||||
 | 
					const signaturePad = new SignaturePad(signaturePadCanvas, {
 | 
				
			||||||
 | 
					  minWidth: 1,
 | 
				
			||||||
 | 
					  maxWidth: 2,
 | 
				
			||||||
 | 
					  penColor: 'black',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function addDraggableFromPad() {
 | 
				
			||||||
 | 
					  if (signaturePad.isEmpty()) return;
 | 
				
			||||||
 | 
					  const startTime = Date.now();
 | 
				
			||||||
 | 
					  const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
 | 
				
			||||||
 | 
					  console.log(Date.now() - startTime);
 | 
				
			||||||
 | 
					  DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getCroppedCanvasDataUrl(canvas) {
 | 
				
			||||||
 | 
					  let originalCtx = canvas.getContext('2d');
 | 
				
			||||||
 | 
					  let originalWidth = canvas.width;
 | 
				
			||||||
 | 
					  let originalHeight = canvas.height;
 | 
				
			||||||
 | 
					  let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let minX = originalWidth + 1,
 | 
				
			||||||
 | 
					    maxX = -1,
 | 
				
			||||||
 | 
					    minY = originalHeight + 1,
 | 
				
			||||||
 | 
					    maxY = -1,
 | 
				
			||||||
 | 
					    x = 0,
 | 
				
			||||||
 | 
					    y = 0,
 | 
				
			||||||
 | 
					    currentPixelColorValueIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (y = 0; y < originalHeight; y++) {
 | 
				
			||||||
 | 
					    for (x = 0; x < originalWidth; x++) {
 | 
				
			||||||
 | 
					      currentPixelColorValueIndex = (y * originalWidth + x) * 4;
 | 
				
			||||||
 | 
					      let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
 | 
				
			||||||
 | 
					      if (currentPixelAlphaValue > 0) {
 | 
				
			||||||
 | 
					        if (minX > x) minX = x;
 | 
				
			||||||
 | 
					        if (maxX < x) maxX = x;
 | 
				
			||||||
 | 
					        if (minY > y) minY = y;
 | 
				
			||||||
 | 
					        if (maxY < y) maxY = y;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let croppedWidth = maxX - minX;
 | 
				
			||||||
 | 
					  let croppedHeight = maxY - minY;
 | 
				
			||||||
 | 
					  if (croppedWidth < 0 || croppedHeight < 0) return null;
 | 
				
			||||||
 | 
					  let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let croppedCanvas = document.createElement('canvas'),
 | 
				
			||||||
 | 
					    croppedCtx = croppedCanvas.getContext('2d');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  croppedCanvas.width = croppedWidth;
 | 
				
			||||||
 | 
					  croppedCanvas.height = croppedHeight;
 | 
				
			||||||
 | 
					  croppedCtx.putImageData(cuttedImageData, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return croppedCanvas.toDataURL();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function resizeCanvas() {
 | 
				
			||||||
 | 
					  var ratio = Math.max(window.devicePixelRatio || 1, 1);
 | 
				
			||||||
 | 
					  var additionalFactor = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
 | 
				
			||||||
 | 
					  signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
 | 
				
			||||||
 | 
					  signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  signaturePad.clear();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					new IntersectionObserver((entries, observer) => {
 | 
				
			||||||
 | 
					  if (entries.some((entry) => entry.intersectionRatio > 0)) {
 | 
				
			||||||
 | 
					    resizeCanvas();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}).observe(signaturePadCanvas);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function addDraggableFromText() {
 | 
				
			||||||
 | 
					  const sigText = document.getElementById('sigText').value;
 | 
				
			||||||
 | 
					  const font = document.querySelector('select[name=font]').value;
 | 
				
			||||||
 | 
					  const fontSize = 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const canvas = document.createElement('canvas');
 | 
				
			||||||
 | 
					  const ctx = canvas.getContext('2d');
 | 
				
			||||||
 | 
					  ctx.font = `${fontSize}px ${font}`;
 | 
				
			||||||
 | 
					  const textWidth = ctx.measureText(sigText).width;
 | 
				
			||||||
 | 
					  const textHeight = fontSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let paragraphs = sigText.split(/\r?\n/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  canvas.width = textWidth;
 | 
				
			||||||
 | 
					  canvas.height = paragraphs.length * textHeight * 1.35; // for tails
 | 
				
			||||||
 | 
					  ctx.font = `${fontSize}px ${font}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ctx.textBaseline = 'top';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let y = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  paragraphs.forEach((paragraph) => {
 | 
				
			||||||
 | 
					    ctx.fillText(paragraph, 0, y);
 | 
				
			||||||
 | 
					    y += fontSize;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const dataURL = canvas.toDataURL();
 | 
				
			||||||
 | 
					  DraggableUtils.createDraggableCanvasFromUrl(dataURL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function goToFirstOrLastPage(page) {
 | 
				
			||||||
 | 
					  if (page) {
 | 
				
			||||||
 | 
					    const lastPage = DraggableUtils.pdfDoc.numPages;
 | 
				
			||||||
 | 
					    await DraggableUtils.goToPage(lastPage - 1);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    await DraggableUtils.goToPage(0);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById('download-pdf').addEventListener('click', async () => {
 | 
				
			||||||
 | 
					  const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
 | 
				
			||||||
 | 
					  const modifiedPdfBytes = await modifiedPdf.save();
 | 
				
			||||||
 | 
					  const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'});
 | 
				
			||||||
 | 
					  const link = document.createElement('a');
 | 
				
			||||||
 | 
					  link.href = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					  link.download = originalFileName + '_signed.pdf';
 | 
				
			||||||
 | 
					  link.click();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -34,140 +34,8 @@
 | 
				
			|||||||
                <canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas>
 | 
					                <canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					              <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
              <script>
 | 
					              <script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
 | 
				
			||||||
                let pdfCanvas  = document.getElementById('cropPdfCanvas');
 | 
					              <script type="module" th:src="@{'/js/pages/pdf-to-csv.js'}">
 | 
				
			||||||
                let overlayCanvas = document.getElementById('overlayCanvas');
 | 
					 | 
				
			||||||
                let canvasesContainer = document.getElementById('canvasesContainer');
 | 
					 | 
				
			||||||
                canvasesContainer.style.display = "none";
 | 
					 | 
				
			||||||
                // let paginationBtnContainer = ;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let context = pdfCanvas.getContext('2d');
 | 
					 | 
				
			||||||
                let overlayContext = overlayCanvas.getContext('2d');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let btn1Object = document.getElementById('previous-page-btn');
 | 
					 | 
				
			||||||
                let btn2Object = document.getElementById('next-page-btn');
 | 
					 | 
				
			||||||
                overlayCanvas.width = pdfCanvas.width;
 | 
					 | 
				
			||||||
                overlayCanvas.height = pdfCanvas.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let fileInput = document.getElementById('fileInput-input');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let file;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let pdfDoc = null;
 | 
					 | 
				
			||||||
                let pageId = document.getElementById('pageId');
 | 
					 | 
				
			||||||
                let currentPage = 1;
 | 
					 | 
				
			||||||
                let totalPages = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let startX = 0;
 | 
					 | 
				
			||||||
                let startY = 0;
 | 
					 | 
				
			||||||
                let rectWidth = 0;
 | 
					 | 
				
			||||||
                let rectHeight = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let timeId = null; // timeout id for resizing canvases event
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                btn1Object.addEventListener('click',function (e){
 | 
					 | 
				
			||||||
                  if (currentPage !== 1) {
 | 
					 | 
				
			||||||
                    currentPage = currentPage - 1;
 | 
					 | 
				
			||||||
                    pageId.value = currentPage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (file.type === 'application/pdf') {
 | 
					 | 
				
			||||||
                      let reader = new FileReader();
 | 
					 | 
				
			||||||
                      reader.onload = function (ev) {
 | 
					 | 
				
			||||||
                        let typedArray = new Uint8Array(reader.result);
 | 
					 | 
				
			||||||
                        pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
 | 
					 | 
				
			||||||
                        pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
 | 
					 | 
				
			||||||
                          pdfDoc = pdf;
 | 
					 | 
				
			||||||
                          totalPages = pdf.numPages;
 | 
					 | 
				
			||||||
                          renderPage(currentPage);
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                      };
 | 
					 | 
				
			||||||
                      reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                btn2Object.addEventListener('click',function (e){
 | 
					 | 
				
			||||||
                  if (currentPage !== totalPages){
 | 
					 | 
				
			||||||
                    currentPage=currentPage+1;
 | 
					 | 
				
			||||||
                    pageId.value = currentPage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (file.type === 'application/pdf') {
 | 
					 | 
				
			||||||
                      let reader = new FileReader();
 | 
					 | 
				
			||||||
                      reader.onload = function(ev) {
 | 
					 | 
				
			||||||
                        let typedArray = new Uint8Array(reader.result);
 | 
					 | 
				
			||||||
                        pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
 | 
					 | 
				
			||||||
                        pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
 | 
					 | 
				
			||||||
                          pdfDoc = pdf;
 | 
					 | 
				
			||||||
                          totalPages = pdf.numPages;
 | 
					 | 
				
			||||||
                          renderPage(currentPage);
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                      };
 | 
					 | 
				
			||||||
                      reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function renderPageFromFile(file) {
 | 
					 | 
				
			||||||
                  if (file.type === 'application/pdf') {
 | 
					 | 
				
			||||||
                    let reader = new FileReader();
 | 
					 | 
				
			||||||
                    reader.onload = function (ev) {
 | 
					 | 
				
			||||||
                      let typedArray = new Uint8Array(reader.result);
 | 
					 | 
				
			||||||
                      pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
					 | 
				
			||||||
                      pdfjsLib.getDocument(typedArray).promise.then(function (pdf) {
 | 
					 | 
				
			||||||
                        pdfDoc = pdf;
 | 
					 | 
				
			||||||
                        totalPages = pdf.numPages;
 | 
					 | 
				
			||||||
                        renderPage(currentPage);
 | 
					 | 
				
			||||||
                      });
 | 
					 | 
				
			||||||
                      pageId.value = currentPage;
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
                    document.getElementById("pagination-button-container").style.display = "flex";
 | 
					 | 
				
			||||||
                    document.getElementById("instruction-text").style.display = "block";
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                window.addEventListener("resize", function() {
 | 
					 | 
				
			||||||
                  clearTimeout(timeId);
 | 
					 | 
				
			||||||
                  timeId = setTimeout(function () {
 | 
					 | 
				
			||||||
                    if (fileInput.files.length == 0) return;
 | 
					 | 
				
			||||||
                    let canvasesContainer = document.getElementById('canvasesContainer');
 | 
					 | 
				
			||||||
                    let containerRect = canvasesContainer.getBoundingClientRect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    pdfCanvas.width = containerRect.width;
 | 
					 | 
				
			||||||
                    pdfCanvas.height = containerRect.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    overlayCanvas.width = containerRect.width;
 | 
					 | 
				
			||||||
                    overlayCanvas.height = containerRect.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let file = fileInput.files[0];
 | 
					 | 
				
			||||||
                    renderPageFromFile(file);
 | 
					 | 
				
			||||||
                  }, 1000);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                fileInput.addEventListener('change', function(e) {
 | 
					 | 
				
			||||||
                  canvasesContainer.style.display = "block"; // set for visual purposes
 | 
					 | 
				
			||||||
                  file = e.target.files[0];
 | 
					 | 
				
			||||||
                  renderPageFromFile(file);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function renderPage(pageNumber) {
 | 
					 | 
				
			||||||
                  pdfDoc.getPage(pageNumber).then(function(page) {
 | 
					 | 
				
			||||||
                    let viewport = page.getViewport({ scale: 1.0 });
 | 
					 | 
				
			||||||
                    pdfCanvas.width = viewport.width;
 | 
					 | 
				
			||||||
                    pdfCanvas.height = viewport.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    overlayCanvas.width = viewport.width;  // Match overlay canvas size with PDF canvas
 | 
					 | 
				
			||||||
                    overlayCanvas.height = viewport.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let renderContext = { canvasContext: context, viewport: viewport };
 | 
					 | 
				
			||||||
                    page.render(renderContext);
 | 
					 | 
				
			||||||
                    pdfCanvas.classList.add("shadow-canvas");
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              </script>
 | 
					              </script>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -32,163 +32,7 @@
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					          <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
          <script>
 | 
					          <script type="module" th:src="@{'/js/pages/crop.js'}"></script>
 | 
				
			||||||
            let pdfCanvas  = document.getElementById('cropPdfCanvas');
 | 
					 | 
				
			||||||
            let overlayCanvas = document.getElementById('overlayCanvas');
 | 
					 | 
				
			||||||
            let canvasesContainer = document.getElementById('canvasesContainer');
 | 
					 | 
				
			||||||
            canvasesContainer.style.display = "none";
 | 
					 | 
				
			||||||
            let containerRect = canvasesContainer.getBoundingClientRect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let context = pdfCanvas.getContext('2d');
 | 
					 | 
				
			||||||
            let overlayContext = overlayCanvas.getContext('2d');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            overlayCanvas.width = pdfCanvas.width;
 | 
					 | 
				
			||||||
            overlayCanvas.height = pdfCanvas.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let isDrawing = false; // New flag to check if drawing is ongoing
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let cropForm = document.getElementById('cropForm');
 | 
					 | 
				
			||||||
            let fileInput = document.getElementById('fileInput-input');
 | 
					 | 
				
			||||||
            let xInput = document.getElementById('x');
 | 
					 | 
				
			||||||
            let yInput = document.getElementById('y');
 | 
					 | 
				
			||||||
            let widthInput = document.getElementById('width');
 | 
					 | 
				
			||||||
            let heightInput = document.getElementById('height');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let pdfDoc = null;
 | 
					 | 
				
			||||||
            let currentPage = 1;
 | 
					 | 
				
			||||||
            let totalPages = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let startX = 0;
 | 
					 | 
				
			||||||
            let startY = 0;
 | 
					 | 
				
			||||||
            let rectWidth = 0;
 | 
					 | 
				
			||||||
            let rectHeight = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let pageScale = 1; // The scale which the pdf page renders
 | 
					 | 
				
			||||||
            let timeId = null; // timeout id for resizing canvases event
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            function renderPageFromFile(file) {
 | 
					 | 
				
			||||||
              if (file.type === 'application/pdf') {
 | 
					 | 
				
			||||||
                let reader = new FileReader();
 | 
					 | 
				
			||||||
                reader.onload = function(ev) {
 | 
					 | 
				
			||||||
                  let typedArray = new Uint8Array(reader.result);
 | 
					 | 
				
			||||||
                  pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
 | 
					 | 
				
			||||||
                  pdfjsLib.getDocument(typedArray).promise.then(function(pdf) {
 | 
					 | 
				
			||||||
                    pdfDoc = pdf;
 | 
					 | 
				
			||||||
                    totalPages = pdf.numPages;
 | 
					 | 
				
			||||||
                    renderPage(currentPage);
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                reader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            window.addEventListener("resize", function() {
 | 
					 | 
				
			||||||
              clearTimeout(timeId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              timeId = setTimeout(function () {
 | 
					 | 
				
			||||||
                 if (fileInput.files.length == 0) return;
 | 
					 | 
				
			||||||
                 let canvasesContainer = document.getElementById('canvasesContainer');
 | 
					 | 
				
			||||||
                 let containerRect = canvasesContainer.getBoundingClientRect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                 context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                 overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                 pdfCanvas.width = containerRect.width;
 | 
					 | 
				
			||||||
                 pdfCanvas.height = containerRect.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                 overlayCanvas.width = containerRect.width;
 | 
					 | 
				
			||||||
                 overlayCanvas.height = containerRect.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                 let file = fileInput.files[0];
 | 
					 | 
				
			||||||
                 renderPageFromFile(file);
 | 
					 | 
				
			||||||
             }, 1000);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            fileInput.addEventListener('change', function(e) {
 | 
					 | 
				
			||||||
              canvasesContainer.style.display = "block"; // set for visual purposes
 | 
					 | 
				
			||||||
              let file = e.target.files[0];
 | 
					 | 
				
			||||||
              renderPageFromFile(file);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cropForm.addEventListener('submit', function(e) {
 | 
					 | 
				
			||||||
              if (xInput.value == "" && yInput.value == "" && widthInput.value == "" && heightInput.value == "") {
 | 
					 | 
				
			||||||
                // Ορίστε συντεταγμένες για ολόκληρη την επιφάνεια του PDF
 | 
					 | 
				
			||||||
                xInput.value = 0;
 | 
					 | 
				
			||||||
                yInput.value = 0;
 | 
					 | 
				
			||||||
                widthInput.value = containerRect.width;
 | 
					 | 
				
			||||||
                heightInput.value = containerRect.height;
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            overlayCanvas.addEventListener('mousedown', function(e) {
 | 
					 | 
				
			||||||
              // Clear previously drawn rectangle on the main canvas
 | 
					 | 
				
			||||||
              context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height);
 | 
					 | 
				
			||||||
              renderPage(currentPage); // Re-render the PDF
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              // Clear the overlay canvas to ensure old drawings are removed
 | 
					 | 
				
			||||||
              overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              startX = e.offsetX;
 | 
					 | 
				
			||||||
              startY = e.offsetY;
 | 
					 | 
				
			||||||
              isDrawing = true;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            overlayCanvas.addEventListener('mousemove', function(e) {
 | 
					 | 
				
			||||||
              if (!isDrawing) return;
 | 
					 | 
				
			||||||
              overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              rectWidth = e.offsetX - startX;
 | 
					 | 
				
			||||||
              rectHeight = e.offsetY - startY;
 | 
					 | 
				
			||||||
              overlayContext.strokeStyle = 'red';
 | 
					 | 
				
			||||||
              overlayContext.strokeRect(startX, startY, rectWidth, rectHeight);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            overlayCanvas.addEventListener('mouseup', function(e) {
 | 
					 | 
				
			||||||
              isDrawing = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              rectWidth = e.offsetX - startX;
 | 
					 | 
				
			||||||
              rectHeight = e.offsetY - startY;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              let flippedY = pdfCanvas.height - e.offsetY;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              xInput.value = startX / pageScale;
 | 
					 | 
				
			||||||
              yInput.value = flippedY / pageScale;
 | 
					 | 
				
			||||||
              widthInput.value = rectWidth / pageScale;
 | 
					 | 
				
			||||||
              heightInput.value = rectHeight /pageScale;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              // Draw the final rectangle on the main canvas
 | 
					 | 
				
			||||||
              context.strokeStyle = 'red';
 | 
					 | 
				
			||||||
              context.strokeRect(startX, startY, rectWidth, rectHeight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            function renderPage(pageNumber) {
 | 
					 | 
				
			||||||
              pdfDoc.getPage(pageNumber).then(function(page) {
 | 
					 | 
				
			||||||
                let canvasesContainer = document.getElementById('canvasesContainer');
 | 
					 | 
				
			||||||
                let containerRect = canvasesContainer.getBoundingClientRect();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                pageScale = containerRect.width / page.getViewport({ scale: 1 }).width; // The new scale
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let viewport = page.getViewport({ scale: containerRect.width / page.getViewport({ scale: 1 }).width });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                canvasesContainer.width =viewport.width;
 | 
					 | 
				
			||||||
                canvasesContainer.height = viewport.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                pdfCanvas.width = viewport.width;
 | 
					 | 
				
			||||||
                pdfCanvas.height = viewport.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                overlayCanvas.width = viewport.width;  // Match overlay canvas size with PDF canvas
 | 
					 | 
				
			||||||
                overlayCanvas.height = viewport.height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let renderContext = { canvasContext: context, viewport: viewport };
 | 
					 | 
				
			||||||
                page.render(renderContext);
 | 
					 | 
				
			||||||
                pdfCanvas.classList.add("shadow-canvas");
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          </script>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
					      <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@
 | 
				
			|||||||
            <p th:text="#{error.contactTip}"></p>
 | 
					            <p th:text="#{error.contactTip}"></p>
 | 
				
			||||||
            <div id="button-group">
 | 
					            <div id="button-group">
 | 
				
			||||||
              <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a>
 | 
					              <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a>
 | 
				
			||||||
              <a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a>
 | 
					              <a href="https://discord.gg/HYmhKj45pU" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary"  th:text="#{goHomepage}"></a>
 | 
					            <a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary"  th:text="#{goHomepage}"></a>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -203,7 +203,17 @@
 | 
				
			|||||||
                </script>
 | 
					                </script>
 | 
				
			||||||
                <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					                <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
                <script th:src="@{'/js/downloader.js'}"></script>
 | 
					                <script th:src="@{'/js/downloader.js'}"></script>
 | 
				
			||||||
 | 
					                <script>
 | 
				
			||||||
 | 
					                  window.decrypt = {
 | 
				
			||||||
 | 
					                    passwordPrompt: '[[#{decrypt.passwordPrompt}]]',
 | 
				
			||||||
 | 
					                    cancelled: '[[#{decrypt.cancelled}]]',
 | 
				
			||||||
 | 
					                    noPassword: '[[#{decrypt.noPassword}]]',
 | 
				
			||||||
 | 
					                    invalidPassword: '[[#{decrypt.invalidPassword}]]',
 | 
				
			||||||
 | 
					                    invalidPasswordHeader: '[[#{decrypt.invalidPasswordHeader}]]',
 | 
				
			||||||
 | 
					                    unexpectedError: '[[#{decrypt.unexpectedError}]]',
 | 
				
			||||||
 | 
					                    serverError: '[[#{decrypt.serverError}]]',
 | 
				
			||||||
 | 
					                    success: '[[#{decrypt.success}]]',
 | 
				
			||||||
 | 
					              };</script>
 | 
				
			||||||
                <div class="custom-file-chooser mb-3" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
 | 
					                <div class="custom-file-chooser mb-3" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
 | 
				
			||||||
                  <div class="mb-3 d-flex flex-row justify-content-center align-items-center flex-wrap input-container" th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}">
 | 
					                  <div class="mb-3 d-flex flex-row justify-content-center align-items-center flex-wrap input-container" th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}">
 | 
				
			||||||
                      <label class="file-input-btn d-none">
 | 
					                      <label class="file-input-btn d-none">
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <!-- Buttons to submit a ticket on GitHub and join Discord server -->
 | 
					      <!-- Buttons to submit a ticket on GitHub and join Discord server -->
 | 
				
			||||||
      <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.github}"></a>
 | 
					      <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.github}"></a>
 | 
				
			||||||
      <a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank" th:text="#{joinDiscord}"></a>
 | 
					      <a href="https://discord.gg/HYmhKj45pU" id="discord-button" target="_blank" th:text="#{joinDiscord}"></a>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <script>
 | 
					  <script>
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@
 | 
				
			|||||||
                      <p th:text="#{error.contactTip}"></p>
 | 
					                      <p th:text="#{error.contactTip}"></p>
 | 
				
			||||||
                      <div id="button-group">
 | 
					                      <div id="button-group">
 | 
				
			||||||
                        <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.githubSubmit}"></a>
 | 
					                        <a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.githubSubmit}"></a>
 | 
				
			||||||
                        <a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank" th:text="#{error.discordSubmit}"></a>
 | 
					                        <a href="https://discord.gg/HYmhKj45pU" id="discord-button" target="_blank" th:text="#{error.discordSubmit}"></a>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                      <a th:href="@{'/'}" class="home-button" th:text="#{goHomepage}"></a>
 | 
					                      <a th:href="@{'/'}" class="home-button" th:text="#{goHomepage}"></a>
 | 
				
			||||||
                      <a data-bs-dismiss="modal" class="home-button" th:text="#{close}"></a>
 | 
					                      <a data-bs-dismiss="modal" class="home-button" th:text="#{close}"></a>
 | 
				
			||||||
 | 
				
			|||||||
@ -482,7 +482,11 @@ document.addEventListener("DOMContentLoaded", function() {
 | 
				
			|||||||
        localStorage.setItem('surveyVersion', surveyVersion);
 | 
					        localStorage.setItem('surveyVersion', surveyVersion);
 | 
				
			||||||
        modal.hide();
 | 
					        modal.hide();
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
});
 | 
					
 | 
				
			||||||
 | 
					      if (localStorage.getItem('dontShowSurvey')) {
 | 
				
			||||||
 | 
					        modal.hide();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  </script>
 | 
					  </script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,46 +22,9 @@
 | 
				
			|||||||
              <!-- pdf selector -->
 | 
					              <!-- pdf selector -->
 | 
				
			||||||
              <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
 | 
					              <div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
 | 
				
			||||||
              <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					              <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
              <script>
 | 
					              <script type="module" th:src="@{'/js/pages/add-image.js'}"></script>
 | 
				
			||||||
                let originalFileName = '';
 | 
					 | 
				
			||||||
                document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
 | 
					 | 
				
			||||||
                  const file = event.target.files[0];
 | 
					 | 
				
			||||||
                  if (file) {
 | 
					 | 
				
			||||||
                    originalFileName = file.name.replace(/\.[^/.]+$/, "");
 | 
					 | 
				
			||||||
                    const pdfData = await file.arrayBuffer();
 | 
					 | 
				
			||||||
                    pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
 | 
					 | 
				
			||||||
                    const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
 | 
					 | 
				
			||||||
                    await DraggableUtils.renderPage(pdfDoc, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    document.querySelectorAll(".show-on-file-selected").forEach(el => {
 | 
					 | 
				
			||||||
                      el.style.cssText = '';
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                document.addEventListener("DOMContentLoaded", () => {
 | 
					 | 
				
			||||||
                  document.querySelectorAll(".show-on-file-selected").forEach(el => {
 | 
					 | 
				
			||||||
                    el.style.cssText = "display:none !important";
 | 
					 | 
				
			||||||
                  })
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
              </script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              <div class="tab-group show-on-file-selected">
 | 
					              <div class="tab-group show-on-file-selected">
 | 
				
			||||||
                  <div th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
 | 
					                  <div th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
 | 
				
			||||||
                  <script>
 | 
					 | 
				
			||||||
                    const imageUpload = document.querySelector('input[name=image-upload]');
 | 
					 | 
				
			||||||
                    imageUpload.addEventListener('change', e => {
 | 
					 | 
				
			||||||
                      if(!e.target.files) {
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                      for (const imageFile of e.target.files) {
 | 
					 | 
				
			||||||
                        var reader = new FileReader();
 | 
					 | 
				
			||||||
                        reader.readAsDataURL(imageFile);
 | 
					 | 
				
			||||||
                        reader.onloadend = function (e) {
 | 
					 | 
				
			||||||
                          DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
 | 
					 | 
				
			||||||
                        };
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                  </script>
 | 
					 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              <!-- draggables box -->
 | 
					              <!-- draggables box -->
 | 
				
			||||||
@ -93,17 +56,6 @@
 | 
				
			|||||||
              <div class="margin-auto-parent">
 | 
					              <div class="margin-auto-parent">
 | 
				
			||||||
                <button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center" th:text="#{downloadPdf}"></button>
 | 
					                <button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center" th:text="#{downloadPdf}"></button>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              <script>
 | 
					 | 
				
			||||||
                document.getElementById("download-pdf").addEventListener('click', async() => {
 | 
					 | 
				
			||||||
                  const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
 | 
					 | 
				
			||||||
                  const modifiedPdfBytes = await modifiedPdf.save();
 | 
					 | 
				
			||||||
                  const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
 | 
					 | 
				
			||||||
                  const link = document.createElement('a');
 | 
					 | 
				
			||||||
                  link.href = URL.createObjectURL(blob);
 | 
					 | 
				
			||||||
                  link.download = originalFileName + '_addedImage.pdf';
 | 
					 | 
				
			||||||
                  link.click();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
              </script>
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -55,247 +55,7 @@
 | 
				
			|||||||
              </form>
 | 
					              </form>
 | 
				
			||||||
              <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					              <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
              <script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
 | 
					              <script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
 | 
				
			||||||
              <script>
 | 
					              <script type="module" th:src="@{'/js/pages/adjust-contrast.js'}"></script>
 | 
				
			||||||
                var canvas = document.getElementById('contrast-pdf-canvas');
 | 
					 | 
				
			||||||
                var context = canvas.getContext('2d');
 | 
					 | 
				
			||||||
                var originalImageData = null;
 | 
					 | 
				
			||||||
                var allPages = [];
 | 
					 | 
				
			||||||
                var pdfDoc = null;
 | 
					 | 
				
			||||||
                var pdf = null; // This is the current PDF document
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                async function renderPDFAndSaveOriginalImageData(file) {
 | 
					 | 
				
			||||||
                  var fileReader = new FileReader();
 | 
					 | 
				
			||||||
                  fileReader.onload = async function() {
 | 
					 | 
				
			||||||
                    var data = new Uint8Array(this.result);
 | 
					 | 
				
			||||||
                    pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
 | 
					 | 
				
			||||||
                    pdf = await pdfjsLib.getDocument({data: data}).promise;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Get the number of pages in the PDF
 | 
					 | 
				
			||||||
                    var numPages = pdf.numPages;
 | 
					 | 
				
			||||||
                    allPages = Array.from({length: numPages}, (_, i) => i + 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Create a new PDF document
 | 
					 | 
				
			||||||
                    pdfDoc = await PDFLib.PDFDocument.create();
 | 
					 | 
				
			||||||
                    // Render the first page in the viewer
 | 
					 | 
				
			||||||
                    await renderPageAndAdjustImageProperties(1);
 | 
					 | 
				
			||||||
                    document.getElementById("sliders-container").style.display = "block";
 | 
					 | 
				
			||||||
                  };
 | 
					 | 
				
			||||||
                  fileReader.readAsArrayBuffer(file);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // This function is now async and returns a promise
 | 
					 | 
				
			||||||
                function renderPageAndAdjustImageProperties(pageNum) {
 | 
					 | 
				
			||||||
                  return new Promise(async function(resolve, reject) {
 | 
					 | 
				
			||||||
                    var page = await pdf.getPage(pageNum);
 | 
					 | 
				
			||||||
                    var scale = 1.5;
 | 
					 | 
				
			||||||
                    var viewport = page.getViewport({ scale: scale });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    canvas.height = viewport.height;
 | 
					 | 
				
			||||||
                    canvas.width = viewport.width;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var renderContext = {
 | 
					 | 
				
			||||||
                      canvasContext: context,
 | 
					 | 
				
			||||||
                      viewport: viewport
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var renderTask = page.render(renderContext);
 | 
					 | 
				
			||||||
                    renderTask.promise.then(function () {
 | 
					 | 
				
			||||||
                      originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
 | 
					 | 
				
			||||||
                      adjustImageProperties();
 | 
					 | 
				
			||||||
                      resolve();
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    canvas.classList.add("fixed-shadow-canvas");
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function adjustImageProperties() {
 | 
					 | 
				
			||||||
                  var contrast = parseFloat(document.getElementById('contrast-slider').value);
 | 
					 | 
				
			||||||
                  var brightness = parseFloat(document.getElementById('brightness-slider').value);
 | 
					 | 
				
			||||||
                  var saturation = parseFloat(document.getElementById('saturation-slider').value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  contrast /= 100; // normalize to range [0, 2]
 | 
					 | 
				
			||||||
                  brightness /= 100; // normalize to range [0, 2]
 | 
					 | 
				
			||||||
                  saturation /= 100; // normalize to range [0, 2]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  if (originalImageData) {
 | 
					 | 
				
			||||||
                    var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
 | 
					 | 
				
			||||||
                    newImageData.data.set(originalImageData.data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    for(var i=0; i<newImageData.data.length; i+=4) {
 | 
					 | 
				
			||||||
                      var r = newImageData.data[i];
 | 
					 | 
				
			||||||
                      var g = newImageData.data[i+1];
 | 
					 | 
				
			||||||
                      var b = newImageData.data[i+2];
 | 
					 | 
				
			||||||
                      // Adjust contrast
 | 
					 | 
				
			||||||
                      r = adjustContrastForPixel(r, contrast);
 | 
					 | 
				
			||||||
                      g = adjustContrastForPixel(g, contrast);
 | 
					 | 
				
			||||||
                      b = adjustContrastForPixel(b, contrast);
 | 
					 | 
				
			||||||
                      // Adjust brightness
 | 
					 | 
				
			||||||
                      r = adjustBrightnessForPixel(r, brightness);
 | 
					 | 
				
			||||||
                      g = adjustBrightnessForPixel(g, brightness);
 | 
					 | 
				
			||||||
                      b = adjustBrightnessForPixel(b, brightness);
 | 
					 | 
				
			||||||
                      // Adjust saturation
 | 
					 | 
				
			||||||
                      var rgb = adjustSaturationForPixel(r, g, b, saturation);
 | 
					 | 
				
			||||||
                      newImageData.data[i] = rgb[0];
 | 
					 | 
				
			||||||
                      newImageData.data[i+1] = rgb[1];
 | 
					 | 
				
			||||||
                      newImageData.data[i+2] = rgb[2];
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    context.putImageData(newImageData, 0, 0);
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function rgbToHsl(r, g, b) {
 | 
					 | 
				
			||||||
                  r /= 255, g /= 255, b /= 255;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  var max = Math.max(r, g, b), min = Math.min(r, g, b);
 | 
					 | 
				
			||||||
                  var h, s, l = (max + min) / 2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  if (max === min) {
 | 
					 | 
				
			||||||
                    h = s = 0; // achromatic
 | 
					 | 
				
			||||||
                  } else {
 | 
					 | 
				
			||||||
                    var d = max - min;
 | 
					 | 
				
			||||||
                    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    switch (max) {
 | 
					 | 
				
			||||||
                      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
 | 
					 | 
				
			||||||
                      case g: h = (b - r) / d + 2; break;
 | 
					 | 
				
			||||||
                      case b: h = (r - g) / d + 4; break;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    h /= 6;
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  return [h, s, l];
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function hslToRgb(h, s, l) {
 | 
					 | 
				
			||||||
                  var r, g, b;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  if (s === 0) {
 | 
					 | 
				
			||||||
                    r = g = b = l; // achromatic
 | 
					 | 
				
			||||||
                  } else {
 | 
					 | 
				
			||||||
                    var hue2rgb = function hue2rgb(p, q, t) {
 | 
					 | 
				
			||||||
                      if (t < 0) t += 1;
 | 
					 | 
				
			||||||
                      if (t > 1) t -= 1;
 | 
					 | 
				
			||||||
                      if (t < 1 / 6) return p + (q - p) * 6 * t;
 | 
					 | 
				
			||||||
                      if (t < 1 / 2) return q;
 | 
					 | 
				
			||||||
                      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
 | 
					 | 
				
			||||||
                      return p;
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
 | 
					 | 
				
			||||||
                    var p = 2 * l - q;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    r = hue2rgb(p, q, h + 1 / 3);
 | 
					 | 
				
			||||||
                    g = hue2rgb(p, q, h);
 | 
					 | 
				
			||||||
                    b = hue2rgb(p, q, h - 1 / 3);
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  return [r * 255, g * 255, b * 255];
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function adjustContrastForPixel(pixel, contrast) {
 | 
					 | 
				
			||||||
                  // Normalize to range [-0.5, 0.5]
 | 
					 | 
				
			||||||
                  var normalized = pixel / 255 - 0.5;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Apply contrast
 | 
					 | 
				
			||||||
                  normalized *= contrast;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Denormalize back to [0, 255]
 | 
					 | 
				
			||||||
                  return (normalized + 0.5) * 255;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function clamp(value, min, max) {
 | 
					 | 
				
			||||||
                  return Math.min(Math.max(value, min), max);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function adjustSaturationForPixel(r, g, b, saturation) {
 | 
					 | 
				
			||||||
                  var hsl = rgbToHsl(r, g, b);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Adjust saturation
 | 
					 | 
				
			||||||
                  hsl[1] = clamp(hsl[1] * saturation, 0, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Convert back to RGB
 | 
					 | 
				
			||||||
                  var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Return adjusted RGB values
 | 
					 | 
				
			||||||
                  return rgb;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                function adjustBrightnessForPixel(pixel, brightness) {
 | 
					 | 
				
			||||||
                  return Math.max(0, Math.min(255, pixel * brightness));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let inputFileName = '';
 | 
					 | 
				
			||||||
                async function downloadPDF() {
 | 
					 | 
				
			||||||
                  for (var i = 0; i < allPages.length; i++) {
 | 
					 | 
				
			||||||
                    await renderPageAndAdjustImageProperties(allPages[i]);
 | 
					 | 
				
			||||||
                    const pngImageBytes = canvas.toDataURL('image/png');
 | 
					 | 
				
			||||||
                    const pngImage = await pdfDoc.embedPng(pngImageBytes);
 | 
					 | 
				
			||||||
                    const pngDims = pngImage.scale(1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Create a blank page matching the dimensions of the image
 | 
					 | 
				
			||||||
                    const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Draw the PNG image
 | 
					 | 
				
			||||||
                    page.drawImage(pngImage, {
 | 
					 | 
				
			||||||
                      x: 0,
 | 
					 | 
				
			||||||
                      y: 0,
 | 
					 | 
				
			||||||
                      width: pngDims.width,
 | 
					 | 
				
			||||||
                      height: pngDims.height
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Serialize the PDFDocument to bytes (a Uint8Array)
 | 
					 | 
				
			||||||
                  const pdfBytes = await pdfDoc.save();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Create a Blob
 | 
					 | 
				
			||||||
                  const blob = new Blob([pdfBytes.buffer], {type: "application/pdf"});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Create download link
 | 
					 | 
				
			||||||
                  const downloadLink = document.createElement('a');
 | 
					 | 
				
			||||||
                  downloadLink.href = URL.createObjectURL(blob);
 | 
					 | 
				
			||||||
                  let newFileName = inputFileName ? inputFileName.replace('.pdf', '') : 'download';
 | 
					 | 
				
			||||||
                  newFileName += '_adjusted_color.pdf';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  downloadLink.download = newFileName;
 | 
					 | 
				
			||||||
                  downloadLink.click();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // After download, reset the viewer and clear stored data
 | 
					 | 
				
			||||||
                  allPages = [];  // Clear the pages
 | 
					 | 
				
			||||||
                  originalImageData = null;  // Clear the image data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  // Go back to page 1 and render it in the viewer
 | 
					 | 
				
			||||||
                  if (pdf !== null) {
 | 
					 | 
				
			||||||
                    renderPageAndAdjustImageProperties(1);
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Event listeners
 | 
					 | 
				
			||||||
                document.getElementById('fileInput-input').addEventListener('change', function(e) {
 | 
					 | 
				
			||||||
                  if (e.target.files.length > 0) {
 | 
					 | 
				
			||||||
                    inputFileName = e.target.files[0].name;
 | 
					 | 
				
			||||||
                    renderPDFAndSaveOriginalImageData(e.target.files[0]);
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                document.getElementById('contrast-slider').addEventListener('input', function() {
 | 
					 | 
				
			||||||
                  document.getElementById('contrast-val').textContent = this.value;
 | 
					 | 
				
			||||||
                  adjustImageProperties();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                document.getElementById('brightness-slider').addEventListener('input', function() {
 | 
					 | 
				
			||||||
                  document.getElementById('brightness-val').textContent = this.value;
 | 
					 | 
				
			||||||
                  adjustImageProperties();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                document.getElementById('saturation-slider').addEventListener('input', function() {
 | 
					 | 
				
			||||||
                  document.getElementById('saturation-val').textContent = this.value;
 | 
					 | 
				
			||||||
                  adjustImageProperties();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                document.getElementById('download-button').addEventListener('click', function() {
 | 
					 | 
				
			||||||
                  downloadPDF();
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
              </script>
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -85,140 +85,7 @@
 | 
				
			|||||||
                <br>
 | 
					                <br>
 | 
				
			||||||
                <button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
 | 
					                <button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
 | 
				
			||||||
                <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					                <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
                <script>
 | 
					                <script type="module" th:src="@{'/js/pages/change-metadata.js'}">
 | 
				
			||||||
                  const deleteAllCheckbox = document.querySelector("#deleteAll");
 | 
					 | 
				
			||||||
                  let inputs = document.querySelectorAll("input");
 | 
					 | 
				
			||||||
                  const customMetadataDiv = document.getElementById('customMetadata');
 | 
					 | 
				
			||||||
                  const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  deleteAllCheckbox.addEventListener("change", function(event) {
 | 
					 | 
				
			||||||
                    inputs.forEach(input => {
 | 
					 | 
				
			||||||
                      // If it's the deleteAllCheckbox or any file input, skip
 | 
					 | 
				
			||||||
                      if (input === deleteAllCheckbox || input.type === "file") {
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                      // Disable or enable based on the checkbox state
 | 
					 | 
				
			||||||
                      input.disabled = deleteAllCheckbox.checked;
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  const customModeCheckbox = document.getElementById('customModeCheckbox');
 | 
					 | 
				
			||||||
                  const addMetadataBtn = document.getElementById("addMetadataBtn");
 | 
					 | 
				
			||||||
                  const customMetadataFormContainer = document.getElementById("customMetadataEntries");
 | 
					 | 
				
			||||||
                  var count = 1;
 | 
					 | 
				
			||||||
                  const fileInput = document.querySelector("#fileInput-input");
 | 
					 | 
				
			||||||
                  const authorInput = document.querySelector("#author");
 | 
					 | 
				
			||||||
                  const creationDateInput = document.querySelector("#creationDate");
 | 
					 | 
				
			||||||
                  const creatorInput = document.querySelector("#creator");
 | 
					 | 
				
			||||||
                  const keywordsInput = document.querySelector("#keywords");
 | 
					 | 
				
			||||||
                  const modificationDateInput = document.querySelector("#modificationDate");
 | 
					 | 
				
			||||||
                  const producerInput = document.querySelector("#producer");
 | 
					 | 
				
			||||||
                  const subjectInput = document.querySelector("#subject");
 | 
					 | 
				
			||||||
                  const titleInput = document.querySelector("#title");
 | 
					 | 
				
			||||||
                  const trappedInput = document.querySelector("#trapped");
 | 
					 | 
				
			||||||
                  var lastPDFFileMeta = null;
 | 
					 | 
				
			||||||
                  fileInput.addEventListener("change", async function() {
 | 
					 | 
				
			||||||
                    while (otherMetadataEntriesDiv.firstChild) {
 | 
					 | 
				
			||||||
                      otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    while (customMetadataFormContainer.firstChild) {
 | 
					 | 
				
			||||||
                      customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const file = this.files[0];
 | 
					 | 
				
			||||||
                    var url = URL.createObjectURL(file)
 | 
					 | 
				
			||||||
                    pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
 | 
					 | 
				
			||||||
                    const pdf = await pdfjsLib.getDocument(url).promise;
 | 
					 | 
				
			||||||
                    const pdfMetadata = await pdf.getMetadata();
 | 
					 | 
				
			||||||
                    lastPDFFile = pdfMetadata?.info
 | 
					 | 
				
			||||||
                    console.log(pdfMetadata);
 | 
					 | 
				
			||||||
                    if(!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) {
 | 
					 | 
				
			||||||
                      customModeCheckbox.disabled = true;
 | 
					 | 
				
			||||||
                      customModeCheckbox.checked = false;
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                      customModeCheckbox.disabled = false;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    authorInput.value = pdfMetadata?.info?.Author;
 | 
					 | 
				
			||||||
                    creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate);
 | 
					 | 
				
			||||||
                    creatorInput.value = pdfMetadata?.info?.Creator;
 | 
					 | 
				
			||||||
                    keywordsInput.value = pdfMetadata?.info?.Keywords;
 | 
					 | 
				
			||||||
                    modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate);
 | 
					 | 
				
			||||||
                    producerInput.value = pdfMetadata?.info?.Producer;
 | 
					 | 
				
			||||||
                    subjectInput.value = pdfMetadata?.info?.Subject;
 | 
					 | 
				
			||||||
                    titleInput.value = pdfMetadata?.info?.Title;
 | 
					 | 
				
			||||||
                    console.log(pdfMetadata?.info);
 | 
					 | 
				
			||||||
                    const trappedValue = pdfMetadata?.info?.Trapped;
 | 
					 | 
				
			||||||
                    // Get all options in the select element
 | 
					 | 
				
			||||||
                    const options = trappedInput.options;
 | 
					 | 
				
			||||||
                    // Loop through all options to find the one with a matching value
 | 
					 | 
				
			||||||
                    for (let i = 0; i < options.length; i++) {
 | 
					 | 
				
			||||||
                      if (options[i].value === trappedValue) {
 | 
					 | 
				
			||||||
                        options[i].selected = true;
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    addExtra();
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  addMetadataBtn.addEventListener("click", () => {
 | 
					 | 
				
			||||||
                    const keyInput = document.createElement("input");
 | 
					 | 
				
			||||||
                    keyInput.type = "text";
 | 
					 | 
				
			||||||
                    keyInput.placeholder = 'Key';
 | 
					 | 
				
			||||||
                    keyInput.className = "form-control";
 | 
					 | 
				
			||||||
                    keyInput.name = `allRequestParams[customKey${count}]`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const valueInput = document.createElement("input");
 | 
					 | 
				
			||||||
                    valueInput.type = "text";
 | 
					 | 
				
			||||||
                    valueInput.placeholder = 'Value';
 | 
					 | 
				
			||||||
                    valueInput.className = "form-control";
 | 
					 | 
				
			||||||
                    valueInput.name = `allRequestParams[customValue${count}]`;
 | 
					 | 
				
			||||||
                    count = count + 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const formGroup = document.createElement("div");
 | 
					 | 
				
			||||||
                    formGroup.className = "mb-3";
 | 
					 | 
				
			||||||
                    formGroup.appendChild(keyInput);
 | 
					 | 
				
			||||||
                    formGroup.appendChild(valueInput);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    customMetadataFormContainer.appendChild(formGroup);
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
                  function convertDateFormat(dateTimeString) {
 | 
					 | 
				
			||||||
                    if (!dateTimeString || dateTimeString.length < 17) {
 | 
					 | 
				
			||||||
                      return dateTimeString;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const year = dateTimeString.substring(2, 6);
 | 
					 | 
				
			||||||
                    const month = dateTimeString.substring(6, 8);
 | 
					 | 
				
			||||||
                    const day = dateTimeString.substring(8, 10);
 | 
					 | 
				
			||||||
                    const hour = dateTimeString.substring(10, 12);
 | 
					 | 
				
			||||||
                    const minute = dateTimeString.substring(12, 14);
 | 
					 | 
				
			||||||
                    const second = dateTimeString.substring(14, 16);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second;
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  function addExtra() {
 | 
					 | 
				
			||||||
                    const event = document.getElementById("customModeCheckbox");
 | 
					 | 
				
			||||||
                    if (event.checked && lastPDFFile.Custom != null) {
 | 
					 | 
				
			||||||
                      customMetadataDiv.style.display = 'block';
 | 
					 | 
				
			||||||
                      for (const [key, value] of Object.entries(lastPDFFile.Custom)) {
 | 
					 | 
				
			||||||
                        if (key === 'Author' || key === 'CreationDate' || key === 'Creator' || key === 'Keywords' || key === 'ModDate' || key === 'Producer' || key === 'Subject' || key === 'Title' || key === 'Trapped') {
 | 
					 | 
				
			||||||
                          continue;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        const entryDiv = document.createElement('div');
 | 
					 | 
				
			||||||
                        entryDiv.className = 'mb-3';
 | 
					 | 
				
			||||||
                        entryDiv.innerHTML = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
 | 
					 | 
				
			||||||
                        otherMetadataEntriesDiv.appendChild(entryDiv);
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                      customMetadataDiv.style.display = 'none';
 | 
					 | 
				
			||||||
                      while (otherMetadataEntriesDiv.firstChild) {
 | 
					 | 
				
			||||||
                        otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
 | 
					 | 
				
			||||||
                      }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  customModeCheckbox.addEventListener('change', (event) => {
 | 
					 | 
				
			||||||
                    addExtra();
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
                </script>
 | 
					                </script>
 | 
				
			||||||
              </form>
 | 
					              </form>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
 | 
					<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
 | 
				
			||||||
  <head>
 | 
					  <head>
 | 
				
			||||||
  <th:block th:insert="~{fragments/common :: head(title=#{extractImages.title}, header=#{extractImages.header})}"></th:block>
 | 
					  <th:block th:insert="~{fragments/common :: head(title=#{extractImages.title}, header=#{extractImages.header})}"></th:block>
 | 
				
			||||||
 | 
					  <script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <th:block th:insert="~{fragments/common :: head(title=#{replace-color.title}, header=#{replace-color.header})}"></th:block>
 | 
					    <th:block th:insert="~{fragments/common :: head(title=#{replace-color.title}, header=#{replace-color.header})}"></th:block>
 | 
				
			||||||
 | 
					    <script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
 | 
				
			|||||||
@ -162,10 +162,19 @@
 | 
				
			|||||||
        insertPageBreak:'[[#{multiTool.insertPageBreak}]]',
 | 
					        insertPageBreak:'[[#{multiTool.insertPageBreak}]]',
 | 
				
			||||||
        dragDropMessage:'[[#{multiTool.dragDropMessage}]]',
 | 
					        dragDropMessage:'[[#{multiTool.dragDropMessage}]]',
 | 
				
			||||||
        undo: '[[#{multiTool.undo}]]',
 | 
					        undo: '[[#{multiTool.undo}]]',
 | 
				
			||||||
        redo: '[[#{multiTool.redo}]]'
 | 
					        redo: '[[#{multiTool.redo}]]',
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.decrypt = {
 | 
				
			||||||
 | 
					                    passwordPrompt: '[[#{decrypt.passwordPrompt}]]',
 | 
				
			||||||
 | 
					                    cancelled: '[[#{decrypt.cancelled}]]',
 | 
				
			||||||
 | 
					                    noPassword: '[[#{decrypt.noPassword}]]',
 | 
				
			||||||
 | 
					                    invalidPassword: '[[#{decrypt.invalidPassword}]]',
 | 
				
			||||||
 | 
					                    invalidPasswordHeader: '[[#{decrypt.invalidPasswordHeader}]]',
 | 
				
			||||||
 | 
					                    unexpectedError: '[[#{decrypt.unexpectedError}]]',
 | 
				
			||||||
 | 
					                    serverError: '[[#{decrypt.serverError}]]',
 | 
				
			||||||
 | 
					                    success: '[[#{decrypt.success}]]',
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const csvInput = document.getElementById("csv-input");
 | 
					    const csvInput = document.getElementById("csv-input");
 | 
				
			||||||
    csvInput.addEventListener("keydown", function (event) {
 | 
					    csvInput.addEventListener("keydown", function (event) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
 | 
					<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
 | 
				
			||||||
  <head>
 | 
					  <head>
 | 
				
			||||||
  <th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
 | 
					  <th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
 | 
				
			||||||
 | 
					  <script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@
 | 
				
			|||||||
      th:href="@{'/css/pipeline.css'}"
 | 
					      th:href="@{'/css/pipeline.css'}"
 | 
				
			||||||
      th:if="${currentPage == 'pipeline'}"
 | 
					      th:if="${currentPage == 'pipeline'}"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
					    <script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
 | 
				
			||||||
    <script th:inline="javascript">
 | 
					    <script th:inline="javascript">
 | 
				
			||||||
      const saveSettings = /*[[#{pipelineOptions.saveSettings}]]*/ "";
 | 
					      const saveSettings = /*[[#{pipelineOptions.saveSettings}]]*/ "";
 | 
				
			||||||
      const deletePipelineText = /*[[#{pipeline.deletePrompt}]]*/ "Are you sure you want to delete pipeline";
 | 
					      const deletePipelineText = /*[[#{pipeline.deletePrompt}]]*/ "Are you sure you want to delete pipeline";
 | 
				
			||||||
 | 
				
			|||||||
@ -19,9 +19,10 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
  </th:block>
 | 
					  </th:block>
 | 
				
			||||||
 | 
					 | 
				
			||||||
  <script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
 | 
					  <script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
 | 
				
			||||||
  <script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
 | 
					  <script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
 | 
				
			||||||
 | 
					  <script type="module" th:src="@{'/js/pages/sign.js'}"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
@ -42,75 +43,6 @@
 | 
				
			|||||||
              th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
 | 
					              th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
					            <script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
 | 
				
			||||||
            <script>
 | 
					 | 
				
			||||||
              let currentPreviewSrc = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function toggleSignatureView() {
 | 
					 | 
				
			||||||
                const gridView = document.getElementById('gridView');
 | 
					 | 
				
			||||||
                const listView = document.getElementById('listView');
 | 
					 | 
				
			||||||
                const gridText = document.querySelector('.grid-view-text');
 | 
					 | 
				
			||||||
                const listText = document.querySelector('.list-view-text');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (gridView.style.display !== 'none') {
 | 
					 | 
				
			||||||
                  gridView.style.display = 'none';
 | 
					 | 
				
			||||||
                  listView.style.display = 'block';
 | 
					 | 
				
			||||||
                  gridText.style.display = 'none';
 | 
					 | 
				
			||||||
                  listText.style.display = 'inline';
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                  gridView.style.display = 'block';
 | 
					 | 
				
			||||||
                  listView.style.display = 'none';
 | 
					 | 
				
			||||||
                  gridText.style.display = 'inline';
 | 
					 | 
				
			||||||
                  listText.style.display = 'none';
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function previewSignature(element) {
 | 
					 | 
				
			||||||
                const src = element.dataset.src;
 | 
					 | 
				
			||||||
                currentPreviewSrc = src;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Extract filename from the data source path
 | 
					 | 
				
			||||||
                const filename = element.querySelector('.signature-list-name').textContent;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Update preview modal
 | 
					 | 
				
			||||||
                const previewImage = document.getElementById('previewImage');
 | 
					 | 
				
			||||||
                const previewFileName = document.getElementById('previewFileName');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                previewImage.src = src;
 | 
					 | 
				
			||||||
                previewFileName.textContent = filename;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
 | 
					 | 
				
			||||||
                modal.show();
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function addSignatureFromPreview() {
 | 
					 | 
				
			||||||
                if (currentPreviewSrc) {
 | 
					 | 
				
			||||||
                  DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
 | 
					 | 
				
			||||||
                  bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              let originalFileName = '';
 | 
					 | 
				
			||||||
              document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
 | 
					 | 
				
			||||||
                const file = event.target.files[0];
 | 
					 | 
				
			||||||
                if (file) {
 | 
					 | 
				
			||||||
                  originalFileName = file.name.replace(/\.[^/.]+$/, "");
 | 
					 | 
				
			||||||
                  const pdfData = await file.arrayBuffer();
 | 
					 | 
				
			||||||
                  pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
 | 
					 | 
				
			||||||
                  const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
 | 
					 | 
				
			||||||
                  await DraggableUtils.renderPage(pdfDoc, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  document.querySelectorAll(".show-on-file-selected").forEach(el => {
 | 
					 | 
				
			||||||
                    el.style.cssText = '';
 | 
					 | 
				
			||||||
                  });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
              document.addEventListener("DOMContentLoaded", () => {
 | 
					 | 
				
			||||||
                document.querySelectorAll(".show-on-file-selected").forEach(el => {
 | 
					 | 
				
			||||||
                  el.style.cssText = "display:none !important";
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            </script>
 | 
					 | 
				
			||||||
            <div class="tab-group show-on-file-selected">
 | 
					            <div class="tab-group show-on-file-selected">
 | 
				
			||||||
              <div class="tab-container" th:title="#{sign.upload}">
 | 
					              <div class="tab-container" th:title="#{sign.upload}">
 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
@ -237,123 +169,6 @@
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
            <script>
 | 
					 | 
				
			||||||
              const imageUpload = document.querySelector('input[name=image-upload]');
 | 
					 | 
				
			||||||
              imageUpload.addEventListener('change', e => {
 | 
					 | 
				
			||||||
                if (!e.target.files) return;
 | 
					 | 
				
			||||||
                for (const imageFile of e.target.files) {
 | 
					 | 
				
			||||||
                  var reader = new FileReader();
 | 
					 | 
				
			||||||
                  reader.readAsDataURL(imageFile);
 | 
					 | 
				
			||||||
                  reader.onloadend = function (e) {
 | 
					 | 
				
			||||||
                    DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
 | 
					 | 
				
			||||||
                  };
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            </script>
 | 
					 | 
				
			||||||
            <script>
 | 
					 | 
				
			||||||
              const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
 | 
					 | 
				
			||||||
              const signaturePad = new SignaturePad(signaturePadCanvas, {
 | 
					 | 
				
			||||||
                minWidth: 1,
 | 
					 | 
				
			||||||
                maxWidth: 2,
 | 
					 | 
				
			||||||
                penColor: 'black',
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function addDraggableFromPad() {
 | 
					 | 
				
			||||||
                if (signaturePad.isEmpty()) return;
 | 
					 | 
				
			||||||
                const startTime = Date.now();
 | 
					 | 
				
			||||||
                const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
 | 
					 | 
				
			||||||
                console.log(Date.now() - startTime);
 | 
					 | 
				
			||||||
                DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function getCroppedCanvasDataUrl(canvas) {
 | 
					 | 
				
			||||||
                let originalCtx = canvas.getContext('2d');
 | 
					 | 
				
			||||||
                let originalWidth = canvas.width;
 | 
					 | 
				
			||||||
                let originalHeight = canvas.height;
 | 
					 | 
				
			||||||
                let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for (y = 0; y < originalHeight; y++) {
 | 
					 | 
				
			||||||
                  for (x = 0; x < originalWidth; x++) {
 | 
					 | 
				
			||||||
                    currentPixelColorValueIndex = (y * originalWidth + x) * 4;
 | 
					 | 
				
			||||||
                    let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
 | 
					 | 
				
			||||||
                    if (currentPixelAlphaValue > 0) {
 | 
					 | 
				
			||||||
                      if (minX > x) minX = x;
 | 
					 | 
				
			||||||
                      if (maxX < x) maxX = x;
 | 
					 | 
				
			||||||
                      if (minY > y) minY = y;
 | 
					 | 
				
			||||||
                      if (maxY < y) maxY = y;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let croppedWidth = maxX - minX;
 | 
					 | 
				
			||||||
                let croppedHeight = maxY - minY;
 | 
					 | 
				
			||||||
                if (croppedWidth < 0 || croppedHeight < 0) return null;
 | 
					 | 
				
			||||||
                let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let croppedCanvas = document.createElement('canvas'),
 | 
					 | 
				
			||||||
                  croppedCtx = croppedCanvas.getContext('2d');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                croppedCanvas.width = croppedWidth;
 | 
					 | 
				
			||||||
                croppedCanvas.height = croppedHeight;
 | 
					 | 
				
			||||||
                croppedCtx.putImageData(cuttedImageData, 0, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return croppedCanvas.toDataURL();
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              function resizeCanvas() {
 | 
					 | 
				
			||||||
                var ratio = Math.max(window.devicePixelRatio || 1, 1);
 | 
					 | 
				
			||||||
                var additionalFactor = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
 | 
					 | 
				
			||||||
                signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
 | 
					 | 
				
			||||||
                signaturePadCanvas.getContext("2d").scale(ratio * additionalFactor, ratio * additionalFactor);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                signaturePad.clear();
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              new IntersectionObserver((entries, observer) => {
 | 
					 | 
				
			||||||
                if (entries.some(entry => entry.intersectionRatio > 0)) {
 | 
					 | 
				
			||||||
                  resizeCanvas();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }).observe(signaturePadCanvas);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
 | 
					 | 
				
			||||||
            </script>
 | 
					 | 
				
			||||||
            <script>
 | 
					 | 
				
			||||||
              function addDraggableFromText() {
 | 
					 | 
				
			||||||
                const sigText = document.getElementById('sigText').value;
 | 
					 | 
				
			||||||
                const font = document.querySelector('select[name=font]').value;
 | 
					 | 
				
			||||||
                const fontSize = 100;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const canvas = document.createElement('canvas');
 | 
					 | 
				
			||||||
                const ctx = canvas.getContext('2d');
 | 
					 | 
				
			||||||
                ctx.font = `${fontSize}px ${font}`;
 | 
					 | 
				
			||||||
                const textWidth = ctx.measureText(sigText).width;
 | 
					 | 
				
			||||||
                const textHeight = fontSize;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let paragraphs = sigText.split(/\r?\n/);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                canvas.width = textWidth;
 | 
					 | 
				
			||||||
                canvas.height = paragraphs.length * textHeight * 1.35; // for tails
 | 
					 | 
				
			||||||
                ctx.font = `${fontSize}px ${font}`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ctx.textBaseline = 'top';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let y = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                paragraphs.forEach(paragraph => {
 | 
					 | 
				
			||||||
                  ctx.fillText(paragraph, 0, y);
 | 
					 | 
				
			||||||
                  y += fontSize;
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const dataURL = canvas.toDataURL();
 | 
					 | 
				
			||||||
                DraggableUtils.createDraggableCanvasFromUrl(dataURL);
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            </script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- draggables box -->
 | 
					            <!-- draggables box -->
 | 
				
			||||||
            <div id="box-drag-container" class="show-on-file-selected">
 | 
					            <div id="box-drag-container" class="show-on-file-selected">
 | 
				
			||||||
              <canvas id="pdf-canvas"></canvas>
 | 
					              <canvas id="pdf-canvas"></canvas>
 | 
				
			||||||
@ -410,35 +225,11 @@
 | 
				
			|||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
            <!-- download button -->
 | 
					            <!-- download button -->
 | 
				
			||||||
            <div class="margin-auto-parent">
 | 
					            <div class="margin-auto-parent">
 | 
				
			||||||
              <button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center"
 | 
					              <button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center"
 | 
				
			||||||
                th:text="#{downloadPdf}"></button>
 | 
					                th:text="#{downloadPdf}"></button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
            <script>
 | 
					 | 
				
			||||||
              async function goToFirstOrLastPage(page) {
 | 
					 | 
				
			||||||
                if (page) {
 | 
					 | 
				
			||||||
                  const lastPage = DraggableUtils.pdfDoc.numPages
 | 
					 | 
				
			||||||
                  await DraggableUtils.goToPage(lastPage - 1)
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                  await DraggableUtils.goToPage(0)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            </script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <script>
 | 
					 | 
				
			||||||
              document.getElementById("download-pdf").addEventListener('click', async () => {
 | 
					 | 
				
			||||||
                const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
 | 
					 | 
				
			||||||
                const modifiedPdfBytes = await modifiedPdf.save();
 | 
					 | 
				
			||||||
                const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
 | 
					 | 
				
			||||||
                const link = document.createElement('a');
 | 
					 | 
				
			||||||
                link.href = URL.createObjectURL(blob);
 | 
					 | 
				
			||||||
                link.download = originalFileName + '_signed.pdf';
 | 
					 | 
				
			||||||
                link.click();
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
            </script>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@ -447,6 +238,7 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <!-- Link the draggable.js file -->
 | 
					  <!-- Link the draggable.js file -->
 | 
				
			||||||
  <script th:src="@{'/js/draggable.js'}"></script>
 | 
					  <script th:src="@{'/js/draggable.js'}"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user