/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package sagan.projects.support; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.List; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.xmlbeam.XBProjector; import org.xmlbeam.XBProjector.Flags; import org.xmlbeam.config.DefaultXMLFactoriesConfig; import org.xmlbeam.config.DefaultXMLFactoriesConfig.NamespacePhilosophy; import sagan.projects.Project; import sagan.projects.ProjectRelease; import sagan.projects.ProjectRelease.ReleaseStatus; import sagan.projects.support.BadgeSvg.GraphicElement; import sagan.projects.support.BadgeSvg.Path; import com.google.common.io.Resources; /** * Service to generate SVG badges. * * @author Mark Paluch */ @Service public class VersionBadgeService { private final URL PRERELEASE_TEMPLATE = Resources.getResource("badge/milestone.svg"); private final URL GENERAL_AVAILABILITY_TEMPLATE = Resources.getResource("badge/release.svg"); private final URL SNAPSHOT_TEMPLATE = Resources.getResource("badge/snapshot.svg"); private final URL VERDANA_FONT = Resources.getResource("badge/Verdana.ttf"); private final BufferedImage DUMMY = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); private Graphics graphics; private XBProjector xbProjector; @PostConstruct public void postConstruct() throws Exception { Font font; try (InputStream is = VERDANA_FONT.openStream()) { font = Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(0, 11); } graphics = DUMMY.getGraphics(); graphics.setFont(font); DefaultXMLFactoriesConfig myConfig = new DefaultXMLFactoriesConfig(); myConfig.setNamespacePhilosophy(NamespacePhilosophy.NIHILISTIC); xbProjector = new XBProjector(myConfig, Flags.TO_STRING_RENDERS_XML); } @PreDestroy public void preDestroy() { graphics.dispose(); } /** * Create a version badge for a given {@link Project} and {@link ProjectRelease}. The badge uses SVG and is returned as byte * array. * * @param project must not be {@literal null}. * @param projectRelease must not be {@literal null}. * @return * @throws IOException */ public byte[] createSvgBadge(Project project, ProjectRelease projectRelease) throws IOException { Assert.notNull(project, "Project must not be null!"); Assert.notNull(projectRelease, "ProjectRelease must not be null!"); URL template = getTemplate(projectRelease.getReleaseStatus()); BadgeSvg svgDocument = xbProjector.io().url(template.toString()).read(BadgeSvg.class); List<Path> paths = svgDocument.getPaths(); String label = project.getName(); String version = projectRelease.getVersion(); return createSvgBadge(svgDocument, paths, label, version); } private byte[] createSvgBadge(BadgeSvg svgDocument, List<Path> paths, String label, String version) { List<GraphicElement> graphicElements = svgDocument.getGraphicElements(); graphicElements.get(0).setText(label); graphicElements.get(1).setText(label); graphicElements.get(2).setText(version); graphicElements.get(3).setText(version); int labelMarginLeft = 6; int labelMarginRight = 4; int versionMarginLeft = 4; int versionMarginRight = 6; int labelTextWidth; int versionTextWidth; synchronized (graphics) { FontMetrics fontMetrics = graphics.getFontMetrics(); labelTextWidth = fontMetrics.stringWidth(label); versionTextWidth = fontMetrics.stringWidth(version); } int labelWidth = labelTextWidth + labelMarginLeft + labelMarginRight; int versionWidth = versionTextWidth + versionMarginLeft + versionMarginRight; svgDocument.setWidth(labelWidth + versionWidth); svgDocument.getMask().setWidth(labelWidth + versionWidth); graphicElements.get(0).setXPosition(labelMarginLeft + (labelTextWidth / 2)); graphicElements.get(1).setXPosition(labelMarginLeft + (labelTextWidth / 2)); graphicElements.get(2).setXPosition(labelWidth + versionMarginLeft + (versionTextWidth / 2)); graphicElements.get(3).setXPosition(labelWidth + versionMarginLeft + (versionTextWidth / 2)); paths.get(0).setDraw(String.format("M0 0h%dv20H0z", labelWidth)); paths.get(1).setDraw(String.format("M%d 0h%dv20H%dz", labelWidth, versionWidth, labelWidth)); paths.get(2).setDraw(String.format("M0 0h%dv20H0z", labelWidth + versionWidth)); return svgDocument.toString().getBytes(); } private URL getTemplate(ReleaseStatus releaseStatus) { switch (releaseStatus) { case GENERAL_AVAILABILITY: return GENERAL_AVAILABILITY_TEMPLATE; case PRERELEASE: return PRERELEASE_TEMPLATE; default: return SNAPSHOT_TEMPLATE; } } }