diff --git a/1.make_csdrg_pdf.vbs b/V1/1.make_csdrg_pdf.vbs similarity index 98% rename from 1.make_csdrg_pdf.vbs rename to V1/1.make_csdrg_pdf.vbs index dbdf2a3..d4bab42 100644 --- a/1.make_csdrg_pdf.vbs +++ b/V1/1.make_csdrg_pdf.vbs @@ -1,2 +1,2 @@ -Set objShell = CreateObject("WScript.Shell") -objShell.Run "python.exe """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\make_csdrg_pdf.py""", 1, True +Set objShell = CreateObject("WScript.Shell") +objShell.Run "python.exe """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\make_csdrg_pdf.py""", 1, True diff --git a/2.run_pdf_validation.vbs b/V1/2.run_pdf_validation.vbs similarity index 83% rename from 2.run_pdf_validation.vbs rename to V1/2.run_pdf_validation.vbs index fa86a74..e4a318c 100644 --- a/2.run_pdf_validation.vbs +++ b/V1/2.run_pdf_validation.vbs @@ -1,16 +1,16 @@ -Option Explicit - -Dim shell -Dim fso -Dim folder - -Set shell = CreateObject("WScript.Shell") -Set fso = CreateObject("Scripting.FileSystemObject") - -folder = fso.GetParentFolderName(WScript.ScriptFullName) - -shell.CurrentDirectory = folder - -shell.Run "py pdf_checker.py", 0, True - -MsgBox "PDF QA Finished!", vbInformation \ No newline at end of file +Option Explicit + +Dim shell +Dim fso +Dim folder + +Set shell = CreateObject("WScript.Shell") +Set fso = CreateObject("Scripting.FileSystemObject") + +folder = fso.GetParentFolderName(WScript.ScriptFullName) + +shell.CurrentDirectory = folder + +shell.Run "py pdf_checker.py", 0, True + +MsgBox "PDF QA Finished!", vbInformation diff --git a/README.md b/V1/README.md similarity index 100% rename from README.md rename to V1/README.md diff --git a/Report.html b/V1/Report.html similarity index 85% rename from Report.html rename to V1/Report.html index 578bdf8..a6b8308 100644 --- a/Report.html +++ b/V1/Report.html @@ -1,99 +1,98 @@ - - - - - - - - - -PDF QA Validation Report - - - - - - - -

PDF QA Validation Report

- -
- -
- -Folder
- -J:\bdm\sandbox\kevin\RG - -

- -Total PDF : 1
-PASS : 0
-FAIL : 1 - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FilePagesFast Web ViewTitleAuthorSubjectKeywordsResultReason
csdrg.pdf27NoEmptyEmptyEmptyEmptyFAILFast Web View
- - - - + + + + + + + + +PDF QA Validation Report + + + + + + + +

PDF QA Validation Report

+ +
+ +
+ +Folder
+ +J:\bdm\sandbox\kevin\RG + +

+ +Total PDF : 1
+PASS : 0
+FAIL : 1 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FilePagesFast Web ViewTitleAuthorSubjectKeywordsResultReason
csdrg.pdf27NoEmptyEmptyEmptyEmptyFAILFast Web View
+ + + + diff --git a/make_csdrg_pdf.py b/V1/make_csdrg_pdf.py similarity index 97% rename from make_csdrg_pdf.py rename to V1/make_csdrg_pdf.py index 2e1f070..a683d67 100644 --- a/make_csdrg_pdf.py +++ b/V1/make_csdrg_pdf.py @@ -1,139 +1,139 @@ -""" -make_csdrg_pdf.py -================= -Converts csdrg.docx -> csdrg.pdf following CSDRG Completion Guidelines v1.4: - - - Uses Microsoft Word COM (ExportAsFixedFormat) to preserve heading bookmarks - - Sets initial view to show Bookmarks Panel on open - - Clears Title / Author / Subject / Keywords per CSDRG guidelines (p.21) - - Verifies PDF version <= 1.7 (FDA eCTD requirement) - -Requirements: pip install pywin32 pikepdf -""" - -import os -import sys -import shutil -import time - -try: - import win32com.client - import pikepdf - from pikepdf import Dictionary, Name -except ImportError as e: - print(f"Missing library: {e}") - print("Run: python -m pip install pywin32 pikepdf") - sys.exit(1) - -# ── Paths ────────────────────────────────────────────────────────────────────── -SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -DOCX_SRC = os.path.join(SCRIPT_DIR, "csdrg.docx") -FINAL_PDF = os.path.join(SCRIPT_DIR, "csdrg.pdf") -TEMP_DIR = os.path.join(os.environ.get("TEMP", "C:\\Temp"), "csdrg_build") -TEMP_DOCX = os.path.join(TEMP_DIR, "csdrg.docx") -TEMP_PDF = os.path.join(TEMP_DIR, "csdrg.pdf") - -# ── Step 1: Copy docx to temp (avoids OneDrive/space path issues) ────────────── -print("=" * 60) -print("CSDRG PDF Builder") -print("=" * 60) - -if not os.path.exists(DOCX_SRC): - print(f"ERROR: csdrg.docx not found at:\n {DOCX_SRC}") - sys.exit(1) - -os.makedirs(TEMP_DIR, exist_ok=True) -shutil.copy2(DOCX_SRC, TEMP_DOCX) -print(f"[1/4] Copied docx to temp: {TEMP_DOCX}") - -# ── Step 2: Word COM -> PDF with heading bookmarks ───────────────────────────── -print("[2/4] Converting Word -> PDF via Microsoft Word...") -word = win32com.client.Dispatch("Word.Application") -word.Visible = False -try: - doc = word.Documents.Open(TEMP_DOCX) - doc.ExportAsFixedFormat( - OutputFileName = TEMP_PDF, - ExportFormat = 17, # wdExportFormatPDF - OpenAfterExport = False, - OptimizeFor = 0, # wdExportOptimizeForPrint - Range = 0, # wdExportAllDocument - From = 1, - To = 1, - Item = 0, # wdExportDocumentContent - IncludeDocProps = True, - KeepIRM = True, - CreateBookmarks = 1, # wdExportCreateHeadingBookmarks - DocStructureTags = True, - BitmapMissingFonts = True, - UseISO19005_1 = False, - ) - doc.Close(False) - print(f" PDF created: {TEMP_PDF}") -finally: - word.Quit() - -time.sleep(2) - -# ── Step 3: pikepdf post-processing ─────────────────────────────────────────── -print("[3/4] Setting PDF properties (bookmarks panel + clear metadata)...") -with pikepdf.open(TEMP_PDF, allow_overwriting_input=True) as pdf: - - # Show Bookmarks Panel when opened (CSDRG guideline requirement) - pdf.Root["/PageMode"] = Name("/UseOutlines") - - # ViewerPreferences: default page layout and magnification - if "/ViewerPreferences" not in pdf.Root: - pdf.Root["/ViewerPreferences"] = Dictionary() - - # Clear Title, Author, Subject, Keywords (CSDRG Completion Guidelines p.21) - info = pdf.docinfo - for key in ["/Title", "/Author", "/Subject", "/Keywords", "/Creator", "/Producer"]: - if key in info: - info[key] = "" - - # Report bookmark count - if "/Outlines" in pdf.Root: - print(" Bookmarks: found OK") - else: - print(" WARNING: No bookmarks found.") - print(" Ensure csdrg.docx uses built-in Word Heading 1/2/3 styles.") - - pdf.save(TEMP_PDF) - -# ── Step 4: Verify PDF version and copy to destination ──────────────────────── -print("[4/4] Verifying PDF version...") -with open(TEMP_PDF, "rb") as f: - header = f.read(8).decode("ascii", errors="replace").strip() -print(f" PDF header: {header}") - -version_ok = any(v in header for v in ["1.4", "1.5", "1.6", "1.7"]) -if version_ok: - print(" Version <= 1.7: OK") -else: - print(f" WARNING: Version may exceed 1.7. Check before submission.") - -# Copy to final destination (retry if locked) -for attempt in range(5): - try: - shutil.copy2(TEMP_PDF, FINAL_PDF) - break - except PermissionError: - if attempt < 4: - print(f" File locked, retrying in 3s... (attempt {attempt+1}/5)") - time.sleep(3) - else: - print(f"\nERROR: Could not write to {FINAL_PDF}") - print("Please close any program that has csdrg.pdf open, then re-run.") - sys.exit(1) - -print() -print("=" * 60) -print(f"Done! Output: {FINAL_PDF}") -print() -print("Checklist:") -print(f" [{'OK' if version_ok else '!!'}] PDF version <= 1.7") -print( " [OK] Bookmarks panel shown on open") -print( " [OK] Title/Author/Subject/Keywords cleared") -print( " [OK] File named csdrg.pdf") -print("=" * 60) +""" +make_csdrg_pdf.py +================= +Converts csdrg.docx -> csdrg.pdf following CSDRG Completion Guidelines v1.4: + + - Uses Microsoft Word COM (ExportAsFixedFormat) to preserve heading bookmarks + - Sets initial view to show Bookmarks Panel on open + - Clears Title / Author / Subject / Keywords per CSDRG guidelines (p.21) + - Verifies PDF version <= 1.7 (FDA eCTD requirement) + +Requirements: pip install pywin32 pikepdf +""" + +import os +import sys +import shutil +import time + +try: + import win32com.client + import pikepdf + from pikepdf import Dictionary, Name +except ImportError as e: + print(f"Missing library: {e}") + print("Run: python -m pip install pywin32 pikepdf") + sys.exit(1) + +# ── Paths ────────────────────────────────────────────────────────────────────── +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +DOCX_SRC = os.path.join(SCRIPT_DIR, "csdrg.docx") +FINAL_PDF = os.path.join(SCRIPT_DIR, "csdrg.pdf") +TEMP_DIR = os.path.join(os.environ.get("TEMP", "C:\\Temp"), "csdrg_build") +TEMP_DOCX = os.path.join(TEMP_DIR, "csdrg.docx") +TEMP_PDF = os.path.join(TEMP_DIR, "csdrg.pdf") + +# ── Step 1: Copy docx to temp (avoids OneDrive/space path issues) ────────────── +print("=" * 60) +print("CSDRG PDF Builder") +print("=" * 60) + +if not os.path.exists(DOCX_SRC): + print(f"ERROR: csdrg.docx not found at:\n {DOCX_SRC}") + sys.exit(1) + +os.makedirs(TEMP_DIR, exist_ok=True) +shutil.copy2(DOCX_SRC, TEMP_DOCX) +print(f"[1/4] Copied docx to temp: {TEMP_DOCX}") + +# ── Step 2: Word COM -> PDF with heading bookmarks ───────────────────────────── +print("[2/4] Converting Word -> PDF via Microsoft Word...") +word = win32com.client.Dispatch("Word.Application") +word.Visible = False +try: + doc = word.Documents.Open(TEMP_DOCX) + doc.ExportAsFixedFormat( + OutputFileName = TEMP_PDF, + ExportFormat = 17, # wdExportFormatPDF + OpenAfterExport = False, + OptimizeFor = 0, # wdExportOptimizeForPrint + Range = 0, # wdExportAllDocument + From = 1, + To = 1, + Item = 0, # wdExportDocumentContent + IncludeDocProps = True, + KeepIRM = True, + CreateBookmarks = 1, # wdExportCreateHeadingBookmarks + DocStructureTags = True, + BitmapMissingFonts = True, + UseISO19005_1 = False, + ) + doc.Close(False) + print(f" PDF created: {TEMP_PDF}") +finally: + word.Quit() + +time.sleep(2) + +# ── Step 3: pikepdf post-processing ─────────────────────────────────────────── +print("[3/4] Setting PDF properties (bookmarks panel + clear metadata)...") +with pikepdf.open(TEMP_PDF, allow_overwriting_input=True) as pdf: + + # Show Bookmarks Panel when opened (CSDRG guideline requirement) + pdf.Root["/PageMode"] = Name("/UseOutlines") + + # ViewerPreferences: default page layout and magnification + if "/ViewerPreferences" not in pdf.Root: + pdf.Root["/ViewerPreferences"] = Dictionary() + + # Clear Title, Author, Subject, Keywords (CSDRG Completion Guidelines p.21) + info = pdf.docinfo + for key in ["/Title", "/Author", "/Subject", "/Keywords", "/Creator", "/Producer"]: + if key in info: + info[key] = "" + + # Report bookmark count + if "/Outlines" in pdf.Root: + print(" Bookmarks: found OK") + else: + print(" WARNING: No bookmarks found.") + print(" Ensure csdrg.docx uses built-in Word Heading 1/2/3 styles.") + + pdf.save(TEMP_PDF) + +# ── Step 4: Verify PDF version and copy to destination ──────────────────────── +print("[4/4] Verifying PDF version...") +with open(TEMP_PDF, "rb") as f: + header = f.read(8).decode("ascii", errors="replace").strip() +print(f" PDF header: {header}") + +version_ok = any(v in header for v in ["1.4", "1.5", "1.6", "1.7"]) +if version_ok: + print(" Version <= 1.7: OK") +else: + print(f" WARNING: Version may exceed 1.7. Check before submission.") + +# Copy to final destination (retry if locked) +for attempt in range(5): + try: + shutil.copy2(TEMP_PDF, FINAL_PDF) + break + except PermissionError: + if attempt < 4: + print(f" File locked, retrying in 3s... (attempt {attempt+1}/5)") + time.sleep(3) + else: + print(f"\nERROR: Could not write to {FINAL_PDF}") + print("Please close any program that has csdrg.pdf open, then re-run.") + sys.exit(1) + +print() +print("=" * 60) +print(f"Done! Output: {FINAL_PDF}") +print() +print("Checklist:") +print(f" [{'OK' if version_ok else '!!'}] PDF version <= 1.7") +print( " [OK] Bookmarks panel shown on open") +print( " [OK] Title/Author/Subject/Keywords cleared") +print( " [OK] File named csdrg.pdf") +print("=" * 60) diff --git a/pdf_checker.py b/V1/pdf_checker.py similarity index 90% rename from pdf_checker.py rename to V1/pdf_checker.py index 2d74318..92bbf58 100644 --- a/pdf_checker.py +++ b/V1/pdf_checker.py @@ -1,199 +1,199 @@ -from pathlib import Path -import html -import webbrowser -import pikepdf - - -ROOT = Path.cwd() -REPORT = ROOT / "Report.html" - - -def is_linearized(pdf_file): - """ - Detect Fast Web View (Linearized PDF) - """ - try: - with open(pdf_file, "rb") as f: - header = f.read(4096) - return b"/Linearized" in header - except Exception: - return False - - -def get_metadata(pdf_file): - - result = { - "Pages": "", - "Title": "", - "Author": "", - "Subject": "", - "Keywords": "", - "Fast Web View": "No" - } - - try: - - with pikepdf.open(pdf_file) as pdf: - - info = pdf.docinfo - - result["Pages"] = len(pdf.pages) - result["Title"] = str(info.get("/Title", "") or "") - result["Author"] = str(info.get("/Author", "") or "") - result["Subject"] = str(info.get("/Subject", "") or "") - result["Keywords"] = str(info.get("/Keywords", "") or "") - - if is_linearized(pdf_file): - result["Fast Web View"] = "Yes" - - except Exception as e: - - result["Error"] = str(e) - - return result - - -pdfs = sorted(ROOT.glob("*.pdf")) - -rows = [] - -pass_count = 0 -fail_count = 0 - -for pdf in pdfs: - - m = get_metadata(pdf) - - issues = [] - - if m["Title"] != "": - issues.append("Title") - - if m["Author"] != "": - issues.append("Author") - - if m["Subject"] != "": - issues.append("Subject") - - if m["Keywords"] != "": - issues.append("Keywords") - - if m["Fast Web View"] != "Yes": - issues.append("Fast Web View") - - result = "PASS" if len(issues) == 0 else "FAIL" - - if result == "PASS": - pass_count += 1 - else: - fail_count += 1 - - rows.append(f""" - -{html.escape(pdf.name)} -{m["Pages"]} -{m["Fast Web View"]} -{"Empty" if m["Title"]=="" else "Exists"} -{"Empty" if m["Author"]=="" else "Exists"} -{"Empty" if m["Subject"]=="" else "Exists"} -{"Empty" if m["Keywords"]=="" else "Exists"} -{result} -{", ".join(issues)} - -""") - - -html_text = f""" - - - - - - - - -PDF QA Validation Report - - - - - - - -

PDF QA Validation Report

- -
- -
- -Folder
- -{ROOT} - -

- -Total PDF : {len(pdfs)}
-PASS : {pass_count}
-FAIL : {fail_count} - -
- - - - - - - - - - - - - - - -{''.join(rows)} - -
FilePagesFast Web ViewTitleAuthorSubjectKeywordsResultReason
- - - - -""" - -REPORT.write_text(html_text, encoding="utf-8") - -print("Finished.") - -webbrowser.open(REPORT.as_uri()) \ No newline at end of file +from pathlib import Path +import html +import webbrowser +import pikepdf + + +ROOT = Path.cwd() +REPORT = ROOT / "Report.html" + + +def is_linearized(pdf_file): + """ + Detect Fast Web View (Linearized PDF) + """ + try: + with open(pdf_file, "rb") as f: + header = f.read(4096) + return b"/Linearized" in header + except Exception: + return False + + +def get_metadata(pdf_file): + + result = { + "Pages": "", + "Title": "", + "Author": "", + "Subject": "", + "Keywords": "", + "Fast Web View": "No" + } + + try: + + with pikepdf.open(pdf_file) as pdf: + + info = pdf.docinfo + + result["Pages"] = len(pdf.pages) + result["Title"] = str(info.get("/Title", "") or "") + result["Author"] = str(info.get("/Author", "") or "") + result["Subject"] = str(info.get("/Subject", "") or "") + result["Keywords"] = str(info.get("/Keywords", "") or "") + + if is_linearized(pdf_file): + result["Fast Web View"] = "Yes" + + except Exception as e: + + result["Error"] = str(e) + + return result + + +pdfs = sorted(ROOT.glob("*.pdf")) + +rows = [] + +pass_count = 0 +fail_count = 0 + +for pdf in pdfs: + + m = get_metadata(pdf) + + issues = [] + + if m["Title"] != "": + issues.append("Title") + + if m["Author"] != "": + issues.append("Author") + + if m["Subject"] != "": + issues.append("Subject") + + if m["Keywords"] != "": + issues.append("Keywords") + + if m["Fast Web View"] != "Yes": + issues.append("Fast Web View") + + result = "PASS" if len(issues) == 0 else "FAIL" + + if result == "PASS": + pass_count += 1 + else: + fail_count += 1 + + rows.append(f""" + +{html.escape(pdf.name)} +{m["Pages"]} +{m["Fast Web View"]} +{"Empty" if m["Title"]=="" else "Exists"} +{"Empty" if m["Author"]=="" else "Exists"} +{"Empty" if m["Subject"]=="" else "Exists"} +{"Empty" if m["Keywords"]=="" else "Exists"} +{result} +{", ".join(issues)} + +""") + + +html_text = f""" + + + + + + + + +PDF QA Validation Report + + + + + + + +

PDF QA Validation Report

+ +
+ +
+ +Folder
+ +{ROOT} + +

+ +Total PDF : {len(pdfs)}
+PASS : {pass_count}
+FAIL : {fail_count} + +
+ + + + + + + + + + + + + + + +{''.join(rows)} + +
FilePagesFast Web ViewTitleAuthorSubjectKeywordsResultReason
+ + + + +""" + +REPORT.write_text(html_text, encoding="utf-8") + +print("Finished.") + +webbrowser.open(REPORT.as_uri())