Skip to content

Commit

Permalink
Support for striketrough text
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas-C committed Jan 8, 2025
1 parent 94dbb86 commit cc96d1a
Show file tree
Hide file tree
Showing 18 changed files with 160 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This can also be enabled programmatically with `warnings.simplefilter('default',
## [2.8.3] - Not released yet
### Added
* support for [shading patterns (gradients)](https://py-pdf.github.io/fpdf2/Patterns.html)
* support for strikethrough text
### Fixed
* [`FPDF.write_html()`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.write_html): Fixed rendering of content following `<a>` tags; now correctly resets emphasis style post `</a>` tag: hyperlink styling contained within the tag authority. - [Issue #1311](https://github.com/py-pdf/fpdf2/issues/1311)

Expand Down
15 changes: 5 additions & 10 deletions docs/DocumentOutlineAndTableOfContents.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# Document Outline & Table of Contents

## Overview

This document explains how to implement and customize the Document Outline (also known as Bookmarks) and Table of Contents (ToC) features in `fpdf2`.

---

## Document Outline (Bookmarks)

Document outlines allow users to navigate quickly through sections in the PDF by creating a hierarchical structure of clickable links.

Quoting the 6th edition of the PDF format reference (v1.7 - 2006) :
Expand All @@ -27,7 +25,6 @@ However, you can configure **global title styles** by calling [`set_section_titl
To provide a document outline to the PDF you generate, you just have to call the `start_section` method for every hierarchical section you want to define.

### Nested outlines

Outlines can be nested by specifying different levels. Higher-level outlines (e.g., level 0) appear at the top, while sub-levels (e.g., level 1, level 2) are indented.

```python
Expand All @@ -38,23 +35,21 @@ pdf.start_section(name="Section 1.1: Background", level=1)
---

## Table of Contents

Quoting [Wikipedia](https://en.wikipedia.org/wiki/Table_of_contents), a **table of contents** is:
> a list, usually found on a page before the start of a written work, of its chapter or section titles or brief descriptions with their commencing page numbers.
### Inserting a Table of Contents

Use the [`insert_toc_placeholder`](fpdf/fpdf.html#fpdf.fpdf.FPDF.insert_toc_placeholder) method to define a placeholder for the ToC. A page break is triggered after inserting the ToC.

**Parameters:**
Parameters:

- **render_toc_function**: Function called to render the ToC, receiving two parameters: `pdf`, an FPDF instance, and `outline`, a list of `fpdf.outline.OutlineSection`.
- **pages**: The number of pages that the ToC will span, including the current one. A page break occurs for each page specified.
- **allow_extra_pages**: If `True`, allows unlimited additional pages to be added to the ToC as needed. These extra ToC pages are initially created at the end of the document and then reordered when the final PDF is produced.

**Note**: Enabling `allow_extra_pages` may affect page numbering for headers or footers. Since extra ToC pages are added after the document content, they might cause page numbers to appear out of sequence. To maintain consistent numbering, use (Page Labels)[PageLabels.md] to assign a specific numbering style to the ToC pages. When using Page Labels, any extra ToC pages will follow the numbering style of the first ToC page.

### Reference Implementation

_New in [:octicons-tag-24: 2.8.2](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)_

The `fpdf.outline.TableOfContents` class provides a reference implementation of the ToC, which can be used as-is or subclassed.
Expand All @@ -72,7 +67,6 @@ pdf.insert_toc_placeholder(toc.render_toc, allow_extra_pages=True)
---

## Using Outlines and ToC with HTML

When using [`FPDF.write_html`](HTML.md), a document outline is automatically generated, and a ToC can be added with the `<toc>` tag.

To customize ToC styling, override the `render_toc` method in a subclass:
Expand Down Expand Up @@ -105,7 +99,6 @@ pdf.output("html_toc.pdf")
---

## Additional Code Samples

The regression tests are a good place to find code samples.

For example, the [`test_simple_outline`](https://github.com/py-pdf/fpdf2/blob/master/test/outline/test_outline.py) test function generates the PDF document [simple_outline.pdf](https://github.com/py-pdf/fpdf2/blob/master/test/outline/simple_outline.pdf).
Expand All @@ -116,4 +109,6 @@ generates [test_html_toc.pdf](https://github.com/py-pdf/fpdf2/blob/5453422bf560a
---

## Manually Adjusting `pdf.page`
Setting `pdf.page` manually may result in unexpected behavior. `pdf.add_page()` takes special care to ensure the page's content stream matches fpdf's instance attributes. Manually setting the page does not.
⚠️ Setting `pdf.page` manually may result in unexpected behavior.
`pdf.add_page()` takes special care to ensure the page's content stream matches fpdf's instance attributes.
Manually setting the page does not.
2 changes: 1 addition & 1 deletion docs/HTML.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ pdf.output("html_helvetica.pdf")
* `<h1>` to `<h6>`: headings (and `align` attribute)
* `<p>`: paragraphs (and `align`, `line-height` attributes)
* `<br>` & `<hr>` tags
* `<b>`, `<i>`, `<u>`: bold, italic, underline
* `<b>`, `<i>`, `<s>`, `<u>`: bold, italic, strikethrough, underline
* `<font>`: (and `face`, `size`, `color` attributes)
* `<center>` for aligning
* `<a>`: links (and `href` attribute) to a file, URL, or page number.
Expand Down
1 change: 1 addition & 0 deletions docs/PageLabels.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ _New in [:octicons-tag-24: 2.8.2](https://github.com/py-pdf/fpdf2/blob/master/CH
In a PDF document, each page is identified by an integer page index, representing the page's position within the document. Optionally, a document can also define **page labels** to visually display page identifiers.

**Page labels** can be customized. For example, a document might begin with front matter numbered in roman numerals and transition to arabic numerals for the main content. In this case:

- The first page (index `0`) would have a label `i`
- The twelfth page (index `11`) would have label `xii`
- The thirteenth page (index `12`) would start with label `1`
Expand Down
3 changes: 1 addition & 2 deletions docs/Text.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ character string. The upper-left corner of the cell corresponds to the current
position. The text can be aligned or centered. After the call, the current
position moves to the selected `new_x`/`new_y` position. It is possible to put a link on the text.
If `markdown=True`, then minimal [markdown](TextStyling.md#markdowntrue)
styling is enabled, to render parts of the text in bold, italics, and/or
underlined.
styling is enabled, to render parts of the text in bold, italics, strikethrough and/or underlined.

If automatic page breaking is enabled and the cell goes beyond the limit, a
page break is performed before outputting.
Expand Down
4 changes: 3 additions & 1 deletion docs/TextStyling.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ Setting emphasis on text can be controlled by using `.set_font(style=...)`:

* `style="B"` indicates **bold**
* `style="I"` indicates _italics_
* `style="S"` indicates <s>strikethrough</s>
* `style="U"` indicates <u>underline</u>
* `style="BI"` indicates _**bold italics**_

Letters can be combined, for example: `style="BI"` indicates _**bold italics**_

```python
from fpdf import FPDF
Expand Down
7 changes: 6 additions & 1 deletion docs/Unicode.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ pdf.add_font("dejavu-sans-narrow", style="", fname="DejaVuSansCondensed.ttf")
pdf.add_font("dejavu-sans-narrow", style="i", fname="DejaVuSansCondensed-Oblique.ttf")
```

To actually use the loaded font, or to use one of the standard built-in fonts, you'll have to set the current font before calling any text generating method. `.set_font()` uses the same combinations of family name and style as arguments, plus the font size in typographic points. In addition to the previously mentioned styles, the letter "u" may be included for creating underlined text. If the family or size are omitted, the already set values will be retained. If the style is omitted, it defaults to regular.
To actually use the loaded font, or to use one of the standard built-in fonts, you'll have to set the current font before calling any text generating method.
`.set_font()` uses the same combinations of family name and style as arguments, plus the font size in typographic points.
In addition to the previously mentioned styles, the letter `u` may be included for creating underlined text,
and `s` for creating strikethrough text.
If the family or size are omitted, the already set values will be retained.
If the style is omitted, it defaults to regular.

```python
# Set and use first family in regular style.
Expand Down
1 change: 0 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ Online classes & open source projects:
## Misc ##

* Release notes for every versions of `fpdf2`: [CHANGELOG.md](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)
* Project history: [History](History.md)
* This library could only exist thanks to the dedication of many volunteers around the world:
[list & map of contributors](https://github.com/py-pdf/fpdf2/blob/master/README.md#contributors-)
* You can download an offline PDF version of this manual: [fpdf2-manual.pdf](fpdf2-manual.pdf)
5 changes: 5 additions & 0 deletions fpdf/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ class TextEmphasis(CoerciveIntFlag):
U = 4
"Underline"

S = 8
"Strikethrough"

@property
def style(self):
return "".join(
Expand All @@ -268,6 +271,8 @@ def coerce(cls, value):
return cls.I
if value.upper() == "UNDERLINE":
return cls.U
if value.upper() == "STRIKETHROUGH":
return cls.S
return super(cls, cls).coerce(value)


Expand Down
42 changes: 31 additions & 11 deletions fpdf/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,27 @@ def __init__(self, *args, **kwargs):

class CoreFont:
# RAM usage optimization:
__slots__ = ("i", "type", "name", "up", "ut", "cw", "fontkey", "emphasis")
__slots__ = (
"i",
"type",
"name",
"sp",
"ss",
"up",
"ut",
"cw",
"fontkey",
"emphasis",
)

def __init__(self, fpdf, fontkey, style):
self.i = len(fpdf.fonts) + 1
self.type = "core"
self.name = CORE_FONTS[fontkey]
self.up = -100
self.ut = 50
self.sp = 250 # strikethrough horizontal position
self.ss = 50 # strikethrough size (height)
self.up = -100 # underline horizontal position
self.ut = 50 # underline height
self.cw = CORE_FONTS_CHARWIDTHS[fontkey]
self.fontkey = fontkey
self.emphasis = TextEmphasis.coerce(style)
Expand All @@ -226,6 +239,8 @@ class TTFFont:
"desc",
"glyph_ids",
"hbfont",
"sp",
"ss",
"up",
"ut",
"cw",
Expand Down Expand Up @@ -254,18 +269,21 @@ def __init__(self, fpdf, font_file_path, fontkey, style):
self.scale = 1000 / self.ttfont["head"].unitsPerEm
default_width = round(self.scale * self.ttfont["hmtx"].metrics[".notdef"][0])

os2_table = self.ttfont["OS/2"]
post_table = self.ttfont["post"]

try:
cap_height = self.ttfont["OS/2"].sCapHeight
cap_height = os2_table.sCapHeight
except AttributeError:
cap_height = self.ttfont["hhea"].ascent

# entry for the PDF font descriptor specifying various characteristics of the font
flags = FontDescriptorFlags.SYMBOLIC
if self.ttfont["post"].isFixedPitch:
if post_table.isFixedPitch:
flags |= FontDescriptorFlags.FIXED_PITCH
if self.ttfont["post"].italicAngle != 0:
if post_table.italicAngle != 0:
flags |= FontDescriptorFlags.ITALIC
if self.ttfont["OS/2"].usWeightClass >= 600:
if os2_table.usWeightClass >= 600:
flags |= FontDescriptorFlags.FORCE_BOLD

self.desc = PDFFontDescriptor(
Expand All @@ -277,8 +295,8 @@ def __init__(self, fpdf, font_file_path, fontkey, style):
f"[{self.ttfont['head'].xMin * self.scale:.0f} {self.ttfont['head'].yMin * self.scale:.0f}"
f" {self.ttfont['head'].xMax * self.scale:.0f} {self.ttfont['head'].yMax * self.scale:.0f}]"
),
italic_angle=int(self.ttfont["post"].italicAngle),
stem_v=round(50 + int(pow((self.ttfont["OS/2"].usWeightClass / 65), 2))),
italic_angle=int(post_table.italicAngle),
stem_v=round(50 + int(pow((os2_table.usWeightClass / 65), 2))),
missing_width=default_width,
)

Expand Down Expand Up @@ -320,8 +338,10 @@ def __init__(self, fpdf, font_file_path, fontkey, style):
sbarr += fpdf.str_alias_nb_pages

self.name = re.sub("[ ()]", "", self.ttfont["name"].getBestFullName())
self.up = round(self.ttfont["post"].underlinePosition * self.scale)
self.ut = round(self.ttfont["post"].underlineThickness * self.scale)
self.up = round(post_table.underlinePosition * self.scale)
self.ut = round(post_table.underlineThickness * self.scale)
self.sp = round(os2_table.yStrikeoutPosition * self.scale)
self.ss = round(os2_table.yStrikeoutSize * self.scale)
self.emphasis = TextEmphasis.coerce(style)
self.subset = SubsetMap(self, [ord(char) for char in sbarr])

Expand Down
Loading

0 comments on commit cc96d1a

Please sign in to comment.