_bid_preview.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. # -*- coding: utf-8 -*-
  2. """技术方案排版自检:导出 PDF、渲染指定页预览、报告版面留白过多的页。
  3. 用法:python doc/_bid_preview.py <文件名关键字> [页码 ...] [--export]
  4. 例: python doc/_bid_preview.py 第2章 1 5 9
  5. """
  6. import io
  7. import os
  8. import sys
  9. import time
  10. import fitz
  11. HERE = os.path.dirname(os.path.abspath(__file__))
  12. PREV = os.path.join(HERE, "_bid_preview_pages")
  13. FILL_WARN = 0.62
  14. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
  15. def find_docx(keyword):
  16. hits = [n for n in os.listdir(HERE)
  17. if n.endswith(".docx") and not n.startswith("~$") and keyword in n]
  18. if not hits:
  19. raise SystemExit(f"未找到含「{keyword}」的 docx")
  20. if len(hits) > 1:
  21. print("匹配到多个,取第一个:", hits)
  22. return os.path.join(HERE, hits[0])
  23. def export_pdf(src, pdf):
  24. import win32com.client as win32
  25. for attempt in range(4):
  26. try:
  27. word = win32.gencache.EnsureDispatch("Word.Application")
  28. word.Visible = False
  29. d = word.Documents.Open(src, ReadOnly=True)
  30. d.ExportAsFixedFormat(pdf, 17)
  31. d.Close(False)
  32. word.Quit()
  33. return
  34. except Exception as e: # Word COM 偶发拒绝调用
  35. print(f" 导出重试 {attempt + 1}/4:{e}")
  36. time.sleep(4)
  37. raise SystemExit("Word 导出 PDF 失败")
  38. def report_gaps(doc):
  39. print(f"版面留白检查(内容占比 < {FILL_WARN:.0%}):")
  40. hit = False
  41. for i, pg in enumerate(doc, 1):
  42. r = pg.rect
  43. bottoms = [b[3] for b in pg.get_text("blocks") if b[3] < r.height - 40]
  44. bottoms += [bb.y1 for bb in
  45. (pg.get_image_bbox(x) for x in pg.get_images(full=True)) if bb]
  46. if not bottoms:
  47. print(f" P{i:<3} 空白页")
  48. hit = True
  49. continue
  50. used = (max(bottoms) - 60) / (r.height - 120)
  51. if used < FILL_WARN:
  52. print(f" P{i:<3} 内容占比 {used * 100:5.1f}%")
  53. hit = True
  54. if not hit:
  55. print(" 无")
  56. def main():
  57. args = sys.argv[1:]
  58. if not args:
  59. raise SystemExit(__doc__)
  60. keyword = args[0]
  61. pages = [int(a) for a in args[1:] if a.isdigit()]
  62. src = find_docx(keyword)
  63. # PDF 按源文档命名,避免不同章节复用同一个缓存文件
  64. stem = os.path.splitext(os.path.basename(src))[0]
  65. pdf = os.path.join(HERE, "_bid_preview", stem + ".pdf")
  66. os.makedirs(os.path.dirname(pdf), exist_ok=True)
  67. os.makedirs(PREV, exist_ok=True)
  68. print("文档:", os.path.basename(src))
  69. stale = (not os.path.exists(pdf)
  70. or os.path.getmtime(pdf) < os.path.getmtime(src))
  71. if "--export" in args or stale:
  72. export_pdf(src, pdf)
  73. doc = fitz.open(pdf)
  74. print("PDF 页数:", doc.page_count)
  75. for p in pages:
  76. if 1 <= p <= doc.page_count:
  77. doc[p - 1].get_pixmap(dpi=110).save(
  78. os.path.join(PREV, f"p{p:02d}.png"))
  79. if pages:
  80. print("已渲染预览页:", pages)
  81. report_gaps(doc)
  82. if __name__ == "__main__":
  83. main()