Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support opacity #2997

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/page.rst
Original file line number Diff line number Diff line change
Expand Up @@ -635,17 +635,18 @@ In a nutshell, this is what you can do with PyMuPDF:
pair: overlay; insert_htmlbox
pair: rotate; insert_htmlbox
pair: oc; insert_htmlbox
pair: opacity; insert_htmlbox
pair: morph; insert_htmlbox

.. method:: insert_htmlbox(rect, text, *, css=None, scale_low=0, archive=None, rotate=0, oc=0, overlay=True)
.. method:: insert_htmlbox(rect, text, *, css=None, scale_low=0, archive=None, rotate=0, oc=0, opacity=1, overlay=True)

* New in v1.23.8

**PDF only:** Insert text into the specified rectangle. The method has similarities with methods :meth:`Page.insert_textbox` and :meth:`TextWriter.fill_textbox`, but is **much more powerful**. This is achieved by letting a :ref:`Story` object do all the required processing.

* Parameter `text` may be a string as in the other methods. But it will be **interpreted as HTML source** and may therefore also contain HTML language elements -- including styling. The `css` parameter may be used to pass in additional styling instructions.

* Automatic line breaks are inserted at word boundaries. The "soft hyphen" character `"&#173;"` can be used to cause hyphenation and thus also cause line breaks. **Forced** line breaks however are only achievable via the HTML tag `<br>` - `"\\n"` is ignored and will be treated like a space.
* Automatic line breaks are generated at word boundaries. The "soft hyphen" character `"&#173;"` (or `&shy;`) can be used to cause hyphenation and thus may also cause line breaks. **Forced** line breaks however are only achievable via the HTML tag `<br>` - `"\\n"` is ignored and will be treated like a space.

* With this method the following can be achieved:

Expand Down Expand Up @@ -676,6 +677,7 @@ In a nutshell, this is what you can do with PyMuPDF:
.. image:: images/img-rotate.*

:arg int oc: the xref of an :data:`OCG` / :data:`OCMD` or 0. Please refer to :meth:`Page.show_pdf_page` for details.
:arg float opacity: set the fill and stroke opacity for the content in the rectangle. Only values `0 <= opacity < 1` are considered.
:arg bool overlay: put the text in front of other content. Please refer to :meth:`Page.show_pdf_page` for details.

:returns: A tuple of floats `(spare_height, scale)`.
Expand Down
14 changes: 12 additions & 2 deletions src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,7 @@ def insert_htmlbox(
archive=None,
rotate=0,
oc=0,
opacity=1,
overlay=True,
) -> float:
"""Insert text with optional HTML tags and stylings into a rectangle.
Expand All @@ -1871,13 +1872,14 @@ def insert_htmlbox(
archive: Archive object pointing to locations of used fonts or images
rotate: (int) rotate the text in the box by a multiple of 90 degrees.
oc: (int) the xref of an OCG / OCMD (Optional Content).
opacity: (float) set opacity of inserted content.
overlay: (bool) put text on top of page content.
Returns:
A tuple of floats (spare_height, scale).
spare_height: -1 if content did not fit, else >= 0. It is the height of the
unused (still available) rectangle stripe. Positive only if
scale_min = 1 (no down scaling).
scale: downscaling factor, 0 < scale <= 1. Set to 0 if spare_height = -1.
scale: downscaling factor, 0 < scale <= 1. Set to 0 if spare_height = -1 (no fit).
"""

# normalize rotation angle
Expand Down Expand Up @@ -1910,7 +1912,6 @@ def insert_htmlbox(
story = text
else:
raise ValueError("'text' must be a string or a Story")

# ----------------------------------------------------------------
# Find a scaling factor that lets our story fit in
# ----------------------------------------------------------------
Expand All @@ -1935,6 +1936,15 @@ def rect_function(*args):
# draw story on temp PDF page
doc = story.write_with_links(rect_function)

# Insert opacity if requested.
# For this, we prepend a command to the /Contents.
if 0 <= opacity < 1:
tpage = doc[0] # load page
# generate /ExtGstate for the page
alp0 = tpage._set_opacity(CA=opacity, ca=opacity)
s = f"/{alp0} gs\n" # generate graphic state command
fitz.TOOLS._insert_contents(tpage, s.encode(), 0)

# put result in target page
page.show_pdf_page(rect, doc, 0, rotate=rotate, oc=oc, overlay=overlay)

Expand Down
19 changes: 19 additions & 0 deletions tests/test_textbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,22 @@ def test_htmlbox2():
assert 0 < spare_height < rect.height
bottoms.add(spare_height)
assert len(bottoms) == 1 # same result for all rotations


def test_htmlbox3():
"""Test insertion with opacity"""
if not hasattr(fitz, "mupdf"):
print("'test_htmlbox3' not executed in classic.")
return

rect = fitz.Rect(100, 250, 300, 350)
text = """<span style="color:red;font-size:20px;">Just some text.</span>"""
doc = fitz.open()
page = doc.new_page()

# insert some text with opacity
page.insert_htmlbox(rect, text, opacity=0.5)

# lowlevel-extract inserted text to access opacity
span = page.get_texttrace()[0]
assert span["opacity"] == 0.5
Loading