Skip to content

Commit

Permalink
Allowing to change appearance of highlight annotations (#438)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C authored May 18, 2022
1 parent 13738c1 commit f792cd8
Show file tree
Hide file tree
Showing 13 changed files with 76 additions and 48 deletions.
2 changes: 0 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ e.g. Fixes #0 <!-- This will automatically close issue #0 once the PR is merged:

- [ ] A mention of the change is present in `CHANGELOG.md`

- [ ] The PR description or comment contains a picture of a cute animal (not mandatory but encouraged 😉)

<!-- Feel free to add additional comments, and to ask questions if some of those guidelines are unclear to you! -->

<!--
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
onto [text_annotation()](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.text_annotation)
- allowing correctly parsing of SVG files with CSS styling (`style="..."` attribute), thanks to @RedShy
- [`FPDF.star`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.star): new method added to draw regular stars, thanks to @digidigital and @RedShy
- allowing to change appearance of [highlight annotations](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.add_highlight) by specifying a [`TextMarkupType`](https://pyfpdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.TextMarkupType)
- documentation on how to control objects transparency: [link to docs](https://pyfpdf.github.io/fpdf2/Transparency.html)
- documentation on how to create tables and charts using [pandas](https://pandas.pydata.org/) DataFrames: [link to docs](https://pyfpdf.github.io/fpdf2/Maths.html), thanks to @iwayankurniawan

Expand Down
3 changes: 3 additions & 0 deletions docs/Annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ Rendering by Sumatra PDF reader:

Method documentation: [`FPDF.add_highlight`](https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.add_highlight)

The appearance of the "highlight effect" can be controlled through the `type` argument:
it can be `Highlight` (default), `Underline`, `Squiggly` or `StrikeOut`.


## Named actions ##

Expand Down
31 changes: 19 additions & 12 deletions docs/Barcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,28 @@ pdf.image(img, positionX, positionY, width, height)

### Extend FPDF with a datamatrix() method ###

The code above could be added to the FPDF class as an extension method in the following way.
The code above could be added to the FPDF class as an extension method in the following way:

```python
from fpdf import FPDF
from pystrich.datamatrix import DataMatrixEncoder, DataMatrixRenderer


def datamatrix(self, text='', x=0, y=0, w=57, h=57, cellsize=5):
"Convert text to a datamatrix barcode and put in on the page"
encoder = DataMatrixEncoder(text)
encoder.width = w
encoder.height = h
renderer = DataMatrixRenderer(encoder.matrix, encoder.regions)
img = renderer.get_pilimage(cellsize)
self.image(img, x, y, w, h)

FPDF.datamatrix = datamatrix
class PDF(FPDF):
def datamatrix(self, text, w, h=None, x=None, y=None, cellsize=5):
if x is None: x = self.x
if y is None: y = self.y
if h is None: h = w
encoder = DataMatrixEncoder(text)
encoder.width = w
encoder.height = h
renderer = DataMatrixRenderer(encoder.matrix, encoder.regions)
img = renderer.get_pilimage(cellsize)
self.image(img, x, y, w, h)

# Usage example:
pdf = PDF()
pdf.add_page()
pdf.set_font("Helvetica", size=24)
pdf.datamatrix("Hello world!", w=100)
pdf.output("datamatrix.pdf")
```
5 changes: 5 additions & 0 deletions docs/Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ About the original `PyFPDF` lib:
> you can still access the [old issues](https://github.com/reingart/pyfpdf_googlecode/issues),
> and [old wiki](https://github.com/reingart/pyfpdf_googlecode/tree/wiki).
As of version [2.5.4](https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md),
`fpdf2` is fully backward compatible with PyFPDF, with the exception of one minor point:
for the [`cell()` method](fpdf/fpdf.html#fpdf.fpdf.FPDF.cell), the default value of `h` has changed.
It used to be `0` and is now set to the current value of `FPDF.font_size`.


## Usage ##

Expand Down
8 changes: 4 additions & 4 deletions docs/Shapes.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,16 @@ pdf.add_page()
pdf.set_line_width(0.5)

pdf.set_fill_color(r=30, g=200, b=0)
pdf.regular_polygon(x=40, y=80, poly_width=30, rotate_degrees=270, num_sides=3, style="FD")
pdf.regular_polygon(x=40, y=80, polyWidth=30, rotateDegrees=270, numSides=3, style="FD")

pdf.set_fill_color(r=10, g=30, b=255)
pdf.regular_polygon(x=80, y=80, poly_width=30, rotate_degrees=135, num_sides=4, style="FD")
pdf.regular_polygon(x=80, y=80, polyWidth=30, rotateDegrees=135, numSides=4, style="FD")

pdf.set_fill_color(r=165, g=10, b=255)
pdf.regular_polygon(x=120, y=80, poly_width=30, rotate_degrees=198, num_sides=5, style="FD")
pdf.regular_polygon(x=120, y=80, polyWidth=30, rotateDegrees=198, numSides=5, style="FD")

pdf.set_fill_color(r=255, g=125, b=10)
pdf.regular_polygon(x=160, y=80, poly_width=30, rotate_degrees=270, num_sides=6, style="FD")
pdf.regular_polygon(x=160, y=80, polyWidth=30, rotateDegrees=270, numSides=6, style="FD")
pdf.output("regular_polygon.pdf")
```
![](regular_polygon.png)
Expand Down
2 changes: 1 addition & 1 deletion docs/UsageInWebAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Note that `FPDF` instance objects are not designed to be reusable:
**content cannot be added** once [`output()`](fpdf/fpdf.html#fpdf.fpdf.FPDF.output) has been called.

Hence, even if `FPDF` should be thread-safe, we recommend that you either **create an instance for every request**,
Hence, even if the `FPDF` class should be thread-safe, we recommend that you either **create an instance for every request**,
or if you want to use a global / shared object, to only store the bytes returned from `output()`.

## Django ##
Expand Down
9 changes: 1 addition & 8 deletions fpdf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,7 @@
from .deprecation import WarnOnDeprecatedModuleAttributes

FPDF_VERSION = _FPDF_VERSION
"""Current FPDF Version, also available via `__version__` (which is read by `setup.py`):
<pre>
>>> import fpdf
>>> fpdf.__version__
'2.2.0'
</pre>
"""
"Current FPDF Version, also available via `__version__`"

FPDF_FONT_DIR = _FPDF_FONT_DIR
"""This is the location of where to look for fonts."""
Expand Down
12 changes: 12 additions & 0 deletions fpdf/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,18 @@ class PageMode(CoerciveEnum):
"Attachments panel visible"


class TextMarkupType(CoerciveEnum):
"Subtype of a text markup annotation"

HIGHLIGHT = Name("Highlight")

UNDERLINE = Name("Underline")

SQUIGGLY = Name("Squiggly")

STRIKE_OUT = Name("StrikeOut")


class BlendMode(CoerciveEnum):
"""
An enumeration of the named standard named blend functions supported by PDF.
Expand Down
47 changes: 27 additions & 20 deletions fpdf/fpdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from contextlib import contextmanager
from datetime import datetime
from functools import wraps
from math import isclose
from os.path import splitext
from pathlib import Path
from typing import Callable, List, NamedTuple, Optional, Tuple, Union
Expand Down Expand Up @@ -55,6 +56,7 @@ class Image:
PageMode,
PathPaintRule,
RenderStyle,
TextMarkupType,
TextMode,
XPos,
YPos,
Expand Down Expand Up @@ -399,8 +401,7 @@ def __init__(
# We set their default values here.
self.font_family = "" # current font family
self.font_style = "" # current font style
self.font_size_pt = 12 # current font size in points
self.font_size = self.font_size_pt / self.k
self.font_size = 12 / self.k
self.font_stretching = 100 # current font stretching
self.underline = 0 # underlining flag
self.draw_color = self.DEFAULT_DRAW_COLOR
Expand Down Expand Up @@ -441,6 +442,10 @@ def __init__(
def _set_min_pdf_version(self, version):
self.pdf_version = max(self.pdf_version, version)

@property
def font_size_pt(self):
return self.font_size * self.k

@property
def unifontsubset(self):
return self.current_font.get("type") == "TTF"
Expand Down Expand Up @@ -1763,7 +1768,7 @@ def set_font(self, family=None, style="", size=0):
if (
self.font_family == family
and self.font_style == style
and self.font_size_pt == size
and isclose(self.font_size_pt, size)
):
return

Expand All @@ -1789,7 +1794,6 @@ def set_font(self, family=None, style="", size=0):
# Select it
self.font_family = family
self.font_style = style
self.font_size_pt = size
self.font_size = size / self.k
self.current_font = self.fonts[fontkey]
if self.page > 0:
Expand All @@ -1802,9 +1806,8 @@ def set_font_size(self, size):
Args:
size (float): font size in points
"""
if self.font_size_pt == size:
if isclose(self.font_size_pt, size):
return
self.font_size_pt = size
self.font_size = size / self.k
if self.page > 0:
if not self.current_font:
Expand Down Expand Up @@ -1942,7 +1945,9 @@ def add_action(self, action, x, y, w, h):
return annotation

@contextmanager
def add_highlight(self, text, title="", color=(1, 1, 0), modification_time=None):
def add_highlight(
self, text, title="", type="Highlight", color=(1, 1, 0), modification_time=None
):
"""
Context manager that adds a single highlight annotation based on the text lines inserted
inside its indented block.
Expand All @@ -1951,8 +1956,9 @@ def add_highlight(self, text, title="", color=(1, 1, 0), modification_time=None)
text (str): text of the annotation
title (str): the text label that shall be displayed in the title bar of the annotation’s
pop-up window when open and active. This entry shall identify the user who added the annotation.
type (fpdf.enums.TextMarkupType, str): "Highlight", "Underline", "Squiggly" or "StrikeOut".
color (tuple): a tuple of numbers in the range 0.0 to 1.0, representing a colour used for
the title bar of the annotation’s pop-up window
the title bar of the annotation’s pop-up window. Defaults to yellow.
modification_time (datetime): date and time when the annotation was most recently modified
"""
if self.record_text_quad_points:
Expand All @@ -1961,7 +1967,7 @@ def add_highlight(self, text, title="", color=(1, 1, 0), modification_time=None)
yield
for page, quad_points in self.text_quad_points.items():
self.add_text_markup_annotation(
"Highlight",
type,
text,
quad_points=quad_points,
title=title,
Expand All @@ -1987,7 +1993,7 @@ def add_text_markup_annotation(
Adds a text markup annotation on some quadrilateral areas of the page.
Args:
type (str): "Highlight", "Underline", "Squiggly" or "StrikeOut"
type (fpdf.enums.TextMarkupType, str): "Highlight", "Underline", "Squiggly" or "StrikeOut"
text (str): text of the annotation
quad_points (tuple): array of 8 × n numbers specifying the coordinates of n quadrilaterals
in default user space that comprise the region in which the link should be activated.
Expand All @@ -1996,12 +2002,11 @@ def add_text_markup_annotation(
title (str): the text label that shall be displayed in the title bar of the annotation’s
pop-up window when open and active. This entry shall identify the user who added the annotation.
color (tuple): a tuple of numbers in the range 0.0 to 1.0, representing a colour used for
the title bar of the annotation’s pop-up window
the title bar of the annotation’s pop-up window. Defaults to yellow.
modification_time (datetime): date and time when the annotation was most recently modified
page (int): index of the page where this annotation is added
"""
if type not in ("Highlight", "Underline", "Squiggly", "StrikeOut"):
raise ValueError(f"Invalid text markup annotation subtype: {type}")
type = TextMarkupType.coerce(type).value
if modification_time is None:
modification_time = datetime.now()
if page is None:
Expand Down Expand Up @@ -2061,23 +2066,25 @@ def text(self, x, y, txt=""):
s = f"q {self.text_color.pdf_repr().lower()} {s} Q"
self._out(s)
if self.record_text_quad_points:
unscaled_width = self.get_normalized_string_width_with_style(
txt, self.font_style
w = (
self.get_normalized_string_width_with_style(txt, self.font_style)
* self.font_stretching
/ 100
* self.font_size
/ 1000
)
if self.font_stretching != 100:
unscaled_width *= self.font_stretching / 100
w = unscaled_width * self.font_size / 1000
h = self.font_size
y -= 0.8 * h # same coefficient as in _render_styled_text_line()
self.text_quad_points[self.page].extend(
[
x * self.k,
(self.h - y) * self.k,
(x + w) * self.k,
(self.h - y) * self.k,
x * self.k,
(self.h - y + h) * self.k,
(self.h - y - h) * self.k,
(x + w) * self.k,
(self.h - y + h) * self.k,
(self.h - y - h) * self.k,
]
)

Expand Down
Binary file modified test/highlighted.pdf
Binary file not shown.
Binary file modified test/image/image_types/image_types_insert_jpg_jpxdecode.pdf
Binary file not shown.
4 changes: 3 additions & 1 deletion test/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ def test_highlighted(tmp_path):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=24)
with pdf.add_highlight("Highlight comment", modification_time=EPOCH):
with pdf.add_highlight(
"Highlight comment", type="Squiggly", modification_time=EPOCH
):
pdf.text(50, 50, "Line 1")
pdf.set_y(50)
pdf.multi_cell(w=30, txt="Line 2")
Expand Down

0 comments on commit f792cd8

Please sign in to comment.