diff --git a/src/__init__.py b/src/__init__.py index 97890ed45..f4a0f0d12 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -21544,13 +21544,18 @@ def JM_new_buffer_from_stext_page(page): for block in page: if block.m_internal.type == mupdf.FZ_STEXT_BLOCK_TEXT: for line in block: + break_line = True for ch in line: if (not JM_rects_overlap(rect, JM_char_bbox(line, ch)) and not mupdf.fz_is_infinite_rect(rect) ): continue - mupdf.fz_append_rune(buf, ch.m_internal.c) - mupdf.fz_append_byte(buf, ord('\n')) + if not ch.m_internal.next and (line.m_internal.flags & mupdf.FZ_STEXT_LINE_FLAGS_JOINED): + break_line = 0 + else: + mupdf.fz_append_rune(buf, ch.m_internal.c) + if break_line: + mupdf.fz_append_byte(buf, ord('\n')) mupdf.fz_append_byte(buf, ord('\n')) return buf @@ -22031,9 +22036,8 @@ def __str__(self): break haystack += 1 #next_char:; - assert haystack_string[haystack] == '\n', \ - f'{haystack=} {haystack_string[haystack]=}' - haystack += 1 + if haystack_string[haystack] == '\n': + haystack += 1 assert haystack_string[haystack] == '\n', \ f'{haystack=} {haystack_string[haystack]=}' haystack += 1 diff --git a/src/extra.i b/src/extra.i index dd1670a2e..f7da7bf19 100644 --- a/src/extra.i +++ b/src/extra.i @@ -788,6 +788,10 @@ static void JM_set_annot_callout_line(mupdf::PdfAnnot& annot, PyObject *callout, { fz_point points[3]; mupdf::FzPoint p; + if (count > sizeof(points)/sizeof(points[0])) + { + throw std::runtime_error(MSG_BAD_ARG_POINTS); + } for (int i = 0; i < count; i++) { p = JM_point_from_py(PyTuple_GetItem(callout, (Py_ssize_t) i)); @@ -2301,16 +2305,23 @@ void _as_text(fz_stext_block *block, mupdf::FzBuffer& res, mupdf::FzStextPage& p last_char = 0; for (line = block->u.t.first_line; line; line = line->next) { + int break_line = 1; for (ch = line->first_char; ch; ch = ch->next) { fz_rect chbbox = JM_char_bbox( line, ch); - if (mupdf::ll_fz_is_infinite_rect(rect) || JM_rects_overlap(rect, chbbox)) + if (!ch->next && (line->flags & FZ_STEXT_LINE_FLAGS_JOINED)) { + break_line = 0; + } + else if (mupdf::ll_fz_is_infinite_rect(rect) || JM_rects_overlap(rect, chbbox)) { last_char = ch->c; JM_append_rune(res.m_internal, last_char); } } - if (last_char != 10 && last_char > 0) + if (!break_line) + { + } + else if (last_char != 10 && last_char > 0) { mupdf::ll_fz_append_string(res.m_internal, "\n"); last_char = 10; @@ -3973,11 +3984,11 @@ PyObject *set_pixel(fz_pixmap* pm, int x, int y, PyObject *color) throw std::range_error( MSG_PIXEL_OUTSIDE); } int n = pm->n; - if (!PySequence_Check(color) || PySequence_Size(color) != n) { - throw std::range_error(MSG_BAD_COLOR_SEQ); - } int i, j; unsigned char c[5]; + if (!PySequence_Check(color) || PySequence_Size(color) != n || n > sizeof(c)) { + throw std::range_error(MSG_BAD_COLOR_SEQ); + } for (j = 0; j < n; j++) { if (JM_INT_ITEM(color, j, &i) == 1) { throw std::range_error(MSG_BAD_COLOR_SEQ); @@ -4013,14 +4024,22 @@ JM_new_buffer_from_stext_page(fz_stext_page *page) for (block = page->first_block; block; block = block->next) { if (block->type == FZ_STEXT_BLOCK_TEXT) { for (line = block->u.t.first_line; line; line = line->next) { + int break_line = 1; for (ch = line->first_char; ch; ch = ch->next) { if (!JM_rects_overlap(rect, JM_char_bbox(line, ch)) && !fz_is_infinite_rect(rect)) { continue; } - fz_append_rune(ctx, buf, ch->c); + if (!ch->next && (line->flags & FZ_STEXT_LINE_FLAGS_JOINED)) { + break_line = 0; + } + else { + fz_append_rune(ctx, buf, ch->c); + } + } + if (break_line) { + fz_append_byte(ctx, buf, '\n'); } - fz_append_byte(ctx, buf, '\n'); } fz_append_byte(ctx, buf, '\n'); } @@ -4261,8 +4280,9 @@ try_new_match: haystack += fz_chartorune(&c, haystack); next_char:; } - assert(*haystack == '\n'); - ++haystack; + if (*haystack == '\n') { + ++haystack; + } } assert(*haystack == '\n'); ++haystack; @@ -4347,7 +4367,10 @@ PyObject* ll_JM_color_count(fz_pixmap *pm, PyObject *clip) unsigned char* s = pm->samples + stride * (irect.y0 - pm->y) + n * (irect.x0 - pm->x); // Cache previous pixel. char oldpix[10]; - assert(n <= sizeof(oldpix)); + if (n > sizeof(oldpix)) + { + throw std::range_error(MSG_PIXEL_OUTSIDE); + } memcpy(oldpix, s, n); long cnt = 0; for (size_t i = 0; i < height; i++) diff --git a/tests/resources/test_4994.pdf b/tests/resources/test_4994.pdf new file mode 100644 index 000000000..b54ce5a6f Binary files /dev/null and b/tests/resources/test_4994.pdf differ diff --git a/tests/test_annots.py b/tests/test_annots.py index d5ebdcb3d..f6d1dd561 100644 --- a/tests/test_annots.py +++ b/tests/test_annots.py @@ -794,3 +794,21 @@ def test_4936(): drawings = page.get_drawings() print(f'{len(drawings)=}') assert len(drawings) == 0 + + +def test_5112(): + print() + with pymupdf.open() as document: + page = document.new_page() + rect = pymupdf.Rect(50, 50, 200, 150) + callout = [(10, 10)] * 100 + try: + print(f'test_5112(): calling page.add_freetext_annot().', flush=1) + e = page.add_freetext_annot(rect, "test", callout=callout) + except Exception as e: + print(f'Received exception: {e}') + assert isinstance(e, RuntimeError) + assert str(e) == 'bad seq of points' + else: + assert 0, f'Expected exception from page.add_freetext_annot().' + diff --git a/tests/test_textextract.py b/tests/test_textextract.py index 1abc2b9ee..caf42829e 100644 --- a/tests/test_textextract.py +++ b/tests/test_textextract.py @@ -948,3 +948,26 @@ def test_4503(): 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,' + + +def test_4994(): + print() + print(f'{pymupdf.pymupdf_version=}.') + print(f'{pymupdf.pymupdf_version_tuple=}.') + sys.stdout.flush() + path = os.path.normpath(f'{__file__}/../../tests/resources/test_4994.pdf') + with pymupdf.open(path) as document: + for page in document: + text = page.get_text() + print(f'test_4994(): {page.get_text()=}') + print(f'test_4994(): {page.get_text(flags=pymupdf.TEXTFLAGS_TEXT | pymupdf.TEXT_DEHYPHENATE)=}') + print(f'test_4994(): {page.get_text(flags=pymupdf.TEXT_DEHYPHENATE)=}') + sys.stdout.flush() + search_results = page.search_for('climate') + print(f'test_4994(): {search_results=}') + print(f'test_4994(): {len(search_results)=}') + print(f'test_4994(): {search_results=}') + sys.stdout.flush() + assert len(search_results) == 2 + #assert text == 'planet-warming gas driving climate change.' + sys.stdout.flush()