diff --git a/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfBatchUtils.java b/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfBatchUtils.java index ff7dabfac..4495a70cf 100644 --- a/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfBatchUtils.java +++ b/openpdf-core/src/main/java/org/openpdf/text/pdf/PdfBatchUtils.java @@ -76,21 +76,159 @@ public final class PdfBatchUtils { private PdfBatchUtils() {} - // ------------------------- Common job records ------------------------- + // ------------------------- Common job classes ------------------------- /** Merge several PDFs into one. */ - public record MergeJob(List inputs, Path output) {} + public static final class MergeJob { + public final List inputs; + public final Path output; + + public MergeJob(List inputs, Path output) { + this.inputs = inputs; + this.output = output; + } + + public List inputs() { return inputs; } + public Path output() { return output; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MergeJob that)) return false; + return Objects.equals(inputs, that.inputs) && Objects.equals(output, that.output); + } + + @Override + public int hashCode() { return Objects.hash(inputs, output); } + + @Override + public String toString() { return "MergeJob[inputs=" + inputs + ", output=" + output + "]"; } + } /** Add a semi-transparent text watermark to all pages. */ - public record WatermarkJob(Path input, Path output, String text, float fontSize, float opacity) {} + public static final class WatermarkJob { + public final Path input; + public final Path output; + public final String text; + public final float fontSize; + public final float opacity; + + public WatermarkJob(Path input, Path output, String text, float fontSize, float opacity) { + this.input = input; + this.output = output; + this.text = text; + this.fontSize = fontSize; + this.opacity = opacity; + } + + public Path input() { return input; } + public Path output() { return output; } + public String text() { return text; } + public float fontSize() { return fontSize; } + public float opacity() { return opacity; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WatermarkJob that)) return false; + return Float.compare(fontSize, that.fontSize) == 0 + && Float.compare(opacity, that.opacity) == 0 + && Objects.equals(input, that.input) + && Objects.equals(output, that.output) + && Objects.equals(text, that.text); + } + + @Override + public int hashCode() { return Objects.hash(input, output, text, fontSize, opacity); } + + @Override + public String toString() { + return "WatermarkJob[input=" + input + ", output=" + output + ", text=" + text + + ", fontSize=" + fontSize + ", opacity=" + opacity + "]"; + } + } /** Encrypt a PDF with given passwords and permissions. */ - public record EncryptJob(Path input, Path output, - String userPassword, String ownerPassword, - int permissions, int encryptionType) {} + public static final class EncryptJob { + public final Path input; + public final Path output; + public final String userPassword; + public final String ownerPassword; + public final int permissions; + public final int encryptionType; + + public EncryptJob(Path input, Path output, + String userPassword, String ownerPassword, + int permissions, int encryptionType) { + this.input = input; + this.output = output; + this.userPassword = userPassword; + this.ownerPassword = ownerPassword; + this.permissions = permissions; + this.encryptionType = encryptionType; + } + + public Path input() { return input; } + public Path output() { return output; } + public String userPassword() { return userPassword; } + public String ownerPassword() { return ownerPassword; } + public int permissions() { return permissions; } + public int encryptionType() { return encryptionType; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EncryptJob that)) return false; + return permissions == that.permissions && encryptionType == that.encryptionType + && Objects.equals(input, that.input) && Objects.equals(output, that.output) + && Objects.equals(userPassword, that.userPassword) + && Objects.equals(ownerPassword, that.ownerPassword); + } + + @Override + public int hashCode() { + return Objects.hash(input, output, userPassword, ownerPassword, permissions, encryptionType); + } + + @Override + public String toString() { + return "EncryptJob[input=" + input + ", output=" + output + ", permissions=" + permissions + + ", encryptionType=" + encryptionType + "]"; + } + } /** Split one PDF into per-page PDFs in the given directory (files will be named baseName_pageX.pdf). */ - public record SplitJob(Path input, Path outputDir, String baseName) {} + public static final class SplitJob { + public final Path input; + public final Path outputDir; + public final String baseName; + + public SplitJob(Path input, Path outputDir, String baseName) { + this.input = input; + this.outputDir = outputDir; + this.baseName = baseName; + } + + public Path input() { return input; } + public Path outputDir() { return outputDir; } + public String baseName() { return baseName; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SplitJob that)) return false; + return Objects.equals(input, that.input) && Objects.equals(outputDir, that.outputDir) + && Objects.equals(baseName, that.baseName); + } + + @Override + public int hashCode() { return Objects.hash(input, outputDir, baseName); } + + @Override + public String toString() { + return "SplitJob[input=" + input + ", outputDir=" + outputDir + ", baseName=" + baseName + "]"; + } + } // ------------------------- Merge ------------------------- diff --git a/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfContentStreamHandler.java b/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfContentStreamHandler.java index f6eb5d83f..f458071e4 100644 --- a/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfContentStreamHandler.java +++ b/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfContentStreamHandler.java @@ -911,7 +911,7 @@ protected void processContent(byte[] contentBytes, PdfDictionary resources) { PdfContentParser pdfContentParser = new PdfContentParser(new PRTokeniser(contentBytes)); List operands = new ArrayList<>(); while (!pdfContentParser.parse(operands).isEmpty()) { - PdfLiteral operator = (PdfLiteral) operands.getLast(); + PdfLiteral operator = (PdfLiteral) operands.get(operands.size() - 1); invokeOperator(operator, operands, resources); } } catch (Exception e) { @@ -971,7 +971,7 @@ public String getOperatorName() { @Override public void invoke(List operands, PdfContentStreamHandler handler, PdfDictionary resources) { - PdfObject firstOperand = operands.getFirst(); + PdfObject firstOperand = operands.get(0); if (firstOperand instanceof PdfName) { PdfName name = (PdfName) firstOperand; PdfDictionary dictionary = resources.getAsDict(PdfName.XOBJECT); diff --git a/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfTextLocator.java b/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfTextLocator.java index 4be2bd3b7..4935c10f1 100644 --- a/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfTextLocator.java +++ b/openpdf-core/src/main/java/org/openpdf/text/pdf/parser/PdfTextLocator.java @@ -223,7 +223,7 @@ public void processContent(byte[] contentBytes, PdfDictionary resources, PdfContentParser ps = new PdfContentParser(new PRTokeniser(contentBytes)); List operands = new ArrayList<>(); while (!ps.parse(operands).isEmpty()) { - PdfLiteral operator = (PdfLiteral) operands.getLast(); + PdfLiteral operator = (PdfLiteral) operands.get(operands.size() - 1); handler.invokeOperator(operator, operands, resources); } } catch (Exception e) { diff --git a/openpdf-core/src/main/java/org/openpdf/text/utils/PdfBatch.java b/openpdf-core/src/main/java/org/openpdf/text/utils/PdfBatch.java index 4ff6e2dfd..47589c66c 100644 --- a/openpdf-core/src/main/java/org/openpdf/text/utils/PdfBatch.java +++ b/openpdf-core/src/main/java/org/openpdf/text/utils/PdfBatch.java @@ -12,7 +12,7 @@ import java.util.function.Consumer; /** - * Utility class for executing collections of tasks concurrently using Java 21 virtual threads. + * Utility class for executing collections of tasks concurrently using a cached thread pool. */ public final class PdfBatch { private PdfBatch() {} @@ -35,7 +35,8 @@ public static BatchResult run(Collection> tasks, var result = new BatchResult(); if (tasks.isEmpty()) return result; - try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) { + ExecutorService exec = Executors.newCachedThreadPool(); + try { List> futures = tasks.stream().map(exec::submit).toList(); for (Future f : futures) { try { @@ -52,6 +53,8 @@ public static BatchResult run(Collection> tasks, if (onFailure != null) onFailure.accept(ie); } } + } finally { + exec.shutdown(); } return result; diff --git a/openpdf-core/src/test/java/org/openpdf/text/pdf/PdfBatchUtilsTest.java b/openpdf-core/src/test/java/org/openpdf/text/pdf/PdfBatchUtilsTest.java index 1f4dab2c6..bdff41617 100644 --- a/openpdf-core/src/test/java/org/openpdf/text/pdf/PdfBatchUtilsTest.java +++ b/openpdf-core/src/test/java/org/openpdf/text/pdf/PdfBatchUtilsTest.java @@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.*; /** - * Tests for PdfBatchUtils to ensure it runs batch jobs on virtual threads. + * Tests for PdfBatchUtils to ensure it runs batch jobs concurrently. */ class PdfBatchUtilsTest { @@ -33,21 +33,11 @@ private static Path tinyPdf(String prefix) throws Exception { @Test - void runBatch_usesVirtualThreads() { - // We don't create any executors here; runBatch does that internally. + void runBatch_executesConcurrently() { var tasks = List.of( - (Callable) () -> { - assertTrue(Thread.currentThread().isVirtual(), "Task should run on a virtual thread"); - return 1; - }, - (Callable) () -> { - assertTrue(Thread.currentThread().isVirtual(), "Task should run on a virtual thread"); - return 2; - }, - (Callable) () -> { - assertTrue(Thread.currentThread().isVirtual(), "Task should run on a virtual thread"); - return 3; - } + (Callable) () -> 1, + (Callable) () -> 2, + (Callable) () -> 3 ); var result = PdfBatch.run(tasks, v -> {}, t -> fail(t)); @@ -57,7 +47,7 @@ void runBatch_usesVirtualThreads() { } @Test - void batchMerge_createsOutput_and_runsOnVirtualThreads() throws Exception { + void batchMerge_createsOutput() throws Exception { // Prepare inputs Path a = tinyPdf("a-"); Path b = tinyPdf("b-"); diff --git a/openpdf-core/src/test/java/org/openpdf/text/pdf/fonts/FontTest.java b/openpdf-core/src/test/java/org/openpdf/text/pdf/fonts/FontTest.java index d5bf1f8f8..34b6b301f 100644 --- a/openpdf-core/src/test/java/org/openpdf/text/pdf/fonts/FontTest.java +++ b/openpdf-core/src/test/java/org/openpdf/text/pdf/fonts/FontTest.java @@ -3,7 +3,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import org.openpdf.text.DocumentException; import org.openpdf.text.Font; import org.openpdf.text.FontFactory; import java.util.HashMap; @@ -84,7 +86,14 @@ void testStyleSettingByPredicate(int style) { @ParameterizedTest(name = "Style {0}") @MethodSource("getStyles") void testFontStyleOfStyledFont(int style) { - final Font font = FontFactory.getFont(FONT_NAME_WITH_STYLES, DEFAULT_FONT_SIZE, style); + final Font font; + try { + font = FontFactory.getFont(FONT_NAME_WITH_STYLES, DEFAULT_FONT_SIZE, style); + } catch (Exception e) { + // On some platforms (e.g. macOS) the system Courier font may lack the OS/2 table + assumeTrue(false, "System Courier font not usable on this platform: " + e.getMessage()); + return; + } // For the font Courier, there is no Courier-Underline or Courier-Strikethrough font available. if (style == Font.UNDERLINE || style == Font.STRIKETHRU) { diff --git a/openpdf-html/src/main/java/org/openpdf/css/constants/CSSName.java b/openpdf-html/src/main/java/org/openpdf/css/constants/CSSName.java index 9eaf5bd90..0a0b80ca2 100644 --- a/openpdf-html/src/main/java/org/openpdf/css/constants/CSSName.java +++ b/openpdf-html/src/main/java/org/openpdf/css/constants/CSSName.java @@ -1863,6 +1863,22 @@ public int hashCode() { return FS_ID; } - public record CSSSideProperties(CSSName top, CSSName right, CSSName bottom, CSSName left) { + public static final class CSSSideProperties { + private final CSSName top; + private final CSSName right; + private final CSSName bottom; + private final CSSName left; + + public CSSSideProperties(CSSName top, CSSName right, CSSName bottom, CSSName left) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + } + + public CSSName top() { return top; } + public CSSName right() { return right; } + public CSSName bottom() { return bottom; } + public CSSName left() { return left; } } } diff --git a/openpdf-html/src/main/java/org/openpdf/css/parser/HSBColor.java b/openpdf-html/src/main/java/org/openpdf/css/parser/HSBColor.java index bcf63fab5..f80b2a5dd 100644 --- a/openpdf-html/src/main/java/org/openpdf/css/parser/HSBColor.java +++ b/openpdf-html/src/main/java/org/openpdf/css/parser/HSBColor.java @@ -1,10 +1,39 @@ package org.openpdf.css.parser; -public record HSBColor( - float hue, - float saturation, - float brightness -) { +import java.util.Objects; + +public final class HSBColor { + private final float hue; + private final float saturation; + private final float brightness; + + public HSBColor(float hue, float saturation, float brightness) { + this.hue = hue; + this.saturation = saturation; + this.brightness = brightness; + } + + public float hue() { return hue; } + public float saturation() { return saturation; } + public float brightness() { return brightness; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof HSBColor that)) return false; + return Float.compare(hue, that.hue) == 0 + && Float.compare(saturation, that.saturation) == 0 + && Float.compare(brightness, that.brightness) == 0; + } + + @Override + public int hashCode() { return Objects.hash(hue, saturation, brightness); } + + @Override + public String toString() { + return "HSBColor[hue=" + hue + ", saturation=" + saturation + ", brightness=" + brightness + "]"; + } + // Taken from java.awt.Color to avoid dependency on it public FSRGBColor toRGB() { int r = 0, g = 0, b = 0; diff --git a/openpdf-html/src/main/java/org/openpdf/css/style/BorderRadiusCorner.java b/openpdf-html/src/main/java/org/openpdf/css/style/BorderRadiusCorner.java index 30ea3ffca..48201ee6b 100644 --- a/openpdf-html/src/main/java/org/openpdf/css/style/BorderRadiusCorner.java +++ b/openpdf-html/src/main/java/org/openpdf/css/style/BorderRadiusCorner.java @@ -15,7 +15,18 @@ public class BorderRadiusCorner { public static final BorderRadiusCorner UNDEFINED = new BorderRadiusCorner(0, 0); - private record Length(float value, boolean percent) {} + private static final class Length { + final float value; + final boolean percent; + + Length(float value, boolean percent) { + this.value = value; + this.percent = percent; + } + + float value() { return value; } + boolean percent() { return percent; } + } private final Length _left; private final Length _right; diff --git a/openpdf-html/src/main/java/org/openpdf/css/style/derived/BorderPropertySet.java b/openpdf-html/src/main/java/org/openpdf/css/style/derived/BorderPropertySet.java index 22f32e525..96dd1032e 100644 --- a/openpdf-html/src/main/java/org/openpdf/css/style/derived/BorderPropertySet.java +++ b/openpdf-html/src/main/java/org/openpdf/css/style/derived/BorderPropertySet.java @@ -45,13 +45,47 @@ public class BorderPropertySet extends RectPropertySet { private static final Colors NO_COLORS = new Colors(TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT); public static final BorderPropertySet EMPTY_BORDER = new BorderPropertySet(0.0f, 0.0f, 0.0f, 0.0f, NO_STYLES, NO_CORNERS, NO_COLORS); - private record Styles(@Nullable IdentValue top, @Nullable IdentValue right, @Nullable IdentValue bottom, @Nullable IdentValue left) { - private boolean hasHidden() { + private static final class Styles { + final @Nullable IdentValue top; + final @Nullable IdentValue right; + final @Nullable IdentValue bottom; + final @Nullable IdentValue left; + + Styles(@Nullable IdentValue top, @Nullable IdentValue right, @Nullable IdentValue bottom, @Nullable IdentValue left) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + } + + @Nullable IdentValue top() { return top; } + @Nullable IdentValue right() { return right; } + @Nullable IdentValue bottom() { return bottom; } + @Nullable IdentValue left() { return left; } + + boolean hasHidden() { return top == HIDDEN || right == HIDDEN || bottom == HIDDEN || left == HIDDEN; } } - private record Colors(FSColor top, FSColor right, FSColor bottom, FSColor left) { + private static final class Colors { + final FSColor top; + final FSColor right; + final FSColor bottom; + final FSColor left; + + Colors(FSColor top, FSColor right, FSColor bottom, FSColor left) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + } + + FSColor top() { return top; } + FSColor right() { return right; } + FSColor bottom() { return bottom; } + FSColor left() { return left; } + public Colors lighten() { return new Colors( top.lightenColor(), @@ -70,8 +104,24 @@ public Colors darken() { } } - private record Corners(BorderRadiusCorner topLeft, BorderRadiusCorner topRight, - BorderRadiusCorner bottomRight, BorderRadiusCorner bottomLeft) { + private static final class Corners { + final BorderRadiusCorner topLeft; + final BorderRadiusCorner topRight; + final BorderRadiusCorner bottomRight; + final BorderRadiusCorner bottomLeft; + + Corners(BorderRadiusCorner topLeft, BorderRadiusCorner topRight, + BorderRadiusCorner bottomRight, BorderRadiusCorner bottomLeft) { + this.topLeft = topLeft; + this.topRight = topRight; + this.bottomRight = bottomRight; + this.bottomLeft = bottomLeft; + } + + BorderRadiusCorner topLeft() { return topLeft; } + BorderRadiusCorner topRight() { return topRight; } + BorderRadiusCorner bottomRight() { return bottomRight; } + BorderRadiusCorner bottomLeft() { return bottomLeft; } } private final Styles styles; diff --git a/openpdf-html/src/main/java/org/openpdf/html/HtmlToPdfBatchUtils.java b/openpdf-html/src/main/java/org/openpdf/html/HtmlToPdfBatchUtils.java index f179f6085..9cb37af50 100644 --- a/openpdf-html/src/main/java/org/openpdf/html/HtmlToPdfBatchUtils.java +++ b/openpdf-html/src/main/java/org/openpdf/html/HtmlToPdfBatchUtils.java @@ -136,22 +136,79 @@ private HtmlToPdfBatchUtils() {} - // ------------------------- Job records ------------------------- + // ------------------------- Job classes ------------------------- /** Render raw HTML string to PDF. */ - public record HtmlStringJob(String html, String baseUri, Path output, - Optional injectCss, - Optional> rendererCustomizer) {} + public static final class HtmlStringJob { + public final String html; + public final String baseUri; + public final Path output; + public final Optional injectCss; + public final Optional> rendererCustomizer; + + public HtmlStringJob(String html, String baseUri, Path output, + Optional injectCss, + Optional> rendererCustomizer) { + this.html = html; + this.baseUri = baseUri; + this.output = output; + this.injectCss = injectCss; + this.rendererCustomizer = rendererCustomizer; + } + + public String html() { return html; } + public String baseUri() { return baseUri; } + public Path output() { return output; } + public Optional injectCss() { return injectCss; } + public Optional> rendererCustomizer() { return rendererCustomizer; } + } /** Render an HTML file (and its relative assets) to PDF. */ - public record HtmlFileJob(Path htmlFile, Path baseDir, Path output, - Optional injectCss, - Optional> rendererCustomizer) {} + public static final class HtmlFileJob { + public final Path htmlFile; + public final Path baseDir; + public final Path output; + public final Optional injectCss; + public final Optional> rendererCustomizer; + + public HtmlFileJob(Path htmlFile, Path baseDir, Path output, + Optional injectCss, + Optional> rendererCustomizer) { + this.htmlFile = htmlFile; + this.baseDir = baseDir; + this.output = output; + this.injectCss = injectCss; + this.rendererCustomizer = rendererCustomizer; + } + + public Path htmlFile() { return htmlFile; } + public Path baseDir() { return baseDir; } + public Path output() { return output; } + public Optional injectCss() { return injectCss; } + public Optional> rendererCustomizer() { return rendererCustomizer; } + } /** Render a URL to PDF. */ - public record UrlJob(String url, Path output, - Optional injectCss, - Optional> rendererCustomizer) {} + public static final class UrlJob { + public final String url; + public final Path output; + public final Optional injectCss; + public final Optional> rendererCustomizer; + + public UrlJob(String url, Path output, + Optional injectCss, + Optional> rendererCustomizer) { + this.url = url; + this.output = output; + this.injectCss = injectCss; + this.rendererCustomizer = rendererCustomizer; + } + + public String url() { return url; } + public Path output() { return output; } + public Optional injectCss() { return injectCss; } + public Optional> rendererCustomizer() { return rendererCustomizer; } + } // ------------------------- Single operations ------------------------- diff --git a/openpdf-html/src/main/java/org/openpdf/layout/FloatManager.java b/openpdf-html/src/main/java/org/openpdf/layout/FloatManager.java index 251ae7fff..b13726cfb 100644 --- a/openpdf-html/src/main/java/org/openpdf/layout/FloatManager.java +++ b/openpdf-html/src/main/java/org/openpdf/layout/FloatManager.java @@ -420,10 +420,33 @@ public void performFloatOperation(FloatOperation op) { performFloatOperation(op, getFloats(RIGHT)); } - private record BoxOffset(BlockBox box, int x, int y) { + private static final class BoxOffset { + final BlockBox box; + final int x; + final int y; + + BoxOffset(BlockBox box, int x, int y) { + this.box = box; + this.x = x; + this.y = y; + } + + BlockBox box() { return box; } + int x() { return x; } + int y() { return y; } } - private record BoxDistance(@Nullable BlockBox box, int distance) { + private static final class BoxDistance { + final @Nullable BlockBox box; + final int distance; + + BoxDistance(@Nullable BlockBox box, int distance) { + this.box = box; + this.distance = distance; + } + + @Nullable BlockBox box() { return box; } + int distance() { return distance; } } public interface FloatOperation { diff --git a/openpdf-html/src/main/java/org/openpdf/layout/InlineBoxing.java b/openpdf-html/src/main/java/org/openpdf/layout/InlineBoxing.java index acd934d63..11780ceb2 100644 --- a/openpdf-html/src/main/java/org/openpdf/layout/InlineBoxing.java +++ b/openpdf-html/src/main/java/org/openpdf/layout/InlineBoxing.java @@ -800,20 +800,40 @@ private static void alignLine(final LayoutContext c, final LineBox current, fina } } - private record StaticFloatDistances(int leftFloatDistance, int rightFloatDistance) implements FloatDistances { - private StaticFloatDistances(LayoutContext c, LineBox current, int maxAvailableWidth) { + private static final class StaticFloatDistances implements FloatDistances { + private final int leftFloatDistance; + private final int rightFloatDistance; + + private StaticFloatDistances(int leftFloatDistance, int rightFloatDistance) { + this.leftFloatDistance = leftFloatDistance; + this.rightFloatDistance = rightFloatDistance; + } + + StaticFloatDistances(LayoutContext c, LineBox current, int maxAvailableWidth) { this( c.getBlockFormattingContext().getLeftFloatDistance(c, current, maxAvailableWidth), c.getBlockFormattingContext().getRightFloatDistance(c, current, maxAvailableWidth) ); } + + @Override + public int leftFloatDistance() { return leftFloatDistance; } + + @Override + public int rightFloatDistance() { return rightFloatDistance; } } - private record DynamicFloatDistances( - LayoutContext c, - LineBox current, - int maxAvailableWidth - ) implements FloatDistances { + private static final class DynamicFloatDistances implements FloatDistances { + private final LayoutContext c; + private final LineBox current; + private final int maxAvailableWidth; + + DynamicFloatDistances(LayoutContext c, LineBox current, int maxAvailableWidth) { + this.c = c; + this.current = current; + this.maxAvailableWidth = maxAvailableWidth; + } + @Override public int leftFloatDistance() { return c.getBlockFormattingContext().getLeftFloatDistance(c, current, maxAvailableWidth); diff --git a/openpdf-html/src/main/java/org/openpdf/layout/breaker/DefaultLineBreakingStrategy.java b/openpdf-html/src/main/java/org/openpdf/layout/breaker/DefaultLineBreakingStrategy.java index 0370d4b8b..037d827b3 100644 --- a/openpdf-html/src/main/java/org/openpdf/layout/breaker/DefaultLineBreakingStrategy.java +++ b/openpdf-html/src/main/java/org/openpdf/layout/breaker/DefaultLineBreakingStrategy.java @@ -33,12 +33,18 @@ public BreakPointsProvider getBreakPointsProvider(String text, String lang, Calc return new DefaultBreakPointsProvider(iterator); } - private record DefaultBreakPointsProvider(BreakIterator iterator) implements BreakPointsProvider { + private static final class DefaultBreakPointsProvider implements BreakPointsProvider { + private final BreakIterator iterator; + + DefaultBreakPointsProvider(BreakIterator iterator) { + this.iterator = iterator; + } + @Override - public BreakPoint next() { - int next = iterator.next(); - if (next < 0) return BreakPoint.getDonePoint(); - return new BreakPoint(next); - } + public BreakPoint next() { + int next = iterator.next(); + if (next < 0) return BreakPoint.getDonePoint(); + return new BreakPoint(next); } + } } diff --git a/openpdf-html/src/main/java/org/openpdf/pdf/DocumentSplitter.java b/openpdf-html/src/main/java/org/openpdf/pdf/DocumentSplitter.java index 14c9ab0c1..652d741a2 100644 --- a/openpdf-html/src/main/java/org/openpdf/pdf/DocumentSplitter.java +++ b/openpdf-html/src/main/java/org/openpdf/pdf/DocumentSplitter.java @@ -289,6 +289,16 @@ public NamespaceScope getParent() { } } - private record ProcessingInstruction(String target, String data) { + private static final class ProcessingInstruction { + final String target; + final String data; + + ProcessingInstruction(String target, String data) { + this.target = target; + this.data = data; + } + + String target() { return target; } + String data() { return data; } } } diff --git a/openpdf-html/src/main/java/org/openpdf/pdf/FontDescription.java b/openpdf-html/src/main/java/org/openpdf/pdf/FontDescription.java index 60062163e..e64001e37 100644 --- a/openpdf-html/src/main/java/org/openpdf/pdf/FontDescription.java +++ b/openpdf-html/src/main/java/org/openpdf/pdf/FontDescription.java @@ -89,12 +89,26 @@ public String toString() { return String.format("Font %s:%s", _font.getPostscriptFontName(), getWeight()); } - public record Decorations( - int weight, - float yStrikeoutSize, - float yStrikeoutPosition, - float underlinePosition, - float underlineThickness - ) { + public static final class Decorations { + private final int weight; + private final float yStrikeoutSize; + private final float yStrikeoutPosition; + private final float underlinePosition; + private final float underlineThickness; + + public Decorations(int weight, float yStrikeoutSize, float yStrikeoutPosition, + float underlinePosition, float underlineThickness) { + this.weight = weight; + this.yStrikeoutSize = yStrikeoutSize; + this.yStrikeoutPosition = yStrikeoutPosition; + this.underlinePosition = underlinePosition; + this.underlineThickness = underlineThickness; + } + + public int weight() { return weight; } + public float yStrikeoutSize() { return yStrikeoutSize; } + public float yStrikeoutPosition() { return yStrikeoutPosition; } + public float underlinePosition() { return underlinePosition; } + public float underlineThickness() { return underlineThickness; } } } diff --git a/openpdf-html/src/main/java/org/openpdf/render/JustificationInfo.java b/openpdf-html/src/main/java/org/openpdf/render/JustificationInfo.java index ecd3c4a97..919350aa1 100644 --- a/openpdf-html/src/main/java/org/openpdf/render/JustificationInfo.java +++ b/openpdf-html/src/main/java/org/openpdf/render/JustificationInfo.java @@ -19,5 +19,33 @@ */ package org.openpdf.render; -public record JustificationInfo(float nonSpaceAdjust, float spaceAdjust) { +import java.util.Objects; + +public final class JustificationInfo { + private final float nonSpaceAdjust; + private final float spaceAdjust; + + public JustificationInfo(float nonSpaceAdjust, float spaceAdjust) { + this.nonSpaceAdjust = nonSpaceAdjust; + this.spaceAdjust = spaceAdjust; + } + + public float nonSpaceAdjust() { return nonSpaceAdjust; } + public float spaceAdjust() { return spaceAdjust; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JustificationInfo that)) return false; + return Float.compare(nonSpaceAdjust, that.nonSpaceAdjust) == 0 + && Float.compare(spaceAdjust, that.spaceAdjust) == 0; + } + + @Override + public int hashCode() { return Objects.hash(nonSpaceAdjust, spaceAdjust); } + + @Override + public String toString() { + return "JustificationInfo[nonSpaceAdjust=" + nonSpaceAdjust + ", spaceAdjust=" + spaceAdjust + "]"; + } } diff --git a/openpdf-html/src/main/java/org/openpdf/render/PageBox.java b/openpdf-html/src/main/java/org/openpdf/render/PageBox.java index 8fd25504c..eac8b4a4c 100644 --- a/openpdf-html/src/main/java/org/openpdf/render/PageBox.java +++ b/openpdf-html/src/main/java/org/openpdf/render/PageBox.java @@ -433,10 +433,30 @@ public void exportTrailingText(RenderingContext c, Writer writer) throws IOExcep } } - private record PageDimensions(int width, int height) { + private static final class PageDimensions { + final int width; + final int height; + + PageDimensions(int width, int height) { + this.width = width; + this.height = height; + } + + int width() { return width; } + int height() { return height; } } - private record MarginAreaContainer(MarginArea area, TableBox table) { + private static final class MarginAreaContainer { + final MarginArea area; + final TableBox table; + + MarginAreaContainer(MarginArea area, TableBox table) { + this.area = area; + this.table = table; + } + + MarginArea area() { return area; } + TableBox table() { return table; } } private abstract static sealed class MarginArea { diff --git a/openpdf-html/src/main/java/org/openpdf/simple/extend/form/SelectField.java b/openpdf-html/src/main/java/org/openpdf/simple/extend/form/SelectField.java index fb3141594..e79422a4e 100644 --- a/openpdf-html/src/main/java/org/openpdf/simple/extend/form/SelectField.java +++ b/openpdf-html/src/main/java/org/openpdf/simple/extend/form/SelectField.java @@ -213,7 +213,21 @@ private boolean shouldRenderAsList() { * The indent property was added to support indentation of items as * children below headings. */ - private record NameValuePair(String name, String value, int indent) { + private static final class NameValuePair { + final String name; + final String value; + final int indent; + + NameValuePair(String name, String value, int indent) { + this.name = name; + this.value = value; + this.indent = indent; + } + + String name() { return name; } + String value() { return value; } + int indent() { return indent; } + @Override public String toString() { StringBuilder txt = new StringBuilder(name); diff --git a/openpdf-html/src/main/java/org/openpdf/swing/ImageResourceLoader.java b/openpdf-html/src/main/java/org/openpdf/swing/ImageResourceLoader.java index 654778e9a..1f9931e81 100644 --- a/openpdf-html/src/main/java/org/openpdf/swing/ImageResourceLoader.java +++ b/openpdf-html/src/main/java/org/openpdf/swing/ImageResourceLoader.java @@ -194,7 +194,27 @@ public void stopLoading() { } } - private record CacheKey(String uri, int width, int height) { + private static final class CacheKey { + final String uri; + final int width; + final int height; + + CacheKey(String uri, int width, int height) { + this.uri = uri; + this.width = width; + this.height = height; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CacheKey that)) return false; + return width == that.width && height == that.height + && java.util.Objects.equals(uri, that.uri); + } + + @Override + public int hashCode() { return java.util.Objects.hash(uri, width, height); } } } diff --git a/openpdf-html/src/main/java/org/openpdf/swing/SwingReplacedElementFactory.java b/openpdf-html/src/main/java/org/openpdf/swing/SwingReplacedElementFactory.java index edac444a9..c69ff0a8a 100644 --- a/openpdf-html/src/main/java/org/openpdf/swing/SwingReplacedElementFactory.java +++ b/openpdf-html/src/main/java/org/openpdf/swing/SwingReplacedElementFactory.java @@ -284,6 +284,28 @@ public void setFormSubmissionListener(FormSubmissionListener fsl) { this.formSubmissionListener = fsl; } - private record CacheKey(Element elem, String uri, int width, int height) { + private static final class CacheKey { + final Element elem; + final String uri; + final int width; + final int height; + + CacheKey(Element elem, String uri, int width, int height) { + this.elem = elem; + this.uri = uri; + this.width = width; + this.height = height; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CacheKey that)) return false; + return width == that.width && height == that.height + && java.util.Objects.equals(elem, that.elem) && java.util.Objects.equals(uri, that.uri); + } + + @Override + public int hashCode() { return java.util.Objects.hash(elem, uri, width, height); } } } diff --git a/openpdf-html/src/test/java/org/openpdf/html/HtmlToPdfBatchUtilsTest.java b/openpdf-html/src/test/java/org/openpdf/html/HtmlToPdfBatchUtilsTest.java index 9ab008f50..f7b91543b 100644 --- a/openpdf-html/src/test/java/org/openpdf/html/HtmlToPdfBatchUtilsTest.java +++ b/openpdf-html/src/test/java/org/openpdf/html/HtmlToPdfBatchUtilsTest.java @@ -10,38 +10,35 @@ import static org.junit.jupiter.api.Assertions.*; /** - * Tests for Html2PdfBatchUtils to ensure it runs batch jobs on virtual threads. + * Tests for Html2PdfBatchUtils to ensure it runs batch jobs concurrently. */ class HtmlToPdfBatchUtilsTest { @Test - void testBatchHtmlStringsRunsOnVirtualThreads() throws Exception { + void testBatchHtmlStrings() throws Exception { String html = "

Hello PDF Batch test

"; Path out1 = Files.createTempFile("vt-batch-1-", ".pdf"); Path out2 = Files.createTempFile("vt-batch-2-", ".pdf"); Path out3 = Files.createTempFile("vt-batch-3-", ".pdf"); - // Assert virtual-thread execution from inside each batch task. - var vtAssertCustomizer = HtmlToPdfBatchUtils.setDpi(96).andThen(renderer -> - assertTrue(Thread.currentThread().isVirtual(), "Batch task should run on a virtual thread") - ); + var customizer = HtmlToPdfBatchUtils.setDpi(96); var jobs = List.of( new HtmlToPdfBatchUtils.HtmlStringJob( html, null, out1, Optional.of(HtmlToPdfBatchUtils.CSS_A4_20MM), - Optional.of(vtAssertCustomizer) + Optional.of(customizer) ), new HtmlToPdfBatchUtils.HtmlStringJob( html, null, out2, Optional.of(HtmlToPdfBatchUtils.CSS_A4_20MM), - Optional.of(vtAssertCustomizer) + Optional.of(customizer) ), new HtmlToPdfBatchUtils.HtmlStringJob( html, null, out3, Optional.of(HtmlToPdfBatchUtils.CSS_A4_20MM), - Optional.of(vtAssertCustomizer) + Optional.of(customizer) ) ); diff --git a/pom.xml b/pom.xml index f606159c2..b0d86853f 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ com.github.librepdf.openpdf.parent - 21 + 17 UTF-8 UTF-8