def load_glyph(font_name: str, glyph: str, index: int = 0) -> Polygon:
    face = freetype.Face(font_manager.findfont(font_name), index=index)
    face.set_char_size(128 * 64)
    face.load_char(glyph,
                   freetype.FT_LOAD_DEFAULT | freetype.FT_LOAD_NO_BITMAP)
    ctx = []
    face.glyph.outline.decompose(ctx,
                                 move_to=move_to,
                                 line_to=line_to,
                                 conic_to=conic_to,
                                 cubic_to=cubic_to)
    bbox = face.glyph.outline.get_bbox()
    width = bbox.xMax - bbox.xMin
    height = bbox.yMax - bbox.yMin
    svg = f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}">
  <g transform="translate(0, {bbox.yMax - bbox.yMin}) scale(1, -1) translate({-bbox.xMin}, {-bbox.yMin})" transform-origin="0 0">
    <path
      style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
      d="{' '.join(ctx)}"
    />
    <!-- rect x="{bbox.xMin}" y="{bbox.yMin}" width="{width}" height="{height}" /-->
  </g>
</svg>
"""

    svg_io = io.StringIO(svg)
    lc, w, h = vp.read_svg(svg_io, 1)

    mls = lc.as_mls()
    poly = Polygon(mls.geoms[0])
    for geom in mls.geoms[1:]:
        poly = poly.symmetric_difference(Polygon(geom))
    return poly
Esempio n. 2
0
def test_read_svg_visibility(svg_content, line_count, tmp_path):
    svg = f"""<?xml version="1.0"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"
    width="1000" height="1000">
        {svg_content}
</svg>
"""
    path = str(tmp_path / "file.svg")
    with open(path, "w") as fp:
        fp.write(svg)

    lc, _, _ = vp.read_svg(path, 1.0)
    assert len(lc) == line_count
Esempio n. 3
0
def test_read_with_viewbox(tmp_path):
    path = str(tmp_path / "file.svg")
    with open(path, "w") as fp:
        fp.write(f"""<?xml version="1.0"?>
    <svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"
       width="100" height="100" viewBox="50 50 10 10">
       <line x1="50" y1="50" x2="60" y2="60" />
    </svg>
    """)

    lc, width, height = vp.read_svg(path, quantization=0.1)
    assert width == 100
    assert height == 100
    assert len(lc) == 1
    assert np.all(np.isclose(lc[0], np.array([0, 100 + 100j])))
Esempio n. 4
0
def test_read_svg_width_height(params, expected, tmp_path):
    path = str(tmp_path / "file.svg")
    with open(path, "w") as fp:
        fp.write(f"""<?xml version="1.0"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"
   {params}>
</svg>
""")

    lc, width, height = vp.read_svg(path,
                                    quantization=0.1,
                                    default_width=DEFAULT_WIDTH,
                                    default_height=DEFAULT_HEIGHT)
    assert width == expected[0]
    assert height == expected[1]
Esempio n. 5
0
def load_module_set(
        path: str,
        quantization: float) -> Tuple[List[vp.LineCollection], float, float]:
    """
    Load a module set. If tiles are missing, they are created from existing tiles using
    rotation.
    """

    modules = {}
    for suffix in MSET_SUFFIX:
        try:
            modules[suffix] = vp.read_svg(f"{path}_{suffix}.svg",
                                          quantization=quantization,
                                          simplify=True)
        except FileNotFoundError:
            modules[suffix] = None

    # check equality on the sizes
    if not check_equality(m[1] for m in modules.values()
                          if m is not None) or not check_equality(
                              m[2] for m in modules.values() if m is not None):
        raise RuntimeError(f"tile set `{path}` has inconsistent size")

    width = modules["0000"][1]
    height = modules["0000"][2]

    # check missing modules
    for suffix in modules:
        if modules[suffix] is None:
            conv = MSET_CONVERSIONS[suffix]
            lc = vp.LineCollection(modules[conv[0]][0])
            lc.translate(-width / 2, -height / 2)
            lc.rotate(conv[1])
            lc.translate(width / 2, height / 2)
            modules[suffix] = (lc, width, height)

    return [modules[suffix][0] for suffix in MSET_SUFFIX], width, height
Esempio n. 6
0
def read(
    vector_data: VectorData,
    file,
    single_layer: bool,
    layer: Optional[int],
    quantization: float,
    no_simplify: bool,
) -> VectorData:
    """Extract geometries from a SVG file.

    By default, the `read` command attempts to preserve the layer structure of the SVG. In this
    context, top-level groups (<svg:g>) are each considered a layer. If any, all non-group,
    top-level SVG elements are imported into layer 1.

    The following logic is used to determine in which layer each SVG top-level group is
    imported:

        - If a `inkscape:label` attribute is present and contains digit characters, it is
    stripped of non-digit characters the resulting number is used as target layer. If the
    resulting number is 0, layer 1 is used instead.

        - If the previous step fails, the same logic is applied to the `id` attribute.

        - If both previous steps fail, the target layer matches the top-level group's order of
    appearance.

    Using `--single-layer`, the `read` command operates in single-layer mode. In this mode, all
    geometries are in a single layer regardless of the group structure. The current target
    layer is used default and can be specified with the `--layer` option.

    This command only extracts path elements as well as primitives (rectangles, ellipses,
    lines, polylines, polygons). Other elements such as text and bitmap images are discarded,
    and so is all formatting.

    All curved primitives (e.g. bezier path, ellipses, etc.) are linearized and approximated by
    polylines. The quantization length controls the maximum length of individual segments.

    By default, an implicit line simplification with tolerance set to quantization is executed
    (see `linesimplify` command). This behaviour can be disabled with the `--no-simplify` flag.

    Examples:

        Multi-layer import:

            vpype read input_file.svg [...]

        Single-layer import:

            vpype read --single-layer input_file.svg [...]

        Single-layer import with target layer:

            vpype read --single-layer --layer 3 input_file.svg [...]

        Multi-layer import with specified quantization and line simplification disabled:

            vpype read --quantization 0.01mm --no-simplify input_file.svg [...]
    """

    if single_layer:
        vector_data.add(
            read_svg(file, quantization=quantization,
                     simplify=not no_simplify),
            single_to_layer_id(layer, vector_data),
        )
    else:
        if layer is not None:
            logging.warning(
                "read: target layer is ignored in multi-layer mode")
        vector_data.extend(
            read_multilayer_svg(file,
                                quantization=quantization,
                                simplify=not no_simplify))

    return vector_data
Esempio n. 7
0
def test_read_svg_should_not_generate_duplicate_points(path):
    line_collection, _, _ = vp.read_svg(path, quantization=1)
    for line in line_collection:
        assert np.all(line[1:] != line[:-1])
Esempio n. 8
0
def read(
    document: Document,
    file,
    single_layer: bool,
    layer: Optional[int],
    quantization: float,
    simplify: bool,
    parallel: bool,
    no_crop: bool,
    display_size: Tuple[float, float],
    display_landscape: bool,
) -> Document:
    """Extract geometries from a SVG file.

    By default, the `read` command attempts to preserve the layer structure of the SVG. In this
    context, top-level groups (<svg:g>) are each considered a layer. If any, all non-group,
    top-level SVG elements are imported into layer 1.

    The following logic is used to determine in which layer each SVG top-level group is
    imported:

        - If a `inkscape:label` attribute is present and contains digit characters, it is \
stripped of non-digit characters the resulting number is used as target layer. If the \
resulting number is 0, layer 1 is used instead.

        - If the previous step fails, the same logic is applied to the `id` attribute.

        - If both previous steps fail, the target layer matches the top-level group's order \
of appearance.

    Using `--single-layer`, the `read` command operates in single-layer mode. In this mode, \
all geometries are in a single layer regardless of the group structure. The current target \
layer is used default and can be specified with the `--layer` option.

    This command only extracts path elements as well as primitives (rectangles, ellipses,
    lines, polylines, polygons). Other elements such as text and bitmap images are discarded,
    and so is all formatting.

    All curved primitives (e.g. bezier path, ellipses, etc.) are linearized and approximated by
    polylines. The quantization length controls the maximum length of individual segments.

    Optionally, a line simplification with tolerance set to quantization can be applied on the
    SVG's curved element (e.g. circles, ellipses, arcs, bezier curves, etc.). This is enabled
    with the `--simplify` flag. This process reduces significantly the number of segments used
    to approximate the curve while still guaranteeing an accurate conversion, but may increase
    the execution time of this command.

    The `--parallel` option enables multiprocessing for the SVG conversion. This is recommended
    ONLY when using `--simplify` on large SVG files with many curved elements.

    By default, the geometries are cropped to the SVG boundaries defined by its width and
    length attributes. The crop operation can be disabled with the `--no-crop` option.

    In general, SVG boundaries are determined by the `width` and `height` of the top-level
    <svg> tag. However, the some SVG may have their width and/or height specified as percent
    value or even miss them altogether (in which case they are assumed to be set to 100%). In
    these cases, vpype considers by default that 100% corresponds to a A4 page in portrait
    orientation. The options `--display-size FORMAT` and `--display-landscape` can be used
    to specify a different format.

    Examples:

        Multi-layer import:

            vpype read input_file.svg [...]

        Single-layer import:

            vpype read --single-layer input_file.svg [...]

        Single-layer import with target layer:

            vpype read --single-layer --layer 3 input_file.svg [...]

        Multi-layer import with specified quantization and line simplification enabled:

            vpype read --quantization 0.01mm --simplify input_file.svg [...]

        Multi-layer import with cropping disabled:

            vpype read --no-crop input_file.svg [...]
    """

    width, height = display_size
    if display_landscape:
        width, height = height, width

    if single_layer:
        lc, width, height = read_svg(
            file,
            quantization=quantization,
            crop=not no_crop,
            simplify=simplify,
            parallel=parallel,
            default_width=width,
            default_height=height,
        )

        document.add(lc, single_to_layer_id(layer, document))
        document.extend_page_size((width, height))
    else:
        if layer is not None:
            logging.warning("read: target layer is ignored in multi-layer mode")
        document.extend(
            read_multilayer_svg(
                file,
                quantization=quantization,
                crop=not no_crop,
                simplify=simplify,
                parallel=parallel,
                default_width=width,
                default_height=height,
            )
        )

    return document