def output_to_html_element(mro: gws.MapRenderOutput, wrap='relative') -> gws.XmlElement: w, h = mro.view.size_mm css_size = f'left:0;top:0;width:{int(w)}mm;height:{int(h)}mm' css_abs = f'position:absolute;{css_size}' tags: t.List[gws.XmlElement] = [] for plane in mro.planes: if plane.type == 'image': img_path = gws.tempname('mro.png') plane.image.to_path(img_path) tags.append(xml2.tag('img', {'style': css_abs, 'src': img_path})) if plane.type == 'path': tags.append(xml2.tag('img', {'style': css_abs, 'src': plane.path})) if plane.type == 'svg': tags.append( gws.lib.svg.fragment_to_element(plane.elements, {'style': css_abs})) css_div = None if wrap and wrap in {'relative', 'absolute', 'fixed'}: css_div = f'position:{wrap};overflow:hidden;{css_size}' return xml2.tag('div', {'style': css_div}, *tags)
def _marker(uid, sv: gws.StyleValues) -> gws.XmlElement: size = sv.marker_size or DEFAULT_MARKER_SIZE size2 = size // 2 content = None atts: dict = {} _add_paint_atts(atts, sv, 'marker_') if sv.marker == 'circle': atts.update({ 'cx': size2, 'cy': size2, 'r': size2, }) content = 'circle', atts if content: return xml2.tag( 'marker', { 'id': uid, 'viewBox': f'0 0 {size} {size}', 'refX': size2, 'refY': size2, 'markerUnits': 'userSpaceOnUse', 'markerWidth': size, 'markerHeight': size, }, content)
def soup_to_fragment(view: gws.MapView, points: t.List[gws.Point], tags: t.List[t.Any]) -> t.List[gws.XmlElement]: """Convert an svg "soup" to a list of XmlElements. A soup has two components: - a list of points, in the map coordinate system - a list of tuples suitable for `xml2.tag` input (tag-name, {atts}, child1, child2....) The idea is to represent client-side svg drawings (e.g. dimensions) in a resolution-independent way First, points are converted to pixels using the view's transform. Then, each tag's attributes are iterated. If any attribute value is an array, it's assumed to be a 'function'. The first element is a function name, the rest are arguments. Attribute 'functions' are - ['x', n] - returns points[n][0] - ['y', n] - returns points[n][1] - ['r', p1, p2, r] - computes a slope between points[p1] points[p2] and returns a string `rotate(slope, points[r].x, points[r].y)` """ trans = gws.gis.render.map_view_transformer(view) px = [trans(*p) for p in points] def eval_func(v): if v[0] == 'x': return round(px[v[1]][0]) if v[0] == 'y': return round(px[v[1]][1]) if v[0] == 'r': a = _slope(px[v[1]], px[v[2]]) adeg = math.degrees(a) x, y = px[v[3]] return f'rotate({adeg:.0f}, {x:.0f}, {y:.0f})' def convert(tag): for arg in tag: if isinstance(arg, (list, tuple)): convert(arg) elif isinstance(arg, dict): for k, v in arg.items(): if isinstance(v, (list, tuple)): arg[k] = eval_func(v) els = [] for tag in tags: convert(tag) els.append(xml2.tag(*tag)) return els
def shape_to_element(shape: gws.IShape, precision=0, axis: gws.Axis = gws.AXIS_XY, crs_format: gws.CrsFormat = gws.CrsFormat.URN, with_ns='gml') -> gws.XmlElement: """Convert a Shape to a GML3 geometry element.""" geom: shapely.geometry.base.BaseGeometry = getattr(shape, 'geom') srs = shape.crs.to_string(crs_format) ns = (with_ns + ':') if with_ns else '' opts = gws.Data(precision=precision, axis=axis, ns=ns) return xml2.tag(*_tag(geom, opts), srsName=srs)
def _geometry(geom: shapely.geometry.base.BaseGeometry, atts: dict = None) -> gws.XmlElement: def _xy(xy): x, y = xy return f'{x} {y}' def _lpath(coords): ps = [] cs = iter(coords) for c in cs: ps.append(f'M {_xy(c)}') break for c in cs: ps.append(f'L {_xy(c)}') return ' '.join(ps) gt = _geom_type(geom) if gt == _TYPE_POINT: g = t.cast(shapely.geometry.Point, geom) return xml2.tag('circle', {'cx': int(g.x), 'cy': int(g.y)}, atts) if gt == _TYPE_LINESTRING: g = t.cast(shapely.geometry.LineString, geom) d = _lpath(g.coords) return xml2.tag('path', {'d': d}, atts) if gt == _TYPE_POLYGON: g = t.cast(shapely.geometry.Polygon, geom) d = ' '.join( _lpath(interior.coords) + ' z' for interior in g.interiors) d = _lpath(g.exterior.coords) + ' z ' + d return xml2.tag('path', {'fill-rule': 'evenodd', 'd': d.strip()}, atts) if gt > _TYPE_MULTI: g = t.cast(shapely.geometry.base.BaseMultipartGeometry, geom) return xml2.tag('g', *[_geometry(p, atts) for p in g.geoms])
def _label_text(cx, cy, label, sv: gws.StyleValues) -> gws.XmlElement: font_name = _font_name(sv) font_size = sv.label_font_size or DEFAULT_FONT_SIZE font = gws.lib.font.from_name(font_name, font_size) anchor = 'start' if sv.label_align == 'right': anchor = 'end' elif sv.label_align == 'center': anchor = 'middle' atts = {'text-anchor': anchor} _add_font_atts(atts, sv, 'label_') _add_paint_atts(atts, sv, 'label_') lines = label.split('\n') _, em_height = font.getsize('MMM') metrics = [font.getsize(s) for s in lines] line_height = sv.label_line_height or 1 padding = sv.label_padding or [0, 0, 0, 0] ly = cy - padding[2] lx = cx if anchor == 'start': lx += padding[3] elif anchor == 'end': lx -= padding[1] else: lx += padding[3] // 2 height = em_height * len(lines) + line_height * ( len(lines) - 1) + padding[0] + padding[2] pad_bottom = metrics[-1][1] - em_height if pad_bottom > 0: height += pad_bottom ly -= pad_bottom spans = [] for s in reversed(lines): spans.append(['tspan', {'x': lx, 'y': ly}, s]) ly -= (em_height + line_height) tags = [] tags.append(('text', atts, *reversed(spans))) # @TODO a hack to emulate 'paint-order' which wkhtmltopdf doesn't seem to support # place a copy without the stroke above the text if atts.get('stroke'): no_stroke_atts = { k: v for k, v in atts.items() if not k.startswith('stroke') } tags.append(('text', no_stroke_atts, *reversed(spans))) # @TODO label backgrounds don't really work if sv.label_background: width = max(xy[0] for xy in metrics) + padding[1] + padding[3] if anchor == 'start': bx = cx elif anchor == 'end': bx = cx - width else: bx = cx - width // 2 ratts = { 'x': bx, 'y': cy - height, 'width': width, 'height': height, 'fill': sv.label_background, } tags.insert(0, ('rect', ratts)) # a hack to move labels forward: emit a (non-supported) z-index attribute # and sort elements by it later on (see `fragment_to_element`) return xml2.tag('g', {'z-index': 100}, *tags)
def to_xml(ARGS, tag): if ARGS.with_soap: tag = 'soap:Envelope', ('soap:Header', ''), ('soap:Body', tag) el = xml2.tag(*tag) xml = xml2.to_string(el, with_xml=True, with_xmlns=True, with_schemas=True) return gws.ContentResponse(content=xml, mime=gws.lib.mime.XML)
def sanitize_element(el: gws.XmlElement) -> t.Optional[gws.XmlElement]: children = gws.compact(_sanitize(c) for c in el.children) if children: return xml2.tag('svg', _sanitize_atts(el.attributes), *children)
def fragment_to_element(fragment: t.List[gws.XmlElement], atts: dict = None) -> gws.XmlElement: fr = sorted(fragment, key=lambda el: el.attributes.get('z-index', 0)) return xml2.tag('svg', _SVG_TAG_ATTS, atts, *fr)