diff --git a/build.gradle b/build.gradle index d3d451d..e8ba540 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,25 @@ plugins { id 'application' } +// 编码设置 - UTF-8に統一 + +// タスクの文字エンコーディング設定 +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +tasks.withType(Javadoc) { + options.encoding = 'UTF-8' +} + +tasks.withType(Test) { + jvmArgs '-Dfile.encoding=UTF-8' +} + +run { + jvmArgs '-Dfile.encoding=UTF-8' +} + group = 'co.jp.techsor.pdf' version = '1.0.0' sourceCompatibility = '17' @@ -15,7 +34,7 @@ dependencies { implementation 'com.itextpdf:kernel:8.0.2' implementation 'com.itextpdf:layout:8.0.2' implementation 'commons-cli:commons-cli:1.5.0' - + testImplementation 'junit:junit:4.13.2' } @@ -26,7 +45,7 @@ application { jar { manifest { attributes( - 'Main-Class': 'co.jp.techsor.pdf.App' + 'Main-Class': 'co.jp.techsor.pdf.App' ) } from { @@ -58,4 +77,18 @@ task probeJar(type: Jar) { // 両方のJARを作成するタスク task allJars { dependsOn fatJar, probeJar +} + +// 图层修复测试タスク +task runLayerTest(type: JavaExec) { + mainClass = 'co.jp.techsor.pdf.TestLayerFix' + classpath = sourceSets.main.runtimeClasspath + jvmArgs '-Dfile.encoding=UTF-8' +} + +// 详细图层修复测试タスク +task runLayerTestDetailed(type: JavaExec) { + mainClass = 'co.jp.techsor.pdf.TestLayerFixDetailed' + classpath = sourceSets.main.runtimeClasspath + jvmArgs '-Dfile.encoding=UTF-8' } \ No newline at end of file diff --git a/src/main/java/co/jp/techsor/pdf/PdfMemoLayerService.java b/src/main/java/co/jp/techsor/pdf/PdfMemoLayerService.java index 9237248..1a2c38e 100644 --- a/src/main/java/co/jp/techsor/pdf/PdfMemoLayerService.java +++ b/src/main/java/co/jp/techsor/pdf/PdfMemoLayerService.java @@ -19,6 +19,95 @@ import java.util.List; */ public class PdfMemoLayerService { + /** + * 指定された名前のレイヤーを取得または作成する + * + * @param pdf PDFドキュメント + * @param layerName レイヤー名 + * @param initialVisibility 初期表示状態 + * @return 既存または新しく作成されたレイヤー + */ + private PdfLayer getOrCreateLayer(PdfDocument pdf, String layerName, boolean initialVisibility) { + // 既存レイヤーを検索 + PdfDictionary catalog = pdf.getCatalog().getPdfObject(); + + // 统一使用new PdfName()创建名称对象,确保iText 8.0.2兼容性 + PdfDictionary ocProps = catalog.getAsDictionary(new PdfName("OCProperties")); + + System.out.println("レイヤー検索開始: " + layerName); + System.out.println("Catalog OCProperties: " + (ocProps != null ? "存在" : "不存在")); + + if (ocProps != null) { + // 检查OCGs数组 + PdfArray ocgs = ocProps.getAsArray(new PdfName("OCGs")); + System.out.println("OCGs数组: " + (ocgs != null ? "存在 (size: " + ocgs.size() + ")" : "不存在")); + + if (ocgs != null) { + for (int i = 0; i < ocgs.size(); i++) { + PdfObject ocgObj = ocgs.get(i); + System.out.println("OCG[" + i + "] タイプ: " + ocgObj.getClass().getSimpleName()); + + if (ocgObj instanceof PdfDictionary ocgDict) { + PdfString name = ocgDict.getAsString(new PdfName("Name")); + if (name != null) { + // エンコーディング問題を回避するためにtoUnicodeString()を使用 + String foundName = name.toUnicodeString(); + System.out.println("OCG[" + i + "] 名前: " + foundName); + + // 使用equalsIgnoreCase进行不区分大小写的比较,提高兼容性 + if (layerName.equalsIgnoreCase(foundName)) { + // 既存レイヤーを見つけた場合 + System.out.println("既存レイヤーを使用します: " + layerName + " (見つかった名前: " + foundName + ")"); + PdfLayer existingLayer = new PdfLayer(ocgDict); + existingLayer.setOn(initialVisibility); // 初期表示状態を更新 + return existingLayer; + } + } + } else if (ocgObj instanceof PdfIndirectReference ref) { + // 处理间接引用 + PdfObject directObj = ref.getRefersTo(); + if (directObj instanceof PdfDictionary ocgDict) { + PdfString name = ocgDict.getAsString(new PdfName("Name")); + if (name != null) { + // エンコーディング問題を回避するためにtoUnicodeString()を使用 + String foundName = name.toUnicodeString(); + System.out.println("OCG[" + i + "] (间接引用) 名前: " + foundName); + + if (layerName.equalsIgnoreCase(foundName)) { + System.out.println("既存レイヤーを使用します: " + layerName + " (見つかった名前: " + foundName + ")"); + PdfLayer existingLayer = new PdfLayer(ocgDict); + existingLayer.setOn(initialVisibility); + return existingLayer; + } + } + } + } + } + } + + // 检查可选内容配置(可能在其他位置) + PdfDictionary ocConfig = ocProps.getAsDictionary(new PdfName("OCConfig")); + if (ocConfig != null) { + PdfArray onArray = ocConfig.getAsArray(new PdfName("On")); + if (onArray != null) { + System.out.println("OCConfig On数组存在 (size: " + onArray.size() + ")"); + } + + PdfArray offArray = ocConfig.getAsArray(new PdfName("Off")); + if (offArray != null) { + System.out.println("OCConfig Off数组存在 (size: " + offArray.size() + ")"); + } + } + } + + // レイヤーが存在しない場合、新しく作成 + System.out.println("新しいレイヤーを作成します: " + layerName); + // PdfLayerコンストラクタは自動的にOCGs配列に追加します + PdfLayer newLayer = new PdfLayer(layerName, pdf); + newLayer.setOn(initialVisibility); + return newLayer; + } + /** * PDFにメモ用レイヤーを2つ追加し、オプションで描画を行う * @@ -72,12 +161,9 @@ public class PdfMemoLayerService { try (PdfDocument pdf = new PdfDocument(new PdfReader(inputPath, readerProps), new PdfWriter(outputPath, writerProps))) { - // レイヤー(OCG)の作成 - PdfLayer memoLayer1 = new PdfLayer(layer1Name, pdf); - memoLayer1.setOn(layer1On); - - PdfLayer memoLayer2 = new PdfLayer(layer2Name, pdf); - memoLayer2.setOn(layer2On); + // レイヤー(OCG)の作成または既存レイヤーの取得 + PdfLayer memoLayer1 = getOrCreateLayer(pdf, layer1Name, layer1On); + PdfLayer memoLayer2 = getOrCreateLayer(pdf, layer2Name, layer2On); System.out.println("レイヤーを作成しました:"); System.out.println(" - " + layer1Name + " (初期状態: " + (layer1On ? "ON" : "OFF") + ")"); @@ -239,7 +325,12 @@ public class PdfMemoLayerService { * @return 対象レイヤー */ private PdfLayer determineTargetLayer(PdfAnnotation annotation, PdfLayer layer1, PdfLayer layer2, int defaultLayer) { - // 注釈タイプに基づく自動判定ロジック(カスタマイズ可能) + // 如果用户明确指定了图层(不等于默认值1),则忽略注释类型,使用指定的图层 + if (defaultLayer != 1) { + return (defaultLayer == 1) ? layer1 : layer2; + } + + // デフォルトの自動判定ロジック(ユーザーがレイヤーを指定しない場合のみ使用) PdfName subtype = annotation.getSubtype(); if (subtype != null) { @@ -260,11 +351,11 @@ public class PdfMemoLayerService { default: // デフォルトレイヤーを使用 - return (defaultLayer == 1) ? layer1 : layer2; + return layer1; } } // サブタイプが不明な場合はデフォルトレイヤーを使用 - return (defaultLayer == 1) ? layer1 : layer2; + return layer1; } } diff --git a/src/main/java/co/jp/techsor/pdf/TestLayerFix.java b/src/main/java/co/jp/techsor/pdf/TestLayerFix.java new file mode 100644 index 0000000..79b7fba --- /dev/null +++ b/src/main/java/co/jp/techsor/pdf/TestLayerFix.java @@ -0,0 +1,93 @@ +package co.jp.techsor.pdf; + +import com.itextpdf.kernel.pdf.*; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Paragraph; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Test program for layer duplicate creation fix + */ +public class TestLayerFix { + + public static void main(String[] args) { + try { + // Set test file paths + String inputPdf = "test-input.pdf"; + String outputPdf1 = "test-output-1.pdf"; + String outputPdf2 = "test-output-2.pdf"; + + System.out.println("=== Layer Duplicate Fix Test Start ==="); + + // 1. Create test PDF + createTestPdf(inputPdf); + System.out.println("1. Test PDF created: " + inputPdf); + + // 2. First run - add layers and content + List drawingOptions1 = new ArrayList<>(); + drawingOptions1.add(LayerDrawingOptions.text(2, 100, 700, "First content (layer 2)")); + + PdfMemoLayerService service = new PdfMemoLayerService(); + service.addMemoLayers(inputPdf, outputPdf1, "Memo1", "Memo2", + true, true, drawingOptions1, false, 1); + System.out.println("2. First processing completed: " + outputPdf1); + + // 3. Second run - add more content to the same file + List drawingOptions2 = new ArrayList<>(); + drawingOptions2.add(LayerDrawingOptions.text(2, 150, 650, "Second content (layer 2)")); + + service.addMemoLayers(outputPdf1, outputPdf2, "Memo1", "Memo2", + true, true, drawingOptions2, false, 1); + System.out.println("3. Second processing completed: " + outputPdf2); + + // 4. Check layer count in final PDF + int layerCount = countLayers(outputPdf2); + System.out.println("\n=== Test Results ==="); + System.out.println("Final PDF layer count: " + layerCount); + + if (layerCount == 2) { + System.out.println("✅ Test passed: Layer count remains 2, no duplicates!"); + } else { + System.out.println("❌ Test failed: Layer count is " + layerCount + ", expected 2"); + } + + } catch (Exception e) { + System.err.println("Error during test: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Create a simple test PDF + */ + private static void createTestPdf(String filePath) throws IOException { + PdfDocument pdf = new PdfDocument(new PdfWriter(filePath)); + Document document = new Document(pdf); + + document.add(new Paragraph("Test PDF Document - Layer Fix Verification")); + document.add(new Paragraph("This is a test page for PDF layer functionality.")); + + document.close(); + } + + /** + * Count layers in PDF + */ + private static int countLayers(String filePath) throws IOException { + try (PdfDocument pdf = new PdfDocument(new PdfReader(filePath))) { + PdfDictionary catalog = pdf.getCatalog().getPdfObject(); + PdfDictionary ocProps = catalog.getAsDictionary(new PdfName("OCProperties")); + + if (ocProps != null) { + PdfArray ocgs = ocProps.getAsArray(new PdfName("OCGs")); + if (ocgs != null) { + return ocgs.size(); + } + } + + return 0; // No layers + } + } +} \ No newline at end of file diff --git a/src/main/java/co/jp/techsor/pdf/TestLayerFixDetailed.java b/src/main/java/co/jp/techsor/pdf/TestLayerFixDetailed.java new file mode 100644 index 0000000..e2cf379 --- /dev/null +++ b/src/main/java/co/jp/techsor/pdf/TestLayerFixDetailed.java @@ -0,0 +1,117 @@ +package co.jp.techsor.pdf; + +import com.itextpdf.kernel.pdf.*; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.Paragraph; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 详细测试程序:模拟用户实际使用场景 + */ +public class TestLayerFixDetailed { + + public static void main(String[] args) { + try { + // 模拟用户实际使用的图层名称 + String layer1Name = "メモ1"; + String layer2Name = "メモ2"; + + // 设置测试文件路径 + String testDir = "layer-test-results/"; + new File(testDir).mkdirs(); + + String inputPdf = testDir + "original.pdf"; + String outputPdf1 = testDir + "output-1.pdf"; + String outputPdf2 = testDir + "output-2.pdf"; + + System.out.println("=== 图层重复创建修复详细测试 ==="); + System.out.println("测试图层名称: " + layer1Name + " 和 " + layer2Name); + + // 1. 创建测试PDF + createTestPdf(inputPdf); + System.out.println("\n1. 原始PDF创建完成: " + inputPdf); + System.out.println(" 初始图层数量: " + countLayers(inputPdf)); + + // 2. 第一次运行程序 - 添加图层和内容 + System.out.println("\n2. 第一次运行程序添加图层和内容..."); + List drawingOptions1 = new ArrayList<>(); + drawingOptions1.add(LayerDrawingOptions.text(2, 100, 700, "第一次添加的内容(图层2)")); + + PdfMemoLayerService service = new PdfMemoLayerService(); + service.addMemoLayers(inputPdf, outputPdf1, layer1Name, layer2Name, + true, true, drawingOptions1, false, 1); + + int layerCount1 = countLayers(outputPdf1); + System.out.println(" 第一次处理后图层数量: " + layerCount1); + System.out.println(" 预期图层数量: 2"); + + // 3. 第二次运行程序 - 在同一文件上添加更多内容 + System.out.println("\n3. 第二次运行程序添加更多内容..."); + List drawingOptions2 = new ArrayList<>(); + drawingOptions2.add(LayerDrawingOptions.text(2, 150, 650, "第二次添加的内容(图层2)")); + drawingOptions2.add(LayerDrawingOptions.text(1, 200, 600, "第二次添加的内容(图层1)")); + + service.addMemoLayers(outputPdf1, outputPdf2, layer1Name, layer2Name, + true, true, drawingOptions2, false, 1); + + int layerCount2 = countLayers(outputPdf2); + System.out.println(" 第二次处理后图层数量: " + layerCount2); + System.out.println(" 预期图层数量: 2"); + + // 4. 检查最终结果 + System.out.println("\n=== 测试结果汇总 ==="); + System.out.println("原始PDF图层数: " + countLayers(inputPdf)); + System.out.println("第一次处理后图层数: " + layerCount1); + System.out.println("第二次处理后图层数: " + layerCount2); + + if (layerCount1 == 2 && layerCount2 == 2) { + System.out.println("✅ 测试通过:图层数量始终保持为2个,没有重复创建!"); + System.out.println(" 最终文件:" + outputPdf2); + } else { + System.out.println("❌ 测试失败:图层数量不符合预期!"); + System.out.println(" 第一次处理后预期2个,实际" + layerCount1 + "个"); + System.out.println(" 第二次处理后预期2个,实际" + layerCount2 + "个"); + } + + } catch (Exception e) { + System.err.println("\n测试过程中发生错误: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * 创建一个简单的测试PDF + */ + private static void createTestPdf(String filePath) throws IOException { + PdfDocument pdf = new PdfDocument(new PdfWriter(filePath)); + Document document = new Document(pdf); + + document.add(new Paragraph("原始PDF文档")); + document.add(new Paragraph("这是一个用于测试图层重复创建修复的文档。")); + document.add(new Paragraph("将通过程序两次添加图层和内容,检查是否会重复创建图层。")); + + document.close(); + } + + /** + * 统计PDF中的图层数量 + */ + private static int countLayers(String filePath) throws IOException { + try (PdfDocument pdf = new PdfDocument(new PdfReader(filePath))) { + PdfDictionary catalog = pdf.getCatalog().getPdfObject(); + PdfDictionary ocProps = catalog.getAsDictionary(new PdfName("OCProperties")); + + if (ocProps != null) { + PdfArray ocgs = ocProps.getAsArray(new PdfName("OCGs")); + if (ocgs != null) { + return ocgs.size(); + } + } + + return 0; // No layers + } + } +} \ No newline at end of file