Commit cde84b82 authored by Florian Schäfer's avatar Florian Schäfer

Merge branch 'layer', which adds new WikiLayer

parents a7057d54 43e7c349
......@@ -30,14 +30,14 @@ repositories {
jcenter()
}
dependencies {
packIntoJar "com.fasterxml.jackson.core:jackson-databind:2.9.5"
errorprone "com.google.errorprone:error_prone_core:${versions.errorprone}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${versions.junit}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${versions.junit}"
testImplementation "org.junit.vintage:junit-vintage-engine:${versions.junit}"
testImplementation "com.github.spotbugs:spotbugs-annotations:${versions.spotbugs}"
packIntoJar("com.fasterxml.jackson.core:jackson-databind:2.9.5")
errorprone("com.google.errorprone:error_prone_core:${versions.errorprone}")
testImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}")
testImplementation("org.junit.vintage:junit-vintage-engine:${versions.junit}")
testImplementation("com.github.spotbugs:spotbugs-annotations:${versions.spotbugs}")
testImplementation("org.openstreetmap.josm:josm-unittest:"){changing=true}
testImplementation "com.github.tomakehurst:wiremock:2.18.0"
testImplementation("com.github.tomakehurst:wiremock:2.18.0")
}
task copyToLib(type: Sync) {
from(configurations.packIntoJar)
......
......@@ -26,8 +26,11 @@ import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.gui.Notification;
import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
import org.openstreetmap.josm.tools.HttpClient;
import org.openstreetmap.josm.tools.I18n;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Pair;
import org.openstreetmap.josm.tools.Utils;
......@@ -104,6 +107,13 @@ public final class WikipediaApp {
// parse XML document
try (InputStream in = connect(url).getContent()) {
final Document doc = newDocumentBuilder().parse(in);
final String errorInfo = X_PATH.evaluateString("//error/@info", doc);
if (errorInfo != null && errorInfo.length() >= 1) {
// I18n: {0} is the error message returned by the API
new Notification(I18n.tr("Downloading entries with geo coordinates failed: {0}", errorInfo))
.setIcon(WikipediaPlugin.W_IMAGE.setMaxSize(ImageProvider.ImageSizes.DEFAULT).get())
.show();
}
final List<WikipediaEntry> entries = X_PATH.evaluateNodes("//gs", doc).stream()
.map(node -> {
final String name = X_PATH.evaluateString("@title", node);
......@@ -117,7 +127,7 @@ public final class WikipediaApp {
}
}).collect(Collectors.toList());
if ("wikidata".equals(wikipediaLang)) {
return getLabelForWikidata(entries, Locale.getDefault()).stream().collect(Collectors.toList());
return new ArrayList<>(getLabelForWikidata(entries, Locale.getDefault()));
} else {
return entries;
}
......@@ -362,7 +372,7 @@ public final class WikipediaApp {
}
}
static List<WikidataEntry> getLabelForWikidata(List<? extends WikipediaEntry> entries, Locale locale, String... preferredLanguage) {
static List<WikidataEntry> getLabelForWikidata(final List<? extends WikipediaEntry> entries, final Locale locale, final String... preferredLanguage) {
final Collection<String> languages = new ArrayList<>();
if (locale != null) {
languages.add(getMediawikiLocale(locale));
......@@ -378,19 +388,19 @@ public final class WikipediaApp {
final String url = "https://www.wikidata.org/w/api.php" +
"?action=wbgetentities" +
"&props=labels|descriptions" +
"&ids=" + entries.stream().map(x -> x.article).collect(Collectors.joining("|")) +
"&ids=" + batch.stream().map(x -> x.article).collect(Collectors.joining("|")) +
"&format=xml";
try (InputStream in = connect(url).getContent()) {
final Document xml = newDocumentBuilder().parse(in);
for (final WikipediaEntry entry : entries) {
final Node entity = X_PATH.evaluateNode("//entity[@id='" + entry.article + "']", xml);
for (final WikipediaEntry batchEntry : batch) {
final Node entity = X_PATH.evaluateNode("//entity[@id='" + batchEntry.article + "']", xml);
if (entity == null) {
continue;
}
result.add(new WikidataEntry(
entry.article,
batchEntry.article,
getFirstField(languages, "label", entity),
entry.coordinate,
batchEntry.coordinate,
getFirstField(languages, "description", entity)
));
}
......
......@@ -26,6 +26,7 @@ import org.wikipedia.validator.WikipediaAgainstWikidata;
public final class WikipediaPlugin extends Plugin {
public static final ImageIcon LOGO = ImageProvider.get("dialogs/wikipedia");
public static final ImageProvider W_IMAGE = new ImageProvider("w");
private static String name;
private static String versionInfo;
......
// License: GPL. For details, see LICENSE file.
package org.wikipedia.actions;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import javax.swing.Action;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.gui.SideButton;
import org.openstreetmap.josm.tools.Logging;
public class MultiAction extends JosmAction {
public static SideButton createButton(final String name, final String iconName, final String tooltip, final Action... actions) {
final MultiAction popupAction = new MultiAction(name, iconName, tooltip, actions);
final SideButton button = new SideButton(popupAction);
popupAction.setParent(button);
button.createArrow(it -> button.getAction().actionPerformed(it));
return button;
}
private final JPopupMenu popup;
private Component parent;
private MultiAction(final String name, final String iconName, final String tooltip, final Action... actions) {
// I18n: {0} is the number of available sources
super(name, iconName, tooltip, null, false, false);
popup = new ActionsPopup(actions);
}
@Override
public void actionPerformed(ActionEvent e) {
final Component finalParent = parent;
if (finalParent != null) {
final Rectangle parentBounds = finalParent.getBounds();
popup.show(finalParent, parentBounds.x - parentBounds.width, parentBounds.y);
} else {
Logging.error("The parent component of a MultiAction must not be null!");
}
}
private void setParent(final Component parent) {
this.parent = parent;
}
private static class ActionsPopup extends JPopupMenu {
private ActionsPopup(final Action... actions) {
for (final Action action : actions) {
add(new JMenuItem(action));
}
}
}
}
// License: GPL. For details, see LICENSE file.
package org.wikipedia.actions;
import java.awt.event.ActionEvent;
import java.util.List;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.tools.I18n;
import org.openstreetmap.josm.tools.ImageProvider;
import org.wikipedia.gui.WikiLayer;
import org.wikipedia.gui.WikipediaToggleDialog;
public class ToggleWikiLayerAction extends JosmAction {
private static final ImageProvider ICON_ADDWIKI = new ImageProvider("layer", "addwiki").setMaxSize(ImageProvider.ImageSizes.SIDEBUTTON);
private static final ImageProvider ICON_REMOVEWIKI = new ImageProvider("layer", "removewiki").setMaxSize(ImageProvider.ImageSizes.SIDEBUTTON);
private final WikipediaToggleDialog dialog;
public ToggleWikiLayerAction(final WikipediaToggleDialog dialog) {
super(
I18n.tr("Wiki layer"),
ICON_ADDWIKI,
I18n.tr("Toggle the layer displaying Wikipedia articles or Wikidata items"),
null,
false,
null,
true
);
this.dialog = dialog;
}
@Override
public synchronized void actionPerformed(ActionEvent e) {
final List<WikiLayer> wikiLayers = MainApplication.getLayerManager().getLayersOfType(WikiLayer.class);
if (wikiLayers.size() <= 0) {
MainApplication.getLayerManager().addLayer(new WikiLayer(dialog));
} else {
for (WikiLayer layer : wikiLayers) {
MainApplication.getLayerManager().removeLayer(layer);
}
}
}
@Override
protected boolean listenToSelectionChange() {
return false;
}
@Override
public void updateEnabledState() {
final ImageProvider provider;
if (MainApplication.getLayerManager().getLayersOfType(WikiLayer.class).size() <= 0) {
provider = ICON_ADDWIKI;
} else {
provider = ICON_REMOVEWIKI;
}
provider.getResource().attachImageIcon(this, true);
}
}
// License: GPL. For details, see LICENSE file.
package org.wikipedia.gui;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.OptionalDouble;
import java.util.stream.Collectors;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
import org.openstreetmap.josm.gui.layer.Layer;
import org.openstreetmap.josm.tools.I18n;
import org.openstreetmap.josm.tools.ImageProvider;
import org.wikipedia.data.WikipediaEntry;
import org.wikipedia.tools.WikiProperties;
public class WikiLayer extends Layer implements ListDataListener {
private static final Icon LAYER_ICON = new ImageProvider("w").setMaxSize(ImageProvider.ImageSizes.LAYER).get();
private static final Color MARKER_FILL_COLOR = new Color(200, 9, 9, 180);
private static final Color MARKER_STROKE_COLOR = new Color(255, 255, 255);
private static final Color MARKER_FILL_SELECTED_COLOR = new Color(45, 204, 123, 180);
private static final Color MARKER_STROKE_SELECTED_COLOR = new Color(255, 255, 0);
private static final double MIN_MARKER_HEIGHT = 10.0;
private static double markerHeight;
static {
WikiProperties.WIKI_LAYER_MARKER_HEIGHT.addListener(it -> setMarkerHeight(it.getProperty().get()));
setMarkerHeight(WikiProperties.WIKI_LAYER_MARKER_HEIGHT.get());
}
private final WikipediaToggleDialog wikiDialog;
public WikiLayer(final WikipediaToggleDialog wikiDialog) {
super("WikiLayer");
this.wikiDialog = wikiDialog;
wikiDialog.model.addListDataListener(this);
wikiDialog.list.addListSelectionListener(it -> invalidate());
}
private static double getMarkerWidth() {
return markerHeight / 3 * 2;
}
private static double getMarkerHeight() {
return markerHeight;
}
private static void setMarkerHeight(final double markerHeight) {
WikiLayer.markerHeight = Math.max(MIN_MARKER_HEIGHT, markerHeight);
}
@Override
public Icon getIcon() {
return LAYER_ICON;
}
@Override
public Object getInfoComponent() {
return MessageFormat.format("{0} elements with coordinates, {1} elements with missing coordinates are not displayed", wikiDialog.model.getSize(), Collections.list(wikiDialog.model.elements()).stream().filter(it -> it.coordinate == null).count());
}
@Override
public String getToolTipText() {
return I18n.tr("The Wikipedia/Wikidata layer");
}
@Override
public void mergeFrom(Layer from) {
throw new UnsupportedOperationException(MessageFormat.format("Layer of type {0} is not mergable!", WikiLayer.class.getSimpleName()));
}
@Override
public boolean isMergable(Layer other) {
return false;
}
@Override
public void visitBoundingBox(BoundingXYVisitor v) {
for (WikipediaEntry entry : Collections.list(wikiDialog.model.elements())) {
v.visit(entry.coordinate);
}
}
@Override
public Action[] getMenuEntries() {
return new Action[]{new LayerListPopup.InfoAction(this)};
}
@Override
public void paint(final Graphics2D g, final MapView mv, final Bounds bbox) {
// Expand bbox to also paint markers that are only partially visible (marker is centered above the location, 20px wide, 30px tall)
final Point maxPoint = mv.getPoint(bbox.getMax());
bbox.extend(mv.getLatLon(maxPoint.getX() + 10, maxPoint.getY() + 30));
final Point minPoint = mv.getPoint(bbox.getMin());
bbox.extend(mv.getLatLon(minPoint.getX() - 10, minPoint.getY() + 30));
final Collection<WikipediaEntry> selectedEntries = wikiDialog.list.getSelectedValuesList();
final Collection<Collection<Point>> entriesInBbox = Collections.list(wikiDialog.model.elements()).parallelStream()
.filter(it -> it.coordinate != null && bbox.contains(it.coordinate) && !selectedEntries.contains(it))
.map(it -> mv.getPoint(it.coordinate))
.collect(new WikiLayerClusteringCollector(getMarkerWidth(), getMarkerHeight()));
paintWikiMarkers(g, entriesInBbox, false);
paintWikiMarkers(g, selectedEntries.stream().map(it -> Collections.singleton(mv.getPoint(it.coordinate))).collect(Collectors.toList()), true);
}
private void paintWikiMarkers(final Graphics2D g, final Collection<Collection<Point>> clusters, final boolean selected) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g.setStroke(new BasicStroke(selected ? 3 : 2));
for (final Collection<Point> cluster: clusters) {
if (cluster.size() <= 1) {
final Point point = cluster.iterator().next();
final Path2D path = new Path2D.Double();
path.moveTo(point.getX(), point.getY());
path.append(new Arc2D.Double(point.getX() - markerHeight / 3, point.getY() - markerHeight, markerHeight / 3 * 2, markerHeight / 3 * 2, -30, 240.0, Arc2D.OPEN), true);
path.closePath();
g.setColor(selected ? MARKER_FILL_SELECTED_COLOR : MARKER_FILL_COLOR);
g.fill(path);
g.setColor(selected ? MARKER_STROKE_SELECTED_COLOR : MARKER_STROKE_COLOR);
g.draw(path);
} else {
OptionalDouble avgX = cluster.stream().mapToDouble(Point::getX).average();
OptionalDouble avgY = cluster.stream().mapToDouble(Point::getY).average();
avgX.ifPresent(x -> avgY.ifPresent(y -> {
g.setColor(selected ? MARKER_STROKE_SELECTED_COLOR : MARKER_STROKE_COLOR);
final Ellipse2D ellipse = new Ellipse2D.Double(x - getMarkerHeight() / 2, y - getMarkerHeight() / 2, getMarkerHeight(), getMarkerHeight());
g.fill(ellipse);
g.setColor(selected ? MARKER_FILL_SELECTED_COLOR : MARKER_FILL_COLOR);
g.draw(ellipse);
g.setFont(g.getFont().deriveFont((float) getMarkerWidth() * .5f));
final String label = cluster.size() + "";
final int labelWidth = g.getFontMetrics().stringWidth(label);
g.drawString(
label,
(int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, x - labelWidth / 2)),
(int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, y + getMarkerWidth() * .2))
);
}));
}
}
}
@Override
public void intervalAdded(ListDataEvent e) {
invalidate();
}
@Override
public void intervalRemoved(ListDataEvent e) {
invalidate();
}
@Override
public void contentsChanged(ListDataEvent e) {
invalidate();
}
}
// License: GPL. For details, see LICENSE file.
package org.wikipedia.gui;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import org.openstreetmap.josm.tools.Pair;
public class WikiLayerClusteringCollector implements Collector<Point, Map<Pair<Integer, Integer>, Collection<Point>>, Collection<Collection<Point>>> {
private final int gridWidth;
private final int gridHeight;
WikiLayerClusteringCollector(final double markerWidth, final double markerHeight) {
this.gridWidth = (int) Math.min(Integer.MAX_VALUE, 3 * markerWidth);
this.gridHeight = (int) Math.min(Integer.MAX_VALUE, 3 * markerHeight);
}
@Override
public Supplier<Map<Pair<Integer, Integer>, Collection<Point>>> supplier() {
return ConcurrentHashMap::new;
}
@Override
public BiConsumer<Map<Pair<Integer, Integer>, Collection<Point>>, Point> accumulator() {
return (result, p) -> {
final Pair<Integer, Integer> gridPos = Pair.create(p.x / gridWidth, p.y / gridHeight);
if (!result.containsKey(gridPos)) {
result.put(gridPos, new ArrayList<>());
}
result.get(gridPos).add(p);
};
}
@Override
public BinaryOperator<Map<Pair<Integer, Integer>, Collection<Point>>> combiner() {
return (a, b) -> {
for (final Map.Entry<Pair<Integer, Integer>, Collection<Point>> bEntry : b.entrySet()) {
if (a.containsKey(bEntry.getKey())) {
a.get(bEntry.getKey()).addAll(bEntry.getValue());
} else {
a.put(bEntry.getKey(), bEntry.getValue());
}
}
return a;
};
}
@Override
public Function<Map<Pair<Integer, Integer>, Collection<Point>>, Collection<Collection<Point>>> finisher() {
return Map::values;
}
@Override
public Set<Characteristics> characteristics() {
final HashSet<Characteristics> characteristics = new HashSet<>(2);
Collections.addAll(characteristics, Characteristics.UNORDERED);
return characteristics;
}
}
......@@ -15,6 +15,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
......@@ -42,12 +43,15 @@ import org.openstreetmap.josm.gui.SideButton;
import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
import org.openstreetmap.josm.tools.I18n;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.LanguageInfo;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.OpenBrowser;
import org.wikipedia.WikipediaApp;
import org.wikipedia.actions.FetchWikidataAction;
import org.wikipedia.actions.MultiAction;
import org.wikipedia.actions.ToggleWikiLayerAction;
import org.wikipedia.data.WikipediaEntry;
import org.wikipedia.tools.ListUtil;
......@@ -55,13 +59,24 @@ public class WikipediaToggleDialog extends ToggleDialog implements ActiveLayerCh
public WikipediaToggleDialog() {
super(tr("Wikipedia"), "wikipedia", tr("Fetch Wikipedia articles with coordinates"), null, 150);
final Action[] downloadActions = {
new WikipediaLoadCoordinatesAction(false),
new WikipediaLoadCoordinatesAction(true),
new WikipediaLoadCategoryAction()
};
createLayout(list, true, Arrays.asList(
new SideButton(new WikipediaLoadCoordinatesAction(false)),
new SideButton(new WikipediaLoadCoordinatesAction(true)),
new SideButton(new WikipediaLoadCategoryAction()),
new SideButton(new ToggleWikiLayerAction(this)),
MultiAction.createButton(
I18n.tr("Download elements"),
"download",
I18n.tr("Download all elements in the current viewport from one of {0} sources", downloadActions.length),
downloadActions
),
new SideButton(new PasteWikipediaArticlesAction()),
new SideButton(new AddWikipediaTagAction(list)),
new SideButton(new WikipediaSettingsAction(), false)));
new SideButton(new WikipediaSettingsAction(), false)
));
updateTitle();
}
......
// License: GPL. For details, see LICENSE file.
package org.wikipedia.tools;
import org.openstreetmap.josm.data.preferences.DoubleProperty;
public final class WikiProperties {
public static final DoubleProperty WIKI_LAYER_MARKER_HEIGHT = new DoubleProperty("wikipedia.layer.marker_height", 30.0);
private WikiProperties() {
// Private constructor to avoid instantiation
}
}
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24px" height="24px" viewBox="0 0 24 24">
<path d="m1.5,16.5h17l4,-10h-17z" fill="#ddd" stroke="#000"/>
<g transform="translate(7, 8) skewX(-22) scale(.4)">
<path d="M10.76 3.62l-.5-1C9.44.98 9.46.9 8.62.78 8.37.74 8.25.71 8.25.6V.07l.07-.05h4.91l.13.04v.52c0 .12-.08.18-.26.18l-.35.06c-.9.07-.76.43-.16 1.62l5.68 11.62.2.05L23.5 2.15c.18-.48.15-.82-.07-1.02-.23-.2-.39-.32-.97-.34l-.48-.02a.27.27 0 0 1-.16-.06.17.17 0 0 1-.08-.15v-.5l.07-.05h5.7l.05.05v.5c0 .14-.07.2-.23.2-.74.04-1.3.2-1.65.49-.36.29-.64.69-.84 1.21 0 0-4.64 10.6-6.22 14.14-.61 1.15-1.2 1.05-1.73-.04-1.11-2.28-4.24-9.21-4.24-9.21z"/>
<path d="M19.85.01h-4.63l-.07.05v.5c0 .06.03.1.08.14.05.04.1.06.17.06l.23.03c.58.02.85.17.93.27.14.17.2.35-.13 1.09l-5.98 11.93-.16-.04S6.01 5 4.77 1.97a2.04 2.04 0 0 1-.19-.69c0-.3.28-.47.83-.5l.65-.02c.17 0 .25-.07.25-.2v-.5L6.26.02H.06L.02.07V.6c0 .1.12.15.36.18.65.04 1.08.14 1.28.31.2.18.42.62.71 1.3C3.92 6.14 7.23 12.8 8.84 16.6c.46 1.04 1.04 1.2 1.75-.03 1.23-2.27 4.55-9.22 4.55-9.22l2.62-4.9c.3-.51.6-.97.75-1.19.27-.39.42-.46 1.17-.5.15 0 .23-.07.23-.2v-.5z"/>
</g>
<path d="m15.5,11.5h3v4h4v3h-4v4h-3v-4h-4v-3h4z" fill="#53f428" stroke="#419a28"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24px" height="24px" viewBox="0 0 24 24">
<path d="m1.5,16.5h17l4,-10h-17z" fill="#ddd" stroke="#000"/>
<g transform="translate(7, 8) skewX(-22) scale(.4)">
<path d="M10.76 3.62l-.5-1C9.44.98 9.46.9 8.62.78 8.37.74 8.25.71 8.25.6V.07l.07-.05h4.91l.13.04v.52c0 .12-.08.18-.26.18l-.35.06c-.9.07-.76.43-.16 1.62l5.68 11.62.2.05L23.5 2.15c.18-.48.15-.82-.07-1.02-.23-.2-.39-.32-.97-.34l-.48-.02a.27.27 0 0 1-.16-.06.17.17 0 0 1-.08-.15v-.5l.07-.05h5.7l.05.05v.5c0 .14-.07.2-.23.2-.74.04-1.3.2-1.65.49-.36.29-.64.69-.84 1.21 0 0-4.64 10.6-6.22 14.14-.61 1.15-1.2 1.05-1.73-.04-1.11-2.28-4.24-9.21-4.24-9.21z"/>
<path d="M19.85.01h-4.63l-.07.05v.5c0 .06.03.1.08.14.05.04.1.06.17.06l.23.03c.58.02.85.17.93.27.14.17.2.35-.13 1.09l-5.98 11.93-.16-.04S6.01 5 4.77 1.97a2.04 2.04 0 0 1-.19-.69c0-.3.28-.47.83-.5l.65-.02c.17 0 .25-.07.25-.2v-.5L6.26.02H.06L.02.07V.6c0 .1.12.15.36.18.65.04 1.08.14 1.28.31.2.18.42.62.71 1.3C3.92 6.14 7.23 12.8 8.84 16.6c.46 1.04 1.04 1.2 1.75-.03 1.23-2.27 4.55-9.22 4.55-9.22l2.62-4.9c.3-.51.6-.97.75-1.19.27-.39.42-.46 1.17-.5.15 0 .23-.07.23-.2v-.5z"/>
</g>
<path d="m11.5,15.5h11v3h-11z" fill="#fa0d0d" stroke="#a41111"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200" viewBox="-1.22 -1.27 30 20">
<path d="M10.76 3.62l-.5-1C9.44.98 9.46.9 8.62.78 8.37.74 8.25.71 8.25.6V.07l.07-.05h4.91l.13.04v.52c0 .12-.08.18-.26.18l-.35.06c-.9.07-.76.43-.16 1.62l5.68 11.62.2.05L23.5 2.15c.18-.48.15-.82-.07-1.02-.23-.2-.39-.32-.97-.34l-.48-.02a.27.27 0 0 1-.16-.06.17.17 0 0 1-.08-.15v-.5l.07-.05h5.7l.05.05v.5c0 .14-.07.2-.23.2-.74.04-1.3.2-1.65.49-.36.29-.64.69-.84 1.21 0 0-4.64 10.6-6.22 14.14-.61 1.15-1.2 1.05-1.73-.04-1.11-2.28-4.24-9.21-4.24-9.21z"/>
<path d="M19.85.01h-4.63l-.07.05v.5c0 .06.03.1.08.14.05.04.1.06.17.06l.23.03c.58.02.85.17.93.27.14.17.2.35-.13 1.09l-5.98 11.93-.16-.04S6.01 5 4.77 1.97a2.04 2.04 0 0 1-.19-.69c0-.3.28-.47.83-.5l.65-.02c.17 0 .25-.07.25-.2v-.5L6.26.02H.06L.02.07V.6c0 .1.12.15.36.18.65.04 1.08.14 1.28.31.2.18.42.62.71 1.3C3.92 6.14 7.23 12.8 8.84 16.6c.46 1.04 1.04 1.2 1.75-.03 1.23-2.27 4.55-9.22 4.55-9.22l2.62-4.9c.3-.51.6-.97.75-1.19.27-.39.42-.46 1.17-.5.15 0 .23-.07.23-.2v-.5z"/>
</svg>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment