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 special case handling for layout tight #556

Merged
merged 2 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -6,6 +6,7 @@ Release date: UNRELEASED

### New features and improvements

* The `layout` command now properly handles the `tight` special case by fitting the page size around the existing geometries, accommodating for a margin if provided (#556)
* Added new units (`yd`, `mi`, and `km`) (#541)
* Added `inch` unit as a synonym to `in`, useful for expressions (in which `in` is a reserved keyword) (#541)
* Migrated to PySide6 (from PySide2), which simplifies installation on Apple silicon Macs (#552)
Expand Down
46 changes: 33 additions & 13 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,23 +546,43 @@ def test_splitall_filter_duplicates(line, expected):
("-m 3cm -h right 10x20cm", (3, 8, 7, 12)),
("-m 3cm -h right -l 20x10cm", (13, 3, 17, 7)),
("-m 3cm -h right -l 10x20cm", (13, 3, 17, 7)),
("tight", (0, 0, 1, 1)),
("-m 1cm tight", (1, 1, 2, 2)),
],
)
def test_layout(runner, args, expected_bounds):
document = vp.Document()
def test_layout(args, expected_bounds):
doc = vpype_cli.execute(f"random -n 100 rect 0 0 1cm 1cm layout {args}")
assert doc.bounds() == pytest.approx([i * CM for i in expected_bounds])

@cli.command()
@global_processor
def sample(doc: vp.Document):
nonlocal document
document = doc

res = runner.invoke(cli, f"random -n 100 rect 0 0 1cm 1cm layout {args} sample")
assert res.exit_code == 0
bounds = document.bounds()
assert bounds is not None
for act, exp in zip(bounds, expected_bounds):
assert act == pytest.approx(exp * CM)
def test_layout_tight():
"""`layout tight` fits tightly the page size around the geometry, accommodating for margins
if provided"""

doc = vpype_cli.execute("rect 5cm 10cm 2cm 3cm layout tight")
assert doc.bounds() == pytest.approx((0, 0, 2 * CM, 3 * CM))
assert doc.page_size == pytest.approx((2 * CM, 3 * CM))

doc = vpype_cli.execute("rect 5cm 10cm 2cm 3cm layout -m 1cm tight")
assert doc.bounds() == pytest.approx((CM, CM, 3 * CM, 4 * CM))
assert doc.page_size == pytest.approx((4 * CM, 5 * CM))


def test_layout_empty():
"""page size is set to size provided to layout, unless it's tight, in which case it is
unchanged"""

doc = vpype_cli.execute("layout 10x12cm")
assert doc.page_size == pytest.approx((10 * CM, 12 * CM))

doc = vpype_cli.execute("pagesize a3 layout 10x12cm")
assert doc.page_size == pytest.approx((10 * CM, 12 * CM))

doc = vpype_cli.execute("layout tight")
assert doc.page_size is None

doc = vpype_cli.execute("pagesize 10x12cm layout tight")
assert doc.page_size == pytest.approx((10 * CM, 12 * CM))


@pytest.mark.parametrize("font_name", vp.FONT_NAMES)
Expand Down
30 changes: 26 additions & 4 deletions vpype_cli/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ def pagesize(document: vp.Document, size, landscape) -> vp.Document:

{', '.join(vp.PAGE_SIZES.keys())}

Note that `tight` is special case, see below.

Alternatively, a custom size can be specified in the form of WIDTHxHEIGHT. WIDTH and
HEIGHT may include units. If only one has an unit, the other is assumed to have the
same unit. If none have units, both are assumed to be pixels by default. Here are some
Expand All @@ -531,13 +533,24 @@ def pagesize(document: vp.Document, size, landscape) -> vp.Document:
Optionally, this command can scale the geometries to fit specified margins with the
`--fit-to-margins` option.

When SIZE is `tight`, the page size is set to fit the width and height of the existing
geometries. If `--fit-to-margins` is used, the page size is increased to accommodate the
margin. By construction, `--align` and `--valign` have no effect with `tight`.

On an empty pipeline, `layout` simply sets the page size to SIZE, unless `tight` is used. In
this case, `layout` has no effect at all.

Examples:

\b
Fit the geometries to 3cm margins with top alignment (a generally
pleasing arrangement for square designs on portrait-oriented pages):

vpype read input.svg layout --fit-to-margins 3cm --valign top a4 write.svg
vpype read input.svg layout --fit-to-margins 3cm --valign top a4 write output.svg

Set the page size to the geometries' boundary, with a 1cm margin:

vpype read input.svg layout --fit-to-margins 3cm tight write output.svg
"""


Expand Down Expand Up @@ -578,17 +591,26 @@ def layout(
"""Layout command"""

size = _normalize_page_size(size, landscape)

document.page_size = size
bounds = document.bounds()
tight = size == vp.PAGE_SIZES["tight"]

# handle empty geometry special cases
if bounds is None:
# nothing to layout
if not tight:
document.page_size = size
return document

min_x, min_y, max_x, max_y = bounds
width = max_x - min_x
height = max_y - min_y

# handle "tight" special case
if tight:
extra = 2 * (margin or 0.0)
size = width + extra, height + extra

document.page_size = size

if margin is not None:
document.translate(-min_x, -min_y)
scale = min((size[0] - 2 * margin) / width, (size[1] - 2 * margin) / height)
Expand Down