Closes #17: use svgwrite.

This commit is contained in:
Sergey Vartanov 2020-08-30 10:41:39 +03:00
parent 715ffcacb1
commit f3a9e149b4
6 changed files with 118 additions and 384 deletions

View file

@ -1,2 +1,4 @@
svgwrite
numpy>=1.18.1
PyYAML>=4.2b1 PyYAML>=4.2b1
urllib3>=1.25.6 urllib3>=1.25.6

View file

@ -4,22 +4,21 @@ Author: Sergey Vartanov (me@enzet.ru).
import os import os
import random import random
import svgwrite
import yaml import yaml
from roentgen import extract_icon from roentgen import extract_icon
from roentgen import svg
from typing import Any, Dict from typing import Any, Dict
def draw_icon( def draw_icon(
output_file, icon: Dict[str, Any], x: float, y: float, svg, icon: Dict[str, Any], x: float, y: float,
color: str = "444444"): color: str = "444444"):
output_file.write( svg.add(svg.path(
f'<path d="{icon["path"]}" ' d=icon["path"], fill=f"#{color}", stroke="none",
f'style="fill:#{color};stroke:none;" ' transform=f'translate({icon["x"] + x},{icon["y"] + y})'))
f'transform="translate({icon["x"] + x},{icon["y"] + y})" />\n')
def draw_grid(): def draw_grid():
@ -103,16 +102,16 @@ def draw_grid():
height = int(number / (width / step) + 1) * step height = int(number / (width / step) + 1) * step
output_file = svg.SVG(open(icon_grid_file_name, "w+")) svg = svgwrite.Drawing(icon_grid_file_name, (width, height))
output_file.begin(width, height)
output_file.rect(0, 0, width, height, color="FFFFFF") svg.add(svg.rect((0, 0), (width, height), fill="#FFFFFF"))
for icon in icons: for icon in icons:
background_color, foreground_color = random.choice(icon_colors) background_color, foreground_color = random.choice(icon_colors)
output_file.rect(x - 2 - 8, y - 2 - 8, 20, 20, color=background_color) svg.add(svg.rect(
(x - 2 - 8, y - 2 - 8), (20, 20), fill=f"#{background_color}"))
for i in icon["icons"]: for i in icon["icons"]:
draw_icon(output_file, i, x, y, foreground_color) draw_icon(svg, i, x, y, foreground_color)
x += step x += step
if x > width - 8: if x > width - 8:
x = step / 2 x = step / 2
@ -121,4 +120,4 @@ def draw_grid():
print(f"Icons: {number}.") print(f"Icons: {number}.")
output_file.end() svg.write(open(icon_grid_file_name, "w"))

View file

@ -3,23 +3,25 @@ Simple OpenStreetMap renderer.
Author: Sergey Vartanov (me@enzet.ru). Author: Sergey Vartanov (me@enzet.ru).
""" """
import numpy as np
import os import os
import random import svgwrite
import sys import sys
import yaml import yaml
import numpy as np from svgwrite.container import Group
from svgwrite.path import Path
from svgwrite.shapes import Circle, Rect
from svgwrite.text import Text
from typing import List
from roentgen import extract_icon from roentgen import extract_icon
from roentgen import ui from roentgen import ui
from roentgen import svg
from roentgen.constructor import Constructor, get_path from roentgen.constructor import Constructor, get_path
from roentgen.flinger import GeoFlinger, Geo from roentgen.flinger import GeoFlinger, Geo
from roentgen.osm_reader import OSMReader
from roentgen.osm_getter import get_osm
from roentgen.grid import draw_grid from roentgen.grid import draw_grid
from roentgen.osm_getter import get_osm
from typing import List from roentgen.osm_reader import Map, OSMReader
ICONS_FILE_NAME: str = "icons/icons.svg" ICONS_FILE_NAME: str = "icons/icons.svg"
TAGS_FILE_NAME: str = "data/tags.yml" TAGS_FILE_NAME: str = "data/tags.yml"
@ -31,7 +33,7 @@ class Painter:
def __init__( def __init__(
self, show_missing_tags, overlap, draw_nodes, mode, draw_captions, self, show_missing_tags, overlap, draw_nodes, mode, draw_captions,
map_, flinger, output_file, icons, scheme): map_, flinger, svg: svgwrite.Drawing, icons, scheme):
self.show_missing_tags = show_missing_tags self.show_missing_tags = show_missing_tags
self.overlap = overlap self.overlap = overlap
@ -41,16 +43,10 @@ class Painter:
self.map_ = map_ self.map_ = map_
self.flinger = flinger self.flinger = flinger
self.output_file = output_file self.svg: svgwrite.Drawing = svg
self.icons = icons self.icons = icons
self.scheme = scheme self.scheme = scheme
def draw_raw_nodes(self):
for node_id in self.map_.node_map:
node = self.map_.node_map[node_id]
flung = self.flinger.fling(node)
self.output_file.circle(flung[0], flung[1], 0.2, color="FFFFFF")
def no_draw(self, key): def no_draw(self, key):
if key in self.scheme["tags_to_write"] or \ if key in self.scheme["tags_to_write"] or \
key in self.scheme["tags_to_skip"]: key in self.scheme["tags_to_skip"]:
@ -125,27 +121,21 @@ class Painter:
#------# #------#
###### ######
""" """
text = text.replace("&", "and")
if out_fill_2: if out_fill_2:
self.output_file.write( self.svg.add(Text(
f'<text x="{x}" y="{y}" style="font-size:' + text, (x, y), font_size=size, text_anchor="middle",
str( font_family="Roboto", fill=f"#{out_fill_2}",
size) + ";text-anchor:middle;font-family:Roboto;fill:#" + stroke_linejoin="round", stroke_width=5,
out_fill_2 + ";stroke-linejoin:round;stroke-width:5;stroke:#" + stroke=f"#{out_fill_2}", opacity=out_opacity_2))
out_fill_2 + ";opacity:" + str(
out_opacity_2) + ';">' + text + "</text>")
if out_fill: if out_fill:
self.output_file.write( self.svg.add(Text(
f'<text x="{x}" y="{y}" style="font-size:' + text, (x, y), font_size=size, text_anchor="middle",
str( font_family="Roboto", fill=f"#{out_fill}",
size) + ";text-anchor:middle;font-family:Roboto;fill:#" + stroke_linejoin="round", stroke_width=3,
out_fill + ";stroke-linejoin:round;stroke-width:3;stroke:#" + stroke=f"#{out_fill}", opacity=out_opacity))
out_fill + ";opacity:" + str( self.svg.add(Text(
out_opacity) + ';">' + text + "</text>") text, (x, y), font_size=size, text_anchor="middle",
self.output_file.write( font_family="Roboto", fill=f"#{fill}"))
f'<text x="{x}" y="{y}" style="font-size:' +
str(size) + ";text-anchor:middle;font-family:Roboto;fill:#" +
fill + ';">' + text + "</text>")
def wr(self, text, x, y, fill, text_y, size=10): def wr(self, text, x, y, fill, text_y, size=10):
text = text[:26] + ("..." if len(text) > 26 else "") text = text[:26] + ("..." if len(text) > 26 else "")
@ -268,16 +258,12 @@ class Painter:
node_2 = self.map_.node_map[way.nodes[i + 1]] node_2 = self.map_.node_map[way.nodes[i + 1]]
flung_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon)) flung_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon))
flung_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon)) flung_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon))
shifted_1 = np.add(flung_1, shift_1)
shifted_2 = np.add(flung_2, shift_2) self.svg.add(self.svg.path(
self.output_file.write( d=("M", np.add(flung_1, shift_1), "L",
f'<path d="M ' np.add(flung_2, shift_1), np.add(flung_2, shift_2),
f"{shifted_1[0]},{shifted_1[1]} L " np.add(flung_1, shift_2), "Z"),
f"{shifted_1[0]},{shifted_1[1]} " fill=f"#{color}", stroke=f"#{color}", stroke_width=1))
f"{shifted_2[0]},{shifted_2[1]} "
f'{shifted_2[0]},{shifted_2[1]} Z" '
f'style="fill:#{color};stroke:#{color};'
f'stroke-width:1;" />\n')
elif way.path: elif way.path:
# TODO: implement # TODO: implement
pass pass
@ -289,34 +275,31 @@ class Painter:
if way.kind == "way": if way.kind == "way":
if way.nodes: if way.nodes:
path = get_path(way.nodes, [0, 0], self.map_, self.flinger) path = get_path(way.nodes, [0, 0], self.map_, self.flinger)
self.output_file.write( self.svg.add(Path(d=path, style=way.style))
f'<path d="{path}" style="' + way.style + '" />\n')
else: else:
self.output_file.write( self.svg.add(Path(d=way.path, style=way.style))
f'<path d="{way.path}" style="' + way.style + '" />\n')
# Building shade # Building shade
self.output_file.write('<g style="opacity:0.1;">\n') building_shade = Group(opacity=0.1)
for way in ways: for way in ways:
if way.kind == "building": if way.kind != "building" or not way.nodes:
if way.nodes: continue
shift = [-5, 5] shift = [-5, 5]
if way.levels: if way.levels:
shift = [-5 * way.levels, 5 * way.levels] shift = [-5 * way.levels, 5 * way.levels]
for i in range(len(way.nodes) - 1): for i in range(len(way.nodes) - 1):
node_1 = self.map_.node_map[way.nodes[i]] node_1 = self.map_.node_map[way.nodes[i]]
node_2 = self.map_.node_map[way.nodes[i + 1]] node_2 = self.map_.node_map[way.nodes[i + 1]]
flung_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon)) flung_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon))
flung_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon)) flung_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon))
self.output_file.write( building_shade.add(Path(
f'<path d="M ' ("M", flung_1, "L", flung_2, np.add(flung_2, shift),
f'{flung_1[0]},{flung_1[1]} L ' np.add(flung_1, shift), "Z"),
f"{flung_2[0]},{flung_2[1]} " fill="#000000", stroke="#000000", stroke_width=1))
f"{flung_2[0] + shift[0]},{flung_2[1] + shift[1]} "
f'{flung_1[0] + shift[0]},{flung_1[1] + shift[1]} Z" ' self.svg.add(building_shade)
f'style="fill:#000000;stroke:#000000;stroke-width:1;" />\n')
self.output_file.write("</g>\n")
# Building walls # Building walls
@ -327,18 +310,16 @@ class Painter:
# Building roof # Building roof
for way in ways: for way in ways:
if way.kind == "building": if way.kind != "building":
if way.nodes: continue
shift = [0, -3] if way.nodes:
if way.levels: shift = [0, -3]
shift = [0 * way.levels, min(-3, -1 * way.levels)] if way.levels:
path = get_path(way.nodes, shift, self.map_, self.flinger) shift = [0 * way.levels, min(-3, -1 * way.levels)]
self.output_file.write( path = get_path(way.nodes, shift, self.map_, self.flinger)
f'<path d="{path}" style="{way.style};opacity:1;" />\n') self.svg.add(Path(d=path, style=way.style, opacity=1))
else: else:
self.output_file.write( self.svg.add(Path(d=way.path, style=way.style, opacity=1))
f'<path d="{way.path}" '
f'style="{way.style};opacity:1;" />\n')
# Trees # Trees
@ -347,11 +328,10 @@ class Painter:
node.tags["natural"] == "tree" and node.tags["natural"] == "tree" and
"diameter_crown" in node.tags): "diameter_crown" in node.tags):
continue continue
self.output_file.circle( self.svg.add(Circle(
float(node.x) + (random.random() - 0.5) * 10, (float(node.x), float(node.y)),
float(node.y) + (random.random() - 0.5) * 10,
float(node.tags["diameter_crown"]) * 1.2, float(node.tags["diameter_crown"]) * 1.2,
color='688C44', fill='688C44', opacity=0.3) fill="#688C44", stroke="#688C44", opacity=0.3))
# All other nodes # All other nodes
@ -386,20 +366,15 @@ class Painter:
def draw_point(self, shape, x, y, fill, size=16, xx=0, yy=0, tags=None): def draw_point(self, shape, x, y, fill, size=16, xx=0, yy=0, tags=None):
x = int(float(x)) x = int(float(x))
y = int(float(y)) y = int(float(y))
self.output_file.write( path = self.svg.path(
'<path d="' + shape + '" style="fill:#' + fill + d=shape, fill=f"#{fill}", fill_opacity=1,
';fill-opacity:1" transform="translate(' + transform=f"translate({x - size / 2.0 - xx * 16},"
str(x - size / 2.0 - xx * 16) + "," + str( f"{y - size / 2.0 - yy * 16})")
y - size / 2.0 - yy * 16) + path.set_desc(title="\n".join(map(lambda x: x + ": " + tags[x], tags)))
')">') self.svg.add(path)
if tags:
self.output_file.write("<title>")
self.output_file.write(
"\n".join(map(lambda x: x + ": " + tags[x], tags)))
self.output_file.write("</title>")
self.output_file.write("</path>\n")
def draw_point_outline(self, shape, x, y, fill, mode="default", size=16, xx=0, yy=0): def draw_point_outline(
self, shape, x, y, fill, mode="default", size=16, xx=0, yy=0):
x = int(float(x)) x = int(float(x))
y = int(float(y)) y = int(float(y))
opacity = 0.5 opacity = 0.5
@ -413,12 +388,12 @@ class Painter:
if Y > 200: if Y > 200:
outline_fill = "000000" outline_fill = "000000"
opacity = 0.7 opacity = 0.7
self.output_file.write( self.svg.add(self.svg.path(
'<path d="' + shape + '" style="fill:#' + outline_fill + ";opacity:" + d=shape, fill=f"#{outline_fill}", opacity=opacity,
str(opacity) + ";" + "stroke:#" + outline_fill + stroke=f"#{outline_fill}", stroke_width=stroke_width,
f';stroke-width:{stroke_width};stroke-linejoin:round;" ' + 'transform="translate(' + stroke_linejoin="round",
str(x - size / 2.0 - xx * 16) + "," + str(y - size / 2.0 - yy * 16) + transform=f"translate({x - size / 2.0 - xx * 16},"
')" />\n') f"{y - size / 2.0 - yy * 16})"))
def check_level_number(tags, level): def check_level_number(tags, level):
@ -453,9 +428,9 @@ def main():
if not options: if not options:
sys.exit(1) sys.exit(1)
background_color = "EEEEEE" background_color = "#EEEEEE"
if options.mode in ["user-coloring", "time"]: if options.mode in ["user-coloring", "time"]:
background_color = "111111" background_color = "#111111"
if options.input_file_name: if options.input_file_name:
input_file_name = options.input_file_name input_file_name = options.input_file_name
@ -480,18 +455,18 @@ def main():
print("Fatal: no such file: " + file_name + ".") print("Fatal: no such file: " + file_name + ".")
sys.exit(1) sys.exit(1)
map_ = osm_reader.parse_osm_file( osm_reader.parse_osm_file(
file_name, parse_ways=options.draw_ways, file_name, parse_ways=options.draw_ways,
parse_relations=options.draw_ways, full=full) parse_relations=options.draw_ways, full=full)
output_file = svg.SVG(open(options.output_file_name, "w+")) map_: Map = osm_reader.map_
w, h = list(map(lambda x: float(x), options.size.split(","))) w, h = list(map(lambda x: float(x), options.size.split(",")))
output_file.begin(w, h) svg: svgwrite.Drawing = \
output_file.write( svgwrite.Drawing(options.output_file_name, size=(w, h))
"<title>Rӧntgen</title><style>path:hover {stroke: #FF0000;}</style>\n")
output_file.rect(0, 0, w, h, color=background_color) svg.add(Rect((0, 0), (w, h), fill=background_color))
min1 = Geo(boundary_box[1], boundary_box[0]) min1 = Geo(boundary_box[1], boundary_box[0])
max1 = Geo(boundary_box[3], boundary_box[2]) max1 = Geo(boundary_box[3], boundary_box[2])
@ -537,18 +512,18 @@ def main():
show_missing_tags=options.show_missing_tags, overlap=options.overlap, show_missing_tags=options.show_missing_tags, overlap=options.overlap,
draw_nodes=options.draw_nodes, mode=options.mode, draw_nodes=options.draw_nodes, mode=options.mode,
draw_captions=options.draw_captions, draw_captions=options.draw_captions,
map_=map_, flinger=flinger, output_file=output_file, icons=icons, map_=map_, flinger=flinger, svg=svg, icons=icons,
scheme=scheme) scheme=scheme)
painter.draw(constructor.nodes, constructor.ways, points) painter.draw(constructor.nodes, constructor.ways, points)
if flinger.space[0] == 0: if flinger.space[0] == 0:
output_file.rect(0, 0, w, flinger.space[1], color="FFFFFF") svg.add(Rect((0, 0), (w, flinger.space[1]), fill="#FFFFFF"))
output_file.rect( svg.add(Rect(
0, h - flinger.space[1], w, flinger.space[1], color="FFFFFF") (0, h - flinger.space[1]), (w, flinger.space[1]), fill="#FFFFFF"))
if flinger.space[1] == 0: if flinger.space[1] == 0:
output_file.rect(0, 0, flinger.space[0], h, color="FFFFFF") svg.add(Rect((0, 0), (flinger.space[0], h), fill="#FFFFFF"))
output_file.rect( svg.add(Rect(
w - flinger.space[0], 0, flinger.space[0], h, color="FFFFFF") (w - flinger.space[0], 0), (flinger.space[0], h), fill="#FFFFFF"))
if options.show_index: if options.show_index:
print(min1.lon, max1.lon) print(min1.lon, max1.lon)
@ -595,12 +570,13 @@ def main():
t2 = flinger.fling(Geo( t2 = flinger.fling(Geo(
min1.lat + (i + 1) * lat_step, min1.lat + (i + 1) * lat_step,
min1.lon + (j + 1) * lon_step)) min1.lon + (j + 1) * lon_step))
output_file.text( svg.add(Text(
((t1 + t2) * 0.5)[0], ((t1 + t2) * 0.5)[1] + 40, str(int(matrix[i][j])),
str(int(matrix[i][j])), size=80, color="440000", (((t1 + t2) * 0.5)[0], ((t1 + t2) * 0.5)[1] + 40),
opacity=0.1, align="center") font_size=80, fill="440000",
opacity=0.1, align="center"))
output_file.end() svg.write(open(options.output_file_name, "w"))
top_missing_tags = \ top_missing_tags = \
sorted(missing_tags.keys(), key=lambda x: -missing_tags[x]) sorted(missing_tags.keys(), key=lambda x: -missing_tags[x])

View file

@ -3,7 +3,7 @@ Reading OpenStreetMap data from XML file.
Author: Sergey Vartanov Author: Sergey Vartanov
""" """
from typing import Any, Dict, List, Optional from typing import Dict, List, Optional
from roentgen import ui from roentgen import ui

View file

@ -1,245 +0,0 @@
"""
Very simple SVG library.
Author: Sergey Vartanov (me@enzet.ru)
"""
import math
class SVG:
def __init__(self, file_):
self.file = file_
self.index = 0
def begin(self, width, height):
self.file.write(
'<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n\n')
self.file.write(
f'''<svg version="1.1" baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ev="http://www.w3.org/2001/xml-events"
bgcolor="#e5e9e6" width="{width}" height="{height}">\n''')
def end(self):
self.file.write('</svg>\n')
def path(
self, path: str, color: str = "black", width: float = 1,
fill: str = "none", end: str = "butt", id: str = None,
color2: str = None, gx1: float = 0, gy1: float = 0, gx2: float = 0,
gy2: float = 0, dash: str = None, dashoffset: str = None,
opacity: float = 1, transform: str = None):
if color2:
self.index += 1
self.file.write(
f'<defs><linearGradient id="grad{str(self.index)}" '
f'x1="{str(gx1)}" y1="{str(gy1)}" '
f'x2="{str(gx2)}" y2="{str(gy2)}" '
f'gradientUnits="userSpaceOnUse">'
f'<stop style="stop-color:{self.get_color(color)};'
f'stop-opacity:1;" offset="0" />'
f'<stop style="stop-color:{self.get_color(color2)};'
f'stop-opacity:1;" offset="1" /></linearGradient></defs>\n')
self.file.write(' <path d = "' + path + '" ')
if id:
self.file.write(f'id="{id}" ')
if transform:
self.file.write('transform="' + transform + '" ')
self.file.write('style = "')
if not color2:
self.file.write('stroke:' + self.get_color(color) + '; ')
else:
self.file.write('stroke:url(#grad' + str(self.index) + '); ')
self.file.write('stroke-width:' + str(width) + '; ')
self.file.write('fill:' + self.get_color(fill) + '; ')
self.file.write('stroke-linecap:' + end + '; ')
if opacity != 1:
self.file.write('opacity:' + str(opacity) + '; ')
if dash:
self.file.write('stroke-dasharray:' + dash + '; ')
if dashoffset:
self.file.write('stroke-dashoffset:' + str(dashoffset) + '; ')
self.file.write('" />\n')
def line(
self, x1, y1, x2, y2, width=1, color='black', end='butt', id_=None,
color2=None, gx1=None, gy1=None, gx2=None, gy2=None, dash=None,
dashoffset=None, opacity=None):
if color2:
if not gx1:
gx1 = x1
if not gy1:
gy1 = y1
if not gx2:
gx2 = x2
if not gy2:
gy2 = y2
self.index += 1
self.file.write(
f'<defs><linearGradient id="grad{str(self.index)}" '
f'x1="{str(gx1)}" y1="{str(gy1)}" x2="{str(gx2)}" '
f'y2="{str(gy2)}" gradientUnits="userSpaceOnUse">\n'
f'<stop style="stop-color:#{str(color)};stop-opacity:1;" '
f'offset="0" />'
f'<stop style=\"stop-color:#{str(color2)};stop-opacity:1;" '
f'offset="1" />'
f'</linearGradient></defs>\n')
self.file.write(
f' <path d = "M {str(x1)},{str(y1)} {str(x2)},{str(y2)}" ')
if id_:
self.file.write(f'id="{id_}" ')
self.file.write('style="')
if not color2:
self.file.write(f'stroke:{self.get_color(color)}; ')
else:
self.file.write(f'stroke:url(#grad{self.index}); ')
self.file.write(f'stroke-width:{width}; ')
self.file.write(f'stroke-linecap:{end}; ')
if dash:
self.file.write('stroke-dasharray:' + dash + '; ')
if dashoffset:
self.file.write('stroke-dashoffset:' + str(dashoffset) + '; ')
if opacity:
self.file.write('opacity: ' + str(opacity) + '; ')
self.file.write('" />\n')
def rect(
self, x, y, width, height, color='black', id=None, rx=0, ry=0,
opacity=1.0, stroke_color='none', stroke_width=1.0):
self.file.write(' <rect x = "' + str(x) + '" y = "' + str(y) + '" rx = "' + str(rx) + '" ry = "' + str(ry) +
'" ')
self.file.write(' width = "' + str(width) + '" height = "' + str(height) + '" ')
if id:
self.file.write('id = "' + id + '" ')
self.file.write('style = "')
if opacity != 1:
self.file.write('opacity:' + str(opacity) + '; ')
self.file.write('fill:' + self.get_color(color) + '; ')
self.file.write('stroke:' + self.get_color(stroke_color) + '; ')
self.file.write('stroke-width:' + str(stroke_width) + '; ')
self.file.write('" />\n')
def curve(self, x1, y1, x2, y2, x3, y3, x4, y4, id=None, width=1, color='black'):
self.file.write(' <path d = "M ' + str(x1) + ',' + str(y1) + ' C ' + str(x2) + ',' + str(y2) + ' ')
self.file.write(str(x3) + ',' + str(y3) + ' ' + str(x4) + ',' + str(y4) + '" ')
self.file.write('style = "')
self.file.write('stroke:' + self.get_color(color) + '; ')
self.file.write('stroke-width:' + str(width) + '; ')
self.file.write('fill: none; ')
self.file.write('" />\n')
def rhombus(self, x, y, width, height, color='black', id=None):
self.file.write(''' <path d = "M %5.1f %5.1f L %5.1f %5.1f L %5.1f %5.1f
L %5.1f %5.1f L %5.1f %5.1f" ''' % (x, y - height, x + width, y, x, y + height, x - width, y, x,
y - height))
if id:
self.file.write('id = "' + id + '" ')
self.file.write('style = "')
self.file.write('fill:' + self.get_color(color) + '; ')
self.file.write('" />\n')
def circle(self, x, y, d, color='black', color2='white', fill='none',
opacity=None, width=1, id_=None, gx=0, gy=0, gr=0, fx=0, fy=0):
is_grad = gx != 0 or gy != 0 or gr != 0
if is_grad:
self.index += 1
self.file.write(
f'<defs><radialGradient id="grad{str(self.index)}" '
f'cx="{gx}%" cy="{gy}%" r="{gr}%" fx="{fx}%" fy="{fy}%">'
f'<stop offset="0%" style="stop-color:rgb(255,255,255);'
f'stop-opacity:0" /><stop offset="100%" '
f'style="stop-color:{self.get_color(color)};stop-opacity:1" />'
f'</radialGradient></defs>\n')
c = 0.577
self.file.write(
f' <path d = "'
f'M {x:5.1f} {y + d:5.1f} '
f'C {x - d * c:5.1f} {y + d:5.1f} {x - d:5.1f} {y + d * c:5.1f} '
f'{x - d:5.1f} {y:5.1f} '
f'C {x - d:5.1f} {y - d * c:5.1f} {x - d * c:5.1f} {y - d:5.1f} '
f'{x:5.1f} {y - d:5.1f} '
f'C {x + d * c:5.1f} {y - d:5.1f} {x + d:5.1f} {y - d * c:5.1f} '
f'{x + d:5.1f} {y:5.1f} '
f'C {x + d:5.1f} {y + d * c:5.1f} {x + d * c:5.1f} {y + d:5.1f} '
f'{x:5.1f} {y + d:5.1f}" ')
if id_:
self.file.write('id = "' + id_ + '" ')
self.file.write('style = "')
if is_grad:
self.file.write('fill:url(#grad' + str(self.index) + '); ')
else:
self.file.write('fill:' + self.get_color(fill) + '; ')
self.file.write('stroke-width:' + str(width) + '; ')
self.file.write('stroke:' + self.get_color(color) + '; ')
if opacity:
self.file.write('opacity:' + str(opacity) + '; ')
self.file.write('" />\n')
def text(self, x, y, text, font='Myriad Pro', size='10', align='left',
color='black', id=None, weight=None, letter_spacing=None, angle=None,
opacity=None):
"""
Drawing SVG <text> element.
"""
if angle is None:
self.file.write(f' <text x="{str(x)}" y="{str(y)}" ')
else:
self.file.write(' <text x="0" y="0" ')
self.file.write('font-size = "' + str(size) + '" ')
if id:
self.file.write('id = "' + id + '" ')
if not (angle is None) and angle <= 0:
align = 'end'
if align == 'right':
align = 'end'
if align == 'center':
align = 'middle'
self.file.write('style = "')
self.file.write('text-anchor:' + align + '; ')
if opacity:
self.file.write('opacity:' + str(opacity) + '; ')
self.file.write('font-family: ' + font + '; ')
self.file.write('fill: ' + self.get_color(color) + '; ')
if weight == 'bold':
self.file.write('font-weight:bold; ')
if letter_spacing:
self.file.write('letter-spacing:' + str(letter_spacing) + '; ')
self.file.write('"')
if not (angle is None):
if math.sin(angle) > 0:
trans = 'transform = "matrix(' + str(math.sin(angle)) + ',' + str(math.cos(angle)) + ',' + \
str(-math.cos(angle)) + ',' + str(math.sin(angle)) + ',' + str(x) + ',' + str(y) + ')"'
else:
trans = 'transform = "matrix(' + str(math.sin(angle + math.pi)) + ',' + str(math.cos(angle + math.pi)) + ',' + \
str(-math.cos(angle + math.pi)) + ',' + str(math.sin(angle + math.pi)) + ',' + str(x) + ',' + str(y) + ')"'
self.file.write(' ' + trans)
self.file.write('>')
self.file.write(text)
self.file.write('</text>\n')
@staticmethod
def get_color(color):
if color == 'none':
return 'none'
if color == 'black':
return 'black'
return '#' + str(color)
def begin_layer(self, name):
self.file.write(f'<g id="{name}" label="{name}" ')
self.file.write('inkscape:groupmode="layer">\n')
def end_layer(self):
self.file.write('</g>\n')
def write(self, raw_code):
self.file.write(raw_code)

View file

@ -8,7 +8,9 @@ from typing import Optional
def parse_options(args): def parse_options(args):
"""
Parse Röntgen command-line options.
"""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
@ -56,9 +58,9 @@ def parse_options(args):
"--show-index", dest="show_index", action="store_true") "--show-index", dest="show_index", action="store_true")
parser.add_argument( parser.add_argument(
"--no-show-index", dest="show_index", action="store_false") "--no-show-index", dest="show_index", action="store_false")
parser.add_argument("--mode", dest="mode", default="normal") parser.add_argument("--mode", default="normal")
parser.add_argument("--seed", dest="seed", default="") parser.add_argument("--seed", default="")
parser.add_argument("--level", dest="level", default=None) parser.add_argument("--level", default=None)
arguments = parser.parse_args(args[1:]) arguments = parser.parse_args(args[1:])