summaryrefslogtreecommitdiff
path: root/scss/extension/compass/layouts.py
diff options
context:
space:
mode:
Diffstat (limited to 'scss/extension/compass/layouts.py')
-rw-r--r--scss/extension/compass/layouts.py347
1 files changed, 347 insertions, 0 deletions
diff --git a/scss/extension/compass/layouts.py b/scss/extension/compass/layouts.py
new file mode 100644
index 0000000..ae086ce
--- /dev/null
+++ b/scss/extension/compass/layouts.py
@@ -0,0 +1,347 @@
+"""Functions used for generating packed CSS sprite maps.
+
+These are ported from the Binary Tree Bin Packing Algorithm:
+http://codeincomplete.com/posts/2011/5/7/bin_packing/
+"""
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+# Copyright (c) 2011, 2012, 2013 Jake Gordon and contributors
+# Copyright (c) 2013 German M. Bravo
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+class LayoutNode(object):
+ def __init__(self, x, y, w, h, down=None, right=None, used=False):
+ self.x = x
+ self.y = y
+ self.w = w
+ self.h = h
+ self.down = down
+ self.right = right
+ self.used = used
+ self.width = 0
+ self.height = 0
+
+ @property
+ def area(self):
+ return self.width * self.height
+
+ def __repr__(self):
+ return '<%s (%s, %s) [%sx%s]>' % (self.__class__.__name__, self.x, self.y, self.w, self.h)
+
+
+class SpritesLayout(object):
+ def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None):
+ self.num_blocks = len(blocks)
+
+ if margin is None:
+ margin = [[0] * 4] * self.num_blocks
+ elif not isinstance(margin, (tuple, list)):
+ margin = [[margin] * 4] * self.num_blocks
+ elif not isinstance(margin[0], (tuple, list)):
+ margin = [margin] * self.num_blocks
+
+ if padding is None:
+ padding = [[0] * 4] * self.num_blocks
+ elif not isinstance(padding, (tuple, list)):
+ padding = [[padding] * 4] * self.num_blocks
+ elif not isinstance(padding[0], (tuple, list)):
+ padding = [padding] * self.num_blocks
+
+ if pmargin is None:
+ pmargin = [[0.0] * 4] * self.num_blocks
+ elif not isinstance(pmargin, (tuple, list)):
+ pmargin = [[pmargin] * 4] * self.num_blocks
+ elif not isinstance(pmargin[0], (tuple, list)):
+ pmargin = [pmargin] * self.num_blocks
+
+ if ppadding is None:
+ ppadding = [[0.0] * 4] * self.num_blocks
+ elif not isinstance(ppadding, (tuple, list)):
+ ppadding = [[ppadding] * 4] * self.num_blocks
+ elif not isinstance(ppadding[0], (tuple, list)):
+ ppadding = [ppadding] * self.num_blocks
+
+ self.blocks = tuple((
+ b[0] + padding[i][3] + padding[i][1] + margin[i][3] + margin[i][1] + int(round(b[0] * (ppadding[i][3] + ppadding[i][1] + pmargin[i][3] + pmargin[i][1]))),
+ b[1] + padding[i][0] + padding[i][2] + margin[i][0] + margin[i][2] + int(round(b[1] * (ppadding[i][0] + ppadding[i][2] + pmargin[i][0] + pmargin[i][2]))),
+ b[0],
+ b[1],
+ i
+ ) for i, b in enumerate(blocks))
+
+ self.margin = margin
+ self.padding = padding
+ self.pmargin = pmargin
+ self.ppadding = ppadding
+
+
+class PackedSpritesLayout(SpritesLayout):
+ @staticmethod
+ def MAXSIDE(a, b):
+ """maxside: Sort pack by maximum sides"""
+ return cmp(max(b[0], b[1]), max(a[0], a[1])) or cmp(min(b[0], b[1]), min(a[0], a[1])) or cmp(b[1], a[1]) or cmp(b[0], a[0])
+
+ @staticmethod
+ def WIDTH(a, b):
+ """width: Sort pack by width"""
+ return cmp(b[0], a[0]) or cmp(b[1], a[1])
+
+ @staticmethod
+ def HEIGHT(a, b):
+ """height: Sort pack by height"""
+ return cmp(b[1], a[1]) or cmp(b[0], a[0])
+
+ @staticmethod
+ def AREA(a, b):
+ """area: Sort pack by area"""
+ return cmp(b[0] * b[1], a[0] * a[1]) or cmp(b[1], a[1]) or cmp(b[0], a[0])
+
+ def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, methods=None):
+ super(PackedSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin)
+
+ ratio = 0
+
+ if methods is None:
+ methods = (self.MAXSIDE, self.WIDTH, self.HEIGHT, self.AREA)
+
+ for method in methods:
+ sorted_blocks = sorted(
+ self.blocks,
+ cmp=method,
+ )
+ root = LayoutNode(
+ x=0,
+ y=0,
+ w=sorted_blocks[0][0] if sorted_blocks else 0,
+ h=sorted_blocks[0][1] if sorted_blocks else 0
+ )
+
+ area = 0
+ nodes = [None] * self.num_blocks
+
+ for block in sorted_blocks:
+ w, h, width, height, i = block
+ node = self._findNode(root, w, h)
+ if node:
+ node = self._splitNode(node, w, h)
+ else:
+ root = self._growNode(root, w, h)
+ node = self._findNode(root, w, h)
+ if node:
+ node = self._splitNode(node, w, h)
+ else:
+ node = None
+ nodes[i] = node
+ node.width = width
+ node.height = height
+ area += node.area
+
+ this_ratio = area / float(root.w * root.h)
+ # print method.__doc__, "%g%%" % (this_ratio * 100)
+ if ratio < this_ratio:
+ self.root = root
+ self.nodes = nodes
+ self.method = method
+ ratio = this_ratio
+ if ratio > 0.96:
+ break
+ # print self.method.__doc__, "%g%%" % (ratio * 100)
+
+ def __iter__(self):
+ for i, node in enumerate(self.nodes):
+ margin, padding = self.margin[i], self.padding[i]
+ pmargin, ppadding = self.pmargin[i], self.ppadding[i]
+ cssw = node.width + padding[3] + padding[1] + int(round(node.width * (ppadding[3] + ppadding[1]))) # image width plus padding
+ cssh = node.height + padding[0] + padding[2] + int(round(node.height * (ppadding[0] + ppadding[2]))) # image height plus padding
+ cssx = node.x + margin[3] + int(round(node.width * pmargin[3]))
+ cssy = node.y + margin[0] + int(round(node.height * pmargin[0]))
+ x = cssx + padding[3] + int(round(node.width * ppadding[3]))
+ y = cssy + padding[0] + int(round(node.height * ppadding[0]))
+ yield x, y, node.width, node.height, cssx, cssy, cssw, cssh
+
+ @property
+ def width(self):
+ return self.root.w
+
+ @property
+ def height(self):
+ return self.root.h
+
+ def _findNode(self, root, w, h):
+ if root.used:
+ return self._findNode(root.right, w, h) or self._findNode(root.down, w, h)
+ elif w <= root.w and h <= root.h:
+ return root
+ else:
+ return None
+
+ def _splitNode(self, node, w, h):
+ node.used = True
+ node.down = LayoutNode(
+ x=node.x,
+ y=node.y + h,
+ w=node.w,
+ h=node.h - h
+ )
+ node.right = LayoutNode(
+ x=node.x + w,
+ y=node.y,
+ w=node.w - w,
+ h=h
+ )
+ return node
+
+ def _growNode(self, root, w, h):
+ canGrowDown = w <= root.w
+ canGrowRight = h <= root.h
+
+ shouldGrowRight = canGrowRight and (root.h >= root.w + w) # attempt to keep square-ish by growing right when height is much greater than width
+ shouldGrowDown = canGrowDown and (root.w >= root.h + h) # attempt to keep square-ish by growing down when width is much greater than height
+
+ if shouldGrowRight:
+ return self._growRight(root, w, h)
+ elif shouldGrowDown:
+ return self._growDown(root, w, h)
+ elif canGrowRight:
+ return self._growRight(root, w, h)
+ elif canGrowDown:
+ return self._growDown(root, w, h)
+ else:
+ # need to ensure sensible root starting size to avoid this happening
+ assert False, "Blocks must be properly sorted!"
+
+ def _growRight(self, root, w, h):
+ root = LayoutNode(
+ used=True,
+ x=0,
+ y=0,
+ w=root.w + w,
+ h=root.h,
+ down=root,
+ right=LayoutNode(
+ x=root.w,
+ y=0,
+ w=w,
+ h=root.h
+ )
+ )
+ return root
+
+ def _growDown(self, root, w, h):
+ root = LayoutNode(
+ used=True,
+ x=0,
+ y=0,
+ w=root.w,
+ h=root.h + h,
+ down=LayoutNode(
+ x=0,
+ y=root.h,
+ w=root.w,
+ h=h
+ ),
+ right=root
+ )
+ return root
+
+
+class HorizontalSpritesLayout(SpritesLayout):
+ def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, position=None):
+ super(HorizontalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin)
+
+ self.width = sum(block[0] for block in self.blocks)
+ self.height = max(block[1] for block in self.blocks)
+
+ if position is None:
+ position = [0.0] * self.num_blocks
+ elif not isinstance(position, (tuple, list)):
+ position = [position] * self.num_blocks
+ self.position = position
+
+ def __iter__(self):
+ cx = 0
+ for i, block in enumerate(self.blocks):
+ w, h, width, height, i = block
+ margin, padding = self.margin[i], self.padding[i]
+ pmargin, ppadding = self.pmargin[i], self.ppadding[i]
+ position = self.position[i]
+ cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding
+ cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding
+ cssx = cx + margin[3] + int(round(width * pmargin[3])) # anchored at x
+ cssy = int(round((self.height - cssh) * position)) # centered vertically
+ x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding
+ y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding
+ yield x, y, width, height, cssx, cssy, cssw, cssh
+ cx += cssw + margin[3] + margin[1] + int(round(width * (pmargin[3] + pmargin[1])))
+
+
+class VerticalSpritesLayout(SpritesLayout):
+ def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, position=None):
+ super(VerticalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin)
+
+ self.width = max(block[0] for block in self.blocks)
+ self.height = sum(block[1] for block in self.blocks)
+
+ if position is None:
+ position = [0.0] * self.num_blocks
+ elif not isinstance(position, (tuple, list)):
+ position = [position] * self.num_blocks
+ self.position = position
+
+ def __iter__(self):
+ cy = 0
+ for i, block in enumerate(self.blocks):
+ w, h, width, height, i = block
+ margin, padding = self.margin[i], self.padding[i]
+ pmargin, ppadding = self.pmargin[i], self.ppadding[i]
+ position = self.position[i]
+ cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding
+ cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding
+ cssx = int(round((self.width - cssw) * position)) # centered horizontally
+ cssy = cy + margin[0] + int(round(height * pmargin[0])) # anchored at y
+ x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding
+ y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding
+ yield x, y, width, height, cssx, cssy, cssw, cssh
+ cy += cssh + margin[0] + margin[2] + int(round(height * (pmargin[0] + pmargin[2])))
+
+
+class DiagonalSpritesLayout(SpritesLayout):
+ def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None):
+ super(DiagonalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin)
+ self.width = sum(block[0] for block in self.blocks)
+ self.height = sum(block[1] for block in self.blocks)
+
+ def __iter__(self):
+ cx, cy = 0, 0
+ for i, block in enumerate(self.blocks):
+ w, h, width, height, i = block
+ margin, padding = self.margin[i], self.padding[i]
+ pmargin, ppadding = self.pmargin[i], self.ppadding[i]
+ cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding
+ cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding
+ cssx = cx + margin[3] + int(round(width * pmargin[3])) # anchored at x
+ cssy = cy + margin[0] + int(round(height * pmargin[0])) # anchored at y
+ x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding
+ y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding
+ yield x, y, width, height, cssx, cssy, cssw, cssh
+ cx += cssw + margin[3] + margin[1] + int(round(width * (pmargin[3] + pmargin[1])))
+ cy += cssh + margin[0] + margin[2] + int(round(height * (pmargin[0] + pmargin[2])))