Merge pull request #4168 from poqudrof/master-origin

Extended support of SVG format
This commit is contained in:
Ben Fry
2019-01-18 11:35:32 -08:00
committed by GitHub
2 changed files with 274 additions and 2 deletions
+77
View File
@@ -22,8 +22,23 @@
package processing.core;
import java.awt.Image;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Base64.Decoder;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.nio.charset.StandardCharsets;
import javax.swing.ImageIcon;
import javax.xml.bind.DatatypeConverter;
import processing.core.PApplet;
@@ -106,6 +121,7 @@ public class PShape implements PConstants {
/** Texture or image data associated with this shape. */
protected PImage image;
protected String imagePath = null;
public static final String OUTSIDE_BEGIN_END_ERROR =
"%1$s can only be called between beginShape() and endShape()";
@@ -1645,6 +1661,10 @@ public class PShape implements PConstants {
params[6], params[7]);
} else if (kind == RECT) {
if (imagePath != null){
loadImage(g);
}
if (image != null) {
int oldMode = g.imageMode;
g.imageMode(CORNER);
@@ -1879,6 +1899,63 @@ public class PShape implements PConstants {
g.endShape(close ? CLOSE : OPEN);
}
private void loadImage(PGraphics g){
if(this.imagePath.startsWith("data:image")){
loadBase64Image();
}
if(this.imagePath.startsWith("file://")){
loadFileSystemImage(g);
}
this.imagePath = null;
}
private void loadFileSystemImage(PGraphics g){
imagePath = imagePath.substring(7);
PImage loadedImage = g.parent.loadImage(imagePath);
if(loadedImage == null){
System.err.println("Error loading image file: " + imagePath);
}else{
setTexture(loadedImage);
}
}
private void loadBase64Image(){
String[] parts = this.imagePath.split(";base64,");
String extension = parts[0].substring(11);
String encodedData = parts[1];
byte[] decodedBytes = DatatypeConverter.parseBase64Binary(encodedData);
if(decodedBytes == null){
System.err.println("Decode Error on image: " + imagePath.substring(0, 20));
return;
}
Image awtImage = new ImageIcon(decodedBytes).getImage();
if (awtImage instanceof BufferedImage) {
BufferedImage buffImage = (BufferedImage) awtImage;
int space = buffImage.getColorModel().getColorSpace().getType();
if (space == ColorSpace.TYPE_CMYK) {
return;
}
}
PImage loadedImage = new PImage(awtImage);
if (loadedImage.width == -1) {
// error...
}
// if it's a .gif image, test to see if it has transparency
if (extension.equals("gif") || extension.equals("png") ||
extension.equals("unknown")) {
loadedImage.checkAlpha();
}
setTexture(loadedImage);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+197 -2
View File
@@ -24,6 +24,9 @@
package processing.core;
import static java.awt.Font.BOLD;
import static java.awt.Font.ITALIC;
import static java.awt.Font.PLAIN;
import processing.data.*;
// TODO replace these with PMatrix2D
@@ -331,6 +334,10 @@ public class PShapeSVG extends PShape {
} else if (name.equals("rect")) {
shape = createShape(this, elem, true);
shape.parseRect();
} else if (name.equals("image")) {
shape = createShape(this, elem, true);
shape.parseImage();
} else if (name.equals("polygon")) {
shape = createShape(this, elem, true);
@@ -360,8 +367,10 @@ public class PShapeSVG extends PShape {
// return new FontGlyph(this, elem);
} else if (name.equals("text")) { // || name.equals("font")) {
PGraphics.showWarning("Text and fonts in SVG files are " +
"not currently supported, convert text to outlines instead.");
return new Text(this, elem);
} else if (name.equals("tspan")) {
return new LineOfText(this, elem);
} else if (name.equals("filter")) {
PGraphics.showWarning("Filters are not supported.");
@@ -441,8 +450,23 @@ public class PShapeSVG extends PShape {
getFloatWithUnit(element, "height", svgHeight)
};
}
protected void parseImage() {
kind = RECT;
textureMode = NORMAL;
family = PRIMITIVE;
params = new float[] {
getFloatWithUnit(element, "x", svgWidth),
getFloatWithUnit(element, "y", svgHeight),
getFloatWithUnit(element, "width", svgWidth),
getFloatWithUnit(element, "height", svgHeight)
};
this.imagePath = element.getString("xlink:href");
}
/**
* Parse a polyline or polygon from an SVG file.
* Syntax defined at http://www.w3.org/TR/SVG/shapes.html#PointsBNF
@@ -1572,8 +1596,179 @@ public class PShapeSVG extends PShape {
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
public static float TEXT_QUALITY = 1;
protected PFont parseFont(XML properties) {
// FontFace fontFace = new FontFace(this, properties);
String fontFamily = null;
float size = 10;
int weight = PLAIN; // 0
int italic = 0;
if (properties.hasAttribute("style")) {
String styleText = properties.getString("style");
String[] styleTokens = PApplet.splitTokens(styleText, ";");
//PApplet.println(styleTokens);
for (int i = 0; i < styleTokens.length; i++) {
String[] tokens = PApplet.splitTokens(styleTokens[i], ":");
//PApplet.println(tokens);
tokens[0] = PApplet.trim(tokens[0]);
if (tokens[0].equals("font-style")) {
// PApplet.println("font-style: " + tokens[1]);
if (tokens[1].contains("italic")) {
italic = ITALIC;
}
} else if (tokens[0].equals("font-variant")) {
// PApplet.println("font-variant: " + tokens[1]);
// setFillOpacity(tokens[1]);
} else if (tokens[0].equals("font-weight")) {
// PApplet.println("font-weight: " + tokens[1]);
if (tokens[1].contains("bold")) {
weight = BOLD;
// PApplet.println("Bold weight ! ");
}
} else if (tokens[0].equals("font-stretch")) {
// not supported.
} else if (tokens[0].equals("font-size")) {
// PApplet.println("font-size: " + tokens[1]);
size = Float.parseFloat(tokens[1].split("px")[0]);
// PApplet.println("font-size-parsed: " + size);
} else if (tokens[0].equals("line-height")) {
// not supported
} else if (tokens[0].equals("font-family")) {
// PApplet.println("Font-family: " + tokens[1]);
fontFamily = tokens[1];
} else if (tokens[0].equals("text-align")) {
// not supported
} else if (tokens[0].equals("letter-spacing")) {
// not supported
} else if (tokens[0].equals("word-spacing")) {
// not supported
} else if (tokens[0].equals("writing-mode")) {
// not supported
} else if (tokens[0].equals("text-anchor")) {
// not supported
} else {
// Other attributes are not yet implemented
}
}
}
if (fontFamily == null) {
return null;
}
size = size * TEXT_QUALITY;
return createFont(fontFamily, weight | italic, size, true);
}
protected PFont createFont(String name, int weight, float size, boolean smooth) {
// System.out.println("Try to create a font of " + name + " family, " + weight);
java.awt.Font baseFont = new java.awt.Font(name, weight, (int) size); // PFont.findFont(name);ç
// System.out.println("Resulting family : " + baseFont.getFamily() + " " + baseFont.getStyle());
PFont outputPFont = new PFont(baseFont.deriveFont(size), smooth, null);
// System.out.println("Resulting PFont family : " + outputPFont.getName());
return outputPFont;
}
public static class Text extends PShapeSVG {
protected PFont font;
public Text(PShapeSVG parent, XML properties) {
super(parent, properties, true);
// get location
float x = Float.parseFloat(properties.getString("x"));
float y = Float.parseFloat(properties.getString("y"));
if (matrix == null) {
matrix = new PMatrix2D();
}
matrix.translate(x, y);
family = GROUP;
font = parseFont(properties);
}
// @Override
// public void drawImpl(PGraphics g){
// }
}
public static class LineOfText extends PShapeSVG {
String textToDisplay;
PFont font;
public LineOfText(PShapeSVG parent, XML properties) {
// TODO: child should ideally be parsed too...
// for inline content.
super(parent, properties, false);
// // get location
float x = Float.parseFloat(properties.getString("x"));
float y = Float.parseFloat(properties.getString("y"));
float parentX = Float.parseFloat(parent.element.getString("x"));
float parentY = Float.parseFloat(parent.element.getString("y"));
if (matrix == null) matrix = new PMatrix2D();
matrix.translate(x - parentX, (y - parentY) / 2f);
// get the first properties
parseColors(properties);
font = parseFont(properties);
// It is a line..
boolean isALine = properties.getString("role") == "line";
// NO inline content yet.
if (this.childCount > 0) {
}
String text = properties.getContent();
textToDisplay = text;
}
@Override
public void drawImpl(PGraphics g) {
if (font == null) {
font = ((Text) parent).font;
if (font == null) {
return;
}
}
pre(g);
g.textFont(font, font.size / TEXT_QUALITY);
g.text(textToDisplay, 0, 0);
post(g);
}
}
public static class Font extends PShapeSVG {
public FontFace face;