diff --git a/changes.txt b/changes.txt index 44347c5b2..3ec8841a8 100644 --- a/changes.txt +++ b/changes.txt @@ -2,7 +2,7 @@ Change Log ========== -**Changes in version 1.28.0** +**Changes in version 1.28.0** (2026-06-29) Fixed issues: diff --git a/src/__init__.py b/src/__init__.py index 73a5e3bb9..7434f7bde 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -4967,11 +4967,8 @@ def get_ocgs(self): o = mupdf.pdf_array_get( intent, j) if mupdf.pdf_is_name( o): intents.append( mupdf.pdf_to_name( o)) - if mupdf_version_tuple >= (1, 26, 11): - resource_stack = mupdf.PdfResourceStack() - hidden = mupdf.pdf_is_ocg_hidden( pdf, resource_stack, usage, ocg) - else: - hidden = mupdf.pdf_is_ocg_hidden( pdf, mupdf.PdfObj(), usage, ocg) + resource_stack = mupdf.PdfResourceStack() + hidden = mupdf.pdf_is_ocg_hidden( pdf, resource_stack, usage, ocg) item = { "name": name, "intent": intents, diff --git a/src/fitz___init__.py b/src/fitz___init__.py index f382e1063..95fc9ce4c 100644 --- a/src/fitz___init__.py +++ b/src/fitz___init__.py @@ -11,3 +11,5 @@ from pymupdf import __doc__ from pymupdf import _globals from pymupdf import _g_out_message + +message_warning('The `fitz` API is deprecated and will be removed in future. Use `import pymupdf` instead.') diff --git a/tests/resources/test_markdown_bad_unicode.md b/tests/resources/test_markdown_bad_unicode.md new file mode 100644 index 000000000..f59f8fa56 --- /dev/null +++ b/tests/resources/test_markdown_bad_unicode.md @@ -0,0 +1,22 @@ +# Title + +## A Table + +|**Boiling Points °C**|**min**|**max**|**avg**| +|---|---|---|---| +|**Noble gases**|-269|-62|-170.5| +|**Nonmetals**|-253|4827|414.1| +|**Metalloids**|335|3900|741.5| +|**Metals**|357|>5000|2755.9| + +## A List + +* Comment 1 +* Comment 2 +* Comment 3 with a link to [Find out more](https://pymupdf.readthedocs.io) + +## My TO-DOs + +* [x] Done! +* [x] Also done! +* [ ] Still open diff --git a/tests/test_font.py b/tests/test_font.py index b54ec2723..bfd6c6358 100644 --- a/tests/test_font.py +++ b/tests/test_font.py @@ -323,14 +323,7 @@ def test_4457(): assert text_before == text assert rms_before == 0 - - if pymupdf.mupdf_version_tuple >= (1, 26, 6): - assert rms_after == 0 - else: - # As of 2025-05-20 there are some differences in some characters, - # e.g. the non-ascii characters in `Philipp Krahenbuhl`. See - # and . - assert abs(rms_after - rms_old_after_max) < 2 + assert rms_after == 0 # Avoid test failure caused by mupdf warnings. wt = pymupdf.TOOLS.mupdf_warnings() diff --git a/tests/test_general.py b/tests/test_general.py index 9a531faee..0d729107e 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -72,8 +72,6 @@ def test_iswrapped(): doc = pymupdf.open(filename) page = doc[0] assert page.is_wrapped - if (1, 26, 0) <= pymupdf.mupdf_version_tuple < (1, 27): - assert pymupdf.TOOLS.mupdf_warnings() == 'bogus font ascent/descent values (0 / 0)' def test_wrapcontents(): @@ -87,10 +85,7 @@ def test_wrapcontents(): assert len(page.get_contents()) == 1 page.clean_contents() wt = pymupdf.TOOLS.mupdf_warnings() - if (1, 26, 0) <= pymupdf.mupdf_version_tuple < (1, 27): - assert wt == 'bogus font ascent/descent values (0 / 0)\nPDF stream Length incorrect' - else: - assert wt == 'PDF stream Length incorrect' + assert wt == 'PDF stream Length incorrect' def test_page_clean_contents(): @@ -251,8 +246,6 @@ def test_get_text_dict(): blocks=page.get_text("dict")["blocks"] # Check no opaque types in `blocks`. json.dumps( blocks, indent=4) - if (1, 26, 0) <= pymupdf.mupdf_version_tuple < (1, 27): - assert pymupdf.TOOLS.mupdf_warnings() == 'bogus font ascent/descent values (0 / 0)' def test_font(): font = pymupdf.Font() @@ -608,9 +601,6 @@ def test_2596(): page = doc.reload_page(page) pix1 = page.get_pixmap() assert pix1.samples == pix0.samples - if pymupdf.mupdf_version_tuple < (1, 26, 6): - wt = pymupdf.TOOLS.mupdf_warnings() - assert wt == 'too many indirections (possible indirection cycle involving 24 0 R)' def test_2730(): @@ -1966,10 +1956,7 @@ def test_4533(): cp = subprocess.run(command, shell=1, check=0) e = cp.returncode print(f'{e=}') - if pymupdf.mupdf_version_tuple >= (1, 26, 6): - assert e == 0 - else: - assert e != 0 + assert e == 0 def test_4564(): @@ -2147,9 +2134,6 @@ def test_4712(): ''' Crash with "corrupted double-linked list ''' - if pymupdf.mupdf_version_tuple < (1, 26, 11): - print(f'test_4712m(): Not running because known to fail on mupdf < 1.26.11: {pymupdf.mupdf_version=}.') - return path_a = os.path.normpath(f'{__file__}/../../tests/resources/test_4712_a.pdf') path_b = os.path.normpath(f'{__file__}/../../tests/resources/test_4712_b.pdf') doc1 = pymupdf.open(path_a) @@ -2161,10 +2145,6 @@ def test_4712(): def test_4712m(): - if pymupdf.mupdf_version_tuple < (1, 26, 11): - print(f'test_4712m(): Not running because known to fail on mupdf < 1.26.11: {pymupdf.mupdf_version=}.') - return - path_a = os.path.normpath(f'{__file__}/../../tests/resources/test_4712_a.pdf') path_b = os.path.normpath(f'{__file__}/../../tests/resources/test_4712_b.pdf') diff --git a/tests/test_insertpdf.py b/tests/test_insertpdf.py index 99b2aa0b1..d6a4526e0 100644 --- a/tests/test_insertpdf.py +++ b/tests/test_insertpdf.py @@ -326,12 +326,7 @@ def test_4571(): print(f'Have saved to: {path_out=}') with open(path_out, 'rb') as f: content = f.read() - if pymupdf.mupdf_version_tuple >= (1, 26, 6): - # Correct. - assert b'<>' in content - else: - # Incorrect. - assert b'<>' in content + assert b'<>' in content def test_4958(): diff --git a/tests/test_markdown_support.py b/tests/test_markdown_support.py index a67c688c8..0f7435eec 100644 --- a/tests/test_markdown_support.py +++ b/tests/test_markdown_support.py @@ -84,3 +84,80 @@ def test_markdown_save(): with pymupdf.open(stream=md.encode(), filetype='md') as document_md: out_pdf = os.path.normpath(f'{__file__}/../../tests/test_markdown_save.pdf') document_md.save(out_pdf) + + +def test_markdown_bad_unicode(): + path_md = os.path.normpath(f'{__file__}/../../tests/resources/test_markdown_bad_unicode.md') + path_md_pdf = os.path.normpath(f'{__file__}/../../tests/test_markdown_bad_unicode.md.pdf') + with pymupdf.open(path_md) as md_doc: + md_doc.save(path_md_pdf) + + # To check that pymupdf has done the right thing, we extract text from our + # generated pdf and assert that it is as expected. + with pymupdf.open(path_md_pdf) as document: + text = document[0].get_text() + textb = text.encode('utf8') + print() + print(textwrap.indent(text, ' ')) + print(f'textb:') + for line in textb.split(b'\n'): + print(f' {line}') + print(f'{pymupdf.mupdf_version_tuple=}') + if pymupdf.mupdf_version_tuple > (1, 28, 0): + textb_expected = ( + b'Title', + b'A Table', + b'Boiling Points \xc8\xb9C', + b'min', + b'max', + b'avg', + b'Noble gases', + b'-269', + b'-62', + b'-170.5', + b'Nonmetals', + b'-253', + b'4827', + b'414.1', + b'Metalloids', + b'335', + b'3900', + b'741.5', + b'Metals', + b'357', + b'>5000', + b'2755.9', + b'A List', + b'\xe2\x80\xa2 Comment 1', + b'\xe2\x80\xa2 Comment 2', + b'\xe2\x80\xa2 Comment 3 with a link to Find out more', + b'My TO-DOs', + b'\xe2\x80\xa2 \xe2\x98\x92 Done!', + b'\xe2\x80\xa2 \xe2\x98\x92 Also done!', + b'\xe2\x80\xa2 \xe2\x98\x90 Still open', + b'', + ) + else: + # Table is not recognised because it contains illegal utf8 sequence. + textb_expected = ( + b'Title', + b'A Table', + b'|Boiling Points \xc8\xb9C|min|max|avg| |---|---|---|---| |Noble', + b'gases|-269|-62|-170.5| |Nonmetals|-253|4827|414.1| |Metalloids|', + b'335|3900|741.5| |Metals|357|>5000|2755.9|', + b'A List', + b'\xe2\x80\xa2 Comment 1', + b'\xe2\x80\xa2 Comment 2', + b'\xe2\x80\xa2 Comment 3 with a link to Find out more', + b'My TO-DOs', + b'\xe2\x80\xa2 \xe2\x98\x92 Done!', + b'\xe2\x80\xa2 \xe2\x98\x92 Also done!', + b'\xe2\x80\xa2 \xe2\x98\x90 Still open', + b'', + ) + textb_expected = b'\n'.join(textb_expected) + if textb != textb_expected: + print(f'textb_expected:') + for line in textb_expected.split(b'\n'): + print(f' {line}') + assert textb == textb_expected diff --git a/tests/test_pixmap.py b/tests/test_pixmap.py index 0fac14357..10812311c 100644 --- a/tests/test_pixmap.py +++ b/tests/test_pixmap.py @@ -199,10 +199,7 @@ def product(x, y): print(f'{rms=}') assert rms == 0 wt = pymupdf.TOOLS.mupdf_warnings() - if (1, 26, 0) <= pymupdf.mupdf_version_tuple < (1, 27): - assert wt == 'bogus font ascent/descent values (0 / 0)\nPDF stream Length incorrect' - else: - assert wt == 'PDF stream Length incorrect' + assert wt == 'PDF stream Length incorrect' def test_3058(): doc = pymupdf.Document(os.path.abspath(f'{__file__}/../../tests/resources/test_3058.pdf')) @@ -616,10 +613,7 @@ def test_3806(): pixmap.save(path_png) rms = gentle_compare.pixmaps_rms(path_png_expected, pixmap) print(f'{rms=}') - if pymupdf.mupdf_version_tuple >= (1, 26, 6): - assert rms < 0.1 - else: - assert rms > 50 + assert rms < 0.1 def test_4388(): @@ -644,10 +638,7 @@ def test_4388(): rms = gentle_compare.pixmaps_rms(pixmap_correct, pixmap_test) print(f'{rms=}') - if pymupdf.mupdf_version_tuple >= (1, 26, 6): - assert rms == 0 - else: - assert rms >= 10 + assert rms == 0 def test_4699(): @@ -661,12 +652,7 @@ def test_4699(): print(f'Have saved to {path_png_actual=}.') rms = gentle_compare.pixmaps_rms(path_png_expected, pixmap) print(f'test_4699(): {rms=}') - if pymupdf.mupdf_version_tuple >= (1, 26, 11): - assert rms == 0 - else: - wt = pymupdf.TOOLS.mupdf_warnings() - assert 'syntax error: cannot find ExtGState resource' in wt - assert rms > 20 + assert rms == 0 def test_5001(): diff --git a/tests/test_tables.py b/tests/test_tables.py index d5fa5ef7f..d42f9e652 100644 --- a/tests/test_tables.py +++ b/tests/test_tables.py @@ -305,25 +305,14 @@ def test_markdown(): doc = pymupdf.open(filename) page = doc[0] tab = page.find_tables(strategy="lines_strict")[0] - if pymupdf.mupdf_version_tuple < (1, 26, 3): - md_expected = textwrap.dedent(''' - |Header1|Header2|Header3| - |---|---|---| - |Col11
Col12|~~Col21~~
~~Col22~~|Col31
Col32
Col33| - |Col13|~~Col23~~|Col34
Col35| - |Col14|~~Col24~~|Col36| - |Col15|~~Col25~~
~~Col26~~|| - - ''').lstrip() - else: - md_expected = ( - "|Header1|Header2|Header3|\n" - "|---|---|---|\n" - "|Col11
Col12|Col21
Col22|Col31
Col32
Col33|\n" - "|Col13|Col23|Col34
Col35|\n" - "|Col14|Col24|Col36|\n" - "|Col15|Col25
Col26||\n\n" - ) + md_expected = ( + "|Header1|Header2|Header3|\n" + "|---|---|---|\n" + "|Col11
Col12|Col21
Col22|Col31
Col32
Col33|\n" + "|Col13|Col23|Col34
Col35|\n" + "|Col14|Col24|Col36|\n" + "|Col15|Col25
Col26||\n\n" + ) md = tab.to_markdown() diff --git a/tests/test_textextract.py b/tests/test_textextract.py index 1be5d7662..1abc2b9ee 100644 --- a/tests/test_textextract.py +++ b/tests/test_textextract.py @@ -354,9 +354,7 @@ def test_3594(): print(f' {line!r}') print('='*40) wt = pymupdf.TOOLS.mupdf_warnings() - if pymupdf.mupdf_version_tuple < (1, 26, 8): - assert not wt - elif pymupdf.mupdf_version_tuple < (1, 28): + if pymupdf.mupdf_version_tuple < (1, 28): assert wt == 'Actualtext with no position. Text may be lost or mispositioned.\n... repeated 2 times...' else: assert wt == 'ActualText with no position. Text may be lost or mispositioned.\n... repeated 2 times...' @@ -407,14 +405,10 @@ def get_all_page_from_pdf(document, last_page=None): if pymupdf.mupdf_version_tuple >= (1, 27, 1): expected = '' assert wt == expected - elif pymupdf.mupdf_version_tuple >= (1, 27): + else: expected = 'format error: No common ancestor in structure tree\nstructure tree broken, assume tree is missing' expected = '\n'.join([expected] * 56) assert wt == expected - elif pymupdf.mupdf_version_tuple >= (1, 26, 8): - assert wt == 'Actualtext with no position. Text may be lost or mispositioned.\n... repeated 7684 times...' - else: - assert wt == 'Actualtext with no position. Text may be lost or mispositioned.\n... repeated 434 times...' def test_3650(): path = os.path.normpath(f'{__file__}/../../tests/resources/test_3650.pdf') @@ -910,18 +904,11 @@ def test_4546(): print(f'{text.encode()=}') wt = pymupdf.TOOLS.mupdf_warnings() - if pymupdf.mupdf_version_tuple >= (1, 26, 8): - assert text == expected_mupdf_1_27_0 - if pymupdf.mupdf_version_tuple >= (1, 28): - assert wt == 'ActualText with no position. Text may be lost or mispositioned.\n... repeated 120 times...' - else: - assert wt == 'Actualtext with no position. Text may be lost or mispositioned.\n... repeated 120 times...' - elif pymupdf.mupdf_version_tuple >= (1, 26, 1): - assert text == expected_mupdf_1_26_1 - assert not wt + assert text == expected_mupdf_1_27_0 + if pymupdf.mupdf_version_tuple >= (1, 28): + assert wt == 'ActualText with no position. Text may be lost or mispositioned.\n... repeated 120 times...' else: - print(f'No expected output for {pymupdf.mupdf_version_tuple=}') - assert not wt + assert wt == 'Actualtext with no position. Text may be lost or mispositioned.\n... repeated 120 times...' def test_4503(): @@ -959,21 +946,5 @@ def test_4503(): strikeout = span_0['char_flags'] & pymupdf.mupdf.FZ_STEXT_STRIKEOUT print(f'{strikeout=}') - if pymupdf.mupdf_version_tuple >= (1, 26, 3): - assert strikeout, f'Expected bit 0 (FZ_STEXT_STRIKEOUT) to be set in {span_0["char_flags"]=:#x}.' - assert text_0 == 'the right to request the state to review and, if appropriate,' - elif pymupdf.mupdf_version_tuple >= (1, 26, 2): - # 2025-06-09: This is still incorrect - the span should include the - # following text 'and, if appropriate,'. It looks like following spans - # are: - # strikeout=0: 'and, ' - # strikeout=1: 'if ' - # strikeout=0: 'appropri' - # strikeout=1: 'ate,' - # - assert strikeout, f'Expected bit 0 (FZ_STEXT_STRIKEOUT) to be set in {span_0["char_flags"]=:#x}.' - assert text_0 == 'the right to request the state to review ' - else: - # Expecting the bug. - assert not strikeout, f'Expected bit 0 (FZ_STEXT_STRIKEOUT) to be unset in {span_0["char_flags"]=:#x}.' - assert text_0 == 'notice the right to request the state to review and, if appropriate,' + assert strikeout, f'Expected bit 0 (FZ_STEXT_STRIKEOUT) to be set in {span_0["char_flags"]=:#x}.' + assert text_0 == 'the right to request the state to review and, if appropriate,'