View Javadoc
1   /*
2    * SPDX-License-Identifier: Apache-2.0
3    * See LICENSE file for details.
4    *
5    * Copyright 2012-2026 Hazendaz
6    */
7   package net.jangaroo.smartsprites.maven;
8   
9   import java.io.File;
10  import java.io.IOException;
11  import java.util.ArrayList;
12  import java.util.List;
13  import java.util.Locale;
14  
15  import org.apache.maven.plugin.AbstractMojo;
16  import org.apache.maven.plugin.MojoExecutionException;
17  import org.apache.maven.plugin.MojoFailureException;
18  import org.apache.maven.plugins.annotations.LifecyclePhase;
19  import org.apache.maven.plugins.annotations.Mojo;
20  import org.apache.maven.plugins.annotations.Parameter;
21  import org.carrot2.labs.smartsprites.SmartSpritesParameters;
22  import org.carrot2.labs.smartsprites.SpriteBuilder;
23  import org.carrot2.labs.smartsprites.message.Message;
24  import org.carrot2.labs.smartsprites.message.MessageLog;
25  import org.carrot2.labs.smartsprites.message.PrintStreamMessageSink;
26  
27  /**
28   * Goal which creates Spritesheets from given css and image files Explanation of every variable is taken directly from
29   * the smartsprites documentation (https://www.w3schools.com/css/css_image_sprites.asp)
30   * <p>
31   * For further information on the use of smartsprites please refer to:
32   * https://www.w3schools.com/css/css_image_sprites.asp
33   */
34  @Mojo(name = "smartsprites", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresProject = false, threadSafe = true)
35  public class SmartSpritesMojo extends AbstractMojo {
36  
37      /** The static root directory mode. */
38      private static final String ROOT_DIR_MODE = "rootDirMode";
39  
40      /** The static css files mode. */
41      private static final String CSS_FILES_MODE = "cssFilesMode";
42  
43      /**
44       * Directory in which SmartSprites processing should be done, required if css-files not specified or if
45       * output-dir-path specified, default: not specified.
46       * <p>
47       * SmartSprites will process all files with the *.css extension found in root-dir-path or any subdirectory of it.
48       * For more fine-grained control over the processed CSS files, see the css-files option.
49       * <p>
50       * If the provided root directory path is relative, it will be resolved against the current working directory.
51       */
52      @Parameter(defaultValue = "${project.basedir}/src/main/sprites", property = "rootDirPath")
53      private File rootDirPath;
54  
55      /**
56       * Paths of CSS files to process, required if root-dir-path not specified, default: not specified.
57       * <p>
58       * SmartSprites will process all CSS files listed using this option. If css-files is to be used together with
59       * output-dir-path, root-dir-path must also be specified so that SmartSprites can preserve the directory structure
60       * found in root-dir-path in output-dir-path. If root-dir-path and output-dir-path are used, css-files outside of
61       * root-dir-path will be ignored.
62       * <p>
63       * Relative CSS file paths provided using this option will be resolved against the current working directory. Please
64       * note that SmartSprites will not expand any wildcards (like style/*.css), it assumes the expansion is performed at
65       * the command line shell level.
66       * <p>
67       * To specify the list of CSS files to process in the SmartSprites Ant task, use one or more nested fileset
68       * elements. Please see the build.xml file in the distribution archive for an example.
69       */
70      @Parameter(property = "cssFiles")
71      private List<File> cssFiles;
72  
73      /**
74       * Output directory for processed CSS files and CSS-relative sprite images, optional, default: not specified.
75       * <p>
76       * If a non-empty output-dir-path is specified, a non-empty root-dir-path must also be provided. The directory
77       * structure relative to the root-dir-path will be preserved in the output directory. E.g. if CSS files are
78       * contained in the css/base directory of root-dir-path, the processed results will be written to
79       * output-dir-path/css/base. Also, CSS-relative sprite images will be written to the output directory. Sprite images
80       * with document-root-relative URLs will be written relative to the document-root-dir-path. If the output-dir-path
81       * directory does not exist, it will be created. If the provided output directory path is relative, it will be
82       * resolved against the current working directory.
83       * <p>
84       * You can leave the output-dir-path empty, in which case the CSS files will be written next to the original CSS
85       * files with the css-file-suffix, and sprite images will be written relative to the original CSS files. If you are
86       * using a non-empty output-dir-path, you might want to use an empty css-file-suffix.
87       */
88      @Parameter(defaultValue = "${project.build.directory}/generated-resources/META-INF/resources/spritesheets", property = "outPutDirPath")
89      private File outputDirPath;
90  
91      /**
92       * Document root path for document-root-relative (starting with /) image urls in CSS, optional, default: not
93       * specified.
94       * <p>
95       * All document-root-relative image and sprite URLs will be taken relative to document-root-dir-path. Also
96       * document-root-relative sprite URLs will be written relative to document-root-dir-path. You can leave this
97       * property empty if your CSS uses only CSS-relative image URLs. If the provided document root directory path is
98       * relative, it will be resolved against the current working directory.
99       */
100     @Parameter(property = "documentRootDirPath")
101     private File documentRootDirPath;
102 
103     /**
104      * Message logging level, optional, default: WARN.
105      * <p>
106      * Messages less important than log-level will not be shown.
107      * <p>
108      * SmartSprites has 5 levels of log messages (in the increasing order of importance).
109      * <p>
110      * INFO: information messages, can be safely ignored.
111      * <p>
112      * DEPRECATION: Notice messages related to deprecated features.
113      * <p>
114      * WARN: Warning messages, ignoring can lead to the converted designs looking broken.
115      * <p>
116      * ERROR: Error messages, SmartSprites cannot perform processing.
117      * <p>
118      * STATUS: Status messages displayed at the end of processing.
119      */
120     @Parameter(defaultValue = "WARN", property = "logLevel")
121     private String logLevel;
122 
123     /**
124      * Color depth of sprites in the PNG format, optional, default: AUTO. AUTO: PNG color depth will be chosen
125      * automatically. If the sprite image does not contain partial transparencies (alpha channel) and has less than 256
126      * colors, PNG8 will be used. Otherwise, the sprite will be saved in PNG24. DIRECT: PNG sprites will always be saved
127      * in the PNG24 format. INDEXED: PNG sprites will always be saved in the PNG8 format. If the sprite image contains
128      * partial transparencies (alpha channel) or has more than 255 colors, image quality loss may occur and appropriate
129      * warnings will be issued. See also the sprite-matte-color property.
130      */
131     @Parameter(defaultValue = "AUTO", property = "spritePngDepth")
132     private String spritePngDepth;
133 
134     /**
135      * The encoding to assume for input and output CSS files, default: UTF-8. For the list of allowed values, please see
136      * the list of encodings supported in Java.
137      */
138     @Parameter(defaultValue = "UTF-8", property = "cssFileEncoding")
139     private String cssFileEncoding;
140 
141     /**
142      * Suffix to be appended to the processed CSS file name, optional, default: .
143      */
144     @Parameter(defaultValue = "", property = "cssFileSuffix")
145     private String cssFileSuffix;
146 
147     /**
148      * To make sure the different modes are working correctly a mode has to be specified. Modes: - rootDirMode (rootDir
149      * is set, optionally outputdir! No css-Files!) - cssFilesMode (only cssFiles is set, no rootDir, no OutputDir) -
150      * cssFilesWithOutputDirMode (cssFiles are set, as well as rootDir and outputDir)
151      */
152     @Parameter(defaultValue = SmartSpritesMojo.ROOT_DIR_MODE, property = "workingMode")
153     private String workingMode;
154 
155     /**
156      * To enable skipping run of plugin.
157      */
158     @Parameter(defaultValue = "false", alias = "skip", property = "skip")
159     private boolean skip;
160 
161     @Override
162     public void execute() throws MojoExecutionException, MojoFailureException {
163         // Check if plugin run should be skipped
164         if (this.skip) {
165             this.getLog().info("Smartsprites is skipped");
166             return;
167         }
168 
169         // Check for the correct log-level
170         Message.MessageLevel msgLogLevel;
171         try {
172             msgLogLevel = Message.MessageLevel.valueOf(this.logLevel.toUpperCase(Locale.ENGLISH));
173         } catch (final Exception e) {
174             throw new MojoExecutionException("LogLevel Error - please select a valid value! (INFO, WARN) ", e);
175         }
176 
177         // Check for the correct PNG-Depth
178         SmartSpritesParameters.PngDepth pngDepth;
179         try {
180             pngDepth = SmartSpritesParameters.PngDepth.valueOf(this.spritePngDepth);
181         } catch (final Exception e) {
182             throw new MojoExecutionException("PngDepth Error - please select a valid value! (AUTO, DIRECT, INDEXED)  ",
183                     e);
184         }
185 
186         // Make sure we are using only correct workingModes
187         if (!(this.workingMode.equals(SmartSpritesMojo.ROOT_DIR_MODE)
188                 || this.workingMode.equals(SmartSpritesMojo.CSS_FILES_MODE)
189                 || this.workingMode.equals("cssFilesWithOutputDirMode"))) {
190             throw new MojoExecutionException(
191                     "workingMode Error - plese select a valid value! (rootDirMode, cssFilesMode, cssFilesWithOutputDirMode)");
192         }
193 
194         // Variables for paths
195         String rootDirPathTemp = "";
196         String outputDirPathTemp = "";
197         String documentRootDirPathTemp = "";
198         List<String> cssFilesTemp = new ArrayList<>();
199 
200         // Check if we should set cssFiles to null, or if we are in a working mode
201         // where we want to use cssFiles
202         if (!this.workingMode.equals(SmartSpritesMojo.ROOT_DIR_MODE)) {
203             for (final File cssFile : this.cssFiles) {
204                 if (cssFile.exists()) {
205                     cssFilesTemp.add(cssFile.toString());
206                 } else {
207                     throw new MojoExecutionException("The following css-file doesn't exist: " + cssFile);
208                 }
209             }
210         } else {
211             this.cssFiles = null;
212         }
213 
214         // Check if the folders exist or are at least configured
215         if (this.rootDirPath != null && !this.workingMode.equals(SmartSpritesMojo.CSS_FILES_MODE)) {
216             if (this.rootDirPath.exists()) {
217                 rootDirPathTemp = this.rootDirPath.toString();
218             } else {
219                 throw new MojoExecutionException("The rootDirPath doesn't exist. " + this.rootDirPath.toString());
220             }
221         }
222 
223         // Check if the outputDirPath is set
224         if (this.outputDirPath != null && !this.workingMode.equals(SmartSpritesMojo.CSS_FILES_MODE)) {
225             outputDirPathTemp = this.outputDirPath.toString();
226         }
227 
228         // Check if the documentRootDirPath is set
229         if (this.documentRootDirPath != null) {
230             documentRootDirPathTemp = this.documentRootDirPath.toString();
231         }
232 
233         // Determine which workingMode we are in and check if all conditions for that
234         // specific mode are matched
235         if (this.workingMode.equals(SmartSpritesMojo.ROOT_DIR_MODE)) {
236             if (this.rootDirPath == null) {
237                 throw new MojoExecutionException("Please configure a rootDirPath.");
238             }
239             cssFilesTemp = null;
240         } else if (this.workingMode.equals(SmartSpritesMojo.CSS_FILES_MODE)) {
241             if (this.cssFiles == null) {
242                 throw new MojoExecutionException("Please configure some cssFiles.");
243             }
244             rootDirPathTemp = null;
245             outputDirPathTemp = null;
246         } else if ((this.workingMode.equals("cssFilesWithOutputDirMode") && this.cssFiles == null)
247                 || this.rootDirPath == null || this.outputDirPath == null) {
248             throw new MojoExecutionException("Please configure cssFiles and/or a rootDirPath and/or an outputDirPath");
249         }
250 
251         // Configure the SmartSpritesParameters for execution
252         SmartSpritesParameters smartParameters = new SmartSpritesParameters(rootDirPathTemp, cssFilesTemp,
253                 outputDirPathTemp, documentRootDirPathTemp, msgLogLevel, this.cssFileSuffix, pngDepth,
254                 this.cssFileEncoding);
255 
256         final MessageLog messageLog = new MessageLog(
257                 new PrintStreamMessageSink(System.out, smartParameters.getLogLevel()));
258         SpriteBuilder spriteBuilder;
259 
260         // Try to execute SmartSprites with the configured parameters and the defined messageLog
261         try {
262             spriteBuilder = new SpriteBuilder(smartParameters, messageLog);
263             spriteBuilder.buildSprites();
264         } catch (final IOException e) {
265             throw new MojoExecutionException("Smartsprites error: ", e);
266         }
267     }
268 
269 }