| /******************************************************************************* |
| * (c) Copyright 2015 l33t labs LLC and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * l33t labs LLC and others - initial contribution |
| *******************************************************************************/ |
| |
| package org.eclipse.images.renderer; |
| |
| import java.awt.RenderingHints; |
| import java.awt.image.BufferedImage; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.concurrent.ForkJoinPool; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.stream.Collectors; |
| |
| import javax.imageio.ImageIO; |
| |
| import org.apache.batik.dom.svg.SAXSVGDocumentFactory; |
| import org.apache.batik.gvt.renderer.ImageRenderer; |
| import org.apache.batik.gvt.renderer.StaticRenderer; |
| import org.apache.batik.transcoder.ErrorHandler; |
| import org.apache.batik.transcoder.TranscoderException; |
| import org.apache.batik.transcoder.TranscoderInput; |
| import org.apache.batik.transcoder.TranscoderOutput; |
| import org.apache.batik.transcoder.image.PNGTranscoder; |
| import org.apache.batik.util.XMLResourceDescriptor; |
| import org.apache.maven.plugin.AbstractMojo; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugin.logging.Log; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.svg.SVGDocument; |
| |
| import com.jhlabs.image.ContrastFilter; |
| import com.jhlabs.image.GrayscaleFilter; |
| import com.jhlabs.image.HSBAdjustFilter; |
| |
| /** |
| * <p> |
| * Mojo which renders SVG icons into PNG format. |
| * </p> |
| * |
| * @goal render-icons |
| * @phase generate-resources |
| */ |
| public class RenderMojo extends AbstractMojo { |
| |
| /** Maven logger */ |
| Log log; |
| |
| /** Used for high resolution (HiDPI) rendering support. */ |
| public static final String ECLIPSE_SVG_SCALE = "eclipse.svg.scale"; |
| |
| /** Used to specify the number of render threads when rasterizing icons. */ |
| public static final String RENDERTHREADS = "eclipse.svg.renderthreads"; |
| |
| /** Used to specify the directory name where the SVGs are taken from. */ |
| public static final String SOURCE_DIR = "eclipse.svg.sourcedirectory"; |
| |
| /** Used to specify the directory name where the PNGs are saved to. */ |
| public static final String TARGET_DIR = "eclipse.svg.targetdirectory"; |
| |
| /** |
| * Used to specify whether to create separate fragments or putting the high |
| * resolution icons next to the low-resolution icons. |
| */ |
| public static final String CREATE_FRAGMENTS = "eclipse.svg.createFragments"; |
| |
| /** Used to specify whether to use stylesheets. */ |
| public static final String USE_STYLESHEET = "eclipse.svg.stylesheet"; |
| |
| /** |
| * Used to specify whether to recreate css styles with SASS or use the |
| * existing css files. |
| */ |
| public static final String REGENERATE_STYLES = "eclipse.svg.stylesheet.regenerate"; |
| |
| /** A list of directories with svg sources to rasterize. */ |
| private List<IconEntry> icons; |
| |
| /** The number of threads to use when rendering icons. */ |
| private int threads; |
| |
| private final class CustomTranscoder extends PNGTranscoder { |
| @Override |
| protected ImageRenderer createRenderer() { |
| ImageRenderer renderer = new StaticRenderer(); |
| |
| RenderingHints renderHints = renderer.getRenderingHints(); |
| |
| renderHints.add( |
| new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)); |
| |
| renderHints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)); |
| |
| renderHints.add(new RenderingHints(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE)); |
| |
| renderHints.add( |
| new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC)); |
| |
| renderHints.add(new RenderingHints(RenderingHints.KEY_ALPHA_INTERPOLATION, |
| RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)); |
| |
| renderHints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); |
| |
| renderHints.add( |
| new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY)); |
| |
| renderHints.add(new RenderingHints(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE)); |
| |
| renderHints.add(new RenderingHints(RenderingHints.KEY_FRACTIONALMETRICS, |
| RenderingHints.VALUE_FRACTIONALMETRICS_ON)); |
| |
| renderer.setRenderingHints(renderHints); |
| |
| return renderer; |
| } |
| } |
| |
| /** |
| * A counter used to keep track of the number of rendered icons. Atomic is |
| * used to make it easy to access between threads concurrently. |
| */ |
| private AtomicInteger counter; |
| |
| /** List of icons that failed to render, made safe for parallel access */ |
| List<IconEntry> failedIcons = Collections.synchronizedList(new ArrayList<IconEntry>(5)); |
| |
| /** The amount of scaling to apply to rasterized images. */ |
| private double outputScale; |
| |
| /** An absolute path to a stylesheet to use when rendering icons. */ |
| private String stylesheetName; |
| |
| /** |
| * If true, existing css stylesheets will be deleted and recreated with SASS |
| * for the supplied theme, only used during CSS-based rendering. |
| */ |
| private boolean regenerateCss = false; |
| |
| /** |
| * @return the number of icons rendered at the time of the call |
| */ |
| public int getIconsRendered() { |
| return counter.get(); |
| } |
| |
| /** |
| * @return the number of icons that failed during the rendering process |
| */ |
| public int getFailedIcons() { |
| return failedIcons.size(); |
| } |
| |
| /** |
| * <p> |
| * Generates raster images from the input SVG vector image. |
| * </p> |
| * |
| * @param icon |
| * the icon to render |
| * @param grayFilter |
| * @param desaturator |
| * @param decontrast |
| */ |
| public void rasterize(IconEntry icon) { |
| if (icon == null) { |
| log.error("Null icon definition, skipping."); |
| failedIcons.add(icon); |
| return; |
| } |
| |
| if (icon.inputPath == null) { |
| log.error("Null icon input path, skipping: " + icon.nameBase); |
| failedIcons.add(icon); |
| return; |
| } |
| |
| if (!icon.inputPath.exists()) { |
| log.error("Input path specified does not exist, skipping: " + icon.nameBase); |
| failedIcons.add(icon); |
| return; |
| } |
| |
| if (icon.outputPath != null && !icon.outputPath.exists()) { |
| icon.outputPath.mkdirs(); |
| } |
| |
| if (icon.disabledPath != null && !icon.disabledPath.exists()) { |
| icon.disabledPath.mkdirs(); |
| } |
| |
| // Create the document to rasterize |
| SVGDocument svgDocument = generateSVGDocument(icon); |
| |
| if (svgDocument == null) { |
| return; |
| } |
| |
| // Determine the output sizes (native, double, quad) |
| // We render at quad size and resample down for output |
| Element svgDocumentNode = svgDocument.getDocumentElement(); |
| String nativeWidthStr = svgDocumentNode.getAttribute("width"); |
| String nativeHeightStr = svgDocumentNode.getAttribute("height"); |
| int nativeWidth = -1; |
| int nativeHeight = -1; |
| |
| try { |
| if (!"".equals(nativeWidthStr) && !"".equals(nativeHeightStr)) { |
| nativeWidthStr = stripOffPx(nativeWidthStr); |
| nativeHeightStr = stripOffPx(nativeHeightStr); |
| nativeWidth = Integer.parseInt(nativeWidthStr); |
| nativeHeight = Integer.parseInt(nativeHeightStr); |
| } else { |
| // Vector graphics editing programs don't always output height |
| // and width attributes on SVG. |
| // As fall back: parse the viewBox attribute (which is almost |
| // always set). |
| String viewBoxStr = svgDocumentNode.getAttribute("viewBox"); |
| if ("".equals(viewBoxStr)) { |
| log.error("Icon defines neither width/height nor a viewBox, skipping: " + icon.nameBase); |
| failedIcons.add(icon); |
| return; |
| } |
| String[] splitted = viewBoxStr.split(" "); |
| if (splitted.length != 4) { |
| log.error("Dimension could not be parsed. Skipping: " + icon.nameBase); |
| failedIcons.add(icon); |
| return; |
| } |
| String widthStr = splitted[2]; |
| widthStr = stripOffPx(widthStr); |
| String heightStr = splitted[3]; |
| heightStr = stripOffPx(heightStr); |
| |
| nativeWidth = Integer.parseInt(widthStr); |
| nativeHeight = Integer.parseInt(heightStr); |
| } |
| } catch (NumberFormatException e) { |
| log.error("Dimension could not be parsed ( " + e.getMessage() + "), skipping: " + icon.nameBase); |
| failedIcons.add(icon); |
| return; |
| } |
| |
| int outputWidth = (int) (nativeWidth * outputScale); |
| int outputHeight = (int) (nativeHeight * outputScale); |
| |
| // Guesstimate the PNG size in memory, BAOS will enlarge if necessary. |
| int outputInitSize = nativeWidth * nativeHeight * 4 + 1024; |
| ByteArrayOutputStream iconOutput = new ByteArrayOutputStream(outputInitSize); |
| |
| // Render to SVG |
| try { |
| log.info(Thread.currentThread().getName() + " " + " Rasterizing: " + icon.nameBase + ".png at " |
| + outputWidth + "x" + outputHeight); |
| |
| TranscoderInput svgInput = new TranscoderInput(svgDocument); |
| |
| boolean success = renderIcon(icon, outputWidth, outputHeight, svgInput, iconOutput); |
| |
| if (!success) { |
| log.error("Failed to render icon: " + icon.nameBase + ".png, skipping."); |
| failedIcons.add(icon); |
| return; |
| } else { |
| counter.getAndAdd(1); |
| } |
| } catch (Exception e) { |
| log.error("Failed to render icon: " + e.getMessage(), e); |
| failedIcons.add(icon); |
| return; |
| } |
| |
| // Generate a buffered image from Batik's png output |
| byte[] imageBytes = iconOutput.toByteArray(); |
| ByteArrayInputStream imageInputStream = new ByteArrayInputStream(imageBytes); |
| |
| BufferedImage inputImage = null; |
| try { |
| inputImage = ImageIO.read(imageInputStream); |
| |
| if (inputImage == null) { |
| log.error( |
| "Failed to generate BufferedImage from rendered icon, ImageIO returned null: " + icon.nameBase); |
| failedIcons.add(icon); |
| return; |
| } |
| } catch (IOException e2) { |
| log.error( |
| "Failed to generate BufferedImage from rendered icon: " + icon.nameBase + " - " + e2.getMessage()); |
| failedIcons.add(icon); |
| return; |
| } |
| |
| writeIcon(icon, outputWidth, outputHeight, inputImage); |
| |
| try { |
| if (icon.disabledPath != null) { |
| GrayscaleFilter grayFilter = new GrayscaleFilter(); |
| |
| HSBAdjustFilter desaturator = new HSBAdjustFilter(); |
| desaturator.setSFactor(0.0f); |
| |
| ContrastFilter decontrast = new ContrastFilter(); |
| decontrast.setBrightness(2.9f); |
| decontrast.setContrast(0.2f); |
| |
| BufferedImage desaturated16 = desaturator.filter(grayFilter.filter(inputImage, null), null); |
| |
| BufferedImage decontrasted = decontrast.filter(desaturated16, null); |
| |
| String outputName = getOutputName(icon.nameBase); |
| ImageIO.write(decontrasted, "PNG", new File(icon.disabledPath, outputName)); |
| } |
| } catch (Exception e1) { |
| log.error("Failed to render disabled icon: " + icon.nameBase, e1); |
| failedIcons.add(icon); |
| } |
| } |
| |
| private String stripOffPx(String dimensionString) { |
| if (dimensionString.endsWith("px")) { |
| return dimensionString.substring(0, dimensionString.length() - 2); |
| } |
| return dimensionString; |
| } |
| |
| /** |
| * <p> |
| * Generates a Batik SVGDocument for the supplied IconEntry's input file. |
| * </p> |
| * |
| * @param icon |
| * the icon entry to generate an SVG document for |
| * |
| * @return a batik SVGDocument instance or null if one could not be |
| * generated |
| */ |
| private SVGDocument generateSVGDocument(IconEntry icon) { |
| // Load the document and find out the native height/width |
| // We reuse the document later for rasterization |
| SVGDocument svgDocument = null; |
| try (FileInputStream iconDocumentStream = new FileInputStream(icon.inputPath)) { |
| |
| String parser = XMLResourceDescriptor.getXMLParserClassName(); |
| SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser); |
| |
| // What kind of URI is batik expecting here??? the docs don't say |
| svgDocument = f.createSVGDocument("file://" + icon.nameBase + ".svg", iconDocumentStream); |
| } catch (Exception e3) { |
| log.error("Error parsing SVG icon document: " + e3.getMessage()); |
| failedIcons.add(icon); |
| return null; |
| } |
| return svgDocument; |
| } |
| |
| /** |
| * <p> |
| * Resizes the supplied inputImage to the specified width and height, using |
| * lanczos resampling techniques. |
| * </p> |
| * |
| * @param icon |
| * the icon that's being resized |
| * @param width |
| * the desired output width after rescaling operations |
| * @param height |
| * the desired output height after rescaling operations |
| * @param sourceImage |
| * the source image to resource |
| */ |
| private void writeIcon(IconEntry icon, int width, int height, BufferedImage sourceImage) { |
| try { |
| String outputName = getOutputName(icon.nameBase); |
| ImageIO.write(sourceImage, "PNG", new File(icon.outputPath, outputName)); |
| } catch (Exception e1) { |
| log.error("Failed to resize rendered icon to output size: " + icon.nameBase, e1); |
| failedIcons.add(icon); |
| } |
| } |
| |
| /** |
| * |
| * @param outputName |
| * @return |
| */ |
| private String getOutputName(String outputName) { |
| if (outputScale != 1) { |
| String scaleId = outputScale == (double) (int) outputScale ? Integer.toString((int) outputScale) |
| : Double.toString(outputScale); |
| outputName += "@" + scaleId + "x"; |
| } |
| outputName += ".png"; |
| return outputName; |
| } |
| |
| /** |
| * Use batik to rasterize the input SVG into a raster image at the specified |
| * image dimensions. |
| * |
| * @param icon |
| * @param width |
| * the width to render the icons at |
| * @param height |
| * the height to render the icon at |
| * @param transcoderInput |
| * the SVG transcoder input |
| * @param stream |
| * the stream to write the PNG data to |
| * |
| * @return true if the icon was rendered successfully, false otherwise |
| * @throws MojoExecutionException |
| */ |
| public boolean renderIcon(final IconEntry icon, int width, int height, TranscoderInput transcoderInput, |
| OutputStream stream) throws MojoExecutionException { |
| PNGTranscoder transcoder = new CustomTranscoder(); |
| |
| removeStyleDashPrefix(transcoderInput.getDocument().getDocumentElement()); |
| |
| if (stylesheetName != null) { |
| String cssRoot = icon.inputPath.getAbsolutePath().replace("eclipse-svg", "eclipse-css"); |
| cssRoot = cssRoot.replace("/icons/", "/styles/" + stylesheetName + "/"); |
| cssRoot = cssRoot.replace(".svg", ".scss"); |
| File cssPath = new File(cssRoot); |
| |
| File preprocessedCss = generateCSS(icon.nameBase, cssPath.getAbsolutePath()); |
| |
| if (!preprocessedCss.exists()) { |
| log.error("Could not resolve supplied stylesheet: " + preprocessedCss.getAbsolutePath() |
| + ", using defaults."); |
| } else { |
| removeInlineStyle(transcoderInput.getDocument().getDocumentElement()); |
| |
| transcoder.addTranscodingHint(PNGTranscoder.KEY_USER_STYLESHEET_URI, |
| preprocessedCss.toURI().toString()); |
| } |
| } |
| |
| transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, Float.valueOf(width)); |
| transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, Float.valueOf(height)); |
| |
| transcoder.setErrorHandler(new ErrorHandler() { |
| @Override |
| public void warning(TranscoderException arg0) throws TranscoderException { |
| log.error("Icon: " + icon + " - WARN: " + arg0.getMessage()); |
| } |
| |
| @Override |
| public void fatalError(TranscoderException arg0) throws TranscoderException { |
| log.error("Icon: " + icon + " - FATAL: " + arg0.getMessage()); |
| } |
| |
| @Override |
| public void error(TranscoderException arg0) throws TranscoderException { |
| log.error("Icon: " + icon + " - ERROR: " + arg0.getMessage()); |
| } |
| }); |
| |
| // Transcode the SVG document input to a PNG via the output stream |
| TranscoderOutput output = new TranscoderOutput(stream); |
| |
| try { |
| transcoder.transcode(transcoderInput, output); |
| return true; |
| } catch (Exception e) { |
| log.error("Error transcoding SVG to bitmap.", e); |
| return false; |
| } finally { |
| try { |
| stream.close(); |
| } catch (IOException e) { |
| log.error("Error closing transcoder stream.", e); |
| } |
| } |
| } |
| |
| /** |
| * <p> |
| * Initializes rasterizer defaults |
| * </p> |
| * |
| * @param threads |
| * the number of threads to render with |
| * @param scale |
| * multiplier to use with icon output dimensions |
| */ |
| private void init(int threads, double scale) { |
| this.threads = threads; |
| this.outputScale = Math.max(1, scale); |
| icons = new ArrayList<>(); |
| counter = new AtomicInteger(); |
| } |
| |
| /** |
| * @see AbstractMojo#execute() |
| */ |
| public void execute() throws MojoExecutionException, MojoFailureException { |
| log = getLog(); |
| |
| // Default to 2x the number of processor cores but allow override via |
| // jvm arg |
| int systemCores = Math.max(1, Runtime.getRuntime().availableProcessors()); |
| String threadStr = System.getProperty(RENDERTHREADS); |
| if (threadStr != null) { |
| try { |
| threads = Integer.parseInt(threadStr); |
| } catch (Exception e) { |
| log.error("Could not parse thread count, using default thread count.", e); |
| threads = systemCores; |
| } |
| } |
| |
| // if high res is enabled, the icons output size will be scaled by |
| // iconScale |
| // Defaults to 1, meaning native size |
| double iconScale = 1; |
| String iconScaleStr = System.getProperty(ECLIPSE_SVG_SCALE); |
| if (iconScaleStr != null) { |
| iconScale = Double.parseDouble(iconScaleStr); |
| if (iconScale != 1 && iconScale != 1.5 && iconScale != 2) { |
| log.warn("Unusual scale factor: " + iconScaleStr + " (@" + iconScale + "x)"); |
| } |
| } |
| |
| // Defaults to "eclipse-svg" |
| String sourceDir = "eclipse-svg"; |
| String sourceDirProp = System.getProperty(SOURCE_DIR); |
| if (sourceDirProp != null) { |
| sourceDir = sourceDirProp; |
| } |
| |
| // Defaults to "eclipse-png" |
| String targetDir = "eclipse-png"; |
| String targetDirProp = System.getProperty(TARGET_DIR); |
| if (targetDirProp != null) { |
| targetDir = targetDirProp; |
| } |
| |
| // Defaults to "true" |
| boolean createFragements = true; |
| String createFragmentsProp = System.getProperty(CREATE_FRAGMENTS); |
| if (createFragmentsProp != null) { |
| createFragements = Boolean.parseBoolean(createFragmentsProp); |
| } |
| |
| // Defaults to "false" |
| String inputStylesheet = System.getProperty(USE_STYLESHEET); |
| if (inputStylesheet != null) { |
| stylesheetName = inputStylesheet; |
| } |
| |
| // Defaults to "false" |
| String regenerateStyles = System.getProperty(REGENERATE_STYLES); |
| if (regenerateStyles != null) { |
| regenerateCss = Boolean.parseBoolean(regenerateStyles); |
| } |
| |
| // Track the time it takes to render the entire set |
| long totalStartTime = System.currentTimeMillis(); |
| |
| // initialize defaults (the old renderer was instantiated via |
| // constructor) |
| init(systemCores, iconScale); |
| |
| String workingDirectory = System.getProperty("user.dir"); |
| |
| String dirSuffix = "/" + targetDir + "/"; |
| if ((iconScale != 1) && createFragements) { |
| dirSuffix = "/" + targetDir + "-hidpi/"; |
| } |
| |
| if(stylesheetName != null) { |
| if ((iconScale != 1) && createFragements) { |
| dirSuffix = "/" + targetDir + "-" + stylesheetName + "-hidpi/"; |
| } else { |
| dirSuffix = "/" + targetDir + "-" + stylesheetName + "/"; |
| } |
| } |
| |
| File outputDir = new File(workingDirectory + dirSuffix); |
| File iconDirectoryRoot = new File(sourceDir + "/"); |
| |
| if (!iconDirectoryRoot.exists()) { |
| log.error("Source directory' " + sourceDir + "' does not exist."); |
| return; |
| } |
| |
| // Search each subdir in the root dir for svg icons |
| for (File file : iconDirectoryRoot.listFiles()) { |
| if (!file.isDirectory()) { |
| continue; |
| } |
| |
| String dirName = file.getName(); |
| |
| // Where to place the rendered icon |
| String child = dirName; |
| if ((iconScale != 1) && createFragements) { |
| child = dirName + ".hidpi"; |
| } |
| |
| File outputBase = new File(outputDir, child); |
| if ((iconScale != 1) && createFragements) { |
| createFragmentFiles(outputBase, dirName); |
| } |
| |
| IconGatherer.gatherIcons(icons, "svg", file, file, outputBase, true, FolderState.include); |
| } |
| |
| log.info("Working directory: " + outputDir.getAbsolutePath()); |
| log.info("SVG Icon Directory: " + iconDirectoryRoot.getAbsolutePath()); |
| log.info("Rendering icons with " + systemCores + " threads, scaling output to " + iconScale + "x"); |
| long startTime = System.currentTimeMillis(); |
| |
| ForkJoinPool forkJoinPool = new ForkJoinPool(threads); |
| |
| try { |
| forkJoinPool.submit(() -> { |
| icons.parallelStream().forEach(this::rasterize); |
| return null; |
| }).get(); |
| } catch (Exception e) { |
| log.error("Error while rendering icons.", e); |
| } |
| |
| // Print summary of operations |
| int iconRendered = getIconsRendered(); |
| int failedIcons = getFailedIcons(); |
| int fullIconCount = iconRendered - failedIcons; |
| |
| log.info(fullIconCount + " Icons Rendered"); |
| log.info(failedIcons + " Icons Failed"); |
| log.info("Took: " + (System.currentTimeMillis() - startTime) + " ms."); |
| |
| log.info("Rasterization operations completed, Took: " + (System.currentTimeMillis() - totalStartTime) + " ms."); |
| } |
| |
| private void createFragmentFiles(File outputBase, String dirName) { |
| createFile(new File(outputBase, "build.properties"), "bin.includes = META-INF/,icons/,.\n"); |
| createFile(new File(outputBase, ".project"), |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<projectDescription>\n" + " <name>" + dirName |
| + ".hidpi</name>\n" + " <comment></comment>\n" + " <projects>\n" + " </projects>\n" |
| + " <buildSpec>\n" + " <buildCommand>\n" |
| + " <name>org.eclipse.pde.ManifestBuilder</name>\n" + " <arguments>\n" |
| + " </arguments>\n" + " </buildCommand>\n" + " <buildCommand>\n" |
| + " <name>org.eclipse.pde.SchemaBuilder</name>\n" + " <arguments>\n" |
| + " </arguments>\n" + " </buildCommand>\n" + " </buildSpec>\n" |
| + " <natures>\n" + " <nature>org.eclipse.pde.PluginNature</nature>\n" |
| + " </natures>\n" + "</projectDescription>\n"); |
| createFile(new File(outputBase, "META-INF/MANIFEST.MF"), |
| "Manifest-Version: 1.0\n" + "Bundle-ManifestVersion: 2\n" + "Bundle-Name: " + dirName + ".hidpi\n" |
| + "Bundle-SymbolicName: " + dirName + ".hidpi\n" + "Bundle-Version: 0.1.0.qualifier\n" |
| + "Fragment-Host: " + dirName + "\n"); |
| } |
| |
| /** |
| * |
| * @param file |
| * @param contents |
| */ |
| private void createFile(File file, String contents) { |
| try (FileWriter writer = new FileWriter(file)) { |
| file.getParentFile().mkdirs(); |
| writer.write(contents); |
| } catch (IOException e) { |
| log.error(e); |
| } |
| } |
| |
| /** |
| * <p> |
| * Uses SASS to generate a CSS style sheet for the given style name. |
| * </p> |
| * |
| * @param inputStylesheet |
| * @return |
| * @throws MojoExecutionException |
| */ |
| private File generateCSS(String styleName, String inputStylesheet) throws MojoExecutionException { |
| String workingDirectory = System.getProperty("user.dir"); |
| |
| File styleDir = new File(workingDirectory, "eclipse-css/styles/"); |
| File targetStyleSheet = new File(inputStylesheet); |
| File targetStyleDir = targetStyleSheet.getParentFile(); |
| |
| try { |
| File outputCss = new File(targetStyleDir, styleName + ".css"); |
| |
| if (regenerateCss && outputCss.exists() && !outputCss.delete()) { |
| throw new MojoExecutionException( |
| "Could not delete existing CSS during preprocessing: " + outputCss.getAbsolutePath()); |
| } |
| |
| if (regenerateCss || !outputCss.exists()) { |
| ProcessBuilder procBuilder = new ProcessBuilder("sass", "--sourcemap=none", "-I" + styleDir, |
| targetStyleSheet.getAbsolutePath(), outputCss.getAbsolutePath()).directory(targetStyleDir); |
| |
| Process process = procBuilder.start(); |
| |
| log.info( |
| "Running SASS precompiler: " + procBuilder.command().stream().collect(Collectors.joining(" "))); |
| int waitFor = process.waitFor(); |
| |
| if (waitFor > 0) { |
| throw new MojoExecutionException( |
| "Error generating CSS from SASS input, is SASS installed on your machine?"); |
| } |
| } |
| |
| return outputCss; |
| } catch (IOException | InterruptedException e) { |
| log.error("Error generating CSS stylesheet from SASS.", e); |
| throw new MojoExecutionException("Error while SASS preprocessing styles: " + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * <p> |
| * Removes broken inkscape prefix from documents, preventing broken |
| * rendering with Batik, see: |
| * |
| * https://issues.apache.org/jira/browse/BATIK-1112 |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=493994 |
| * </p> |
| * |
| * @param nodes |
| */ |
| private void removeStyleDashPrefix(Node node) { |
| NodeList nodes = node.getChildNodes(); |
| int len = nodes.getLength(); |
| for (int i = 0; i < len; i++) { |
| Node item = nodes.item(i); |
| |
| if (item instanceof Element) { |
| Element elem = (Element) item; |
| Attr attr = elem.getAttributeNodeNS(null, "style"); |
| |
| if (attr != null) { |
| String style = attr.getValue(); |
| String replaceAll = style.replaceAll("-inkscape", "inkscape"); |
| elem.setAttributeNS(null, "style", replaceAll); |
| } |
| } |
| |
| removeStyleDashPrefix(item); |
| } |
| } |
| |
| /** |
| * <p> |
| * Strips inline CSS styles from the supplied node and all descendents. |
| * </p> |
| * |
| * @param node |
| */ |
| private void removeInlineStyle(Node node) { |
| NodeList nodes = node.getChildNodes(); |
| int len = nodes.getLength(); |
| for (int i = 0; i < len; i++) { |
| Node item = nodes.item(i); |
| |
| if (item instanceof Element) { |
| Element elem = (Element) item; |
| elem.setAttributeNS(null, "style", ""); |
| } |
| |
| removeInlineStyle(item); |
| } |
| } |
| |
| } |