Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d59cb18666 | ||
|
|
a772b4fa09 |
67
.github/workflows/mac-unix-artifact-creation.yml
vendored
67
.github/workflows/mac-unix-artifact-creation.yml
vendored
@@ -1,67 +0,0 @@
|
||||
name: Create Application Bundles
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'mac'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
create-unix-bundle:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew bootJar
|
||||
|
||||
- name: Create tar.gz Bundle
|
||||
run: |
|
||||
mkdir -p Stirling-PDF-unix
|
||||
cp build/libs/Stirling-PDF-*.jar Stirling-PDF-unix/Stirling-PDF.jar
|
||||
cp scripts/launcher.sh Stirling-PDF-unix/Stirling-PDF
|
||||
cp src/main/resources/static/favicon.ico Stirling-PDF-unix/icon.png
|
||||
chmod +x Stirling-PDF-unix/Stirling-PDF
|
||||
tar -czf Stirling-PDF-unix.tar.gz Stirling-PDF-unix/
|
||||
|
||||
- name: Upload Unix Bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unix-bundle
|
||||
path: Stirling-PDF-unix.tar.gz
|
||||
|
||||
create-mac-bundle:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew bootJar
|
||||
|
||||
- name: Create Mac App Bundle
|
||||
run: |
|
||||
cp build/libs/Stirling-PDF-*.jar build/libs/Stirling-PDF.jar
|
||||
chmod +x scripts/create-mac-launcher.sh
|
||||
./scripts/create-mac-launcher.sh
|
||||
|
||||
- name: Create DMG
|
||||
run: |
|
||||
hdiutil create -volname "Stirling-PDF" -srcfolder "Stirling-PDF.app" -ov -format UDZO "Stirling-PDF-mac.dmg"
|
||||
|
||||
- name: Upload Mac Bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mac-bundle
|
||||
path: Stirling-PDF-mac.dmg
|
||||
22
Dockerfile
22
Dockerfile
@@ -6,7 +6,6 @@ COPY scripts /scripts
|
||||
COPY pipeline /pipeline
|
||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||
#COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
@@ -19,6 +18,10 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||
PGID=1000 \
|
||||
UMASK=022
|
||||
|
||||
# Create non-root user first
|
||||
RUN addgroup -S stirlingpdfgroup && \
|
||||
adduser -S stirlingpdfuser -G stirlingpdfgroup
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
@@ -31,8 +34,6 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
bash \
|
||||
curl \
|
||||
qpdf \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
@@ -50,15 +51,16 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
# uno unoconv and HTML
|
||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||
fc-cache -f -v && \
|
||||
chmod +x /scripts/* && \
|
||||
chmod +x /scripts/init.sh && \
|
||||
# User permissions
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||
tesseract --list-langs
|
||||
mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||
chmod -R 755 ${HOME} /configs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||
tesseract --list-langs && \
|
||||
chmod -R 777 /logs
|
||||
|
||||
COPY build/libs/*.jar app.jar
|
||||
RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Build the application
|
||||
# Build stage
|
||||
FROM gradle:8.11-jdk17 AS build
|
||||
|
||||
# Set the working directory
|
||||
@@ -7,18 +7,20 @@ WORKDIR /app
|
||||
# Copy the entire project to the working directory
|
||||
COPY . .
|
||||
|
||||
# Build the application with DOCKER_ENABLE_SECURITY=false
|
||||
# Build the application
|
||||
RUN DOCKER_ENABLE_SECURITY=true \
|
||||
./gradlew clean build
|
||||
./gradlew clean build
|
||||
|
||||
# Main stage
|
||||
FROM alpine:3.20.3
|
||||
|
||||
# Copy necessary files
|
||||
# Create non-root user first
|
||||
RUN addgroup -S stirlingpdfgroup && \
|
||||
adduser -S stirlingpdfuser -G stirlingpdfgroup
|
||||
|
||||
COPY scripts /scripts
|
||||
COPY pipeline /pipeline
|
||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||
COPY --from=build /app/build/libs/*.jar app.jar
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
||||
@@ -33,51 +35,45 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
||||
FAT_DOCKER=true \
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
||||
|
||||
# Create necessary directories with correct permissions
|
||||
RUN mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||
chmod -R 755 ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom
|
||||
|
||||
# JDK for app
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
# JDK and other dependencies
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
|
||||
apk upgrade --no-cache -a && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
shadow \
|
||||
su-exec \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
# Doc conversion
|
||||
libreoffice \
|
||||
# pdftohtml
|
||||
poppler-utils \
|
||||
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||
qpdf \
|
||||
tesseract-ocr-data-eng \
|
||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||
# CV
|
||||
py3-opencv \
|
||||
# python3/pip
|
||||
python3 \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
openjdk21-jre \
|
||||
libreoffice \
|
||||
poppler-utils \
|
||||
qpdf \
|
||||
tesseract-ocr-data-eng \
|
||||
tesseract-ocr-data-fra \
|
||||
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra \
|
||||
py3-opencv \
|
||||
python3 \
|
||||
py3-pip && \
|
||||
# uno unoconv and HTML
|
||||
pip install --break-system-packages --no-cache-dir --upgrade unoconv WeasyPrint pdf2image pillow && \
|
||||
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders && \
|
||||
fc-cache -f -v && \
|
||||
chmod +x /scripts/* && \
|
||||
chmod +x /scripts/init.sh && \
|
||||
# User permissions
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||
tesseract --list-langs
|
||||
mkdir -p /usr/share/tessdata && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup /usr/share/tessdata /usr/share/fonts/opentype/noto && \
|
||||
fc-cache -f -v
|
||||
|
||||
COPY build/libs/*.jar app.jar
|
||||
RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
# Set user and run command
|
||||
|
||||
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||
CMD ["java", "-Dfile.encoding=UTF-8", "-jar", "/app.jar"]
|
||||
@@ -17,8 +17,11 @@ COPY scripts/download-security-jar.sh /scripts/download-security-jar.sh
|
||||
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||
COPY pipeline /pipeline
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
# Create non-root user first
|
||||
RUN addgroup -S stirlingpdfgroup && \
|
||||
adduser -S stirlingpdfuser -G stirlingpdfgroup
|
||||
|
||||
# Set up necessary directories and permissions
|
||||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
@@ -30,18 +33,15 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
||||
tini \
|
||||
bash \
|
||||
curl \
|
||||
shadow \
|
||||
su-exec \
|
||||
openjdk21-jre && \
|
||||
# User permissions
|
||||
mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto && \
|
||||
chmod +x /scripts/*.sh && \
|
||||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
mkdir -p ${HOME} /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /scripts /usr/share/fonts/custom && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup ${HOME} /configs /logs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||
chmod -R 755 ${HOME} /configs /customFiles /pipeline /scripts /usr/share/fonts/custom && \
|
||||
chmod -R 777 /logs
|
||||
|
||||
# Set environment variables
|
||||
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||
COPY build/libs/*.jar app.jar
|
||||
RUN chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||
|
||||
EXPOSE 8080/tcp
|
||||
|
||||
|
||||
35
README.md
35
README.md
@@ -19,7 +19,7 @@ All files and PDFs exist either exclusively on the client side, reside in server
|
||||
|
||||
## Features
|
||||
|
||||
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
||||
- Enterprise features like SSO Check [here](https://docs.stirlingpdf.com/Enterprise%20Edition)
|
||||
- Dark mode support
|
||||
- Custom download options
|
||||
- Parallel file processing and downloads
|
||||
@@ -187,15 +187,15 @@ Certain functionality like `Sign` supports pre-saved files stored at `/customFil
|
||||
|
||||
## Supported Languages
|
||||
|
||||
Stirling-PDF currently supports 38 languages!
|
||||
Stirling-PDF currently supports 37 languages!
|
||||
|
||||
| Language | Progress |
|
||||
| -------------------------------------------- | -------------------------------------- |
|
||||
| Arabic (العربية) (ar_AR) |  |
|
||||
| Azerbaijani (Azərbaycan Dili) (az_AZ) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Basque (Euskara) (eu_ES) |  |
|
||||
| Bulgarian (Български) (bg_BG) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Catalan (Català) (ca_CA) |  |
|
||||
| Croatian (Hrvatski) (hr_HR) |  |
|
||||
| Czech (Česky) (cs_CZ) |  |
|
||||
| Danish (Dansk) (da_DK) |  |
|
||||
@@ -208,27 +208,26 @@ Stirling-PDF currently supports 38 languages!
|
||||
| Hindi (हिंदी) (hi_IN) |  |
|
||||
| Hungarian (Magyar) (hu_HU) |  |
|
||||
| Indonesian (Bahasa Indonesia) (id_ID) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Irish (Gaeilge) (ga_IE) |  |
|
||||
| Italian (Italiano) (it_IT) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Persian (فارسی) (fa_IR) |  |
|
||||
| Japanese (日本語) (ja_JP) |  |
|
||||
| Korean (한국어) (ko_KR) |  |
|
||||
| Norwegian (Norsk) (no_NB) |  |
|
||||
| Polish (Polski) (pl_PL) |  |
|
||||
| Portuguese (Português) (pt_PT) |  |
|
||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Romanian (Română) (ro_RO) |  |
|
||||
| Russian (Русский) (ru_RU) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||
| Slovakian (Slovensky) (sk_SK) |  |
|
||||
| Spanish (Español) (es_ES) |  |
|
||||
| Swedish (Svenska) (sv_SE) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Thai (ไทย) (th_TH) |  |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
| Turkish (Türkçe) (tr_TR) |  |
|
||||
| Ukrainian (Українська) (uk_UA) |  |
|
||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||
|
||||
## Contributing (Creating Issues, Translations, Fixing Bugs, etc.)
|
||||
|
||||
@@ -241,7 +240,7 @@ Stirling PDF offers a Enterprise edition of its software, This is the same great
|
||||
### Whats included
|
||||
|
||||
- Prioritised Support tickets via support@stirlingpdf.com to reach directly to Stirling-PDF team for support and 1:1 meetings where applicable (Provided they come from same email domain registered with us)
|
||||
- Prioritised Enhancements to Stirling-PDF where applicable
|
||||
- Prioritised Enhancements to Stirling-PDF where applicable
|
||||
- Base SSO support
|
||||
- Advanced SSO such as automated login handling (Coming very soon)
|
||||
- SAML SSO (Coming very soon)
|
||||
|
||||
@@ -14,14 +14,17 @@ services:
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
user: "stirlingpdfuser"
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
LANGS: "ALL"
|
||||
TESSERACT_LANGS: "eng,fra,deu,spa,ita"
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
|
||||
@@ -14,9 +14,9 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
|
||||
@@ -14,9 +14,9 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
|
||||
@@ -14,9 +14,9 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
|
||||
@@ -14,8 +14,8 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
|
||||
@@ -14,9 +14,9 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||
- ./stirling/latest/config:/configs:rw
|
||||
- ./stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/bin/bash
|
||||
# scripts/create-mac-launcher.sh
|
||||
|
||||
# Create app structure
|
||||
APP_NAME="Stirling-PDF"
|
||||
APP_BUNDLE="$APP_NAME.app"
|
||||
mkdir -p "$APP_BUNDLE/Contents/"{MacOS,Resources,Java}
|
||||
|
||||
# Convert icon
|
||||
sips -s format icns "src/main/resources/static/favicon.ico" --out "$APP_BUNDLE/Contents/Resources/AppIcon.icns"
|
||||
|
||||
# Create Info.plist
|
||||
cat > "$APP_BUNDLE/Contents/Info.plist" << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$APP_NAME</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.frooodle.stirlingpdf</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$APP_NAME</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10.0</string>
|
||||
<key>LSEnvironment</key>
|
||||
<dict>
|
||||
<key>BROWSER_OPEN</key>
|
||||
<string>true</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# Create launcher script
|
||||
cat > "$APP_BUNDLE/Contents/MacOS/$APP_NAME" << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
MIN_JAVA_VERSION="17"
|
||||
PREFERRED_JAVA_VERSION="21"
|
||||
JAVA_DOWNLOAD_URL="https://download.oracle.com/java/21/latest/jdk-21_macos-x64_bin.dmg"
|
||||
|
||||
# Check Java version
|
||||
if type -p java > /dev/null; then
|
||||
_java=java
|
||||
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
_java="$JAVA_HOME/bin/java"
|
||||
else
|
||||
osascript -e 'display dialog "Java not found. Please install Java 21." buttons {"Download Java", "Cancel"} default button "Download Java"' \
|
||||
&& open "$JAVA_DOWNLOAD_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$("$_java" -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
|
||||
if [[ "$version" -lt "$MIN_JAVA_VERSION" ]]; then
|
||||
osascript -e 'display dialog "Wrong Java version. Please install Java 21." buttons {"Download Java", "Cancel"} default button "Download Java"' \
|
||||
&& open "$JAVA_DOWNLOAD_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run application
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
exec java -jar "$DIR/../Java/Stirling-PDF.jar" "$@"
|
||||
EOF
|
||||
|
||||
chmod +x "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
|
||||
|
||||
# Copy JAR file
|
||||
cp "build/libs/Stirling-PDF.jar" "$APP_BUNDLE/Contents/Java/"
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
# scripts/create-unix-launcher.sh
|
||||
|
||||
cat > launcher.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
APP_NAME="Stirling-PDF"
|
||||
MIN_JAVA_VERSION="17"
|
||||
PREFERRED_JAVA_VERSION="21"
|
||||
JAVA_DOWNLOAD_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz"
|
||||
BROWSER_OPEN="true"
|
||||
|
||||
# Check Java version
|
||||
if type -p java > /dev/null; then
|
||||
_java=java
|
||||
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
_java="$JAVA_HOME/bin/java"
|
||||
else
|
||||
echo "Java not found. Please install Java 21."
|
||||
xdg-open "$JAVA_DOWNLOAD_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$("$_java" -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
|
||||
if [[ "$version" -lt "$MIN_JAVA_VERSION" ]]; then
|
||||
echo "Java version $version detected. Please install Java 21."
|
||||
xdg-open "$JAVA_DOWNLOAD_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run application
|
||||
exec java -jar "$(dirname "$0")/Stirling-PDF.jar" "$@"
|
||||
EOF
|
||||
|
||||
chmod +x launcher.sh
|
||||
@@ -66,6 +66,7 @@ ignore = [
|
||||
[es_ES]
|
||||
ignore = [
|
||||
'adminUserSettings.roles',
|
||||
'color',
|
||||
'error',
|
||||
'language.direction',
|
||||
'no',
|
||||
|
||||
@@ -1,37 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Update the user and group IDs as per environment variables
|
||||
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
|
||||
usermod -o -u "$PUID" stirlingpdfuser || true
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then
|
||||
groupmod -o -g "$PGID" stirlingpdfgroup || true
|
||||
fi
|
||||
umask "$UMASK" || true
|
||||
|
||||
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" && "$FAT_DOCKER" != "true" ]]; then
|
||||
echo "issue with calibre in current version, feature currently disabled on Stirling-PDF"
|
||||
#apk add --no-cache calibre@testing
|
||||
fi
|
||||
|
||||
if [[ "$FAT_DOCKER" != "true" ]]; then
|
||||
/scripts/download-security-jar.sh
|
||||
/scripts/download-security-jar.sh
|
||||
fi
|
||||
|
||||
if [[ -n "$LANGS" ]]; then
|
||||
/scripts/installFonts.sh $LANGS
|
||||
/scripts/installFonts.sh $LANGS
|
||||
fi
|
||||
|
||||
echo "Setting permissions and ownership for necessary directories..."
|
||||
# Attempt to change ownership of directories and files
|
||||
if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar; then
|
||||
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar || true
|
||||
# If chown succeeds, execute the command as stirlingpdfuser
|
||||
exec su-exec stirlingpdfuser "$@"
|
||||
else
|
||||
# If chown fails, execute the command without changing the user context
|
||||
echo "[WARN] Chown failed, running as host user"
|
||||
exec "$@"
|
||||
fi
|
||||
exec "$@"
|
||||
@@ -1,31 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copy the original tesseract-ocr files to the volume directory without overwriting existing files
|
||||
echo "Copying original files without overwriting existing files"
|
||||
mkdir -p /usr/share/tessdata
|
||||
cp -rn /usr/share/tessdata-original/* /usr/share/tessdata
|
||||
cp -rn /usr/share/tessdata-original/* /usr/share/tessdata 2>/dev/null || true
|
||||
|
||||
# Copy additional tessdata if available
|
||||
if [ -d /usr/share/tesseract-ocr/4.00/tessdata ]; then
|
||||
cp -r /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tessdata || true;
|
||||
cp -rn /usr/share/tesseract-ocr/4.00/tessdata/* /usr/share/tessdata 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then
|
||||
cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true;
|
||||
cp -rn /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Check if TESSERACT_LANGS environment variable is set and is not empty
|
||||
if [[ -n "$TESSERACT_LANGS" ]]; then
|
||||
# Convert comma-separated values to a space-separated list
|
||||
LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
|
||||
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
|
||||
# Install each language pack
|
||||
for LANG in $LANGS; do
|
||||
if [[ $LANG =~ $pattern ]]; then
|
||||
apk add --no-cache "tesseract-ocr-data-$LANG"
|
||||
else
|
||||
echo "Skipping invalid language code"
|
||||
fi
|
||||
done
|
||||
# Convert comma-separated values to a space-separated list
|
||||
TES_LANGS=$(echo $TESSERACT_LANGS | tr ',' ' ')
|
||||
pattern='^[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?$'
|
||||
|
||||
# Log available languages
|
||||
echo "Currently installed languages:"
|
||||
tesseract --list-langs
|
||||
|
||||
echo "Requested additional languages: $TES_LANGS"
|
||||
|
||||
# Instead of apk add, download language files from a known source
|
||||
for LANG in $TES_LANGS; do
|
||||
if [[ $LANG =~ $pattern ]]; then
|
||||
# Download to user-writable directory
|
||||
wget -P /usr/share/tessdata/ "https://github.com/tesseract-ocr/tessdata/raw/main/${LANG}.traineddata" || \
|
||||
echo "Failed to download language pack for ${LANG}"
|
||||
else
|
||||
echo "Skipping invalid language code"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
/scripts/init-without-ocr.sh "$@"
|
||||
@@ -1,67 +1,156 @@
|
||||
#!/bin/bash
|
||||
|
||||
LANGS=$1
|
||||
FONT_DIR="$HOME/.local/share/fonts"
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
|
||||
# Function to install a font package
|
||||
install_font() {
|
||||
echo "Installing font package: $1"
|
||||
if ! apk add "$1" --no-cache; then
|
||||
echo "Failed to install $1"
|
||||
fi
|
||||
# Create fonts directory if it doesn't exist
|
||||
mkdir -p "$FONT_DIR"
|
||||
|
||||
# Function to get latest GitHub release
|
||||
get_latest_release() {
|
||||
local repo=$1
|
||||
local api_url="https://api.github.com/repos/$repo/releases/latest"
|
||||
curl --silent "$api_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'
|
||||
}
|
||||
|
||||
# Install common fonts used across many languages
|
||||
#common_fonts=(
|
||||
# font-terminus
|
||||
# font-dejavu
|
||||
# font-noto
|
||||
# font-noto-cjk
|
||||
# font-awesome
|
||||
# font-noto-extra
|
||||
#)
|
||||
#
|
||||
#for font in "${common_fonts[@]}"; do
|
||||
# install_font $font
|
||||
#done
|
||||
# Function to download and install a font
|
||||
install_font() {
|
||||
local font_name=$1
|
||||
echo "Installing font package: $font_name"
|
||||
|
||||
# Map font package names to actual font URLs and installation methods
|
||||
case $font_name in
|
||||
"font-dejavu")
|
||||
local version=$(get_latest_release "dejavu-fonts/dejavu-fonts")
|
||||
version=${version#version_} # Remove 'version_' prefix
|
||||
local url="https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_${version}/dejavu-fonts-ttf-${version}.tar.bz2"
|
||||
wget -q "$url" -P "$TEMP_DIR" && \
|
||||
tar xjf "$TEMP_DIR/dejavu-fonts-ttf-${version}.tar.bz2" -C "$TEMP_DIR" && \
|
||||
find "$TEMP_DIR" -name "*.ttf" -exec cp {} "$FONT_DIR/" \;
|
||||
;;
|
||||
|
||||
"font-noto")
|
||||
# Base Noto Sans and Serif
|
||||
wget -q "https://noto-website-2.storage.googleapis.com/pkgs/NotoSans-hinted.zip" -P "$TEMP_DIR" && \
|
||||
wget -q "https://noto-website-2.storage.googleapis.com/pkgs/NotoSerif-hinted.zip" -P "$TEMP_DIR" && \
|
||||
unzip -q "$TEMP_DIR/NotoSans-hinted.zip" -d "$TEMP_DIR/noto-sans" && \
|
||||
unzip -q "$TEMP_DIR/NotoSerif-hinted.zip" -d "$TEMP_DIR/noto-serif" && \
|
||||
cp "$TEMP_DIR/noto-sans"/*.ttf "$FONT_DIR/" && \
|
||||
cp "$TEMP_DIR/noto-serif"/*.ttf "$FONT_DIR/"
|
||||
;;
|
||||
|
||||
"font-noto-cjk")
|
||||
# Noto CJK
|
||||
wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Japanese/NotoSansCJKjp-Regular.otf" -P "$FONT_DIR"
|
||||
wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Korean/NotoSansCJKkr-Regular.otf" -P "$FONT_DIR"
|
||||
wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf" -P "$FONT_DIR"
|
||||
wget -q "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/TraditionalChinese/NotoSansCJKtc-Regular.otf" -P "$FONT_DIR"
|
||||
;;
|
||||
|
||||
"font-noto-arabic")
|
||||
wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoNaskhArabic/NotoNaskhArabic-Regular.ttf" -P "$FONT_DIR"
|
||||
wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoKufiArabic/NotoKufiArabic-Regular.ttf" -P "$FONT_DIR"
|
||||
;;
|
||||
|
||||
"font-noto-devanagari")
|
||||
wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSansDevanagari/NotoSansDevanagari-Regular.ttf" -P "$FONT_DIR"
|
||||
wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSerifDevanagari/NotoSerifDevanagari-Regular.ttf" -P "$FONT_DIR"
|
||||
;;
|
||||
|
||||
"font-noto-thai")
|
||||
wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSansThai/NotoSansThai-Regular.ttf" -P "$FONT_DIR"
|
||||
wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSerifThai/NotoSerifThai-Regular.ttf" -P "$FONT_DIR"
|
||||
;;
|
||||
|
||||
"font-noto-hebrew")
|
||||
wget -q "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSansHebrew/NotoSansHebrew-Regular.ttf" -P "$FONT_DIR"
|
||||
;;
|
||||
|
||||
"font-awesome")
|
||||
local version=$(get_latest_release "FortAwesome/Font-Awesome")
|
||||
wget -q "https://use.fontawesome.com/releases/v${version}/fontawesome-free-${version}-desktop.zip" -P "$TEMP_DIR" && \
|
||||
unzip -q "$TEMP_DIR/fontawesome-free-${version}-desktop.zip" -d "$TEMP_DIR" && \
|
||||
cp "$TEMP_DIR/fontawesome-free-${version}-desktop/otfs"/*.otf "$FONT_DIR/"
|
||||
;;
|
||||
|
||||
"font-source-code-pro")
|
||||
local version=$(get_latest_release "adobe-fonts/source-code-pro")
|
||||
wget -q "https://github.com/adobe-fonts/source-code-pro/releases/download/${version}/TTF-source-code-pro-${version}.zip" -P "$TEMP_DIR" && \
|
||||
unzip -q "$TEMP_DIR/TTF-source-code-pro-${version}.zip" -d "$TEMP_DIR/source-code-pro" && \
|
||||
cp "$TEMP_DIR/source-code-pro"/*.ttf "$FONT_DIR/"
|
||||
;;
|
||||
|
||||
"font-vollkorn")
|
||||
wget -q "https://github.com/FAlthausen/Vollkorn-Typeface/raw/main/fonts/TTF/Vollkorn-Regular.ttf" -P "$FONT_DIR"
|
||||
;;
|
||||
|
||||
"font-liberation")
|
||||
wget -q "https://github.com/liberationfonts/liberation-fonts/files/7261482/liberation-fonts-ttf-2.1.5.tar.gz" -P "$TEMP_DIR" && \
|
||||
tar xzf "$TEMP_DIR/liberation-fonts-ttf-2.1.5.tar.gz" -C "$TEMP_DIR" && \
|
||||
cp "$TEMP_DIR/liberation-fonts-ttf-2.1.5"/*.ttf "$FONT_DIR/"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Completed installation attempt for $font_name"
|
||||
}
|
||||
|
||||
# Map languages to specific font packages
|
||||
# Enhanced language-specific font mappings
|
||||
declare -A language_fonts=(
|
||||
["ar_AR"]="font-noto-arabic"
|
||||
["zh_CN"]="font-isas-misc"
|
||||
["zh_TW"]="font-isas-misc"
|
||||
["ja_JP"]="font-noto font-noto-thai font-noto-tibetan font-ipa font-sony-misc font-jis-misc"
|
||||
["ru_RU"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
||||
["sr_LATN_RS"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
||||
["uk_UA"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
||||
["ko_KR"]="font-noto font-noto-thai font-noto-tibetan"
|
||||
["zh_CN"]="font-noto-cjk"
|
||||
["zh_TW"]="font-noto-cjk"
|
||||
["ja_JP"]="font-noto font-noto-cjk"
|
||||
["ru_RU"]="font-noto font-liberation font-vollkorn"
|
||||
["sr_LATN_RS"]="font-noto font-liberation"
|
||||
["uk_UA"]="font-noto font-liberation"
|
||||
["ko_KR"]="font-noto font-noto-cjk"
|
||||
["el_GR"]="font-noto"
|
||||
["hi_IN"]="font-noto-devanagari"
|
||||
["bg_BG"]="font-vollkorn font-misc-cyrillic"
|
||||
["GENERAL"]="font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra"
|
||||
["bg_BG"]="font-noto font-liberation"
|
||||
["th_TH"]="font-noto-thai"
|
||||
["he_IL"]="font-noto-hebrew"
|
||||
["GENERAL"]="font-noto font-dejavu font-liberation font-source-code-pro font-awesome"
|
||||
)
|
||||
|
||||
# Install fonts for other languages which generally do not need special packages beyond 'font-noto'
|
||||
other_langs=("en_GB" "en_US" "de_DE" "fr_FR" "es_ES" "ca_CA" "it_IT" "pt_BR" "nl_NL" "sv_SE" "pl_PL" "ro_RO" "hu_HU" "tr_TR" "id_ID" "eu_ES")
|
||||
# Install fonts based on specified languages
|
||||
if [[ $LANGS == "ALL" ]]; then
|
||||
# Install all fonts from the language_fonts map
|
||||
declare -A installed_fonts
|
||||
for fonts in "${language_fonts[@]}"; do
|
||||
for font in $fonts; do
|
||||
install_font $font
|
||||
if [[ -z "${installed_fonts[$font]}" ]]; then
|
||||
install_font "$font"
|
||||
installed_fonts[$font]=1
|
||||
fi
|
||||
done
|
||||
done
|
||||
else
|
||||
# Split comma-separated languages and install necessary fonts
|
||||
declare -A installed_fonts
|
||||
IFS=',' read -ra LANG_CODES <<< "$LANGS"
|
||||
for code in "${LANG_CODES[@]}"; do
|
||||
if [[ " ${other_langs[@]} " =~ " ${code} " ]]; then
|
||||
install_font font-noto
|
||||
else
|
||||
fonts_to_install=${language_fonts[$code]}
|
||||
if [ ! -z "$fonts_to_install" ]; then
|
||||
for font in $fonts_to_install; do
|
||||
install_font $font
|
||||
done
|
||||
fi
|
||||
fonts_to_install=${language_fonts[$code]}
|
||||
if [ ! -z "$fonts_to_install" ]; then
|
||||
for font in $fonts_to_install; do
|
||||
if [[ -z "${installed_fonts[$font]}" ]]; then
|
||||
install_font "$font"
|
||||
installed_fonts[$font]=1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# Update font cache
|
||||
if command -v fc-cache >/dev/null; then
|
||||
fc-cache -f "$FONT_DIR"
|
||||
echo "Font cache updated"
|
||||
else
|
||||
echo "Warning: fc-cache not found. You may need to manually update your font cache"
|
||||
fi
|
||||
|
||||
echo "Font installation completed. Fonts installed in: $FONT_DIR"
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
MIN_JAVA_VERSION="17"
|
||||
PREFERRED_JAVA_VERSION="21"
|
||||
JAVA_DOWNLOAD_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz"
|
||||
BROWSER_OPEN="true"
|
||||
|
||||
# Check Java version
|
||||
if type -p java > /dev/null; then
|
||||
_java=java
|
||||
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
_java="$JAVA_HOME/bin/java"
|
||||
else
|
||||
echo "Java not found. Please install Java 21."
|
||||
xdg-open "$JAVA_DOWNLOAD_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$("$_java" -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
|
||||
if [[ "$version" -lt "$MIN_JAVA_VERSION" ]]; then
|
||||
echo "Java version $version detected. Please install Java 21."
|
||||
xdg-open "$JAVA_DOWNLOAD_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run application
|
||||
exec java -jar "$(dirname "$0")/Stirling-PDF.jar" "$@"
|
||||
@@ -28,7 +28,7 @@ public class LicenseKeyChecker {
|
||||
this.checkLicense();
|
||||
}
|
||||
|
||||
@Scheduled(initialDelay = 604800000,fixedRate = 604800000) // 7 days in milliseconds
|
||||
@Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
|
||||
public void checkLicensePeriodically() {
|
||||
checkLicense();
|
||||
}
|
||||
|
||||
@@ -595,7 +595,9 @@ public class GetInfoOnPDF {
|
||||
|
||||
permissionsNode.put("Document Assembly", getPermissionState(ap.canAssembleDocument()));
|
||||
permissionsNode.put("Extracting Content", getPermissionState(ap.canExtractContent()));
|
||||
permissionsNode.put("Extracting for accessibility", getPermissionState(ap.canExtractForAccessibility()));
|
||||
permissionsNode.put(
|
||||
"Extracting for accessibility",
|
||||
getPermissionState(ap.canExtractForAccessibility()));
|
||||
permissionsNode.put("Form Filling", getPermissionState(ap.canFillInForm()));
|
||||
permissionsNode.put("Modifying", getPermissionState(ap.canModify()));
|
||||
permissionsNode.put("Modifying annotations", getPermissionState(ap.canModifyAnnotations()));
|
||||
|
||||
@@ -3,11 +3,12 @@ package stirling.software.SPDF.controller.api.security;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -41,7 +42,6 @@ import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||
@RequestMapping("/api/v1/security")
|
||||
@Tag(name = "Security", description = "Security APIs")
|
||||
public class ValidateSignatureController {
|
||||
|
||||
private final CustomPDDocumentFactory pdfDocumentFactory;
|
||||
private final CertificateValidationService certValidationService;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ValidateSignatureController {
|
||||
@Operation(
|
||||
summary = "Validate PDF Digital Signature",
|
||||
description =
|
||||
"Validates the digital signatures in a PDF file against default or custom certificates. Input:PDF Output:JSON Type:SISO")
|
||||
"Validates digital signatures in a PDF file against default or custom certificates.")
|
||||
@PostMapping(value = "/validate-signature")
|
||||
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
|
||||
@ModelAttribute SignatureValidationRequest request) throws IOException {
|
||||
@@ -80,7 +80,6 @@ public class ValidateSignatureController {
|
||||
|
||||
for (PDSignature sig : signatures) {
|
||||
SignatureValidationResult result = new SignatureValidationResult();
|
||||
|
||||
try {
|
||||
byte[] signedContent = sig.getSignedContent(file.getInputStream());
|
||||
byte[] signatureBytes = sig.getContents(file.getInputStream());
|
||||
@@ -92,77 +91,90 @@ public class ValidateSignatureController {
|
||||
SignerInformationStore signerStore = signedData.getSignerInfos();
|
||||
|
||||
for (SignerInformation signer : signerStore.getSigners()) {
|
||||
X509CertificateHolder certHolder = (X509CertificateHolder) certStore.getMatches(signer.getSID()).iterator().next();
|
||||
X509Certificate cert = new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||
X509CertificateHolder certHolder =
|
||||
(X509CertificateHolder)
|
||||
certStore.getMatches(signer.getSID()).iterator().next();
|
||||
X509Certificate cert =
|
||||
new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||
|
||||
boolean isValid = signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));
|
||||
result.setValid(isValid);
|
||||
// Basic signature validation
|
||||
result.setValid(
|
||||
signer.verify(
|
||||
new JcaSimpleSignerInfoVerifierBuilder().build(cert)));
|
||||
|
||||
// Additional validations
|
||||
result.setChainValid(customCert != null
|
||||
? certValidationService.validateCertificateChainWithCustomCert(cert, customCert)
|
||||
: certValidationService.validateCertificateChain(cert));
|
||||
|
||||
result.setTrustValid(customCert != null
|
||||
? certValidationService.validateTrustWithCustomCert(cert, customCert)
|
||||
: certValidationService.validateTrustStore(cert));
|
||||
|
||||
result.setNotRevoked(!certValidationService.isRevoked(cert));
|
||||
result.setNotExpired(!cert.getNotAfter().before(new Date()));
|
||||
|
||||
// Set basic signature info
|
||||
result.setSignerName(sig.getName());
|
||||
result.setSignatureDate(sig.getSignDate().getTime().toString());
|
||||
result.setReason(sig.getReason());
|
||||
result.setLocation(sig.getLocation());
|
||||
|
||||
// Set new certificate details
|
||||
result.setIssuerDN(cert.getIssuerX500Principal().getName());
|
||||
result.setSubjectDN(cert.getSubjectX500Principal().getName());
|
||||
result.setSerialNumber(cert.getSerialNumber().toString(16)); // Hex format
|
||||
result.setValidFrom(cert.getNotBefore().toString());
|
||||
result.setValidUntil(cert.getNotAfter().toString());
|
||||
result.setSignatureAlgorithm(cert.getSigAlgName());
|
||||
|
||||
// Get key size (if possible)
|
||||
try {
|
||||
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
||||
} catch (Exception e) {
|
||||
// If not RSA or error, set to 0
|
||||
result.setKeySize(0);
|
||||
// Perform chain validation
|
||||
CertificateValidationService.ValidationResult chainResult;
|
||||
if (customCert != null) {
|
||||
chainResult =
|
||||
certValidationService.validateWithCustomCert(cert, customCert);
|
||||
} else {
|
||||
chainResult = certValidationService.validateCertificateChain(cert);
|
||||
}
|
||||
|
||||
result.setVersion(String.valueOf(cert.getVersion()));
|
||||
|
||||
// Set key usage
|
||||
List<String> keyUsages = new ArrayList<>();
|
||||
boolean[] keyUsageFlags = cert.getKeyUsage();
|
||||
if (keyUsageFlags != null) {
|
||||
String[] keyUsageLabels = {
|
||||
"Digital Signature", "Non-Repudiation", "Key Encipherment",
|
||||
"Data Encipherment", "Key Agreement", "Certificate Signing",
|
||||
"CRL Signing", "Encipher Only", "Decipher Only"
|
||||
};
|
||||
for (int i = 0; i < keyUsageFlags.length; i++) {
|
||||
if (keyUsageFlags[i]) {
|
||||
keyUsages.add(keyUsageLabels[i]);
|
||||
}
|
||||
result.setChainValid(chainResult.isValid());
|
||||
result.setTrustValid(chainResult.isValid());
|
||||
result.setNotExpired(!chainResult.isExpired());
|
||||
|
||||
// Check if signature was valid at the time of signing
|
||||
if (sig.getSignDate() != null) {
|
||||
try {
|
||||
cert.checkValidity(sig.getSignDate().getTime());
|
||||
result.setValidAtTimeOfSigning(true);
|
||||
} catch (CertificateExpiredException
|
||||
| CertificateNotYetValidException e) {
|
||||
result.setValidAtTimeOfSigning(false);
|
||||
}
|
||||
}
|
||||
result.setKeyUsages(keyUsages);
|
||||
|
||||
// Check if self-signed
|
||||
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
|
||||
|
||||
// Set signature info
|
||||
populateSignatureInfo(result, sig, cert);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.setValid(false);
|
||||
result.setErrorMessage("Signature validation failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
results.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(results);
|
||||
}
|
||||
|
||||
private void populateSignatureInfo(
|
||||
SignatureValidationResult result, PDSignature sig, X509Certificate cert) {
|
||||
result.setSignerName(sig.getName());
|
||||
result.setSignatureDate(sig.getSignDate().getTime().toString());
|
||||
result.setReason(sig.getReason());
|
||||
result.setLocation(sig.getLocation());
|
||||
result.setIssuerDN(cert.getIssuerX500Principal().getName());
|
||||
result.setSubjectDN(cert.getSubjectX500Principal().getName());
|
||||
result.setSerialNumber(cert.getSerialNumber().toString(16));
|
||||
result.setValidFrom(cert.getNotBefore().toString());
|
||||
result.setValidUntil(cert.getNotAfter().toString());
|
||||
result.setSignatureAlgorithm(cert.getSigAlgName());
|
||||
|
||||
try {
|
||||
result.setKeySize(((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());
|
||||
} catch (Exception e) {
|
||||
result.setKeySize(0);
|
||||
}
|
||||
|
||||
result.setVersion(String.valueOf(cert.getVersion()));
|
||||
|
||||
List<String> keyUsages = new ArrayList<>();
|
||||
boolean[] keyUsageFlags = cert.getKeyUsage();
|
||||
if (keyUsageFlags != null) {
|
||||
String[] keyUsageLabels = {
|
||||
"Digital Signature", "Non-Repudiation", "Key Encipherment",
|
||||
"Data Encipherment", "Key Agreement", "Certificate Signing",
|
||||
"CRL Signing", "Encipher Only", "Decipher Only"
|
||||
};
|
||||
for (int i = 0; i < keyUsageFlags.length; i++) {
|
||||
if (keyUsageFlags[i]) {
|
||||
keyUsages.add(keyUsageLabels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
result.setKeyUsages(keyUsages);
|
||||
result.setSelfSigned(cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ public class WatermarkController {
|
||||
float opacity = request.getOpacity();
|
||||
int widthSpacer = request.getWidthSpacer();
|
||||
int heightSpacer = request.getHeightSpacer();
|
||||
String customColor = request.getCustomColor();
|
||||
boolean convertPdfToImage = request.isConvertPDFToImage();
|
||||
|
||||
// Load the input PDF
|
||||
@@ -98,8 +97,7 @@ public class WatermarkController {
|
||||
widthSpacer,
|
||||
heightSpacer,
|
||||
fontSize,
|
||||
alphabet,
|
||||
customColor);
|
||||
alphabet);
|
||||
} else if ("image".equalsIgnoreCase(watermarkType)) {
|
||||
addImageWatermark(
|
||||
contentStream,
|
||||
@@ -138,8 +136,7 @@ public class WatermarkController {
|
||||
int widthSpacer,
|
||||
int heightSpacer,
|
||||
float fontSize,
|
||||
String alphabet,
|
||||
String colorString)
|
||||
String alphabet)
|
||||
throws IOException {
|
||||
String resourceDir = "";
|
||||
PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
|
||||
@@ -176,18 +173,7 @@ public class WatermarkController {
|
||||
}
|
||||
|
||||
contentStream.setFont(font, fontSize);
|
||||
|
||||
Color redactColor;
|
||||
try {
|
||||
if (!colorString.startsWith("#")) {
|
||||
colorString = "#" + colorString;
|
||||
}
|
||||
redactColor = Color.decode(colorString);
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
redactColor = Color.LIGHT_GRAY;
|
||||
}
|
||||
contentStream.setNonStrokingColor(redactColor);
|
||||
contentStream.setNonStrokingColor(Color.LIGHT_GRAY);
|
||||
|
||||
String[] textLines = watermarkText.split("\\\\n");
|
||||
float maxLineWidth = 0;
|
||||
|
||||
@@ -45,9 +45,6 @@ public class AddWatermarkRequest extends PDFFile {
|
||||
@Schema(description = "The height spacer between watermark elements", example = "50")
|
||||
private int heightSpacer;
|
||||
|
||||
@Schema(description = "The color for watermark", defaultValue = "#d3d3d3")
|
||||
private String customColor = "#d3d3d3";
|
||||
|
||||
@Schema(description = "Convert the redacted PDF to an image", defaultValue = "false")
|
||||
private boolean convertPDFToImage;
|
||||
}
|
||||
|
||||
@@ -16,16 +16,16 @@ public class SignatureValidationResult {
|
||||
private boolean trustValid;
|
||||
private boolean notExpired;
|
||||
private boolean notRevoked;
|
||||
|
||||
private String issuerDN; // Certificate issuer's Distinguished Name
|
||||
private String subjectDN; // Certificate subject's Distinguished Name
|
||||
private String serialNumber; // Certificate serial number
|
||||
private String validFrom; // Certificate validity start date
|
||||
private String validUntil; // Certificate validity end date
|
||||
private String signatureAlgorithm;// Algorithm used for signing
|
||||
private int keySize; // Key size in bits
|
||||
private String version; // Certificate version
|
||||
private List<String> keyUsages; // List of key usage purposes
|
||||
private boolean isSelfSigned; // Whether the certificate is self-signed
|
||||
|
||||
private boolean validAtTimeOfSigning;
|
||||
|
||||
private String issuerDN; // Certificate issuer's Distinguished Name
|
||||
private String subjectDN; // Certificate subject's Distinguished Name
|
||||
private String serialNumber; // Certificate serial number
|
||||
private String validFrom; // Certificate validity start date
|
||||
private String validUntil; // Certificate validity end date
|
||||
private String signatureAlgorithm; // Algorithm used for signing
|
||||
private int keySize; // Key size in bits
|
||||
private String version; // Certificate version
|
||||
private List<String> keyUsages; // List of key usage purposes
|
||||
private boolean isSelfSigned; // Whether the certificate is self-signed
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package stirling.software.SPDF.service;
|
||||
|
||||
import io.github.pixee.security.BoundedLineReader;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.*;
|
||||
import java.security.cert.CertPath;
|
||||
import java.security.cert.CertPathValidator;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
@@ -16,84 +14,126 @@ import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.PKIXParameters;
|
||||
import java.security.cert.TrustAnchor;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.*;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
|
||||
import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CertificateValidationService {
|
||||
private KeyStore trustStore;
|
||||
private static final String AATL_RESOURCE = "/tl12.acrobatsecuritysettings";
|
||||
|
||||
@PostConstruct
|
||||
private void initializeTrustStore() throws Exception {
|
||||
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
trustStore.load(null, null);
|
||||
loadMozillaCertificates();
|
||||
loadAATLCertificatesFromPDF();
|
||||
}
|
||||
|
||||
private void loadMozillaCertificates() throws Exception {
|
||||
try (InputStream is = getClass().getResourceAsStream("/certdata.txt")) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
String line;
|
||||
StringBuilder certData = new StringBuilder();
|
||||
boolean inCert = false;
|
||||
int certCount = 0;
|
||||
private void loadAATLCertificatesFromPDF() throws Exception {
|
||||
log.debug("Starting AATL certificate loading from PDF...");
|
||||
|
||||
while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {
|
||||
if (line.startsWith("CKA_VALUE MULTILINE_OCTAL")) {
|
||||
inCert = true;
|
||||
certData = new StringBuilder();
|
||||
continue;
|
||||
}
|
||||
if (inCert) {
|
||||
if ("END".equals(line)) {
|
||||
inCert = false;
|
||||
byte[] certBytes = parseOctalData(certData.toString());
|
||||
if (certBytes != null) {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate cert =
|
||||
(X509Certificate)
|
||||
cf.generateCertificate(
|
||||
new ByteArrayInputStream(certBytes));
|
||||
trustStore.setCertificateEntry("mozilla-cert-" + certCount++, cert);
|
||||
}
|
||||
} else {
|
||||
certData.append(line).append("\n");
|
||||
}
|
||||
try (InputStream pdfStream = new ClassPathResource(AATL_RESOURCE).getInputStream()) {
|
||||
PDDocument document = Loader.loadPDF(pdfStream.readAllBytes());
|
||||
|
||||
PDEmbeddedFilesNameTreeNode embeddedFiles =
|
||||
document.getDocumentCatalog().getNames().getEmbeddedFiles();
|
||||
Map<String, PDComplexFileSpecification> files = embeddedFiles.getNames();
|
||||
|
||||
for (Map.Entry<String, PDComplexFileSpecification> entry : files.entrySet()) {
|
||||
log.debug(entry.getKey());
|
||||
if (entry.getKey().equals("SecuritySettings.xml")) {
|
||||
byte[] xmlContent = entry.getValue().getEmbeddedFile().toByteArray();
|
||||
processSecuritySettingsXML(xmlContent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] parseOctalData(String data) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
String[] tokens = data.split("\\\\");
|
||||
for (String token : tokens) {
|
||||
token = token.trim();
|
||||
if (!token.isEmpty()) {
|
||||
baos.write(Integer.parseInt(token, 8));
|
||||
private void processSecuritySettingsXML(byte[] xmlContent) throws Exception {
|
||||
// Simple XML parsing using String operations
|
||||
String xmlString = new String(xmlContent, "UTF-8");
|
||||
int certCount = 0;
|
||||
int failedCerts = 0;
|
||||
|
||||
// Find all Certificate tags
|
||||
String startTag = "<Certificate>";
|
||||
String endTag = "</Certificate>";
|
||||
int startIndex = 0;
|
||||
|
||||
while ((startIndex = xmlString.indexOf(startTag, startIndex)) != -1) {
|
||||
int endIndex = xmlString.indexOf(endTag, startIndex);
|
||||
if (endIndex == -1) break;
|
||||
|
||||
// Extract certificate data
|
||||
String certData = xmlString.substring(startIndex + startTag.length(), endIndex).trim();
|
||||
startIndex = endIndex + endTag.length();
|
||||
|
||||
try {
|
||||
byte[] certBytes = Base64.getDecoder().decode(certData);
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate cert =
|
||||
(X509Certificate)
|
||||
cf.generateCertificate(new ByteArrayInputStream(certBytes));
|
||||
|
||||
// Only store root certificates (self-signed)
|
||||
if (cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal())) {
|
||||
trustStore.setCertificateEntry("aatl-cert-" + certCount, cert);
|
||||
log.trace(
|
||||
"Successfully loaded AATL root certificate #"
|
||||
+ certCount
|
||||
+ "\n Subject: "
|
||||
+ cert.getSubjectX500Principal().getName()
|
||||
+ "\n Valid until: "
|
||||
+ cert.getNotAfter());
|
||||
certCount++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failedCerts++;
|
||||
log.error("Failed to process AATL certificate: " + e.getMessage());
|
||||
}
|
||||
return baos.toByteArray();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
log.debug("AATL Certificate loading completed:");
|
||||
log.debug(" Total root certificates successfully loaded: " + certCount);
|
||||
log.debug(" Failed certificates: " + failedCerts);
|
||||
}
|
||||
|
||||
public boolean validateCertificateChain(X509Certificate cert) {
|
||||
@Data
|
||||
public static class ValidationResult {
|
||||
private boolean valid;
|
||||
private boolean expired;
|
||||
private boolean validAtSigningTime;
|
||||
private String errorMessage;
|
||||
}
|
||||
|
||||
public ValidationResult validateCertificateChain(X509Certificate signerCert) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
try {
|
||||
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
||||
// Build the certificate chain
|
||||
List<X509Certificate> certChain = buildCertificateChain(signerCert);
|
||||
|
||||
// Create certificate path
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
List<X509Certificate> certList = Arrays.asList(cert);
|
||||
CertPath certPath = cf.generateCertPath(certList);
|
||||
CertPath certPath = cf.generateCertPath(certChain);
|
||||
|
||||
// Set up trust anchors
|
||||
Set<TrustAnchor> anchors = new HashSet<>();
|
||||
Enumeration<String> aliases = trustStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
@@ -103,31 +143,113 @@ public class CertificateValidationService {
|
||||
}
|
||||
}
|
||||
|
||||
// Set up validation parameters
|
||||
PKIXParameters params = new PKIXParameters(anchors);
|
||||
params.setRevocationEnabled(false);
|
||||
|
||||
// Validate the path
|
||||
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
||||
validator.validate(certPath, params);
|
||||
return true;
|
||||
|
||||
result.setValid(true);
|
||||
result.setExpired(isExpired(signerCert));
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
result.setValid(false);
|
||||
result.setErrorMessage(e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateTrustStore(X509Certificate cert) {
|
||||
public ValidationResult validateWithCustomCert(
|
||||
X509Certificate signerCert, X509Certificate customCert) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
try {
|
||||
// Build the complete chain from signer cert
|
||||
List<X509Certificate> certChain = buildCertificateChain(signerCert);
|
||||
|
||||
// Check if custom cert matches any cert in the chain
|
||||
boolean matchFound = false;
|
||||
for (X509Certificate chainCert : certChain) {
|
||||
if (chainCert.equals(customCert)) {
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchFound) {
|
||||
// Check if custom cert is a valid issuer for any cert in the chain
|
||||
for (X509Certificate chainCert : certChain) {
|
||||
try {
|
||||
chainCert.verify(customCert.getPublicKey());
|
||||
matchFound = true;
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
// Continue checking next cert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.setValid(matchFound);
|
||||
if (!matchFound) {
|
||||
result.setErrorMessage(
|
||||
"Custom certificate is not part of the chain and is not a valid issuer");
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
result.setValid(false);
|
||||
result.setErrorMessage(e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private List<X509Certificate> buildCertificateChain(X509Certificate signerCert)
|
||||
throws CertificateException {
|
||||
List<X509Certificate> chain = new ArrayList<>();
|
||||
chain.add(signerCert);
|
||||
|
||||
X509Certificate current = signerCert;
|
||||
while (!isSelfSigned(current)) {
|
||||
X509Certificate issuer = findIssuer(current);
|
||||
if (issuer == null) break;
|
||||
chain.add(issuer);
|
||||
current = issuer;
|
||||
}
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
private boolean isSelfSigned(X509Certificate cert) {
|
||||
return cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
|
||||
}
|
||||
|
||||
private X509Certificate findIssuer(X509Certificate cert) throws CertificateException {
|
||||
try {
|
||||
Enumeration<String> aliases = trustStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
Object trustCert = trustStore.getCertificate(aliases.nextElement());
|
||||
if (trustCert instanceof X509Certificate && cert.equals(trustCert)) {
|
||||
return true;
|
||||
Certificate trustCert = trustStore.getCertificate(aliases.nextElement());
|
||||
if (trustCert instanceof X509Certificate) {
|
||||
X509Certificate x509TrustCert = (X509Certificate) trustCert;
|
||||
if (cert.getIssuerX500Principal()
|
||||
.equals(x509TrustCert.getSubjectX500Principal())) {
|
||||
try {
|
||||
cert.verify(x509TrustCert.getPublicKey());
|
||||
return x509TrustCert;
|
||||
} catch (Exception e) {
|
||||
// Continue searching if verification fails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (KeyStoreException e) {
|
||||
return false;
|
||||
throw new CertificateException("Error accessing trust store", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isRevoked(X509Certificate cert) {
|
||||
private boolean isExpired(X509Certificate cert) {
|
||||
try {
|
||||
cert.checkValidity();
|
||||
return false;
|
||||
@@ -135,23 +257,4 @@ public class CertificateValidationService {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateCertificateChainWithCustomCert(
|
||||
X509Certificate cert, X509Certificate customCert) {
|
||||
try {
|
||||
cert.verify(customCert.getPublicKey());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateTrustWithCustomCert(X509Certificate cert, X509Certificate customCert) {
|
||||
try {
|
||||
// Compare the issuer of the signature certificate with the custom certificate
|
||||
return cert.getIssuerX500Principal().equals(customCert.getSubjectX500Principal());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=تشفير
|
||||
#watermark
|
||||
watermark.title=إضافة علامة مائية
|
||||
watermark.header=إضافة علامة مائية
|
||||
watermark.customColor=لون نص مخصص
|
||||
watermark.selectText.1=حدد PDF لإضافة العلامة المائية إليه:
|
||||
watermark.selectText.2=نص العلامة المائية:
|
||||
watermark.selectText.3=حجم الخط:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Şifrlə
|
||||
#watermark
|
||||
watermark.title=Watermark Əlavə Et
|
||||
watermark.header=Watermark Əlavə Et
|
||||
watermark.customColor=Fərdi Mətn Rəngi
|
||||
watermark.selectText.1=Watermark əlavə olunacaq PDF-i seç
|
||||
watermark.selectText.2=Watermark Mətni:
|
||||
watermark.selectText.3=Şrift Ölçüsü:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Шифроване
|
||||
#watermark
|
||||
watermark.title=Добавяне на воден знак
|
||||
watermark.header=Добавяне на воден знак
|
||||
watermark.customColor=Персонализиран цвят на текста
|
||||
watermark.selectText.1=Изберете PDF, към който да добавите воден знак:
|
||||
watermark.selectText.2=Текст на воден знак:
|
||||
watermark.selectText.3=Размер на шрифта:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Encripta
|
||||
#watermark
|
||||
watermark.title=Afegir Marca d'Aigua
|
||||
watermark.header=Afegir Marca d'Aigua
|
||||
watermark.customColor=Color de Text Personalitzat
|
||||
watermark.selectText.1=Selecciona el PDF per afegir la Marca d'Aigua:
|
||||
watermark.selectText.2=Text de la Marca d'Aigua
|
||||
watermark.selectText.3=Mida de la Font:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Šifrovat
|
||||
#watermark
|
||||
watermark.title=Přidat vodoznak
|
||||
watermark.header=Přidat vodoznak
|
||||
watermark.customColor=Vlastní barva textu
|
||||
watermark.selectText.1=Vyberte PDF, ke kterému chcete přidat vodoznak:
|
||||
watermark.selectText.2=Text vodoznaku:
|
||||
watermark.selectText.3=Velikost písma:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Kryptér
|
||||
#watermark
|
||||
watermark.title=Tilføj Vandmærke
|
||||
watermark.header=Tilføj Vandmærke
|
||||
watermark.customColor=Brugerdefineret Tekstfarve
|
||||
watermark.selectText.1=Vælg PDF til at tilføje vandmærke:
|
||||
watermark.selectText.2=Vandmærketekst:
|
||||
watermark.selectText.3=Skriftstørrelse:
|
||||
|
||||
@@ -146,8 +146,8 @@ navbar.search=Suche
|
||||
navbar.sections.organize=Organisieren
|
||||
navbar.sections.convertTo=In PDF konvertieren
|
||||
navbar.sections.convertFrom=Konvertieren von PDF
|
||||
navbar.sections.security=Signieren und Sicherheit
|
||||
navbar.sections.advance=Erweiterte Funktionen
|
||||
navbar.sections.security=Zeichen und Sicherheit
|
||||
navbar.sections.advance=Fortschrittlich
|
||||
navbar.sections.edit=Anzeigen und Bearbeiten
|
||||
navbar.sections.popular=Beliebt
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Verschlüsseln
|
||||
#watermark
|
||||
watermark.title=Wasserzeichen hinzufügen
|
||||
watermark.header=Wasserzeichen hinzufügen
|
||||
watermark.customColor=Benutzerdefinierte Textfarbe
|
||||
watermark.selectText.1=PDF auswählen, dem ein Wasserzeichen hinzugefügt werden soll:
|
||||
watermark.selectText.2=Wasserzeichen Text:
|
||||
watermark.selectText.3=Schriftgröße:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Κρυπτογράφηση
|
||||
#watermark
|
||||
watermark.title=Προσθήκη Υδατογραφήματος
|
||||
watermark.header=Προσθήκη Υδατογραφήματος
|
||||
watermark.customColor=Προσαρμοσμένο χρώμα κειμένου
|
||||
watermark.selectText.1=Επιλέξτε PDF για την προσθήκη του υδατογραφήματος:
|
||||
watermark.selectText.2=Κείμενο Υδατογραφήματος:
|
||||
watermark.selectText.3=Μέγεθος Κειμένου:
|
||||
|
||||
@@ -419,7 +419,7 @@ home.auto-rename.title=Auto Rename PDF File
|
||||
home.auto-rename.desc=Auto renames a PDF file based on its detected header
|
||||
auto-rename.tags=auto-detect,header-based,organize,relabel
|
||||
|
||||
home.adjust-contrast.title=Adjust Colours/Contrast
|
||||
home.adjust-contrast.title=Adjust Coloors/Contrast
|
||||
home.adjust-contrast.desc=Adjust Contrast, Saturation and Brightness of a PDF
|
||||
adjust-contrast.tags=color-correction,tune,modify,enhance,colour-correction
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Encrypt
|
||||
#watermark
|
||||
watermark.title=Add Watermark
|
||||
watermark.header=Add Watermark
|
||||
watermark.customColor=Custom Text Colour
|
||||
watermark.selectText.1=Select PDF to add watermark to:
|
||||
watermark.selectText.2=Watermark Text:
|
||||
watermark.selectText.3=Font Size:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Encrypt
|
||||
#watermark
|
||||
watermark.title=Add Watermark
|
||||
watermark.header=Add Watermark
|
||||
watermark.customColor=Custom Text Color
|
||||
watermark.selectText.1=Select PDF to add watermark to:
|
||||
watermark.selectText.2=Watermark Text:
|
||||
watermark.selectText.3=Font Size:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Encriptar
|
||||
#watermark
|
||||
watermark.title=Añadir marca de agua
|
||||
watermark.header=Añadir marca de agua
|
||||
watermark.customColor=Personalizar color de texto
|
||||
watermark.selectText.1=Seleccionar PDF para añadir marca de agua:
|
||||
watermark.selectText.2=Texto de la marca de agua:
|
||||
watermark.selectText.3=Tamaño de la Fuente:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Enkriptatu
|
||||
#watermark
|
||||
watermark.title=Gehitu ur-marka
|
||||
watermark.header=Gehitu ur-marka
|
||||
watermark.customColor=Custom Text Color
|
||||
watermark.selectText.1=Hautatu PDFa ur-marka gehitzeko:
|
||||
watermark.selectText.2=Ur-markaren testua:
|
||||
watermark.selectText.3=Letra-tipoaren tamaina:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1056,7 +1056,6 @@ addPassword.submit=Chiffrer
|
||||
#watermark
|
||||
watermark.title=Ajouter un filigrane
|
||||
watermark.header=Ajouter un filigrane
|
||||
watermark.customColor=Couleur de texte personnalisée
|
||||
watermark.selectText.1=PDF auquel ajouter un filigrane
|
||||
watermark.selectText.2=Texte du filigrane
|
||||
watermark.selectText.3=Taille de police
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Criptigh
|
||||
#watermark
|
||||
watermark.title=Cuir Uisce leis
|
||||
watermark.header=Cuir Uisce leis
|
||||
watermark.customColor=Dath Téacs Saincheaptha
|
||||
watermark.selectText.1=Roghnaigh PDF chun comhartha uisce a chur leis:
|
||||
watermark.selectText.2=Téacs Comhartha Uisce:
|
||||
watermark.selectText.3=Méid cló:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=एन्क्रिप्ट करें
|
||||
#watermark
|
||||
watermark.title=वॉटरमार्क जोड़ें
|
||||
watermark.header=वॉटरमार्क जोड़ें
|
||||
watermark.customColor=संवैधित टेक्स्ट रंग
|
||||
watermark.selectText.1=वॉटरमार्क जोड़ने के लिए पीडीएफ चुनें:
|
||||
watermark.selectText.2=वॉटरमार्क टेक्स्ट:
|
||||
watermark.selectText.3=फ़ॉन्ट साइज़:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Šifriraj
|
||||
#watermark
|
||||
watermark.title=Dodaj vodeni žig
|
||||
watermark.header=Dodaj vodeni žig
|
||||
watermark.customColor=Prilagođena boja teksta
|
||||
watermark.selectText.1=Izaberite PDF za dodavanje vodenog žiga:
|
||||
watermark.selectText.2=Tekst vodenog žiga:
|
||||
watermark.selectText.3=Veličina fonta:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Titkosítás
|
||||
#watermark
|
||||
watermark.title=Vízjel hozzáadása
|
||||
watermark.header=Vízjel hozzáadása
|
||||
watermark.customColor=Egyéni szövegszín
|
||||
watermark.selectText.1=Válassza ki a PDF-t, amelyhez vízjelet kíván hozzáadni:
|
||||
watermark.selectText.2=Vízjel szövege:
|
||||
watermark.selectText.3=Betűméret:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Enkripsi
|
||||
#watermark
|
||||
watermark.title=Tambahkan Watermark
|
||||
watermark.header=Tambahkan Watermark
|
||||
watermark.customColor=Warna Teks Kustom
|
||||
watermark.selectText.1=Pilih PDF untuk menambahkan watermark:
|
||||
watermark.selectText.2=Text Watermark:
|
||||
watermark.selectText.3=Ukuran Huruf:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Crittografa
|
||||
#watermark
|
||||
watermark.title=Aggiungi Filigrana
|
||||
watermark.header=Aggiungi filigrana
|
||||
watermark.customColor=Colore testo personalizzato
|
||||
watermark.selectText.1=Seleziona PDF a cui aggiungere la filigrana:
|
||||
watermark.selectText.2=Testo:
|
||||
watermark.selectText.3=Dimensione carattere:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=暗号化
|
||||
#watermark
|
||||
watermark.title=透かしの追加
|
||||
watermark.header=透かしの追加
|
||||
watermark.customColor=文字色のカスタム
|
||||
watermark.selectText.1=透かしを追加するPDFを選択:
|
||||
watermark.selectText.2=透かしのテキスト:
|
||||
watermark.selectText.3=文字サイズ:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=암호화
|
||||
#watermark
|
||||
watermark.title=워터마크 추가
|
||||
watermark.header=워터마크 추가
|
||||
watermark.customColor=사용자 정의 텍스트 색상
|
||||
watermark.selectText.1=워터마크를 추가할 PDF 선택:
|
||||
watermark.selectText.2=워터마크 텍스트:
|
||||
watermark.selectText.3=폰트 크기:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Versleutelen
|
||||
#watermark
|
||||
watermark.title=Watermerk toevoegen
|
||||
watermark.header=Watermerk toevoegen
|
||||
watermark.customColor=Aangepaste tekstkleur
|
||||
watermark.selectText.1=Selecteer PDF om watermerk toe te voegen:
|
||||
watermark.selectText.2=Watermerk tekst:
|
||||
watermark.selectText.3=Tekengrootte:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Krypter
|
||||
#watermark
|
||||
watermark.title=Legg til vannmerke
|
||||
watermark.header=Legg til vannmerke
|
||||
watermark.customColor=Tilpasset Tekstfarge
|
||||
watermark.selectText.1=Velg PDF-fil å legge til vannmerke på:
|
||||
watermark.selectText.2=Vannmerketekst:
|
||||
watermark.selectText.3=Skriftstørrelse:
|
||||
|
||||
1
src/main/resources/messages_pl_PL.properties
Normal file → Executable file
1
src/main/resources/messages_pl_PL.properties
Normal file → Executable file
@@ -1056,7 +1056,6 @@ addPassword.submit=Zablokuj
|
||||
#watermark
|
||||
watermark.title=Dodaj znak wodny
|
||||
watermark.header=Dodaj znak wodny
|
||||
watermark.customColor=Własny kolor tekstu
|
||||
watermark.selectText.1=Wybierz dokument PDF, do którego chcesz dodać znak wodny:
|
||||
watermark.selectText.2=Treść znaku wodnego:
|
||||
watermark.selectText.3=Rozmiar czcionki:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Criptografar
|
||||
#watermark
|
||||
watermark.title=Adicionar marca d'água
|
||||
watermark.header=Adicionar marca d'água
|
||||
watermark.customColor=Cor de texto personalizada
|
||||
watermark.selectText.1=Selecione PDF para adicionar a marca d'água:
|
||||
watermark.selectText.2=Texto da marca d'água:
|
||||
watermark.selectText.3=Tamanho da fonte:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Proteger
|
||||
#watermark
|
||||
watermark.title=Adicionar Marca d'Água
|
||||
watermark.header=Adicionar Marca d'Água
|
||||
watermark.customColor=Personalizar a cor do texto
|
||||
watermark.selectText.1=Seleccione o PDF para Adicionar a Marca d'Água
|
||||
watermark.selectText.2=Texto da Marca d'Água
|
||||
watermark.selectText.3=Tamanho da Fonte
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Criptează
|
||||
#watermark
|
||||
watermark.title=Adaugă Filigran
|
||||
watermark.header=Adaugă Filigran
|
||||
watermark.customColor=Culoare Text Personalizată
|
||||
watermark.selectText.1=Selectează PDF-ul la care să adaugi filigranul:
|
||||
watermark.selectText.2=Textul Filigranului:
|
||||
watermark.selectText.3=Mărimea fontului:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Шифровать
|
||||
#watermark
|
||||
watermark.title=Добавить водяной знак
|
||||
watermark.header=Добавить водяной знак
|
||||
watermark.customColor=Настроенный цвет текста
|
||||
watermark.selectText.1=Выберите PDF, чтобы добавить водяной знак:
|
||||
watermark.selectText.2=Текст водяного знака:
|
||||
watermark.selectText.3=Размер шрифта:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Zašifrovať
|
||||
#watermark
|
||||
watermark.title=Pridať vodotlač
|
||||
watermark.header=Pridať vodotlač
|
||||
watermark.customColor=Vlastná farba textu
|
||||
watermark.selectText.1=Vyberte PDF, do ktorého chcete pridať vodotlač:
|
||||
watermark.selectText.2=Text vodotlače:
|
||||
watermark.selectText.3=Veľkosť písma:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Enkriptuj
|
||||
#watermark
|
||||
watermark.title=Dodaj vodeni žig
|
||||
watermark.header=Dodaj vodeni žig
|
||||
watermark.customColor=Custom Text Color
|
||||
watermark.selectText.1=Izaberite PDF za dodavanje vodenog žiga:
|
||||
watermark.selectText.2=Tekst vodenog žiga:
|
||||
watermark.selectText.3=Veličina fonta:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Kryptera
|
||||
#watermark
|
||||
watermark.title=Lägg till vattenstämpel
|
||||
watermark.header=Lägg till vattenstämpel
|
||||
watermark.customColor=Anpassad textfärg
|
||||
watermark.selectText.1=Välj PDF för att lägga till vattenstämpel till:
|
||||
watermark.selectText.2=Vattenmärkestext:
|
||||
watermark.selectText.3=Teckenstorlek:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=เข้ารหัส
|
||||
#watermark
|
||||
watermark.title=เพิ่มลายน้ำ
|
||||
watermark.header=เพิ่มลายน้ำ
|
||||
watermark.customColor=สีข้อความที่กำหนดเอง
|
||||
watermark.selectText.1=เลือก PDF เพื่อเพิ่มลายน้ำ:
|
||||
watermark.selectText.2=ข้อความลายน้ำ:
|
||||
watermark.selectText.3=ขนาดฟอนต์:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Şifrele
|
||||
#watermark
|
||||
watermark.title=Filigran Ekle
|
||||
watermark.header=Filigran Ekle
|
||||
watermark.customColor=Özel Metin Rengi
|
||||
watermark.selectText.1=Filigran eklemek için PDF seçin:
|
||||
watermark.selectText.2=Filigran Metni:
|
||||
watermark.selectText.3=Yazı Boyutu:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Шифрувати
|
||||
#watermark
|
||||
watermark.title=Додати водяний знак
|
||||
watermark.header=Додати водяний знак
|
||||
watermark.customColor=Користувацький колір тексту
|
||||
watermark.selectText.1=Виберіть PDF, щоб додати водяний знак:
|
||||
watermark.selectText.2=Текст водяного знаку:
|
||||
watermark.selectText.3=Розмір шрифту:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=Mã hóa
|
||||
#watermark
|
||||
watermark.title=Thêm hình mờ
|
||||
watermark.header=Thêm hình mờ
|
||||
watermark.customColor=Màu văn bản tùy chỉnh
|
||||
watermark.selectText.1=Chọn PDF để thêm hình mờ:
|
||||
watermark.selectText.2=Văn bản hình mờ:
|
||||
watermark.selectText.3=Cỡ chữ:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=加密
|
||||
#watermark
|
||||
watermark.title=添加水印
|
||||
watermark.header=添加水印
|
||||
watermark.customColor=自定义文本颜色
|
||||
watermark.selectText.1=选择要添加水印的PDF:
|
||||
watermark.selectText.2=水印文本:
|
||||
watermark.selectText.3=字体大小:
|
||||
|
||||
@@ -1056,7 +1056,6 @@ addPassword.submit=加密
|
||||
#watermark
|
||||
watermark.title=新增浮水印
|
||||
watermark.header=新增浮水印
|
||||
watermark.customColor=自訂文字顏色
|
||||
watermark.selectText.1=選擇要新增浮水印的 PDF:
|
||||
watermark.selectText.2=浮水印文字:
|
||||
watermark.selectText.3=字型大小:
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ir" viewBox="0 0 640 480">
|
||||
<defs>
|
||||
<clipPath id="ir-a">
|
||||
<path fill-opacity=".7" d="M-85.3 0h682.7v512H-85.3z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g fill-rule="evenodd" clip-path="url(#ir-a)" transform="translate(80)scale(.9375)">
|
||||
<path fill="#fff" d="M-192 0h896v512h-896z"/>
|
||||
<path fill="#da0000" d="M-192 343.8h896V512h-896z"/>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M-21.6 351h49v3.3h-49zm7.3 16.8h3.4v3.3h-3.4zm41.9 0v3.3h-9.8v-3.4zm5.2-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M52.4 367.7v3.4H33.8v-3.4zm-34.6-7.9H21v11.3h-3.3z"/>
|
||||
<path d="M49.6 351H53v20h-3.4zm-8.4 0h3.3v20h-3.3zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M17.8 359.9H21V371h-3.3z"/>
|
||||
<path d="M17.8 359.9H21V371h-3.3z"/>
|
||||
<path d="M17.8 359.9H21V371h-3.3zm-39.3 0h3.3V371h-3.3zm28.8 0h3.4V371H7.3zm-14.3 0h3.4V371H-7z"/>
|
||||
<path d="M9.6 367.7v3.4H-5.5v-3.4zm1-8.7v3.4H1V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M-102.2 351h49v3.3h-49zm7.3 16.8h3.4v3.3H-95zm41.9 0v3.3h-9.8v-3.4zm5.2-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M-28.2 367.7v3.4h-18.6v-3.4zm-34.6-7.9h3.3v11.3h-3.3z"/>
|
||||
<path d="M-31 351h3.4v20H-31zm-8.4 0h3.3v20h-3.3zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M-62.8 359.9h3.3V371h-3.3z"/>
|
||||
<path d="M-62.8 359.9h3.3V371h-3.3z"/>
|
||||
<path d="M-62.8 359.9h3.3V371h-3.3zm-39.3 0h3.3V371h-3.3zm28.8 0h3.3V371h-3.3zm-14.3 0h3.4V371h-3.4z"/>
|
||||
<path d="M-71 367.7v3.4h-15v-3.4zm1-8.7v3.4h-9.6V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M58.3 351h49v3.3h-49zm7.3 16.8H69v3.3h-3.4zm41.9 0v3.3h-9.8v-3.4zm5.3-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M132.3 367.7v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M129.5 351h3.4v20h-3.4zm-8.4 0h3.4v20H121zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M97.7 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M97.7 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M97.7 359.9h3.4V371h-3.4zm-39.3 0h3.4V371h-3.4zm28.8 0h3.4V371h-3.4zm-14.3 0h3.4V371h-3.4z"/>
|
||||
<path d="M89.6 367.7v3.4H74.4v-3.4zm1-8.7v3.4H81V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M622.7 351h49v3.3h-49zm7.3 16.8h3.4v3.3H630zm41.9 0v3.3H662v-3.4zm5.3-16.8h3.3v20h-3.4z"/>
|
||||
<path d="M696.7 367.7v3.4H678v-3.4zm-34.6-7.9h3.4v11.3H662z"/>
|
||||
<path d="M694 351h3.3v20h-3.4zm-8.5 0h3.4v20h-3.4zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M662 359.9h3.5V371H662z"/>
|
||||
<path d="M662 359.9h3.5V371H662z"/>
|
||||
<path d="M662 359.9h3.5V371H662zm-39.2 0h3.4V371h-3.4zm28.8 0h3.4V371h-3.4zm-14.3 0h3.4V371h-3.4z"/>
|
||||
<path d="M654 367.7v3.4h-15.2v-3.4zm1-8.7v3.4h-9.6V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M138.7 351h49.1v3.3h-49zm7.4 16.8h3.3v3.3h-3.3zm41.8 0v3.3h-9.8v-3.4zm5.3-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M212.8 367.7v3.4h-18.6v-3.4zm-34.7-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M210 351h3.4v20H210zm-8.5 0h3.4v20h-3.4zm-44.8 8v3.4h-17.9V359zm39.3 0v3.4h-17.9V359z"/>
|
||||
<path d="M178.1 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M178.1 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M178.1 359.9h3.4V371h-3.4zm-39.3 0h3.4V371h-3.4zm28.8 0h3.4V371h-3.4zm-14.2 0h3.3V371h-3.3z"/>
|
||||
<path d="M170 367.7v3.4h-15.1v-3.4zm1-8.7v3.4h-9.6V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M219.5 351h49v3.3h-49zm7.3 16.8h3.4v3.3h-3.4zm41.9 0v3.3h-9.8v-3.4zM274 351h3.3v20H274z"/>
|
||||
<path d="M293.5 367.7v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M290.7 351h3.4v20h-3.4zm-8.4 0h3.4v20h-3.4zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M258.9 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M258.9 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M258.9 359.9h3.4V371h-3.4zm-39.3 0h3.3V371h-3.3zm28.8 0h3.4V371h-3.4zm-14.3 0h3.4V371H234z"/>
|
||||
<path d="M250.8 367.7v3.4h-15.2v-3.4zm1-8.7v3.4H242V359z"/>
|
||||
</g>
|
||||
<path fill="#239f40" d="M-192 0h896v168.2h-896z"/>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M300.7 351h49v3.3h-49zm7.3 16.8h3.4v3.3H308zm41.9 0v3.3H340v-3.4zm5.3-16.8h3.3v20h-3.3z"/>
|
||||
<path d="M374.7 367.7v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3H340z"/>
|
||||
<path d="M372 351h3.3v20H372zm-8.5 0h3.4v20h-3.4zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M340 359.9h3.5V371H340z"/>
|
||||
<path d="M340 359.9h3.5V371H340z"/>
|
||||
<path d="M340 359.9h3.5V371H340zm-39.2 0h3.4V371h-3.4zm28.8 0h3.4V371h-3.4zm-14.3 0h3.4V371h-3.4z"/>
|
||||
<path d="M332 367.7v3.4h-15.2v-3.4zm1-8.7v3.4h-9.6V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M381.4 351h49v3.3h-49zm7.3 16.8h3.4v3.3h-3.4zm42 0v3.3h-9.9v-3.4zm5.2-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M455.4 367.7v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M452.7 351h3.3v20h-3.3zm-8.5 0h3.4v20h-3.4zm-44.8 8v3.4h-17.9V359zm39.3 0v3.4h-17.9V359z"/>
|
||||
<path d="M420.8 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M420.8 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M420.8 359.9h3.4V371h-3.4zm-39.3 0h3.4V371h-3.4zm28.8 0h3.4V371h-3.4zm-14.3 0h3.4V371h-3.3z"/>
|
||||
<path d="M412.7 367.7v3.4h-15.1v-3.4zm1-8.7v3.4H404V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M462.2 351h49v3.3h-49zm7.3 16.8h3.4v3.3h-3.4zm41.9 0v3.3h-9.8v-3.4zm5.2-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M536.2 367.7v3.4h-18.6v-3.4zm-34.7-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M533.4 351h3.4v20h-3.4zm-8.4 0h3.3v20H525zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M501.6 359.9h3.3V371h-3.3z"/>
|
||||
<path d="M501.6 359.9h3.3V371h-3.3z"/>
|
||||
<path d="M501.6 359.9h3.3V371h-3.3zm-39.4 0h3.4V371h-3.4zm28.9 0h3.3V371h-3.3zm-14.3 0h3.4V371h-3.4z"/>
|
||||
<path d="M493.4 367.7v3.4h-15.1v-3.4zm1-8.7v3.4h-9.6V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M543.4 351h49v3.3h-49zm7.3 16.8h3.4v3.3h-3.4zm41.9 0v3.3h-9.8v-3.4zm5.2-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M617.4 367.7v3.4h-18.6v-3.4zm-34.6-7.9h3.3v11.3h-3.3z"/>
|
||||
<path d="M614.6 351h3.4v20h-3.4zm-8.4 0h3.3v20h-3.3zm-44.8 8v3.4h-18V359zm39.3 0v3.4h-18V359z"/>
|
||||
<path d="M582.8 359.9h3.3V371h-3.3z"/>
|
||||
<path d="M582.8 359.9h3.3V371h-3.3z"/>
|
||||
<path d="M582.8 359.9h3.3V371h-3.3zm-39.3 0h3.3V371h-3.3zm28.8 0h3.4V371h-3.4zm-14.3 0h3.4V371H558z"/>
|
||||
<path d="M574.6 367.7v3.4h-15.1v-3.4zm1-8.7v3.4H566V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M-183.8 351h49v3.3h-49zm7.3 16.8h3.4v3.3h-3.4zm42 0v3.3h-9.9v-3.4zm5.2-16.8h3.4v20h-3.4z"/>
|
||||
<path d="M-109.8 367.7v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M-112.5 351h3.3v20h-3.3zm-8.5 0h3.4v20h-3.4zm-44.8 8v3.4h-17.9V359zm39.3 0v3.4h-17.9V359z"/>
|
||||
<path d="M-144.4 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M-144.4 359.9h3.4V371h-3.4z"/>
|
||||
<path d="M-144.4 359.9h3.4V371h-3.4zm-39.3 0h3.4V371h-3.4zm28.8 0h3.4V371h-3.4zm-14.3 0h3.4V371h-3.4z"/>
|
||||
<path d="M-152.5 367.7v3.4h-15.2v-3.4zm1-8.7v3.4h-9.6V359z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M-21.6 143.4h49v3.4h-49zm7.3 17h3.4v3.2h-3.4zm41.9-.2v3.4h-9.8v-3.4zm5.2-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M52.4 160.2v3.4H33.8v-3.4zm-34.6-7.9H21v11.3h-3.3z"/>
|
||||
<path d="M49.6 143.4H53v20.2h-3.4zm-8.4 0h3.3v20.2h-3.3zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M17.8 152.3H21v11.3h-3.3z"/>
|
||||
<path d="M17.8 152.3H21v11.3h-3.3z"/>
|
||||
<path d="M17.8 152.3H21v11.3h-3.3zm-39.3 0h3.3v11.3h-3.3zm28.8 0h3.4v11.3H7.3zm-14.3 0h3.4v11.3H-7z"/>
|
||||
<path d="M9.6 160.2v3.4H-5.5v-3.4zm1-8.7v3.3H1v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M-102.2 143.4h49v3.4h-49zm7.3 17h3.4v3.2H-95zm41.9-.2v3.4h-9.8v-3.4zm5.2-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M-28.2 160.2v3.4h-18.6v-3.4zm-34.6-7.9h3.3v11.3h-3.3z"/>
|
||||
<path d="M-31 143.4h3.4v20.2H-31zm-8.4 0h3.3v20.2h-3.3zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M-62.8 152.3h3.3v11.3h-3.3z"/>
|
||||
<path d="M-62.8 152.3h3.3v11.3h-3.3z"/>
|
||||
<path d="M-62.8 152.3h3.3v11.3h-3.3zm-39.3 0h3.3v11.3h-3.3zm28.8 0h3.3v11.3h-3.3zm-14.3 0h3.4v11.3h-3.4z"/>
|
||||
<path d="M-71 160.2v3.4h-15v-3.4zm1-8.7v3.3h-9.6v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M58.3 143.4h49v3.4h-49zm7.3 17H69v3.2h-3.4zm41.9-.2v3.4h-9.8v-3.4zm5.3-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M132.3 160.2v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M129.5 143.4h3.4v20.2h-3.4zm-8.4 0h3.4v20.2H121zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M97.7 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M97.7 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M97.7 152.3h3.4v11.3h-3.4zm-39.3 0h3.4v11.3h-3.4zm28.8 0h3.4v11.3h-3.4zm-14.3 0h3.4v11.3h-3.4z"/>
|
||||
<path d="M89.6 160.2v3.4H74.4v-3.4zm1-8.7v3.3H81v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M622.7 143.4h49v3.4h-49zm7.3 17h3.4v3.2H630zm41.9-.2v3.4H662v-3.4zm5.3-16.8h3.3v20.2h-3.4z"/>
|
||||
<path d="M696.7 160.2v3.4H678v-3.4zm-34.6-7.9h3.4v11.3H662z"/>
|
||||
<path d="M694 143.4h3.3v20.2h-3.4zm-8.5 0h3.4v20.2h-3.4zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M662 152.3h3.5v11.3H662z"/>
|
||||
<path d="M662 152.3h3.5v11.3H662z"/>
|
||||
<path d="M662 152.3h3.5v11.3H662zm-39.2 0h3.4v11.3h-3.4zm28.8 0h3.4v11.3h-3.4zm-14.3 0h3.4v11.3h-3.4z"/>
|
||||
<path d="M654 160.2v3.4h-15.2v-3.4zm1-8.7v3.3h-9.6v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M138.7 143.4h49.1v3.4h-49zm7.4 17h3.3v3.2h-3.3zm41.8-.2v3.4h-9.8v-3.4zm5.3-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M212.8 160.2v3.4h-18.6v-3.4zm-34.7-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M210 143.4h3.4v20.2H210zm-8.5 0h3.4v20.2h-3.4zm-44.8 8v3.4h-17.9v-3.3zm39.3 0v3.4h-17.9v-3.3z"/>
|
||||
<path d="M178.1 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M178.1 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M178.1 152.3h3.4v11.3h-3.4zm-39.3 0h3.4v11.3h-3.4zm28.8 0h3.4v11.3h-3.4zm-14.2 0h3.3v11.3h-3.3z"/>
|
||||
<path d="M170 160.2v3.4h-15.1v-3.4zm1-8.7v3.3h-9.6v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M219.5 143.4h49v3.4h-49zm7.3 17h3.4v3.2h-3.4zm41.9-.2v3.4h-9.8v-3.4zm5.3-16.8h3.3v20.2H274z"/>
|
||||
<path d="M293.5 160.2v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M290.7 143.4h3.4v20.2h-3.4zm-8.4 0h3.4v20.2h-3.4zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M258.9 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M258.9 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M258.9 152.3h3.4v11.3h-3.4zm-39.3 0h3.3v11.3h-3.3zm28.8 0h3.4v11.3h-3.4zm-14.3 0h3.4v11.3H234z"/>
|
||||
<path d="M250.8 160.2v3.4h-15.2v-3.4zm1-8.7v3.3H242v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M300.7 143.4h49v3.4h-49zm7.3 17h3.4v3.2H308zm41.9-.2v3.4H340v-3.4zm5.3-16.8h3.3v20.2h-3.3z"/>
|
||||
<path d="M374.7 160.2v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3H340z"/>
|
||||
<path d="M372 143.4h3.3v20.2H372zm-8.5 0h3.4v20.2h-3.4zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M340 152.3h3.5v11.3H340z"/>
|
||||
<path d="M340 152.3h3.5v11.3H340z"/>
|
||||
<path d="M340 152.3h3.5v11.3H340zm-39.2 0h3.4v11.3h-3.4zm28.8 0h3.4v11.3h-3.4zm-14.3 0h3.4v11.3h-3.4z"/>
|
||||
<path d="M332 160.2v3.4h-15.2v-3.4zm1-8.7v3.3h-9.6v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M381.4 143.4h49v3.4h-49zm7.3 17h3.4v3.2h-3.4zm42-.2v3.4h-9.9v-3.4zm5.2-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M455.4 160.2v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M452.7 143.4h3.3v20.2h-3.3zm-8.5 0h3.4v20.2h-3.4zm-44.8 8v3.4h-17.9v-3.3zm39.3 0v3.4h-17.9v-3.3z"/>
|
||||
<path d="M420.8 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M420.8 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M420.8 152.3h3.4v11.3h-3.4zm-39.3 0h3.4v11.3h-3.4zm28.8 0h3.4v11.3h-3.4zm-14.3 0h3.4v11.3h-3.3z"/>
|
||||
<path d="M412.7 160.2v3.4h-15.1v-3.4zm1-8.7v3.3H404v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M462.2 143.4h49v3.4h-49zm7.3 17h3.4v3.2h-3.4zm41.9-.2v3.4h-9.8v-3.4zm5.2-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M536.2 160.2v3.4h-18.6v-3.4zm-34.7-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M533.4 143.4h3.4v20.2h-3.4zm-8.4 0h3.3v20.2H525zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M501.6 152.3h3.3v11.3h-3.3z"/>
|
||||
<path d="M501.6 152.3h3.3v11.3h-3.3z"/>
|
||||
<path d="M501.6 152.3h3.3v11.3h-3.3zm-39.4 0h3.4v11.3h-3.4zm28.9 0h3.3v11.3h-3.3zm-14.3 0h3.4v11.3h-3.4z"/>
|
||||
<path d="M493.4 160.2v3.4h-15.1v-3.4zm1-8.7v3.3h-9.6v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M543.4 143.4h49v3.4h-49zm7.3 17h3.4v3.2h-3.4zm41.9-.2v3.4h-9.8v-3.4zm5.2-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M617.4 160.2v3.4h-18.6v-3.4zm-34.6-7.9h3.3v11.3h-3.3z"/>
|
||||
<path d="M614.6 143.4h3.4v20.2h-3.4zm-8.4 0h3.3v20.2h-3.3zm-44.8 8v3.4h-18v-3.3zm39.3 0v3.4h-18v-3.3z"/>
|
||||
<path d="M582.8 152.3h3.3v11.3h-3.3z"/>
|
||||
<path d="M582.8 152.3h3.3v11.3h-3.3z"/>
|
||||
<path d="M582.8 152.3h3.3v11.3h-3.3zm-39.3 0h3.3v11.3h-3.3zm28.8 0h3.4v11.3h-3.4zm-14.3 0h3.4v11.3H558z"/>
|
||||
<path d="M574.6 160.2v3.4h-15.1v-3.4zm1-8.7v3.3H566v-3.3z"/>
|
||||
</g>
|
||||
<g fill="#fff" stroke-width="1pt">
|
||||
<path d="M-183.8 143.4h49v3.4h-49zm7.3 17h3.4v3.2h-3.4zm42-.2v3.4h-9.9v-3.4zm5.2-16.8h3.4v20.2h-3.4z"/>
|
||||
<path d="M-109.8 160.2v3.4h-18.6v-3.4zm-34.6-7.9h3.4v11.3h-3.4z"/>
|
||||
<path d="M-112.5 143.4h3.3v20.2h-3.3zm-8.5 0h3.4v20.2h-3.4zm-44.8 8v3.4h-17.9v-3.3zm39.3 0v3.4h-17.9v-3.3z"/>
|
||||
<path d="M-144.4 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M-144.4 152.3h3.4v11.3h-3.4z"/>
|
||||
<path d="M-144.4 152.3h3.4v11.3h-3.4zm-39.3 0h3.4v11.3h-3.4zm28.8 0h3.4v11.3h-3.4zm-14.3 0h3.4v11.3h-3.4z"/>
|
||||
<path d="M-152.5 160.2v3.4h-15.2v-3.4zm1-8.7v3.3h-9.6v-3.3z"/>
|
||||
</g>
|
||||
<path fill="#d90000" d="M-68.8 339.5h6V350h-6zm160.5 0h6V350h-6zm-283.7 0h6V350h-6zm81.5 0h6V350h-6zm80.9 0h6V350h-6zm40 0h6V350h-6zm40.9 0h6V350h-6zm80.4 0h6V350h-6zm203 0h6.1V350h-6zm-162.1 0h6V350h-6zm40 0h6V350h-6zm40.5 0h6V350h-6zm40.4 0h6V350h-6zm323.2 0h6V350h-6zm-242.7 0h6V350h-6zm40.8 0h6V350h-6zm41.3 0h6V350h-6zm38.8 0h6V350h-6zm41.3 0h6V350h-6zm40.4 0h6V350h-6zm119.7 0h6V350h-6zm-38.8 0h6V350h-6zm-808.9 0h6V350h-6z"/>
|
||||
<path fill="#239e3f" d="M-68.8 162.6h6v10.5h-6zm160.5 0h6v10.5h-6zm-283.7 0h6v10.5h-6zm81.5 0h6v10.5h-6zm80.9 0h6v10.5h-6zm40 0h6v10.5h-6zm40.9 0h6v10.5h-6zm80.4 0h6v10.5h-6zm203 0h6.1v10.5h-6zm-162.1 0h6v10.5h-6zm40 0h6v10.5h-6zm40.5 0h6v10.5h-6zm40.4 0h6v10.5h-6zm323.2 0h6v10.5h-6zm-242.7 0h6v10.5h-6zm40.8 0h6v10.5h-6zm41.3 0h6v10.5h-6zm38.8 0h6v10.5h-6zm41.3 0h6v10.5h-6zm40.4 0h6v10.5h-6zm119.7 0h6v10.5h-6zm-38.8 0h6v10.5h-6zm-808.9 0h6v10.5h-6z"/>
|
||||
<g fill="#da0000">
|
||||
<path d="M279.8 197.5c8.4 10.4 34.5 67.6-15.7 105.2-23.7 17.8-9 18.6-8.3 21.6 38-20.1 50.3-47.5 50-72-.2-24.4-13.2-46-26-54.8"/>
|
||||
<path d="M284.8 194.8a73.3 73.3 0 0 1 15.7 112.4c27.2-6 62-86.4-15.7-112.4m-57.6 0a73.3 73.3 0 0 0-15.6 112.4c-27.3-6-62-86.4 15.6-112.4"/>
|
||||
<path d="M232.2 197.5c-8.4 10.4-34.5 67.6 15.7 105.2 23.6 17.8 9 18.6 8.3 21.6-38-20.1-50.3-47.5-50-72 .2-24.4 13.2-46 26-54.8"/>
|
||||
<path d="M304.2 319.1c-14.9.2-33.6-2-47.5-9.3 2.3 4.5 4.2 7.3 6.5 11.7 13.2 1.3 31.5 2.8 41-2.4m-95 0c14.9.2 33.6-2 47.5-9.3-2.3 4.5-4.2 7.3-6.5 11.7-13.2 1.3-31.5 2.8-41-2.4m27.3-138.7c3 8 10.9 9.2 19.3 4.5 6.2 3.6 15.7 3.9 19-4.1 2.5 19.8-18.3 15-19 11.2-7.8 7.5-22.2 3.2-19.3-11.6"/>
|
||||
<path d="m256.4 331.6 7.8-9 1.1-120.1-9.3-8.2-9.3 7.8 1.9 121z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -16,7 +16,6 @@
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="ga_IE"> <img th:src="@{'/images/flags/ie.svg'}" alt="icon" width="20" height="15"> Irish</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="it_IT"> <img th:src="@{'/images/flags/it.svg'}" alt="icon" width="20" height="15"> Italiano</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="nl_NL"> <img th:src="@{'/images/flags/nl.svg'}" alt="icon" width="20" height="15"> Nederlands</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="fa_IR"> <img th:src="@{'/images/flags/ir.svg'}" alt="icon" width="20" height="15"> پارسی</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pl_PL"> <img th:src="@{'/images/flags/pl.svg'}" alt="icon" width="20" height="15"> Polski</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_BR"> <img th:src="@{'/images/flags/pt_br.svg'}" alt="icon" width="20" height="15"> Português (BR)</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="pt_PT"> <img th:src="@{'/images/flags/pt_pt.svg'}" alt="icon" width="20" height="15"> Português (PT)</a>
|
||||
|
||||
@@ -92,48 +92,26 @@
|
||||
appendPercentageSymbol();
|
||||
</script>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="rotation" th:text="#{watermark.selectText.4}"></label>
|
||||
<input type="text" id="rotation" name="rotation" class="form-control" value="45">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
|
||||
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
|
||||
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="customColor" class="form-label" th:text="#{watermark.customColor}">Custom
|
||||
Color</label>
|
||||
<div class="form-control form-control-color" style="background-color: #d3d3d3;">
|
||||
<input type="color" id="customColor" name="customColor" value="#d3d3d3">
|
||||
</div>
|
||||
<script>
|
||||
let colorInput = document.getElementById("customColor");
|
||||
if (colorInput) {
|
||||
let colorInputContainer = colorInput.parentElement;
|
||||
if (colorInputContainer) {
|
||||
colorInput.onchange = function() {
|
||||
colorInputContainer.style.backgroundColor = colorInput.value;
|
||||
}
|
||||
colorInputContainer.style.backgroundColor = colorInput.value;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" id="convertPDFToImage" name="convertPDFToImage">
|
||||
<label for="convertPDFToImage" th:text="#{watermark.selectText.10}"></label>
|
||||
</div>
|
||||
<div class="mb-3 text-left">
|
||||
<input type="submit" id="submitBtn" th:value="#{watermark.submit}" class="btn btn-primary">
|
||||
</div>
|
||||
</form>
|
||||
<div class="mb-3">
|
||||
<label for="rotation" th:text="#{watermark.selectText.4}"></label>
|
||||
<input type="text" id="rotation" name="rotation" class="form-control" value="45">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="widthSpacer" th:text="#{watermark.selectText.5}"></label>
|
||||
<input type="text" id="widthSpacer" name="widthSpacer" class="form-control" value="50">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="heightSpacer" th:text="#{watermark.selectText.6}"></label>
|
||||
<input type="text" id="heightSpacer" name="heightSpacer" class="form-control" value="50">
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" id="convertPDFToImage" name="convertPDFToImage">
|
||||
<label for="convertPDFToImage" th:text="#{watermark.selectText.10}"></label>
|
||||
</div>
|
||||
<div class="mb-3 text-left">
|
||||
<input type="submit" id="submitBtn" th:value="#{watermark.submit}" class="btn btn-primary">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function toggleFileOption() {
|
||||
|
||||
BIN
src/main/resources/tl12.acrobatsecuritysettings
Normal file
BIN
src/main/resources/tl12.acrobatsecuritysettings
Normal file
Binary file not shown.
Reference in New Issue
Block a user