COLRv1: support unlimited paints as 255-ary tree of PaintColrLayers
Fixes https://github.com/googlefonts/nanoemoji/pull/225 E.g. BASKET noto-emoji U+1F9FA contains 364 layers
This commit is contained in:
parent
a8d366e3b2
commit
9d33afe04d
@ -6,6 +6,7 @@ import collections
|
||||
import copy
|
||||
import enum
|
||||
from functools import partial
|
||||
from math import ceil, log
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@ -632,7 +633,10 @@ class LayerV1ListBuilder:
|
||||
ot_paint.Format = int(ot.Paint.Format.PaintColrLayers)
|
||||
self.slices.append(ot_paint)
|
||||
|
||||
paints = [self.buildPaint(p) for p in paints]
|
||||
paints = [
|
||||
self.buildPaint(p)
|
||||
for p in _build_n_ary_tree(paints, n=MAX_PAINT_COLR_LAYER_COUNT)
|
||||
]
|
||||
|
||||
# Look for reuse, with preference to longer sequences
|
||||
found_reuse = True
|
||||
@ -776,3 +780,45 @@ def buildColrV1(
|
||||
glyphs.BaseGlyphCount = len(baseGlyphs)
|
||||
glyphs.BaseGlyphV1Record = baseGlyphs
|
||||
return (layers, glyphs)
|
||||
|
||||
|
||||
def _build_n_ary_tree(leaves, n):
|
||||
"""Build N-ary tree from sequence of leaf nodes.
|
||||
|
||||
Return a list of lists where each non-leaf node is a list containing
|
||||
max n nodes.
|
||||
"""
|
||||
if not leaves:
|
||||
return []
|
||||
|
||||
assert n > 1
|
||||
|
||||
depth = ceil(log(len(leaves), n))
|
||||
|
||||
if depth <= 1:
|
||||
return list(leaves)
|
||||
|
||||
# Fully populate complete subtrees of root until we have enough leaves left
|
||||
root = []
|
||||
unassigned = None
|
||||
full_step = n ** (depth - 1)
|
||||
for i in range(0, len(leaves), full_step):
|
||||
subtree = leaves[i : i + full_step]
|
||||
if len(subtree) < full_step:
|
||||
unassigned = subtree
|
||||
break
|
||||
while len(subtree) > n:
|
||||
subtree = [subtree[k : k + n] for k in range(0, len(subtree), n)]
|
||||
root.append(subtree)
|
||||
|
||||
if unassigned:
|
||||
# Recurse to fill the last subtree, which is the only partially populated one
|
||||
subtree = _build_n_ary_tree(unassigned, n)
|
||||
if len(subtree) <= n - len(root):
|
||||
# replace last subtree with its children if they can still fit
|
||||
root.extend(subtree)
|
||||
else:
|
||||
root.append(subtree)
|
||||
assert len(root) <= n
|
||||
|
||||
return root
|
||||
|
@ -2,7 +2,7 @@ from fontTools.ttLib import newTable
|
||||
from fontTools.ttLib.tables import otTables as ot
|
||||
from fontTools.colorLib import builder
|
||||
from fontTools.colorLib.geometry import round_start_circle_stable_containment, Circle
|
||||
from fontTools.colorLib.builder import LayerV1ListBuilder
|
||||
from fontTools.colorLib.builder import LayerV1ListBuilder, _build_n_ary_tree
|
||||
from fontTools.colorLib.errors import ColorLibError
|
||||
import pytest
|
||||
from typing import List
|
||||
@ -1105,3 +1105,81 @@ class TrickyRadialGradientTest:
|
||||
)
|
||||
def test_nudge_start_circle_position(self, c0, r0, c1, r1, inside, expected):
|
||||
assert self.round_start_circle(c0, r0, c1, r1, inside) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"lst, n, expected",
|
||||
[
|
||||
([0], 2, [0]),
|
||||
([0, 1], 2, [0, 1]),
|
||||
([0, 1, 2], 2, [[0, 1], 2]),
|
||||
([0, 1, 2], 3, [0, 1, 2]),
|
||||
([0, 1, 2, 3], 2, [[0, 1], [2, 3]]),
|
||||
([0, 1, 2, 3], 3, [[0, 1, 2], 3]),
|
||||
([0, 1, 2, 3, 4], 3, [[0, 1, 2], 3, 4]),
|
||||
([0, 1, 2, 3, 4, 5], 3, [[0, 1, 2], [3, 4, 5]]),
|
||||
(list(range(7)), 3, [[0, 1, 2], [3, 4, 5], 6]),
|
||||
(list(range(8)), 3, [[0, 1, 2], [3, 4, 5], [6, 7]]),
|
||||
(list(range(9)), 3, [[0, 1, 2], [3, 4, 5], [6, 7, 8]]),
|
||||
(list(range(10)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], 9]),
|
||||
(list(range(11)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], 9, 10]),
|
||||
(list(range(12)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [9, 10, 11]]),
|
||||
(list(range(13)), 3, [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [9, 10, 11], 12]),
|
||||
(
|
||||
list(range(14)),
|
||||
3,
|
||||
[[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [[9, 10, 11], 12, 13]],
|
||||
),
|
||||
(
|
||||
list(range(15)),
|
||||
3,
|
||||
[[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [9, 10, 11], [12, 13, 14]],
|
||||
),
|
||||
(
|
||||
list(range(16)),
|
||||
3,
|
||||
[[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [[9, 10, 11], [12, 13, 14], 15]],
|
||||
),
|
||||
(
|
||||
list(range(23)),
|
||||
3,
|
||||
[
|
||||
[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
|
||||
[[9, 10, 11], [12, 13, 14], [15, 16, 17]],
|
||||
[[18, 19, 20], 21, 22],
|
||||
],
|
||||
),
|
||||
(
|
||||
list(range(27)),
|
||||
3,
|
||||
[
|
||||
[[0, 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]],
|
||||
],
|
||||
),
|
||||
(
|
||||
list(range(28)),
|
||||
3,
|
||||
[
|
||||
[
|
||||
[[0, 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,
|
||||
],
|
||||
),
|
||||
(list(range(257)), 256, [list(range(256)), 256]),
|
||||
(list(range(258)), 256, [list(range(256)), 256, 257]),
|
||||
(list(range(512)), 256, [list(range(256)), list(range(256, 512))]),
|
||||
(list(range(512 + 1)), 256, [list(range(256)), list(range(256, 512)), 512]),
|
||||
(
|
||||
list(range(256 ** 2)),
|
||||
256,
|
||||
[list(range(k * 256, k * 256 + 256)) for k in range(256)],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_build_n_ary_tree(lst, n, expected):
|
||||
assert _build_n_ary_tree(lst, n) == expected
|
||||
|
Loading…
x
Reference in New Issue
Block a user