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

Add support for hyphenation to wrapped text #668

Merged
merged 10 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Release date: UNRELEASED
### New features and improvements

* Added an option to the `reverse` command to also flip the line direction (#654)
* Added a `--hyphenate LANG` option to the `text` command (#668)

### Bug fixes

Expand Down
43 changes: 41 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ glcontext = { version = ">=2.3.2", optional = true } # 2.3.2 needed to fix #200
moderngl = { version = ">=5.6.2,!=5.7.1,!=5.7.2", optional = true } # see moderngl/moderngl#525
Pillow = { version = ">=9.0.1", optional = true }
PySide6 = { version = ">=6.4.0.1", optional = true }
pyphen = "^0.14.0"


[tool.poetry.group.dev.dependencies]
Expand Down
36 changes: 27 additions & 9 deletions vpype/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
from pathlib import Path
from typing import Callable

import pyphen

from .model import LineCollection

__all__ = ["FONT_NAMES", "text_line", "text_block"]
Expand Down Expand Up @@ -145,8 +147,12 @@ def text_line(
return lc


def _word_wrap(paragraph: str, width: float, measure_func: Callable[[str], float]):
def _word_wrap(
paragraph: str, width: float, lang: str | None, measure_func: Callable[[str], float]
):
"""Break text in multiple line."""
dic = pyphen.Pyphen(lang=lang) if lang else None
print(lang, dic)
result = []
for line in paragraph.split("\n"):
# handle empty lines
Expand All @@ -162,13 +168,23 @@ def _word_wrap(paragraph: str, width: float, measure_func: Callable[[str], float
for a, b in zip(fields[::2], fields[1::2]):
w = measure_func(x + a)
if w > width:
if x == "":
result.append(a)
continue
else:
result.append(x)
x = ""
x += a + b
for aa, ab in dic.iterate(a) if dic else (): # try hyphenating
xa = x + aa + "\u002D" # our fonts don't have a true hyphen
w = measure_func(xa)
if w <= width:
result.append(xa)
x = ab + b
break
else: # no fitting hyphenation
if x == "": # single word exceeds width
result.append(a)
continue
else:
result.append(x)
x = ""
x += a + b
else:
x += a + b
if x != "":
result.append(x + "\n")
return result
Expand All @@ -195,6 +211,7 @@ def text_block(
align: str = "left",
line_spacing: float = 1,
justify=False,
hyphenate=None,
) -> LineCollection:
"""Create a wrapped block of text using the provided width.

Expand All @@ -210,6 +227,7 @@ def text_block(
left alignment)
line_spacing: line spacing (default: 1.0)
justify: should the text be justified (default: False)
hyphenate: wrapped text is hyphenated with the given language (default: None)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to indicate what format(s) are acceptable for the language, and provide a couple of examples.

"""

font = _Font.get(font_name)
Expand All @@ -221,7 +239,7 @@ def measure(txt):
bounds = _text_line(txt, font).bounds()
return bounds[2] if bounds else 0.0

lines = _word_wrap(paragraph, width, measure)
lines = _word_wrap(paragraph, width, hyphenate, measure)

lc_arr = [
_justify_text(line, font, width)
Expand Down
18 changes: 16 additions & 2 deletions vpype_cli/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
)
@click.option("-s", "--size", type=LengthType(), default=18, help="Text size (default: 18).")
@click.option("-w", "--wrap", type=LengthType(), help="Wrap to provided width.")
@click.option(
"-h",
"--hyphenate",
type=TextType(),
help="Hyphenate wrapped words using the provided language.",
)
@click.option("-j", "--justify", is_flag=True, help="Justify text block (wrap-mode only).")
@click.option(
"-p",
Expand All @@ -38,6 +44,7 @@ def text(
font: str,
size: float,
wrap: float | None,
hyphenate: str | None,
justify: float,
position: tuple[float, float],
align: str,
Expand All @@ -53,7 +60,8 @@ def text(

In wrap mode, the text start at (0, 0) and expends left until it reach the specified width.
The `--align` option controls how the text is laid out within the column and behaves as
typically expected.
typically expected. The `--hyphenate` options enables hyphenation using the provided
language code.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.


To start the text at the different location than (0, 0), use the `--position` option.
"""
Expand All @@ -64,7 +72,13 @@ def text(

if wrap:
lc = vp.text_block(
string, font_name=font, width=wrap, size=size, align=align, justify=justify
string,
font_name=font,
width=wrap,
size=size,
align=align,
justify=justify,
hyphenate=hyphenate,
)
else:
lc = vp.text_line(string, font_name=font, size=size, align=align)
Expand Down