Example #1
0
def _apply_solid_paint(el: etree.Element, paint: PaintSolid):
    if etree.QName(el.tag).localname == "g":
        assert paint.color.opaque() == Color.fromstring(
            "black"), "Unexpected color choice"
    if paint.color.opaque() != Color.fromstring("black"):
        el.attrib["fill"] = paint.color.opaque().to_string()
    if paint.color.alpha != 1.0:
        el.attrib["opacity"] = _ntos(paint.color.alpha)
Example #2
0
def _paint_glyph(
    debug_hint: str,
    config: FontConfig,
    picosvg: SVG,
    context: SVGTraverseContext,
    glyph_width: int,
) -> Paint:
    shape = context.shape()

    if shape.fill.startswith("url("):
        fill_el = picosvg.resolve_url(shape.fill, "*")
        try:
            glyph_paint = _GRADIENT_INFO[etree.QName(fill_el).localname](
                config,
                fill_el,
                shape.bounding_box(),
                picosvg.view_box(),
                glyph_width,
                shape.opacity,
            )
        except ValueError as e:
            raise ValueError(
                f"parse failed for {debug_hint}, {etree.tostring(fill_el)[:128]}"
            ) from e
    else:
        glyph_paint = PaintSolid(
            color=Color.fromstring(shape.fill, alpha=shape.opacity)
        )

    return PaintGlyph(glyph=shape.as_path().d, paint=glyph_paint)
Example #3
0
def _color_index_node(palette, color_index):
    color = Color.fromstring(palette[color_index.PaletteIndex].hex())
    ci_alpha = color_index.Alpha.value
    node_id = f"{ci_alpha:.2f}.{color.opaque().to_string()}.{color.alpha:.2f}"
    return Node(
        node_id=node_id,
        node_label=color._replace(alpha=color.alpha * ci_alpha).to_string(),
    )
Example #4
0
def _color_stop(stop_el):
    offset = stop_el.attrib.get("offset", "0")
    if offset.endswith("%"):
        offset = float(offset[:-1]) / 100
    else:
        offset = float(offset)
    color = stop_el.attrib.get("stop-color", "black")
    if "stop-opacity" in stop_el.attrib:
        raise ValueError("<stop stop-opacity/> not supported")
    return ColorStop(stopOffset=offset, color=Color.fromstring(color))
Example #5
0
def _paint(nsvg, shape):
    match = regex.match(r"^url[(]#([^)]+)[)]$", shape.fill)
    if shape.fill.startswith("url("):
        el = nsvg.resolve_url(shape.fill, "*")

        grad_type, grad_type_parser = _GRADIENT_INFO[etree.QName(el).localname]
        grad_args = _common_gradient_parts(el)
        grad_args.update(grad_type_parser(el))
        return grad_type(**grad_args)

    return PaintSolid(color=Color.fromstring(shape.fill, alpha=shape.opacity))
Example #6
0
class PaintSolid(Paint):
    format: ClassVar[int] = int(ot.PaintFormat.PaintSolid)
    color: Color = Color.fromstring("black")

    def colors(self):
        yield self.color

    def to_ufo_paint(self, colors):
        return {
            "Format": self.format,
            "PaletteIndex": colors.index(self.color.opaque()),
            "Alpha": self.color.alpha,
        }
Example #7
0
def _paint(
    debug_hint: str, config: FontConfig, picosvg: SVG, shape: SVGPath, glyph_width: int
) -> Paint:
    if shape.fill.startswith("url("):
        el = picosvg.resolve_url(shape.fill, "*")
        try:
            return _GRADIENT_INFO[etree.QName(el).localname](
                config,
                el,
                shape.bounding_box(),
                picosvg.view_box(),
                glyph_width,
                shape.opacity,
            )
        except ValueError as e:
            raise ValueError(
                f"parse failed for {debug_hint}, {etree.tostring(el)[:128]}"
            ) from e

    return PaintSolid(color=Color.fromstring(shape.fill, alpha=shape.opacity))
Example #8
0
    def _paint(self, shape):
        upem = self.ufo.info.unitsPerEm
        if shape.fill.startswith("url("):
            el = self.picosvg.resolve_url(shape.fill, "*")

            grad_type, grad_type_parser = _GRADIENT_INFO[etree.QName(
                el).localname]
            grad_args = _common_gradient_parts(el, shape.opacity)
            try:
                grad_args.update(
                    grad_type_parser(el, shape.bounding_box(),
                                     self.picosvg.view_box(), upem))
            except ValueError as e:
                raise ValueError(
                    f"parse failed for {self.filename}, {etree.tostring(el)[:128]}"
                ) from e
            return grad_type(**grad_args)

        return PaintSolid(
            color=Color.fromstring(shape.fill, alpha=shape.opacity))
Example #9
0
            r1=round(paint.r1, prec),
            affine2x2=(tuple(round(v, prec) for v in paint.affine2x2)
                       if paint.affine2x2 is not None else None),
        )
    else:
        return paint


@pytest.mark.parametrize(
    "svg_in, expected_paints",
    [
        # solid
        (
            "rect.svg",
            {
                PaintSolid(color=Color.fromstring("blue")),
                PaintSolid(color=Color.fromstring("blue", alpha=0.8)),
            },
        ),
        # linear
        (
            "linear_gradient_rect.svg",
            {
                PaintLinearGradient(
                    stops=(
                        ColorStop(stopOffset=0.1,
                                  color=Color.fromstring("blue")),
                        ColorStop(stopOffset=0.9,
                                  color=Color.fromstring("cyan", 0.8)),
                    ),
                    p0=Point(200, 800),
Example #10
0
def _painted_layers(
    debug_hint: str,
    config: FontConfig,
    picosvg: SVG,
    glyph_width: int,
) -> Tuple[Paint, ...]:

    defs_seen = False
    layers = []

    # Reverse to get leaves first because that makes building Paint's easier
    # shapes *must* be leaves per picosvg
    for context in reversed(tuple(picosvg.depth_first())):
        if context.depth() == 0:
            continue  # svg root
        # picosvg will deliver us exactly one defs
        if context.path == "/svg[0]/defs[0]":
            assert not defs_seen
            defs_seen = True
            continue  # defs are pulled in by the consuming paints

        if context.is_shape():
            while len(layers) < context.depth():
                layers.append([])
            assert len(layers) == context.depth()
            layers[context.depth() - 1].append(
                _paint_glyph(debug_hint, config, picosvg, context, glyph_width)
            )

        if context.is_group():
            # flush child shapes into a new group
            opacity = float(context.element.get("opacity"))
            assert (
                0.0 < opacity < 1.0
            ), f"{debug_hint} {context.path} should be transparent"
            assert (
                len(layers) == context.depth() + 1
            ), "Should have a list of child nodes"
            child_nodes = layers.pop(context.depth())
            assert (
                len(child_nodes) > 1
            ), f"{debug_hint} {context.path} should have 2+ children"
            assert {"opacity"} == set(
                context.element.attrib.keys()
            ), f"{debug_hint} {context.path} only attribute should be opacity. Found {context.element.attrib.keys()}"
            # insert reversed to undo the reversed at the top of loop
            paint = PaintComposite(
                mode=CompositeMode.SRC_IN,
                source=PaintColrLayers(tuple(reversed(child_nodes))),
                backdrop=PaintSolid(Color(0, 0, 0, opacity)),
            )
            layers[context.depth() - 1].append(paint)

    assert defs_seen, f"{debug_hint} we never saw defs, what's up with that?!"

    if not layers:
        return ()

    assert len(layers) == 1, f"Unexpected layers: {[len(l) for l in layers]}"
    # undo the reversed at the top of loop
    layers = reversed(layers[0])

    return tuple(layers)
Example #11
0
def _color_stop(stop_el, shape_opacity=1.0) -> ColorStop:
    offset = number_or_percentage(stop_el.attrib.get("offset", "0"))
    color = Color.fromstring(stop_el.attrib.get("stop-color", "black"))
    opacity = number_or_percentage(stop_el.attrib.get("stop-opacity", "1"))
    color = color._replace(alpha=color.alpha * opacity * shape_opacity)
    return ColorStop(stopOffset=offset, color=color)
Example #12
0
        )
    if is_transform(paint):
        return transformed(paint.gettransform().round(prec), paint.paint)
    return paint


@pytest.mark.parametrize(
    "svg_in, expected_paints",
    [
        # solid
        (
            "rect.svg",
            (
                PaintGlyph(
                    glyph="M2,2 L8,2 L8,4 L2,4 L2,2 Z",
                    paint=PaintSolid(color=Color.fromstring("blue")),
                ),
                PaintGlyph(
                    glyph="M4,4 L10,4 L10,6 L4,6 L4,4 Z",
                    paint=PaintSolid(
                        color=Color.fromstring("blue", alpha=0.8)),
                ),
            ),
        ),
        # linear
        (
            "linear_gradient_rect.svg",
            (PaintGlyph(
                glyph="M2,2 L8,2 L8,4 L2,4 L2,2 Z",
                paint=PaintLinearGradient(
                    stops=(
Example #13
0
class ColorStop:
    stopOffset: float = 0.0
    color: Color = Color.fromstring("black")
Example #14
0
    )
    color_glyph = ColorGlyph.create(
        _ufo(upem), "duck", 1, [0x0042], SVG.fromstring(svg_str)
    )

    assert color_glyph.transform_for_font_space() == expected_transform


@pytest.mark.parametrize(
    "svg_in, expected_paints",
    [
        # solid
        (
            "rect.svg",
            {
                PaintSolid(color=Color.fromstring("blue")),
                PaintSolid(color=Color.fromstring("blue", alpha=0.8)),
            },
        ),
        # linear
        (
            "linear_gradient_rect.svg",
            {
                PaintLinearGradient(
                    stops=(
                        ColorStop(stopOffset=0.1, color=Color.fromstring("blue")),
                        ColorStop(stopOffset=0.9, color=Color.fromstring("cyan")),
                    )
                )
            },
        ),
Example #15
0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from nanoemoji.colors import Color
import pytest


@pytest.mark.parametrize(
    "color_string, expected_color",
    [
        # 3-hex digits
        ("#BCD", Color(0xBB, 0xCC, 0xDD, 1.0)),
        # 4-hex digits
        ("#BCD3", Color(0xBB, 0xCC, 0xDD, 0.2)),
        # 6-hex digits
        ("#F1E2D3", Color(0xF1, 0xE2, 0xD3, 1.0)),
        # 8-hex digits
        ("#F1E2D366", Color(0xF1, 0xE2, 0xD3, 0.4)),
        # CSS named color
        ("wheat", Color(0xF5, 0xDE, 0xB3, 1.0)),
        # rgb(r,g,b)
        ("rgb(0, 256, -1)", Color(0, 255, 0, 1.0)),
        # rgb(r g b)
        ("rgb(42 101 43)", Color(42, 101, 43, 1.0)),
        # extra whitespace as found in the noto-emoji Luxembourg flag
        ("#00A1DE\n", Color(0, 161, 222, 1.0)),
    ],
Example #16
0
def test_color_fromstring(color_string, expected_color):
    assert expected_color == Color.fromstring(color_string)
Example #17
0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from nanoemoji.colors import Color
import pytest


@pytest.mark.parametrize(
    "color_string, expected_color",
    [
        # 3-hex digits
        ("#BCD", Color(0xBB, 0xCC, 0xDD, 1.0)),
        # 4-hex digits
        ("#BCD3", Color(0xBB, 0xCC, 0xDD, 0.2)),
        # 6-hex digits
        ("#F1E2D3", Color(0xF1, 0xE2, 0xD3, 1.0)),
        # 8-hex digits
        ("#F1E2D366", Color(0xF1, 0xE2, 0xD3, 0.4)),
        # CSS named color
        ("wheat", Color(0xF5, 0xDE, 0xB3, 1.0)),
        # rgb(r,g,b)
        ("rgb(0, 256, -1)", Color(0, 255, 0, 1.0)),
        # rgb(r g b)
        ("rgb(42 101 43)", Color(42, 101, 43, 1.0)),
    ],
)
def test_color_fromstring(color_string, expected_color):