Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions icon-scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/hidpi-icons/target/
/illustrator_exports/
add_illustrator_artboards.jsx
11 changes: 8 additions & 3 deletions icon-scripts/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,7 @@ Process for Exporting SVG Files from Adobe Illustrator
Save as type: SVG
Use artboards: Check and select "All".

5) In the SVG export options dialog that shows up, enter the following settings,
which have been tested and are known to work with NetBeans' SVG loader
implementation:
5) In the SVG export options dialog that shows up, enter the following settings:

SVG Profiles: SVG 1.1
Type: Convert to outline
Expand All @@ -157,6 +155,13 @@ Process for Exporting SVG Files from Adobe Illustrator
Encoding: UTF-8
Responsive: Disabled

The settings above have been tested and are known to work with NetBeans' SVG
loader implementation, which is used by ImageUtilities.loadIcon and friends.

(The settings above worked both with the old loader which used the Batik SVG
library, and the current loader which replaced Batik with JSVG. See
https://github.com/apache/netbeans/pull/7941 )

6) Run the IconTasks script. It will copy the SVG files to the various
required locations in the NetBeans repo, with an Apache License header
automatically appended. See "Running the IconTasks script" above.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@
*/
package org.netbeans.build.icons;

import com.github.weisj.jsvg.SVGDocument;
import com.github.weisj.jsvg.geometry.size.FloatSize;
import com.github.weisj.jsvg.parser.LoaderContext;
import com.github.weisj.jsvg.parser.SVGLoader;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
Expand All @@ -33,11 +29,9 @@
import com.google.common.collect.Sets;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
Expand All @@ -49,8 +43,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import org.netbeans.build.icons.TypeTaggedString.ArtboardName;
import org.netbeans.build.icons.TypeTaggedString.IconPath;
import org.netbeans.build.icons.TypeTaggedString.Hash;
Expand All @@ -63,8 +55,13 @@
* the default when running this file from the NetBeans IDE).
*/
public class IconTasks {
/* Constants relating to artboard positioning in the generated add_illustrator_exports.jsx
script. All values are in pixels. */
private static final int ARTBOARD_MAX_X = 288;
private static final int ARTBOARD_GRID = 12;
private static final int ARTBOARD_MIN_SPACING = 2;

private static final String LICENSE_HEADER = readLicenseHeader();
private static final SVGLoader SVG_LOADER = new SVGLoader();

public static void main(String[] args) throws IOException {
final File ICON_SCRIPTS_DIR = new File(System.getProperty("user.dir"), "../");
Expand All @@ -85,13 +82,13 @@ public static void main(String[] args) throws IOException {
System.err.println("Path " + args[0] + " (in command-line argument) is not a cloned NetBeans repository");
System.exit(-1);
}
final File ILLUSTRATOR_SVGS_DIR =
new File(ICON_SCRIPTS_DIR, "illustrator_exports/");
final File ILLUSTRATOR_SVGS_DIR = new File(ICON_SCRIPTS_DIR, "illustrator_exports/");
final File TABLES_DIR = new File(ICON_SCRIPTS_DIR, "tables/");
final File ICON_HASHES_FILE = new File(TABLES_DIR, "icon-hashes.txt");
final File MAPPINGS_FILE = new File(TABLES_DIR, "mappings.tsv");
final File READY_ARTBOARDS_FILE = new File(TABLES_DIR, "ready-artboards.txt");
final File ICONS_HTML_FILE = new File(NBSRC_DIR, "icons.html");
final File ILLUSTRATOR_ARTBOARD_SCRIPT_FILE = new File(ICON_SCRIPTS_DIR, "add_illustrator_artboards.jsx");
boolean copySVGfiles =
ILLUSTRATOR_SVGS_DIR.listFiles(f-> f.toString().endsWith(".svg")).length > 0;
System.out.println("Using icon hashes file : " + ICON_HASHES_FILE);
Expand Down Expand Up @@ -192,6 +189,8 @@ public static void main(String[] args) throws IOException {
} else {
svgContentToWrite = null;
}
BufferedImage srcImage = (svgContentToWrite == null) ? null :
ImageUtil.renderSVGImageFromXMLString(svgContentToWrite);
for (IconPath ip : filesByArtboard.get(artboard)) {
if (shouldIgnoreFile(ip)) {
continue;
Expand All @@ -200,10 +199,16 @@ public static void main(String[] args) throws IOException {
// System.out.println(srcSVGFile + "\t" + destSVG);
File destSVGFile = new File(NBSRC_DIR, destSVG.toString());
if (svgContentToWrite != null) {
try (PrintWriter pw = createPrintWriter(destSVGFile)) {
pw.print(svgContentToWrite);
BufferedImage destImage = destSVGFile.exists() ? ImageUtil.renderSVGImage(destSVGFile) : null;
if (destImage != null && ImageUtil.imagesAreEqual(srcImage, destImage)) {
System.out.println(
"Target SVG file is pixel-identical when rendered; skipping copy (" + destSVGFile + ")");
} else {
try (PrintWriter pw = createPrintWriter(destSVGFile)) {
pw.print(svgContentToWrite);
}
System.out.println("Copied SVG file to " + destSVGFile);
}
System.out.println("Copied SVG file to " + destSVGFile);
} else {
if (!destSVGFile.exists()) {
throw new RuntimeException(destSVGFile + " does not exist, and no " +
Expand All @@ -214,11 +219,35 @@ public static void main(String[] args) throws IOException {
}
}

int artboardX = 0;
int artboardY = 0;
int currentArtboardRowTallestIcon = 0;

/* The mappings file is assumed to be in a git repo so that the user of the script can
see what changed from run to run. */
try (PrintWriter mappingsPW = createPrintWriter(MAPPINGS_FILE);
PrintWriter htmlPW = createPrintWriter(ICONS_HTML_FILE))
PrintWriter htmlPW = createPrintWriter(ICONS_HTML_FILE);
PrintWriter scriptPW = createPrintWriter(ILLUSTRATOR_ARTBOARD_SCRIPT_FILE))
{
scriptPW.println("/* This generated Adobe Illustrator script places newly mapped artboards in the");
scriptPW.println("existing nb_vector_icons.ai file, with old PNG or GIF icons placed, embedded,");
scriptPW.println("and locked in the \"Old Bitmaps\" layer.\n");
scriptPW.println("To use this script, first open nb_vector_icons.ai in Adobe Illustrator. Then ");
scriptPW.println("click File->Scripts->Other Script, and browse to this file. */\n");
scriptPW.println("var doc = app.activeDocument;");
scriptPW.println("var targetLayer = doc.layers.getByName(\"Old Bitmaps\");");
scriptPW.println("var left, top, right, bottom, placedItem, embeddedItem, scaleX, scaleY;\n");

scriptPW.println("var firstColumnX = 0;");
scriptPW.println("for (var i = 0; i < doc.artboards.length; i++) {");
scriptPW.println(" var abRect = doc.artboards[i].artboardRect;");
scriptPW.println(" var rightX = abRect[2]; // right side of this artboard");
scriptPW.println(" if (rightX > firstColumnX) {");
scriptPW.println(" firstColumnX = rightX;");
scriptPW.println(" }");
scriptPW.println("}");
scriptPW.println("firstColumnX += 32;\n");

htmlPW.println(LICENSE_HEADER);
htmlPW.println("""
<html>
Expand Down Expand Up @@ -282,7 +311,9 @@ <p>This file lists bitmap icon files (GIF and PNG) in the NetBeans repo along wi
htmlPW.print(artboardIdx % 2 == 0 ? "<tr>" :
"<tr style='background: #eee'>");
if (subRowIdx == 0) {
htmlPW.print("<td rowspan='" + ips.size() + "'>" + artboard);
/* Add an invisible "^" to make it possible to search for artboard names
with Ctrl+F (e.g. "^ok") without getting matches in the path column. */
htmlPW.print("<td rowspan='" + ips.size() + "'><span style=\"color: #00000000\">^</span>" + artboard);
htmlPW.print("<td rowspan='" + ips.size() + "'>");
if (readyArtboards.contains(artboard)) {
htmlPW.print("<img src='" + getSVGIconPath(ip) + "'>");
Expand All @@ -302,6 +333,50 @@ <p>This file lists bitmap icon files (GIF and PNG) in the NetBeans repo along wi
previousHash = hash;
subRowIdx++;
}

if (copySVGfiles && !UNASSIGNED_ARTBOARD.equals(artboard) &&
/* We assume that _all_ existing artboards, ready or not, have been exported
to ILLUSTRATOR_SVGS_DIR. That way we can use the presence of an SVG file there
to determine if the Illustrator file already contains a given artboard or
not. */
!getIllustratorSVGFile(ILLUSTRATOR_SVGS_DIR, artboard).exists())
{
IconPath ip = ips.get(0);
Hash hash = Util.getChecked(iconHashesByFile, ip);
Dimension dim = Util.getChecked(dimensionsByHash, hash);

if (artboardX + dim.width > ARTBOARD_MAX_X) {
artboardX = 0;
artboardY -= getArtboardAdvance(-artboardY, currentArtboardRowTallestIcon);
currentArtboardRowTallestIcon = 0;
}
File file = new File(NBSRC_DIR, ip.toString());
if (!file.exists()) {
throw new AssertionError("File existence should have been checked earlier");
}
scriptPW.println("left = " + artboardX + " + firstColumnX;");
scriptPW.println("top = " + artboardY + ";");
scriptPW.println("right = left + " + dim.width + ";");
scriptPW.println("bottom = top - " + dim.height + ";"); // Minus appears correct here.
scriptPW.println("newArtboard = doc.artboards.add([left, top, right, bottom]);");
scriptPW.println("newArtboard.name = \"" + artboard + "\";");
scriptPW.println("placedItem = targetLayer.placedItems.add();");
scriptPW.println("placedItem.file = new File(\"" + file.toString() + "\");");
/* PNGs may have embedded DPI values, which should be disregarded. Resize to get a
1:1 pixel mapping. */
scriptPW.println("scaleX = " + dim.width + " / placedItem.width;");
scriptPW.println("scaleY = " + dim.height + " / placedItem.height;");
scriptPW.println("placedItem.resize(scaleX * 100, scaleY * 100);");
scriptPW.println("placedItem.left = left;");
scriptPW.println("placedItem.top = top;");
scriptPW.println("placedItem.locked = true;");
scriptPW.println("placedItem.embed();");
scriptPW.println();

currentArtboardRowTallestIcon = Math.max(currentArtboardRowTallestIcon, dim.height);
artboardX += getArtboardAdvance(artboardX, dim.width);
}

artboardIdx++;
}
htmlPW.println("</table>");
Expand All @@ -313,6 +388,12 @@ <p>This file lists bitmap icon files (GIF and PNG) in the NetBeans repo along wi
System.out.println(ICONS_HTML_FILE);
}

private static int getArtboardAdvance(int currentPosition, int iconSize) {
int minAdvance = iconSize + ARTBOARD_MIN_SPACING;
int nextGridPosition = Math.ceilDiv(currentPosition + minAdvance, ARTBOARD_GRID) * ARTBOARD_GRID;
return nextGridPosition - currentPosition;
}

private static PrintWriter createPrintWriter(File file) throws IOException {
// See https://stackoverflow.com/questions/1014287/is-there-a-way-to-make-printwriter-output-to-unix-format/14749004
return new PrintWriter(new BufferedOutputStream(new FileOutputStream(file))) {
Expand Down Expand Up @@ -354,6 +435,8 @@ private static ImmutableMap<IconPath, ArtboardName> readArtboardByFileMappings(F
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty())
continue;
String[] parts = line.split("\\s+", 2);
if (parts.length == 2) {
String artboard = parts[0].trim();
Expand Down Expand Up @@ -437,7 +520,11 @@ private static String prepareSVGWithInsertedLicense(File illustratorSVGsDir, Art
firstLine = false;
}
}
return ret.toString();
return ret.toString()
/* Illustrator keeps generating this useless/incorrect metadata element, and I can't
find a way to get rid of it. Just remove it here if it's present. (Though it didn't
really do any harm in any case.) */
.replace(" <description>Apache NetBeans Logo\n </description>", "");
}

private static String readLicenseHeader() {
Expand All @@ -456,26 +543,6 @@ private static String readLicenseHeader() {
return ret.toString();
}

private static @Nullable Dimension readImageDimension(File file) throws IOException {
if (file.getName().endsWith(".svg")) {
SVGDocument svgDocument = SVG_LOADER.load(new BufferedInputStream(
new FileInputStream(file)), null, LoaderContext.builder().build());
if (svgDocument == null) {
throw new IOException("Failed to load SVG file " + file);
}
FloatSize floatSize = svgDocument.size();
return new Dimension(
(int) Math.ceil(floatSize.getWidth()),
(int) Math.ceil(floatSize.getHeight()));
}
BufferedImage image = ImageIO.read(file);
if (image == null)
throw new IOException("ImageIO.read returned null for " + file);
int width = image.getWidth();
int height = image.getHeight();
return new Dimension(width, height);
}

private static ImmutableMap<Hash, Dimension> readImageDimensions(
File nbsrcDir, ImmutableSetMultimap<Hash, IconPath> filesByHash)
throws IOException
Expand All @@ -488,7 +555,7 @@ private static ImmutableMap<Hash, Dimension> readImageDimensions(
continue;
}
File file = new File(nbsrcDir, ip.toString());
Dimension dim = readImageDimension(file);
Dimension dim = ImageUtil.readImageDimension(file);
Util.putChecked(ret, hash, dim);
}
return ImmutableMap.copyOf(ret);
Expand Down
Loading