PyMuPDF 1.26.1__tar.gz → 1.26.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. {pymupdf-1.26.1 → pymupdf-1.26.4}/PKG-INFO +1 -1
  2. {pymupdf-1.26.1 → pymupdf-1.26.4}/changes.txt +52 -1
  3. {pymupdf-1.26.1 → pymupdf-1.26.4}/pipcl.py +393 -29
  4. {pymupdf-1.26.1 → pymupdf-1.26.4}/scripts/sysinstall.py +20 -4
  5. {pymupdf-1.26.1 → pymupdf-1.26.4}/scripts/test.py +209 -54
  6. {pymupdf-1.26.1 → pymupdf-1.26.4}/setup.py +67 -29
  7. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/__init__.py +76 -47
  8. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/table.py +126 -3
  9. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/utils.py +6 -2
  10. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/README.md +8 -0
  11. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/conftest.py +63 -6
  12. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/gentle_compare.py +9 -10
  13. pymupdf-1.26.4/tests/resources/test-styled-table.pdf +0 -0
  14. pymupdf-1.26.4/tests/resources/test_3806-expected.png +0 -0
  15. pymupdf-1.26.4/tests/resources/test_3806.pdf +0 -0
  16. pymupdf-1.26.4/tests/resources/test_4388_BOZ1.pdf +0 -0
  17. pymupdf-1.26.4/tests/resources/test_4388_BUL1.pdf +0 -0
  18. pymupdf-1.26.4/tests/resources/test_4496.hwpx +0 -0
  19. pymupdf-1.26.4/tests/resources/test_4503.pdf +0 -0
  20. pymupdf-1.26.4/tests/resources/test_4564.pdf +51 -0
  21. pymupdf-1.26.4/tests/resources/test_4571.pdf +0 -0
  22. pymupdf-1.26.4/tests/resources/test_4614.pdf +5843 -7
  23. pymupdf-1.26.4/tests/resources/test_4639.pdf +0 -0
  24. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/run_compound.py +2 -1
  25. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_2548.py +5 -1
  26. pymupdf-1.26.4/tests/test_4503.py +38 -0
  27. pymupdf-1.26.4/tests/test_4614.py +10 -0
  28. pymupdf-1.26.4/tests/test_clip_page.py +37 -0
  29. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_font.py +32 -26
  30. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_general.py +188 -31
  31. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_insertpdf.py +19 -0
  32. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_memory.py +6 -0
  33. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_pagedelete.py +22 -0
  34. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_pixmap.py +49 -0
  35. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_pylint.py +1 -0
  36. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_story.py +4 -1
  37. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_tables.py +45 -10
  38. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_textextract.py +87 -46
  39. {pymupdf-1.26.1 → pymupdf-1.26.4}/COPYING +0 -0
  40. {pymupdf-1.26.1 → pymupdf-1.26.4}/README.md +0 -0
  41. {pymupdf-1.26.1 → pymupdf-1.26.4}/READMEb.md +0 -0
  42. {pymupdf-1.26.1 → pymupdf-1.26.4}/READMEd.md +0 -0
  43. {pymupdf-1.26.1 → pymupdf-1.26.4}/pyproject.toml +0 -0
  44. {pymupdf-1.26.1 → pymupdf-1.26.4}/pytest.ini +0 -0
  45. {pymupdf-1.26.1 → pymupdf-1.26.4}/scripts/gh_release.py +0 -0
  46. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/__main__.py +0 -0
  47. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/_apply_pages.py +0 -0
  48. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/_wxcolors.py +0 -0
  49. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/extra.i +0 -0
  50. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/fitz___init__.py +0 -0
  51. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/fitz_table.py +0 -0
  52. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/fitz_utils.py +0 -0
  53. {pymupdf-1.26.1 → pymupdf-1.26.4}/src/pymupdf.py +0 -0
  54. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/__init__.py +0 -0
  55. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/__main__.py +0 -0
  56. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/_config.h +0 -0
  57. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/fitz_old.i +0 -0
  58. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-annot.i +0 -0
  59. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-convert.i +0 -0
  60. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-defines.i +0 -0
  61. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-devices.i +0 -0
  62. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-fields.i +0 -0
  63. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-fileobj.i +0 -0
  64. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-geo-c.i +0 -0
  65. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-geo-py.i +0 -0
  66. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-globals.i +0 -0
  67. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-other.i +0 -0
  68. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-pdfinfo.i +0 -0
  69. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-pixmap.i +0 -0
  70. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-portfolio.i +0 -0
  71. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-python.i +0 -0
  72. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-select.i +0 -0
  73. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-stext.i +0 -0
  74. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/helper-xobject.i +0 -0
  75. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/utils.py +0 -0
  76. {pymupdf-1.26.1 → pymupdf-1.26.4}/src_classic/version.i +0 -0
  77. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/001003ED.pdf +0 -0
  78. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/1.pdf +0 -0
  79. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/2.pdf +0 -0
  80. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/2201.00069.pdf +0 -0
  81. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/3.pdf +0 -0
  82. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/4.pdf +0 -0
  83. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/Bezier.epub +0 -0
  84. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/PragmaticaC.otf +0 -0
  85. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/battery-file-22.pdf +0 -0
  86. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/bug1945.pdf +0 -0
  87. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/bug1971.pdf +0 -0
  88. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/chinese-tables.pdf +0 -0
  89. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/chinese-tables.pickle +0 -0
  90. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/circular-toc.pdf +0 -0
  91. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/cms-etc-filled.pdf +0 -0
  92. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/cython.pdf +0 -0
  93. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/cython.pickle +0 -0
  94. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/dotted-gridlines.pdf +0 -0
  95. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/full_toc.txt +0 -0
  96. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/github_sample.pdf +0 -0
  97. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/has-bad-fonts.pdf +0 -0
  98. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/image-file1.pdf +0 -0
  99. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/img-regular.pdf +0 -0
  100. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/img-transparent.pdf +0 -0
  101. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/img-transparent.png +0 -0
  102. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/interfield-calculation.pdf +0 -0
  103. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/joined.pdf +0 -0
  104. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/merge-form1.pdf +0 -0
  105. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/merge-form2.pdf +0 -0
  106. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/metadata.txt +0 -0
  107. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/mupdf_explored.pdf +0 -0
  108. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/nur-ruhig.jpg +0 -0
  109. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/quad-calc-0.pdf +0 -0
  110. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/simple_toc.txt +0 -0
  111. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/small-table.pdf +0 -0
  112. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/strict-yes-no.pdf +0 -0
  113. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/symbol-list.pdf +0 -0
  114. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/symbols.txt +0 -0
  115. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-2333.pdf +0 -0
  116. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-2462.pdf +0 -0
  117. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-2812.pdf +0 -0
  118. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-3143.pdf +0 -0
  119. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-3150.pdf +0 -0
  120. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-3207.pdf +0 -0
  121. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-3591.pdf +0 -0
  122. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-3820.pdf +0 -0
  123. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-4055.pdf +0 -0
  124. /pymupdf-1.26.1/tests/resources/test_4503.pdf → /pymupdf-1.26.4/tests/resources/test-4503.pdf +0 -0
  125. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-707448.pdf +0 -0
  126. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-707673.pdf +0 -0
  127. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-E+A.pdf +0 -0
  128. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-linebreaks.pdf +0 -0
  129. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test-rewrite-images.pdf +0 -0
  130. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test2093.pdf +0 -0
  131. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test2182.pdf +0 -0
  132. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test2238.pdf +0 -0
  133. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_1645_expected.pdf +0 -0
  134. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_1824.pdf +0 -0
  135. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2108.pdf +0 -0
  136. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2270.pdf +0 -0
  137. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2533.pdf +0 -0
  138. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2548.pdf +0 -0
  139. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2553-2.pdf +0 -0
  140. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2553.pdf +0 -0
  141. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2596.pdf +0 -0
  142. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2608_expected +0 -0
  143. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2608_expected_1.26 +0 -0
  144. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2634.pdf +0 -0
  145. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2635.pdf +0 -0
  146. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2645_1.pdf +0 -0
  147. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2645_2.pdf +0 -0
  148. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2645_3.pdf +0 -0
  149. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2710.pdf +0 -0
  150. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2730.pdf +0 -0
  151. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2742.pdf +0 -0
  152. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2788.pdf +0 -0
  153. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2791_content.pdf +0 -0
  154. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2791_coverpage.pdf +0 -0
  155. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2861.pdf +0 -0
  156. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2871.pdf +0 -0
  157. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2885.pdf +0 -0
  158. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2904.pdf +0 -0
  159. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2907.pdf +0 -0
  160. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2954.pdf +0 -0
  161. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2957_1.pdf +0 -0
  162. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2957_2.pdf +0 -0
  163. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2969.pdf +0 -0
  164. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_2979.pdf +0 -0
  165. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3050_expected.png +0 -0
  166. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3058.pdf +0 -0
  167. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3062.pdf +0 -0
  168. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3070.pdf +0 -0
  169. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3072.pdf +0 -0
  170. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3087.pdf +0 -0
  171. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3179.pdf +0 -0
  172. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3186.pdf +0 -0
  173. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3197.pdf +0 -0
  174. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3357.pdf +0 -0
  175. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3362.pdf +0 -0
  176. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3376.pdf +0 -0
  177. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3448.pdf +0 -0
  178. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3448.pdf-expected.png +0 -0
  179. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3450.pdf +0 -0
  180. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3493.epub +0 -0
  181. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3569.pdf +0 -0
  182. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3594.pdf +0 -0
  183. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3615.epub +0 -0
  184. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3624.pdf +0 -0
  185. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3624_expected.png +0 -0
  186. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3650.pdf +0 -0
  187. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3654.docx +0 -0
  188. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3677.pdf +0 -0
  189. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3687-3.epub +0 -0
  190. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3687.epub +0 -0
  191. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3705.pdf +0 -0
  192. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3725.pdf +0 -0
  193. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3727.pdf +0 -0
  194. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3780.pdf +0 -0
  195. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3789.pdf +0 -0
  196. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3842.pdf +0 -0
  197. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3848.pdf +0 -0
  198. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3854.pdf +0 -0
  199. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3854_expected.png +0 -0
  200. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf +0 -0
  201. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.0.png +0 -0
  202. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.1.png +0 -0
  203. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.2.png +0 -0
  204. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.3.png +0 -0
  205. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.4.png +0 -0
  206. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.5.png +0 -0
  207. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.6.png +0 -0
  208. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3863.pdf.pdf.7.png +0 -0
  209. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3886.pdf +0 -0
  210. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3887.pdf +0 -0
  211. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3933.pdf +0 -0
  212. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3950.pdf +0 -0
  213. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_3994.pdf +0 -0
  214. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4004.pdf +0 -0
  215. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4017.pdf +0 -0
  216. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4026.pdf +0 -0
  217. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4034.pdf +0 -0
  218. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4043.pdf +0 -0
  219. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4047.pdf +0 -0
  220. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4079.pdf +0 -0
  221. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4079_after.pdf +0 -0
  222. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4079_after_1.25.pdf +0 -0
  223. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4090.pdf +0 -0
  224. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4125.pdf +0 -0
  225. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4139.pdf +0 -0
  226. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4141.pdf +0 -0
  227. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4147.pdf +0 -0
  228. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4179.pdf +0 -0
  229. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4179_expected.png +0 -0
  230. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4180.pdf +0 -0
  231. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4180_expected.png +0 -0
  232. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4182.pdf +0 -0
  233. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4182_expected.png +0 -0
  234. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4224.pdf +0 -0
  235. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4245.pdf +0 -0
  236. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4245_expected.png +0 -0
  237. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4263.pdf +0 -0
  238. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4363.pdf +0 -0
  239. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4412.pdf +0 -0
  240. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4415.pdf +0 -0
  241. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4415_out_expected.png +0 -0
  242. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4423.pdf +0 -0
  243. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4435.pdf +0 -0
  244. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4479.pdf +0 -0
  245. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4505.pdf +0 -0
  246. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_4546.pdf +0 -0
  247. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_annot_file_info.pdf +0 -0
  248. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_delete_image.pdf +0 -0
  249. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.cbz +0 -0
  250. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.doc +0 -0
  251. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.docx +0 -0
  252. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.epub +0 -0
  253. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.fb2 +0 -0
  254. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.html +0 -0
  255. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.jpg +0 -0
  256. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.mobi +0 -0
  257. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.pdf +0 -0
  258. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.svg +0 -0
  259. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.xhtml +0 -0
  260. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.xml +0 -0
  261. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2.xps +0 -0
  262. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_open2_expected.json +0 -0
  263. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/test_toc_count.pdf +0 -0
  264. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/text-find-ligatures.pdf +0 -0
  265. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/type3font.pdf +0 -0
  266. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/v110-changes.pdf +0 -0
  267. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/resources/widgettest.pdf +0 -0
  268. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_2634.py +0 -0
  269. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_2904.py +0 -0
  270. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_2907.py +0 -0
  271. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_4141.py +0 -0
  272. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_4466.pdf +0 -0
  273. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_4505.py +0 -0
  274. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_4520.py +0 -0
  275. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_annots.py +0 -0
  276. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_badfonts.py +0 -0
  277. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_balance_count.py +0 -0
  278. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_barcode.py +0 -0
  279. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_cluster_drawings.py +0 -0
  280. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_codespell.py +0 -0
  281. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_crypting.py +0 -0
  282. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_docs_samples.py +0 -0
  283. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_drawings.py +0 -0
  284. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_embeddedfiles.py +0 -0
  285. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_extractimage.py +0 -0
  286. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_flake8.py +0 -0
  287. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_geometry.py +0 -0
  288. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_imagebbox.py +0 -0
  289. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_imagemasks.py +0 -0
  290. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_import.py +0 -0
  291. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_insertimage.py +0 -0
  292. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_linebreaks.py +0 -0
  293. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_linequad.py +0 -0
  294. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_metadata.py +0 -0
  295. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_mupdf_regressions.py +0 -0
  296. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_named_links.py +0 -0
  297. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_nonpdf.py +0 -0
  298. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_object_manipulation.py +0 -0
  299. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_objectstreams.py +0 -0
  300. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_optional_content.py +0 -0
  301. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_page_links.py +0 -0
  302. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_pagelabels.py +0 -0
  303. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_remove-rotation.py +0 -0
  304. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_rewrite_images.py +0 -0
  305. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_rtl.py +0 -0
  306. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_showpdfpage.py +0 -0
  307. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_spikes.py +0 -0
  308. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_tesseract.py +0 -0
  309. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_textbox.py +0 -0
  310. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_textsearch.py +0 -0
  311. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_toc.py +0 -0
  312. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_widgets.py +0 -0
  313. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/test_word_delimiters.py +0 -0
  314. {pymupdf-1.26.1 → pymupdf-1.26.4}/tests/util.py +0 -0
  315. {pymupdf-1.26.1 → pymupdf-1.26.4}/valgrind.supp +0 -0
  316. {pymupdf-1.26.1 → pymupdf-1.26.4}/wdev.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyMuPDF
3
- Version: 1.26.1
3
+ Version: 1.26.4
4
4
  Summary: A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents.
5
5
  Description-Content-Type: text/markdown
6
6
  Author: Artifex
@@ -2,7 +2,57 @@ Change Log
2
2
  ==========
3
3
 
4
4
 
5
- **Changes in version 1.26.1**
5
+ **Changes in version 1.26.4**
6
+
7
+ * Use MuPDF-1.26.7.
8
+
9
+ * Fixed issues:
10
+
11
+ * **Fixed** `3806 <https://github.com/pymupdf/PyMuPDF/issues/3806>`_: pdf to image rendering ignore optional content offs
12
+ * **Fixed** `4388 <https://github.com/pymupdf/PyMuPDF/issues/4388>`_: Incorrect PixMap from page due to cached data from other PDF
13
+ * **Fixed** `4457 <https://github.com/pymupdf/PyMuPDF/issues/4457>`_: Wrong characters displayed after font subsetting (w/ native method)
14
+ * **Fixed** `4462 <https://github.com/pymupdf/PyMuPDF/issues/4462>`_: delete_pages() does not accept a single int
15
+ * **Fixed** `4533 <https://github.com/pymupdf/PyMuPDF/issues/4533>`_: Open PDF error segmentation fault
16
+ * **Fixed** `4565 <https://github.com/pymupdf/PyMuPDF/issues/4565>`_: MacOS uses Tesseract and not Tesseract-OCR
17
+ * **Fixed** `4571 <https://github.com/pymupdf/PyMuPDF/issues/4571>`_: Broken merged pdfs.
18
+ * **Fixed** `4590 <https://github.com/pymupdf/PyMuPDF/issues/4590>`_: TypeError in utils.py scrub(): annot.update_file(buffer=...) is invalid
19
+ * **Fixed** `4614 <https://github.com/pymupdf/PyMuPDF/issues/4614>`_: Intercept bad widgets when inserting to another PDF
20
+ * **Fixed** `4639 <https://github.com/pymupdf/PyMuPDF/issues/4639>`_: pymupdf.mupdf.FzErrorGeneric: code=1: Director error: <class 'AttributeError'>: 'JM_new_bbox_device_Device' object has no attribute 'layer_name'
21
+
22
+ * Other:
23
+
24
+ * Check that #4392 `Segfault when running with pytest and -Werror` is fixed if PyMuPDF is built with swig>=4.4.
25
+ * Add `Page.clip_to_rect()`.
26
+ * Improved search for Tesseract data.
27
+ * Retrospectively mark #4496 as fixed in 1.26.1.
28
+ * Retrospectively mark #4503 as fixed in 1.26.3.
29
+ * Added experimental support for Graal.
30
+
31
+
32
+ **Changes in version 1.26.3 (2025-07-02)**
33
+
34
+ * Use MuPDF-1.26.3.
35
+
36
+ * Fixed issues:
37
+
38
+ * **Fixed** `4462 <https://github.com/pymupdf/PyMuPDF/issues/4462>`_: delete_pages() does not accept a single int
39
+ * **Fixed** `4503 <https://github.com/pymupdf/PyMuPDF/issues/4503>`_: Undetected character styles
40
+ * **Fixed** `4527 <https://github.com/pymupdf/PyMuPDF/issues/4527>`_: Rect.intersects() is much slower than necessary
41
+ * **Fixed** `4564 <https://github.com/pymupdf/PyMuPDF/issues/4564>`_: Possible encoding issue in PDF metadata
42
+ * **Fixed** `4575 <https://github.com/pymupdf/PyMuPDF/issues/4575>`_: Bug with IRect contains method
43
+
44
+ * Other:
45
+
46
+ * Class Shape is now available as pymupdf.Shape.
47
+ * Added table cell markdown support.
48
+
49
+
50
+ **Changes in version 1.26.2**
51
+
52
+ [Skipped.]
53
+
54
+
55
+ **Changes in version 1.26.1 (2025-06-11)**
6
56
 
7
57
  * Use MuPDF-1.26.2.
8
58
 
@@ -11,6 +61,7 @@ Change Log
11
61
  * **Fixed** `4520 <https://github.com/pymupdf/PyMuPDF/issues/4520>`_: show_pdf_page does not like empty pages created by new_page
12
62
  * **Fixed** `4524 <https://github.com/pymupdf/PyMuPDF/issues/4524>`_: fitz.get_text ignores 'pages' kwarg
13
63
  * **Fixed** `4412 <https://github.com/pymupdf/PyMuPDF/issues/4412>`_: Regression? Spurious error? in insert_pdf in v1.25.4
64
+ * **Fixed** `4496 <https://github.com/pymupdf/PyMuPDF/issues/4496>`_: pymupdf4llm with pymupdfpro
14
65
 
15
66
  * Other:
16
67
 
@@ -11,9 +11,14 @@ a SWIG extension, but we also give access to the located compiler/linker so
11
11
  that a `setup.py` script can take over the details itself.
12
12
 
13
13
  Run doctests with: `python -m doctest pipcl.py`
14
+
15
+ For Graal we require that PIPCL_GRAAL_PYTHON is set to non-graal Python (we
16
+ build for non-graal except with Graal Python's include paths and library
17
+ directory).
14
18
  '''
15
19
 
16
20
  import base64
21
+ import codecs
17
22
  import glob
18
23
  import hashlib
19
24
  import inspect
@@ -572,6 +577,11 @@ class Package:
572
577
 
573
578
  self.wheel_compression = wheel_compression
574
579
  self.wheel_compresslevel = wheel_compresslevel
580
+
581
+ # If true and we are building for graal, we set PIPCL_PYTHON_CONFIG to
582
+ # a command that will print includes/libs from graal_py's sysconfig.
583
+ #
584
+ self.graal_legacy_python_config = True
575
585
 
576
586
 
577
587
  def build_wheel(self,
@@ -592,6 +602,59 @@ class Package:
592
602
  f' metadata_directory={metadata_directory!r}'
593
603
  )
594
604
 
605
+ if sys.implementation.name == 'graalpy':
606
+ # We build for Graal by building a native Python wheel with Graal
607
+ # Python's include paths and library directory. We then rename the
608
+ # wheel to contain graal's tag etc.
609
+ #
610
+ log0(f'### Graal build: deferring to cpython.')
611
+ python_native = os.environ.get('PIPCL_GRAAL_PYTHON')
612
+ assert python_native, f'Graal build requires that PIPCL_GRAAL_PYTHON is set.'
613
+ env_extra = dict(
614
+ PIPCL_SYSCONFIG_PATH_include = sysconfig.get_path('include'),
615
+ PIPCL_SYSCONFIG_PATH_platinclude = sysconfig.get_path('platinclude'),
616
+ PIPCL_SYSCONFIG_CONFIG_VAR_LIBDIR = sysconfig.get_config_var('LIBDIR'),
617
+ )
618
+ # Tell native build to run pipcl.py itself to get python-config
619
+ # information about include paths etc.
620
+ if self.graal_legacy_python_config:
621
+ env_extra['PIPCL_PYTHON_CONFIG'] = f'{python_native} {os.path.abspath(__file__)} --graal-legacy-python-config'
622
+
623
+ # Create venv.
624
+ venv_name = os.environ.get('PIPCL_GRAAL_NATIVE_VENV')
625
+ if venv_name:
626
+ log1(f'Graal using pre-existing {venv_name=}')
627
+ else:
628
+ venv_name = 'venv-pipcl-graal-native'
629
+ run(f'{shlex.quote(python_native)} -m venv {venv_name}')
630
+ log1(f'Graal using {venv_name=}')
631
+
632
+ newfiles = NewFiles(f'{wheel_directory}/*.whl')
633
+ run(
634
+ f'. {venv_name}/bin/activate && python setup.py --dist-dir {shlex.quote(wheel_directory)} bdist_wheel',
635
+ env_extra = env_extra,
636
+ prefix = f'pipcl.py graal {python_native}: ',
637
+ )
638
+ wheel = newfiles.get_one()
639
+ wheel_leaf = os.path.basename(wheel)
640
+ python_major_minor = run(f'{shlex.quote(python_native)} -c "import platform; import sys; sys.stdout.write(str().join(platform.python_version_tuple()[:2]))"', capture=1)
641
+ cpabi = f'cp{python_major_minor}-abi3'
642
+ assert cpabi in wheel_leaf, f'Expected wheel to be for {cpabi=}, but {wheel=}.'
643
+ graalpy_ext_suffix = sysconfig.get_config_var('EXT_SUFFIX')
644
+ log1(f'{graalpy_ext_suffix=}')
645
+ m = re.match(r'\.graalpy(\d+[^\-]*)-(\d+)', graalpy_ext_suffix)
646
+ gpver = m[1]
647
+ cpver = m[2]
648
+ graalpy_wheel_tag = f'graalpy{cpver}-graalpy{gpver}_{cpver}_native'
649
+ name = wheel_leaf.replace(cpabi, graalpy_wheel_tag)
650
+ destination = f'{wheel_directory}/{name}'
651
+ log0(f'### Graal build: copying {wheel=} to {destination=}')
652
+ # Copying results in two wheels which appears to confuse pip, showing:
653
+ # Found multiple .whl files; unspecified behaviour. Will call build_wheel.
654
+ os.rename(wheel, destination)
655
+ log1(f'Returning {name=}.')
656
+ return name
657
+
595
658
  wheel_name = self.wheel_name()
596
659
  path = f'{wheel_directory}/{wheel_name}'
597
660
 
@@ -1137,6 +1200,10 @@ class Package:
1137
1200
  assert command is None, 'Two commands specified: {command} and {arg}.'
1138
1201
  command = arg
1139
1202
 
1203
+ elif arg in ('windows-vs', 'windows-python', 'show-sysconfig'):
1204
+ assert command is None, 'Two commands specified: {command} and {arg}.'
1205
+ command = arg
1206
+
1140
1207
  elif arg == '--all': opt_all = True
1141
1208
  elif arg == '--compile': pass
1142
1209
  elif arg == '--dist-dir' or arg == '-d': opt_dist_dir = args.next()
@@ -1149,12 +1216,6 @@ class Package:
1149
1216
  elif arg == '--single-version-externally-managed': pass
1150
1217
  elif arg == '--verbose' or arg == '-v': g_verbose += 1
1151
1218
 
1152
- elif arg == 'windows-vs':
1153
- command = arg
1154
- break
1155
- elif arg == 'windows-python':
1156
- command = arg
1157
- break
1158
1219
  else:
1159
1220
  raise Exception(f'Unrecognised arg: {arg}')
1160
1221
 
@@ -1205,6 +1266,39 @@ class Package:
1205
1266
  vs = wdev.WindowsVS(year=year, grade=grade, version=version)
1206
1267
  print(f'Visual Studio is:\n{vs.description_ml(" ")}')
1207
1268
 
1269
+ elif command == 'show-sysconfig':
1270
+ show_sysconfig()
1271
+ for mod in platform, sys:
1272
+ log0(f'{mod.__name__}:')
1273
+ for n in dir(mod):
1274
+ if n.startswith('_'):
1275
+ continue
1276
+ log0(f'{mod.__name__}.{n}')
1277
+ if mod is platform and n == 'uname':
1278
+ continue
1279
+ if mod is platform and n == 'pdb':
1280
+ continue
1281
+ if mod is sys and n in ('breakpointhook', 'exit'):
1282
+ # We don't want to call these.
1283
+ continue
1284
+ v = getattr(mod, n)
1285
+ if callable(v):
1286
+ try:
1287
+ v = v()
1288
+ except Exception:
1289
+ pass
1290
+ else:
1291
+ #print(f'{n=}', flush=1)
1292
+ try:
1293
+ print(f' {mod.__name__}.{n}()={v!r}')
1294
+ except Exception:
1295
+ print(f' Failed to print value of {mod.__name__}.{n}().')
1296
+ else:
1297
+ try:
1298
+ print(f' {mod.__name__}.{n}={v!r}')
1299
+ except Exception:
1300
+ print(f' Failed to print value of {mod.__name__}.{n}.')
1301
+
1208
1302
  else:
1209
1303
  assert 0, f'Unrecognised command: {command}'
1210
1304
 
@@ -1402,7 +1496,7 @@ def build_extension(
1402
1496
  debug=False,
1403
1497
  compiler_extra='',
1404
1498
  linker_extra='',
1405
- swig='swig',
1499
+ swig=None,
1406
1500
  cpp=True,
1407
1501
  prerequisites_swig=None,
1408
1502
  prerequisites_compile=None,
@@ -1453,7 +1547,7 @@ def build_extension(
1453
1547
  linker_extra:
1454
1548
  Extra linker flags. Can be None.
1455
1549
  swig:
1456
- Base swig command.
1550
+ Swig command; if false we use 'swig'.
1457
1551
  cpp:
1458
1552
  If true we tell SWIG to generate C++ code instead of C.
1459
1553
  prerequisites_swig:
@@ -1503,6 +1597,8 @@ def build_extension(
1503
1597
  linker_extra = ''
1504
1598
  if builddir is None:
1505
1599
  builddir = outdir
1600
+ if not swig:
1601
+ swig = 'swig'
1506
1602
  includes_text = _flags( includes, '-I')
1507
1603
  defines_text = _flags( defines, '-D')
1508
1604
  libpaths_text = _flags( libpaths, '/LIBPATH:', '"') if windows() else _flags( libpaths, '-L')
@@ -1863,6 +1959,34 @@ def base_linker(vs=None, pythonflags=None, cpp=False, use_env=True):
1863
1959
  return linker, pythonflags
1864
1960
 
1865
1961
 
1962
+ def git_info( directory):
1963
+ '''
1964
+ Returns `(sha, comment, diff, branch)`, all items are str or None if not
1965
+ available.
1966
+
1967
+ directory:
1968
+ Root of git checkout.
1969
+ '''
1970
+ sha, comment, diff, branch = None, None, None, None
1971
+ e, out = run(
1972
+ f'cd {directory} && (PAGER= git show --pretty=oneline|head -n 1 && git diff)',
1973
+ capture=1,
1974
+ check=0
1975
+ )
1976
+ if not e:
1977
+ sha, _ = out.split(' ', 1)
1978
+ comment, diff = _.split('\n', 1)
1979
+ e, out = run(
1980
+ f'cd {directory} && git rev-parse --abbrev-ref HEAD',
1981
+ capture=1,
1982
+ check=0
1983
+ )
1984
+ if not e:
1985
+ branch = out.strip()
1986
+ log(f'git_info(): directory={directory!r} returning branch={branch!r} sha={sha!r} comment={comment!r}')
1987
+ return sha, comment, diff, branch
1988
+
1989
+
1866
1990
  def git_items( directory, submodules=False):
1867
1991
  '''
1868
1992
  Returns list of paths for all files known to git within a `directory`.
@@ -1912,37 +2036,78 @@ def git_get(
1912
2036
  tag=None,
1913
2037
  update=True,
1914
2038
  submodules=True,
2039
+ default_remote=None,
1915
2040
  ):
1916
2041
  '''
1917
2042
  Ensures that <local> is a git checkout (at either <tag>, or <branch> HEAD)
1918
2043
  of a remote repository.
1919
2044
 
1920
- Exactly one of <branch> and <tag> must be specified.
2045
+ Exactly one of <branch> and <tag> must be specified, or <remote> must start
2046
+ with 'git:' and match the syntax described below.
1921
2047
 
1922
2048
  Args:
1923
2049
  remote:
1924
2050
  Remote git repostitory, for example
1925
2051
  'https://github.com/ArtifexSoftware/mupdf.git'.
2052
+
2053
+ If starts with 'git:', the remaining text should be a command-line
2054
+ style string containing some or all of these args:
2055
+ --branch <branch>
2056
+ --tag <tag>
2057
+ <remote>
2058
+ These overrides <branch>, <tag> and <default_remote>.
2059
+
2060
+ For example these all clone/update/branch master of https://foo.bar/qwerty.git to local
2061
+ checkout 'foo-local':
2062
+
2063
+ git_get('https://foo.bar/qwerty.git', 'foo-local', branch='master')
2064
+ git_get('git:--branch master https://foo.bar/qwerty.git', 'foo-local')
2065
+ git_get('git:--branch master', 'foo-local', default_remote='https://foo.bar/qwerty.git')
2066
+ git_get('git:', 'foo-local', branch='master', default_remote='https://foo.bar/qwerty.git')
2067
+
1926
2068
  local:
1927
2069
  Local directory. If <local>/.git exists, we attempt to run `git
1928
2070
  update` in it.
1929
2071
  branch:
1930
- Branch to use.
2072
+ Branch to use. Is used as default if remote starts with 'git:'.
1931
2073
  depth:
1932
2074
  Depth of local checkout when cloning and fetching, or None.
1933
2075
  env_extra:
1934
2076
  Dict of extra name=value environment variables to use whenever we
1935
2077
  run git.
1936
2078
  tag:
1937
- Tag to use.
2079
+ Tag to use. Is used as default if remote starts with 'git:'.
1938
2080
  update:
1939
2081
  If false we do not update existing repository. Might be useful if
1940
2082
  testing without network access.
1941
2083
  submodules:
1942
2084
  If true, we clone with `--recursive --shallow-submodules` and run
1943
2085
  `git submodule update --init --recursive` before returning.
2086
+ default_remote:
2087
+ The remote URL if <remote> starts with 'git:' but does not specify
2088
+ the remote URL.
1944
2089
  '''
1945
2090
  log0(f'{remote=} {local=} {branch=} {tag=}')
2091
+ if remote.startswith('git:'):
2092
+ remote0 = remote
2093
+ args = iter(shlex.split(remote0[len('git:'):]))
2094
+ remote = default_remote
2095
+ while 1:
2096
+ try:
2097
+ arg = next(args)
2098
+ except StopIteration:
2099
+ break
2100
+ if arg == '--branch':
2101
+ branch = next(args)
2102
+ tag = None
2103
+ elif arg == '--tag':
2104
+ tag == next(args)
2105
+ branch = None
2106
+ else:
2107
+ remote = arg
2108
+ assert remote, f'{default_remote=} and no remote specified in remote={remote0!r}.'
2109
+ assert branch or tag, f'{branch=} {tag=} and no branch/tag specified in remote={remote0!r}.'
2110
+
1946
2111
  assert (branch and not tag) or (not branch and tag), f'Must specify exactly one of <branch> and <tag>.'
1947
2112
 
1948
2113
  depth_arg = f' --depth {depth}' if depth else ''
@@ -2007,9 +2172,11 @@ def run(
2007
2172
  capture=False,
2008
2173
  check=1,
2009
2174
  verbose=1,
2175
+ env=None,
2010
2176
  env_extra=None,
2011
2177
  timeout=None,
2012
2178
  caller=1,
2179
+ prefix=None,
2013
2180
  ):
2014
2181
  '''
2015
2182
  Runs a command using `subprocess.run()`.
@@ -2032,12 +2199,22 @@ def run(
2032
2199
  command's returncode in our return value.
2033
2200
  verbose:
2034
2201
  If true we show the command.
2202
+ env:
2203
+ None or dict to use instead of <os.environ>.
2035
2204
  env_extra:
2036
- None or dict to add to environ.
2205
+ None or dict to add to <os.environ> or <env>.
2037
2206
  timeout:
2038
2207
  If not None, timeout in seconds; passed directly to
2039
2208
  subprocess.run(). Note that on MacOS subprocess.run() seems to
2040
2209
  leave processes running if timeout expires.
2210
+ prefix:
2211
+ String prefix for each line of output.
2212
+
2213
+ If true:
2214
+ * We run command with stdout=subprocess.PIPE and
2215
+ stderr=subprocess.STDOUT, repetaedly reading the command's output
2216
+ and writing it to stdout with <prefix>.
2217
+ * We do not support <timeout>, which must be None.
2041
2218
  Returns:
2042
2219
  check capture Return
2043
2220
  --------------------------
@@ -2046,9 +2223,11 @@ def run(
2046
2223
  true false None or raise exception
2047
2224
  true true output or raise exception
2048
2225
  '''
2049
- env = None
2226
+ if env is None:
2227
+ env = os.environ
2228
+ if env_extra:
2229
+ env = env.copy()
2050
2230
  if env_extra:
2051
- env = os.environ.copy()
2052
2231
  env.update(env_extra)
2053
2232
  lines = _command_lines( command)
2054
2233
  if verbose:
@@ -2061,16 +2240,59 @@ def run(
2061
2240
  log1(text, caller=caller+1)
2062
2241
  sep = ' ' if windows() else ' \\\n'
2063
2242
  command2 = sep.join( lines)
2064
- cp = subprocess.run(
2065
- command2,
2066
- shell=True,
2067
- stdout=subprocess.PIPE if capture else None,
2068
- stderr=subprocess.STDOUT if capture else None,
2069
- check=check,
2070
- encoding='utf8',
2071
- env=env,
2072
- timeout=timeout,
2073
- )
2243
+
2244
+ if prefix:
2245
+ assert not timeout, f'Timeout not supported with prefix.'
2246
+ child = subprocess.Popen(
2247
+ command2,
2248
+ shell=True,
2249
+ stdout=subprocess.PIPE,
2250
+ stderr=subprocess.STDOUT,
2251
+ encoding='utf8',
2252
+ env=env,
2253
+ )
2254
+ if capture:
2255
+ capture_text = ''
2256
+ decoder = codecs.getincrementaldecoder('utf8')('replace')
2257
+ line_start = True
2258
+ while 1:
2259
+ raw = os.read( child.stdout.fileno(), 10000)
2260
+ text = decoder.decode(raw, final=not raw)
2261
+ if text:
2262
+ if capture:
2263
+ capture_text += text
2264
+ lines = text.split('\n')
2265
+ for i, line in enumerate(lines):
2266
+ if line_start:
2267
+ sys.stdout.write(prefix)
2268
+ line_start = False
2269
+ sys.stdout.write(line)
2270
+ if i < len(lines) - 1:
2271
+ sys.stdout.write('\n')
2272
+ line_start = True
2273
+ sys.stdout.flush()
2274
+ if not raw:
2275
+ break
2276
+ if not line_start:
2277
+ sys.stdout.write('\n')
2278
+ e = child.wait()
2279
+ if check and e:
2280
+ raise subprocess.CalledProcessError(e, command2, capture_text if capture else None)
2281
+ if check:
2282
+ return capture_text if capture else None
2283
+ else:
2284
+ return (e, capture_text) if capture else e
2285
+ else:
2286
+ cp = subprocess.run(
2287
+ command2,
2288
+ shell=True,
2289
+ stdout=subprocess.PIPE if capture else None,
2290
+ stderr=subprocess.STDOUT if capture else None,
2291
+ check=check,
2292
+ encoding='utf8',
2293
+ env=env,
2294
+ timeout=timeout,
2295
+ )
2074
2296
  if check:
2075
2297
  return cp.stdout if capture else None
2076
2298
  else:
@@ -2108,9 +2330,11 @@ def show_system():
2108
2330
  log(f'{os.getcwd()=}')
2109
2331
  log(f'{platform.machine()=}')
2110
2332
  log(f'{platform.platform()=}')
2333
+ log(f'{platform.python_implementation()=}')
2111
2334
  log(f'{platform.python_version()=}')
2112
2335
  log(f'{platform.system()=}')
2113
- log(f'{platform.uname()=}')
2336
+ if sys.implementation.name != 'graalpy':
2337
+ log(f'{platform.uname()=}')
2114
2338
  log(f'{sys.executable=}')
2115
2339
  log(f'{sys.version=}')
2116
2340
  log(f'{sys.version_info=}')
@@ -2143,8 +2367,23 @@ class PythonFlags:
2143
2367
  String containing linker flags for library paths.
2144
2368
  '''
2145
2369
  def __init__(self):
2370
+
2371
+ # Experimental detection of python flags from sysconfig.*() instead of
2372
+ # python-config command.
2373
+ includes_, ldflags_ = sysconfig_python_flags()
2374
+
2375
+ if pyodide():
2376
+ _include_dir = os.environ[ 'PYO3_CROSS_INCLUDE_DIR']
2377
+ _lib_dir = os.environ[ 'PYO3_CROSS_LIB_DIR']
2378
+ self.includes = f'-I {_include_dir}'
2379
+ self.ldflags = f'-L {_lib_dir}'
2146
2380
 
2147
- if windows():
2381
+ elif 0:
2382
+
2383
+ self.includes = includes_
2384
+ self.ldflags = ldflags_
2385
+
2386
+ elif windows():
2148
2387
  wp = wdev.WindowsPython()
2149
2388
  self.includes = f'/I"{wp.include}"'
2150
2389
  self.ldflags = f'/LIBPATH:"{wp.libs}"'
@@ -2213,8 +2452,10 @@ class PythonFlags:
2213
2452
  log2(f'### Have removed `-lcrypt` from ldflags: {self.ldflags!r} -> {ldflags2!r}')
2214
2453
  self.ldflags = ldflags2
2215
2454
 
2216
- log2(f'{self.includes=}')
2217
- log2(f'{self.ldflags=}')
2455
+ log1(f'{self.includes=}')
2456
+ log1(f' {includes_=}')
2457
+ log1(f'{self.ldflags=}')
2458
+ log1(f' {ldflags_=}')
2218
2459
 
2219
2460
 
2220
2461
  def macos_add_cross_flags(command):
@@ -2758,7 +2999,10 @@ class NewFiles:
2758
2999
  def _file_id(self, path):
2759
3000
  mtime = os.stat(path).st_mtime
2760
3001
  with open(path, 'rb') as f:
2761
- hash_ = hashlib.file_digest(f, hashlib.md5).digest()
3002
+ content = f.read()
3003
+ hash_ = hashlib.md5(content).digest()
3004
+ # With python >= 3.11 we can do:
3005
+ #hash_ = hashlib.file_digest(f, hashlib.md5).digest()
2762
3006
  return mtime, hash_
2763
3007
  def _items(self):
2764
3008
  ret = dict()
@@ -2766,3 +3010,123 @@ class NewFiles:
2766
3010
  if os.path.isfile(path):
2767
3011
  ret[path] = self._file_id(path)
2768
3012
  return ret
3013
+
3014
+
3015
+ def swig_get(swig, quick, swig_local='pipcl-swig-git'):
3016
+ '''
3017
+ Returns <swig> or a new swig binary.
3018
+
3019
+ If <swig> is true and starts with 'git:' (not Windows), the remaining text
3020
+ is passed to git_get() and we clone/update/build swig, and return the built
3021
+ binary. We default to the main swig repository, branch master, so for
3022
+ example 'git:' will return the latest swig from branch master.
3023
+
3024
+ Otherwise we simply return <swig>.
3025
+
3026
+ Args:
3027
+ swig:
3028
+ If starts with 'git:', passed as <remote> arg to git_remote().
3029
+ quick:
3030
+ If true, we do not update/build local checkout if the binary is
3031
+ already present.
3032
+ swig_local:
3033
+ path to use for checkout.
3034
+ '''
3035
+ if swig and swig.startswith('git:'):
3036
+ assert platform.system() != 'Windows'
3037
+ swig_local = os.path.abspath(swig_local)
3038
+ # Note that {swig_local}/install/bin/swig doesn't work on MacoS because
3039
+ # {swig_local}/INSTALL is a file and the fs is case-insensitive.
3040
+ swig_binary = f'{swig_local}/install-dir/bin/swig'
3041
+ if quick and os.path.isfile(swig_binary):
3042
+ log1(f'{quick=} and {swig_binary=} already exists, so not downloading/building.')
3043
+ else:
3044
+ # Clone swig.
3045
+ swig_env_extra = None
3046
+ git_get(
3047
+ swig,
3048
+ swig_local,
3049
+ default_remote='https://github.com/swig/swig.git',
3050
+ branch='master',
3051
+ )
3052
+ if darwin():
3053
+ run(f'brew install automake')
3054
+ run(f'brew install pcre2')
3055
+ # Default bison doesn't work, and Brew's bison is not added to $PATH.
3056
+ #
3057
+ # > bison is keg-only, which means it was not symlinked into /opt/homebrew,
3058
+ # > because macOS already provides this software and installing another version in
3059
+ # > parallel can cause all kinds of trouble.
3060
+ # >
3061
+ # > If you need to have bison first in your PATH, run:
3062
+ # > echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
3063
+ #
3064
+ run(f'brew install bison')
3065
+ PATH = os.environ['PATH']
3066
+ PATH = f'/opt/homebrew/opt/bison/bin:{PATH}'
3067
+ swig_env_extra = dict(PATH=PATH)
3068
+ # Build swig.
3069
+ run(f'cd {swig_local} && ./autogen.sh', env_extra=swig_env_extra)
3070
+ run(f'cd {swig_local} && ./configure --prefix={swig_local}/install-dir', env_extra=swig_env_extra)
3071
+ run(f'cd {swig_local} && make', env_extra=swig_env_extra)
3072
+ run(f'cd {swig_local} && make install', env_extra=swig_env_extra)
3073
+ assert os.path.isfile(swig_binary)
3074
+ return swig_binary
3075
+ else:
3076
+ return swig
3077
+
3078
+
3079
+ def _show_dict(d):
3080
+ ret = ''
3081
+ for n in sorted(d.keys()):
3082
+ v = d[n]
3083
+ ret += f' {n}: {v!r}\n'
3084
+ return ret
3085
+
3086
+ def show_sysconfig():
3087
+ '''
3088
+ Shows contents of sysconfig.get_paths() and sysconfig.get_config_vars() dicts.
3089
+ '''
3090
+ import sysconfig
3091
+ paths = sysconfig.get_paths()
3092
+ log0(f'show_sysconfig().')
3093
+ log0(f'sysconfig.get_paths():\n{_show_dict(sysconfig.get_paths())}')
3094
+ log0(f'sysconfig.get_config_vars():\n{_show_dict(sysconfig.get_config_vars())}')
3095
+
3096
+
3097
+ def sysconfig_python_flags():
3098
+ '''
3099
+ Returns include paths and library directory for Python.
3100
+
3101
+ Uses sysconfig.*(), overridden by environment variables
3102
+ PIPCL_SYSCONFIG_PATH_include, PIPCL_SYSCONFIG_PATH_platinclude and
3103
+ PIPCL_SYSCONFIG_CONFIG_VAR_LIBDIR if set.
3104
+ '''
3105
+ include1_ = os.environ.get('PIPCL_SYSCONFIG_PATH_include') or sysconfig.get_path('include')
3106
+ include2_ = os.environ.get('PIPCL_SYSCONFIG_PATH_platinclude') or sysconfig.get_path('platinclude')
3107
+ ldflags_ = os.environ.get('PIPCL_SYSCONFIG_CONFIG_VAR_LIBDIR') or sysconfig.get_config_var('LIBDIR')
3108
+
3109
+ includes_ = [include1_]
3110
+ if include2_ != include1_:
3111
+ includes_.append(include2_)
3112
+ if windows():
3113
+ includes_ = [f'/I"{i}"' for i in includes_]
3114
+ ldflags_ = f'/LIBPATH:"{ldflags_}"'
3115
+ else:
3116
+ includes_ = [f'-I {i}' for i in includes_]
3117
+ ldflags_ = f'-L {ldflags_}'
3118
+ includes_ = ' '.join(includes_)
3119
+ return includes_, ldflags_
3120
+
3121
+
3122
+ if __name__ == '__main__':
3123
+ # Internal-only limited command line support, used if
3124
+ # graal_legacy_python_config is true.
3125
+ #
3126
+ includes, ldflags = sysconfig_python_flags()
3127
+ if sys.argv[1:] == ['--graal-legacy-python-config', '--includes']:
3128
+ print(includes)
3129
+ elif sys.argv[1:] == ['--graal-legacy-python-config', '--ldflags']:
3130
+ print(ldflags)
3131
+ else:
3132
+ assert 0, f'Expected `--graal-legacy-python-config --includes|--ldflags` but {sys.argv=}'