コード例 #1
0
ファイル: color_glyph.py プロジェクト: yisibl/nanoemoji
def _scale_viewbox_to_font_metrics(
    view_box: Rect, ascender: int, descender: int, width: int
):
    assert descender <= 0
    # scale height to (ascender - descender)
    scale = (ascender - descender) / view_box.h
    # shift so width is centered
    dx = (width - scale * view_box.w) / 2
    return Affine2D.compose_ltr(
        (
            # first normalize viewbox origin
            Affine2D(1, 0, 0, 1, -view_box.x, -view_box.y),
            Affine2D(scale, 0, 0, scale, dx, 0),
        )
    )
コード例 #2
0
ファイル: svg.py プロジェクト: yisibl/picosvg
    def _apply_gradient_translation(self, inplace=False):
        if not inplace:
            svg = SVG(copy.deepcopy(self.svg_root))
            svg._apply_gradient_translation(inplace=True)
            return svg

        for el in self._select_gradients():
            gradient = _GRADIENT_CLASSES[strip_ns(el.tag)].from_element(
                el, self.view_box())
            a, b, c, d, e, f = (round(v, _GRADIENT_TRANSFORM_NDIGITS)
                                for v in gradient.gradientTransform)
            affine = Affine2D(a, b, c, d, e, f)
            #  no translate? nop!
            if (e, f) == (0, 0):
                continue

            # split translation from rest of the transform and apply to gradient coords
            translate, affine_prime = affine.decompose_translation()
            for x_attr, y_attr in _GRADIENT_COORDS[strip_ns(el.tag)]:
                # if at default just ignore
                if x_attr not in el.attrib and y_attr not in el.attrib:
                    continue
                x = getattr(gradient, x_attr)
                y = getattr(gradient, y_attr)
                x_prime, y_prime = translate.map_point((x, y))
                el.attrib[x_attr] = ntos(
                    round(x_prime, _GRADIENT_TRANSFORM_NDIGITS))
                el.attrib[y_attr] = ntos(
                    round(y_prime, _GRADIENT_TRANSFORM_NDIGITS))

            if affine_prime != Affine2D.identity():
                el.attrib["gradientTransform"] = (
                    "matrix(" + " ".join(ntos(v) for v in affine_prime) + ")")
            else:
                del el.attrib["gradientTransform"]
コード例 #3
0
def _decompose_uniform_transform(transform: Affine2D) -> Tuple[Affine2D, Affine2D]:
    scale, remaining_transform = transform.decompose_scale()
    s = max(*scale.getscale())
    # most transforms will contain a Y-flip component as result of mapping from SVG to
    # font coordinate space. Here we keep this negative Y sign as part of the uniform
    # transform since it does not affect the circle-ness, and also makes so that the
    # font-mapped gradient geometry is more likely to be in the +x,+y quadrant like
    # the path geometry it is applied to.
    uniform_scale = Affine2D(s, 0, 0, copysign(s, transform.d), 0, 0)
    remaining_transform = Affine2D.compose_ltr(
        (uniform_scale.inverse(), scale, remaining_transform)
    )

    translate, remaining_transform = remaining_transform.decompose_translation()
    # round away very small float-math noise, so we get clean 0s and 1s for the special
    # case of identity matrix which implies no wrapping transform
    remaining_transform = remaining_transform.round(9)

    logging.debug(
        "Decomposing %r:\n\tscale: %r\n\ttranslate: %r\n\tremaining: %r",
        transform,
        uniform_scale,
        translate,
        remaining_transform,
    )

    uniform_transform = Affine2D.compose_ltr((uniform_scale, translate))
    return uniform_transform, remaining_transform
コード例 #4
0
ファイル: color_glyph.py プロジェクト: mavit/nanoemoji
def map_viewbox_to_otsvg_emsquare(view_box: Rect, upem: int) -> Affine2D:
    x_scale, y_scale = _scale_viewbox_to_emsquare(view_box, upem)
    dx, dy = _shift_origin_0_0(view_box, x_scale, y_scale)

    # shift so things are in the right place
    dy = dy - upem
    return Affine2D(x_scale, 0, 0, y_scale, dx, dy)
コード例 #5
0
def test_addComponent_decompose_with_transform():
    pen = SVGPathPen(glyphSet={"a": DummyGlyph()})
    pen.addComponent("a", Affine2D(2, 0, 0, 2, 0, 0))

    assert pen.path.d == (
        "M0,0 L0,20 L20,20 L20,0 Z "
        "M0,30 C0,40 20,40 20,30 Z "
        "M0,-10 Q0,-16 3,-18 Q6,-20 10,-20 Q14,-20 17,-18 Q20,-16 20,-10")
コード例 #6
0
ファイル: color_glyph.py プロジェクト: mavit/nanoemoji
def map_viewbox_to_font_emsquare(view_box: Rect, upem: int) -> Affine2D:
    x_scale, y_scale = _scale_viewbox_to_emsquare(view_box, upem)
    # flip y axis
    y_scale = -y_scale
    # shift so things are in the right place
    dx, dy = _shift_origin_0_0(view_box, x_scale, y_scale)
    dy = dy + upem
    return Affine2D(x_scale, 0, 0, y_scale, dx, dy)
コード例 #7
0
ファイル: color_glyph.py プロジェクト: yisibl/nanoemoji
def map_viewbox_to_otsvg_space(
    view_box: Rect, ascender: int, descender: int, width: int, user_transform: Affine2D
) -> Affine2D:
    return Affine2D.compose_ltr(
        [
            _scale_viewbox_to_font_metrics(view_box, ascender, descender, width),
            # shift things in the [+x,-y] quadrant where OT-SVG expects them
            Affine2D(1, 0, 0, 1, 0, -ascender),
            user_transform,
        ]
    )
コード例 #8
0
ファイル: color_glyph.py プロジェクト: yisibl/nanoemoji
def map_viewbox_to_font_space(
    view_box: Rect, ascender: int, descender: int, width: int, user_transform: Affine2D
) -> Affine2D:
    return Affine2D.compose_ltr(
        [
            _scale_viewbox_to_font_metrics(view_box, ascender, descender, width),
            # flip y axis and shift so things are in the right place
            Affine2D(1, 0, 0, -1, 0, ascender),
            user_transform,
        ]
    )
コード例 #9
0
ファイル: color_glyph.py プロジェクト: mavit/nanoemoji
def _parse_radial_gradient(grad_el, shape_bbox, view_box, upem):
    width, height = _get_gradient_units_relative_scale(grad_el, view_box)

    cx = _number_or_percentage(grad_el.attrib.get("cx", "50%"), width)
    cy = _number_or_percentage(grad_el.attrib.get("cy", "50%"), height)
    r = _number_or_percentage(grad_el.attrib.get("r", "50%"), width)

    raw_fx = grad_el.attrib.get("fx")
    fx = _number_or_percentage(raw_fx, width) if raw_fx is not None else cx
    raw_fy = grad_el.attrib.get("fy")
    fy = _number_or_percentage(raw_fy, height) if raw_fy is not None else cy
    fr = _number_or_percentage(grad_el.attrib.get("fr", "0%"), width)

    c0 = Point(fx, fy)
    r0 = fr
    c1 = Point(cx, cy)
    r1 = r

    transform = _get_gradient_transform(grad_el, shape_bbox, view_box, upem)

    # The optional Affine2x2 matrix of COLRv1.RadialGradient is used to transform
    # the circles into ellipses "around their centres": i.e. centres coordinates
    # are _not_ transformed by it. Thus we apply the full transform to them.
    c0 = transform.map_point(c0)
    c1 = transform.map_point(c1)

    # As for the circle radii (which are affected by Affine2x2), we only scale them
    # by the maximum of the (absolute) scale or skew.
    # Then in Affine2x2 we only store a "fraction" of the original transform, i.e.
    # multiplied by the inverse of the scale that we've already applied to the radii.
    # Especially when gradientUnits="objectBoundingBox", where circle positions and
    # radii are expressed using small floats in the range [0..1], this pre-scaling
    # helps reducing the inevitable rounding errors that arise from storing these
    # values as integers in COLRv1 tables.
    s = max(abs(v) for v in transform[:4])

    rscale = Affine2D(s, 0, 0, s, 0, 0)
    r0 = rscale.map_vector((r0, 0)).x
    r1 = rscale.map_vector((r1, 0)).x

    affine2x2 = Affine2D.product(rscale.inverse(), transform)

    gradient = {
        "c0": c0,
        "c1": c1,
        "r0": r0,
        "r1": r1,
        "affine2x2":
        (affine2x2[:4] if affine2x2 != Affine2D.identity() else None),
    }

    # TODO handle degenerate cases, fallback to solid, w/e

    return gradient
コード例 #10
0
def _about_paint(paint):
    hashable = colr_builder._paint_tuple(paint)
    if hashable in visited:
        return
    visited.add(hashable)

    count_by_type[paint.getFormatName()] += 1

    if paint.getFormatName() == 'PaintTransform':
        tr = paint.Transform
        transform = (tr.xx, tr.yx, tr.xy, tr.yy, tr.dx, tr.dy)
        # just scale and/or translate?
        if tr.xy == 0 and tr.yx == 0:
            # Relationship between scale and translate leads to div 0?
            if ((tr.xx == 1) != (tr.dx == 0)) or ((tr.yy == 1) !=
                                                  (tr.dy == 0)):
                count_by_type[paint.getFormatName() + "::weird_scale"] += 1
            elif f2dot14_safe(tr.xx, tr.yy):
                cx = cy = 0
                if tr.dx != 0:
                    cx = tr.dx / (1 - tr.xx)
                if tr.dy != 0:
                    cy = tr.dy / (1 - tr.yy)

                if int16_safe(cx, cy):
                    if tr.dx == 0 and tr.dy == 0:
                        count_by_type[paint.getFormatName() +
                                      "::scale_origin"] += 1
                    else:
                        count_by_type[paint.getFormatName() +
                                      "::scale_around"] += 1
                    unique_dropped_affines.add(transform)
                else:
                    count_by_type[paint.getFormatName() +
                                  "::scale_around_non_int"] += 1
            else:
                count_by_type[paint.getFormatName() + "::large_scale"] += 1
        else:
            translate, other = Affine2D(*transform).decompose_translation()
            if _common_angle(*other[:4]):
                if translate.almost_equals(Affine2D.identity()):
                    count_by_type[paint.getFormatName() + "::pure_rotate"] += 1
                else:
                    count_by_type[paint.getFormatName() + "::move_rotate"] += 1
            elif (tr.dx, tr.dy) == (0, 0):
                count_by_type[paint.getFormatName() +
                              "::inexplicable_2x2"] += 1
            else:
                count_by_type[paint.getFormatName() +
                              "::inexplicable_2x3"] += 1
コード例 #11
0
ファイル: color_glyph_test.py プロジェクト: mavit/nanoemoji
    return ufo


def _test_file(filename):
    return os.path.join(os.path.dirname(__file__), filename)


def _nsvg(filename):
    return SVG.parse(_test_file(filename)).topicosvg()


@pytest.mark.parametrize(
    "view_box, upem, expected_transform",
    [
        # same upem, flip y
        ("0 0 1024 1024", 1024, Affine2D(1, 0, 0, -1, 0, 1024)),
        # noto emoji norm. scale, flip y
        ("0 0 128 128", 1024, Affine2D(8, 0, 0, -8, 0, 1024)),
        # noto emoji emoji_u26be.svg viewBox. Scale, flip y and translate
        ("-151 297 128 128", 1024, Affine2D(8, 0, 0, -8, 1208, 3400)),
        # made up example. Scale, translate, flip y
        (
            "10 11 20 21",
            100,
            Affine2D(a=5.0, b=0, c=0, d=-4.761905, e=-50.0, f=152.380952),
        ),
    ],
)
def test_transform(view_box, upem, expected_transform):
    svg_str = ('<svg version="1.1"'
               ' xmlns="http://www.w3.org/2000/svg"'
コード例 #12
0
def _nsvg(filename):
    return SVG.parse(_test_file(filename)).topicosvg()


def _pprint(thing):
    stream = io.StringIO()
    pprint.pprint(thing, indent=2, stream=stream)
    return stream.getvalue()


@pytest.mark.parametrize(
    "view_box, upem, width, ascender, descender, expected_transform, expected_width",
    [
        # same upem, flip y
        ("0 0 1024 1024", 1024, 1024, 1024, 0, Affine2D(1, 0, 0, -1, 0,
                                                        1024), 1024),
        # noto emoji norm. scale, flip y
        ("0 0 128 128", 1024, 1024, 1024, 0, Affine2D(8, 0, 0, -8, 0,
                                                      1024), 1024),
        # noto emoji emoji_u26be.svg viewBox. Scale, flip y and translate
        (
            "-151 297 128 128",
            1024,
            1024,
            1024,
            0,
            Affine2D(8, 0, 0, -8, 1208, 3400),
            1024,
        ),
        # made up example. Scale, translate, flip y, center horizontally
        (
コード例 #13
0
 def gettransform(self) -> Affine2D:
     return Affine2D(*self.transform)
コード例 #14
0
 ),
 # path observed in wild to normalize but not compute affine_between
 # caused by failure to normalize equivalent d attributes in affine_between
 (
     SVGPath(fill="#99AAB5",
             d="M18 12H2 c-1.104 0-2 .896-2 2h20c0-1.104-.896-2-2-2z"),
     SVGPath(fill="#99AAB5",
             d="M34 12H18c-1.104 0-2 .896-2 2h20c0-1.104-.896-2-2-2z"),
     Affine2D.identity().translate(16, 0),
     0.01,
 ),
 # Triangles facing one another, same size
 (
     SVGPath(d="m60,64 -50,-32 0,30 z"),
     SVGPath(d="m68,64 50,-32 0,30 z"),
     Affine2D(-1.0, 0.0, 0.0, 1.0, 128.0, -0.0),
     0.01,
 ),
 # Triangles, different rotation, different size
 (
     SVGPath(d="m50,100 -48,-75 81,0 z"),
     SVGPath(d="m70,64 50,-32 0,54 z"),
     Affine2D(
         a=-0.0, b=0.6667, c=-0.6667, d=-0.0, e=136.6667, f=30.6667),
     0.01,
 ),
 # TODO triangles, one point stretched not aligned with X or Y
 # A square and a rect; different scale for each axis
 (
     SVGRect(x=10, y=10, width=50, height=50),
     SVGRect(x=70, y=20, width=20, height=100),
コード例 #15
0
def _colr_v1_glyph_to_svg(ttfont: ttLib.TTFont, view_box: Rect,
                          glyph: otTables.BaseGlyphRecord) -> etree.Element:
    glyph_set = ttfont.getGlyphSet()
    svg_root = _svg_root(view_box)
    defs = svg_root[0]
    for glyph_layer in glyph.LayerV1List.LayerV1Record:
        svg_path = etree.SubElement(svg_root, "path")

        # TODO care about variations, such as for alpha
        paint = glyph_layer.Paint
        if paint.Format == _PAINT_SOLID:
            _solid_paint(svg_path, ttfont, paint.Color.PaletteIndex,
                         paint.Color.Alpha.value)
        elif paint.Format == _PAINT_LINEAR_GRADIENT:
            _linear_gradient_paint(
                defs,
                svg_path,
                ttfont,
                view_box,
                stops=[
                    _ColorStop(
                        stop.StopOffset.value,
                        stop.Color.PaletteIndex,
                        stop.Color.Alpha.value,
                    ) for stop in paint.ColorLine.ColorStop
                ],
                extend=Extend((paint.ColorLine.Extend.value, )),
                p0=Point(paint.x0.value, paint.y0.value),
                p1=Point(paint.x1.value, paint.y1.value),
                p2=Point(paint.x2.value, paint.y2.value),
            )
        elif paint.Format == _PAINT_RADIAL_GRADIENT:
            _radial_gradient_paint(
                defs,
                svg_path,
                ttfont,
                view_box,
                stops=[
                    _ColorStop(
                        stop.StopOffset.value,
                        stop.Color.PaletteIndex,
                        stop.Color.Alpha.value,
                    ) for stop in paint.ColorLine.ColorStop
                ],
                extend=Extend((paint.ColorLine.Extend.value, )),
                c0=Point(paint.x0.value, paint.y0.value),
                c1=Point(paint.x1.value, paint.y1.value),
                r0=paint.r0.value,
                r1=paint.r1.value,
                transform=(Affine2D.identity()
                           if not paint.Transform else Affine2D(
                               paint.Transform.xx.value,
                               paint.Transform.xy.value,
                               paint.Transform.yx.value,
                               paint.Transform.yy.value,
                               0,
                               0,
                           )),
            )

        _draw_svg_path(svg_path, view_box, ttfont, glyph_layer.LayerGlyph,
                       glyph_set)

    return svg_root