Compare commits
59 Commits
v0.29.0
...
saml2-sso-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92c0ddf63b | ||
|
|
7297e9e62d | ||
|
|
f8944fd2a9 | ||
|
|
3fd44fe7af | ||
|
|
4c9c9b5cbe | ||
|
|
e660237e28 | ||
|
|
83e93688ee | ||
|
|
dedfabd630 | ||
|
|
3b5b7772a9 | ||
|
|
2a6b4ca87f | ||
|
|
e325943f16 | ||
|
|
48aae48f0e | ||
|
|
494bc2c09f | ||
|
|
45e4c15d2d | ||
|
|
22a58ad0c3 | ||
|
|
c59d3ff3e0 | ||
|
|
bb37ba1f30 | ||
|
|
5832147b30 | ||
|
|
092b4cc5cb | ||
|
|
86bb37aa7a | ||
|
|
20dc2f60cd | ||
|
|
da988e8127 | ||
|
|
b8115531e2 | ||
|
|
9b96367496 | ||
|
|
3ded6de576 | ||
|
|
1c6e5df77d | ||
|
|
df901db1f8 | ||
|
|
b46ccdde44 | ||
|
|
fde1f626eb | ||
|
|
f47ed3b42e | ||
|
|
a81856d83b | ||
|
|
4fa1b4adb0 | ||
|
|
03158b05e4 | ||
|
|
28c55ca80c | ||
|
|
d6e9e8b20b | ||
|
|
0f43062cc1 | ||
|
|
f72e5c8bca | ||
|
|
936f36f171 | ||
|
|
dab230e3f8 | ||
|
|
0d3ac8bebe | ||
|
|
6ca14edaf1 | ||
|
|
f9677b1fe8 | ||
|
|
04a6ebf515 | ||
|
|
262e2ed47a | ||
|
|
4436759e12 | ||
|
|
6e1a5d2ea0 | ||
|
|
35490f6ff7 | ||
|
|
9f63b0b115 | ||
|
|
c2a8771c66 | ||
|
|
f87801323c | ||
|
|
bed6227bbe | ||
|
|
24f99fce31 | ||
|
|
ba2311b3e5 | ||
|
|
688e01d70d | ||
|
|
0014560a96 | ||
|
|
cbf1c3a59b | ||
|
|
b13b925bf0 | ||
|
|
87925ac618 | ||
|
|
c6c33d611a |
@@ -15,6 +15,7 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
|||||||
# Copy necessary files
|
# Copy necessary files
|
||||||
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||||
|
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||||
COPY pipeline /pipeline
|
COPY pipeline /pipeline
|
||||||
COPY build/libs/*.jar app.jar
|
COPY build/libs/*.jar app.jar
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||||||
su-exec \
|
su-exec \
|
||||||
openjdk21-jre && \
|
openjdk21-jre && \
|
||||||
# User permissions
|
# User permissions
|
||||||
mkdir /configs /logs /customFiles && \
|
mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \
|
||||||
chmod +x /scripts/*.sh && \
|
chmod +x /scripts/*.sh && \
|
||||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||||
|
|||||||
@@ -257,9 +257,11 @@ To override the default configuration, you can add the following to `/.git/Stirl
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0 # Not working - use instead address
|
||||||
|
address: 0.0.0.0
|
||||||
port: 3000
|
port: 3000
|
||||||
```
|
```
|
||||||
|
'-Djava.net.preferIPv4Stack=true' --> To force ipv4 only in the java starting command
|
||||||
|
|
||||||
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
|
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
|
||||||
|
|
||||||
|
|||||||
68
README.md
68
README.md
@@ -172,42 +172,42 @@ Stirling PDF currently supports 38!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| ------------------------------------------- | -------------------------------------- |
|
| ------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| 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) |  |
|
||||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
| Indonesia (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) |  |
|
||||||
| 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.)
|
||||||
|
|
||||||
|
|||||||
21
build.gradle
21
build.gradle
@@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "java"
|
id "java"
|
||||||
id "org.springframework.boot" version "3.3.3"
|
id "org.springframework.boot" version "3.3.4"
|
||||||
id "io.spring.dependency-management" version "1.1.6"
|
id "io.spring.dependency-management" version "1.1.6"
|
||||||
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
id "org.springdoc.openapi-gradle-plugin" version "1.8.0"
|
||||||
id "io.swagger.swaggerhub" version "1.3.2"
|
id "io.swagger.swaggerhub" version "1.3.2"
|
||||||
@@ -13,7 +13,7 @@ plugins {
|
|||||||
import com.github.jk1.license.render.*
|
import com.github.jk1.license.render.*
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
springBootVersion = "3.3.3"
|
springBootVersion = "3.3.4"
|
||||||
pdfboxVersion = "3.0.3"
|
pdfboxVersion = "3.0.3"
|
||||||
logbackVersion = "1.5.7"
|
logbackVersion = "1.5.7"
|
||||||
imageioVersion = "3.11.0"
|
imageioVersion = "3.11.0"
|
||||||
@@ -32,6 +32,12 @@ java {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
|
maven {
|
||||||
|
url "https://build.shibboleth.net/nexus/content/repositories/releases/"
|
||||||
|
}
|
||||||
|
maven {
|
||||||
|
url "https://build.shibboleth.net/maven/releases/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
@@ -115,7 +121,7 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//security updates
|
//security updates
|
||||||
implementation "org.springframework:spring-webmvc:6.1.9"
|
implementation "org.springframework:spring-webmvc:6.1.13"
|
||||||
|
|
||||||
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
||||||
|
|
||||||
@@ -127,6 +133,7 @@ dependencies {
|
|||||||
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-jetty:$springBootVersion"
|
||||||
|
|
||||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||||
|
implementation 'com.posthog.java:posthog:1.1.1'
|
||||||
|
|
||||||
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"
|
||||||
@@ -134,6 +141,8 @@ dependencies {
|
|||||||
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"
|
||||||
|
|
||||||
|
implementation 'org.springframework.security:spring-security-saml2-service-provider:6.3.3'
|
||||||
|
implementation 'com.unboundid.product.scim2:scim2-sdk-client:2.3.5'
|
||||||
//2.2.x requires rebuild of DB file.. need migration path
|
//2.2.x requires rebuild of DB file.. need migration path
|
||||||
runtimeOnly "com.h2database:h2:2.1.214"
|
runtimeOnly "com.h2database:h2:2.1.214"
|
||||||
// implementation "com.h2database:h2:2.2.224"
|
// implementation "com.h2database:h2:2.2.224"
|
||||||
@@ -162,7 +171,7 @@ dependencies {
|
|||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
runtimeOnly "com.twelvemonkeys.imageio:imageio-webp:$imageioVersion"
|
||||||
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
|
// runtimeOnly "com.twelvemonkeys.imageio:imageio-xwd:$imageioVersion"
|
||||||
|
|
||||||
implementation "commons-io:commons-io:2.16.1"
|
implementation "commons-io:commons-io:2.17.0"
|
||||||
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0"
|
||||||
//general PDF
|
//general PDF
|
||||||
|
|
||||||
@@ -187,8 +196,8 @@ dependencies {
|
|||||||
implementation "io.micrometer:micrometer-core:1.13.4"
|
implementation "io.micrometer:micrometer-core:1.13.4"
|
||||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
implementation "org.commonmark:commonmark:0.22.0"
|
implementation "org.commonmark:commonmark:0.23.0"
|
||||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.22.0"
|
implementation "org.commonmark:commonmark-ext-gfm-tables:0.23.0"
|
||||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||||
implementation "com.fathzer:javaluator:3.0.5"
|
implementation "com.fathzer:javaluator:3.0.5"
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ Feature: API Validation
|
|||||||
| odt | .odt |
|
| odt | .odt |
|
||||||
| doc | .doc |
|
| doc | .doc |
|
||||||
|
|
||||||
@ocr
|
@ocr @pdfa1
|
||||||
Scenario: PDFA
|
Scenario: PDFA
|
||||||
Given I use an example file at "exampleFiles/pdfa2.pdf" as parameter "fileInput"
|
Given I use an example file at "exampleFiles/pdfa2.pdf" as parameter "fileInput"
|
||||||
And the request data includes
|
And the request data includes
|
||||||
@@ -134,7 +134,7 @@ Feature: API Validation
|
|||||||
And the response file should have extension ".pdf"
|
And the response file should have extension ".pdf"
|
||||||
And the response file should have size greater than 100
|
And the response file should have size greater than 100
|
||||||
|
|
||||||
@ocr
|
@ocr @pdfa2
|
||||||
Scenario: PDFA1
|
Scenario: PDFA1
|
||||||
Given I use an example file at "exampleFiles/pdfa1.pdf" as parameter "fileInput"
|
Given I use an example file at "exampleFiles/pdfa1.pdf" as parameter "fileInput"
|
||||||
And the request data includes
|
And the request data includes
|
||||||
|
|||||||
10
scripts/replace_translation_line.sh
Normal file
10
scripts/replace_translation_line.sh
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
translation_key="pdfToPDFA.credit"
|
||||||
|
old_value="OCRmyPDF"
|
||||||
|
new_value="ghostscript"
|
||||||
|
|
||||||
|
for file in ../src/main/resources/messages_*.properties; do
|
||||||
|
sed -i "/^$translation_key=/s/$old_value/$new_value/" "$file"
|
||||||
|
echo "Updated $file"
|
||||||
|
done
|
||||||
@@ -17,9 +17,10 @@ public class EEAppConfig {
|
|||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Bean(name = "RunningEE")
|
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
||||||
|
|
||||||
|
@Bean(name = "runningEE")
|
||||||
public boolean runningEnterpriseEdition() {
|
public boolean runningEnterpriseEdition() {
|
||||||
// TODO: Implement EE detection
|
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package stirling.software.SPDF.EE;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.posthog.java.shaded.org.json.JSONObject;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class KeygenLicenseVerifier {
|
||||||
|
private static final String ACCOUNT_ID = "e5430f69-e834-4ae4-befd-b602aae5f372";
|
||||||
|
private static final String PRODUCT_ID = "f9bb2423-62c9-4d39-8def-4fdc5aca751e";
|
||||||
|
private static final String BASE_URL = "https://api.keygen.sh/v1/accounts";
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
// 23:26:20.344 [scheduling-1] INFO s.s.SPDF.EE.KeygenLicenseVerifier -
|
||||||
|
// validateLicenseResponse body:
|
||||||
|
// {"data":{"id":"808ed3c9-584b-46dd-8a80-c9217ef70915","type":"licenses","attributes":{"name":"userCounTest","key":"A7EW-KUPF-PRML-RRVL-HLMP-7THR-F7KE-XF4C","expiry":"2024-10-31T21:39:49.271Z","status":"ACTIVE","uses":0,"suspended":false,"scheme":null,"encrypted":false,"strict":true,"floating":true,"protected":true,"version":null,"maxMachines":1,"maxProcesses":null,"maxUsers":null,"maxCores":null,"maxUses":null,"requireHeartbeat":false,"requireCheckIn":false,"lastValidated":"2024-10-01T22:26:18.121Z","lastCheckIn":null,"nextCheckIn":null,"lastCheckOut":null,"metadata":{"users":10},"created":"2024-10-01T21:39:49.268Z","updated":"2024-10-01T21:39:49.268Z"},"relationships":{"account":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372"},"data":{"type":"accounts","id":"e5430f69-e834-4ae4-befd-b602aae5f372"}},"environment":{"links":{"related":null},"data":null},"product":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/product"},"data":{"type":"products","id":"f9bb2423-62c9-4d39-8def-4fdc5aca751e"}},"policy":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/policy"},"data":{"type":"policies","id":"04caef06-9ac2-4084-bf3c-bca4a0d29143"}},"group":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/group"},"data":null},"owner":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/owner"},"data":null},"users":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/users"},"meta":{"count":0}},"machines":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/machines"},"meta":{"cores":0,"count":0}},"tokens":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/tokens"}},"entitlements":{"links":{"related":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915/entitlements"}}},"links":{"self":"/v1/accounts/e5430f69-e834-4ae4-befd-b602aae5f372/licenses/808ed3c9-584b-46dd-8a80-c9217ef70915"}},"meta":{"ts":"2024-10-01T22:26:18.124Z","valid":false,"detail":"fingerprint is not activated (has no associated machines)","code":"NO_MACHINES","scope":{"fingerprint":"example-fingerprint"}}}
|
||||||
|
|
||||||
|
public boolean verifyLicense(String licenseKey) {
|
||||||
|
try {
|
||||||
|
log.info("Checking license key");
|
||||||
|
String machineFingerprint = generateMachineFingerprint();
|
||||||
|
|
||||||
|
// First, try to validate the license
|
||||||
|
JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint);
|
||||||
|
if (validationResponse != null) {
|
||||||
|
boolean isValid = validationResponse.path("meta").path("valid").asBoolean();
|
||||||
|
String licenseId = validationResponse.path("data").path("id").asText();
|
||||||
|
if (!isValid) {
|
||||||
|
String code = validationResponse.path("meta").path("code").asText();
|
||||||
|
log.debug(code);
|
||||||
|
if ("NO_MACHINE".equals(code)
|
||||||
|
|| "NO_MACHINES".equals(code)
|
||||||
|
|| "FINGERPRINT_SCOPE_MISMATCH".equals(code)) {
|
||||||
|
log.info(
|
||||||
|
"License not activated for this machine. Attempting to activate...");
|
||||||
|
boolean activated =
|
||||||
|
activateMachine(licenseKey, licenseId, machineFingerprint);
|
||||||
|
if (activated) {
|
||||||
|
// Revalidate after activation
|
||||||
|
validationResponse = validateLicense(licenseKey, machineFingerprint);
|
||||||
|
isValid =
|
||||||
|
validationResponse != null
|
||||||
|
&& validationResponse
|
||||||
|
.path("meta")
|
||||||
|
.path("valid")
|
||||||
|
.asBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error verifying license: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonNode validateLicense(String licenseKey, String machineFingerprint)
|
||||||
|
throws Exception {
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
String requestBody =
|
||||||
|
String.format(
|
||||||
|
"{\"meta\":{\"key\":\"%s\",\"scope\":{\"fingerprint\":\"%s\"}}}",
|
||||||
|
licenseKey, machineFingerprint);
|
||||||
|
HttpRequest request =
|
||||||
|
HttpRequest.newBuilder()
|
||||||
|
.uri(
|
||||||
|
URI.create(
|
||||||
|
BASE_URL
|
||||||
|
+ "/"
|
||||||
|
+ ACCOUNT_ID
|
||||||
|
+ "/licenses/actions/validate-key"))
|
||||||
|
.header("Content-Type", "application/vnd.api+json")
|
||||||
|
.header("Accept", "application/vnd.api+json")
|
||||||
|
// .header("Authorization", "License " + licenseKey)
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
log.info(" validateLicenseResponse body: " + response.body());
|
||||||
|
JsonNode jsonResponse = objectMapper.readTree(response.body());
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
|
||||||
|
JsonNode metaNode = jsonResponse.path("meta");
|
||||||
|
boolean isValid = metaNode.path("valid").asBoolean();
|
||||||
|
|
||||||
|
String detail = metaNode.path("detail").asText();
|
||||||
|
String code = metaNode.path("code").asText();
|
||||||
|
|
||||||
|
log.debug("License validity: " + isValid);
|
||||||
|
log.debug("Validation detail: " + detail);
|
||||||
|
log.debug("Validation code: " + code);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.error("Error validating license. Status code: " + response.statusCode());
|
||||||
|
}
|
||||||
|
return jsonResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean activateMachine(
|
||||||
|
String licenseKey, String licenseId, String machineFingerprint) throws Exception {
|
||||||
|
HttpClient client = HttpClient.newHttpClient();
|
||||||
|
|
||||||
|
String hostname;
|
||||||
|
try {
|
||||||
|
hostname = java.net.InetAddress.getLocalHost().getHostName();
|
||||||
|
} catch (Exception e) {
|
||||||
|
hostname = "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject body =
|
||||||
|
new JSONObject()
|
||||||
|
.put(
|
||||||
|
"data",
|
||||||
|
new JSONObject()
|
||||||
|
.put("type", "machines")
|
||||||
|
.put(
|
||||||
|
"attributes",
|
||||||
|
new JSONObject()
|
||||||
|
.put("fingerprint", machineFingerprint)
|
||||||
|
.put(
|
||||||
|
"platform",
|
||||||
|
System.getProperty(
|
||||||
|
"os.name")) // Added
|
||||||
|
// platform
|
||||||
|
// parameter
|
||||||
|
.put(
|
||||||
|
"name",
|
||||||
|
hostname)) // Added name parameter
|
||||||
|
.put(
|
||||||
|
"relationships",
|
||||||
|
new JSONObject()
|
||||||
|
.put(
|
||||||
|
"license",
|
||||||
|
new JSONObject()
|
||||||
|
.put(
|
||||||
|
"data",
|
||||||
|
new JSONObject()
|
||||||
|
.put(
|
||||||
|
"type",
|
||||||
|
"licenses")
|
||||||
|
.put(
|
||||||
|
"id",
|
||||||
|
licenseId)))));
|
||||||
|
|
||||||
|
HttpRequest request =
|
||||||
|
HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(BASE_URL + "/" + ACCOUNT_ID + "/machines"))
|
||||||
|
.header("Content-Type", "application/vnd.api+json")
|
||||||
|
.header("Accept", "application/vnd.api+json")
|
||||||
|
.header(
|
||||||
|
"Authorization",
|
||||||
|
"License " + licenseKey) // Keep the license key authentication
|
||||||
|
.POST(
|
||||||
|
HttpRequest.BodyPublishers.ofString(
|
||||||
|
body.toString())) // Send the JSON body
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
log.debug("activateMachine Response body: " + response.body());
|
||||||
|
if (response.statusCode() == 201) {
|
||||||
|
log.info("Machine activated successfully");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.error(
|
||||||
|
"Error activating machine. Status code: {}, error: {}",
|
||||||
|
response.statusCode(),
|
||||||
|
response.body());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateMachineFingerprint() {
|
||||||
|
return GeneralUtils.generateMachineFingerprint();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package stirling.software.SPDF.EE;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class LicenseKeyChecker {
|
||||||
|
|
||||||
|
private final KeygenLicenseVerifier licenseService;
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
private boolean enterpriseEnbaledResult = false;
|
||||||
|
|
||||||
|
// Inject your license service or configuration
|
||||||
|
@Autowired
|
||||||
|
public LicenseKeyChecker(
|
||||||
|
KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {
|
||||||
|
this.licenseService = licenseService;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 604800000, initialDelay = 1000) // 7 days in milliseconds
|
||||||
|
public void checkLicensePeriodically() {
|
||||||
|
checkLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLicense() {
|
||||||
|
if (!applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||||
|
enterpriseEnbaledResult = false;
|
||||||
|
} else {
|
||||||
|
enterpriseEnbaledResult =
|
||||||
|
licenseService.verifyLicense(
|
||||||
|
applicationProperties.getEnterpriseEdition().getKey());
|
||||||
|
if (enterpriseEnbaledResult) {
|
||||||
|
log.info("License key is valid.");
|
||||||
|
} else {
|
||||||
|
log.info("License key is invalid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLicenseKey(String newKey) throws IOException {
|
||||||
|
applicationProperties.getEnterpriseEdition().setKey(newKey);
|
||||||
|
GeneralUtils.saveKeyToConfig("EnterpriseEdition.key", newKey, false);
|
||||||
|
checkLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getEnterpriseEnabledResult() {
|
||||||
|
return enterpriseEnbaledResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package stirling.software.SPDF.Factories;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||||
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||||
|
import stirling.software.SPDF.utils.misc.CustomColorReplaceStrategy;
|
||||||
|
import stirling.software.SPDF.utils.misc.InvertFullColorStrategy;
|
||||||
|
import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ReplaceAndInvertColorFactory {
|
||||||
|
|
||||||
|
public ReplaceAndInvertColorStrategy replaceAndInvert(
|
||||||
|
MultipartFile file,
|
||||||
|
ReplaceAndInvert replaceAndInvertOption,
|
||||||
|
HighContrastColorCombination highContrastColorCombination,
|
||||||
|
String backGroundColor,
|
||||||
|
String textColor) {
|
||||||
|
|
||||||
|
if (replaceAndInvertOption == ReplaceAndInvert.CUSTOM_COLOR
|
||||||
|
|| replaceAndInvertOption == ReplaceAndInvert.HIGH_CONTRAST_COLOR) {
|
||||||
|
|
||||||
|
return new CustomColorReplaceStrategy(
|
||||||
|
file,
|
||||||
|
replaceAndInvertOption,
|
||||||
|
textColor,
|
||||||
|
backGroundColor,
|
||||||
|
highContrastColorCombination);
|
||||||
|
|
||||||
|
} else if (replaceAndInvertOption == ReplaceAndInvert.FULL_INVERSION) {
|
||||||
|
|
||||||
|
return new InvertFullColorStrategy(file, replaceAndInvertOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import io.github.pixee.security.SystemCommand;
|
import io.github.pixee.security.SystemCommand;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class LibreOfficeListener {
|
public class LibreOfficeListener {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
|
private static final Logger logger = LoggerFactory.getLogger(LibreOfficeListener.class);
|
||||||
@@ -31,7 +34,7 @@ public class LibreOfficeListener {
|
|||||||
private LibreOfficeListener() {}
|
private LibreOfficeListener() {}
|
||||||
|
|
||||||
private boolean isListenerRunning() {
|
private boolean isListenerRunning() {
|
||||||
System.out.println("waiting for listener to start");
|
log.info("waiting for listener to start");
|
||||||
try (Socket socket = new Socket()) {
|
try (Socket socket = new Socket()) {
|
||||||
socket.connect(
|
socket.connect(
|
||||||
new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
|
new InetSocketAddress("localhost", 2002), 1000); // Timeout after 1 second
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package stirling.software.SPDF;
|
package stirling.software.SPDF;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -30,15 +31,37 @@ public class SPdfApplication {
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
private static final Logger logger = LoggerFactory.getLogger(SPdfApplication.class);
|
||||||
|
|
||||||
@Autowired private Environment env;
|
@Autowired private Environment env;
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private static String serverPortStatic;
|
private static String serverPortStatic;
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
@Value("${server.port:8080}")
|
||||||
public void setServerPortStatic(String port) {
|
public void setServerPortStatic(String port) {
|
||||||
|
if (port.equalsIgnoreCase("auto")) {
|
||||||
|
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||||
|
SPdfApplication.serverPortStatic =
|
||||||
|
"0"; // This will let Spring Boot assign an available port
|
||||||
|
} else {
|
||||||
SPdfApplication.serverPortStatic = port;
|
SPdfApplication.serverPortStatic = port;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally keep this method if you want to provide a manual port-incrementation fallback.
|
||||||
|
private static String findAvailablePort(int startPort) {
|
||||||
|
int port = startPort;
|
||||||
|
while (!isPortAvailable(port)) {
|
||||||
|
port++;
|
||||||
|
}
|
||||||
|
return String.valueOf(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPortAvailable(int port) {
|
||||||
|
try (ServerSocket socket = new ServerSocket(port)) {
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
@@ -47,13 +70,17 @@ public class SPdfApplication {
|
|||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
String url = "http://localhost:" + getNonStaticPort();
|
String url = "http://localhost:" + getStaticPort();
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
if (os.contains("win")) {
|
if (os.contains("win")) {
|
||||||
// For Windows
|
// For Windows
|
||||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||||
|
} else if (os.contains("mac")) {
|
||||||
|
rt.exec("open " + url);
|
||||||
|
} else if (os.contains("nix") || os.contains("nux")) {
|
||||||
|
rt.exec("xdg-open " + url);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error opening browser: {}", e.getMessage());
|
logger.error("Error opening browser: {}", e.getMessage());
|
||||||
@@ -69,15 +96,13 @@ public class SPdfApplication {
|
|||||||
app.addInitializers(new ConfigInitializer());
|
app.addInitializers(new ConfigInitializer());
|
||||||
Map<String, String> propertyFiles = new HashMap<>();
|
Map<String, String> propertyFiles = new HashMap<>();
|
||||||
|
|
||||||
// stirling pdf settings file
|
// External config files
|
||||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||||
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
||||||
} else {
|
} else {
|
||||||
logger.warn(
|
logger.warn("External configuration file 'configs/settings.yml' does not exist.");
|
||||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom javs settings file
|
|
||||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
||||||
String existingLocation =
|
String existingLocation =
|
||||||
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||||
@@ -100,19 +125,14 @@ public class SPdfApplication {
|
|||||||
|
|
||||||
app.run(args);
|
app.run(args);
|
||||||
|
|
||||||
try {
|
// Ensure directories are created
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw new RuntimeException("Thread interrupted while sleeping", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(Path.of("customFiles/static/"));
|
Files.createDirectories(Path.of("customFiles/static/"));
|
||||||
Files.createDirectories(Path.of("customFiles/templates/"));
|
Files.createDirectories(Path.of("customFiles/templates/"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error creating directories: {}", e.getMessage());
|
logger.error("Error creating directories: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
printStartupLogs();
|
printStartupLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,4 +160,27 @@ public class AppConfig {
|
|||||||
public String accessibilityStatement() {
|
public String accessibilityStatement() {
|
||||||
return applicationProperties.getLegal().getAccessibilityStatement();
|
return applicationProperties.getLegal().getAccessibilityStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean(name = "analyticsPrompt")
|
||||||
|
public boolean analyticsPrompt() {
|
||||||
|
return applicationProperties.getSystem().getEnableAnalytics() == null
|
||||||
|
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "analyticsEnabled")
|
||||||
|
public boolean analyticsEnabled() {
|
||||||
|
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
||||||
|
return applicationProperties.getSystem().getEnableAnalytics() != null
|
||||||
|
&& Boolean.parseBoolean(applicationProperties.getSystem().getEnableAnalytics());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "StirlingPDFLabel")
|
||||||
|
public String stirlingPDFLabel() {
|
||||||
|
return "Stirling-PDF" + " v" + appVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "UUID")
|
||||||
|
public String uuid() {
|
||||||
|
return applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||||
|
public class InitialSetup {
|
||||||
|
|
||||||
|
@Autowired private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initUUIDKey() throws IOException {
|
||||||
|
String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();
|
||||||
|
if (!GeneralUtils.isValidUUID(uuid)) {
|
||||||
|
uuid = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||||
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.UUID", uuid);
|
||||||
|
applicationProperties.getAutomaticallyGenerated().setUUID(uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initSecretKey() throws IOException {
|
||||||
|
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||||
|
if (!GeneralUtils.isValidUUID(secretKey)) {
|
||||||
|
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||||
|
GeneralUtils.saveKeyToConfig("AutomaticallyGenerated.key", secretKey);
|
||||||
|
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
|||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class Beans implements WebMvcConfigurer {
|
public class LocaleConfiguration implements WebMvcConfigurer {
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -32,10 +33,11 @@ public class MetricsFilter extends OncePerRequestFilter {
|
|||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
|
|
||||||
if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
|
if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
String sessionId = (session != null) ? session.getId() : "no-session";
|
||||||
Counter counter =
|
Counter counter =
|
||||||
Counter.builder("http.requests")
|
Counter.builder("http.requests")
|
||||||
.tag("session", request.getSession().getId())
|
.tag("session", sessionId)
|
||||||
.tag("method", request.getMethod())
|
.tag("method", request.getMethod())
|
||||||
.tag("uri", uri)
|
.tag("uri", uri)
|
||||||
.register(meterRegistry);
|
.register(meterRegistry);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package stirling.software.SPDF.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.posthog.java.PostHog;
|
||||||
|
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class PostHogConfig {
|
||||||
|
|
||||||
|
@Value("${posthog.api.key}")
|
||||||
|
private String posthogApiKey;
|
||||||
|
|
||||||
|
@Value("${posthog.host}")
|
||||||
|
private String posthogHost;
|
||||||
|
|
||||||
|
private PostHog postHogClient;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PostHog postHogClient() {
|
||||||
|
postHogClient = new PostHog.Builder(posthogApiKey).host(posthogHost).build();
|
||||||
|
return postHogClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void shutdownPostHog() {
|
||||||
|
if (postHogClient != null) {
|
||||||
|
postHogClient.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// package stirling.software.SPDF.config.fingerprint;
|
||||||
|
//
|
||||||
|
// import java.io.IOException;
|
||||||
|
//
|
||||||
|
// import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
// import org.springframework.stereotype.Component;
|
||||||
|
// import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
//
|
||||||
|
// import jakarta.servlet.FilterChain;
|
||||||
|
// import jakarta.servlet.ServletException;
|
||||||
|
// import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
// import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
// import jakarta.servlet.http.HttpSession;
|
||||||
|
// import lombok.extern.slf4j.Slf4j;
|
||||||
|
// import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
//
|
||||||
|
//// @Component
|
||||||
|
// @Slf4j
|
||||||
|
// public class FingerprintBasedSessionFilter extends OncePerRequestFilter {
|
||||||
|
// private final FingerprintGenerator fingerprintGenerator;
|
||||||
|
// private final FingerprintBasedSessionManager sessionManager;
|
||||||
|
//
|
||||||
|
// @Autowired
|
||||||
|
// public FingerprintBasedSessionFilter(
|
||||||
|
// FingerprintGenerator fingerprintGenerator,
|
||||||
|
// FingerprintBasedSessionManager sessionManager) {
|
||||||
|
// this.fingerprintGenerator = fingerprintGenerator;
|
||||||
|
// this.sessionManager = sessionManager;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// protected void doFilterInternal(
|
||||||
|
// HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
// throws ServletException, IOException {
|
||||||
|
//
|
||||||
|
// if (RequestUriUtils.isStaticResource(request.getContextPath(), request.getRequestURI())) {
|
||||||
|
// filterChain.doFilter(request, response);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// String fingerprint = fingerprintGenerator.generateFingerprint(request);
|
||||||
|
// log.debug("Generated fingerprint for request: {}", fingerprint);
|
||||||
|
//
|
||||||
|
// HttpSession session = request.getSession();
|
||||||
|
// boolean isNewSession = session.isNew();
|
||||||
|
// String sessionId = session.getId();
|
||||||
|
//
|
||||||
|
// if (isNewSession) {
|
||||||
|
// log.info("New session created: {}", sessionId);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!sessionManager.isFingerPrintAllowed(fingerprint)) {
|
||||||
|
// log.info("Blocked fingerprint detected, redirecting: {}", fingerprint);
|
||||||
|
// response.sendRedirect(request.getContextPath() + "/too-many-requests");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// session.setAttribute("userFingerprint", fingerprint);
|
||||||
|
// session.setAttribute(
|
||||||
|
// FingerprintBasedSessionManager.STARTUP_TIMESTAMP,
|
||||||
|
// FingerprintBasedSessionManager.APP_STARTUP_TIME);
|
||||||
|
//
|
||||||
|
// sessionManager.registerFingerprint(fingerprint, sessionId);
|
||||||
|
//
|
||||||
|
// log.debug("Proceeding with request: {}", request.getRequestURI());
|
||||||
|
// filterChain.doFilter(request, response);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// package stirling.software.SPDF.config.fingerprint;
|
||||||
|
//
|
||||||
|
// import java.util.Iterator;
|
||||||
|
// import java.util.Map;
|
||||||
|
// import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
// import java.util.concurrent.TimeUnit;
|
||||||
|
//
|
||||||
|
// import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
// import org.springframework.stereotype.Component;
|
||||||
|
//
|
||||||
|
// import jakarta.servlet.http.HttpSession;
|
||||||
|
// import jakarta.servlet.http.HttpSessionAttributeListener;
|
||||||
|
// import jakarta.servlet.http.HttpSessionEvent;
|
||||||
|
// import jakarta.servlet.http.HttpSessionListener;
|
||||||
|
// import lombok.AllArgsConstructor;
|
||||||
|
// import lombok.Data;
|
||||||
|
// import lombok.extern.slf4j.Slf4j;
|
||||||
|
//
|
||||||
|
// @Slf4j
|
||||||
|
// @Component
|
||||||
|
// public class FingerprintBasedSessionManager
|
||||||
|
// implements HttpSessionListener, HttpSessionAttributeListener {
|
||||||
|
// private static final ConcurrentHashMap<String, FingerprintInfo> activeFingerprints =
|
||||||
|
// new ConcurrentHashMap<>();
|
||||||
|
//
|
||||||
|
// // To be reduced in later version to 8~
|
||||||
|
// private static final int MAX_ACTIVE_FINGERPRINTS = 30;
|
||||||
|
//
|
||||||
|
// static final String STARTUP_TIMESTAMP = "appStartupTimestamp";
|
||||||
|
// static final long APP_STARTUP_TIME = System.currentTimeMillis();
|
||||||
|
// private static final long FINGERPRINT_EXPIRATION = TimeUnit.MINUTES.toMillis(30);
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void sessionCreated(HttpSessionEvent se) {
|
||||||
|
// HttpSession session = se.getSession();
|
||||||
|
// String sessionId = session.getId();
|
||||||
|
// String fingerprint = (String) session.getAttribute("userFingerprint");
|
||||||
|
//
|
||||||
|
// if (fingerprint == null) {
|
||||||
|
// log.warn("Session created without fingerprint: {}", sessionId);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// synchronized (activeFingerprints) {
|
||||||
|
// if (activeFingerprints.size() >= MAX_ACTIVE_FINGERPRINTS
|
||||||
|
// && !activeFingerprints.containsKey(fingerprint)) {
|
||||||
|
// log.info("Max fingerprints reached. Marking session as blocked: {}", sessionId);
|
||||||
|
// session.setAttribute("blocked", true);
|
||||||
|
// } else {
|
||||||
|
// activeFingerprints.put(
|
||||||
|
// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis()));
|
||||||
|
// log.info(
|
||||||
|
// "New fingerprint registered: {}. Total active fingerprints: {}",
|
||||||
|
// fingerprint,
|
||||||
|
// activeFingerprints.size());
|
||||||
|
// }
|
||||||
|
// session.setAttribute(STARTUP_TIMESTAMP, APP_STARTUP_TIME);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void sessionDestroyed(HttpSessionEvent se) {
|
||||||
|
// HttpSession session = se.getSession();
|
||||||
|
// String fingerprint = (String) session.getAttribute("userFingerprint");
|
||||||
|
//
|
||||||
|
// if (fingerprint != null) {
|
||||||
|
// synchronized (activeFingerprints) {
|
||||||
|
// activeFingerprints.remove(fingerprint);
|
||||||
|
// log.info(
|
||||||
|
// "Fingerprint removed: {}. Total active fingerprints: {}",
|
||||||
|
// fingerprint,
|
||||||
|
// activeFingerprints.size());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public boolean isFingerPrintAllowed(String fingerprint) {
|
||||||
|
// synchronized (activeFingerprints) {
|
||||||
|
// return activeFingerprints.size() < MAX_ACTIVE_FINGERPRINTS
|
||||||
|
// || activeFingerprints.containsKey(fingerprint);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void registerFingerprint(String fingerprint, String sessionId) {
|
||||||
|
// synchronized (activeFingerprints) {
|
||||||
|
// activeFingerprints.put(
|
||||||
|
// fingerprint, new FingerprintInfo(sessionId, System.currentTimeMillis()));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void unregisterFingerprint(String fingerprint) {
|
||||||
|
// synchronized (activeFingerprints) {
|
||||||
|
// activeFingerprints.remove(fingerprint);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Scheduled(fixedRate = 1800000) // Run every 30 mins
|
||||||
|
// public void cleanupStaleFingerprints() {
|
||||||
|
// log.info("Starting cleanup of stale fingerprints");
|
||||||
|
// long now = System.currentTimeMillis();
|
||||||
|
// int removedCount = 0;
|
||||||
|
//
|
||||||
|
// synchronized (activeFingerprints) {
|
||||||
|
// Iterator<Map.Entry<String, FingerprintInfo>> iterator =
|
||||||
|
// activeFingerprints.entrySet().iterator();
|
||||||
|
// while (iterator.hasNext()) {
|
||||||
|
// Map.Entry<String, FingerprintInfo> entry = iterator.next();
|
||||||
|
// FingerprintInfo info = entry.getValue();
|
||||||
|
//
|
||||||
|
// if (now - info.getLastAccessTime() > FINGERPRINT_EXPIRATION) {
|
||||||
|
// iterator.remove();
|
||||||
|
// removedCount++;
|
||||||
|
// log.info("Removed stale fingerprint: {}", entry.getKey());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// log.info("Cleanup complete. Removed {} stale fingerprints", removedCount);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public void updateLastAccessTime(String fingerprint) {
|
||||||
|
// FingerprintInfo info = activeFingerprints.get(fingerprint);
|
||||||
|
// if (info != null) {
|
||||||
|
// info.setLastAccessTime(System.currentTimeMillis());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Data
|
||||||
|
// @AllArgsConstructor
|
||||||
|
// private static class FingerprintInfo {
|
||||||
|
// private String sessionId;
|
||||||
|
// private long lastAccessTime;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// package stirling.software.SPDF.config.fingerprint;
|
||||||
|
//
|
||||||
|
// import java.security.MessageDigest;
|
||||||
|
// import java.security.NoSuchAlgorithmException;
|
||||||
|
//
|
||||||
|
// import org.springframework.stereotype.Component;
|
||||||
|
//
|
||||||
|
// import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
//
|
||||||
|
// @Component
|
||||||
|
// public class FingerprintGenerator {
|
||||||
|
//
|
||||||
|
// public String generateFingerprint(HttpServletRequest request) {
|
||||||
|
// if (request == null) {
|
||||||
|
// return "";
|
||||||
|
// }
|
||||||
|
// StringBuilder fingerprintBuilder = new StringBuilder();
|
||||||
|
//
|
||||||
|
// // Add IP address
|
||||||
|
// fingerprintBuilder.append(request.getRemoteAddr());
|
||||||
|
//
|
||||||
|
// // Add X-Forwarded-For header if present (for clients behind proxies)
|
||||||
|
// String forwardedFor = request.getHeader("X-Forwarded-For");
|
||||||
|
// if (forwardedFor != null) {
|
||||||
|
// fingerprintBuilder.append(forwardedFor);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Add User-Agent
|
||||||
|
// String userAgent = request.getHeader("User-Agent");
|
||||||
|
// if (userAgent != null) {
|
||||||
|
// fingerprintBuilder.append(userAgent);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Add Accept-Language header
|
||||||
|
// String acceptLanguage = request.getHeader("Accept-Language");
|
||||||
|
// if (acceptLanguage != null) {
|
||||||
|
// fingerprintBuilder.append(acceptLanguage);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Add Accept header
|
||||||
|
// String accept = request.getHeader("Accept");
|
||||||
|
// if (accept != null) {
|
||||||
|
// fingerprintBuilder.append(accept);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Add Connection header
|
||||||
|
// String connection = request.getHeader("Connection");
|
||||||
|
// if (connection != null) {
|
||||||
|
// fingerprintBuilder.append(connection);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Add server port
|
||||||
|
// fingerprintBuilder.append(request.getServerPort());
|
||||||
|
//
|
||||||
|
// // Add secure flag
|
||||||
|
// fingerprintBuilder.append(request.isSecure());
|
||||||
|
//
|
||||||
|
// // Generate a hash of the fingerprint
|
||||||
|
// return generateHash(fingerprintBuilder.toString());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private String generateHash(String input) {
|
||||||
|
// try {
|
||||||
|
// MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
// byte[] hash = digest.digest(input.getBytes());
|
||||||
|
// StringBuilder hexString = new StringBuilder();
|
||||||
|
// for (byte b : hash) {
|
||||||
|
// String hex = Integer.toHexString(0xff & b);
|
||||||
|
// if (hex.length() == 1) hexString.append('0');
|
||||||
|
// hexString.append(hex);
|
||||||
|
// }
|
||||||
|
// return hexString.toString();
|
||||||
|
// } catch (NoSuchAlgorithmException e) {
|
||||||
|
// throw new RuntimeException("Failed to generate fingerprint hash", e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config.interfaces;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.config.interfaces;
|
||||||
|
|
||||||
public interface ShowAdminInterface {
|
public interface ShowAdminInterface {
|
||||||
default boolean getShowUpdateOnlyAdmins() {
|
default boolean getShowUpdateOnlyAdmins() {
|
||||||
@@ -7,7 +7,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.ShowAdminInterface;
|
import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.repository.UserRepository;
|
import stirling.software.SPDF.repository.UserRepository;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -14,9 +16,12 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class FirstLoginFilter extends OncePerRequestFilter {
|
public class FirstLoginFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
@@ -50,6 +55,22 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
HttpSession session = request.getSession(true);
|
||||||
|
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
String creationTime = timeFormat.format(new Date(session.getCreationTime()));
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"Request Info - New: {}, creationTimeSession {}, ID: {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}",
|
||||||
|
session.isNew(),
|
||||||
|
creationTime,
|
||||||
|
session.getId(),
|
||||||
|
request.getRemoteAddr(),
|
||||||
|
request.getHeader("User-Agent"),
|
||||||
|
request.getHeader("Referer"),
|
||||||
|
request.getRequestURL().toString());
|
||||||
|
}
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.simpleyaml.configuration.file.YamlFile;
|
|
||||||
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
|
||||||
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
|
|
||||||
@@ -39,15 +34,6 @@ public class InitialSecuritySetup {
|
|||||||
initializeInternalApiUser();
|
initializeInternalApiUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void initSecretKey() throws IOException {
|
|
||||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
|
||||||
if (!isValidUUID(secretKey)) {
|
|
||||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
|
||||||
saveKeyToConfig(secretKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeAdminUser() throws IOException {
|
private void initializeAdminUser() throws IOException {
|
||||||
String initialUsername =
|
String initialUsername =
|
||||||
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
applicationProperties.getSecurity().getInitialLogin().getUsername();
|
||||||
@@ -89,33 +75,4 @@ public class InitialSecuritySetup {
|
|||||||
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
log.info("Internal API user created: " + Role.INTERNAL_API_USER.getRoleId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveKeyToConfig(String key) throws IOException {
|
|
||||||
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
|
||||||
|
|
||||||
final YamlFile settingsYml = new YamlFile(path.toFile());
|
|
||||||
DumperOptions yamlOptionssettingsYml =
|
|
||||||
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
|
||||||
yamlOptionssettingsYml.setSplitLines(false);
|
|
||||||
|
|
||||||
settingsYml.loadWithComments();
|
|
||||||
|
|
||||||
settingsYml
|
|
||||||
.path("AutomaticallyGenerated.key")
|
|
||||||
.set(key)
|
|
||||||
.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
|
||||||
settingsYml.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidUUID(String uuid) {
|
|
||||||
if (uuid == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
UUID.fromString(uuid);
|
|
||||||
return true;
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,58 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.util.*;
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
||||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
|
import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication;
|
||||||
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
||||||
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
|
@Slf4j
|
||||||
public class SecurityConfiguration {
|
public class SecurityConfiguration {
|
||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
@Autowired private CustomUserDetailsService userDetailsService;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
|
@Autowired(required = false)
|
||||||
|
private GrantedAuthoritiesMapper userAuthoritiesMapper;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
@@ -73,12 +74,15 @@ public class SecurityConfiguration {
|
|||||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
@Autowired private FirstLoginFilter firstLoginFilter;
|
||||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
@Autowired private SessionPersistentRegistry sessionRegistry;
|
||||||
|
|
||||||
|
@Autowired private ConvertResponseToAuthentication convertResponseToAuthentication;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http.addFilterBefore(userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
http.authenticationManager(authenticationManager(http));
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
|
http.addFilterBefore(
|
||||||
|
userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
http.csrf(csrf -> csrf.disable());
|
http.csrf(csrf -> csrf.disable());
|
||||||
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
@@ -135,6 +139,7 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|| trimmedUri.startsWith("/oauth")
|
|| trimmedUri.startsWith("/oauth")
|
||||||
|
|| trimmedUri.startsWith("/saml2")
|
||||||
|| trimmedUri.endsWith(".svg")
|
|| trimmedUri.endsWith(".svg")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith(
|
||||||
"/register")
|
"/register")
|
||||||
@@ -184,13 +189,37 @@ public class SecurityConfiguration {
|
|||||||
userService,
|
userService,
|
||||||
loginAttemptService))
|
loginAttemptService))
|
||||||
.userAuthoritiesMapper(
|
.userAuthoritiesMapper(
|
||||||
userAuthoritiesMapper())))
|
userAuthoritiesMapper)))
|
||||||
.logout(
|
.logout(
|
||||||
logout ->
|
logout ->
|
||||||
logout.logoutSuccessHandler(
|
logout.logoutSuccessHandler(
|
||||||
new CustomOAuth2LogoutSuccessHandler(
|
new CustomOAuth2LogoutSuccessHandler(
|
||||||
applicationProperties)));
|
applicationProperties)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle SAML
|
||||||
|
if (applicationProperties.getSecurity().getSaml() != null
|
||||||
|
&& applicationProperties.getSecurity().getSaml().getEnabled()
|
||||||
|
&& !applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getLoginMethod()
|
||||||
|
.equalsIgnoreCase("normal")) {
|
||||||
|
http.saml2Login(
|
||||||
|
saml2 -> {
|
||||||
|
saml2.relyingPartyRegistrationRepository(
|
||||||
|
relyingPartyRegistrationRepository)
|
||||||
|
.successHandler(
|
||||||
|
new CustomSAMLAuthenticationSuccessHandler(
|
||||||
|
loginAttemptService,
|
||||||
|
userService,
|
||||||
|
applicationProperties))
|
||||||
|
.failureHandler(
|
||||||
|
new CustomSAMLAuthenticationFailureHandler());
|
||||||
|
})
|
||||||
|
.saml2Logout(withDefaults())
|
||||||
|
.addFilterBefore(
|
||||||
|
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
http.csrf(csrf -> csrf.disable())
|
http.csrf(csrf -> csrf.disable())
|
||||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||||
@@ -199,176 +228,32 @@ public class SecurityConfiguration {
|
|||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client Registration Repository for OAUTH2 OIDC Login
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
public AuthenticationProvider samlAuthenticationProvider() {
|
||||||
value = "security.oauth2.enabled",
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
havingValue = "true",
|
new OpenSaml4AuthenticationProvider();
|
||||||
matchIfMissing = false)
|
authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication);
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
return authenticationProvider;
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
|
||||||
|
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
|
||||||
|
|
||||||
if (registrations.isEmpty()) {
|
|
||||||
logger.error("At least one OAuth2 provider must be configured");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
return google != null && google.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationuri())
|
|
||||||
.tokenUri(google.getTokenuri())
|
|
||||||
.userInfoUri(google.getUserinfouri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
|
||||||
.registrationId(keycloak.getName())
|
|
||||||
.clientId(keycloak.getClientId())
|
|
||||||
.clientSecret(keycloak.getClientSecret())
|
|
||||||
.scope(keycloak.getScopes())
|
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
|
||||||
.clientName(keycloak.getClientName())
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
return github != null && github.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(github.getName())
|
|
||||||
.clientId(github.getClientId())
|
|
||||||
.clientSecret(github.getClientSecret())
|
|
||||||
.scope(github.getScopes())
|
|
||||||
.authorizationUri(github.getAuthorizationuri())
|
|
||||||
.tokenUri(github.getTokenuri())
|
|
||||||
.userInfoUri(github.getUserinfouri())
|
|
||||||
.userNameAttributeName(github.getUseAsUsername())
|
|
||||||
.clientName(github.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null
|
|
||||||
|| oauth.getIssuer() == null
|
|
||||||
|| oauth.getIssuer().isEmpty()
|
|
||||||
|| oauth.getClientId() == null
|
|
||||||
|| oauth.getClientId().isEmpty()
|
|
||||||
|| oauth.getClientSecret() == null
|
|
||||||
|| oauth.getClientSecret().isEmpty()
|
|
||||||
|| oauth.getScopes() == null
|
|
||||||
|| oauth.getScopes().isEmpty()
|
|
||||||
|| oauth.getUseAsUsername() == null
|
|
||||||
|| oauth.getUseAsUsername().isEmpty()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
|
||||||
.registrationId("oidc")
|
|
||||||
.clientId(oauth.getClientId())
|
|
||||||
.clientSecret(oauth.getClientSecret())
|
|
||||||
.scope(oauth.getScopes())
|
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
|
||||||
.clientName("OIDC")
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
|
||||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
|
||||||
*/
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
public AuthenticationProvider daoAuthenticationProvider() {
|
||||||
value = "security.oauth2.enabled",
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
havingValue = "true",
|
provider.setUserDetailsService(userDetailsService); // UserDetailsService
|
||||||
matchIfMissing = false)
|
provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder
|
||||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
return provider;
|
||||||
return (authorities) -> {
|
}
|
||||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
|
||||||
|
|
||||||
authorities.forEach(
|
@Bean
|
||||||
authority -> {
|
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||||
// Add existing OAUTH2 Authorities
|
AuthenticationManagerBuilder authenticationManagerBuilder =
|
||||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||||
|
|
||||||
// Add Authorities from database for existing user, if user is present.
|
authenticationManagerBuilder
|
||||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
.authenticationProvider(daoAuthenticationProvider()) // Benutzername/Passwort
|
||||||
String useAsUsername =
|
.authenticationProvider(samlAuthenticationProvider()); // SAML
|
||||||
applicationProperties
|
|
||||||
.getSecurity()
|
return authenticationManagerBuilder.build();
|
||||||
.getOauth2()
|
|
||||||
.getUseAsUsername();
|
|
||||||
Optional<User> userOpt =
|
|
||||||
userService.findByUsernameIgnoreCase(
|
|
||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
mappedAuthorities.add(
|
|
||||||
new SimpleGrantedAuthority(
|
|
||||||
userService.findRole(user).getAuthority()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mappedAuthorities;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -386,4 +271,13 @@ public class SecurityConfiguration {
|
|||||||
public boolean activSecurity() {
|
public boolean activSecurity() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only Dev test
|
||||||
|
@Bean
|
||||||
|
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||||
|
return (web) ->
|
||||||
|
web.ignoring()
|
||||||
|
.requestMatchers(
|
||||||
|
"/css/**", "/images/**", "/js/**", "/**.svg", "/pdfjs-legacy/**");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -30,13 +29,18 @@ import stirling.software.SPDF.model.User;
|
|||||||
@Component
|
@Component
|
||||||
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
@Autowired @Lazy private UserService userService;
|
private final UserService userService;
|
||||||
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
|
private final boolean loginEnabledValue;
|
||||||
|
|
||||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
public UserAuthenticationFilter(
|
||||||
|
@Lazy UserService userService,
|
||||||
@Autowired
|
SessionPersistentRegistry sessionPersistentRegistry,
|
||||||
@Qualifier("loginEnabled")
|
@Qualifier("loginEnabled") boolean loginEnabledValue) {
|
||||||
public boolean loginEnabledValue;
|
this.userService = userService;
|
||||||
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
|
this.loginEnabledValue = loginEnabledValue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(
|
protected void doFilterInternal(
|
||||||
@@ -51,6 +55,19 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
// Check for session expiration (unsure if needed)
|
||||||
|
// if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
// String sessionId = request.getSession().getId();
|
||||||
|
// SessionInformation sessionInfo =
|
||||||
|
// sessionPersistentRegistry.getSessionInformation(sessionId);
|
||||||
|
//
|
||||||
|
// if (sessionInfo != null && sessionInfo.isExpired()) {
|
||||||
|
// SecurityContextHolder.clearContext();
|
||||||
|
// response.sendRedirect(request.getContextPath() + "/login?expired=true");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Check for API key in the request headers if no authentication exists
|
// Check for API key in the request headers if no authentication exists
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
String apiKey = request.getHeader("X-API-Key");
|
String apiKey = request.getHeader("X-API-Key");
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||||
import stirling.software.SPDF.utils.FileInfo;
|
import stirling.software.SPDF.utils.FileInfo;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ConvertResponseToAuthentication
|
||||||
|
implements Converter<ResponseToken, Saml2Authentication> {
|
||||||
|
|
||||||
|
private final Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup;
|
||||||
|
|
||||||
|
public ConvertResponseToAuthentication(
|
||||||
|
Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup) {
|
||||||
|
this.saml2AuthorityAttributeLookup = saml2AuthorityAttributeLookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Saml2Authentication convert(ResponseToken responseToken) {
|
||||||
|
final Assertion assertion =
|
||||||
|
CollectionUtils.firstElement(responseToken.getResponse().getAssertions());
|
||||||
|
final Map<String, List<Object>> attributes =
|
||||||
|
SamlAssertionUtils.getAssertionAttributes(assertion);
|
||||||
|
final String registrationId =
|
||||||
|
responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
|
||||||
|
final ScimSaml2AuthenticatedPrincipal principal =
|
||||||
|
new ScimSaml2AuthenticatedPrincipal(
|
||||||
|
assertion,
|
||||||
|
attributes,
|
||||||
|
saml2AuthorityAttributeLookup.getIdentityMappings(registrationId));
|
||||||
|
final Collection<? extends GrantedAuthority> assertionAuthorities =
|
||||||
|
getAssertionAuthorities(
|
||||||
|
attributes,
|
||||||
|
saml2AuthorityAttributeLookup.getAuthorityAttribute(registrationId));
|
||||||
|
return new Saml2Authentication(
|
||||||
|
principal, responseToken.getToken().getSaml2Response(), assertionAuthorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<? extends GrantedAuthority> getAssertionAuthorities(
|
||||||
|
final Map<String, List<Object>> attributes, final String authoritiesAttributeName) {
|
||||||
|
if (attributes == null || attributes.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object> groups = new ArrayList<>();
|
||||||
|
|
||||||
|
if (attributes.get(authoritiesAttributeName) != null) {
|
||||||
|
groups = new ArrayList<>(attributes.get(authoritiesAttributeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups.stream()
|
||||||
|
.filter(String.class::isInstance)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.map(String::toLowerCase)
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.DisabledException;
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
AuthenticationException exception)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
if (exception instanceof BadCredentialsException) {
|
||||||
|
log.error("BadCredentialsException", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof DisabledException) {
|
||||||
|
log.error("User is deactivated: ", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof LockedException) {
|
||||||
|
log.error("Account locked: ", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof Saml2AuthenticationException) {
|
||||||
|
log.error("SAML2 Authentication error: ", exception);
|
||||||
|
getRedirectStrategy()
|
||||||
|
.sendRedirect(request, response, "/logout?error=saml2AuthenticationError");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.error("Unhandled authentication exception", exception);
|
||||||
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||||
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomSAMLAuthenticationSuccessHandler
|
||||||
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
private LoginAttemptService loginAttemptService;
|
||||||
|
private UserService userService;
|
||||||
|
private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public CustomSAMLAuthenticationSuccessHandler(
|
||||||
|
LoginAttemptService loginAttemptService,
|
||||||
|
UserService userService,
|
||||||
|
ApplicationProperties applicationProperties) {
|
||||||
|
this.loginAttemptService = loginAttemptService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
String username = "";
|
||||||
|
|
||||||
|
if (principal instanceof OAuth2User) {
|
||||||
|
OAuth2User oauthUser = (OAuth2User) principal;
|
||||||
|
username = oauthUser.getName();
|
||||||
|
} else if (principal instanceof UserDetails) {
|
||||||
|
UserDetails oauthUser = (UserDetails) principal;
|
||||||
|
username = oauthUser.getUsername();
|
||||||
|
} else if (principal instanceof ScimSaml2AuthenticatedPrincipal) {
|
||||||
|
ScimSaml2AuthenticatedPrincipal samlPrincipal =
|
||||||
|
(ScimSaml2AuthenticatedPrincipal) principal;
|
||||||
|
username = samlPrincipal.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the saved request
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
String contextPath = request.getContextPath();
|
||||||
|
SavedRequest savedRequest =
|
||||||
|
(session != null)
|
||||||
|
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (savedRequest != null
|
||||||
|
&& !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) {
|
||||||
|
// Redirect to the original destination
|
||||||
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
|
} else {
|
||||||
|
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
|
if (session != null) {
|
||||||
|
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
||||||
|
}
|
||||||
|
throw new LockedException(
|
||||||
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
|
}
|
||||||
|
if (userService.usernameExistsIgnoreCase(username)
|
||||||
|
&& userService.hasPassword(username)
|
||||||
|
&& !userService.isAuthenticationTypeByUsername(
|
||||||
|
username, AuthenticationType.OAUTH2)
|
||||||
|
&& oAuth.getAutoCreateUser()) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (oAuth.getBlockRegistration()
|
||||||
|
&& !userService.usernameExistsIgnoreCase(username)) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (principal instanceof OAuth2User) {
|
||||||
|
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
|
||||||
|
}
|
||||||
|
response.sendRedirect(contextPath + "/");
|
||||||
|
return;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
String redirectUrl = determineTargetUrl(request, response, authentication);
|
||||||
|
|
||||||
|
if (response.isCommitted()) {
|
||||||
|
log.debug("Response has already been committed. Unable to redirect to " + redirectUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String determineTargetUrl(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Authentication authentication) {
|
||||||
|
// Default to the root URL
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
public interface Saml2AuthorityAttributeLookup {
|
||||||
|
String getAuthorityAttribute(String registrationId);
|
||||||
|
|
||||||
|
SimpleScimMappings getIdentityMappings(String registrationId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Saml2AuthorityAttributeLookupImpl implements Saml2AuthorityAttributeLookup {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthorityAttribute(String registrationId) {
|
||||||
|
return "authorityAttributeName";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleScimMappings getIdentityMappings(String registrationId) {
|
||||||
|
return new SimpleScimMappings();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.opensaml.core.xml.XMLObject;
|
||||||
|
import org.opensaml.core.xml.schema.*;
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
|
||||||
|
public class SamlAssertionUtils {
|
||||||
|
|
||||||
|
public static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) {
|
||||||
|
Map<String, List<Object>> attributeMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
assertion
|
||||||
|
.getAttributeStatements()
|
||||||
|
.forEach(
|
||||||
|
attributeStatement -> {
|
||||||
|
attributeStatement
|
||||||
|
.getAttributes()
|
||||||
|
.forEach(
|
||||||
|
attribute -> {
|
||||||
|
List<Object> attributeValues = new ArrayList<>();
|
||||||
|
|
||||||
|
attribute
|
||||||
|
.getAttributeValues()
|
||||||
|
.forEach(
|
||||||
|
xmlObject -> {
|
||||||
|
Object attributeValue =
|
||||||
|
getXmlObjectValue(
|
||||||
|
xmlObject);
|
||||||
|
if (attributeValue != null) {
|
||||||
|
attributeValues.add(
|
||||||
|
attributeValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
attributeMap.put(
|
||||||
|
attribute.getName(), attributeValues);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return attributeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getXmlObjectValue(XMLObject xmlObject) {
|
||||||
|
if (xmlObject instanceof XSAny) {
|
||||||
|
return ((XSAny) xmlObject).getTextContent();
|
||||||
|
} else if (xmlObject instanceof XSString) {
|
||||||
|
return ((XSString) xmlObject).getValue();
|
||||||
|
} else if (xmlObject instanceof XSInteger) {
|
||||||
|
return ((XSInteger) xmlObject).getValue();
|
||||||
|
} else if (xmlObject instanceof XSURI) {
|
||||||
|
return ((XSURI) xmlObject).getURI();
|
||||||
|
} else if (xmlObject instanceof XSBoolean) {
|
||||||
|
return ((XSBoolean) xmlObject).getValue().getValue();
|
||||||
|
} else if (xmlObject instanceof XSDateTime) {
|
||||||
|
Instant dateTime = ((XSDateTime) xmlObject).getValue();
|
||||||
|
return (dateTime != null) ? Instant.ofEpochMilli(dateTime.toEpochMilli()) : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
|
||||||
|
import org.opensaml.security.x509.X509Support;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.security.converter.RsaKeyConverters;
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.*;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class SamlConfig {
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Autowired ResourceLoader resourceLoader;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.saml.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository()
|
||||||
|
throws CertificateException, IOException {
|
||||||
|
|
||||||
|
// Resource signingCertResource = new ClassPathResource(this.rpSigningCertLocation);
|
||||||
|
Resource signingCertResource =
|
||||||
|
resourceLoader.getResource(
|
||||||
|
this.applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSaml()
|
||||||
|
.getCertificateLocation());
|
||||||
|
// Resource signingKeyResource = new ClassPathResource(this.rpSigningKeyLocation);
|
||||||
|
Resource signingKeyResource =
|
||||||
|
resourceLoader.getResource(
|
||||||
|
this.applicationProperties.getSecurity().getSaml().getPrivateKeyLocation());
|
||||||
|
try (InputStream is = signingKeyResource.getInputStream();
|
||||||
|
InputStream certIS = signingCertResource.getInputStream(); ) {
|
||||||
|
X509Certificate rpCertificate = X509Support.decodeCertificate(certIS.readAllBytes());
|
||||||
|
RSAPrivateKey rpKey = RsaKeyConverters.pkcs8().convert(is);
|
||||||
|
final Saml2X509Credential rpSigningCredentials =
|
||||||
|
Saml2X509Credential.signing(rpKey, rpCertificate);
|
||||||
|
|
||||||
|
X509Certificate apCert =
|
||||||
|
X509Support.decodeCertificate(
|
||||||
|
applicationProperties.getSecurity().getSaml().getSigningCertificate());
|
||||||
|
Saml2X509Credential apCredential = Saml2X509Credential.verification(apCert);
|
||||||
|
|
||||||
|
RelyingPartyRegistration registration =
|
||||||
|
RelyingPartyRegistrations.fromMetadataLocation(
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSaml()
|
||||||
|
.getIdpMetadataLocation())
|
||||||
|
.entityId(applicationProperties.getSecurity().getSaml().getEntityId())
|
||||||
|
.registrationId(
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSaml()
|
||||||
|
.getRegistrationId())
|
||||||
|
.signingX509Credentials(c -> c.add(rpSigningCredentials))
|
||||||
|
.assertingPartyDetails(
|
||||||
|
party ->
|
||||||
|
party.wantAuthnRequestsSigned(true)
|
||||||
|
.verificationX509Credentials(
|
||||||
|
c -> c.add(apCredential)))
|
||||||
|
.singleLogoutServiceLocation("http://localhost:8090/logout/saml2/slo")
|
||||||
|
.build();
|
||||||
|
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import com.unboundid.scim2.common.types.Email;
|
||||||
|
import com.unboundid.scim2.common.types.Name;
|
||||||
|
import com.unboundid.scim2.common.types.UserResource;
|
||||||
|
|
||||||
|
public class ScimSaml2AuthenticatedPrincipal implements AuthenticatedPrincipal, Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final transient UserResource userResource;
|
||||||
|
|
||||||
|
public ScimSaml2AuthenticatedPrincipal(
|
||||||
|
final Assertion assertion,
|
||||||
|
final Map<String, List<Object>> attributes,
|
||||||
|
final SimpleScimMappings attributeMappings) {
|
||||||
|
Assert.notNull(assertion, "assertion cannot be null");
|
||||||
|
Assert.notNull(assertion.getSubject(), "assertion subject cannot be null");
|
||||||
|
Assert.notNull(
|
||||||
|
assertion.getSubject().getNameID(), "assertion subject NameID cannot be null");
|
||||||
|
Assert.notNull(attributes, "attributes cannot be null");
|
||||||
|
Assert.notNull(attributeMappings, "attributeMappings cannot be null");
|
||||||
|
|
||||||
|
final Name name =
|
||||||
|
new Name()
|
||||||
|
.setFamilyName(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getFamilyName))
|
||||||
|
.setGivenName(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getGivenName));
|
||||||
|
|
||||||
|
final List<Email> emails = new ArrayList<>(1);
|
||||||
|
emails.add(
|
||||||
|
new Email()
|
||||||
|
.setValue(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getEmail))
|
||||||
|
.setPrimary(true));
|
||||||
|
|
||||||
|
userResource =
|
||||||
|
new UserResource()
|
||||||
|
.setUserName(assertion.getSubject().getNameID().getValue())
|
||||||
|
.setName(name)
|
||||||
|
.setEmails(emails);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAttribute(
|
||||||
|
final Map<String, List<Object>> attributes,
|
||||||
|
final SimpleScimMappings simpleScimMappings,
|
||||||
|
final Function<SimpleScimMappings, String> attributeMapper) {
|
||||||
|
|
||||||
|
final String key = attributeMapper.apply(simpleScimMappings);
|
||||||
|
|
||||||
|
final List<Object> values = attributes.getOrDefault(key, Collections.emptyList());
|
||||||
|
|
||||||
|
return values.stream()
|
||||||
|
.filter(String.class::isInstance)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return this.userResource.getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserResource getUserResource() {
|
||||||
|
return this.userResource;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SimpleScimMappings {
|
||||||
|
String givenName;
|
||||||
|
String familyName;
|
||||||
|
String email;
|
||||||
|
}
|
||||||
@@ -11,16 +11,19 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class CustomHttpSessionListener implements HttpSessionListener {
|
public class CustomHttpSessionListener implements HttpSessionListener {
|
||||||
|
|
||||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
private SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
|
||||||
|
super();
|
||||||
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionCreated(HttpSessionEvent se) {
|
public void sessionCreated(HttpSessionEvent se) {}
|
||||||
log.info("Session created: " + se.getSession().getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionDestroyed(HttpSessionEvent se) {
|
public void sessionDestroyed(HttpSessionEvent se) {
|
||||||
log.info("Session destroyed: " + se.getSession().getId());
|
|
||||||
sessionPersistentRegistry.expireSession(se.getSession().getId());
|
sessionPersistentRegistry.expireSession(se.getSession().getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,14 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (principalName != null) {
|
if (principalName != null) {
|
||||||
|
// Clear old sessions for the principal (unsure if needed)
|
||||||
|
// List<SessionEntity> existingSessions =
|
||||||
|
// sessionRepository.findByPrincipalName(principalName);
|
||||||
|
// for (SessionEntity session : existingSessions) {
|
||||||
|
// session.setExpired(true);
|
||||||
|
// sessionRepository.save(session);
|
||||||
|
// }
|
||||||
|
|
||||||
SessionEntity sessionEntity = new SessionEntity();
|
SessionEntity sessionEntity = new SessionEntity();
|
||||||
sessionEntity.setSessionId(sessionId);
|
sessionEntity.setSessionId(sessionId);
|
||||||
sessionEntity.setPrincipalName(principalName);
|
sessionEntity.setPrincipalName(principalName);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
import stirling.software.SPDF.model.api.general.CropPdfForm;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
|
import stirling.software.SPDF.service.PostHogService;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -36,9 +37,13 @@ public class CropController {
|
|||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
|
private final PostHogService postHogService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public CropController(CustomPDDocumentFactory pdfDocumentFactory) {
|
public CropController(
|
||||||
|
CustomPDDocumentFactory pdfDocumentFactory, PostHogService postHogService) {
|
||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
|
this.postHogService = postHogService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.utils.GeneralUtils;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@Tag(name = "Settings", description = "Settings APIs")
|
||||||
|
@RequestMapping("/api/v1/settings")
|
||||||
|
@Hidden
|
||||||
|
public class SettingsController {
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@PostMapping("/update-enable-analytics")
|
||||||
|
@Hidden
|
||||||
|
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
||||||
|
if (!"undefined".equals(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||||
|
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
||||||
|
.body(
|
||||||
|
"Setting has already been set, To adjust please edit /config/settings.yml");
|
||||||
|
}
|
||||||
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
||||||
|
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
||||||
|
return ResponseEntity.ok("Updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,8 +60,6 @@ public class SplitPDFController {
|
|||||||
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
// PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
|
||||||
int totalPages = document.getNumberOfPages();
|
int totalPages = document.getNumberOfPages();
|
||||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||||
System.out.println(
|
|
||||||
pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
|
||||||
if (!pageNumbers.contains(totalPages - 1)) {
|
if (!pageNumbers.contains(totalPages - 1)) {
|
||||||
// Create a mutable ArrayList so we can add to it
|
// Create a mutable ArrayList so we can add to it
|
||||||
pageNumbers = new ArrayList<>(pageNumbers);
|
pageNumbers = new ArrayList<>(pageNumbers);
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import stirling.software.SPDF.config.PdfMetadataService;
|
|
||||||
import stirling.software.SPDF.model.PdfMetadata;
|
import stirling.software.SPDF.model.PdfMetadata;
|
||||||
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
|
||||||
|
import stirling.software.SPDF.service.PdfMetadataService;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -67,15 +67,6 @@ public class SplitPdfByChaptersController {
|
|||||||
}
|
}
|
||||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
// checks if the document is encrypted by an empty user password
|
|
||||||
if (sourceDocument.isEncrypted()) {
|
|
||||||
try {
|
|
||||||
sourceDocument.setAllSecurityToBeRemoved(true);
|
|
||||||
logger.info("Removing security from the source document ");
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Cannot decrypt the pdf");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();
|
||||||
|
|
||||||
if (outline == null) {
|
if (outline == null) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
@@ -40,6 +41,7 @@ import stirling.software.SPDF.model.api.user.UsernameAndPass;
|
|||||||
@Controller
|
@Controller
|
||||||
@Tag(name = "User", description = "User APIs")
|
@Tag(name = "User", description = "User APIs")
|
||||||
@RequestMapping("/api/v1/user")
|
@RequestMapping("/api/v1/user")
|
||||||
|
@Slf4j
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
@Autowired private UserService userService;
|
@Autowired private UserService userService;
|
||||||
@@ -191,13 +193,11 @@ public class UserController {
|
|||||||
Map<String, String[]> paramMap = request.getParameterMap();
|
Map<String, String[]> paramMap = request.getParameterMap();
|
||||||
Map<String, String> updates = new HashMap<>();
|
Map<String, String> updates = new HashMap<>();
|
||||||
|
|
||||||
System.out.println("Received parameter map: " + paramMap);
|
|
||||||
|
|
||||||
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
|
||||||
updates.put(entry.getKey(), entry.getValue()[0]);
|
updates.put(entry.getKey(), entry.getValue()[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("Processed updates: " + updates);
|
log.debug("Processed updates: " + updates);
|
||||||
|
|
||||||
// Assuming you have a method in userService to update the settings for a user
|
// Assuming you have a method in userService to update the settings for a user
|
||||||
userService.updateUserSettings(principal.getName(), updates);
|
userService.updateUserSettings(principal.getName(), updates);
|
||||||
@@ -209,7 +209,7 @@ public class UserController {
|
|||||||
@PostMapping("/admin/saveUser")
|
@PostMapping("/admin/saveUser")
|
||||||
public RedirectView saveUser(
|
public RedirectView saveUser(
|
||||||
@RequestParam(name = "username", required = true) String username,
|
@RequestParam(name = "username", required = true) String username,
|
||||||
@RequestParam(name = "password", required = true) String password,
|
@RequestParam(name = "password", required = false) String password,
|
||||||
@RequestParam(name = "role") String role,
|
@RequestParam(name = "role") String role,
|
||||||
@RequestParam(name = "authType") String authType,
|
@RequestParam(name = "authType") String authType,
|
||||||
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
@RequestParam(name = "forceChange", required = false, defaultValue = "false")
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
|
|
||||||
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@@ -29,7 +22,6 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
|
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||||
@@ -41,13 +33,6 @@ public class ConvertPDFToPDFA {
|
|||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class);
|
private static final Logger logger = LoggerFactory.getLogger(ConvertPDFToPDFA.class);
|
||||||
|
|
||||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ConvertPDFToPDFA(CustomPDDocumentFactory pdfDocumentFactory) {
|
|
||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a PDF to a PDF/A",
|
summary = "Convert a PDF to a PDF/A",
|
||||||
@@ -61,32 +46,7 @@ public class ConvertPDFToPDFA {
|
|||||||
// Convert MultipartFile to byte[]
|
// Convert MultipartFile to byte[]
|
||||||
byte[] pdfBytes = inputFile.getBytes();
|
byte[] pdfBytes = inputFile.getBytes();
|
||||||
|
|
||||||
// Load the PDF document
|
// Save the uploaded file to a temporary location
|
||||||
PDDocument document = pdfDocumentFactory.load(pdfBytes);
|
|
||||||
|
|
||||||
// Get the document catalog
|
|
||||||
PDDocumentCatalog catalog = document.getDocumentCatalog();
|
|
||||||
|
|
||||||
// Get the AcroForm
|
|
||||||
PDAcroForm acroForm = catalog.getAcroForm();
|
|
||||||
if (acroForm != null) {
|
|
||||||
// Remove signature fields safely
|
|
||||||
List<PDField> fieldsToRemove =
|
|
||||||
acroForm.getFields().stream()
|
|
||||||
.filter(field -> field instanceof PDSignatureField)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (!fieldsToRemove.isEmpty()) {
|
|
||||||
acroForm.flatten(fieldsToRemove, false);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
document.save(baos);
|
|
||||||
pdfBytes = baos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.close();
|
|
||||||
|
|
||||||
// Save the uploaded (and possibly modified) file to a temporary location
|
|
||||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||||
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
try (OutputStream outputStream = new FileOutputStream(tempInputFile.toFile())) {
|
||||||
outputStream.write(pdfBytes);
|
outputStream.write(pdfBytes);
|
||||||
@@ -95,28 +55,37 @@ public class ConvertPDFToPDFA {
|
|||||||
// Prepare the output file path
|
// Prepare the output file path
|
||||||
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
Path tempOutputFile = Files.createTempFile("output_", ".pdf");
|
||||||
|
|
||||||
// Prepare the OCRmyPDF command
|
// Prepare the ghostscript command
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
command.add("ocrmypdf");
|
command.add("gs");
|
||||||
command.add("--skip-text");
|
command.add("-dPDFA=" + ("pdfa".equals(outputFormat) ? "2" : "1"));
|
||||||
command.add("--tesseract-timeout=0");
|
command.add("-dNOPAUSE");
|
||||||
command.add("--output-type");
|
command.add("-dBATCH");
|
||||||
command.add(outputFormat.toString());
|
command.add("-sColorConversionStrategy=sRGB");
|
||||||
command.add(tempInputFile.toString());
|
command.add("-sDEVICE=pdfwrite");
|
||||||
|
command.add("-dPDFACompatibilityPolicy=2");
|
||||||
|
command.add("-o");
|
||||||
command.add(tempOutputFile.toString());
|
command.add(tempOutputFile.toString());
|
||||||
|
command.add(tempInputFile.toString());
|
||||||
|
|
||||||
ProcessExecutorResult returnCode =
|
ProcessExecutorResult returnCode =
|
||||||
ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF)
|
ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT)
|
||||||
.runCommandWithOutputHandling(command);
|
.runCommandWithOutputHandling(command);
|
||||||
|
|
||||||
|
if (returnCode.getRc() != 0) {
|
||||||
|
logger.info(
|
||||||
|
outputFormat + " conversion failed with return code: " + returnCode.getRc());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PDDocument doc = pdfDocumentFactory.load(tempOutputFile.toFile());
|
byte[] pdfBytesOutput = Files.readAllBytes(tempOutputFile);
|
||||||
// Return the optimized PDF as a response
|
// Return the optimized PDF as a response
|
||||||
String outputFilename =
|
String outputFilename =
|
||||||
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
Filenames.toSimpleFileName(inputFile.getOriginalFilename())
|
||||||
.replaceFirst("[.][^.]+$", "")
|
.replaceFirst("[.][^.]+$", "")
|
||||||
+ "_PDFA.pdf";
|
+ "_PDFA.pdf";
|
||||||
return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
|
pdfBytesOutput, outputFilename, MediaType.APPLICATION_PDF);
|
||||||
} finally {
|
} finally {
|
||||||
// Clean up the temporary files
|
// Clean up the temporary files
|
||||||
Files.deleteIfExists(tempInputFile);
|
Files.deleteIfExists(tempInputFile);
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ public class ExtractImagesController {
|
|||||||
MultipartFile file = request.getFileInput();
|
MultipartFile file = request.getFileInput();
|
||||||
String format = request.getFormat();
|
String format = request.getFormat();
|
||||||
boolean allowDuplicates = request.isAllowDuplicates();
|
boolean allowDuplicates = request.isAllowDuplicates();
|
||||||
System.out.println(
|
|
||||||
System.currentTimeMillis() + " file=" + file.getName() + ", format=" + format);
|
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
|
|
||||||
// Determine if multithreading should be used based on PDF size or number of pages
|
// Determine if multithreading should be used based on PDF size or number of pages
|
||||||
@@ -90,11 +88,15 @@ public class ExtractImagesController {
|
|||||||
// Iterate over each page
|
// Iterate over each page
|
||||||
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
for (int pgNum = 0; pgNum < document.getPages().getCount(); pgNum++) {
|
||||||
PDPage page = document.getPage(pgNum);
|
PDPage page = document.getPage(pgNum);
|
||||||
int pageNum = document.getPages().indexOf(page) + 1;
|
|
||||||
// Submit a task for processing each page
|
|
||||||
Future<Void> future =
|
Future<Void> future =
|
||||||
executor.submit(
|
executor.submit(
|
||||||
() -> {
|
() -> {
|
||||||
|
// Use the page number directly from the iterator, so no need to
|
||||||
|
// calculate manually
|
||||||
|
int pageNum = document.getPages().indexOf(page) + 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call the image extraction method for each page
|
||||||
extractImagesFromPage(
|
extractImagesFromPage(
|
||||||
page,
|
page,
|
||||||
format,
|
format,
|
||||||
@@ -103,9 +105,18 @@ public class ExtractImagesController {
|
|||||||
processedImages,
|
processedImages,
|
||||||
zos,
|
zos,
|
||||||
allowDuplicates);
|
allowDuplicates);
|
||||||
return null;
|
} catch (IOException e) {
|
||||||
|
// Log the error and continue processing other pages
|
||||||
|
logger.error(
|
||||||
|
"Error extracting images from page {}: {}",
|
||||||
|
pageNum,
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Callable requires a return type
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add the Future object to the list to track completion
|
||||||
futures.add(future);
|
futures.add(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,13 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/misc")
|
@RequestMapping("/api/v1/misc")
|
||||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||||
|
@Slf4j
|
||||||
public class PrintFileController {
|
public class PrintFileController {
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@@ -59,7 +61,7 @@ public class PrintFileController {
|
|||||||
new IllegalArgumentException(
|
new IllegalArgumentException(
|
||||||
"No matching printer found"));
|
"No matching printer found"));
|
||||||
|
|
||||||
System.out.println("Selected Printer: " + selectedService.getName());
|
log.info("Selected Printer: " + selectedService.getName());
|
||||||
|
|
||||||
if ("application/pdf".equals(contentType)) {
|
if ("application/pdf".equals(contentType)) {
|
||||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
|
||||||
|
import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/misc")
|
||||||
|
public class ReplaceAndInvertColorController {
|
||||||
|
|
||||||
|
private ReplaceAndInvertColorService replaceAndInvertColorService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ReplaceAndInvertColorController(
|
||||||
|
ReplaceAndInvertColorService replaceAndInvertColorService) {
|
||||||
|
this.replaceAndInvertColorService = replaceAndInvertColorService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(consumes = "multipart/form-data", value = "/replace-invert-pdf")
|
||||||
|
@Operation(
|
||||||
|
summary = "Replace-Invert Color PDF",
|
||||||
|
description =
|
||||||
|
"This endpoint accepts a PDF file and option of invert all colors or replace text and background colors. Input:PDF Output:PDF Type:SISO")
|
||||||
|
public ResponseEntity<InputStreamResource> replaceAndInvertColor(
|
||||||
|
@ModelAttribute ReplaceAndInvertColorRequest replaceAndInvertColorRequest)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
InputStreamResource resource =
|
||||||
|
replaceAndInvertColorService.replaceAndInvertColor(
|
||||||
|
replaceAndInvertColorRequest.getFileInput(),
|
||||||
|
replaceAndInvertColorRequest.getReplaceAndInvertOption(),
|
||||||
|
replaceAndInvertColorRequest.getHighContrastColorCombination(),
|
||||||
|
replaceAndInvertColorRequest.getBackGroundColor(),
|
||||||
|
replaceAndInvertColorRequest.getTextColor());
|
||||||
|
|
||||||
|
// Return the modified PDF as a downloadable file
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=inverted.pdf")
|
||||||
|
.contentType(MediaType.APPLICATION_PDF)
|
||||||
|
.body(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,7 +58,6 @@ public class RedactController {
|
|||||||
float customPadding = request.getCustomPadding();
|
float customPadding = request.getCustomPadding();
|
||||||
boolean convertPDFToImage = request.isConvertPDFToImage();
|
boolean convertPDFToImage = request.isConvertPDFToImage();
|
||||||
|
|
||||||
System.out.println(listOfTextString);
|
|
||||||
String[] listOfText = listOfTextString.split("\n");
|
String[] listOfText = listOfTextString.split("\n");
|
||||||
PDDocument document = pdfDocumentFactory.load(file);
|
PDDocument document = pdfDocumentFactory.load(file);
|
||||||
|
|
||||||
@@ -75,7 +74,6 @@ public class RedactController {
|
|||||||
|
|
||||||
for (String text : listOfText) {
|
for (String text : listOfText) {
|
||||||
text = text.trim();
|
text = text.trim();
|
||||||
System.out.println(text);
|
|
||||||
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);
|
||||||
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
List<PDFText> foundTexts = textFinder.getTextLocations(document);
|
||||||
redactFoundText(document, foundTexts, customPadding, redactColor);
|
redactFoundText(document, foundTexts, customPadding, redactColor);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import stirling.software.SPDF.utils.CheckProgramInstall;
|
|||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConverterWebController {
|
public class ConverterWebController {
|
||||||
|
|
||||||
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
|
||||||
@GetMapping("/book-to-pdf")
|
@GetMapping("/book-to-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String convertBookToPdfForm(Model model) {
|
public String convertBookToPdfForm(Model model) {
|
||||||
@@ -60,7 +60,7 @@ public class ConverterWebController {
|
|||||||
|
|
||||||
// PDF TO......
|
// PDF TO......
|
||||||
|
|
||||||
@ConditionalOnExpression("#{bookAndHtmlFormatsInstalled}")
|
@ConditionalOnExpression("${bookAndHtmlFormatsInstalled}")
|
||||||
@GetMapping("/pdf-to-book")
|
@GetMapping("/pdf-to-book")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String convertPdfToBookForm(Model model) {
|
public String convertPdfToBookForm(Model model) {
|
||||||
|
|||||||
@@ -108,6 +108,13 @@ public class GeneralWebController {
|
|||||||
return "split-pdf-by-sections";
|
return "split-pdf-by-sections";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/split-pdf-by-chapters")
|
||||||
|
@Hidden
|
||||||
|
public String splitPdfByChapters(Model model) {
|
||||||
|
model.addAttribute("currentPage", "split-pdf-by-chapters");
|
||||||
|
return "split-pdf-by-chapters";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/view-pdf")
|
@GetMapping("/view-pdf")
|
||||||
@Hidden
|
@Hidden
|
||||||
public String ViewPdfForm2(Model model) {
|
public String ViewPdfForm2(Model model) {
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ public class OtherWebController {
|
|||||||
return "misc/compress-pdf";
|
return "misc/compress-pdf";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/replace-and-invert-color-pdf")
|
||||||
|
@Hidden
|
||||||
|
public String replaceAndInvertColorPdfForm(Model model) {
|
||||||
|
model.addAttribute("currentPage", "replace-invert-color-pdf");
|
||||||
|
return "misc/replace-color";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/extract-image-scans")
|
@GetMapping("/extract-image-scans")
|
||||||
@Hidden
|
@Hidden
|
||||||
public ModelAndView extractImageScansForm() {
|
public ModelAndView extractImageScansForm() {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
@@ -24,6 +26,7 @@ import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
|||||||
@ConfigurationProperties(prefix = "")
|
@ConfigurationProperties(prefix = "")
|
||||||
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
|
@PropertySource(value = "file:./configs/settings.yml", factory = YamlPropertySourceFactory.class)
|
||||||
@Data
|
@Data
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
public class ApplicationProperties {
|
public class ApplicationProperties {
|
||||||
|
|
||||||
private Legal legal = new Legal();
|
private Legal legal = new Legal();
|
||||||
@@ -57,6 +60,7 @@ public class ApplicationProperties {
|
|||||||
private Boolean csrfDisabled;
|
private Boolean csrfDisabled;
|
||||||
private InitialLogin initialLogin = new InitialLogin();
|
private InitialLogin initialLogin = new InitialLogin();
|
||||||
private OAUTH2 oauth2 = new OAUTH2();
|
private OAUTH2 oauth2 = new OAUTH2();
|
||||||
|
private SAML saml = new SAML();
|
||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
private String loginMethod = "all";
|
private String loginMethod = "all";
|
||||||
@@ -67,6 +71,39 @@ public class ApplicationProperties {
|
|||||||
@ToString.Exclude private String password;
|
@ToString.Exclude private String password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class SAML {
|
||||||
|
private Boolean enabled = false;
|
||||||
|
private String entityId;
|
||||||
|
private String registrationId;
|
||||||
|
private String spBaseUrl;
|
||||||
|
private String idpMetadataLocation;
|
||||||
|
// private KeyStore keystore;
|
||||||
|
private String privateKeyLocation;
|
||||||
|
private String certificateLocation;
|
||||||
|
private String singleLogoutBinding;
|
||||||
|
private String singleLogoutResponseUri;
|
||||||
|
private String signingCertificate;
|
||||||
|
|
||||||
|
// @Data
|
||||||
|
// public static class KeyStore {
|
||||||
|
// private String keystoreLocation;
|
||||||
|
// private String keystorePassword;
|
||||||
|
// private String keyAlias;
|
||||||
|
// private String keyPassword;
|
||||||
|
// private String realmCertificateAlias;
|
||||||
|
//
|
||||||
|
// public Resource getKeystoreResource() {
|
||||||
|
// if (keystoreLocation.startsWith("classpath:")) {
|
||||||
|
// return new ClassPathResource(
|
||||||
|
// keystoreLocation.substring("classpath:".length()));
|
||||||
|
// } else {
|
||||||
|
// return new FileSystemResource(keystoreLocation);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class OAUTH2 {
|
public static class OAUTH2 {
|
||||||
private Boolean enabled = false;
|
private Boolean enabled = false;
|
||||||
@@ -136,6 +173,7 @@ public class ApplicationProperties {
|
|||||||
private boolean customHTMLFiles;
|
private boolean customHTMLFiles;
|
||||||
private String tessdataDir;
|
private String tessdataDir;
|
||||||
private Boolean enableAlphaFunctionality;
|
private Boolean enableAlphaFunctionality;
|
||||||
|
private String enableAnalytics;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -175,10 +213,12 @@ public class ApplicationProperties {
|
|||||||
@Data
|
@Data
|
||||||
public static class AutomaticallyGenerated {
|
public static class AutomaticallyGenerated {
|
||||||
@ToString.Exclude private String key;
|
@ToString.Exclude private String key;
|
||||||
|
private String UUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class EnterpriseEdition {
|
public static class EnterpriseEdition {
|
||||||
|
private boolean enabled;
|
||||||
@ToString.Exclude private String key;
|
@ToString.Exclude private String key;
|
||||||
private CustomMetadata customMetadata = new CustomMetadata();
|
private CustomMetadata customMetadata = new CustomMetadata();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package stirling.software.SPDF.model.api.misc;
|
||||||
|
|
||||||
|
public enum HighContrastColorCombination {
|
||||||
|
WHITE_TEXT_ON_BLACK,
|
||||||
|
BLACK_TEXT_ON_WHITE,
|
||||||
|
YELLOW_TEXT_ON_BLACK,
|
||||||
|
GREEN_TEXT_ON_BLACK,
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.SPDF.model.api.misc;
|
||||||
|
|
||||||
|
public enum ReplaceAndInvert {
|
||||||
|
HIGH_CONTRAST_COLOR,
|
||||||
|
CUSTOM_COLOR,
|
||||||
|
FULL_INVERSION,
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package stirling.software.SPDF.model.api.misc;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ReplaceAndInvertColorRequest extends PDFFile {
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "Replace and Invert color options of a pdf.",
|
||||||
|
allowableValues = {"HIGH_CONTRAST_COLOR", "CUSTOM_COLOR", "FULL_INVERSION"})
|
||||||
|
private ReplaceAndInvert replaceAndInvertOption;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"If HIGH_CONTRAST_COLOR option selected, then pick the default color option for text and background.",
|
||||||
|
allowableValues = {
|
||||||
|
"WHITE_TEXT_ON_BLACK",
|
||||||
|
"BLACK_TEXT_ON_WHITE",
|
||||||
|
"YELLOW_TEXT_ON_BLACK",
|
||||||
|
"GREEN_TEXT_ON_BLACK"
|
||||||
|
})
|
||||||
|
private HighContrastColorCombination highContrastColorCombination;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"If CUSTOM_COLOR option selected, then pick the custom color for background. "
|
||||||
|
+ "Expected color value should be 24bit decimal value of a color")
|
||||||
|
private String backGroundColor;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description =
|
||||||
|
"If CUSTOM_COLOR option selected, then pick the custom color for text. "
|
||||||
|
+ "Expected color value should be 24bit decimal value of a color")
|
||||||
|
private String textColor;
|
||||||
|
}
|
||||||
@@ -10,8 +10,10 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||||||
import org.apache.pdfbox.text.PDFTextStripper;
|
import org.apache.pdfbox.text.PDFTextStripper;
|
||||||
import org.apache.pdfbox.text.TextPosition;
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.PDFText;
|
import stirling.software.SPDF.model.PDFText;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class TextFinder extends PDFTextStripper {
|
public class TextFinder extends PDFTextStripper {
|
||||||
|
|
||||||
private final String searchText;
|
private final String searchText;
|
||||||
@@ -92,7 +94,7 @@ public class TextFinder extends PDFTextStripper {
|
|||||||
|
|
||||||
public List<PDFText> getTextLocations(PDDocument document) throws Exception {
|
public List<PDFText> getTextLocations(PDDocument document) throws Exception {
|
||||||
this.getText(document);
|
this.getText(document);
|
||||||
System.out.println(
|
log.debug(
|
||||||
"Found "
|
"Found "
|
||||||
+ textOccurrences.size()
|
+ textOccurrences.size()
|
||||||
+ " occurrences of '"
|
+ " occurrences of '"
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ import java.io.InputStream;
|
|||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
import org.apache.pdfbox.Loader;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.PdfMetadataService;
|
|
||||||
import stirling.software.SPDF.model.PdfMetadata;
|
import stirling.software.SPDF.model.PdfMetadata;
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class CustomPDDocumentFactory {
|
public class CustomPDDocumentFactory {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CustomPDDocumentFactory.class);
|
||||||
|
|
||||||
private final PdfMetadataService pdfMetadataService;
|
private final PdfMetadataService pdfMetadataService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -71,6 +74,7 @@ public class CustomPDDocumentFactory {
|
|||||||
public PDDocument load(byte[] input) throws IOException {
|
public PDDocument load(byte[] input) throws IOException {
|
||||||
PDDocument document = Loader.loadPDF(input);
|
PDDocument document = Loader.loadPDF(input);
|
||||||
pdfMetadataService.setDefaultMetadata(document);
|
pdfMetadataService.setDefaultMetadata(document);
|
||||||
|
removezeropassword(document);
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,5 +100,17 @@ public class CustomPDDocumentFactory {
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PDDocument removezeropassword(PDDocument document) throws IOException {
|
||||||
|
if (document.isEncrypted()) {
|
||||||
|
try {
|
||||||
|
logger.info("Removing security from the source document");
|
||||||
|
document.setAllSecurityToBeRemoved(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Cannot decrypt the pdf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
// Add other load methods as needed, following the same pattern
|
// Add other load methods as needed, following the same pattern
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.micrometer.core.instrument.search.Search;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class MetricsAggregatorService {
|
||||||
|
|
||||||
|
private final MeterRegistry meterRegistry;
|
||||||
|
private final PostHogService postHogService;
|
||||||
|
private final Map<String, Double> lastSentMetrics = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public MetricsAggregatorService(MeterRegistry meterRegistry, PostHogService postHogService) {
|
||||||
|
this.meterRegistry = meterRegistry;
|
||||||
|
this.postHogService = postHogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 900000) // Run every 15 minutes
|
||||||
|
public void aggregateAndSendMetrics() {
|
||||||
|
Map<String, Object> metrics = new HashMap<>();
|
||||||
|
Search.in(meterRegistry)
|
||||||
|
.name("http.requests")
|
||||||
|
.counters()
|
||||||
|
.forEach(
|
||||||
|
counter -> {
|
||||||
|
String key =
|
||||||
|
String.format(
|
||||||
|
"http_requests_%s_%s",
|
||||||
|
counter.getId().getTag("method"),
|
||||||
|
counter.getId().getTag("uri").replace("/", "_"));
|
||||||
|
|
||||||
|
double currentCount = counter.count();
|
||||||
|
double lastCount = lastSentMetrics.getOrDefault(key, 0.0);
|
||||||
|
double difference = currentCount - lastCount;
|
||||||
|
|
||||||
|
if (difference > 0) {
|
||||||
|
metrics.put(key, difference);
|
||||||
|
lastSentMetrics.put(key, currentCount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send aggregated metrics to PostHog
|
||||||
|
if (!metrics.isEmpty()) {
|
||||||
|
postHogService.captureEvent("aggregated_metrics", metrics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package stirling.software.SPDF.config;
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
@@ -15,16 +15,16 @@ import stirling.software.SPDF.model.PdfMetadata;
|
|||||||
public class PdfMetadataService {
|
public class PdfMetadataService {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final String appVersion;
|
private final String stirlingPDFLabel;
|
||||||
private final UserServiceInterface userService;
|
private final UserServiceInterface userService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public PdfMetadataService(
|
public PdfMetadataService(
|
||||||
ApplicationProperties applicationProperties,
|
ApplicationProperties applicationProperties,
|
||||||
@Qualifier("appVersion") String appVersion,
|
@Qualifier("StirlingPDFLabel") String stirlingPDFLabel,
|
||||||
@Autowired(required = false) UserServiceInterface userService) {
|
@Autowired(required = false) UserServiceInterface userService) {
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.appVersion = appVersion;
|
this.stirlingPDFLabel = stirlingPDFLabel;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,51 +59,40 @@ public class PdfMetadataService {
|
|||||||
|
|
||||||
private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
|
private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
|
||||||
|
|
||||||
String creator = "Stirling-PDF";
|
String creator = stirlingPDFLabel;
|
||||||
|
|
||||||
// if (applicationProperties
|
if (applicationProperties
|
||||||
// .getEnterpriseEdition()
|
.getEnterpriseEdition()
|
||||||
// .getCustomMetadata()
|
.getCustomMetadata()
|
||||||
// .isAutoUpdateMetadata()) {
|
.isAutoUpdateMetadata()) {
|
||||||
|
|
||||||
// producer =
|
creator = applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
|
||||||
//
|
pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
|
||||||
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer();
|
}
|
||||||
// creator =
|
|
||||||
// applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator();
|
|
||||||
// title = applicationProperties.getEnterpriseEdition().getCustomMetadata().getTitle();
|
|
||||||
|
|
||||||
// if ("{filename}".equals(title)) {
|
pdf.getDocumentInformation().setCreator(creator);
|
||||||
// title = "Filename"; // Replace with actual filename logic
|
|
||||||
// } else if ("{unchanged}".equals(title)) {
|
|
||||||
// title = pdfMetadata.getTitle(); // Keep the original title
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pdf.getDocumentInformation().setCreator(creator + " " + appVersion);
|
|
||||||
pdf.getDocumentInformation().setCreationDate(Calendar.getInstance());
|
pdf.getDocumentInformation().setCreationDate(Calendar.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
|
private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
|
||||||
String producer = "Stirling-PDF";
|
|
||||||
String title = pdfMetadata.getTitle();
|
String title = pdfMetadata.getTitle();
|
||||||
pdf.getDocumentInformation().setTitle(title);
|
pdf.getDocumentInformation().setTitle(title);
|
||||||
pdf.getDocumentInformation().setProducer(producer + " " + appVersion);
|
pdf.getDocumentInformation().setProducer(stirlingPDFLabel);
|
||||||
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
|
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
|
||||||
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
|
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
|
||||||
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
|
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
|
||||||
|
|
||||||
String author = pdfMetadata.getAuthor();
|
String author = pdfMetadata.getAuthor();
|
||||||
// if (applicationProperties
|
if (applicationProperties
|
||||||
// .getEnterpriseEdition()
|
.getEnterpriseEdition()
|
||||||
// .getCustomMetadata()
|
.getCustomMetadata()
|
||||||
// .isAutoUpdateMetadata()) {
|
.isAutoUpdateMetadata()) {
|
||||||
// author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
|
author = applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor();
|
||||||
|
|
||||||
// if (userService != null) {
|
if (userService != null) {
|
||||||
// author = author.replace("username", userService.getCurrentUsername());
|
author = author.replace("username", userService.getCurrentUsername());
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
pdf.getDocumentInformation().setAuthor(author);
|
pdf.getDocumentInformation().setAuthor(author);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
379
src/main/java/stirling/software/SPDF/service/PostHogService.java
Normal file
379
src/main/java/stirling/software/SPDF/service/PostHogService.java
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.management.*;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.posthog.java.PostHog;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PostHogService {
|
||||||
|
private final PostHog postHog;
|
||||||
|
private final String uniqueId;
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PostHogService(
|
||||||
|
PostHog postHog,
|
||||||
|
@Qualifier("UUID") String uuid,
|
||||||
|
ApplicationProperties applicationProperties) {
|
||||||
|
this.postHog = postHog;
|
||||||
|
this.uniqueId = uuid;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
captureSystemInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void captureSystemInfo() {
|
||||||
|
if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
postHog.capture(uniqueId, "system_info_captured", captureServerMetrics());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Handle exceptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void captureEvent(String eventName, Map<String, Object> properties) {
|
||||||
|
if (!Boolean.getBoolean(applicationProperties.getSystem().getEnableAnalytics())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
postHog.capture(uniqueId, eventName, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> captureServerMetrics() {
|
||||||
|
Map<String, Object> metrics = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// System info
|
||||||
|
metrics.put("os_name", System.getProperty("os.name"));
|
||||||
|
metrics.put("os_version", System.getProperty("os.version"));
|
||||||
|
metrics.put("java_version", System.getProperty("java.version"));
|
||||||
|
metrics.put("user_name", System.getProperty("user.name"));
|
||||||
|
metrics.put("user_home", System.getProperty("user.home"));
|
||||||
|
metrics.put("user_dir", System.getProperty("user.dir"));
|
||||||
|
|
||||||
|
// CPU and Memory
|
||||||
|
metrics.put("cpu_cores", Runtime.getRuntime().availableProcessors());
|
||||||
|
metrics.put("total_memory", Runtime.getRuntime().totalMemory());
|
||||||
|
metrics.put("free_memory", Runtime.getRuntime().freeMemory());
|
||||||
|
|
||||||
|
// Network and Server Identity
|
||||||
|
InetAddress localHost = InetAddress.getLocalHost();
|
||||||
|
metrics.put("ip_address", localHost.getHostAddress());
|
||||||
|
metrics.put("hostname", localHost.getHostName());
|
||||||
|
metrics.put("mac_address", getMacAddress());
|
||||||
|
|
||||||
|
// JVM info
|
||||||
|
metrics.put("jvm_vendor", System.getProperty("java.vendor"));
|
||||||
|
metrics.put("jvm_version", System.getProperty("java.vm.version"));
|
||||||
|
|
||||||
|
// Locale and Timezone
|
||||||
|
metrics.put("system_language", System.getProperty("user.language"));
|
||||||
|
metrics.put("system_country", System.getProperty("user.country"));
|
||||||
|
metrics.put("timezone", TimeZone.getDefault().getID());
|
||||||
|
metrics.put("locale", Locale.getDefault().toString());
|
||||||
|
|
||||||
|
// Disk info
|
||||||
|
File root = new File(".");
|
||||||
|
metrics.put("total_disk_space", root.getTotalSpace());
|
||||||
|
metrics.put("free_disk_space", root.getFreeSpace());
|
||||||
|
|
||||||
|
// Process info
|
||||||
|
metrics.put("process_id", ProcessHandle.current().pid());
|
||||||
|
|
||||||
|
// JVM metrics
|
||||||
|
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
|
||||||
|
metrics.put("jvm_uptime_ms", runtimeMXBean.getUptime());
|
||||||
|
metrics.put("jvm_start_time", runtimeMXBean.getStartTime());
|
||||||
|
|
||||||
|
// Memory metrics
|
||||||
|
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
|
||||||
|
metrics.put("heap_memory_usage", memoryMXBean.getHeapMemoryUsage().getUsed());
|
||||||
|
metrics.put("non_heap_memory_usage", memoryMXBean.getNonHeapMemoryUsage().getUsed());
|
||||||
|
|
||||||
|
// CPU metrics
|
||||||
|
OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
|
||||||
|
metrics.put("system_load_average", osMXBean.getSystemLoadAverage());
|
||||||
|
|
||||||
|
// Thread metrics
|
||||||
|
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
|
||||||
|
metrics.put("thread_count", threadMXBean.getThreadCount());
|
||||||
|
metrics.put("daemon_thread_count", threadMXBean.getDaemonThreadCount());
|
||||||
|
metrics.put("peak_thread_count", threadMXBean.getPeakThreadCount());
|
||||||
|
|
||||||
|
// Garbage collection metrics
|
||||||
|
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||||
|
metrics.put("gc_" + gcBean.getName() + "_count", gcBean.getCollectionCount());
|
||||||
|
metrics.put("gc_" + gcBean.getName() + "_time", gcBean.getCollectionTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network interfaces
|
||||||
|
metrics.put("network_interfaces", getNetworkInterfacesInfo());
|
||||||
|
|
||||||
|
// Docker detection and stats
|
||||||
|
boolean isDocker = isRunningInDocker();
|
||||||
|
metrics.put("is_docker", isDocker);
|
||||||
|
if (isDocker) {
|
||||||
|
metrics.put("docker_metrics", getDockerMetrics());
|
||||||
|
}
|
||||||
|
metrics.put("application_properties", captureApplicationProperties());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
metrics.put("error", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRunningInDocker() {
|
||||||
|
return Files.exists(Paths.get("/.dockerenv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getDockerMetrics() {
|
||||||
|
Map<String, Object> dockerMetrics = new HashMap<>();
|
||||||
|
|
||||||
|
// Network-related Docker info
|
||||||
|
dockerMetrics.put("docker_network_mode", System.getenv("DOCKER_NETWORK_MODE"));
|
||||||
|
|
||||||
|
// Container name (if set)
|
||||||
|
String containerName = System.getenv("CONTAINER_NAME");
|
||||||
|
if (containerName != null && !containerName.isEmpty()) {
|
||||||
|
dockerMetrics.put("container_name", containerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Docker compose information
|
||||||
|
String composeProject = System.getenv("COMPOSE_PROJECT_NAME");
|
||||||
|
String composeService = System.getenv("COMPOSE_SERVICE_NAME");
|
||||||
|
if (composeProject != null && composeService != null) {
|
||||||
|
dockerMetrics.put("compose_project", composeProject);
|
||||||
|
dockerMetrics.put("compose_service", composeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kubernetes-specific info (if running in K8s)
|
||||||
|
String k8sPodName = System.getenv("KUBERNETES_POD_NAME");
|
||||||
|
if (k8sPodName != null) {
|
||||||
|
dockerMetrics.put("k8s_pod_name", k8sPodName);
|
||||||
|
dockerMetrics.put("k8s_namespace", System.getenv("KUBERNETES_NAMESPACE"));
|
||||||
|
dockerMetrics.put("k8s_node_name", System.getenv("KUBERNETES_NODE_NAME"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// New environment variables
|
||||||
|
dockerMetrics.put("version_tag", System.getenv("VERSION_TAG"));
|
||||||
|
dockerMetrics.put("docker_enable_security", System.getenv("DOCKER_ENABLE_SECURITY"));
|
||||||
|
dockerMetrics.put("fat_docker", System.getenv("FAT_DOCKER"));
|
||||||
|
|
||||||
|
return dockerMetrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addIfNotEmpty(Map<String, Object> map, String key, Object value) {
|
||||||
|
if (value != null) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
String strValue = (String) value;
|
||||||
|
if (!StringUtils.isBlank(strValue)) {
|
||||||
|
map.put(key, strValue.trim());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> captureApplicationProperties() {
|
||||||
|
Map<String, Object> properties = new HashMap<>();
|
||||||
|
|
||||||
|
// Capture Legal properties
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"legal_termsAndConditions",
|
||||||
|
applicationProperties.getLegal().getTermsAndConditions());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"legal_privacyPolicy",
|
||||||
|
applicationProperties.getLegal().getPrivacyPolicy());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"legal_accessibilityStatement",
|
||||||
|
applicationProperties.getLegal().getAccessibilityStatement());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"legal_cookiePolicy",
|
||||||
|
applicationProperties.getLegal().getCookiePolicy());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties, "legal_impressum", applicationProperties.getLegal().getImpressum());
|
||||||
|
|
||||||
|
// Capture Security properties
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_enableLogin",
|
||||||
|
applicationProperties.getSecurity().getEnableLogin());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_csrfDisabled",
|
||||||
|
applicationProperties.getSecurity().getCsrfDisabled());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_loginAttemptCount",
|
||||||
|
applicationProperties.getSecurity().getLoginAttemptCount());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_loginResetTimeMinutes",
|
||||||
|
applicationProperties.getSecurity().getLoginResetTimeMinutes());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_loginMethod",
|
||||||
|
applicationProperties.getSecurity().getLoginMethod());
|
||||||
|
|
||||||
|
// Capture OAuth2 properties (excluding sensitive information)
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_oauth2_enabled",
|
||||||
|
applicationProperties.getSecurity().getOauth2().getEnabled());
|
||||||
|
if (applicationProperties.getSecurity().getOauth2().getEnabled()) {
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_oauth2_autoCreateUser",
|
||||||
|
applicationProperties.getSecurity().getOauth2().getAutoCreateUser());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_oauth2_blockRegistration",
|
||||||
|
applicationProperties.getSecurity().getOauth2().getBlockRegistration());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_oauth2_useAsUsername",
|
||||||
|
applicationProperties.getSecurity().getOauth2().getUseAsUsername());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"security_oauth2_provider",
|
||||||
|
applicationProperties.getSecurity().getOauth2().getProvider());
|
||||||
|
}
|
||||||
|
// Capture System properties
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"system_defaultLocale",
|
||||||
|
applicationProperties.getSystem().getDefaultLocale());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"system_googlevisibility",
|
||||||
|
applicationProperties.getSystem().getGooglevisibility());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties, "system_showUpdate", applicationProperties.getSystem().isShowUpdate());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"system_showUpdateOnlyAdmin",
|
||||||
|
applicationProperties.getSystem().getShowUpdateOnlyAdmin());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"system_customHTMLFiles",
|
||||||
|
applicationProperties.getSystem().isCustomHTMLFiles());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"system_tessdataDir",
|
||||||
|
applicationProperties.getSystem().getTessdataDir());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"system_enableAlphaFunctionality",
|
||||||
|
applicationProperties.getSystem().getEnableAlphaFunctionality());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"system_enableAnalytics",
|
||||||
|
applicationProperties.getSystem().getEnableAnalytics());
|
||||||
|
|
||||||
|
// Capture UI properties
|
||||||
|
addIfNotEmpty(properties, "ui_appName", applicationProperties.getUi().getAppName());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"ui_homeDescription",
|
||||||
|
applicationProperties.getUi().getHomeDescription());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties, "ui_appNameNavbar", applicationProperties.getUi().getAppNameNavbar());
|
||||||
|
|
||||||
|
// Capture Metrics properties
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties, "metrics_enabled", applicationProperties.getMetrics().getEnabled());
|
||||||
|
|
||||||
|
// Capture EnterpriseEdition properties
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"enterpriseEdition_enabled",
|
||||||
|
applicationProperties.getEnterpriseEdition().isEnabled());
|
||||||
|
if (applicationProperties.getEnterpriseEdition().isEnabled()) {
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"enterpriseEdition_customMetadata_autoUpdateMetadata",
|
||||||
|
applicationProperties
|
||||||
|
.getEnterpriseEdition()
|
||||||
|
.getCustomMetadata()
|
||||||
|
.isAutoUpdateMetadata());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"enterpriseEdition_customMetadata_author",
|
||||||
|
applicationProperties.getEnterpriseEdition().getCustomMetadata().getAuthor());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"enterpriseEdition_customMetadata_creator",
|
||||||
|
applicationProperties.getEnterpriseEdition().getCustomMetadata().getCreator());
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"enterpriseEdition_customMetadata_producer",
|
||||||
|
applicationProperties.getEnterpriseEdition().getCustomMetadata().getProducer());
|
||||||
|
}
|
||||||
|
// Capture AutoPipeline properties
|
||||||
|
addIfNotEmpty(
|
||||||
|
properties,
|
||||||
|
"autoPipeline_outputFolder",
|
||||||
|
applicationProperties.getAutoPipeline().getOutputFolder());
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMacAddress() {
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> networkInterfaces =
|
||||||
|
NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (networkInterfaces.hasMoreElements()) {
|
||||||
|
NetworkInterface ni = networkInterfaces.nextElement();
|
||||||
|
byte[] hardwareAddress = ni.getHardwareAddress();
|
||||||
|
if (hardwareAddress != null) {
|
||||||
|
String[] hexadecimal = new String[hardwareAddress.length];
|
||||||
|
for (int i = 0; i < hardwareAddress.length; i++) {
|
||||||
|
hexadecimal[i] = String.format("%02X", hardwareAddress[i]);
|
||||||
|
}
|
||||||
|
return String.join("-", hexadecimal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Handle exception
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> getNetworkInterfacesInfo() {
|
||||||
|
Map<String, String> interfacesInfo = new HashMap<>();
|
||||||
|
try {
|
||||||
|
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (nets.hasMoreElements()) {
|
||||||
|
NetworkInterface netint = nets.nextElement();
|
||||||
|
interfacesInfo.put(netint.getName(), netint.getDisplayName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
interfacesInfo.put("error", e.getMessage());
|
||||||
|
}
|
||||||
|
return interfacesInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package stirling.software.SPDF.service.misc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.Factories.ReplaceAndInvertColorFactory;
|
||||||
|
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||||
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||||
|
import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ReplaceAndInvertColorService {
|
||||||
|
private ReplaceAndInvertColorFactory replaceAndInvertColorFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ReplaceAndInvertColorService(ReplaceAndInvertColorFactory replaceAndInvertColorFactory) {
|
||||||
|
this.replaceAndInvertColorFactory = replaceAndInvertColorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStreamResource replaceAndInvertColor(
|
||||||
|
MultipartFile file,
|
||||||
|
ReplaceAndInvert replaceAndInvertOption,
|
||||||
|
HighContrastColorCombination highContrastColorCombination,
|
||||||
|
String backGroundColor,
|
||||||
|
String textColor)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
ReplaceAndInvertColorStrategy replaceColorStrategy =
|
||||||
|
replaceAndInvertColorFactory.replaceAndInvert(
|
||||||
|
file,
|
||||||
|
replaceAndInvertOption,
|
||||||
|
highContrastColorCombination,
|
||||||
|
backGroundColor,
|
||||||
|
textColor);
|
||||||
|
|
||||||
|
return replaceColorStrategy.replace();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,18 +5,28 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.SimpleFileVisitor;
|
import java.nio.file.SimpleFileVisitor;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.simpleyaml.configuration.file.YamlFile;
|
||||||
|
import org.simpleyaml.configuration.file.YamlFileWrapper;
|
||||||
|
import org.simpleyaml.configuration.implementation.SimpleYamlImplementation;
|
||||||
|
import org.simpleyaml.configuration.implementation.snakeyaml.lib.DumperOptions;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -262,4 +272,81 @@ public class GeneralUtils {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isValidUUID(String uuid) {
|
||||||
|
if (uuid == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
UUID.fromString(uuid);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveKeyToConfig(String id, String key) throws IOException {
|
||||||
|
saveKeyToConfig(id, key, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveKeyToConfig(String id, String key, boolean autoGenerated)
|
||||||
|
throws IOException {
|
||||||
|
Path path = Paths.get("configs", "settings.yml"); // Target the configs/settings.yml
|
||||||
|
|
||||||
|
final YamlFile settingsYml = new YamlFile(path.toFile());
|
||||||
|
DumperOptions yamlOptionssettingsYml =
|
||||||
|
((SimpleYamlImplementation) settingsYml.getImplementation()).getDumperOptions();
|
||||||
|
yamlOptionssettingsYml.setSplitLines(false);
|
||||||
|
|
||||||
|
settingsYml.loadWithComments();
|
||||||
|
|
||||||
|
YamlFileWrapper writer = settingsYml.path(id).set(key);
|
||||||
|
if (autoGenerated) {
|
||||||
|
writer.comment("# Automatically Generated Settings (Do Not Edit Directly)");
|
||||||
|
}
|
||||||
|
settingsYml.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateMachineFingerprint() {
|
||||||
|
try {
|
||||||
|
// Get the MAC address
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
InetAddress ip = InetAddress.getLocalHost();
|
||||||
|
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
|
||||||
|
|
||||||
|
if (network == null) {
|
||||||
|
Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (networks.hasMoreElements()) {
|
||||||
|
NetworkInterface net = networks.nextElement();
|
||||||
|
byte[] mac = net.getHardwareAddress();
|
||||||
|
if (mac != null) {
|
||||||
|
for (int i = 0; i < mac.length; i++) {
|
||||||
|
sb.append(String.format("%02X", mac[i]));
|
||||||
|
}
|
||||||
|
break; // Use the first network interface with a MAC address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
byte[] mac = network.getHardwareAddress();
|
||||||
|
if (mac != null) {
|
||||||
|
for (int i = 0; i < mac.length; i++) {
|
||||||
|
sb.append(String.format("%02X", mac[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the MAC address for privacy and consistency
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder fingerprint = new StringBuilder();
|
||||||
|
for (byte b : hash) {
|
||||||
|
fingerprint.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fingerprint.toString();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "GenericID";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,6 @@ public class PDFToFile {
|
|||||||
Files.deleteIfExists(tempInputFile);
|
Files.deleteIfExists(tempInputFile);
|
||||||
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());
|
||||||
}
|
}
|
||||||
System.out.println("fileBytes=" + fileBytes.length);
|
|
||||||
return WebResponseUtils.bytesToWebResponse(
|
return WebResponseUtils.bytesToWebResponse(
|
||||||
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public class RequestUriUtils {
|
|||||||
|| requestURI.startsWith(contextPath + "/public/")
|
|| requestURI.startsWith(contextPath + "/public/")
|
||||||
|| requestURI.startsWith(contextPath + "/pdfjs/")
|
|| requestURI.startsWith(contextPath + "/pdfjs/")
|
||||||
|| requestURI.startsWith(contextPath + "/login")
|
|| requestURI.startsWith(contextPath + "/login")
|
||||||
|
|| requestURI.startsWith(contextPath + "/error")
|
||||||
|| requestURI.endsWith(".svg")
|
|| requestURI.endsWith(".svg")
|
||||||
|| requestURI.endsWith(".png")
|
|| requestURI.endsWith(".png")
|
||||||
|| requestURI.endsWith(".ico")
|
|| requestURI.endsWith(".ico")
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package stirling.software.SPDF.utils.misc;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageTree;
|
||||||
|
import org.apache.pdfbox.pdmodel.font.*;
|
||||||
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||||
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||||
|
|
||||||
|
public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
|
||||||
|
|
||||||
|
private String textColor;
|
||||||
|
private String backgroundColor;
|
||||||
|
private HighContrastColorCombination highContrastColorCombination;
|
||||||
|
|
||||||
|
public CustomColorReplaceStrategy(
|
||||||
|
MultipartFile file,
|
||||||
|
ReplaceAndInvert replaceAndInvert,
|
||||||
|
String textColor,
|
||||||
|
String backgroundColor,
|
||||||
|
HighContrastColorCombination highContrastColorCombination) {
|
||||||
|
super(file, replaceAndInvert);
|
||||||
|
this.textColor = textColor;
|
||||||
|
this.backgroundColor = backgroundColor;
|
||||||
|
this.highContrastColorCombination = highContrastColorCombination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStreamResource replace() throws IOException {
|
||||||
|
|
||||||
|
// If ReplaceAndInvert is HighContrastColor option, then get the colors of text and
|
||||||
|
// background from static
|
||||||
|
if (replaceAndInvert == ReplaceAndInvert.HIGH_CONTRAST_COLOR) {
|
||||||
|
String[] colors =
|
||||||
|
HighContrastColorReplaceDecider.getColors(
|
||||||
|
replaceAndInvert, highContrastColorCombination);
|
||||||
|
this.textColor = colors[0];
|
||||||
|
this.backgroundColor = colors[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary file, with the original filename from the multipart file
|
||||||
|
File file = File.createTempFile("temp", getFileInput().getOriginalFilename());
|
||||||
|
|
||||||
|
// Transfer the content of the multipart file to the file
|
||||||
|
getFileInput().transferTo(file);
|
||||||
|
|
||||||
|
try (PDDocument document = Loader.loadPDF(file)) {
|
||||||
|
|
||||||
|
PDPageTree pages = document.getPages();
|
||||||
|
|
||||||
|
for (PDPage page : pages) {
|
||||||
|
|
||||||
|
PdfTextStripperCustom pdfTextStripperCustom = new PdfTextStripperCustom();
|
||||||
|
// Get text positions
|
||||||
|
List<List<TextPosition>> charactersByArticle =
|
||||||
|
pdfTextStripperCustom.processPageCustom(page);
|
||||||
|
|
||||||
|
// Begin a new content stream
|
||||||
|
PDPageContentStream contentStream =
|
||||||
|
new PDPageContentStream(
|
||||||
|
document, page, PDPageContentStream.AppendMode.APPEND, true, true);
|
||||||
|
|
||||||
|
// Set the new text color
|
||||||
|
contentStream.setNonStrokingColor(Color.decode(this.textColor));
|
||||||
|
|
||||||
|
// Draw the text with the new color
|
||||||
|
for (List<TextPosition> textPositions : charactersByArticle) {
|
||||||
|
for (TextPosition text : textPositions) {
|
||||||
|
// Move to the text position
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.newLineAtOffset(
|
||||||
|
text.getX(), page.getMediaBox().getHeight() - text.getY());
|
||||||
|
PDFont font = null;
|
||||||
|
String unicodeText = text.getUnicode();
|
||||||
|
try {
|
||||||
|
font = PDFontFactory.createFont(text.getFont().getCOSObject());
|
||||||
|
} catch (IOException io) {
|
||||||
|
System.out.println("Primary font not found, using fallback font.");
|
||||||
|
font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||||
|
}
|
||||||
|
// if a character is not supported by font, then look for supported font
|
||||||
|
try {
|
||||||
|
byte[] bytes = font.encode(unicodeText);
|
||||||
|
} catch (IOException io) {
|
||||||
|
System.out.println("text could not be encoded ");
|
||||||
|
font = checkSupportedFontForCharacter(unicodeText);
|
||||||
|
} catch (IllegalArgumentException ie) {
|
||||||
|
System.out.println("text not supported by font ");
|
||||||
|
font = checkSupportedFontForCharacter(unicodeText);
|
||||||
|
} finally {
|
||||||
|
// if any other font is not supported, then replace default character *
|
||||||
|
if (font == null) {
|
||||||
|
font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||||
|
unicodeText = "*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentStream.setFont(font, text.getFontSize());
|
||||||
|
contentStream.showText(unicodeText);
|
||||||
|
contentStream.endText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Close the content stream
|
||||||
|
contentStream.close();
|
||||||
|
// Use a content stream to overlay the background color
|
||||||
|
try (PDPageContentStream contentStreamBg =
|
||||||
|
new PDPageContentStream(
|
||||||
|
document,
|
||||||
|
page,
|
||||||
|
PDPageContentStream.AppendMode.PREPEND,
|
||||||
|
true,
|
||||||
|
true)) {
|
||||||
|
// Set background color (e.g., light yellow)
|
||||||
|
contentStreamBg.setNonStrokingColor(Color.decode(this.backgroundColor));
|
||||||
|
contentStreamBg.addRect(
|
||||||
|
0, 0, page.getMediaBox().getWidth(), page.getMediaBox().getHeight());
|
||||||
|
contentStreamBg.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save the modified PDF to a ByteArrayOutputStream
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
document.save(byteArrayOutputStream);
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
// Prepare the modified PDF for download
|
||||||
|
ByteArrayInputStream inputStream =
|
||||||
|
new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||||
|
InputStreamResource resource = new InputStreamResource(inputStream);
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PDFont checkSupportedFontForCharacter(String unicodeText) {
|
||||||
|
|
||||||
|
Set<String> fonts = Standard14Fonts.getNames();
|
||||||
|
for (String font : fonts) {
|
||||||
|
Standard14Fonts.FontName fontName = Standard14Fonts.getMappedFontName(font);
|
||||||
|
PDFont currentFont = new PDType1Font(fontName);
|
||||||
|
try {
|
||||||
|
byte[] bytes = currentFont.encode(unicodeText);
|
||||||
|
return currentFont;
|
||||||
|
} catch (IOException io) {
|
||||||
|
System.out.println("text could not be encoded ");
|
||||||
|
} catch (IllegalArgumentException ie) {
|
||||||
|
System.out.println("text not supported by font ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package stirling.software.SPDF.utils.misc;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
|
||||||
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||||
|
|
||||||
|
public class HighContrastColorReplaceDecider {
|
||||||
|
|
||||||
|
// To decide the text and background colors for High contrast color option for replace-invert
|
||||||
|
// color feature
|
||||||
|
public static String[] getColors(
|
||||||
|
ReplaceAndInvert replaceAndInvert,
|
||||||
|
HighContrastColorCombination highContrastColorCombination) {
|
||||||
|
|
||||||
|
if (highContrastColorCombination == HighContrastColorCombination.BLACK_TEXT_ON_WHITE) {
|
||||||
|
return new String[] {"0", "16777215"};
|
||||||
|
} else if (highContrastColorCombination
|
||||||
|
== HighContrastColorCombination.GREEN_TEXT_ON_BLACK) {
|
||||||
|
return new String[] {"65280", "0"};
|
||||||
|
} else if (highContrastColorCombination
|
||||||
|
== HighContrastColorCombination.WHITE_TEXT_ON_BLACK) {
|
||||||
|
return new String[] {"16777215", "0"};
|
||||||
|
} else if (highContrastColorCombination
|
||||||
|
== HighContrastColorCombination.YELLOW_TEXT_ON_BLACK) {
|
||||||
|
|
||||||
|
return new String[] {"16776960", "0"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package stirling.software.SPDF.utils.misc;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.Loader;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||||
|
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||||
|
|
||||||
|
public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {
|
||||||
|
|
||||||
|
public InvertFullColorStrategy(MultipartFile file, ReplaceAndInvert replaceAndInvert) {
|
||||||
|
super(file, replaceAndInvert);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStreamResource replace() throws IOException {
|
||||||
|
|
||||||
|
// Create a temporary file, with the original filename from the multipart file
|
||||||
|
File file = File.createTempFile("temp", getFileInput().getOriginalFilename());
|
||||||
|
|
||||||
|
// Transfer the content of the multipart file to the file
|
||||||
|
getFileInput().transferTo(file);
|
||||||
|
|
||||||
|
// Load the uploaded PDF
|
||||||
|
PDDocument document = Loader.loadPDF(file);
|
||||||
|
|
||||||
|
// Render each page and invert colors
|
||||||
|
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||||
|
for (int page = 0; page < document.getNumberOfPages(); page++) {
|
||||||
|
BufferedImage image =
|
||||||
|
pdfRenderer.renderImageWithDPI(page, 300); // Render page at 300 DPI
|
||||||
|
|
||||||
|
// Invert the colors
|
||||||
|
invertImageColors(image);
|
||||||
|
|
||||||
|
// Create a new PDPage from the inverted image
|
||||||
|
PDPage pdPage = document.getPage(page);
|
||||||
|
PDImageXObject pdImage =
|
||||||
|
PDImageXObject.createFromFileByContent(
|
||||||
|
convertToBufferedImageTpFile(image), document);
|
||||||
|
|
||||||
|
PDPageContentStream contentStream =
|
||||||
|
new PDPageContentStream(
|
||||||
|
document, pdPage, PDPageContentStream.AppendMode.OVERWRITE, true);
|
||||||
|
contentStream.drawImage(
|
||||||
|
pdImage,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
pdPage.getMediaBox().getWidth(),
|
||||||
|
pdPage.getMediaBox().getHeight());
|
||||||
|
contentStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the modified PDF to a ByteArrayOutputStream
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
document.save(byteArrayOutputStream);
|
||||||
|
document.close();
|
||||||
|
|
||||||
|
// Prepare the modified PDF for download
|
||||||
|
ByteArrayInputStream inputStream =
|
||||||
|
new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||||
|
InputStreamResource resource = new InputStreamResource(inputStream);
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to invert image colors
|
||||||
|
private void invertImageColors(BufferedImage image) {
|
||||||
|
int width = image.getWidth();
|
||||||
|
int height = image.getHeight();
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
int rgba = image.getRGB(x, y);
|
||||||
|
Color color = new Color(rgba, true);
|
||||||
|
Color invertedColor =
|
||||||
|
new Color(
|
||||||
|
255 - color.getRed(),
|
||||||
|
255 - color.getGreen(),
|
||||||
|
255 - color.getBlue());
|
||||||
|
image.setRGB(x, y, invertedColor.getRGB());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to convert BufferedImage to InputStream
|
||||||
|
private File convertToBufferedImageTpFile(BufferedImage image) throws IOException {
|
||||||
|
File file = new File("image.png");
|
||||||
|
ImageIO.write(image, "png", file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package stirling.software.SPDF.utils.misc;
|
||||||
|
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
|
import org.apache.pdfbox.text.PDFTextStripperByArea;
|
||||||
|
import org.apache.pdfbox.text.TextPosition;
|
||||||
|
|
||||||
|
public class PdfTextStripperCustom extends PDFTextStripperByArea {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @throws IOException If there is an error loading properties.
|
||||||
|
*/
|
||||||
|
public PdfTextStripperCustom() throws IOException {}
|
||||||
|
|
||||||
|
// To process the page text using stripper and returns the TextPosition and its values
|
||||||
|
public List<List<TextPosition>> processPageCustom(PDPage page) throws IOException {
|
||||||
|
|
||||||
|
addRegion(
|
||||||
|
"wholePage",
|
||||||
|
new Rectangle2D.Float(
|
||||||
|
page.getMediaBox().getLowerLeftX(),
|
||||||
|
page.getMediaBox().getLowerLeftY(),
|
||||||
|
page.getMediaBox().getWidth(),
|
||||||
|
page.getMediaBox().getHeight()));
|
||||||
|
extractRegions(page);
|
||||||
|
|
||||||
|
List<List<TextPosition>> textPositions = getCharactersByArticle();
|
||||||
|
|
||||||
|
return textPositions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package stirling.software.SPDF.utils.misc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
|
import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public abstract class ReplaceAndInvertColorStrategy extends PDFFile {
|
||||||
|
|
||||||
|
protected ReplaceAndInvert replaceAndInvert;
|
||||||
|
|
||||||
|
public ReplaceAndInvertColorStrategy(MultipartFile file, ReplaceAndInvert replaceAndInvert) {
|
||||||
|
setFileInput(file);
|
||||||
|
setReplaceAndInvert(replaceAndInvert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract InputStreamResource replace() throws IOException;
|
||||||
|
}
|
||||||
@@ -27,9 +27,9 @@ server.servlet.context-path=${SYSTEM_ROOTURIPATH:/}
|
|||||||
|
|
||||||
spring.devtools.restart.enabled=true
|
spring.devtools.restart.enabled=true
|
||||||
spring.devtools.livereload.enabled=true
|
spring.devtools.livereload.enabled=true
|
||||||
|
|
||||||
spring.thymeleaf.encoding=UTF-8
|
spring.thymeleaf.encoding=UTF-8
|
||||||
|
|
||||||
|
spring.web.resources.mime-mappings.webmanifest=application/manifest+json
|
||||||
|
|
||||||
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
|
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
|
||||||
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
|
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/
|
||||||
@@ -41,7 +41,7 @@ spring.datasource.username=sa
|
|||||||
spring.datasource.password=
|
spring.datasource.password=
|
||||||
spring.h2.console.enabled=false
|
spring.h2.console.enabled=false
|
||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
server.servlet.session.timeout: 30m
|
||||||
# Change the default URL path for OpenAPI JSON
|
# Change the default URL path for OpenAPI JSON
|
||||||
springdoc.api-docs.path=/v1/api-docs
|
springdoc.api-docs.path=/v1/api-docs
|
||||||
|
|
||||||
@@ -49,3 +49,5 @@ springdoc.api-docs.path=/v1/api-docs
|
|||||||
springdoc.swagger-ui.url=/v1/api-docs
|
springdoc.swagger-ui.url=/v1/api-docs
|
||||||
|
|
||||||
|
|
||||||
|
posthog.api.key=phc_fiR65u5j6qmXTYL56MNrLZSWqLaDW74OrZH0Insd2xq
|
||||||
|
posthog.host=https://eu.i.posthog.com
|
||||||
@@ -76,6 +76,8 @@ donate=تبرع
|
|||||||
color=لون
|
color=لون
|
||||||
sponsor=راعٍ
|
sponsor=راعٍ
|
||||||
info=معلومات
|
info=معلومات
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=ضبط حجم/مقياس الصفحة
|
|||||||
home.scalePages.desc=تغيير حجم/مقياس الصفحة و/أو محتواها.
|
home.scalePages.desc=تغيير حجم/مقياس الصفحة و/أو محتواها.
|
||||||
scalePages.tags=تغيير الحجم,تعديل,الأبعاد,تكييف
|
scalePages.tags=تغيير الحجم,تعديل,الأبعاد,تكييف
|
||||||
|
|
||||||
home.pipeline.title=خط الأنابيب (متقدم)
|
home.pipeline.title=خط الأنابيب
|
||||||
home.pipeline.desc=تشغيل إجراءات متعددة على ملفات PDF عن طريق تحديد نصوص خط الأنابيب
|
home.pipeline.desc=تشغيل إجراءات متعددة على ملفات PDF عن طريق تحديد نصوص خط الأنابيب
|
||||||
pipeline.tags=أتمتة,تسلسل,مبرمج,معالجة دفعات
|
pipeline.tags=أتمتة,تسلسل,مبرمج,معالجة دفعات
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=إزالة الصورة
|
|||||||
home.removeImagePdf.desc=إزالة الصورة من PDF لتقليل حجم الملف
|
home.removeImagePdf.desc=إزالة الصورة من PDF لتقليل حجم الملف
|
||||||
removeImagePdf.tags=إزالة الصورة,عمليات الصفحة,الخلفية,جانب الخادم
|
removeImagePdf.tags=إزالة الصورة,عمليات الصفحة,الخلفية,جانب الخادم
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=تغيير
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF إلى PDF/A
|
pdfToPDFA.title=PDF إلى PDF/A
|
||||||
pdfToPDFA.header=PDF إلى PDF/A
|
pdfToPDFA.header=PDF إلى PDF/A
|
||||||
pdfToPDFA.credit=تستخدم هذه الخدمة OCRmyPDF لتحويل PDF/A.
|
pdfToPDFA.credit=تستخدم هذه الخدمة ghostscript لتحويل PDF/A.
|
||||||
pdfToPDFA.submit=تحويل
|
pdfToPDFA.submit=تحويل
|
||||||
pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد
|
pdfToPDFA.tip=لا يعمل حاليًا لمدخلات متعددة في وقت واحد
|
||||||
pdfToPDFA.outputFormat=تنسيق الإخراج
|
pdfToPDFA.outputFormat=تنسيق الإخراج
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Направете дарение
|
|||||||
color=Цвят
|
color=Цвят
|
||||||
sponsor=Спонсор
|
sponsor=Спонсор
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Промени
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF към PDF/A
|
pdfToPDFA.title=PDF към PDF/A
|
||||||
pdfToPDFA.header=PDF към PDF/A
|
pdfToPDFA.header=PDF към PDF/A
|
||||||
pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване.
|
pdfToPDFA.credit=Тази услуга използва ghostscript за PDF/A преобразуване.
|
||||||
pdfToPDFA.submit=Преобразуване
|
pdfToPDFA.submit=Преобразуване
|
||||||
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
||||||
pdfToPDFA.outputFormat=Изходен формат
|
pdfToPDFA.outputFormat=Изходен формат
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Adjust page size/scale
|
|||||||
home.scalePages.desc=Change the size/scale of page and/or its contents.
|
home.scalePages.desc=Change the size/scale of page and/or its contents.
|
||||||
scalePages.tags=resize,modify,dimension,adapt
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (Advanced)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
||||||
pipeline.tags=automate,sequence,scripted,batch-process
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Canvia
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF a PDF/A
|
pdfToPDFA.title=PDF a PDF/A
|
||||||
pdfToPDFA.header=PDF a PDF/A
|
pdfToPDFA.header=PDF a PDF/A
|
||||||
pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A
|
pdfToPDFA.credit=Utilitza ghostscript per la conversió a PDF/A
|
||||||
pdfToPDFA.submit=Converteix
|
pdfToPDFA.submit=Converteix
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Přispějte
|
|||||||
color=Barva
|
color=Barva
|
||||||
sponsor=Sponzor
|
sponsor=Sponzor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Upravit velikost/škálu stránky
|
|||||||
home.scalePages.desc=Změnit velikost/škálu stránky a/nebo její obsah.
|
home.scalePages.desc=Změnit velikost/škálu stránky a/nebo její obsah.
|
||||||
scalePages.tags=změnit velikost,upravit,rozměr,přizpůsobit
|
scalePages.tags=změnit velikost,upravit,rozměr,přizpůsobit
|
||||||
|
|
||||||
home.pipeline.title=Potrubí (Pokročilé)
|
home.pipeline.title=Potrubí
|
||||||
home.pipeline.desc=Spustit více akcí na PDF s definicí skriptů potrubí
|
home.pipeline.desc=Spustit více akcí na PDF s definicí skriptů potrubí
|
||||||
pipeline.tags=automatizovat,sekvence,skriptované,dávkové zpracování
|
pipeline.tags=automatizovat,sekvence,skriptované,dávkové zpracování
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Změnit
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF na PDF/A
|
pdfToPDFA.title=PDF na PDF/A
|
||||||
pdfToPDFA.header=PDF na PDF/A
|
pdfToPDFA.header=PDF na PDF/A
|
||||||
pdfToPDFA.credit=Tato služba používá OCRmyPDF pro konverzi do formátu PDF/A
|
pdfToPDFA.credit=Tato služba používá ghostscript pro konverzi do formátu PDF/A
|
||||||
pdfToPDFA.submit=Převést
|
pdfToPDFA.submit=Převést
|
||||||
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
|
pdfToPDFA.tip=V současné době nepracuje pro více vstupů najednou
|
||||||
pdfToPDFA.outputFormat=Výstupní formát
|
pdfToPDFA.outputFormat=Výstupní formát
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donér
|
|||||||
color=Farve
|
color=Farve
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Fjern billede
|
|||||||
home.removeImagePdf.desc=Fjern billede fra PDF for at reducere filstørrelse
|
home.removeImagePdf.desc=Fjern billede fra PDF for at reducere filstørrelse
|
||||||
removeImagePdf.tags=Fjern Billede,Sideoperationer,Back end,server side
|
removeImagePdf.tags=Fjern Billede,Sideoperationer,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Ændre
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF Til PDF/A
|
pdfToPDFA.title=PDF Til PDF/A
|
||||||
pdfToPDFA.header=PDF Til PDF/A
|
pdfToPDFA.header=PDF Til PDF/A
|
||||||
pdfToPDFA.credit=Denne tjeneste bruger OCRmyPDF til PDF/A-konvertering
|
pdfToPDFA.credit=Denne tjeneste bruger ghostscript til PDF/A-konvertering
|
||||||
pdfToPDFA.submit=Konvertér
|
pdfToPDFA.submit=Konvertér
|
||||||
pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang
|
pdfToPDFA.tip=Fungerer i øjeblikket ikke for flere input på én gang
|
||||||
pdfToPDFA.outputFormat=Outputformat
|
pdfToPDFA.outputFormat=Outputformat
|
||||||
|
|||||||
@@ -76,11 +76,13 @@ donate=Spenden
|
|||||||
color=Farbe
|
color=Farbe
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Informationen
|
info=Informationen
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Datenschutz
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=AGB
|
||||||
legal.accessibility=Accessibility
|
legal.accessibility=Barrierefreiheit
|
||||||
legal.cookie=Cookie Policy
|
legal.cookie=Cookie-Richtlinie
|
||||||
legal.impressum=Impressum
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Seitengröße/Skalierung anpassen
|
|||||||
home.scalePages.desc=Größe/Skalierung der Seite und/oder des Inhalts ändern
|
home.scalePages.desc=Größe/Skalierung der Seite und/oder des Inhalts ändern
|
||||||
scalePages.tags=größe ändern,ändern,dimensionieren,anpassen
|
scalePages.tags=größe ändern,ändern,dimensionieren,anpassen
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (Fortgeschritten)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Mehrere Aktionen auf ein PDF anwenden, definiert durch ein Pipeline Skript
|
home.pipeline.desc=Mehrere Aktionen auf ein PDF anwenden, definiert durch ein Pipeline Skript
|
||||||
pipeline.tags=automatisieren,sequenzieren,skriptgesteuert,batch prozess
|
pipeline.tags=automatisieren,sequenzieren,skriptgesteuert,batch prozess
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Bild entfernen
|
|||||||
home.removeImagePdf.desc=Bild aus PDF entfernen, um die Dateigröße zu verringern
|
home.removeImagePdf.desc=Bild aus PDF entfernen, um die Dateigröße zu verringern
|
||||||
removeImagePdf.tags=bild entfernen,seitenoperationen,back end,server side
|
removeImagePdf.tags=bild entfernen,seitenoperationen,back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Ändern
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF zu PDF/A
|
pdfToPDFA.title=PDF zu PDF/A
|
||||||
pdfToPDFA.header=PDF zu PDF/A
|
pdfToPDFA.header=PDF zu PDF/A
|
||||||
pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung
|
pdfToPDFA.credit=Dieser Dienst verwendet ghostscript für die PDF/A-Konvertierung
|
||||||
pdfToPDFA.submit=Konvertieren
|
pdfToPDFA.submit=Konvertieren
|
||||||
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
|
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
|
||||||
pdfToPDFA.outputFormat=Ausgabeformat
|
pdfToPDFA.outputFormat=Ausgabeformat
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Δωρισε
|
|||||||
color=Χρώμα
|
color=Χρώμα
|
||||||
sponsor=Yποστηρικτής
|
sponsor=Yποστηρικτής
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Αλλαγή
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF σε PDF/A
|
pdfToPDFA.title=PDF σε PDF/A
|
||||||
pdfToPDFA.header=PDF σε PDF/A
|
pdfToPDFA.header=PDF σε PDF/A
|
||||||
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF για PDF/A μετατροπή
|
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί ghostscript για PDF/A μετατροπή
|
||||||
pdfToPDFA.submit=Μετατροπή
|
pdfToPDFA.submit=Μετατροπή
|
||||||
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
pro=Pro
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -108,8 +111,24 @@ pipelineOptions.pipelineHeader=Pipeline:
|
|||||||
pipelineOptions.saveButton=Download
|
pipelineOptions.saveButton=Download
|
||||||
pipelineOptions.validateButton=Validate
|
pipelineOptions.validateButton=Validate
|
||||||
|
|
||||||
|
########################
|
||||||
|
# ENTERPRISE EDITION #
|
||||||
|
########################
|
||||||
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Analytics #
|
||||||
|
#################
|
||||||
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
|
analytics.enable=Enable analytics
|
||||||
|
analytics.disable=Disable analytics
|
||||||
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -126,6 +145,7 @@ navbar.sections.convertFrom=Convert from PDF
|
|||||||
navbar.sections.security=Sign & Security
|
navbar.sections.security=Sign & Security
|
||||||
navbar.sections.advance=Advanced
|
navbar.sections.advance=Advanced
|
||||||
navbar.sections.edit=View & Edit
|
navbar.sections.edit=View & Edit
|
||||||
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -223,6 +243,8 @@ database.fileNotFound=File not found
|
|||||||
database.fileNullOrEmpty=File must not be null or empty
|
database.fileNullOrEmpty=File must not be null or empty
|
||||||
database.failedImportFile=Failed to import file
|
database.failedImportFile=Failed to import file
|
||||||
|
|
||||||
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
@@ -380,7 +402,7 @@ home.scalePages.title=Adjust page size/scale
|
|||||||
home.scalePages.desc=Change the size/scale of a page and/or its contents.
|
home.scalePages.desc=Change the size/scale of a page and/or its contents.
|
||||||
scalePages.tags=resize,modify,dimension,adapt
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (Advanced)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
home.pipeline.desc=Run multiple actions on PDFs by defining pipeline scripts
|
||||||
pipeline.tags=automate,sequence,scripted,batch-process
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
@@ -481,6 +503,31 @@ home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
|||||||
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.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Advanced Colour options
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Advanced Colour options
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
# WEB PAGES #
|
# WEB PAGES #
|
||||||
@@ -504,7 +551,10 @@ login.oauth2AccessDenied=Access Denied
|
|||||||
login.oauth2InvalidTokenResponse=Invalid Token Response
|
login.oauth2InvalidTokenResponse=Invalid Token Response
|
||||||
login.oauth2InvalidIdToken=Invalid Id Token
|
login.oauth2InvalidIdToken=Invalid Id Token
|
||||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||||
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
@@ -1023,7 +1073,7 @@ changeMetadata.submit=Change
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF To PDF/A
|
pdfToPDFA.title=PDF To PDF/A
|
||||||
pdfToPDFA.header=PDF To PDF/A
|
pdfToPDFA.header=PDF To PDF/A
|
||||||
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion
|
||||||
pdfToPDFA.submit=Convert
|
pdfToPDFA.submit=Convert
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
@@ -1132,7 +1182,9 @@ licenses.license=Licence
|
|||||||
survey.nav=Survey
|
survey.nav=Survey
|
||||||
survey.title=Stirling-PDF Survey
|
survey.title=Stirling-PDF Survey
|
||||||
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||||
survey.please=Please consider taking our survey!
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
|
survey.please=Please consider taking our survey to have input on the future of Stirling-PDF!
|
||||||
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||||
survey.button=Take Survey
|
survey.button=Take Survey
|
||||||
survey.dontShowAgain=Don't show again
|
survey.dontShowAgain=Don't show again
|
||||||
@@ -1157,3 +1209,17 @@ removeImage.title=Remove image
|
|||||||
removeImage.header=Remove image
|
removeImage.header=Remove image
|
||||||
removeImage.removeImage=Remove image
|
removeImage.removeImage=Remove image
|
||||||
removeImage.submit=Remove image
|
removeImage.submit=Remove image
|
||||||
|
|
||||||
|
|
||||||
|
splitByChapters.title=Split PDF by Chapters
|
||||||
|
splitByChapters.header=Split PDF by Chapters
|
||||||
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Change
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF To PDF/A
|
pdfToPDFA.title=PDF To PDF/A
|
||||||
pdfToPDFA.header=PDF To PDF/A
|
pdfToPDFA.header=PDF To PDF/A
|
||||||
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
pdfToPDFA.credit=This service uses ghostscript for PDF/A conversion
|
||||||
pdfToPDFA.submit=Convert
|
pdfToPDFA.submit=Convert
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donar
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Patrocinador
|
sponsor=Patrocinador
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Escalar/ajustar tamaño de página
|
|||||||
home.scalePages.desc=Escalar/cambiar el tamaño de una pagina y/o su contenido
|
home.scalePages.desc=Escalar/cambiar el tamaño de una pagina y/o su contenido
|
||||||
scalePages.tags=cambiar tamaño,modificar,dimensionar,adaptar
|
scalePages.tags=cambiar tamaño,modificar,dimensionar,adaptar
|
||||||
|
|
||||||
home.pipeline.title=Secuencia (Avanzado)
|
home.pipeline.title=Secuencia
|
||||||
home.pipeline.desc=Ejecutar varias tareas a PDFs definiendo una secuencia de comandos
|
home.pipeline.desc=Ejecutar varias tareas a PDFs definiendo una secuencia de comandos
|
||||||
pipeline.tags=automatizar,secuencia,con script,proceso por lotes
|
pipeline.tags=automatizar,secuencia,con script,proceso por lotes
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Eliminar imagen
|
|||||||
home.removeImagePdf.desc=Eliminar imagen del PDF> para reducir el tamaño de archivo
|
home.removeImagePdf.desc=Eliminar imagen del PDF> para reducir el tamaño de archivo
|
||||||
removeImagePdf.tags=Eliminar imagen,Operaciones de página,Back end,lado del servidor
|
removeImagePdf.tags=Eliminar imagen,Operaciones de página,Back end,lado del servidor
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Cambiar
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF a PDF/A
|
pdfToPDFA.title=PDF a PDF/A
|
||||||
pdfToPDFA.header=PDF a PDF/A
|
pdfToPDFA.header=PDF a PDF/A
|
||||||
pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A
|
pdfToPDFA.credit=Este servicio usa ghostscript para la conversión a PDF/A
|
||||||
pdfToPDFA.submit=Convertir
|
pdfToPDFA.submit=Convertir
|
||||||
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
||||||
pdfToPDFA.outputFormat=Formato de salida
|
pdfToPDFA.outputFormat=Formato de salida
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Aldatu
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDFa PDF/A bihurtu
|
pdfToPDFA.title=PDFa PDF/A bihurtu
|
||||||
pdfToPDFA.header=PDFa PDF/A bihurtu
|
pdfToPDFA.header=PDFa PDF/A bihurtu
|
||||||
pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko
|
pdfToPDFA.credit=Zerbitzu honek ghostscript erabiltzen du PDFak PDF/A bihurtzeko
|
||||||
pdfToPDFA.submit=Bihurtu
|
pdfToPDFA.submit=Bihurtu
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Faire un don
|
|||||||
color=Couleur
|
color=Couleur
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Ajuster l’échelle ou la taille
|
|||||||
home.scalePages.desc=Modifiez la taille ou l’échelle d’une page et/ou de son contenu.
|
home.scalePages.desc=Modifiez la taille ou l’échelle d’une page et/ou de son contenu.
|
||||||
scalePages.tags=ajuster,redimensionner,resize,modify,dimension,adapt
|
scalePages.tags=ajuster,redimensionner,resize,modify,dimension,adapt
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (avancé)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Exécutez plusieurs actions sur les PDF en définissant des scripts de pipeline.
|
home.pipeline.desc=Exécutez plusieurs actions sur les PDF en définissant des scripts de pipeline.
|
||||||
pipeline.tags=automatiser,séquencer,automate,sequence,scripted,batch-process
|
pipeline.tags=automatiser,séquencer,automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Modifier
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF en PDF/A
|
pdfToPDFA.title=PDF en PDF/A
|
||||||
pdfToPDFA.header=PDF en PDF/A
|
pdfToPDFA.header=PDF en PDF/A
|
||||||
pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion en PDF/A.
|
pdfToPDFA.credit=Ce service utilise ghostscript pour la conversion en PDF/A.
|
||||||
pdfToPDFA.submit=Convertir
|
pdfToPDFA.submit=Convertir
|
||||||
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
||||||
pdfToPDFA.outputFormat=Format de sortie
|
pdfToPDFA.outputFormat=Format de sortie
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Síntiúis
|
|||||||
color=Dath
|
color=Dath
|
||||||
sponsor=Urraitheoir
|
sponsor=Urraitheoir
|
||||||
info=Eolas
|
info=Eolas
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Athrú
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF Go PDF/A
|
pdfToPDFA.title=PDF Go PDF/A
|
||||||
pdfToPDFA.header=PDF Go PDF/A
|
pdfToPDFA.header=PDF Go PDF/A
|
||||||
pdfToPDFA.credit=Úsáideann an tseirbhís seo OCRmyPDF chun PDF/A a thiontú
|
pdfToPDFA.credit=Úsáideann an tseirbhís seo ghostscript chun PDF/A a thiontú
|
||||||
pdfToPDFA.submit=Tiontaigh
|
pdfToPDFA.submit=Tiontaigh
|
||||||
pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna
|
pdfToPDFA.tip=Faoi láthair ní oibríonn sé le haghaidh ionchuir iolracha ag an am céanna
|
||||||
pdfToPDFA.outputFormat=Formáid aschuir
|
pdfToPDFA.outputFormat=Formáid aschuir
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=पृष्ठ
|
||||||
|
pages=पृष्ठों
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=बदलें
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF से PDF/A में
|
pdfToPDFA.title=PDF से PDF/A में
|
||||||
pdfToPDFA.header=PDF से PDF/A में
|
pdfToPDFA.header=PDF से PDF/A में
|
||||||
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए OCRmyPDF का उपयोग किया जाता है।
|
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए ghostscript का उपयोग किया जाता है।
|
||||||
pdfToPDFA.submit=परिवर्तित करें
|
pdfToPDFA.submit=परिवर्तित करें
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Doniraj
|
|||||||
color=Boja
|
color=Boja
|
||||||
sponsor=Sponzor
|
sponsor=Sponzor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Prilagodite veličinu/razmjer stranice
|
|||||||
home.scalePages.desc=Promijenite veličinu/razmjer stranice i/ili njezin sadržaj.
|
home.scalePages.desc=Promijenite veličinu/razmjer stranice i/ili njezin sadržaj.
|
||||||
scalePages.tags=izmjena,modifikacija,dimenzija,adaptacija
|
scalePages.tags=izmjena,modifikacija,dimenzija,adaptacija
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (Advanced)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Izvršite više radnji na PDF-ovima definiranjem skripti u pipeline-u
|
home.pipeline.desc=Izvršite više radnji na PDF-ovima definiranjem skripti u pipeline-u
|
||||||
pipeline.tags=automatizacija,sekvenciranje,skriptirano,batch-process
|
pipeline.tags=automatizacija,sekvenciranje,skriptirano,batch-process
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Promijeniti
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF u PDF/A
|
pdfToPDFA.title=PDF u PDF/A
|
||||||
pdfToPDFA.header=PDF u PDF/A
|
pdfToPDFA.header=PDF u PDF/A
|
||||||
pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za PDF/A pretvorbu
|
pdfToPDFA.credit=Ova usluga koristi ghostscript za PDF/A pretvorbu
|
||||||
pdfToPDFA.submit=Pretvoriti
|
pdfToPDFA.submit=Pretvoriti
|
||||||
pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom
|
pdfToPDFA.tip=Trenutno ne radi za više unosa odjednom
|
||||||
pdfToPDFA.outputFormat=Izlazni format
|
pdfToPDFA.outputFormat=Izlazni format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Módosítás
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF >> PDF/A
|
pdfToPDFA.title=PDF >> PDF/A
|
||||||
pdfToPDFA.header=PDF >> PDF/A
|
pdfToPDFA.header=PDF >> PDF/A
|
||||||
pdfToPDFA.credit=Ez a szolgáltatás az OCRmyPDF-t használja a PDF/A konverzióhoz
|
pdfToPDFA.credit=Ez a szolgáltatás az ghostscript-t használja a PDF/A konverzióhoz
|
||||||
pdfToPDFA.submit=Konvertálás
|
pdfToPDFA.submit=Konvertálás
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Menyesuaikan ukuran/skala halaman
|
|||||||
home.scalePages.desc=Mengubah ukuran/skala halaman dan/atau isinya.
|
home.scalePages.desc=Mengubah ukuran/skala halaman dan/atau isinya.
|
||||||
scalePages.tags=mengubah ukuran, memodifikasi, dimensi, mengadaptasi
|
scalePages.tags=mengubah ukuran, memodifikasi, dimensi, mengadaptasi
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (Lanjutan)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Menjalankan beberapa tindakan pada PDF dengan mendefinisikan skrip pipeline
|
home.pipeline.desc=Menjalankan beberapa tindakan pada PDF dengan mendefinisikan skrip pipeline
|
||||||
pipeline.tags=mengotomatiskan, mengurutkan, menulis, proses batch
|
pipeline.tags=mengotomatiskan, mengurutkan, menulis, proses batch
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Ganti
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF Ke PDF/A
|
pdfToPDFA.title=PDF Ke PDF/A
|
||||||
pdfToPDFA.header=PDF ke PDF/A
|
pdfToPDFA.header=PDF ke PDF/A
|
||||||
pdfToPDFA.credit=Layanan ini menggunakan OCRmyPDF untuk konversi PDF/A.
|
pdfToPDFA.credit=Layanan ini menggunakan ghostscript untuk konversi PDF/A.
|
||||||
pdfToPDFA.submit=Konversi
|
pdfToPDFA.submit=Konversi
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ selectFillter=-- Seleziona --
|
|||||||
pageNum=Numero pagina
|
pageNum=Numero pagina
|
||||||
sizes.small=Piccolo
|
sizes.small=Piccolo
|
||||||
sizes.medium=Medio
|
sizes.medium=Medio
|
||||||
sizes.large=Largo
|
sizes.large=Grande
|
||||||
sizes.x-large=Extra-Large
|
sizes.x-large=Extra-Large
|
||||||
error.pdfPassword=Il documento PDF è protetto da password e la password non è stata fornita oppure non era corretta
|
error.pdfPassword=Il documento PDF è protetto da password e la password non è stata fornita oppure non era corretta
|
||||||
delete=Elimina
|
delete=Elimina
|
||||||
@@ -50,7 +50,7 @@ WorkInProgess=Lavori in corso, potrebbe non funzionare o essere difettoso, segna
|
|||||||
poweredBy=Alimentato da
|
poweredBy=Alimentato da
|
||||||
yes=Si
|
yes=Si
|
||||||
no=No
|
no=No
|
||||||
changedCredsMessage=Credenziali cambiate!
|
changedCredsMessage=Credenziali modificate!
|
||||||
notAuthenticatedMessage=Utente non autenticato.
|
notAuthenticatedMessage=Utente non autenticato.
|
||||||
userNotFoundMessage=Utente non trovato.
|
userNotFoundMessage=Utente non trovato.
|
||||||
incorrectPasswordMessage=La password attuale non è corretta.
|
incorrectPasswordMessage=La password attuale non è corretta.
|
||||||
@@ -76,12 +76,14 @@ donate=Donazione
|
|||||||
color=Colore
|
color=Colore
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Informativa sulla privacy
|
legal.privacy=Informativa sulla privacy
|
||||||
legal.terms=Termini e Condizioni
|
legal.terms=Termini e Condizioni
|
||||||
legal.accessibility=Accessibilità
|
legal.accessibility=Accessibilità
|
||||||
legal.cookie=Informativa sui cookie
|
legal.cookie=Informativa sui cookie
|
||||||
legal.impressum=Impressum
|
legal.impressum=Informazioni legali
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -194,7 +196,7 @@ adminUserSettings.extraApiUser=API utente limitato aggiuntivo
|
|||||||
adminUserSettings.webOnlyUser=Utente solo Web
|
adminUserSettings.webOnlyUser=Utente solo Web
|
||||||
adminUserSettings.demoUser=Utente demo (nessuna impostazione personalizzata)
|
adminUserSettings.demoUser=Utente demo (nessuna impostazione personalizzata)
|
||||||
adminUserSettings.internalApiUser=API utente interna
|
adminUserSettings.internalApiUser=API utente interna
|
||||||
adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso
|
adminUserSettings.forceChange=Forza l'utente a cambiare nome utente/password all'accesso
|
||||||
adminUserSettings.submit=Salva utente
|
adminUserSettings.submit=Salva utente
|
||||||
adminUserSettings.changeUserRole=Cambia il ruolo dell'utente
|
adminUserSettings.changeUserRole=Cambia il ruolo dell'utente
|
||||||
adminUserSettings.authenticated=Autenticato
|
adminUserSettings.authenticated=Autenticato
|
||||||
@@ -226,7 +228,7 @@ database.failedImportFile=Importazione file non riuscita
|
|||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
home.desc=La tua pagina self-hostata per gestire qualsiasi PDF.
|
home.desc=La tua pagina auto-gestita per modificare qualsiasi PDF.
|
||||||
home.searchBar=Cerca funzionalità...
|
home.searchBar=Cerca funzionalità...
|
||||||
|
|
||||||
|
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Regola le dimensioni/scala della pagina
|
|||||||
home.scalePages.desc=Modificare le dimensioni/scala della pagina e/o dei suoi contenuti.
|
home.scalePages.desc=Modificare le dimensioni/scala della pagina e/o dei suoi contenuti.
|
||||||
scalePages.tags=ridimensionare,modificare,dimensionare,adattare
|
scalePages.tags=ridimensionare,modificare,dimensionare,adattare
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (avanzato)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Esegui più azioni sui PDF definendo script di pipeline
|
home.pipeline.desc=Esegui più azioni sui PDF definendo script di pipeline
|
||||||
pipeline.tags=automatizzare,sequenziare,scriptare,elaborare in batch
|
pipeline.tags=automatizzare,sequenziare,scriptare,elaborare in batch
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Rimuovi immagine
|
|||||||
home.removeImagePdf.desc=Rimuovi le immagini dal PDF per ridurre la dimensione del file
|
home.removeImagePdf.desc=Rimuovi le immagini dal PDF per ridurre la dimensione del file
|
||||||
removeImagePdf.tags=Rimuovi immagine,operazioni sulla pagina,back-end,lato server
|
removeImagePdf.tags=Rimuovi immagine,operazioni sulla pagina,back-end,lato server
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Cambia proprietà
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=Da PDF a PDF/A
|
pdfToPDFA.title=Da PDF a PDF/A
|
||||||
pdfToPDFA.header=Da PDF a PDF/A
|
pdfToPDFA.header=Da PDF a PDF/A
|
||||||
pdfToPDFA.credit=Questo servizio utilizza OCRmyPDF per la conversione in PDF/A.
|
pdfToPDFA.credit=Questo servizio utilizza Ghostscript per la conversione in PDF/A.
|
||||||
pdfToPDFA.submit=Converti
|
pdfToPDFA.submit=Converti
|
||||||
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
|
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
|
||||||
pdfToPDFA.outputFormat=Formato di output
|
pdfToPDFA.outputFormat=Formato di output
|
||||||
@@ -1141,14 +1163,14 @@ survey.dontShowAgain=Non mostrare più
|
|||||||
#error
|
#error
|
||||||
error.sorry=Ci scusiamo per il problema!
|
error.sorry=Ci scusiamo per il problema!
|
||||||
error.needHelp=Hai bisogno di aiuto / trovato un problema?
|
error.needHelp=Hai bisogno di aiuto / trovato un problema?
|
||||||
error.contactTip=Se i problemi persistono, non esitare a contattarci per chiedere aiuto. Puoi inviare un ticket sulla nostra pagina GitHub o contattarci tramite Discord:
|
error.contactTip=Se i problemi persistono, non esitare a contattarci per chiedere aiuto. Puoi aprire un ticket sulla nostra pagina GitHub o contattarci tramite Discord:
|
||||||
error.404.head=404 - Pagina non trovata | Spiacenti, siamo inciampati nel codice!
|
error.404.head=404 - Pagina non trovata | Spiacenti, siamo inciampati nel codice!
|
||||||
error.404.1=Non riusciamo a trovare la pagina che stai cercando.
|
error.404.1=Non riusciamo a trovare la pagina che stai cercando.
|
||||||
error.404.2=Qualcosa è andato storto
|
error.404.2=Qualcosa è andato storto
|
||||||
error.github=Invia un ticket su GitHub
|
error.github=Apri un ticket su GitHub
|
||||||
error.showStack=Mostra traccia dello stack
|
error.showStack=Mostra traccia dello stack
|
||||||
error.copyStack=Copia traccia dello stack
|
error.copyStack=Copia traccia dello stack
|
||||||
error.githubSubmit=GitHub: invia un ticket
|
error.githubSubmit=GitHub: apri un ticket
|
||||||
error.discordSubmit=Discord: invia post di supporto
|
error.discordSubmit=Discord: invia post di supporto
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Font Size
|
addPageNumbers.fontSize=フォントサイズ
|
||||||
addPageNumbers.fontName=Font Name
|
addPageNumbers.fontName=フォント名
|
||||||
pdfPrompt=PDFを選択
|
pdfPrompt=PDFを選択
|
||||||
multiPdfPrompt=PDFを選択 (2つ以上)
|
multiPdfPrompt=PDFを選択 (2つ以上)
|
||||||
multiPdfDropPrompt=PDFを選択 (又はドラッグ&ドロップ)
|
multiPdfDropPrompt=PDFを選択 (又はドラッグ&ドロップ)
|
||||||
@@ -76,12 +76,14 @@ donate=寄付する
|
|||||||
color=色
|
color=色
|
||||||
sponsor=スポンサー
|
sponsor=スポンサー
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=プライバシーポリシー
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=利用規約
|
||||||
legal.accessibility=Accessibility
|
legal.accessibility=アクセシビリティ
|
||||||
legal.cookie=Cookie Policy
|
legal.cookie=Cookieポリシー
|
||||||
legal.impressum=Impressum
|
legal.impressum=著作権利者情報
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -198,13 +200,13 @@ adminUserSettings.forceChange=ログイン時にユーザー名/パスワード
|
|||||||
adminUserSettings.submit=ユーザーの保存
|
adminUserSettings.submit=ユーザーの保存
|
||||||
adminUserSettings.changeUserRole=ユーザーの役割を変更する
|
adminUserSettings.changeUserRole=ユーザーの役割を変更する
|
||||||
adminUserSettings.authenticated=認証済
|
adminUserSettings.authenticated=認証済
|
||||||
adminUserSettings.editOwnProfil=Edit own profile
|
adminUserSettings.editOwnProfil=プロフィールの編集
|
||||||
adminUserSettings.enabledUser=enabled user
|
adminUserSettings.enabledUser=有効なユーザー
|
||||||
adminUserSettings.disabledUser=disabled user
|
adminUserSettings.disabledUser=無効なユーザー
|
||||||
adminUserSettings.activeUsers=Active Users:
|
adminUserSettings.activeUsers=アクティブユーザー:
|
||||||
adminUserSettings.disabledUsers=Disabled Users:
|
adminUserSettings.disabledUsers=無効なユーザー:
|
||||||
adminUserSettings.totalUsers=Total Users:
|
adminUserSettings.totalUsers=ユーザー合計:
|
||||||
adminUserSettings.lastRequest=Last Request
|
adminUserSettings.lastRequest=最後のリクエスト
|
||||||
|
|
||||||
|
|
||||||
database.title=データベースのインポート/エクスポート
|
database.title=データベースのインポート/エクスポート
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=ページの縮尺の調整
|
|||||||
home.scalePages.desc=ページやコンテンツの縮尺を変更します。
|
home.scalePages.desc=ページやコンテンツの縮尺を変更します。
|
||||||
scalePages.tags=resize,modify,dimension,adapt
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
home.pipeline.title=パイプライン (高度)
|
home.pipeline.title=パイプライン
|
||||||
home.pipeline.desc=パイプラインスクリプトを定義してPDF上で複数のアクションを実行します。
|
home.pipeline.desc=パイプラインスクリプトを定義してPDF上で複数のアクションを実行します。
|
||||||
pipeline.tags=automate,sequence,scripted,batch-process
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ 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
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -496,21 +518,21 @@ login.locked=あなたのアカウントはロックされています。
|
|||||||
login.signinTitle=サインインしてください
|
login.signinTitle=サインインしてください
|
||||||
login.ssoSignIn=シングルサインオンでログイン
|
login.ssoSignIn=シングルサインオンでログイン
|
||||||
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
||||||
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
login.oauth2AdminBlockedUser=現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。
|
||||||
login.oauth2RequestNotFound=認証リクエストが見つかりません
|
login.oauth2RequestNotFound=認証リクエストが見つかりません
|
||||||
login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答
|
login.oauth2InvalidUserInfoResponse=無効なユーザー情報の応答
|
||||||
login.oauth2invalidRequest=無効なリクエスト
|
login.oauth2invalidRequest=無効なリクエスト
|
||||||
login.oauth2AccessDenied=アクセス拒否
|
login.oauth2AccessDenied=アクセス拒否
|
||||||
login.oauth2InvalidTokenResponse=無効なトークン応答
|
login.oauth2InvalidTokenResponse=無効なトークン応答
|
||||||
login.oauth2InvalidIdToken=無効なIDトークン
|
login.oauth2InvalidIdToken=無効なIDトークン
|
||||||
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
login.userIsDisabled=ユーザーは非アクティブ化されており、現在このユーザー名でのログインはブロックされています。管理者に連絡してください。
|
||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=自動塗りつぶし
|
autoRedact.title=自動塗りつぶし
|
||||||
autoRedact.header=自動塗りつぶし
|
autoRedact.header=自動塗りつぶし
|
||||||
autoRedact.colorLabel=カラー
|
autoRedact.colorLabel=カラー
|
||||||
autoRedact.textsToRedactLabel=編集するテキスト (line-separated)
|
autoRedact.textsToRedactLabel=編集するテキスト(行区切り)
|
||||||
autoRedact.textsToRedactPlaceholder=例 \n機密 \n極秘
|
autoRedact.textsToRedactPlaceholder=例 \n機密 \n極秘
|
||||||
autoRedact.useRegexLabel=正規表現を使用する
|
autoRedact.useRegexLabel=正規表現を使用する
|
||||||
autoRedact.wholeWordSearchLabel=単語単位の検索
|
autoRedact.wholeWordSearchLabel=単語単位の検索
|
||||||
@@ -679,7 +701,7 @@ pageLayout.submit=送信
|
|||||||
scalePages.title=ページの縮尺の調整
|
scalePages.title=ページの縮尺の調整
|
||||||
scalePages.header=ページの縮尺の調整
|
scalePages.header=ページの縮尺の調整
|
||||||
scalePages.pageSize=1ページのサイズ
|
scalePages.pageSize=1ページのサイズ
|
||||||
scalePages.keepPageSize=Original Size
|
scalePages.keepPageSize=元のサイズ
|
||||||
scalePages.scaleFactor=1ページの拡大レベル (トリミング)。
|
scalePages.scaleFactor=1ページの拡大レベル (トリミング)。
|
||||||
scalePages.submit=送信
|
scalePages.submit=送信
|
||||||
|
|
||||||
@@ -728,8 +750,8 @@ removeAnnotations.submit=削除
|
|||||||
#compare
|
#compare
|
||||||
compare.title=比較
|
compare.title=比較
|
||||||
compare.header=PDFの比較
|
compare.header=PDFの比較
|
||||||
compare.highlightColor.1=Highlight Color 1:
|
compare.highlightColor.1=ハイライトカラー 1:
|
||||||
compare.highlightColor.2=Highlight Color 2:
|
compare.highlightColor.2=ハイライトカラー 2:
|
||||||
compare.document.1=ドキュメント 1
|
compare.document.1=ドキュメント 1
|
||||||
compare.document.2=ドキュメント 2
|
compare.document.2=ドキュメント 2
|
||||||
compare.submit=比較
|
compare.submit=比較
|
||||||
@@ -781,7 +803,7 @@ ScannerImageSplit.selectText.7=最小輪郭面積:
|
|||||||
ScannerImageSplit.selectText.8=画像の最小の輪郭面積のしきい値を設定。
|
ScannerImageSplit.selectText.8=画像の最小の輪郭面積のしきい値を設定。
|
||||||
ScannerImageSplit.selectText.9=境界線サイズ:
|
ScannerImageSplit.selectText.9=境界線サイズ:
|
||||||
ScannerImageSplit.selectText.10=出力に白い縁取りが出ないように追加・削除される境界線の大きさを設定 (初期値:1)。
|
ScannerImageSplit.selectText.10=出力に白い縁取りが出ないように追加・削除される境界線の大きさを設定 (初期値:1)。
|
||||||
ScannerImageSplit.info=Python is not installed. It is required to run.
|
ScannerImageSplit.info=Pythonがインストールされていません。実行する必要があります。
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -808,7 +830,7 @@ ocr.submit=OCRでPDFを処理する
|
|||||||
extractImages.title=画像の抽出
|
extractImages.title=画像の抽出
|
||||||
extractImages.header=画像の抽出
|
extractImages.header=画像の抽出
|
||||||
extractImages.selectText=抽出した画像のフォーマットを選択
|
extractImages.selectText=抽出した画像のフォーマットを選択
|
||||||
extractImages.allowDuplicates=Save duplicate images
|
extractImages.allowDuplicates=重複した画像を保存する
|
||||||
extractImages.submit=抽出
|
extractImages.submit=抽出
|
||||||
|
|
||||||
|
|
||||||
@@ -864,7 +886,7 @@ pdfOrganiser.mode.6=奇数-偶数分割
|
|||||||
pdfOrganiser.mode.7=最初に削除
|
pdfOrganiser.mode.7=最初に削除
|
||||||
pdfOrganiser.mode.8=最後を削除
|
pdfOrganiser.mode.8=最後を削除
|
||||||
pdfOrganiser.mode.9=最初と最後を削除
|
pdfOrganiser.mode.9=最初と最後を削除
|
||||||
pdfOrganiser.mode.10=Odd-Even Merge
|
pdfOrganiser.mode.10=奇数-偶数の結合
|
||||||
pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
|
pdfOrganiser.placeholder=(例:1,3,2または4-8,2,10-12または2n-1)
|
||||||
|
|
||||||
|
|
||||||
@@ -933,7 +955,7 @@ pdfToImage.color=カラー
|
|||||||
pdfToImage.grey=グレースケール
|
pdfToImage.grey=グレースケール
|
||||||
pdfToImage.blackwhite=白黒 (データが失われる可能性があります!)
|
pdfToImage.blackwhite=白黒 (データが失われる可能性があります!)
|
||||||
pdfToImage.submit=変換
|
pdfToImage.submit=変換
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Pythonがインストールされていません。WebPの変換に必要です。
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -970,7 +992,7 @@ watermark.selectText.6=高さスペース (各透かし間の垂直方向のス
|
|||||||
watermark.selectText.7=不透明度 (0% - 100%):
|
watermark.selectText.7=不透明度 (0% - 100%):
|
||||||
watermark.selectText.8=透かしの種類:
|
watermark.selectText.8=透かしの種類:
|
||||||
watermark.selectText.9=透かしの画像:
|
watermark.selectText.9=透かしの画像:
|
||||||
watermark.selectText.10=Convert PDF to PDF-Image
|
watermark.selectText.10=PDFをPDFイメージに変換する
|
||||||
watermark.submit=透かしを追加
|
watermark.submit=透かしを追加
|
||||||
watermark.type.1=テキスト
|
watermark.type.1=テキスト
|
||||||
watermark.type.2=画像
|
watermark.type.2=画像
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=変更
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDFをPDF/Aに変換
|
pdfToPDFA.title=PDFをPDF/Aに変換
|
||||||
pdfToPDFA.header=PDFをPDF/Aに変換
|
pdfToPDFA.header=PDFをPDF/Aに変換
|
||||||
pdfToPDFA.credit=本サービスはPDF/Aの変換にOCRmyPDFを使用しています。
|
pdfToPDFA.credit=本サービスはPDF/Aの変換にghostscriptを使用しています。
|
||||||
pdfToPDFA.submit=変換
|
pdfToPDFA.submit=変換
|
||||||
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=기부하기
|
|||||||
color=색상
|
color=색상
|
||||||
sponsor=스폰서
|
sponsor=스폰서
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=변경
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF를 PDF/A로
|
pdfToPDFA.title=PDF를 PDF/A로
|
||||||
pdfToPDFA.header=PDF 문서를 PDF/A로 변환
|
pdfToPDFA.header=PDF 문서를 PDF/A로 변환
|
||||||
pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 OCRmyPDF 문서를 사용합니다.
|
pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 ghostscript 문서를 사용합니다.
|
||||||
pdfToPDFA.submit=변환
|
pdfToPDFA.submit=변환
|
||||||
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
|
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Doneer
|
|||||||
color=Kleur
|
color=Kleur
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Aanpassen paginaformaat/schaal
|
|||||||
home.scalePages.desc=Wijzig de grootte/schaal van een pagina en/of de inhoud ervan.
|
home.scalePages.desc=Wijzig de grootte/schaal van een pagina en/of de inhoud ervan.
|
||||||
scalePages.tags=resize,aanpassen,dimensie,aanpassen
|
scalePages.tags=resize,aanpassen,dimensie,aanpassen
|
||||||
|
|
||||||
home.pipeline.title=Pijplijn (Geavanceerd)
|
home.pipeline.title=Pijplijn
|
||||||
home.pipeline.desc=Voer meerdere acties uit op PDF's door pipelinescripts te definiëren
|
home.pipeline.desc=Voer meerdere acties uit op PDF's door pipelinescripts te definiëren
|
||||||
pipeline.tags=automatiseren,volgorde,gescrript,batch-verwerking
|
pipeline.tags=automatiseren,volgorde,gescrript,batch-verwerking
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Wijzigen
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF naar PDF/A
|
pdfToPDFA.title=PDF naar PDF/A
|
||||||
pdfToPDFA.header=PDF naar PDF/A
|
pdfToPDFA.header=PDF naar PDF/A
|
||||||
pdfToPDFA.credit=Deze service gebruikt OCRmyPDF voor PDF/A-conversie
|
pdfToPDFA.credit=Deze service gebruikt ghostscript voor PDF/A-conversie
|
||||||
pdfToPDFA.submit=Converteren
|
pdfToPDFA.submit=Converteren
|
||||||
pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd.
|
pdfToPDFA.tip=Werkt momenteel niet voor meerdere inputs tegelijkertijd.
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Doner
|
|||||||
color=Farge
|
color=Farge
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Endre
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF til PDF/A
|
pdfToPDFA.title=PDF til PDF/A
|
||||||
pdfToPDFA.header=PDF til PDF/A
|
pdfToPDFA.header=PDF til PDF/A
|
||||||
pdfToPDFA.credit=Denne tjenesten bruker OCRmyPDF for PDF/A-konvertering
|
pdfToPDFA.credit=Denne tjenesten bruker ghostscript for PDF/A-konvertering
|
||||||
pdfToPDFA.submit=Konverter
|
pdfToPDFA.submit=Konverter
|
||||||
pdfToPDFA.tip=Fungere for øyeblikket ikke for flere innganger samtidig
|
pdfToPDFA.tip=Fungere for øyeblikket ikke for flere innganger samtidig
|
||||||
pdfToPDFA.outputFormat=Utdataformat
|
pdfToPDFA.outputFormat=Utdataformat
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Podaruj
|
|||||||
color=kolor
|
color=kolor
|
||||||
sponsor=sponsor
|
sponsor=sponsor
|
||||||
info=informacje
|
info=informacje
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Dopasuj rozmiar stron
|
|||||||
home.scalePages.desc=Dopasuj rozmiar stron wybranego dokumentu PDF
|
home.scalePages.desc=Dopasuj rozmiar stron wybranego dokumentu PDF
|
||||||
scalePages.tags=resize,modify,dimension,adapt
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
home.pipeline.title=Automatyzacja (Zaawansowane)
|
home.pipeline.title=Automatyzacja
|
||||||
home.pipeline.desc=Wykonaj wiele akcji na dokumentach PDF, tworząc automatyzację
|
home.pipeline.desc=Wykonaj wiele akcji na dokumentach PDF, tworząc automatyzację
|
||||||
pipeline.tags=automate,sequence,scripted,batch-process
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Zmień
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF na PDF/A
|
pdfToPDFA.title=PDF na PDF/A
|
||||||
pdfToPDFA.header=PDF na PDF/A
|
pdfToPDFA.header=PDF na PDF/A
|
||||||
pdfToPDFA.credit=Ta usługa używa OCRmyPDF do konwersji PDF/A
|
pdfToPDFA.credit=Ta usługa używa ghostscript do konwersji PDF/A
|
||||||
pdfToPDFA.submit=Konwertuj
|
pdfToPDFA.submit=Konwertuj
|
||||||
pdfToPDFA.tip=Tylko jeden plik na raz
|
pdfToPDFA.tip=Tylko jeden plik na raz
|
||||||
pdfToPDFA.outputFormat=Format wyjściowy:
|
pdfToPDFA.outputFormat=Format wyjściowy:
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Font Size
|
addPageNumbers.fontSize=Tamanho da fonte
|
||||||
addPageNumbers.fontName=Font Name
|
addPageNumbers.fontName=Nome da fonte
|
||||||
pdfPrompt=Selecione PDF(s)
|
pdfPrompt=Selecione PDF(s)
|
||||||
multiPdfPrompt=Selecione PDFs (2+)
|
multiPdfPrompt=Selecione PDFs (2+)
|
||||||
multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs necessários
|
multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs necessários
|
||||||
@@ -76,12 +76,14 @@ donate=Doar
|
|||||||
color=Cor
|
color=Cor
|
||||||
sponsor=Patrocine
|
sponsor=Patrocine
|
||||||
info=Informações
|
info=Informações
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Política de Privacidade
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Termos e Condições
|
||||||
legal.accessibility=Accessibility
|
legal.accessibility=Acessibilidade
|
||||||
legal.cookie=Cookie Policy
|
legal.cookie=Política de Cookies
|
||||||
legal.impressum=Impressum
|
legal.impressum=Informações legais
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -380,7 +382,7 @@ home.scalePages.title=Ajustar Tamanho/Escala de Página
|
|||||||
home.scalePages.desc=Alterar o tamanho/escala da página e/ou seu conteúdo.
|
home.scalePages.desc=Alterar o tamanho/escala da página e/ou seu conteúdo.
|
||||||
scalePages.tags=redimensionar,modificar,dimensão,adaptar
|
scalePages.tags=redimensionar,modificar,dimensão,adaptar
|
||||||
|
|
||||||
home.pipeline.title=Pipeline (Avançado)
|
home.pipeline.title=Pipeline
|
||||||
home.pipeline.desc=Executar várias ações em PDFs definindo scripts de pipeline
|
home.pipeline.desc=Executar várias ações em PDFs definindo scripts de pipeline
|
||||||
pipeline.tags=automatizar,sequência,scriptado,processo-em-lote
|
pipeline.tags=automatizar,sequência,scriptado,processo-em-lote
|
||||||
|
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remover imagem
|
|||||||
home.removeImagePdf.desc=Remova a imagem do PDF para reduzir o tamanho do arquivo
|
home.removeImagePdf.desc=Remova a imagem do PDF para reduzir o tamanho do arquivo
|
||||||
removeImagePdf.tags=Remover imagem,operações de página,back-end,lado do servidor
|
removeImagePdf.tags=Remover imagem,operações de página,back-end,lado do servidor
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -507,12 +529,12 @@ login.userIsDisabled=O usuário está desativado, o login está atualmente bloqu
|
|||||||
|
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto ocultar
|
autoRedact.title=Redigir automaticamente
|
||||||
autoRedact.header=Auto ocultar
|
autoRedact.header=Redigir automaticamente
|
||||||
autoRedact.colorLabel=Cor
|
autoRedact.colorLabel=Cor
|
||||||
autoRedact.textsToRedactLabel=Text para ocultar (separado por linha)
|
autoRedact.textsToRedactLabel=Texto para redigir (separado por linha)
|
||||||
autoRedact.textsToRedactPlaceholder=por exemplo: \nConfidencial \nSecreto
|
autoRedact.textsToRedactPlaceholder=por exemplo: \nConfidencial \nSecreto
|
||||||
autoRedact.useRegexLabel=Usar Regex (Regular Expressions)
|
autoRedact.useRegexLabel=Usar Regex (expressão regular)
|
||||||
autoRedact.wholeWordSearchLabel=Pesquisa de palavras inteiras
|
autoRedact.wholeWordSearchLabel=Pesquisa de palavras inteiras
|
||||||
autoRedact.customPaddingLabel=Preenchimento extra personalizado
|
autoRedact.customPaddingLabel=Preenchimento extra personalizado
|
||||||
autoRedact.convertPDFToImageLabel=Converter PDF em imagem PDF (Usado para remover o texto atrás da caixa)
|
autoRedact.convertPDFToImageLabel=Converter PDF em imagem PDF (Usado para remover o texto atrás da caixa)
|
||||||
@@ -679,7 +701,7 @@ pageLayout.submit=Enviar
|
|||||||
scalePages.title=Ajustar Tamanho/Escala da Página
|
scalePages.title=Ajustar Tamanho/Escala da Página
|
||||||
scalePages.header=Ajustar Tamanho/Escala da Página
|
scalePages.header=Ajustar Tamanho/Escala da Página
|
||||||
scalePages.pageSize=Tamanho de uma página do documento.
|
scalePages.pageSize=Tamanho de uma página do documento.
|
||||||
scalePages.keepPageSize=Original Size
|
scalePages.keepPageSize=Tamanho original
|
||||||
scalePages.scaleFactor=Fator de zoom (corte) de uma página.
|
scalePages.scaleFactor=Fator de zoom (corte) de uma página.
|
||||||
scalePages.submit=Enviar
|
scalePages.submit=Enviar
|
||||||
|
|
||||||
@@ -781,7 +803,7 @@ ScannerImageSplit.selectText.7=Área mínima de contorno:
|
|||||||
ScannerImageSplit.selectText.8=Define o limite mínimo da área de contorno para uma foto
|
ScannerImageSplit.selectText.8=Define o limite mínimo da área de contorno para uma foto
|
||||||
ScannerImageSplit.selectText.9=Tamanho da borda:
|
ScannerImageSplit.selectText.9=Tamanho da borda:
|
||||||
ScannerImageSplit.selectText.10=Define o tamanho da borda adicionada e removida para evitar bordas brancas na saída (padrão: 1).
|
ScannerImageSplit.selectText.10=Define o tamanho da borda adicionada e removida para evitar bordas brancas na saída (padrão: 1).
|
||||||
ScannerImageSplit.info=Python is not installed. It is required to run.
|
ScannerImageSplit.info=Python não está instalado. É necessário para executar.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -808,7 +830,7 @@ ocr.submit=Processar PDF com OCR
|
|||||||
extractImages.title=Extrair imagens
|
extractImages.title=Extrair imagens
|
||||||
extractImages.header=Extrair imagens
|
extractImages.header=Extrair imagens
|
||||||
extractImages.selectText=Selecione o formato de imagem para converter as imagens extraídas
|
extractImages.selectText=Selecione o formato de imagem para converter as imagens extraídas
|
||||||
extractImages.allowDuplicates=Save duplicate images
|
extractImages.allowDuplicates=Salvar imagens duplicadas
|
||||||
extractImages.submit=Extrair
|
extractImages.submit=Extrair
|
||||||
|
|
||||||
|
|
||||||
@@ -933,7 +955,7 @@ pdfToImage.color=Colorida
|
|||||||
pdfToImage.grey=Escala de Cinza
|
pdfToImage.grey=Escala de Cinza
|
||||||
pdfToImage.blackwhite=Preto e Branco (pode perder de dados!)
|
pdfToImage.blackwhite=Preto e Branco (pode perder de dados!)
|
||||||
pdfToImage.submit=Converter
|
pdfToImage.submit=Converter
|
||||||
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Alterar
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF para PDF/A
|
pdfToPDFA.title=PDF para PDF/A
|
||||||
pdfToPDFA.header=PDF para PDF/A
|
pdfToPDFA.header=PDF para PDF/A
|
||||||
pdfToPDFA.credit=Este serviço usa OCRmyPDF para conversão de PDF/A
|
pdfToPDFA.credit=Este serviço usa ghostscript para conversão de PDF/A
|
||||||
pdfToPDFA.submit=Converter
|
pdfToPDFA.submit=Converter
|
||||||
pdfToPDFA.tip=Atualmente não funciona para múltiplas entradas ao mesmo tempo
|
pdfToPDFA.tip=Atualmente não funciona para múltiplas entradas ao mesmo tempo
|
||||||
pdfToPDFA.outputFormat=Formato de saída
|
pdfToPDFA.outputFormat=Formato de saída
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donate
|
|||||||
color=Color
|
color=Color
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Remove image
|
|||||||
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Mudar
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF para PDF/A
|
pdfToPDFA.title=PDF para PDF/A
|
||||||
pdfToPDFA.header=PDF para PDF/A
|
pdfToPDFA.header=PDF para PDF/A
|
||||||
pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A
|
pdfToPDFA.credit=Este serviço usa ghostscript para Conversão de PDF/A
|
||||||
pdfToPDFA.submit=Converter
|
pdfToPDFA.submit=Converter
|
||||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||||
pdfToPDFA.outputFormat=Output format
|
pdfToPDFA.outputFormat=Output format
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ donate=Donează
|
|||||||
color=Culoare
|
color=Culoare
|
||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Informații
|
info=Informații
|
||||||
|
page=Page
|
||||||
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Privacy Policy
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Terms and Conditions
|
legal.terms=Terms and Conditions
|
||||||
@@ -480,6 +482,26 @@ home.removeImagePdf.title=Elimină imagine
|
|||||||
home.removeImagePdf.desc=Elimină imaginea din PDF pentru a reduce dimensiunea fișierului
|
home.removeImagePdf.desc=Elimină imaginea din PDF pentru a reduce dimensiunea fișierului
|
||||||
removeImagePdf.tags=Elimină Imagine,Operații pagină,Back end,server side
|
removeImagePdf.tags=Elimină Imagine,Operații pagină,Back end,server side
|
||||||
|
|
||||||
|
#replace-invert-color
|
||||||
|
replace-color.title=Replace-Invert-Color
|
||||||
|
replace-color.header=Replace-Invert Color PDF
|
||||||
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
|
replace-color.selectText.5=High contrast color options
|
||||||
|
replace-color.selectText.6=white text on black background
|
||||||
|
replace-color.selectText.7=Black text on white background
|
||||||
|
replace-color.selectText.8=Yellow text on black background
|
||||||
|
replace-color.selectText.9=Green text on black background
|
||||||
|
replace-color.selectText.10=Choose text Color
|
||||||
|
replace-color.selectText.11=Choose background Color
|
||||||
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
# #
|
# #
|
||||||
@@ -1023,7 +1045,7 @@ changeMetadata.submit=Schimbă
|
|||||||
#pdfToPDFA
|
#pdfToPDFA
|
||||||
pdfToPDFA.title=PDF către PDF/A
|
pdfToPDFA.title=PDF către PDF/A
|
||||||
pdfToPDFA.header=PDF către PDF/A
|
pdfToPDFA.header=PDF către PDF/A
|
||||||
pdfToPDFA.credit=Acest serviciu utilizează OCRmyPDF pentru conversia în PDF/A
|
pdfToPDFA.credit=Acest serviciu utilizează ghostscript pentru conversia în PDF/A
|
||||||
pdfToPDFA.submit=Convertește
|
pdfToPDFA.submit=Convertește
|
||||||
pdfToPDFA.tip=În prezent nu funcționează pentru mai multe intrări simultan
|
pdfToPDFA.tip=În prezent nu funcționează pentru mai multe intrări simultan
|
||||||
pdfToPDFA.outputFormat=Format de ieșire
|
pdfToPDFA.outputFormat=Format de ieșire
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user