Skip to content

Commit 73d5cae

Browse files
authored
Merge pull request processing#4168 from poqudrof/master-origin
Extended support of SVG format
2 parents 2e476be + 0eb5ba5 commit 73d5cae

2 files changed

Lines changed: 274 additions & 2 deletions

File tree

core/src/processing/core/PShape.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,23 @@
2222

2323
package processing.core;
2424

25+
import java.awt.Image;
26+
import java.awt.color.ColorSpace;
27+
import java.awt.image.BufferedImage;
28+
import java.io.IOException;
29+
import java.nio.charset.Charset;
30+
import java.nio.file.Files;
31+
import java.nio.file.Paths;
2532
import java.util.HashMap;
2633
import java.util.Map;
34+
import java.util.Base64.Decoder;
35+
import java.util.logging.Level;
36+
import java.util.logging.Logger;
37+
import java.nio.charset.StandardCharsets;
38+
import javax.swing.ImageIcon;
39+
import javax.xml.bind.DatatypeConverter;
40+
41+
2742

2843
import processing.core.PApplet;
2944

@@ -106,6 +121,7 @@ public class PShape implements PConstants {
106121

107122
/** Texture or image data associated with this shape. */
108123
protected PImage image;
124+
protected String imagePath = null;
109125

110126
public static final String OUTSIDE_BEGIN_END_ERROR =
111127
"%1$s can only be called between beginShape() and endShape()";
@@ -1645,6 +1661,10 @@ protected void drawPrimitive(PGraphics g) {
16451661
params[6], params[7]);
16461662

16471663
} else if (kind == RECT) {
1664+
1665+
if (imagePath != null){
1666+
loadImage(g);
1667+
}
16481668
if (image != null) {
16491669
int oldMode = g.imageMode;
16501670
g.imageMode(CORNER);
@@ -1879,6 +1899,63 @@ protected void drawPath(PGraphics g) {
18791899
g.endShape(close ? CLOSE : OPEN);
18801900
}
18811901

1902+
private void loadImage(PGraphics g){
1903+
1904+
if(this.imagePath.startsWith("data:image")){
1905+
loadBase64Image();
1906+
}
1907+
1908+
if(this.imagePath.startsWith("file://")){
1909+
loadFileSystemImage(g);
1910+
}
1911+
this.imagePath = null;
1912+
}
1913+
1914+
private void loadFileSystemImage(PGraphics g){
1915+
imagePath = imagePath.substring(7);
1916+
PImage loadedImage = g.parent.loadImage(imagePath);
1917+
if(loadedImage == null){
1918+
System.err.println("Error loading image file: " + imagePath);
1919+
}else{
1920+
setTexture(loadedImage);
1921+
}
1922+
}
1923+
1924+
private void loadBase64Image(){
1925+
String[] parts = this.imagePath.split(";base64,");
1926+
String extension = parts[0].substring(11);
1927+
String encodedData = parts[1];
1928+
1929+
byte[] decodedBytes = DatatypeConverter.parseBase64Binary(encodedData);
1930+
1931+
if(decodedBytes == null){
1932+
System.err.println("Decode Error on image: " + imagePath.substring(0, 20));
1933+
return;
1934+
}
1935+
1936+
Image awtImage = new ImageIcon(decodedBytes).getImage();
1937+
1938+
if (awtImage instanceof BufferedImage) {
1939+
BufferedImage buffImage = (BufferedImage) awtImage;
1940+
int space = buffImage.getColorModel().getColorSpace().getType();
1941+
if (space == ColorSpace.TYPE_CMYK) {
1942+
return;
1943+
}
1944+
}
1945+
1946+
PImage loadedImage = new PImage(awtImage);
1947+
if (loadedImage.width == -1) {
1948+
// error...
1949+
}
1950+
1951+
// if it's a .gif image, test to see if it has transparency
1952+
if (extension.equals("gif") || extension.equals("png") ||
1953+
extension.equals("unknown")) {
1954+
loadedImage.checkAlpha();
1955+
}
1956+
1957+
setTexture(loadedImage);
1958+
}
18821959

18831960
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18841961

core/src/processing/core/PShapeSVG.java

Lines changed: 197 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
package processing.core;
2626

27+
import static java.awt.Font.BOLD;
28+
import static java.awt.Font.ITALIC;
29+
import static java.awt.Font.PLAIN;
2730
import processing.data.*;
2831

2932
// TODO replace these with PMatrix2D
@@ -331,6 +334,10 @@ protected PShape parseChild(XML elem) {
331334
} else if (name.equals("rect")) {
332335
shape = createShape(this, elem, true);
333336
shape.parseRect();
337+
338+
} else if (name.equals("image")) {
339+
shape = createShape(this, elem, true);
340+
shape.parseImage();
334341

335342
} else if (name.equals("polygon")) {
336343
shape = createShape(this, elem, true);
@@ -360,8 +367,10 @@ protected PShape parseChild(XML elem) {
360367
// return new FontGlyph(this, elem);
361368

362369
} else if (name.equals("text")) { // || name.equals("font")) {
363-
PGraphics.showWarning("Text and fonts in SVG files are " +
364-
"not currently supported, convert text to outlines instead.");
370+
return new Text(this, elem);
371+
372+
} else if (name.equals("tspan")) {
373+
return new LineOfText(this, elem);
365374

366375
} else if (name.equals("filter")) {
367376
PGraphics.showWarning("Filters are not supported.");
@@ -441,8 +450,23 @@ protected void parseRect() {
441450
getFloatWithUnit(element, "height", svgHeight)
442451
};
443452
}
453+
454+
455+
protected void parseImage() {
456+
kind = RECT;
457+
textureMode = NORMAL;
444458

459+
family = PRIMITIVE;
460+
params = new float[] {
461+
getFloatWithUnit(element, "x", svgWidth),
462+
getFloatWithUnit(element, "y", svgHeight),
463+
getFloatWithUnit(element, "width", svgWidth),
464+
getFloatWithUnit(element, "height", svgHeight)
465+
};
445466

467+
this.imagePath = element.getString("xlink:href");
468+
}
469+
446470
/**
447471
* Parse a polyline or polygon from an SVG file.
448472
* Syntax defined at http://www.w3.org/TR/SVG/shapes.html#PointsBNF
@@ -1572,8 +1596,179 @@ public RadialGradient(PShapeSVG parent, XML properties) {
15721596

15731597

15741598
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1599+
1600+
public static float TEXT_QUALITY = 1;
1601+
protected PFont parseFont(XML properties) {
1602+
1603+
// FontFace fontFace = new FontFace(this, properties);
1604+
String fontFamily = null;
1605+
float size = 10;
1606+
int weight = PLAIN; // 0
1607+
int italic = 0;
1608+
1609+
if (properties.hasAttribute("style")) {
1610+
String styleText = properties.getString("style");
1611+
String[] styleTokens = PApplet.splitTokens(styleText, ";");
1612+
1613+
//PApplet.println(styleTokens);
1614+
for (int i = 0; i < styleTokens.length; i++) {
1615+
1616+
String[] tokens = PApplet.splitTokens(styleTokens[i], ":");
1617+
//PApplet.println(tokens);
1618+
1619+
tokens[0] = PApplet.trim(tokens[0]);
1620+
1621+
if (tokens[0].equals("font-style")) {
1622+
// PApplet.println("font-style: " + tokens[1]);
1623+
if (tokens[1].contains("italic")) {
1624+
italic = ITALIC;
1625+
}
1626+
} else if (tokens[0].equals("font-variant")) {
1627+
// PApplet.println("font-variant: " + tokens[1]);
1628+
// setFillOpacity(tokens[1]);
1629+
1630+
} else if (tokens[0].equals("font-weight")) {
1631+
// PApplet.println("font-weight: " + tokens[1]);
1632+
1633+
if (tokens[1].contains("bold")) {
1634+
weight = BOLD;
1635+
// PApplet.println("Bold weight ! ");
1636+
}
1637+
1638+
1639+
} else if (tokens[0].equals("font-stretch")) {
1640+
// not supported.
1641+
1642+
} else if (tokens[0].equals("font-size")) {
1643+
// PApplet.println("font-size: " + tokens[1]);
1644+
size = Float.parseFloat(tokens[1].split("px")[0]);
1645+
// PApplet.println("font-size-parsed: " + size);
1646+
} else if (tokens[0].equals("line-height")) {
1647+
// not supported
1648+
1649+
} else if (tokens[0].equals("font-family")) {
1650+
// PApplet.println("Font-family: " + tokens[1]);
1651+
fontFamily = tokens[1];
1652+
1653+
} else if (tokens[0].equals("text-align")) {
1654+
// not supported
1655+
1656+
} else if (tokens[0].equals("letter-spacing")) {
1657+
// not supported
1658+
1659+
} else if (tokens[0].equals("word-spacing")) {
1660+
// not supported
1661+
1662+
} else if (tokens[0].equals("writing-mode")) {
1663+
// not supported
1664+
1665+
} else if (tokens[0].equals("text-anchor")) {
1666+
// not supported
1667+
1668+
} else {
1669+
// Other attributes are not yet implemented
1670+
}
1671+
}
1672+
}
1673+
if (fontFamily == null) {
1674+
return null;
1675+
}
1676+
size = size * TEXT_QUALITY;
1677+
1678+
return createFont(fontFamily, weight | italic, size, true);
1679+
}
1680+
1681+
protected PFont createFont(String name, int weight, float size, boolean smooth) {
1682+
1683+
// System.out.println("Try to create a font of " + name + " family, " + weight);
1684+
java.awt.Font baseFont = new java.awt.Font(name, weight, (int) size); // PFont.findFont(name);ç
1685+
1686+
// System.out.println("Resulting family : " + baseFont.getFamily() + " " + baseFont.getStyle());
1687+
PFont outputPFont = new PFont(baseFont.deriveFont(size), smooth, null);
1688+
1689+
// System.out.println("Resulting PFont family : " + outputPFont.getName());
1690+
return outputPFont;
1691+
}
1692+
1693+
public static class Text extends PShapeSVG {
1694+
1695+
protected PFont font;
1696+
1697+
public Text(PShapeSVG parent, XML properties) {
1698+
super(parent, properties, true);
1699+
1700+
// get location
1701+
float x = Float.parseFloat(properties.getString("x"));
1702+
float y = Float.parseFloat(properties.getString("y"));
1703+
1704+
if (matrix == null) {
1705+
matrix = new PMatrix2D();
1706+
}
1707+
matrix.translate(x, y);
1708+
1709+
family = GROUP;
1710+
1711+
font = parseFont(properties);
15751712

1713+
}
1714+
1715+
// @Override
1716+
// public void drawImpl(PGraphics g){
1717+
// }
1718+
}
1719+
1720+
public static class LineOfText extends PShapeSVG {
15761721

1722+
String textToDisplay;
1723+
PFont font;
1724+
1725+
public LineOfText(PShapeSVG parent, XML properties) {
1726+
1727+
// TODO: child should ideally be parsed too...
1728+
// for inline content.
1729+
super(parent, properties, false);
1730+
1731+
// // get location
1732+
float x = Float.parseFloat(properties.getString("x"));
1733+
float y = Float.parseFloat(properties.getString("y"));
1734+
1735+
float parentX = Float.parseFloat(parent.element.getString("x"));
1736+
float parentY = Float.parseFloat(parent.element.getString("y"));
1737+
1738+
if (matrix == null) matrix = new PMatrix2D();
1739+
matrix.translate(x - parentX, (y - parentY) / 2f);
1740+
1741+
// get the first properties
1742+
parseColors(properties);
1743+
font = parseFont(properties);
1744+
1745+
// It is a line..
1746+
boolean isALine = properties.getString("role") == "line";
1747+
// NO inline content yet.
1748+
if (this.childCount > 0) {
1749+
}
1750+
1751+
String text = properties.getContent();
1752+
textToDisplay = text;
1753+
}
1754+
1755+
@Override
1756+
public void drawImpl(PGraphics g) {
1757+
if (font == null) {
1758+
font = ((Text) parent).font;
1759+
if (font == null) {
1760+
return;
1761+
}
1762+
}
1763+
1764+
pre(g);
1765+
g.textFont(font, font.size / TEXT_QUALITY);
1766+
g.text(textToDisplay, 0, 0);
1767+
post(g);
1768+
}
1769+
1770+
}
1771+
15771772
public static class Font extends PShapeSVG {
15781773
public FontFace face;
15791774

0 commit comments

Comments
 (0)