AsciiDocIcon.java
package pro.verron.asciidoc.converters.svg;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/// Provides SVG paths for icons used in simulated editor interfaces.
/// Icons are sourced from Bootstrap Icons (MIT License).
///
/// Icon path data is lazily loaded from individual SVG resource files
/// in the {@code icons/} package directory and cached on first access.
public final class AsciiDocIcon {
private static final ConcurrentHashMap<Icon, List<String>> CACHE =
new ConcurrentHashMap<>();
private AsciiDocIcon() {
throw new UnsupportedOperationException("Utility class");
}
/// Finds an icon and returns it as an SVG group model.
/// The icon's path data is loaded from its resource file on first
/// access and cached for subsequent calls.
///
/// @param icon the icon identifier
/// @param x x coordinate
/// @param y y coordinate
/// @param size icon size (width and height)
/// @param color icon color
///
/// @return an [Optional] containing the [SvgGroup] for the icon,
/// or empty if the resource could not be found
public static Optional<SvgGroup> findIcon(
Icon icon,
int x,
int y,
int size,
String color
) {
var paths = CACHE.computeIfAbsent(icon, AsciiDocIcon::loadPaths);
if (paths == null || paths.isEmpty()) return Optional.empty();
var transform = String.format(Locale.ROOT,
"translate(%d, %d) scale(%f)", x, y, size / 16.0);
var elements = paths.stream()
.map(d -> (SvgElement) new SvgPath(d, color))
.toList();
return Optional.of(new SvgGroup(transform, elements));
}
private static List<String> loadPaths(Icon icon) {
var resource = AsciiDocIcon.class.getResource(icon.resourceName());
if (resource == null) return null;
try (var in = resource.openStream()) {
return extractAllPathData(new String(in.readAllBytes()));
} catch (IOException e) {
throw new UncheckedIOException(
"Failed to read icon resource: " + icon, e);
}
}
/// Extracts all {@code d} attribute values from {@code <path>}
/// elements in an SVG document.
private static List<String> extractAllPathData(String svg) {
var paths = new ArrayList<String>();
int from = 0;
while (true) {
var dStart = svg.indexOf("d=\"", from);
if (dStart < 0) break;
var valueStart = dStart + 3;
var valueEnd = svg.indexOf('"', valueStart);
if (valueEnd < 0) break;
paths.add(svg.substring(valueStart, valueEnd)
.replaceAll("\\s+", " ")
.trim());
from = valueEnd + 1;
}
return paths;
}
}