-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-- Copyright 2026 Shota FUJI
--
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
--
-- SPDX-License-Identifier: MPL-2.0
module Template.Layout.Packer exposing (pack)
import List
import Svg exposing (..)
import Svg.Attributes as Attrs
import Template.Layout.Coordinate exposing (..)
import Template.Layout.Item exposing (Item)
{-| Packs multiple items into specified sized containers.
This function packs given `items` using stupid bin packing algorithm so they won't
overlap or overflow. As we're dealing with printable leather template, we can
afford multiple containers to be packed.
Items larger than the container will be placed as-is, and won't change container's
size. Caller is responsible for pre-splitting items prior to passing them to this
function if a piece is expected to be larger than the container.
This function treats each item's `AtLeast` size request as `Exactly`.
-}
pack : SizeRequest -> Float -> List (Item msg) -> List (Item msg)
pack container gap items =
case items of
[] ->
[]
_ ->
items
-- Clip items larger than the container, otherwise `packPages` goes into
-- infinite recursion.
|> List.map
(\item -> { item | size = clip item.size container })
|> List.sortBy
(\item ->
let
{ width, height } =
minSizeOf item.size
in
-(max width height)
)
|> packPages container gap
packPages : SizeRequest -> Float -> List (Item msg) -> List (Item msg)
packPages size gap items =
case items of
[] ->
[]
_ ->
let
( children, rest ) =
tryPackInto { x = 0, y = 0 } (minSizeOf size) gap items
in
{ size = size
, element =
\p s ->
children
|> List.map (\item -> item.element p s)
|> g []
}
:: packPages size gap rest
{-| Tries to pack items into a rectangular, and returns packed items and items not fit
into the rectangular.
-}
tryPackInto : Point2D -> Size -> Float -> List (Item msg) -> ( List (Item msg), List (Item msg) )
tryPackInto p size gap items =
case items of
[] ->
( [], [] )
item :: xs ->
let
itemSize =
minSizeOf item.size
in
if contains size itemSize then
let
( topItems, rest ) =
tryPackInto
{ x = p.x + itemSize.width + gap, y = p.y }
{ width = size.width - itemSize.width - gap, height = itemSize.height }
gap
xs
|> Tuple.mapFirst
((::)
{ size = item.size
, element =
\q _ ->
item.element { x = p.x + q.x, y = p.y + q.y } itemSize
}
)
in
tryPackInto
{ x = p.x, y = p.y + itemSize.height + gap }
{ width = size.width, height = size.height - itemSize.height - gap }
gap
rest
|> Tuple.mapFirst
((++) topItems)
else if contains { width = size.height, height = size.width } itemSize then
let
( topItems, rest ) =
tryPackInto
{ x = p.x + itemSize.height + gap, y = p.y }
{ width = size.width - itemSize.height - gap, height = itemSize.width }
gap
xs
|> Tuple.mapFirst
((::)
{ size = item.size
, element =
\q _ ->
let
ox =
p.x + q.x
oy =
p.y + q.y
in
g
[ Attrs.transform
((if itemSize.height > itemSize.width then
[ "rotate(-90 " ++ String.fromFloat ox ++ " " ++ String.fromFloat oy ++ ")"
, "translate(" ++ String.fromFloat -itemSize.width ++ " 0)"
]
else
[ "rotate(90 " ++ String.fromFloat ox ++ " " ++ String.fromFloat (oy + itemSize.height) ++ ")"
, "translate(" ++ String.fromFloat -itemSize.height ++ " 0)"
]
)
|> String.join " "
)
]
[ item.element { x = ox, y = oy } itemSize ]
}
)
in
tryPackInto
{ x = p.x, y = p.y + itemSize.width + gap }
{ width = size.width, height = size.height - itemSize.width - gap }
gap
rest
|> Tuple.mapFirst
((++) topItems)
else
tryPackInto p size gap xs
|> Tuple.mapSecond ((::) item)