From f6bc89d755b57a3164f77c23dce9b3a832e73c0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20=C5=BBak?=
Date: Thu, 21 May 2026 16:41:17 +0200
Subject: [PATCH] Added fonts to support languages which different diacritics
letters, upgrade api documentation add tests
---
gradle/wrapper/gradle-wrapper.properties | 7 +
gradlew | 251 ++++++++++++++++++
gradlew.bat | 94 +++++++
.../boxable/AbstractPageTemplate.java | 38 ++-
.../boxable/AbstractTemplatedTable.java | 37 ++-
.../java/be/quodlibet/boxable/BaseTable.java | 52 +++-
src/main/java/be/quodlibet/boxable/Cell.java | 27 +-
.../boxable/CellContentDrawnListener.java | 17 ++
.../boxable/HorizontalAlignment.java | 17 +-
.../java/be/quodlibet/boxable/ImageCell.java | 6 +
.../java/be/quodlibet/boxable/Paragraph.java | 24 +-
src/main/java/be/quodlibet/boxable/Table.java | 8 +-
.../java/be/quodlibet/boxable/TableCell.java | 4 +-
.../quodlibet/boxable/VerticalAlignment.java | 20 +-
.../boxable/datatable/DataTable.java | 40 +--
.../quodlibet/boxable/utils/FontScript.java | 122 +++++++++
.../be/quodlibet/boxable/utils/FontUtils.java | 36 ++-
.../utils/PageContentStreamOptimized.java | 195 ++++++++++++++
src/main/resources/fonts/NotoSans-Bold.ttf | Bin 0 -> 627176 bytes
.../resources/fonts/NotoSans-BoldItalic.ttf | Bin 0 -> 640424 bytes
src/main/resources/fonts/NotoSans-Italic.ttf | Bin 0 -> 632008 bytes
src/main/resources/fonts/NotoSans-Regular.ttf | Bin 0 -> 616116 bytes
.../resources/fonts/NotoSansArabic-Bold.ttf | Bin 0 -> 248468 bytes
.../fonts/NotoSansArabic-Regular.ttf | Bin 0 -> 240456 bytes
.../resources/fonts/NotoSansHebrew-Bold.ttf | Bin 0 -> 27164 bytes
.../fonts/NotoSansHebrew-Regular.ttf | Bin 0 -> 26900 bytes
.../be/quodlibet/boxable/AlignmentTest.java | 147 ++++++++++
.../java/be/quodlibet/boxable/CellTest.java | 138 ++++++++++
.../boxable/LargeTableMemoryTest.java | 3 +-
.../be/quodlibet/boxable/ParagraphTest.java | 161 +++++++++++
.../java/be/quodlibet/boxable/RowTest.java | 121 +++++++++
.../quodlibet/boxable/line/LineStyleTest.java | 178 +++++++++++++
32 files changed, 1689 insertions(+), 54 deletions(-)
create mode 100644 gradle/wrapper/gradle-wrapper.properties
create mode 100755 gradlew
create mode 100644 gradlew.bat
create mode 100644 src/main/java/be/quodlibet/boxable/utils/FontScript.java
create mode 100644 src/main/resources/fonts/NotoSans-Bold.ttf
create mode 100644 src/main/resources/fonts/NotoSans-BoldItalic.ttf
create mode 100644 src/main/resources/fonts/NotoSans-Italic.ttf
create mode 100644 src/main/resources/fonts/NotoSans-Regular.ttf
create mode 100644 src/main/resources/fonts/NotoSansArabic-Bold.ttf
create mode 100644 src/main/resources/fonts/NotoSansArabic-Regular.ttf
create mode 100644 src/main/resources/fonts/NotoSansHebrew-Bold.ttf
create mode 100644 src/main/resources/fonts/NotoSansHebrew-Regular.ttf
create mode 100644 src/test/java/be/quodlibet/boxable/AlignmentTest.java
create mode 100644 src/test/java/be/quodlibet/boxable/CellTest.java
create mode 100644 src/test/java/be/quodlibet/boxable/ParagraphTest.java
create mode 100644 src/test/java/be/quodlibet/boxable/RowTest.java
create mode 100644 src/test/java/be/quodlibet/boxable/line/LineStyleTest.java
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..2a84e188
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..ef07e016
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH="\\\"\\\""
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..5eed7ee8
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/be/quodlibet/boxable/AbstractPageTemplate.java b/src/main/java/be/quodlibet/boxable/AbstractPageTemplate.java
index f8c27ba3..177e2fb3 100644
--- a/src/main/java/be/quodlibet/boxable/AbstractPageTemplate.java
+++ b/src/main/java/be/quodlibet/boxable/AbstractPageTemplate.java
@@ -9,14 +9,43 @@
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
/**
- * Created by dgautier on 3/18/2015.
+ * Abstract base class for page templates in PDF documents.
+ * Provides methods for managing PDF page content including images and document references.
*/
public abstract class AbstractPageTemplate extends PDPage {
+ /**
+ * Creates a new AbstractPageTemplate.
+ * Subclasses should provide their own constructor implementation.
+ */
+ protected AbstractPageTemplate() {
+ super();
+ }
+
+ /**
+ * Gets the PDF document associated with this page template.
+ *
+ * @return the PDDocument instance
+ */
protected abstract PDDocument getDocument();
+ /**
+ * Gets the starting Y coordinate for content on this page.
+ *
+ * @return the Y coordinate value
+ */
protected abstract float yStart();
+ /**
+ * Adds an image to the page at the specified position and dimensions.
+ *
+ * @param ximage the image object to add
+ * @param cursorX the X coordinate where the image should be placed
+ * @param cursorY the Y coordinate where the image should be placed
+ * @param width the width of the image in points
+ * @param height the height of the image in points
+ * @throws IOException if an error occurs while writing to the PDF stream
+ */
protected void addPicture(PDImageXObject ximage, float cursorX, float cursorY, int width, int height) throws IOException {
PDPageContentStream contentStream = new PDPageContentStream(getDocument(), this,
@@ -25,6 +54,13 @@ protected void addPicture(PDImageXObject ximage, float cursorX, float cursorY, i
contentStream.close();
}
+ /**
+ * Loads an image from a file.
+ *
+ * @param nameJPGFile the file path to the image file
+ * @return the loaded PDImage object
+ * @throws IOException if the file cannot be read or is not a valid image
+ */
protected PDImage loadPicture(String nameJPGFile) throws IOException {
return PDImageXObject.createFromFile(nameJPGFile, getDocument());
}
diff --git a/src/main/java/be/quodlibet/boxable/AbstractTemplatedTable.java b/src/main/java/be/quodlibet/boxable/AbstractTemplatedTable.java
index 732e20d0..8366eb7d 100644
--- a/src/main/java/be/quodlibet/boxable/AbstractTemplatedTable.java
+++ b/src/main/java/be/quodlibet/boxable/AbstractTemplatedTable.java
@@ -7,25 +7,56 @@
import be.quodlibet.boxable.page.PageProvider;
/**
- * Created by dgautier on 3/18/2015.
+ * Abstract templated table implementation for PDF documents with custom page templates.
+ * Extends the base Table class with support for custom page templates.
+ *
+ * @param the page template type extending AbstractPageTemplate
*/
public abstract class AbstractTemplatedTable extends Table {
-
+
@Deprecated
public AbstractTemplatedTable(float yStart, float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, T currentPage, boolean drawLines, boolean drawContent) throws IOException {
super(yStart, yStartNewPage, bottomMargin, width, margin, document, currentPage, drawLines, drawContent);
}
-
+
@Deprecated
public AbstractTemplatedTable(float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, boolean drawLines, boolean drawContent) throws IOException {
super(yStartNewPage, bottomMargin, width, margin, document, drawLines, drawContent);
setYStart(getCurrentPage().yStart());
}
+ /**
+ * Constructs a templated table with an existing page and specified position.
+ *
+ * @param yStart the initial Y coordinate for table content
+ * @param yStartNewPage the Y coordinate to use for new pages
+ * @param bottomMargin the bottom margin of the page in points
+ * @param width the width of the table in points
+ * @param margin the left and right margin of the table in points
+ * @param document the PDF document
+ * @param currentPage the current page template
+ * @param drawLines whether to draw cell borders
+ * @param drawContent whether to draw cell content
+ * @param pageProvider the provider for creating new pages
+ * @throws IOException if an error occurs during initialization
+ */
public AbstractTemplatedTable(float yStart, float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, T currentPage, boolean drawLines, boolean drawContent, PageProvider pageProvider) throws IOException {
super(yStart, yStartNewPage, 0, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, pageProvider);
}
+ /**
+ * Constructs a templated table without an existing page, using the page provider.
+ *
+ * @param yStartNewPage the Y coordinate to use for new pages
+ * @param bottomMargin the bottom margin of the page in points
+ * @param width the width of the table in points
+ * @param margin the left and right margin of the table in points
+ * @param document the PDF document
+ * @param drawLines whether to draw cell borders
+ * @param drawContent whether to draw cell content
+ * @param pageProvider the provider for creating new pages
+ * @throws IOException if an error occurs during initialization
+ */
public AbstractTemplatedTable(float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, boolean drawLines, boolean drawContent, PageProvider pageProvider) throws IOException {
super(yStartNewPage, 0, bottomMargin, width, margin, document, drawLines, drawContent, pageProvider);
setYStart(getCurrentPage().yStart());
diff --git a/src/main/java/be/quodlibet/boxable/BaseTable.java b/src/main/java/be/quodlibet/boxable/BaseTable.java
index 5f3bacaa..55ceae31 100644
--- a/src/main/java/be/quodlibet/boxable/BaseTable.java
+++ b/src/main/java/be/quodlibet/boxable/BaseTable.java
@@ -9,18 +9,64 @@
import be.quodlibet.boxable.page.PageProvider;
/**
- * Created by dgautier on 3/18/2015.
+ * Base implementation of a table for PDF documents using standard PDPage.
+ * Provides a straightforward table implementation without custom page templates.
*/
public class BaseTable extends Table {
+ /**
+ * Constructs a basic table with standard PDPage and no top margin.
+ *
+ * @param yStart the initial Y coordinate for table content
+ * @param yStartNewPage the Y coordinate to use for new pages
+ * @param bottomMargin the bottom margin of the page in points
+ * @param width the width of the table in points
+ * @param margin the left and right margin of the table in points
+ * @param document the PDF document
+ * @param currentPage the current page
+ * @param drawLines whether to draw cell borders
+ * @param drawContent whether to draw cell content
+ * @throws IOException if an error occurs during initialization
+ */
public BaseTable(float yStart, float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent) throws IOException {
super(yStart, yStartNewPage, 0, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, new DefaultPageProvider(document, currentPage.getMediaBox()));
}
-
+
+ /**
+ * Constructs a table with standard PDPage with specified top margin.
+ *
+ * @param yStart the initial Y coordinate for table content
+ * @param yStartNewPage the Y coordinate to use for new pages
+ * @param pageTopMargin the top margin of the page in points
+ * @param bottomMargin the bottom margin of the page in points
+ * @param width the width of the table in points
+ * @param margin the left and right margin of the table in points
+ * @param document the PDF document
+ * @param currentPage the current page
+ * @param drawLines whether to draw cell borders
+ * @param drawContent whether to draw cell content
+ * @throws IOException if an error occurs during initialization
+ */
public BaseTable(float yStart, float yStartNewPage, float pageTopMargin, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent) throws IOException {
super(yStart, yStartNewPage, pageTopMargin, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, new DefaultPageProvider(document, currentPage.getMediaBox()));
}
-
+
+ /**
+ * Constructs a table with custom page provider and specified margins.
+ *
+ * @param yStart the initial Y coordinate for table content
+ * @param yStartNewPage the Y coordinate to use for new pages
+ * @param pageTopMargin the top margin of the page in points
+ * @param bottomMargin the bottom margin of the page in points
+ * @param width the width of the table in points
+ * @param margin the left and right margin of the table in points
+ * @param document the PDF document
+ * @param currentPage the current page
+ * @param drawLines whether to draw cell borders
+ * @param drawContent whether to draw cell content
+ * @param pageProvider the provider for creating new pages
+ * @throws IOException if an error occurs during initialization
+ */
public BaseTable(float yStart, float yStartNewPage, float pageTopMargin, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent, final PageProvider pageProvider) throws IOException {
super(yStart, yStartNewPage, pageTopMargin, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, pageProvider);
}
diff --git a/src/main/java/be/quodlibet/boxable/Cell.java b/src/main/java/be/quodlibet/boxable/Cell.java
index cee4a51c..54b5d1ec 100644
--- a/src/main/java/be/quodlibet/boxable/Cell.java
+++ b/src/main/java/be/quodlibet/boxable/Cell.java
@@ -25,7 +25,18 @@
import be.quodlibet.boxable.utils.FontUtils;
import be.quodlibet.boxable.utils.UnitConverter;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
-
+import java.util.Objects;
+
+/**
+ * Represents a table cell in a PDF document with configurable content, styling, and layout properties.
+ *
+ * A cell contains text content and supports various formatting options including fonts, colors, padding,
+ * borders, alignment, and text rotation. Cells are contained within rows and can be styled individually
+ * or inherit styles from other cells.
+ *
+ *
+ * @param The type of {@link PDPage} that this cell will be rendered on
+ */
public class Cell {
private static final Logger logger = LoggerFactory.getLogger(Cell.class);
@@ -1031,25 +1042,25 @@ public void copyCellStyle(Cell> sourceCell) {
* @return boolean if source cell has the same style
*/
public Boolean hasSameStyle(Cell> sourceCell) {
- if (!sourceCell.getTopBorder().equals(getTopBorder())) {
+ if (!Objects.equals(sourceCell.getTopBorder(), getTopBorder())) {
return false;
}
- if (!sourceCell.getFont().equals(getFont())) {
+ if (!Objects.equals(sourceCell.getFont(), getFont())) {
return false;
}
- if (!sourceCell.getFontBold().equals(getFontBold())) {
+ if (!Objects.equals(sourceCell.getFontBold(), getFontBold())) {
return false;
}
- if (!sourceCell.getFillColor().equals(getFillColor())) {
+ if (!Objects.equals(sourceCell.getFillColor(), getFillColor())) {
return false;
}
- if (!sourceCell.getTextColor().equals(getTextColor())) {
+ if (!Objects.equals(sourceCell.getTextColor(), getTextColor())) {
return false;
}
- if (!sourceCell.getAlign().equals(getAlign())) {
+ if (!Objects.equals(sourceCell.getAlign(), getAlign())) {
return false;
}
- if (!sourceCell.getValign().equals(getValign())) {
+ if (!Objects.equals(sourceCell.getValign(), getValign())) {
return false;
}
return true;
diff --git a/src/main/java/be/quodlibet/boxable/CellContentDrawnListener.java b/src/main/java/be/quodlibet/boxable/CellContentDrawnListener.java
index f4b66d3f..7ad2514d 100644
--- a/src/main/java/be/quodlibet/boxable/CellContentDrawnListener.java
+++ b/src/main/java/be/quodlibet/boxable/CellContentDrawnListener.java
@@ -4,6 +4,23 @@
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
+/**
+ * Listener interface for cell content drawing events.
+ *
+ * Implementations of this interface can be used to handle custom logic when
+ * cell content has been drawn on a PDF page.
+ *
+ *
+ * @param the type of PDPage
+ */
public interface CellContentDrawnListener {
+ /**
+ * Called when cell content has been drawn on the page.
+ *
+ * @param cell the {@link Cell} whose content was drawn
+ * @param document the {@link PDDocument} containing the page
+ * @param page the {@link PDPage} on which content was drawn
+ * @param rectangle the {@link PDRectangle} bounds of the drawn content
+ */
void onContentDrawn(Cell cell, PDDocument document, PDPage page, PDRectangle rectangle);
}
diff --git a/src/main/java/be/quodlibet/boxable/HorizontalAlignment.java b/src/main/java/be/quodlibet/boxable/HorizontalAlignment.java
index 2a4c7c6a..652d9ed2 100644
--- a/src/main/java/be/quodlibet/boxable/HorizontalAlignment.java
+++ b/src/main/java/be/quodlibet/boxable/HorizontalAlignment.java
@@ -1,8 +1,23 @@
package be.quodlibet.boxable;
+/**
+ * Horizontal alignment options for text and cells.
+ */
public enum HorizontalAlignment {
- LEFT, CENTER, RIGHT;
+ /** Left alignment */
+ LEFT,
+ /** Center alignment */
+ CENTER,
+ /** Right alignment */
+ RIGHT;
+ /**
+ * Gets the horizontal alignment from a string key.
+ * Defaults to LEFT if the key is null or not recognized.
+ *
+ * @param key the alignment key ("left", "center", or "right")
+ * @return the corresponding HorizontalAlignment
+ */
public static HorizontalAlignment get(final String key) {
switch (key == null ? "left" : key.toLowerCase().trim()) {
case "left":
diff --git a/src/main/java/be/quodlibet/boxable/ImageCell.java b/src/main/java/be/quodlibet/boxable/ImageCell.java
index 13c7360f..a8c3f643 100644
--- a/src/main/java/be/quodlibet/boxable/ImageCell.java
+++ b/src/main/java/be/quodlibet/boxable/ImageCell.java
@@ -5,6 +5,12 @@
import be.quodlibet.boxable.image.Image;
import be.quodlibet.boxable.line.LineStyle;
+/**
+ * A table cell that contains an image.
+ * Extends the base Cell class to provide image-specific functionality including scaling.
+ *
+ * @param the page type extending PDPage
+ */
public class ImageCell extends Cell {
private Image img;
diff --git a/src/main/java/be/quodlibet/boxable/Paragraph.java b/src/main/java/be/quodlibet/boxable/Paragraph.java
index d673b3a2..f4161b2d 100644
--- a/src/main/java/be/quodlibet/boxable/Paragraph.java
+++ b/src/main/java/be/quodlibet/boxable/Paragraph.java
@@ -16,6 +16,8 @@
import be.quodlibet.boxable.utils.PageContentStreamOptimized;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import be.quodlibet.boxable.text.PipelineLayer;
import be.quodlibet.boxable.text.Token;
@@ -27,6 +29,8 @@
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
public class Paragraph {
+ private static final Logger logger = LoggerFactory.getLogger(Paragraph.class);
+
public static final float SCRIPT_SCALE = 0.6f;
public static final float SUPERSCRIPT_RISE = 0.35f;
public static final float SUBSCRIPT_DROP = 0.2f;
@@ -295,7 +299,7 @@ public List getLines() {
textInLine.push(currentFont, currentFontSize, new Token(TokenType.PADDING, String
.valueOf(orderingNumberAndTab / 1000 * getFontSize())));
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
orderListElement++;
} else {
@@ -305,7 +309,7 @@ public List getLines() {
textInLine.push(currentFont, currentFontSize, new Token(TokenType.PADDING,
String.valueOf(tabBullet / 1000 * getFontSize())));
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
}
textInLine.push(sinceLastWrapPoint);
@@ -406,7 +410,7 @@ public List getLines() {
textInLine.push(currentFont, currentFontSize, new Token(TokenType.PADDING,
String.valueOf((tab + font.getStringWidth(orderingNumber)) / 1000 * getFontSize())));
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
} else {
try {
@@ -416,7 +420,7 @@ public List getLines() {
String.valueOf(tabBullet / 1000 * getFontSize())));
} catch (IOException e) {
// TODO Auto-generated catch block
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
}
}
@@ -463,7 +467,7 @@ public List getLines() {
textInLine.push(currentFont, currentFontSize, new Token(TokenType.PADDING, String
.valueOf(orderingNumberAndTab / 1000 * getFontSize())));
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
} else {
try {
@@ -472,7 +476,7 @@ public List getLines() {
textInLine.push(currentFont, currentFontSize, new Token(TokenType.PADDING,
String.valueOf(tabBullet / 1000 * getFontSize())));
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
}
}
@@ -524,7 +528,7 @@ public List getLines() {
textInLineMaxFontSize = Math.max(textInLineMaxFontSize, currentFontSize);
}
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
} else {
// wrapping at this must-have wrap point
@@ -568,7 +572,7 @@ public List getLines() {
}
}
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
}
}
@@ -595,7 +599,7 @@ public List getLines() {
try {
width += (currentFont.getStringWidth(String.valueOf(c)) / 1000f * currentFontSize);
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
if(alreadyTextInLine){
if (width < this.width - textInLine.trimmedWidth()) {
@@ -667,7 +671,7 @@ public List getLines() {
}
} catch (IOException e) {
- e.printStackTrace();
+ logger.warn("Failed to calculate font string width", e);
}
break;
}
diff --git a/src/main/java/be/quodlibet/boxable/Table.java b/src/main/java/be/quodlibet/boxable/Table.java
index 7ae511ae..8fea91b7 100644
--- a/src/main/java/be/quodlibet/boxable/Table.java
+++ b/src/main/java/be/quodlibet/boxable/Table.java
@@ -5,6 +5,8 @@
import be.quodlibet.boxable.line.LineStyle;
import be.quodlibet.boxable.page.PageProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import be.quodlibet.boxable.text.Token;
import be.quodlibet.boxable.text.WrappingFunction;
import be.quodlibet.boxable.utils.FontUtils;
@@ -32,6 +34,8 @@
public abstract class Table {
+ private static final Logger logger = LoggerFactory.getLogger(Table.class);
+
private static float applyScriptScale(float baseFontSize, int superscriptDepth, int subscriptDepth) {
int depth = Math.max(superscriptDepth, subscriptDepth);
float scaled = baseFontSize;
@@ -1005,7 +1009,7 @@ protected void drawCellContent(Row row, float rowHeight) throws IOException {
}
cursorX += token.getWidth(currentFont) / 1000 * currentFontSize;
} catch (IOException e) {
- e.printStackTrace();
+ logger.error("Failed to render text token", e);
}
}
break;
@@ -1385,7 +1389,7 @@ public void addHeaderRow(Row row) {
* @return header row
*/
public Row getHeader() {
- if (header == null) {
+ if (header == null || header.isEmpty()) {
throw new IllegalArgumentException("Header Row not set on table");
}
diff --git a/src/main/java/be/quodlibet/boxable/TableCell.java b/src/main/java/be/quodlibet/boxable/TableCell.java
index 3ef780a2..e7428fbb 100644
--- a/src/main/java/be/quodlibet/boxable/TableCell.java
+++ b/src/main/java/be/quodlibet/boxable/TableCell.java
@@ -161,7 +161,7 @@ public void fillTable() {
}
tableCellContentStream.close();
} catch (IOException e) {
- logger.warn("Cannot create table in TableCell. Table data: '{}' " + tableData + e);
+ logger.warn("Cannot create table in TableCell. Table data: '{}'", tableData, e);
} finally {
yStart = originalYStart;
}
@@ -512,7 +512,7 @@ public void draw(PDPage page) {
}
tableCellContentStream.close();
} catch (IOException e) {
- logger.warn("Cannot draw table for TableCell! Table data: '{}'" + tableData + e);
+ logger.warn("Cannot draw table for TableCell! Table data: '{}'", tableData, e);
}
}
diff --git a/src/main/java/be/quodlibet/boxable/VerticalAlignment.java b/src/main/java/be/quodlibet/boxable/VerticalAlignment.java
index 0cedfb8c..8eeb9dd5 100644
--- a/src/main/java/be/quodlibet/boxable/VerticalAlignment.java
+++ b/src/main/java/be/quodlibet/boxable/VerticalAlignment.java
@@ -1,8 +1,24 @@
package be.quodlibet.boxable;
+/**
+ * Vertical alignment options for text and cells.
+ * Provides enumeration of alignment positions: top, middle, and bottom.
+ */
public enum VerticalAlignment {
- TOP, MIDDLE, BOTTOM;
-
+ /** Top alignment */
+ TOP,
+ /** Middle (center) alignment */
+ MIDDLE,
+ /** Bottom alignment */
+ BOTTOM;
+
+ /**
+ * Gets the vertical alignment from a string key.
+ * Defaults to TOP if the key is null or not recognized.
+ *
+ * @param key the alignment key ("top", "middle", or "bottom")
+ * @return the corresponding VerticalAlignment
+ */
public static VerticalAlignment get(final String key) {
switch (key == null ? "top" : key.toLowerCase().trim()) {
case "top":
diff --git a/src/main/java/be/quodlibet/boxable/datatable/DataTable.java b/src/main/java/be/quodlibet/boxable/datatable/DataTable.java
index 67a56ba8..658e0da4 100644
--- a/src/main/java/be/quodlibet/boxable/datatable/DataTable.java
+++ b/src/main/java/be/quodlibet/boxable/datatable/DataTable.java
@@ -108,30 +108,30 @@ public DataTable(Table table, PDPage page, List colWidths, UpdateCellProp
this.colWidths = (colWidths.size() == 0) ? null : colWidths;
this.updateCellProperty = updateCellProperty;
// Create a dummy pdf document, page and table to create template cells
- PDDocument ddoc = new PDDocument();
- PDPage dpage = new PDPage();
- dpage.setMediaBox(page.getMediaBox());
- dpage.setRotation(page.getRotation());
- ddoc.addPage(dpage);
- BaseTable dummyTable = new BaseTable(10f, 10f, 10f, table.getWidth(), 10f, ddoc, dpage, false, false);
- Row dr = dummyTable.createRow(0f);
- headerCellTemplate = dr.createCell(10f, "A", HorizontalAlignment.CENTER, VerticalAlignment.MIDDLE);
- if (this.colWidths == null) {
- dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
- dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
- dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
- dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
- dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
- dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
- } else {
- for (int i = 0 ; i < this.colWidths.size(); i++) {
+ try (PDDocument ddoc = new PDDocument()) {
+ PDPage dpage = new PDPage();
+ dpage.setMediaBox(page.getMediaBox());
+ dpage.setRotation(page.getRotation());
+ ddoc.addPage(dpage);
+ BaseTable dummyTable = new BaseTable(10f, 10f, 10f, table.getWidth(), 10f, ddoc, dpage, false, false);
+ Row dr = dummyTable.createRow(0f);
+ headerCellTemplate = dr.createCell(10f, "A", HorizontalAlignment.CENTER, VerticalAlignment.MIDDLE);
+ if (this.colWidths == null) {
dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
+ dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
+ dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
+ dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
+ dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
+ } else {
+ for (int i = 0 ; i < this.colWidths.size(); i++) {
+ dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
+ dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE));
+ }
}
+ defaultCellTemplate = dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE);
+ setDefaultStyles();
}
- defaultCellTemplate = dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE);
- setDefaultStyles();
- ddoc.close();
}
/**
diff --git a/src/main/java/be/quodlibet/boxable/utils/FontScript.java b/src/main/java/be/quodlibet/boxable/utils/FontScript.java
new file mode 100644
index 00000000..1942edf3
--- /dev/null
+++ b/src/main/java/be/quodlibet/boxable/utils/FontScript.java
@@ -0,0 +1,122 @@
+package be.quodlibet.boxable.utils;
+
+/**
+ * Defines the available font scripts for PDF generation.
+ *
+ * This library uses Google's Noto font family. The name "Noto" comes from
+ * "No Tofu" - referring to the blank boxes (□) that appear when a font doesn't support
+ * a character. The Noto project aims to cover every Unicode character.
+ *
+ *
+ * About Noto Fonts
+ *
+ * Because of the enormous number of scripts and characters involved, Noto fonts are
+ * distributed as separate files by script. This library includes the most commonly
+ * needed scripts:
+ *
+ *
+ * - {@link #LATIN} - Noto Sans (Latin, Greek, Cyrillic)
+ * - {@link #ARABIC} - Noto Sans Arabic
+ * - {@link #HEBREW} - Noto Sans Hebrew
+ *
+ *
+ * Extending with Additional Scripts
+ *
+ * Additional Noto fonts can be downloaded from
+ * Google Fonts Noto or
+ * GitHub noto-fonts.
+ *
+ *
+ * Available Noto Sans variants include (not exhaustive):
+ *
+ *
+ * - Noto Sans CJK - Chinese, Japanese, Korean
+ * - Noto Sans Devanagari - Hindi, Sanskrit, Marathi, Nepali
+ * - Noto Sans Thai - Thai
+ * - Noto Sans Bengali - Bengali, Assamese
+ * - Noto Sans Tamil - Tamil
+ * - Noto Sans Georgian - Georgian
+ * - Noto Sans Armenian - Armenian
+ * - And many more for other scripts
+ *
+ *
+ * To add a new script, download the TTF files, add them to the {@code fonts/} resources
+ * directory, and add a new enum constant following the existing pattern.
+ *
+ *
+ * Usage Example
+ * {@code
+ * // Use Latin/Greek/Cyrillic (default)
+ * FontUtils.setNotoSansFontsAsDefault(document, FontScript.LATIN);
+ *
+ * // Use Arabic script
+ * FontUtils.setNotoSansFontsAsDefault(document, FontScript.ARABIC);
+ *
+ * // Use Hebrew script
+ * FontUtils.setNotoSansFontsAsDefault(document, FontScript.HEBREW);
+ * }
+ *
+ * @see FontUtils#setNotoSansFontsAsDefault(org.apache.pdfbox.pdmodel.PDDocument, FontScript)
+ * @see Google Fonts - Noto
+ * @see GitHub - noto-fonts
+ */
+public enum FontScript {
+
+ /**
+ * Latin, Greek, and Cyrillic scripts.
+ *
+ * Supports Western European, Eastern European, Greek, and Russian languages.
+ * Includes Regular, Bold, Italic, and BoldItalic variants.
+ *
+ */
+ LATIN("fonts/NotoSans-Regular.ttf", "fonts/NotoSans-Bold.ttf",
+ "fonts/NotoSans-Italic.ttf", "fonts/NotoSans-BoldItalic.ttf"),
+
+ /**
+ * Arabic script.
+ *
+ * Supports Arabic, Persian, Urdu, and other languages using Arabic script.
+ * Note: Arabic script does not use italic variants; Regular is used for italic styles.
+ *
+ */
+ ARABIC("fonts/NotoSansArabic-Regular.ttf", "fonts/NotoSansArabic-Bold.ttf",
+ "fonts/NotoSansArabic-Regular.ttf", "fonts/NotoSansArabic-Bold.ttf"),
+
+ /**
+ * Hebrew script.
+ *
+ * Supports Hebrew and Yiddish languages.
+ * Note: Hebrew script does not use italic variants; Regular is used for italic styles.
+ *
+ */
+ HEBREW("fonts/NotoSansHebrew-Regular.ttf", "fonts/NotoSansHebrew-Bold.ttf",
+ "fonts/NotoSansHebrew-Regular.ttf", "fonts/NotoSansHebrew-Bold.ttf");
+
+ private final String regularPath;
+ private final String boldPath;
+ private final String italicPath;
+ private final String boldItalicPath;
+
+ FontScript(String regularPath, String boldPath, String italicPath, String boldItalicPath) {
+ this.regularPath = regularPath;
+ this.boldPath = boldPath;
+ this.italicPath = italicPath;
+ this.boldItalicPath = boldItalicPath;
+ }
+
+ public String getRegularPath() {
+ return regularPath;
+ }
+
+ public String getBoldPath() {
+ return boldPath;
+ }
+
+ public String getItalicPath() {
+ return italicPath;
+ }
+
+ public String getBoldItalicPath() {
+ return boldItalicPath;
+ }
+}
diff --git a/src/main/java/be/quodlibet/boxable/utils/FontUtils.java b/src/main/java/be/quodlibet/boxable/utils/FontUtils.java
index 268814f9..e151b14c 100644
--- a/src/main/java/be/quodlibet/boxable/utils/FontUtils.java
+++ b/src/main/java/be/quodlibet/boxable/utils/FontUtils.java
@@ -234,7 +234,7 @@ public static void setMonoSpaceFontsAsDefault(PDDocument document) {
}
public static void setSerifFontsAsDefault(PDDocument document) {
-
+
defaultFonts.get().put("font",new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN));
defaultFonts.get().put("fontBold", new PDType1Font(Standard14Fonts.FontName.TIMES_BOLD));
defaultFonts.get().put("fontItalic", new PDType1Font(Standard14Fonts.FontName.TIMES_ITALIC));
@@ -242,4 +242,38 @@ public static void setSerifFontsAsDefault(PDDocument document) {
}
+ /**
+ * Sets Noto Sans fonts as the default fonts with full Unicode support.
+ *
+ * Uses {@link FontScript#LATIN} (Latin, Greek, Cyrillic) by default.
+ * For Arabic or Hebrew scripts, use {@link #setNotoSansFontsAsDefault(PDDocument, FontScript)}.
+ *
+ *
+ * @param document the {@link PDDocument} in which the fonts will be embedded
+ */
+ public static void setNotoSansFontsAsDefault(PDDocument document) {
+ setNotoSansFontsAsDefault(document, FontScript.LATIN);
+ }
+
+ /**
+ * Sets Noto Sans fonts as the default fonts for the specified script.
+ *
+ * Available scripts:
+ *
+ * - {@link FontScript#LATIN} - Latin, Greek, Cyrillic (Western/Eastern European, Russian)
+ * - {@link FontScript#ARABIC} - Arabic, Persian, Urdu
+ * - {@link FontScript#HEBREW} - Hebrew, Yiddish
+ *
+ *
+ *
+ * @param document the {@link PDDocument} in which the fonts will be embedded
+ * @param script the {@link FontScript} to use for font selection
+ */
+ public static void setNotoSansFontsAsDefault(PDDocument document, FontScript script) {
+ defaultFonts.get().put("font", loadFont(document, script.getRegularPath()));
+ defaultFonts.get().put("fontBold", loadFont(document, script.getBoldPath()));
+ defaultFonts.get().put("fontItalic", loadFont(document, script.getItalicPath()));
+ defaultFonts.get().put("fontBoldItalic", loadFont(document, script.getBoldItalicPath()));
+ }
+
}
diff --git a/src/main/java/be/quodlibet/boxable/utils/PageContentStreamOptimized.java b/src/main/java/be/quodlibet/boxable/utils/PageContentStreamOptimized.java
index 29f9f7e3..396c0c21 100644
--- a/src/main/java/be/quodlibet/boxable/utils/PageContentStreamOptimized.java
+++ b/src/main/java/be/quodlibet/boxable/utils/PageContentStreamOptimized.java
@@ -10,6 +10,14 @@
import java.io.IOException;
import java.util.Arrays;
+/**
+ * Optimized wrapper for PDPageContentStream providing caching and state management.
+ *
+ * This class wraps Apache PDFBox's PDPageContentStream to optimize PDF content
+ * generation by caching the state of text mode, fonts, colors, and line properties.
+ * It prevents redundant operations and manages coordinate transformations for rotated content.
+ *
+ */
public class PageContentStreamOptimized {
private static final Matrix ROTATION = Matrix.getRotateInstance(Math.PI * 0.5, 0, 0);
@@ -19,10 +27,25 @@ public class PageContentStreamOptimized {
private float textCursorAbsoluteY;
private boolean rotated;
+ /**
+ * Constructs an optimized wrapper for the given PDPageContentStream.
+ *
+ * @param pageContentStream the underlying PDPageContentStream to wrap
+ */
public PageContentStreamOptimized(PDPageContentStream pageContentStream) {
this.pageContentStream = pageContentStream;
}
+ /**
+ * Sets the rotation state for text content.
+ *
+ * When rotated is true, subsequent text will be transformed with a 90-degree rotation matrix.
+ * When switching from rotated to non-rotated, any active text mode is ended.
+ *
+ *
+ * @param rotated true to enable rotation, false to disable
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void setRotated(boolean rotated) throws IOException {
if (this.rotated == rotated) return;
if (rotated) {
@@ -37,6 +60,15 @@ public void setRotated(boolean rotated) throws IOException {
this.rotated = rotated;
}
+ /**
+ * Begins a text object if not already in text mode.
+ *
+ * This method ensures text mode is activated before text operations. If a rotation
+ * is active, the rotation matrix is applied to the text transformation.
+ *
+ *
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void beginText() throws IOException {
if (!textMode) {
pageContentStream.beginText();
@@ -49,6 +81,15 @@ public void beginText() throws IOException {
}
}
+ /**
+ * Ends the current text object if in text mode.
+ *
+ * This method marks the end of a text object and exits text mode.
+ * It is automatically called before non-text operations.
+ *
+ *
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void endText() throws IOException {
if (textMode) {
pageContentStream.endText();
@@ -59,6 +100,17 @@ public void endText() throws IOException {
private PDFont currentFont;
private float currentFontSize;
+ /**
+ * Sets the font and font size for text operations, with caching to avoid redundant calls.
+ *
+ * This method only updates the underlying stream if the font or font size differs
+ * from the previously set values.
+ *
+ *
+ * @param font the font to set
+ * @param fontSize the font size in points
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void setFont(PDFont font, float fontSize) throws IOException {
if (font != currentFont || fontSize != currentFontSize) {
pageContentStream.setFont(font, fontSize);
@@ -67,11 +119,31 @@ public void setFont(PDFont font, float fontSize) throws IOException {
}
}
+ /**
+ * Shows the given text at the current text position.
+ *
+ * This method ensures text mode is active before displaying the text.
+ *
+ *
+ * @param text the text to display
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void showText(String text) throws IOException {
beginText();
pageContentStream.showText(text);
}
+ /**
+ * Moves the text cursor to an absolute position on the page.
+ *
+ * This method calculates the offset from the current cursor position and handles
+ * rotation transformations when applicable. Text mode is activated if needed.
+ *
+ *
+ * @param tx the absolute X coordinate
+ * @param ty the absolute Y coordinate
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void newLineAt(float tx, float ty) throws IOException {
beginText();
float dx = tx - textCursorAbsoluteX;
@@ -85,6 +157,19 @@ public void newLineAt(float tx, float ty) throws IOException {
textCursorAbsoluteY = ty;
}
+ /**
+ * Draws an image at the specified position and size.
+ *
+ * This method automatically ends any active text mode before drawing the image.
+ *
+ *
+ * @param image the image to draw
+ * @param x the X coordinate of the image
+ * @param y the Y coordinate of the image
+ * @param width the width of the image
+ * @param height the height of the image
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void drawImage(PDImageXObject image, float x, float y, float width, float height) throws IOException {
endText();
pageContentStream.drawImage(image, x, y, width, height);
@@ -92,6 +177,16 @@ public void drawImage(PDImageXObject image, float x, float y, float width, float
private Color currentStrokingColor;
+ /**
+ * Sets the stroking (outline) color, with caching to avoid redundant calls.
+ *
+ * This method only updates the underlying stream if the color differs
+ * from the previously set stroking color. Also applies alpha transparency if needed.
+ *
+ *
+ * @param color the stroking color to set
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void setStrokingColor(Color color) throws IOException {
if (color != currentStrokingColor) {
pageContentStream.setStrokingColor(color);
@@ -111,6 +206,16 @@ public void setStrokingColor(Color color) throws IOException {
private Color currentNonStrokingColor;
+ /**
+ * Sets the non-stroking (fill) color, with caching to avoid redundant calls.
+ *
+ * This method only updates the underlying stream if the color differs
+ * from the previously set non-stroking color. Also applies alpha transparency if needed.
+ *
+ *
+ * @param color the non-stroking color to set
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void setNonStrokingColor(Color color) throws IOException {
if (color != currentNonStrokingColor) {
pageContentStream.setNonStrokingColor(color);
@@ -128,26 +233,76 @@ public void setNonStrokingColor(Color color) throws IOException {
}
}
+ /**
+ * Appends a rectangle to the current path.
+ *
+ * This method automatically ends any active text mode before adding the rectangle.
+ *
+ *
+ * @param x the X coordinate of the rectangle's lower-left corner
+ * @param y the Y coordinate of the rectangle's lower-left corner
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void addRect(float x, float y, float width, float height) throws IOException {
endText();
pageContentStream.addRect(x, y, width, height);
}
+ /**
+ * Starts a new subpath by moving to the specified point.
+ *
+ * This method automatically ends any active text mode before moving the path position.
+ *
+ *
+ * @param x the X coordinate to move to
+ * @param y the Y coordinate to move to
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void moveTo(float x, float y) throws IOException {
endText();
pageContentStream.moveTo(x, y);
}
+ /**
+ * Appends a line segment to the current path.
+ *
+ * This method automatically ends any active text mode before adding the line to the path.
+ *
+ *
+ * @param x the X coordinate of the line endpoint
+ * @param y the Y coordinate of the line endpoint
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void lineTo(float x, float y) throws IOException {
endText();
pageContentStream.lineTo(x, y);
}
+ /**
+ * Strokes the current path.
+ *
+ * This method automatically ends any active text mode before stroking the path.
+ * The path is rendered with the currently set stroking color and line properties.
+ *
+ *
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void stroke() throws IOException {
endText();
pageContentStream.stroke();
}
+ /**
+ * Fills the current path.
+ *
+ * This method automatically ends any active text mode before filling the path.
+ * The path is filled with the currently set non-stroking color.
+ *
+ *
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void fill() throws IOException {
endText();
pageContentStream.fill();
@@ -155,6 +310,16 @@ public void fill() throws IOException {
private float currentLineWidth = -1;
+ /**
+ * Sets the line width for stroking operations, with caching to avoid redundant calls.
+ *
+ * This method only updates the underlying stream if the line width differs
+ * from the previously set value. Any active text mode is ended before setting.
+ *
+ *
+ * @param lineWidth the line width in user space units
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void setLineWidth(float lineWidth) throws IOException {
if (lineWidth != currentLineWidth) {
endText();
@@ -165,6 +330,16 @@ public void setLineWidth(float lineWidth) throws IOException {
private int currentLineCapStyle = -1;
+ /**
+ * Sets the line cap style, with caching to avoid redundant calls.
+ *
+ * This method only updates the underlying stream if the line cap style differs
+ * from the previously set value. Any active text mode is ended before setting.
+ *
+ *
+ * @param lineCapStyle the line cap style (0 = butt, 1 = round, 2 = projecting square)
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void setLineCapStyle(int lineCapStyle) throws IOException {
if (lineCapStyle != currentLineCapStyle) {
endText();
@@ -176,6 +351,17 @@ public void setLineCapStyle(int lineCapStyle) throws IOException {
private float[] currentLineDashPattern;
private float currentLineDashPhase;
+ /**
+ * Sets the line dash pattern, with caching to avoid redundant calls.
+ *
+ * This method only updates the underlying stream if the pattern or phase differs
+ * from the previously set values. Any active text mode is ended before setting.
+ *
+ *
+ * @param pattern an array representing the dash pattern (e.g., [3, 2] for 3 on, 2 off)
+ * @param phase the phase of the dash pattern
+ * @throws IOException if an error occurs writing to the underlying stream
+ */
public void setLineDashPattern(float[] pattern, float phase) throws IOException {
if ((pattern != currentLineDashPattern &&
!Arrays.equals(pattern, currentLineDashPattern)) || phase != currentLineDashPhase) {
@@ -186,6 +372,15 @@ public void setLineDashPattern(float[] pattern, float phase) throws IOException
}
}
+ /**
+ * Closes the underlying PDPageContentStream.
+ *
+ * This method automatically ends any active text mode before closing the stream.
+ * After calling this method, the wrapper should not be used.
+ *
+ *
+ * @throws IOException if an error occurs closing the underlying stream
+ */
public void close() throws IOException {
endText();
pageContentStream.close();
diff --git a/src/main/resources/fonts/NotoSans-Bold.ttf b/src/main/resources/fonts/NotoSans-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..d04d169e099a243cd84ac81998abd103c3f83750
GIT binary patch
literal 627176
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