/***********************************************************************
* mt4j Copyright (c) 2008 - 2009 C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package org.mt4j.util.xml.svg;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RadialGradientPaint;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;
import javax.swing.JPanel;
import org.apache.batik.bridge.AbstractSVGGradientElementBridge;
import org.apache.batik.bridge.Bridge;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.BridgeException;
import org.apache.batik.bridge.CSSUtilities;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.PaintServer;
import org.apache.batik.bridge.SVGTextElementBridge;
import org.apache.batik.bridge.SVGUtilities;
import org.apache.batik.bridge.TextUtilities;
import org.apache.batik.bridge.UnitProcessor;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.bridge.AbstractSVGGradientElementBridge.SVGStopElementBridge;
import org.apache.batik.bridge.AbstractSVGGradientElementBridge.Stop;
import org.apache.batik.css.engine.SVGCSSEngine;
import org.apache.batik.css.engine.value.ListValue;
import org.apache.batik.css.engine.value.Value;
import org.apache.batik.css.engine.value.svg.ICCColor;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.dom.svg.SVGGraphicsElement;
import org.apache.batik.dom.svg.SVGOMCircleElement;
import org.apache.batik.dom.svg.SVGOMClipPathElement;
import org.apache.batik.dom.svg.SVGOMDefsElement;
import org.apache.batik.dom.svg.SVGOMEllipseElement;
import org.apache.batik.dom.svg.SVGOMForeignObjectElement;
import org.apache.batik.dom.svg.SVGOMGElement;
import org.apache.batik.dom.svg.SVGOMLineElement;
import org.apache.batik.dom.svg.SVGOMLinearGradientElement;
import org.apache.batik.dom.svg.SVGOMMaskElement;
import org.apache.batik.dom.svg.SVGOMPathElement;
import org.apache.batik.dom.svg.SVGOMPolygonElement;
import org.apache.batik.dom.svg.SVGOMPolylineElement;
import org.apache.batik.dom.svg.SVGOMRadialGradientElement;
import org.apache.batik.dom.svg.SVGOMRectElement;
import org.apache.batik.dom.svg.SVGOMSVGElement;
import org.apache.batik.dom.svg.SVGOMSwitchElement;
import org.apache.batik.dom.svg.SVGOMTSpanElement;
import org.apache.batik.dom.svg.SVGOMTextElement;
import org.apache.batik.dom.svg.SVGOMToBeImplementedElement;
import org.apache.batik.dom.svg.SVGTextContentSupport;
import org.apache.batik.dom.svg.SVGURIReferenceGraphicsElement;
import org.apache.batik.dom.svg12.BindableElement;
import org.apache.batik.dom.svg12.SVGOMFlowRootElement;
import org.apache.batik.dom.util.XLinkSupport;
import org.apache.batik.ext.awt.MultipleGradientPaint;
import org.apache.batik.ext.awt.MultipleGradientPaint.CycleMethodEnum;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.TextNode;
import org.apache.batik.gvt.font.AWTGVTFont;
import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.batik.gvt.text.TextPaintInfo;
import org.apache.batik.parser.PathParser;
import org.apache.batik.parser.TransformListParser;
import org.apache.batik.util.SVGConstants;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.mt4j.MTApplication;
import org.mt4j.components.MTComponent;
import org.mt4j.components.TransformSpace;
import org.mt4j.components.bounds.BoundsZPlaneRectangle;
import org.mt4j.components.bounds.IBoundingShape;
import org.mt4j.components.clipping.FillPaint;
import org.mt4j.components.visibleComponents.AbstractVisibleComponent;
import org.mt4j.components.visibleComponents.GeometryInfo;
import org.mt4j.components.visibleComponents.font.FontManager;
import org.mt4j.components.visibleComponents.font.IFont;
import org.mt4j.components.visibleComponents.shapes.AbstractShape;
import org.mt4j.components.visibleComponents.shapes.MTEllipse;
import org.mt4j.components.visibleComponents.shapes.MTLine;
import org.mt4j.components.visibleComponents.shapes.MTPolygon;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.components.visibleComponents.shapes.MTRoundRectangle;
import org.mt4j.components.visibleComponents.shapes.MTStencilPolygon;
import org.mt4j.components.visibleComponents.shapes.MTRectangle.PositionAnchor;
import org.mt4j.components.visibleComponents.shapes.mesh.MTTriangleMesh;
import org.mt4j.components.visibleComponents.widgets.MTTextArea;
import org.mt4j.input.gestureAction.DefaultDragAction;
import org.mt4j.input.gestureAction.DefaultRotateAction;
import org.mt4j.input.gestureAction.DefaultScaleAction;
import org.mt4j.input.inputProcessors.IGestureEventListener;
import org.mt4j.input.inputProcessors.componentProcessors.dragProcessor.DragProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.rotateProcessor.RotateProcessor;
import org.mt4j.input.inputProcessors.componentProcessors.scaleProcessor.ScaleProcessor;
import org.mt4j.util.HelperMethods;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.MTColor;
import org.mt4j.util.SwingTextureRenderer;
import org.mt4j.util.math.ConvexityUtil;
import org.mt4j.util.math.Matrix;
import org.mt4j.util.math.ToolsGeometry;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;
import org.mt4j.util.opengl.GluTrianglulator;
import org.mt4j.util.opengl.GLTexture;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.css.CSSPrimitiveValue;
import org.w3c.dom.css.CSSStyleDeclaration;
import org.w3c.dom.css.CSSValue;
import org.w3c.dom.svg.SVGAnimatedLength;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGLength;
import org.w3c.dom.svg.SVGLengthList;
import org.w3c.dom.svg.SVGMatrix;
import org.w3c.dom.svg.SVGPoint;
import org.w3c.dom.svg.SVGPointList;
import org.w3c.dom.svg.SVGSVGElement;
import processing.core.PApplet;
import processing.opengl.PGraphicsOpenGL;
/**
* This class can be used to load and display scalable vector graphics (svg) files.
*
* @author Christopher Ruff
*/
public class SVGLoader implements SVGConstants{
private static final Logger logger = Logger.getLogger(SVGLoader.class.getName());
static{
logger.setLevel(Level.ERROR);
SimpleLayout l = new SimpleLayout();
ConsoleAppender ca = new ConsoleAppender(l);
logger.addAppender(ca);
}
/** The svg doc. */
private SVGDocument svgDoc;
/** The user agent. */
private UserAgent userAgent;
/** The loader. */
private DocumentLoader loader;
/** The ctx. */
private BridgeContext ctx;
/** The builder. */
private GVTBuilder builder;
/** The root gn. */
private GraphicsNode rootGN;
/** The css engine. */
protected SVGCSSEngine cssEngine;
/** The pa. */
private PApplet pa;
/** The opacity stack. */
private Stack<Float> opacityStack;
/** The default drag action. */
private IGestureEventListener defaultDragAction;
/** The default rotate action. */
private IGestureEventListener defaultRotateAction;
/** The default scale action. */
private IGestureEventListener defaultScaleAction;
/** The current local transform matrix. */
private Matrix currentLocalTransformMatrix;
/**
* Instantiates a new batik svg parser.
*
* @param pa the pa
*/
public SVGLoader(PApplet pa){
this.pa = pa;
opacityStack = new Stack<Float>();
currentLocalTransformMatrix = new Matrix();
defaultDragAction = new DefaultDragAction();
defaultRotateAction = new DefaultRotateAction();
defaultScaleAction = new DefaultScaleAction();
}
/**
* Loads a "*.svg" file, parses it, creates drawable components and returns the
* toplevel component.
*
* @param filedescr the absolute path of the svg file as a string
*
* @return the MT base component
*
* the created top level component of the svg
*/
public MTComponent loadSvg(String filedescr){
return this.getCreatedSvgComponents(this.parseSvg(filedescr));
}
/**
* Uses the batik parser to genererate an svg document from an svg file.
* To create the components in that svg document, call <code>getCreatedSvgComponents(SVGDocument doc)</code>
*
* @param filedescr the filedescr
*
* @return the SVG document
*/
public SVGDocument parseSvg(String filedescr){
Document doc;
try {
String parser = XMLResourceDescriptor.getXMLParserClassName();
SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
File file = new File(filedescr);
if (file.exists()){
URI localFileAsUri = file.toURI();
String uri = localFileAsUri.toASCIIString();
doc = f.createDocument(uri);
}else{
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(filedescr);
if (in == null){
in = pa.getClass().getResourceAsStream(filedescr);
}
doc = f.createDocument(filedescr, in);
/*on it (after casting it to SVGOMDocument) to give it a
URI of some sort. If the document needs to be able to have relative
reference to files on the local file system, give it a URI like
"file:///some/where/file.svg";
*/
//FIXME HACK! this seems to help the "org.apache.batik.bridge.BridgeException: Unable to make sense of URL for connection" error
//occuring with windmill.svg if loading from inputstream instead of local file system file
//FIXME but this might create errors when loading external file like images from the relative svg path?
((SVGDocument)doc).setDocumentURI("") ;
// String sub = filedescr.substring(0, filedescr.lastIndexOf(MTApplication.separator));
// System.out.println("F: " + filedescr + " sub; " + sub);
// svgDoc.setDocumentURI(sub+ MTApplication.separator) ;
}
svgDoc = (SVGDocument)doc;
} catch (Exception ex) {
ex.printStackTrace();
}
//Neccesary? For booting css
try{
userAgent = new UserAgentAdapter();
loader = new DocumentLoader(userAgent);
ctx = new BridgeContext(userAgent, loader);
ctx.setDynamicState(BridgeContext.DYNAMIC); //TODO use static?
builder = new GVTBuilder();
rootGN = builder.build(ctx, svgDoc);
// ctx.getCSSEngineForElement(null).
}catch(Exception e){
e.printStackTrace();
}
return svgDoc;
}
/**
* Creates and returns components of the provided svg document for displaying.
*
* @param svgDoc the svg doc
*
* @return the created svg components
*/
public MTComponent getCreatedSvgComponents(SVGDocument svgDoc){
ArrayList<MTComponent> components = new ArrayList<MTComponent>();
opacityStack.push(1.0f);
traverseSVGDoc(svgDoc, components);
opacityStack.pop();
MTComponent[] comps = (MTComponent[])components.toArray(new MTComponent[components.size()]);
//Only returning the 1st component, since this should be the top-level <svg> element and only 1!?
return comps[0];
}
/**
* Traverse svg doc.
*
* @param node the node
* @param comps the comps
*/
private void traverseSVGDoc(Node node, ArrayList<MTComponent> comps){
logger.debug("Traversing: " + node.getNodeName());
//Damit transformationen konsistent sind muss
//jedes tag, da� eine transform attribut hat
//behandelt werden!
//Default
currentLocalTransformMatrix = new Matrix();
//If there is a TRANSFORM attribute parse that and set the
//current transformation matrix to be used with the svg components created after
NamedNodeMap atts = node.getAttributes();
if (atts != null){
for (int i = 0; i < atts.getLength(); i++) {
Node att = atts.item(i);
if (att.getNodeName().equals(SVG_TRANSFORM_ATTRIBUTE)){
CustomTransformHandler transFormHandler = new CustomTransformHandler();
TransformListParser transFormListParser = new TransformListParser();
transFormListParser.setTransformListHandler(transFormHandler);
transFormListParser.parse(att.getNodeValue());
//Overwrite current default matrix if the element has its own
//transform defined, will be used at gfx obj creation
currentLocalTransformMatrix = transFormHandler.getResultMatrix();
}
}
}
// logger.debug("Node: " + node.getNodeName() + " Class: " + node.getClass());
//For opacity inheritance
if (node instanceof SVGGraphicsElement){
SVGGraphicsElement svgGfx = (SVGGraphicsElement)node;
//Handle inherited opacity settings
float opac = queryPrimitiveFloatValue(svgGfx, "opacity", 1f);
opacityStack.push(opac *= opacityStack.peek().floatValue());
}
// if G (GROUP) element, add all children to this element
if ( node instanceof SVGOMGElement
|| node instanceof SVGSVGElement
|| node instanceof SVGOMSVGElement
){
// SVGOMGElement gElem = (SVGOMGElement)node;
SVGElement gElem = (SVGElement)node;
MTComponent group = new MTComponent(pa);
group.setName(gElem.getTagName());
// Element viewPort = gElem.getViewportElement();
// logger.debug("Viewport " + viewPort.getNodeName());
//Set the <g> group to composite, meaning that it will
//be returned at picking, when one of the children gets picked
group.setComposite(true);
group.setLocalMatrix(currentLocalTransformMatrix);
//IF its <svg> element get the transform
//(to honor the viewBox and the width/height attributes
if (node instanceof SVGOMSVGElement ){
SVGOMSVGElement svgGom = ((SVGOMSVGElement)node);
Element viewPort = svgGom.getViewportElement();
if (viewPort != null)
logger.debug("Viewport " + viewPort.getNodeName());
// SVGMatrix mat = svgGom.getScreenCTM();
SVGAnimatedLength widthA = svgGom.getWidth();
SVGAnimatedLength heightA = svgGom.getHeight();
SVGLength w = widthA.getBaseVal();
float width = w.getValue();
SVGLength h = heightA.getBaseVal();
float height = h.getValue();
logger.debug("-> SVG Width: " + width + " Height: " + height);
SVGMatrix mat = svgGom.getCTM();
/*
logger.debug("mat: " + mat.toString());
logger.debug(mat.getA());
logger.debug(mat.getB());
logger.debug(mat.getC());
logger.debug(mat.getD());
logger.debug(mat.getE());
logger.debug(mat.getF());
SVGRect bbox = svgGom.getBBox();
logger.debug("BBOx: X:" + bbox.getX() + " Y:" + bbox.getY() + " Width:" + bbox.getWidth() + " Height:" + bbox.getHeight());
*/
//Hack, because if no width/height is specified default of 1.0
//is assumed by batik -> things may get scaled too small
if ( !(width == 1 && height == 1) ){
currentLocalTransformMatrix = new Matrix(mat.getA(), mat.getC(), 0, mat.getE(),
mat.getB(), mat.getD(), 0, mat.getF(),
0, 0, 1, 0,
0, 0, 0, 1
);
//logger.debug("Matrix: " + currentLocalTransformMatrix);
group.setLocalMatrix(currentLocalTransformMatrix);
}
}
//Make the group pickable and manipulatable
group.setPickable(true);
group.registerInputProcessor(new DragProcessor(pa));
group.setGestureAllowance(DragProcessor.class, true);
group.addGestureListener(DragProcessor.class, (IGestureEventListener)defaultDragAction);
group.registerInputProcessor(new RotateProcessor(pa));
group.addGestureListener(RotateProcessor.class, defaultRotateAction);
group.registerInputProcessor(new ScaleProcessor(pa));
group.addGestureListener(ScaleProcessor.class, defaultScaleAction);
ArrayList<MTComponent> groupChildren = new ArrayList<MTComponent>();
//Traverse the children and add them to a new arraylist
traverseChildren(gElem, groupChildren);
MTComponent[] childComps = (MTComponent[])groupChildren.toArray(new MTComponent[groupChildren.size()]);
//Add the children to the group
group.addChildren(childComps);
//Add the group to the arraylist of the parent
comps.add(group);
}else{//If NOT GROUP
if (node instanceof SVGGraphicsElement){
SVGGraphicsElement svgGfxElem = (SVGGraphicsElement)node;
//IF node isnt a group node just add it to the passed in comps arraylist
try{
//Create a component from the graphicsnode and add it to the parents arraylist
MTComponent liveComponent = handleGraphicsNode(svgGfxElem);
if (liveComponent != null){
comps.add(liveComponent);
}
}catch(Exception e){
logger.error("Error handling svg node: " + svgGfxElem.getTagName());
e.printStackTrace();
}
}
//FIXME IMPLEMENT
if (node instanceof SVGOMTSpanElement){
SVGOMTSpanElement tSpanElement = (SVGOMTSpanElement)node;
}
//FIXME TEST
if (node instanceof SVGOMTextElement){
boolean useVectorFont = false;
SVGOMTextElement textElement = (SVGOMTextElement)node;
//Get <text> position values (can be a list)
List<Float> xValues = getSVGLengthListAsFloat(textElement.getX().getBaseVal());
List<Float> yValues = getSVGLengthListAsFloat(textElement.getY().getBaseVal());
// /*//Not used
String textContent = TextUtilities.getElementContent(textElement);
textContent = textContent.replaceAll("\\n","");
textContent = textContent.trim();
// */
/*
//TODO USE?
textElement.getTextLength();
textElement.getRotate();
*/
if (textElement.getSVGContext() instanceof SVGTextElementBridge){
SVGTextElementBridge b = (SVGTextElementBridge)textElement.getSVGContext();
GraphicsNode gr = b.createGraphicsNode(ctx, textElement);
TextNode tNode = (TextNode)gr;
b.buildGraphicsNode(ctx, textElement, tNode);
List<?> textRuns = tNode.getTextRuns();
logger.debug("Text runs: " + textRuns);
//Get font size
float fontSize = b.getFontSize();
logger.debug("Text:" + " x:" + xValues.get(0) + " y:" + yValues.get(0) + " FontSize: " + fontSize + " Text: '" + textContent + "'");
//Get font FILL
Value fillOpacValue = CSSUtilities.getComputedStyle(textElement, SVGCSSEngine.FILL_OPACITY_INDEX);
float computedfillOpac = PaintServer.convertOpacity(fillOpacValue);
Value fillIndexValue = CSSUtilities.getComputedStyle(textElement, SVGCSSEngine.FILL_INDEX);
Object fill = SVGLoader.getFillOrStroke(textElement, fillIndexValue, computedfillOpac, ctx);
MTColor fillColor = new MTColor(150,150,150,255);
if (fill instanceof java.awt.Color) {
java.awt.Color color = (Color) fill;
fillColor.setColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
}
//Get STROKE
// Stroke Opacity \\
Value strokeOpacValue = CSSUtilities.getComputedStyle(textElement, SVGCSSEngine.STROKE_OPACITY_INDEX);
float computedStrokeOpacity = PaintServer.convertOpacity(strokeOpacValue);
// Stroke java.awt.Color \\
Value strokeIndexValue = CSSUtilities.getComputedStyle(textElement, SVGCSSEngine.STROKE_INDEX);
Object stroke = SVGLoader.getFillOrStroke(textElement, strokeIndexValue, computedStrokeOpacity, ctx);
MTColor strokeColor = new MTColor(fillColor.getR(), fillColor.getG(), fillColor.getB(), fillColor.getAlpha());
if (stroke instanceof java.awt.Color) {
java.awt.Color color = (Color) stroke;
strokeColor.setColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
}
//Get the font family
Value fontFamilyValue = CSSUtilities.getComputedStyle(textElement, SVGCSSEngine.FONT_FAMILY_INDEX);
String fontFamily = "arial"; //DEFAULT
if (fontFamilyValue instanceof ListValue) {
ListValue listValue = (ListValue) fontFamilyValue;
Value firstValue = listValue.item(0); //Can be a List? -> take only the first one..
if (firstValue != null)
fontFamily = firstValue.getStringValue();
}
logger.debug("Font family: " + fontFamily);
IFont font;
if (useVectorFont)
//Vector font
font = FontManager.getInstance().createFont(pa,
"arial.ttf", Math.round(fontSize), fillColor, strokeColor);
else
//Bitmap font
font = FontManager.getInstance().createFont(pa,
// "Arial", Math.round(fontSize),
fontFamily, Math.round(fontSize), fillColor, strokeColor);
// /*
IFont fontToUse = font;
IFont lastUsedFont = fontToUse;
List<MTTextArea> textAreas = new ArrayList<MTTextArea>();
AttributedCharacterIterator iter = tNode.getAttributedCharacterIterator();
if (font != null && iter != null){ //To avoid not loaded fonts or if text ist empty
for (int i = iter.getBeginIndex(); i < iter.getEndIndex(); i++) {
char currentChar = iter.setIndex(i);
Set<Attribute> keys = iter.getAllAttributeKeys();
Map<Attribute, Object> charAtts = iter.getAttributes();
Object baseLineShift = charAtts.get(SVGTextElementBridge.BASELINE_SHIFT);
Object paintInfo = charAtts.get(SVGTextElementBridge.PAINT_INFO);
Object charX = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.X);
Object charY = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.Y);
Object charDX = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.DX);
Object charDY = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.DY);
Object charRotation = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.ROTATION);
Object gvtFont = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
Object gvtFonts = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.GVT_FONTS);
Object gvtFontFamilies = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES);
Object textCompoundDelimiter = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER);
Object verticalOrientation = charAtts.get(GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION);
logger.debug("Character: " + currentChar + " CharX:" + charX + " CharY: " + charY + " CharDX: " + charDX + " CharDY: " + charDY + " Font: " + gvtFont + " Fonts: " + gvtFonts + " FontFamilies: " + gvtFontFamilies);
AWTGVTFont awtGvtFont = (AWTGVTFont)gvtFont;
if (awtGvtFont != null)
logger.debug("CharfontSize: " + awtGvtFont.getSize());
//FIXME REMOVE, Not working always 0,0
SVGPoint startPosOfChar = SVGTextContentSupport.getStartPositionOfChar(textElement, i);
/////////////////////////////////////
//Get the character information - font, colors
String newFamilyName = new String(fontFamily);
float newFontSize = fontSize;
MTColor newFillColor = new MTColor(fillColor);
MTColor newStrokeColor = new MTColor(strokeColor);
boolean charHasColorInfo = false;
boolean charHasFontInfo = false;
//Get chars paint info
if (paintInfo != null && paintInfo instanceof TextPaintInfo){
charHasColorInfo = true;
TextPaintInfo texInfo = (TextPaintInfo)paintInfo;
if (texInfo.fillPaint instanceof java.awt.Color){
java.awt.Color color = (Color)texInfo.fillPaint;
newFillColor.setColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
}
if (texInfo.strokePaint instanceof java.awt.Color){
java.awt.Color color = (Color)texInfo.strokePaint;
newStrokeColor.setColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
}
}
//Get the chars font family and size
GVTFont aGvtFont = null;
if (gvtFonts!=null){
if (gvtFonts instanceof List) {
List<?> fonts = (List<?>) gvtFonts;
for (Iterator<?> iterator = fonts.iterator(); iterator.hasNext();) {
Object o = (Object) iterator.next();
if (o instanceof GVTFont) {
aGvtFont = (GVTFont) o;
//logger.debug("Char font family: " + aGvtFont.getFamilyName() + " Size:" + aGvtFont.getSize());
}
}
}
}
if (aGvtFont != null){
charHasFontInfo = true;
newFamilyName = aGvtFont.getFamilyName();
newFontSize = aGvtFont.getSize();
}else{
logger.error("Character: " + currentChar + " has no font attached.");
}
if (charHasColorInfo && charHasFontInfo){
logger.debug("Character '" + currentChar + "'-> has font info -> load font!" +
" Family: " + newFamilyName +
" Fontsize: " + Math.round(newFontSize) +
" FillColor: " + newFillColor +
" StrokeColor: " + newStrokeColor);
if (useVectorFont)
fontToUse = FontManager.getInstance().createFont(pa,
"arial.ttf", Math.round(newFontSize), newFillColor, newStrokeColor);
else
fontToUse = FontManager.getInstance().createFont(pa, //uses cached font if available
// "Arial", Math.round(fontSize),
newFamilyName, Math.round(newFontSize), newFillColor, newStrokeColor);
if (fontToUse == null){
fontToUse = font;
}
}else{
fontToUse = font;
}
boolean fontChanged = !FontManager.isFontsAreEqual(fontToUse, lastUsedFont);
lastUsedFont = fontToUse;
// //FIXME REMOVE TEST
// fontChanged = true;
///////////////////////////////////////
boolean textPositionChanged = charX != null || charY != null || charDX != null || charDY != null;
//TODO if we forceAnewTextarea because of font change but ther is NO NEW POSITION, we
//have to set the textareas anchor to the lower left
//TODO problem if we have a tspan centered and a next tspan without new position
//-> the first tspan textarea gets centered on the position
//but we would have to treat them (all in the same line) as 1 textarea when center positioning!
//FIXME there are slight differences because we use a different SPACE character length and no font KERNING!
//FIXME DO WIDHTOUT USERDATA
//FIXME bitmap font has no top border, vector has.. why?
//TODO -> eventuell doch in handleSvgNode machen?
//-> statt graphicsnode /stylable node �bergeben? - SVGOMTextElement is nicht instanceof graphicsnode..
// we have to check font/color etc at every character, not only at new positon because
//pos doesent change at tspans without new posinfo
//check if equal to last used font and if equal original text font
if ( fontChanged || textPositionChanged
){ //Make a new textarea if the text position changed or if the font changed at the current character
MTTextArea previousTextArea = null;
if (!textAreas.isEmpty()){
previousTextArea = textAreas.get(textAreas.size()-1);
}
float newXPos = 0;
float newYPos = 0 ;
//If there is a previous text, get its ending coordinates
//for the DX and DY shift info for the next text area
if (previousTextArea != null){
PositionAnchor oldAnchor = previousTextArea.getAnchor();
// previousTextArea.setAnchor(PositionAnchor.LOWER_RIGHT);
previousTextArea.setAnchor(PositionAnchor.UPPER_LEFT);
//Calculate last/current textposition for DX and DY use
//add up the last textareas start position end position(width)
Vector3D lastPos = previousTextArea.getPosition(TransformSpace.LOCAL);
// lastPos.addLocal(new Vector3D(previousTextArea.getWidthXY(TransformSpace.LOCAL) - 1 * previousTextArea.getInnerPaddingLeft(),0));
lastPos.addLocal(new Vector3D(previousTextArea.getWidthXY(TransformSpace.LOCAL) - 2 * previousTextArea.getInnerPaddingLeft(),0));
// newXPos = lastPos.x - previousTextArea.getInnerPaddingLeft();
newXPos = lastPos.x;
newXPos += (Float)previousTextArea.getUserData("XPos");
newYPos = lastPos.y;
// newYPos -= previousTextArea.getInnerPaddingTop();
// newYPos += fontToUse.getFontMaxDescent(); //FIXME WHY NEVESSARY?
newYPos += (Float)previousTextArea.getUserData("YPos");
previousTextArea.setAnchor(oldAnchor);
}
//IF absolute x or y is present overwrite the position values from the last textarea
if (charX != null)
newXPos = (Float)charX;
if (charY != null)
newYPos = (Float)charY;
if (charDX != null)
newXPos += (Float)charDX;
if (charDY != null)
newYPos += (Float)charDY;
// Create the text area \\
MTTextArea t = new MTTextArea(pa, fontToUse);
t.setNoFill(true);
t.setNoStroke(true);
textAreas.add(t);
try{
t.setLocalMatrix(new Matrix(currentLocalTransformMatrix));
}catch(Exception e){
logger.error(e.getMessage());
}
//FIXME TEST
// if (previousTextArea != null && !textPositionChange){
// t.setAnchor(PositionAnchor.LOWER_LEFT);
// t.setUserData("posRelParent", new Vector3D(newXPos , newYPos - fontToUse.getFontMaxDescent() , 0));
// logger.debug("Character '" + currentChar + "' -> Anchor: LOWER_LEFT");
// }else{
Value v = CSSUtilities.getComputedStyle(textElement, SVGCSSEngine.TEXT_ANCHOR_INDEX);
//INFO: we have to move the BASELINE of the text to the svg position
//The textarea is usually fontmaxascent+fontmaxdescent+2*innerPadding big!
switch (v.getStringValue().charAt(0)) {
case 'e':
t.setAnchor(PositionAnchor.LOWER_RIGHT);
t.setUserData("posRelParent", new Vector3D((newXPos + t.getInnerPaddingLeft()) , newYPos - fontToUse.getFontMaxDescent() + t.getInnerPaddingTop() , 0));
// t.setPositionRelativeToParent(new Vector3D(newXPos, newYPos - font.getFontMaxDescent() , 0));
logger.debug("Character '" + currentChar + "' -> Anchor: LOWER_RIGHT");
break;
case 'm': //text-anchor="middle"
t.setAnchor(PositionAnchor.CENTER);
// t.setUserData("posRelParent", new Vector3D(newXPos, newYPos - fontToUse.getFontMaxAscent()*0.5f - fontToUse.getFontMaxDescent()*0.5f , 0));
// t.setUserData("posRelParent", new Vector3D(newXPos, newYPos - fontToUse.getFontAbsoluteHeight()*0.5f + t.getInnerPaddingTop() , 0));
// t.setPositionRelativeToParent(new Vector3D(newXPos, newYPos - font.getFontMaxAscent()*0.5f - font.getFontMaxDescent()*0.5f, 0)); //- font.getFontMaxAscent()*0.5f
logger.debug("Character '" + currentChar + "' -> Anchor: CENTER");
t.setUserData("posRelParent", new Vector3D((newXPos), (newYPos - fontToUse.getFontMaxDescent() + t.getInnerPaddingTop()) - t.getHeightXY(TransformSpace.LOCAL)/2f , 0));
break;
default: //text-anchor="start" //default!
t.setAnchor(PositionAnchor.LOWER_LEFT);
// t.setUserData("posRelParent", new Vector3D(newXPos -t.getInnerPaddingLeft(), newYPos - fontToUse.getFontMaxDescent() + t.getInnerPaddingTop() , 0));
t.setUserData("posRelParent", new Vector3D(newXPos -t.getInnerPaddingLeft(), newYPos - fontToUse.getFontMaxDescent() + t.getInnerPaddingTop() , 0));
// t.setAnchor(PositionAnchor.UPPER_LEFT);
// t.setUserData("posRelParent", new Vector3D(newXPos -t.getInnerPaddingLeft(), newYPos, 0));
// t.setPositionRelativeToParent(new Vector3D(newXPos, newYPos - font.getFontMaxDescent() , 0));
logger.debug("Character '" + currentChar + "' -> Anchor: LOWER_LEFT");
}
t.setUserData("XPos", newXPos);
t.setUserData("YPos", newYPos);
// }
}
//Add character to the current textarea in the list
if (!textAreas.isEmpty()){
textAreas.get(textAreas.size()-1).appendCharByUnicode(new Character(currentChar).toString());
}
}
//Set the positions of the textareas
for (Iterator<MTTextArea> iterator = textAreas.iterator(); iterator.hasNext();) {
MTTextArea textArea = (MTTextArea) iterator.next();
logger.debug("Adding text area at: " + (Vector3D) textArea.getUserData("posRelParent"));
textArea.setPositionRelativeToParent((Vector3D) textArea.getUserData("posRelParent"));
}
comps.addAll(textAreas);
}
/*
//This gets only the text of this hierarchy level
StringBuffer result = new StringBuffer();
for (Node n = textElement.getFirstChild();
n != null;
n = n.getNextSibling()) {
switch (n.getNodeType()) {
case Node.ELEMENT_NODE:
break;
case Node.CDATA_SECTION_NODE:
case Node.TEXT_NODE:
result.append(n.getNodeValue());
}
}
logger.debug("TEXTTTT2: " + result);
*/
// */////////////////////
}
}
}
if (node instanceof SVGGraphicsElement){
//Remove inherited opacity attribute from stack
opacityStack.pop();
}
//Traverse the children, not if it was a group element
//because then the children are already
//traversed in the if (group) block above
if ( !(node instanceof SVGOMGElement)
&& !(node instanceof SVGSVGElement)
&& !(node instanceof SVGOMSVGElement)
){
traverseChildren(node, comps);
}
}
/**
* Traverse children.
*
* @param node the node
* @param comps the comps
*/
private void traverseChildren(Node node, ArrayList<MTComponent> comps){
//Check the children
NodeList nl = node.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node currentNode = nl.item(i);
traverseSVGDoc(currentNode, comps);
}
}
/**
* Handle graphics node.
*
* @param gfxElem the gfx elem
*
* @return the mT base component
*/
private MTComponent handleGraphicsNode(SVGGraphicsElement gfxElem){
MTComponent returnComp = null;
// logger.debug("Handle Element: " + gfxElem.getTagName());
//Print all css properties and values
// logger.debug("Style Css Text: " + style.getCssText());
// SVG Defaults \\
float fillR = 255;
float fillG = 255;
float fillB = 255;
boolean noFill = false;
float strokeR = 0;
float strokeG = 0;
float strokeB = 0;
float strokeWidth = 1.0f;
boolean noStroke = false;
float strokeOpacity = 1;
float fillOpacity = 1;
int windingRule = GLU.GLU_TESS_WINDING_NONZERO;
// SVG Defaults \\
// Opacity, not as a style attribute but a separate
// as group opacity doesnt get computed right, so we
// mannually track it on a stack
float opacity = opacityStack.peek().floatValue();
//logger.debug("INHERITED OPACITY: " + opacity);
// FILL-RULE \\
Value fillRuleValue = CSSUtilities.getComputedStyle(gfxElem, SVGCSSEngine.FILL_RULE_INDEX);
String fillRule = fillRuleValue.getStringValue();
if (fillRule.equalsIgnoreCase("nonzero")){
windingRule = GLU.GLU_TESS_WINDING_NONZERO;
}else if (fillRule.equalsIgnoreCase("evenodd")){
windingRule = GLU.GLU_TESS_WINDING_ODD;
}else{
windingRule = GLU.GLU_TESS_WINDING_NONZERO;
}
//logger.debug("fillRule: " + fillRule);
// Fill Opacity \\
Value fillOpacValue = CSSUtilities.getComputedStyle(gfxElem, SVGCSSEngine.FILL_OPACITY_INDEX);
float computedfillOpac = PaintServer.convertOpacity(fillOpacValue);
fillOpacity = computedfillOpac;
//Multiplicate inherited opacity with this components opacities
fillOpacity *= opacity;
//Save for eventual lineargradient creation later that needs the not interpolated value
float originalFillOpacity = fillOpacity;
//logger.debug("fill opacity unnormalized: " + fillOpacity);
// Fill java.awt.Color \\
Value fillIndexValue = CSSUtilities.getComputedStyle(gfxElem, SVGCSSEngine.FILL_INDEX);
Object fill = SVGLoader.getFillOrStroke(gfxElem, fillIndexValue, fillOpacity, ctx);
SVGOMLinearGradientElement linearGradient = null;
SVGOMRadialGradientElement radialGradient = null;
if (fill instanceof java.awt.Color) {
java.awt.Color color = (Color) fill;
fillR = color.getRed();
fillG = color.getGreen();
fillB = color.getBlue();
fillOpacity = color.getAlpha();
noFill = false;
//logger.debug("Fill: " + color + " a=" + fillOpacity);
}else if (fill instanceof SVGOMLinearGradientElement) {
//TODO cache gradients so dass man nicht immer neu den gleichen
//machen muss!
linearGradient = (SVGOMLinearGradientElement) fill;
noFill = false;
}else if (fill instanceof SVGOMRadialGradientElement) {
//TODO!! //FIXME TEST
radialGradient = (SVGOMRadialGradientElement)fill;
noFill = false;
}else{
noFill = true;
}
// Stroke Opacity \\
Value strokeOpacValue = CSSUtilities.getComputedStyle(gfxElem, SVGCSSEngine.STROKE_OPACITY_INDEX);
float computedStrokeOpacity = PaintServer.convertOpacity(strokeOpacValue);
strokeOpacity = computedStrokeOpacity;
// Multiplicate inherited opacity with this components group opacities
strokeOpacity *= opacity;
// Stroke java.awt.Color \\
Value strokeIndexValue = CSSUtilities.getComputedStyle(gfxElem, SVGCSSEngine.STROKE_INDEX);
Object stroke = SVGLoader.getFillOrStroke(gfxElem, strokeIndexValue, strokeOpacity, ctx);
if (stroke instanceof java.awt.Color) {
java.awt.Color color = (Color) stroke;
strokeR = color.getRed();
strokeG = color.getGreen();
strokeB = color.getBlue();
strokeOpacity = color.getAlpha();
noStroke = false;
}else{
noStroke = true;
strokeR = fillR;
strokeG = fillG;
strokeB = fillB;
}
// Stroke Width \\
Stroke s = PaintServer.convertStroke(gfxElem);
if (s != null){
if (s instanceof BasicStroke) {
BasicStroke basicStroke = (BasicStroke) s;
strokeWidth = basicStroke.getLineWidth();
}
}else{
strokeWidth = 0.0f;
noStroke = true;
}
/*
logger.debug("Fill COL: " + fillR + " " + fillG + " " + fillB + " " fillopacity);
logger.debug("STROKE COL: " + strokeR + " " + strokeG + " " + strokeB + " " strokeOpacity);
*/
// CHECK WHAT KIND OF GRAPHICS ELEMENT IT IS AND CREATE IT \\
if (gfxElem instanceof SVGOMPathElement){
SVGOMPathElement pathElem = (SVGOMPathElement)gfxElem;
//FIXME handle clip-paths in the future
if (isUnderClipPath(pathElem)){
logger.error("Discarding clip-path path element. Not implemented.");
return null;
}
//Create the shape
AbstractShape pathComp = getLivePathComponent(pathElem, noFill, windingRule);
try{
pathComp.setLocalMatrix(currentLocalTransformMatrix);
}catch(Exception e){
logger.error(e.getMessage());
}
returnComp = pathComp;
}else if (gfxElem instanceof SVGOMPolygonElement){
SVGOMPolygonElement polygonElem = (SVGOMPolygonElement)gfxElem;
//Create the shape
AbstractShape comp = getLivePolygonComponent(polygonElem, noFill, windingRule);
try{
comp.setLocalMatrix(currentLocalTransformMatrix);
}catch(Exception e){
logger.error(e.getMessage());
}
returnComp = comp;
}else if (gfxElem instanceof SVGOMPolylineElement){
SVGOMPolylineElement polyLineElem = (SVGOMPolylineElement)gfxElem;
//Create Vertex[] from points
SVGPointList pointList = polyLineElem.getPoints();
Vertex[] vertices = new Vertex[pointList.getNumberOfItems()];
for (int i = 0; i < pointList.getNumberOfItems(); i++) {
SVGPoint p = pointList.getItem(i);
vertices[i] = new Vertex(p.getX(), p.getY(),0);
}
//Create the shape
AbstractShape comp = createPoly(vertices);
try{
comp.setLocalMatrix(currentLocalTransformMatrix);
}catch(Exception e){
logger.error(e.getMessage());
}
returnComp = comp;
}else if (gfxElem instanceof SVGOMRectElement){
SVGOMRectElement rectElem = (SVGOMRectElement)gfxElem;
if (isUnderClipPath(rectElem)){
logger.error("discarding clip-path Rect");
return null;
}
float x = rectElem.getX().getBaseVal().getValue();
float y = rectElem.getY().getBaseVal().getValue();
float width = rectElem.getWidth().getBaseVal().getValue();
float height = rectElem.getHeight().getBaseVal().getValue();
float rx = rectElem.getRx().getBaseVal().getValue();
float ry = rectElem.getRy().getBaseVal().getValue();
AbstractShape comp;
//Create a normal rectangle or a round rectangle
if (rx != 0.0f || ry != 0.0f){
if (rx > width/2 )
rx = width/2;
if (ry > height/2 )
ry = height/2;
comp = new MTRoundRectangle(x,y,0, width,height,rx, ry, pa);
}else{
comp = new MTRectangle(x,y, width,height, pa);
}
try{
comp.setLocalMatrix(currentLocalTransformMatrix);
}catch(Exception e){
logger.error(e.getMessage());
}
returnComp = comp;
}else if (gfxElem instanceof SVGOMEllipseElement){
SVGOMEllipseElement ellipseElem = (SVGOMEllipseElement)gfxElem;
float cx = ellipseElem.getCx().getBaseVal().getValue();
float cy = ellipseElem.getCy().getBaseVal().getValue();
float r = ellipseElem.getRx().getBaseVal().getValue();
float r2 = ellipseElem.getRy().getBaseVal().getValue();
Vertex middlePoint = new Vertex(cx,cy,0);
//Apply transformation, transform centerpoint and the radii
try{
middlePoint.transform(currentLocalTransformMatrix);
}catch(Exception e){
logger.error(e.getMessage());
}
//somehow the circle radii need to be doubled
//or else theyre too small => processing bug?
// r*=2;
// r2*=2;
MTEllipse comp = new MTEllipse(pa, middlePoint, r, r2);
returnComp = comp;
}else if (gfxElem instanceof SVGOMCircleElement){
SVGOMCircleElement circleElem = (SVGOMCircleElement)gfxElem;
float cx = circleElem.getCx().getBaseVal().getValue();
float cy = circleElem.getCy().getBaseVal().getValue();
float r = circleElem.getR().getBaseVal().getValue();
float r2 = circleElem.getR().getBaseVal().getValue();
Vertex middlePoint = new Vertex(cx,cy,0);
//Apply transformation, transform centerpoint and the radii
try{
middlePoint.transform(currentLocalTransformMatrix);
}catch(Exception e){
logger.error(e.getMessage());
}
//somehow the circle radii need to be doubled
//or else theyre too small => processing bug?
// r*=2;
// r2*=2;
MTEllipse comp = new MTEllipse(pa, middlePoint, r, r2);
returnComp = comp;
}else if (gfxElem instanceof SVGOMLineElement){
SVGOMLineElement line = (SVGOMLineElement)gfxElem;
float x1 = line.getX1().getBaseVal().getValue();
float y1 = line.getY1().getBaseVal().getValue();
float x2 = line.getX2().getBaseVal().getValue();
float y2 = line.getY2().getBaseVal().getValue();
//logger.debug("Line x1: " + x1 + ",y1:" + y1 + ",x2:" + x2 + ",y2:" + y2);
MTLine comp = new MTLine(pa, x1,y1 ,x2,y2);
try{
comp.setLocalMatrix(currentLocalTransformMatrix);
}catch(Exception e){
logger.error(e.getMessage());
}
returnComp = comp;
}else if (gfxElem instanceof SVGOMClipPathElement){
}else if (gfxElem instanceof SVGOMDefsElement){
}else if (gfxElem instanceof SVGOMMaskElement){
}else if (gfxElem instanceof SVGOMSwitchElement){
}else if (gfxElem instanceof SVGOMFlowRootElement){
}else if (gfxElem instanceof SVGURIReferenceGraphicsElement){
}else if (gfxElem instanceof BindableElement){
}else if (gfxElem instanceof SVGOMForeignObjectElement){
}else if (gfxElem instanceof SVGOMToBeImplementedElement){
}
//Do the finishing touch of the svg graphics element
if (returnComp != null){
returnComp.setName(gfxElem.getTagName());
//Set style infos
if (returnComp instanceof AbstractVisibleComponent){
AbstractVisibleComponent comp = (AbstractVisibleComponent)returnComp;
//Set Fill
comp.setFillColor(new MTColor(fillR, fillG, fillB, fillOpacity));
comp.setNoFill(noFill);
//Set Stroke
comp.setStrokeColor(new MTColor(strokeR, strokeG, strokeB, strokeOpacity));
//Opengl cant handle big lines well
//So cap at width 3
if (strokeWidth > 2.0f)
strokeWidth = 2.0f;
comp.setStrokeWeight(strokeWidth);
comp.setNoStroke(noStroke);
//Other
comp.setDrawSmooth(true);
comp.setPickable(false);
//Hack for smoothing non stroked components with a stroke same as fillcolor
if (comp.isNoStroke()
&& linearGradient == null
){
comp.setStrokeColor(new MTColor(fillR, fillG, fillB, fillOpacity)); //fillOpacity
comp.setStrokeWeight(0.6f);
//Ellipse doesent smooth right with 0.1f strokeweight
if (comp instanceof MTEllipse){
comp.setStrokeWeight(1.0f);
}
comp.setNoStroke(false);
}
//Some settings for Geometric shapes (actually should all be)
if (comp instanceof AbstractShape ){
AbstractShape shape = (AbstractShape)comp;
//Set a bounding rectangle to check first at picking
if (shape.getVerticesLocal().length >= 3){
shape.setBoundsBehaviour(AbstractShape.BOUNDS_CHECK_THEN_GEOMETRY_CHECK);
//shape.setBoundingShape(new BoundsZPlaneRectangle(shape)); //already done by override, (ie svgpoly)
//Create amd apply the linear gradient if existant and if we are in opengl rendering mode
if (MT4jSettings.getInstance().isOpenGlMode()){
if (linearGradient != null){
FillPaint gradient = this.createLinearGradient(linearGradient, gfxElem, originalFillOpacity, shape);
if (gradient != null){
shape.setFillPaint(gradient);
}
}
if (radialGradient != null){
FillPaint gradient = this.createRadialGradient(radialGradient, gfxElem, opacity, shape);
if (gradient != null){
shape.setFillPaint(gradient);
}
}
//Per default use direct gl drawing and displaylists in OGL mode
if (pa instanceof MTApplication) {
MTApplication app = (MTApplication) pa;
app.invokeLater(new InvokeLaterAction(shape));
}
}
//IF shape has no or only 1 vertex return null
}else if (shape.getVerticesLocal().length < 2){
return null;
}else{
shape.setBoundsBehaviour(AbstractShape.BOUNDS_DONT_USE);
shape.setBounds(null);
// shape.setUseDirectGL(false);
}
//Allow for picking the shape
shape.setPickable(true);
//Assign default gestures
// shape.assignGestureClassAndAction(DragGestureAnalyzer.class, defaultDragAction);
// shape.registerInputAnalyzer(new DragDetector(pa));
// shape.setGestureAllowance(DragDetector.class, true);
// shape.addGestureListener(DragDetector.class, (IGestureEventListener)defaultDragAction);
// shape.registerInputAnalyzer(new RotationDetector(pa));
// shape.addGestureListener(RotationDetector.class, new DefaultRotateAction());
// shape.registerInputAnalyzer(new ScaleDetector(pa));
// shape.addGestureListener(ScaleDetector.class, new DefaultScaleAction());
}
}
}
return returnComp;
}
private class InvokeLaterAction implements Runnable{
private AbstractShape shape;
public InvokeLaterAction(AbstractShape shape) {
super();
this.shape = shape;
}
//@Override
public void run() {
shape.setUseDirectGL(true);
shape.generateAndUseDisplayLists();
}
}
/**
*
* @param paintedElement the element interested in a Paint
* @param paintDef the paint definition
* @param opacity the opacity to consider for the Paint
* @param ctx the bridge context
*/
public static Object getFillOrStroke(Element paintedElement,
Value paintDef,
float opacity,
BridgeContext ctx) {
if (paintDef.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE) {
switch (paintDef.getPrimitiveType()) {
case CSSPrimitiveValue.CSS_IDENT:
return null; // none
case CSSPrimitiveValue.CSS_RGBCOLOR:
return PaintServer.convertColor(paintDef, opacity);
case CSSPrimitiveValue.CSS_URI:
String uri = paintDef.getStringValue();
Element paintElement = ctx.getReferencedElement(paintedElement, uri);
//logger.debug("Fill -> Uri: \"" + uri + "\" -> Referenced Element: \"" + paintElement.getNodeName() + "\" Class: \"" + paintElement.getClass() + "\"");
if (paintElement instanceof SVGOMLinearGradientElement){
SVGOMLinearGradientElement linearGradient = (SVGOMLinearGradientElement)paintElement;
return linearGradient;
// Bridge bridge = ctx.getBridge(paintElement);
// logger.debug("Bridge: " + bridge.getLocalName() + " Class: " + bridge.getClass());
// SVGLinearGradientElementBridge l = (SVGLinearGradientElementBridge)bridge;
// SVGOMLinearGradientElement linearGradient = (SVGOMLinearGradientElement)paintElement;
// SVGAnimatedEnumeration spreadMethod = linearGradient.getSpreadMethod();
}else if (paintElement instanceof SVGOMRadialGradientElement){
SVGOMRadialGradientElement radialGradElement = (SVGOMRadialGradientElement)paintElement;
// logger.error("Radial gradient encountered -> Not supported yet.");
return radialGradElement;
}else{
logger.error("Couldnt read referenced Fill or Stroke from URI.");
return null;
}
/*
return PaintServer.convertURIPaint(paintedElement,
paintedNode,
paintDef,
opacity,
ctx);
*/
default:
throw new IllegalArgumentException
("Paint argument is not an appropriate CSS value");
}
} else { // List
Value v = paintDef.item(0);
switch (v.getPrimitiveType()) {
case CSSPrimitiveValue.CSS_RGBCOLOR:
return PaintServer.convertRGBICCColor(paintedElement, v,
(ICCColor)paintDef.item(1),
opacity, ctx);
case CSSPrimitiveValue.CSS_URI: {
// Paint result = PaintServer.silentConvertURIPaint(paintedElement,
// paintedNode,
// v, opacity, ctx);
// if (result != null)
// return result;
String uri = v.getStringValue();
Element paintElement = ctx.getReferencedElement(paintedElement, uri);
//logger.debug("Fill -> Uri: \"" + uri + "\" -> Referenced Element: \"" + paintElement.getNodeName() + "\" Class: \"" + paintElement.getClass() + "\"");
if (paintElement instanceof SVGOMLinearGradientElement){
SVGOMLinearGradientElement linearGradient = (SVGOMLinearGradientElement)paintElement;
return linearGradient;
}else if (paintElement instanceof SVGOMRadialGradientElement){
SVGOMRadialGradientElement radialGradElement = (SVGOMRadialGradientElement)paintElement;
// logger.error("Radial gradient encountered -> Not supported yet.");
return radialGradElement;
}
v = paintDef.item(1);
switch (v.getPrimitiveType()) {
case CSSPrimitiveValue.CSS_IDENT:
return null; // none
case CSSPrimitiveValue.CSS_RGBCOLOR:
if (paintDef.getLength() == 2) {
return PaintServer.convertColor(v, opacity);
} else {
return PaintServer.convertRGBICCColor(paintedElement, v,
(ICCColor)paintDef.item(2),
opacity, ctx);
}
default:
throw new IllegalArgumentException
("Paint argument is not an appropriate CSS value");
}
}
default:
// can't be reached
throw new IllegalArgumentException
("Paint argument is not an appropriate CSS value");
}
}
}
//TODO
// - spreadMethod implement linear
// - linearGradient klasse machen mit der man ein gradient erstellen kann
// mit stops[] offsets[] colors[] xy, bbox/userSpace
// - wie shapes ohne stroke mit outline= gradient zeichnen f�r antialiasing?
// evtl gradientshape normal zeichnen, aber mit realshape clipmasken?
private FillPaint createRadialGradient(Element paintElement, SVGGraphicsElement gfxElem, float opacity, AbstractShape shape){
//Get the <stop> elements
List<Stop> stops = this.extractStops(paintElement, opacity, ctx);
// if no stops are defined, painting is the same as 'none'
if (stops == null) {
return null;
}
int stopLength = stops.size();
// if one stops is defined, painting is the same as a single color
if (stopLength == 1) {
return null;
}
float [] offsets = new float[stopLength];
java.awt.Color [] colors = new java.awt.Color[stopLength];
Iterator<Stop> iter = stops.iterator();
for (int i=0; iter.hasNext(); ++i) {
Stop stop = (Stop)iter.next();
offsets[i] = stop.offset;
colors[i] = stop.color;
}
//Get the spread method of the gradient
MultipleGradientPaint.CycleMethodEnum spreadMethod = getSpreadMethod(paintElement);
//'color-interpolation' CSS property
MultipleGradientPaint.ColorSpaceEnum colorSpace = CSSUtilities.convertColorInterpolation(paintElement);
//Get the gradient transform - //'gradientTransform' attribute - default is an Identity matrix
AffineTransform transform = getGradientTransform(paintElement);
//////////////////////////////////buildgradient function
// 'cx' attribute - default is 50%
String cxStr = SVGUtilities.getChainableAttributeNS
(paintElement, null, SVG_CX_ATTRIBUTE, ctx);
if (cxStr.length() == 0) {
cxStr = SVG_RADIAL_GRADIENT_CX_DEFAULT_VALUE;
}
// 'cy' attribute - default is 50%
String cyStr = SVGUtilities.getChainableAttributeNS
(paintElement, null, SVG_CY_ATTRIBUTE, ctx);
if (cyStr.length() == 0) {
cyStr = SVG_RADIAL_GRADIENT_CY_DEFAULT_VALUE;
}
// 'r' attribute - default is 50%
String rStr = SVGUtilities.getChainableAttributeNS
(paintElement, null, SVG_R_ATTRIBUTE, ctx);
if (rStr.length() == 0) {
rStr = SVG_RADIAL_GRADIENT_R_DEFAULT_VALUE;
}
// 'fx' attribute - default is same as cx
String fxStr = SVGUtilities.getChainableAttributeNS
(paintElement, null, SVG_FX_ATTRIBUTE, ctx);
if (fxStr.length() == 0) {
fxStr = cxStr;
}
// 'fy' attribute - default is same as cy
String fyStr = SVGUtilities.getChainableAttributeNS
(paintElement, null, SVG_FY_ATTRIBUTE, ctx);
if (fyStr.length() == 0) {
fyStr = cyStr;
}
// 'gradientUnits' attribute - default is objectBoundingBox
short coordSystemType;
String s = SVGUtilities.getChainableAttributeNS
(paintElement, null, SVG_GRADIENT_UNITS_ATTRIBUTE, ctx);
if (s.length() == 0) {
coordSystemType = SVGUtilities.OBJECT_BOUNDING_BOX;
} else {
coordSystemType = SVGUtilities.parseCoordinateSystem(paintElement, SVG_GRADIENT_UNITS_ATTRIBUTE, s, ctx);
}
UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, paintElement);
float r = SVGUtilities.convertLength(rStr,
SVG_R_ATTRIBUTE,
coordSystemType,
uctx);
// A value of zero will cause the area to be painted as a single color
// using the color and opacity of the last gradient stop.
if (r == 0) {
return null;
}
Point2D c = SVGUtilities.convertPoint(cxStr,
SVG_CX_ATTRIBUTE,
cyStr,
SVG_CY_ATTRIBUTE,
coordSystemType,
uctx);
Point2D f = SVGUtilities.convertPoint(fxStr,
SVG_FX_ATTRIBUTE,
fyStr,
SVG_FY_ATTRIBUTE,
coordSystemType,
uctx);
//Get gradient vector
logger.debug("C: " + c + " F: " +f);
CycleMethod awtCycleMethod = CycleMethod.NO_CYCLE;
if (spreadMethod == MultipleGradientPaint.REPEAT){
awtCycleMethod = CycleMethod.REPEAT;
}else if(spreadMethod == MultipleGradientPaint.REFLECT){
awtCycleMethod = CycleMethod.REFLECT;
}
if (pa instanceof MTApplication) {
MTApplication app = (MTApplication) pa;
//Calculate a bounding rectangle from the rotated shape
BoundsZPlaneRectangle boundsZ = new BoundsZPlaneRectangle(shape, shape.getVerticesLocal());
Vector3D[] boundsVecs = boundsZ.getVectorsLocal();
float bBoxWidth = boundsZ.getWidthXY(TransformSpace.LOCAL);//boundsVecs[1].x - boundsVecs[0].x;
float bBoxHeight = boundsZ.getHeightXY(TransformSpace.LOCAL);//boundsVecs[2].y - boundsVecs[1].y;
SwingTextureRenderer swingTex;
final MTRectangle rectangle;
//Trial to make the texture as big as the bigger side of the bounding rectangle of the shape
//to allow for automatic texture stretching to fit when texture is applied
int size = -1;
if (coordSystemType == SVGUtilities.OBJECT_BOUNDING_BOX){
if (bBoxWidth >= bBoxHeight){
size = Math.round(bBoxWidth);
r *= bBoxWidth;
}else{
size = Math.round(bBoxHeight);
r *= bBoxHeight;
}
AffineTransform Mx = new AffineTransform();
Rectangle2D bounds = new Rectangle(Math.round(boundsVecs[0].x), Math.round(boundsVecs[0].y), size, size);
if (bounds != null) {
//we dont translate the center and focal point
//instead we create the gradient mask shape at that position
// Mx.translate(bounds.getX(), bounds.getY());
Mx.scale(bounds.getWidth(), bounds.getHeight());
}
Mx.concatenate(transform);
transform = Mx;
//Transform gradient vector points with gradientTransform
transform.transform(c, c);
transform.transform(f, f);
GradientPanel gradPanel = new GradientPanel(size, size, r, offsets, colors, (float)c.getX(), (float)c.getY(), (float)f.getX(), (float)f.getY(), awtCycleMethod);
// GradientPanel gradPanel = new GradientPanel(bBoxWidth, bBoxHeight, r, offsets, colors, (float)c.getX(), (float)c.getY(), (float)f.getX(), (float)f.getY());
swingTex = new SwingTextureRenderer(app, gradPanel);
swingTex.scheduleRefresh();
rectangle = new MTRectangle(new Vertex(boundsVecs[0]), bBoxWidth, bBoxHeight, pa);
rectangle.setName("Swing texture rendering");
rectangle.setTexture(swingTex.getTextureToRenderTo());
rectangle.setNoStroke(true);
rectangle.setPickable(false);
rectangle.setFillDrawMode(GL.GL_QUADS);
//Use displaylist by default for gradientshape
if (MT4jSettings.getInstance().isOpenGlMode()){
app.invokeLater(new InvokeLaterAction(rectangle));
}
//FIXME REMOVE TEST
/*//Draw the shape we draw in swing
MTRectangle rectanglePaintedComp = new MTRectangle(new Vertex(boundsVecs[0]), size, size, pa);
rectanglePaintedComp.setName("rectanglePaintedComp");
rectanglePaintedComp.setTexture(swingTex.getTextureToRenderTo());
rectanglePaintedComp.setFillColor(255, 255, 255, 150);
shape.addChild(rectanglePaintedComp);
*/
}else{
//coordsystemtype = userSpaceOnUse!
//FIXME Problem at userOnSpace with proportional length (%)
//seems we have to take the width/height from the viewbox then!? and use bounding box code above? but we have to recalculate absoulte values then..
//Since we draw the gradient at 0,0 we have to transform the gradient points to there
AffineTransform Mx = new AffineTransform();
Mx.translate(-boundsVecs[0].x, -boundsVecs[0].y);
Mx.concatenate(transform);
transform = Mx;
//Transform gradient points with gradientTransform
transform.transform(c, c);
transform.transform(f, f);
// GradientPanel gradPanel = new GradientPanel(size, size, r, offsets, colors, (float)c.getX(), (float)c.getY(), (float)f.getX(), (float)f.getY());
GradientPanel gradPanel = new GradientPanel(bBoxWidth, bBoxHeight, r, offsets, colors, (float)c.getX(), (float)c.getY(), (float)f.getX(), (float)f.getY(), awtCycleMethod);
swingTex = new SwingTextureRenderer(app, gradPanel);
swingTex.scheduleRefresh();
rectangle = new MTRectangle(new Vertex(boundsVecs[0]), bBoxWidth, bBoxHeight, pa);
final GLTexture tex = swingTex.getTextureToRenderTo();
rectangle.setName("Swing texture rendering");
rectangle.setTexture(tex);
rectangle.setNoStroke(true);
rectangle.setPickable(false);
// /*//
if (MT4jSettings.getInstance().isOpenGlMode()){
app.invokeLater(new InvokeLaterAction(rectangle));
}
// */
//FIXME REMOVE TEST
/*//Draw the shape we draw in swing
MTRectangle rectanglePaintedComp = new MTRectangle(new Vertex(boundsVecs[0]), bBoxWidth, bBoxHeight, pa);
rectanglePaintedComp.setName("rectanglePaintedComp");
rectanglePaintedComp.setTexture(swingTex.getTextureToRenderTo());
rectanglePaintedComp.setFillColor(255, 255, 255, 150);
shape.addChild(rectanglePaintedComp);
*/
}
FillPaint gradStencil = new FillPaint(((PGraphicsOpenGL)pa.g).gl, rectangle);
return gradStencil;
// return null;
}
return null;
}
/**
* Helper class to paint a radial gradient with java2D into a texture.
*/
private class GradientPanel extends JPanel{
private float width;
private float height;
private float[] offsets;
private java.awt.Color[] colors;
private float cx;
private float cy;
private float fx;
private float fy;
private float radius;
private CycleMethod cycleMethod;
public GradientPanel(float width, float height, float radius, float[] offsets,
Color[] colors, float cx, float cy, float fx, float fy,
CycleMethod cycleMethod
) {
super();
this.width = width;
this.height = height;
this.radius = radius;
this.offsets = offsets;
this.colors = colors;
this.cx = cx;
this.cy = cy;
this.fx = fx;
this.fy = fy;
this.cycleMethod = cycleMethod;
this.setSize(Math.round(width), Math.round(height));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
int w = getWidth();
int h = getHeight();
Rectangle r = new Rectangle(0, 0, w, h); //original
RadialGradientPaint rgp = new RadialGradientPaint(
cx, cy,
// 400, 250,
radius,
// 100,
fx, fy,
// 400, 250,
offsets,
colors,
cycleMethod);
g2.setPaint(rgp);
g2.fill(r);
}
}
private FillPaint createLinearGradient(Element paintElement, SVGGraphicsElement gfxElem, float opacity, AbstractShape shape){
// stop elements
List<Stop> stops = this.extractStops(paintElement, opacity, ctx);
// if no stops are defined, painting is the same as 'none'
if (stops == null) {
return null;
}
int stopLength = stops.size();
// if one stops is defined, painting is the same as a single color
if (stopLength == 1) {
return null;
}
//Get the spread method of the gradient
MultipleGradientPaint.CycleMethodEnum spreadMethod = getSpreadMethod(paintElement);
//'color-interpolation' CSS property
MultipleGradientPaint.ColorSpaceEnum colorSpace = CSSUtilities.convertColorInterpolation(paintElement);
//Get the gradient transform - //'gradientTransform' attribute - default is an Identity matrix
AffineTransform transform = getGradientTransform(paintElement);
//logger.debug("Gradienttransform: " + transform);
//////////////////////////////////buildgradient function
// 'x1' attribute - default is 0%
String x1Str = SVGUtilities.getChainableAttributeNS(paintElement, null, SVG_X1_ATTRIBUTE, ctx);
if (x1Str.length() == 0) {
x1Str = SVG_LINEAR_GRADIENT_X1_DEFAULT_VALUE;
}
// 'y1' attribute - default is 0%
String y1Str = SVGUtilities.getChainableAttributeNS(paintElement, null, SVG_Y1_ATTRIBUTE, ctx);
if (y1Str.length() == 0) {
y1Str = SVG_LINEAR_GRADIENT_Y1_DEFAULT_VALUE;
}
// 'x2' attribute - default is 100%
String x2Str = SVGUtilities.getChainableAttributeNS(paintElement, null, SVG_X2_ATTRIBUTE, ctx);
if (x2Str.length() == 0) {
x2Str = SVG_LINEAR_GRADIENT_X2_DEFAULT_VALUE;
}
// 'y2' attribute - default is 0%
String y2Str = SVGUtilities.getChainableAttributeNS(paintElement, null, SVG_Y2_ATTRIBUTE, ctx);
if (y2Str.length() == 0) {
y2Str = SVG_LINEAR_GRADIENT_Y2_DEFAULT_VALUE;
}
// 'gradientUnits' attribute - default is objectBoundingBox
short coordSystemType;
String s2 = SVGUtilities.getChainableAttributeNS(paintElement, null, SVG_GRADIENT_UNITS_ATTRIBUTE, ctx);
if (s2.length() == 0) {
coordSystemType = SVGUtilities.OBJECT_BOUNDING_BOX;
} else {
coordSystemType = SVGUtilities.parseCoordinateSystem(paintElement, SVG_GRADIENT_UNITS_ATTRIBUTE, s2, ctx);
}
// additional transform to move to objectBoundingBox coordinate system
//TODO gradienttransform
// if (coordSystemType == SVGUtilities.OBJECT_BOUNDING_BOX) {
// transform = SVGUtilities.toObjectBBox(transform, gfxElem);
// }
UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, gfxElem);
Point2D p1 = SVGUtilities.convertPoint(x1Str,
SVG_X1_ATTRIBUTE,
y1Str,
SVG_Y1_ATTRIBUTE,
coordSystemType,
uctx);
Point2D p2 = SVGUtilities.convertPoint(x2Str,
SVG_X2_ATTRIBUTE,
y2Str,
SVG_Y2_ATTRIBUTE,
coordSystemType,
uctx);
//Transform gradient vector points with gradientTransform
Point2D tp1 = null;
tp1 = transform.transform(p1, tp1);
p1 = tp1;
Point2D tp2 = null;
tp2 = transform.transform(p2, tp2);
p2 = tp2;
//Get gradient vector
logger.debug("P1: " + p1 + " P2: " + p2);
Vector3D ref = new Vector3D(1,0,0);
Vector3D vP1 = new Vector3D((float)p1.getX(), (float)p1.getY(), 0);
Vector3D vP2 = new Vector3D((float)p2.getX(), (float)p2.getY(), 0);
Vector3D gradVect = vP2.getSubtracted(vP1);
//logger.debug("Gradient vector: " + gradVect);
//Get gradient vector angle to rotate the shape to be gradiented to the horizontal vector 1,0,0
//Algorithm for linear gradients used here:
//1. rotate the shape so that the gradient vector in the shape is parallel to 1,0,0
//2. calc bounding rectangle of shape + gradient rectangle (made of the x1,y1 x2,y2 gradient endpoints)
//3. create quads with colored vertices at each (now horizontal) stop along the gradient
//4. rotate the gradient shape with the quads back so it faces the original gradient vector direction
float gradAngle = Vector3D.angleBetween(ref, gradVect);
gradAngle = PApplet.degrees(gradAngle);
Vector3D cross = ref.getCross(gradVect);
//Get the direction of rotation
if (cross.getZ() < 0){
gradAngle*=-1;
}
logger.debug("Gradient angle: " + gradAngle + "�");
logger.debug("Stops:");
for (Stop stop : stops)
logger.debug(" Stop -> Offset: " + stop.offset + " java.awt.Color: " + stop.color);
if (coordSystemType == SVGUtilities.USER_SPACE_ON_USE){
return this.setUpRotatedGradientUserSpace(shape, gradAngle, stops, p1, p2);
}else{
return this.setUpRotatedGradientBBox(shape, gradAngle, stops);
}
// If x1 = x2 and y1 = y2, then the area to be painted will be painted
// as a single color using the color and opacity of the last gradient
// stop.
// if (p1.getX() == p2.getX() && p1.getY() == p2.getY()) {
// return colors[colors.length-1];
// } else {
// return new LinearGradientPaint(p1,
// p2,
// offsets,
// colors,
// spreadMethod,
// colorSpace,
// transform);
// }
}
private FillPaint setUpRotatedGradientUserSpace(AbstractShape testShape, float angle, List<Stop> stops, Point2D p1, Point2D p2){
GL gl = ((PGraphicsOpenGL)pa.g).gl;
float gradAngle = angle;
float invAngle = angle*-1;
//Get copy of shapes vertices
Vertex[] shapeVertsCopy = Vertex.getDeepVertexArrayCopy(testShape.getGeometryInfo().getVertices());
//Rotate the vertices in the inverse direction of the gradients vector angle
shapeVertsCopy = (Vertex[]) Vertex.rotateZVectorArray(shapeVertsCopy, testShape.getCenterPointLocal(), invAngle);
Vertex vP1 = new Vertex((float)p1.getX(), (float)p1.getY(), 0);
Vertex vP2 = new Vertex((float)p2.getX(), (float)p2.getY(), 0);
float gradientRectWidth = vP2.getSubtracted(vP1).length();
Vertex[] gradientRectVerts = new Vertex[]{
vP1,
new Vertex((float)p2.getX(), (float)p1.getY(),0),
vP2,
new Vertex((float)p1.getX(), (float)p2.getY(),0)
};
//Rotate the vertices in the inverse direction of the gradients vector angle
gradientRectVerts = (Vertex[]) Vertex.rotateZVectorArray(gradientRectVerts, testShape.getCenterPointLocal(), invAngle);
//Copy the rotated bounding shape vertices and the rotated gradient rectangle vertices into one array
Vertex[] shapeAndGradVerts = new Vertex[shapeVertsCopy.length + gradientRectVerts.length];
System.arraycopy(shapeVertsCopy, 0, shapeAndGradVerts, 0, shapeVertsCopy.length);
System.arraycopy(gradientRectVerts, 0, shapeAndGradVerts, shapeVertsCopy.length, gradientRectVerts.length);
//Create a temporary polygon with the roated vertices to calc BBox
MTPolygon inverseRotatedShape = new MTPolygon(shapeAndGradVerts, pa);
//Calculate a bounding rectangle from the rotated shape
BoundsZPlaneRectangle inverseRotatedBounds = new BoundsZPlaneRectangle(inverseRotatedShape);
Vector3D[] invBoundsVecs = inverseRotatedBounds.getVectorsLocal();
//logger.debug("Gradient Rectangle width: " + gradientRectWidth);
//Get the positions where the offsets are on the gradient vector
// float bBoxWidth = invBoundsVecs[1].x - invBoundsVecs[0].x;
// logger.debug("BBox width: " + bBoxWidth);
// float w = bBoxWidth/*/100*/;
List<Float> xStops = new ArrayList<Float>();
//- Go through stops
//- multiply stop offsets with bbox width to get the position on gradient vector
//logger.debug("->Gradient Vector stop positions:");
for(Stop stop : stops){
float offsetStopPosition = gradientRectWidth * stop.offset; //position auf gradient vector, stop(0) = vP1.x + offest
xStops.add(offsetStopPosition);
//logger.debug(" Offset-Stop-Position: " + offsetStopPosition);
}
//Calc new gradient polygon vertices with vertices at the stop locations
Vertex[] newBounds = new Vertex[(xStops.size()-1) * 4];
for (int i = 0; i < xStops.size()-1; i++) {
float offset = xStops.get(i);
Color stopColor = stops.get(i).color;
float nextOffset = xStops.get(i+1);
Color nextStopColor = stops.get(i+1).color;
newBounds[i*4] = new Vertex(vP1.x + offset, invBoundsVecs[0].y,0, stopColor.getRed(), stopColor.getGreen(), stopColor.getBlue(), stopColor.getAlpha());
newBounds[i*4+1] = new Vertex(vP1.x + nextOffset, invBoundsVecs[0].y,0, nextStopColor.getRed(), nextStopColor.getGreen(), nextStopColor.getBlue(), nextStopColor.getAlpha());
newBounds[i*4+2] = new Vertex(vP1.x + nextOffset, invBoundsVecs[2].y,0, nextStopColor.getRed(), nextStopColor.getGreen(), nextStopColor.getBlue(), nextStopColor.getAlpha());
newBounds[i*4+3] = new Vertex(vP1.x + offset, invBoundsVecs[2].y,0, stopColor.getRed(), stopColor.getGreen(), stopColor.getBlue(), stopColor.getAlpha());
}
//Put gradient rectangle quads into a list
List<Vertex> gradientRectQuads = new ArrayList<Vertex>();
for (int i = 0; i < newBounds.length; i++) {
Vertex vertex = newBounds[i];
gradientRectQuads.add(vertex);
}
/* Bounding shape with gradient rectangle inside (can also overlap outlines)
invBoundsVecs[0] invBoundsVecs[1]
| _______________ |
| |_____| |
| | G | |
| vp1|____>|vp2 |
|____|_____|____|
*/
//Calc rectangle bands (quads) to fill the gradient shape with the gradVect end colors if the gradient vector is smaller than the shape to draw
List<Vertex> leftQuad = new ArrayList<Vertex>();
if (vP1.x > invBoundsVecs[0].x){
//upper left of bounding rect
Vertex v1 = new Vertex(invBoundsVecs[0].x, invBoundsVecs[0].y, 0, newBounds[0].getR(), newBounds[0].getG(), newBounds[0].getB(), newBounds[0].getA());
//first stop on gradient vector upper
Vertex v2 = new Vertex(newBounds[0].x, newBounds[0].y, 0, newBounds[0].getR(), newBounds[0].getG(), newBounds[0].getB(), newBounds[0].getA());
//first stop on gradient vector lower
Vertex v3 = new Vertex(newBounds[3].x, newBounds[3].y, 0, newBounds[3].getR(), newBounds[3].getG(), newBounds[3].getB(), newBounds[3].getA());
//down left of bounding rect
Vertex v4 = new Vertex(invBoundsVecs[3].x, invBoundsVecs[3].y, 0, newBounds[0].getR(), newBounds[0].getG(), newBounds[0].getB(), newBounds[0].getA());
leftQuad.add(v1);
leftQuad.add(v2);
leftQuad.add(v3);
leftQuad.add(v4);
}
//Add Right quad if gradient rectangle is smaler than overall bounds
List<Vertex> rightQuad = new ArrayList<Vertex>();
if (vP2.x < invBoundsVecs[1].x){
Vertex gradientRectUpperRight = newBounds[newBounds.length-3];
Vertex gradientRectLowerRight = newBounds[newBounds.length-2];
Vertex v1 = new Vertex(gradientRectUpperRight.x, gradientRectUpperRight.y, 0, gradientRectUpperRight.getR(), gradientRectUpperRight.getG(), gradientRectUpperRight.getB(), gradientRectUpperRight.getA());
Vertex v2 = new Vertex(invBoundsVecs[1].x, invBoundsVecs[1].y, 0, gradientRectUpperRight.getR(), gradientRectUpperRight.getG(), gradientRectUpperRight.getB(), gradientRectUpperRight.getA());
Vertex v3 = new Vertex(invBoundsVecs[2].x, invBoundsVecs[2].y, 0, gradientRectUpperRight.getR(), gradientRectUpperRight.getG(), gradientRectUpperRight.getB(), gradientRectUpperRight.getA());
Vertex v4 = new Vertex(gradientRectLowerRight.x, gradientRectLowerRight.y, 0, gradientRectUpperRight.getR(), gradientRectUpperRight.getG(), gradientRectUpperRight.getB(), gradientRectUpperRight.getA());
rightQuad.add(v1);
rightQuad.add(v2);
rightQuad.add(v3);
rightQuad.add(v4);
}
//Create new array for gradient shape with all quads inside
List<Vertex> allGradientShapeVerts = new ArrayList<Vertex>();
allGradientShapeVerts.addAll(leftQuad);
allGradientShapeVerts.addAll(gradientRectQuads);
allGradientShapeVerts.addAll(rightQuad);
newBounds = allGradientShapeVerts.toArray(new Vertex[allGradientShapeVerts.size()]);
//Rotate the vectors of the calculated bounding rect back to the original angle
newBounds = (Vertex[]) Vector3D.rotateZVectorArray(newBounds, testShape.getCenterPointLocal(), gradAngle);
//Create gradient shape to paint over the real shape
MTPolygon p = new MTPolygon(newBounds,pa);
p.setNoStroke(true);
p.setPickable(false);
p.setStrokeWeight(testShape.getStrokeWeight());
p.setFillDrawMode(GL.GL_QUADS);
//Use displaylist by default for gradientshape
p.generateAndUseDisplayLists();
FillPaint gradStencil = new FillPaint(gl, p);
return gradStencil;
}
private FillPaint setUpRotatedGradientBBox(AbstractShape testShape, float angle, List<Stop> stops){
GL gl = ((PGraphicsOpenGL)pa.g).gl;
float gradAngle = angle;
//Get copy of shapes vertices
Vertex[] shapeVertsCopy = Vertex.getDeepVertexArrayCopy(testShape.getGeometryInfo().getVertices());
//Rotate the vertices in the inverse direction of the gradients vector angle
shapeVertsCopy = (Vertex[]) Vertex.rotateZVectorArray(shapeVertsCopy, testShape.getCenterPointLocal(), -gradAngle);
//Create a temporary polygon with the roated vertices to calc BBox
MTPolygon inverseRotatedShape = new MTPolygon(shapeVertsCopy, pa);
//Calculate a bounding rectangle from the rotated shape
BoundsZPlaneRectangle inverseRotatedBounds = new BoundsZPlaneRectangle(inverseRotatedShape);
Vector3D[] invBoundsVecs = inverseRotatedBounds.getVectorsLocal();
//Get the positions where the offsets are on the gradient vector
float bBoxWidth = invBoundsVecs[1].x - invBoundsVecs[0].x;
logger.debug("BBox width: " + bBoxWidth);
float w = bBoxWidth/*/100*/;
List<Float> xStops = new ArrayList<Float>();
//Go through stops and multiply stop offset with bbox width to get the position
for(Stop stop : stops){
float offsetStopPosition = w * stop.offset;
xStops.add(offsetStopPosition);
logger.debug("OffsetStopPosition: " + offsetStopPosition);
}
//Calc new gradient polygon vertices with vertices at the stop locations
Vertex[] newBounds = new Vertex[(xStops.size()-1) * 4];
for (int i = 0; i < xStops.size()-1; i++) {
float offset = xStops.get(i);
Color stopColor = stops.get(i).color;
float nextOffset = xStops.get(i+1);
Color nextStopColor = stops.get(i+1).color;
newBounds[i*4] = new Vertex(invBoundsVecs[0].x + offset, invBoundsVecs[0].y,0, stopColor.getRed(), stopColor.getGreen(), stopColor.getBlue(), stopColor.getAlpha());
newBounds[i*4+1] = new Vertex(invBoundsVecs[0].x + nextOffset, invBoundsVecs[0].y,0, nextStopColor.getRed(), nextStopColor.getGreen(), nextStopColor.getBlue(), nextStopColor.getAlpha());
newBounds[i*4+2] = new Vertex(invBoundsVecs[0].x + nextOffset, invBoundsVecs[2].y,0, nextStopColor.getRed(), nextStopColor.getGreen(), nextStopColor.getBlue(), nextStopColor.getAlpha());
newBounds[i*4+3] = new Vertex(invBoundsVecs[0].x + offset, invBoundsVecs[2].y,0, stopColor.getRed(), stopColor.getGreen(), stopColor.getBlue(), stopColor.getAlpha());
}
/*
logger.debug("->New bounds:");
for (int i = 0; i < newBounds.length; i++) {
Vertex vertex = newBounds[i];
logger.debug(vertex);
}
*/
//Rotate the vectors of the calculated bounding rect back to the original angle
newBounds = (Vertex[]) Vector3D.rotateZVectorArray(newBounds, testShape.getCenterPointLocal(), gradAngle);
//Create gradient shape to paint over the real shape
MTPolygon p = new MTPolygon(newBounds,pa);
p.setNoStroke(true);
p.setPickable(false);
p.setFillDrawMode(GL.GL_QUADS);
p.setStrokeWeight(testShape.getStrokeWeight());
//Use displaylist by default for gradientshape
p.generateAndUseDisplayLists();
FillPaint gradStencil = new FillPaint(gl, p);
return gradStencil;
}
private CycleMethodEnum getSpreadMethod(Element paintElement){
String s = new String();
//SPREADMETHOD 'spreadMethod' attribute - default is pad
CycleMethodEnum spreadMethod = MultipleGradientPaint.NO_CYCLE;
s = SVGUtilities.getChainableAttributeNS(paintElement, null, SVG_SPREAD_METHOD_ATTRIBUTE, ctx);
if (s.length() != 0) {
// spreadMethod = AbstractSVGGradientElementBridge.convertSpreadMethod(paintElement, s, ctx);
if (SVG_REPEAT_VALUE.equals(s)) {
spreadMethod = MultipleGradientPaint.REPEAT;
}else
if (SVG_REFLECT_VALUE.equals(s)) {
spreadMethod = MultipleGradientPaint.REFLECT;
}else
if (SVG_PAD_VALUE.equals(s)) {
spreadMethod = MultipleGradientPaint.NO_CYCLE;
}else
throw new BridgeException(ctx, paintElement, "ERR_ATTRIBUTE_VALUE_MALFORMED", new Object[] {SVG_SPREAD_METHOD_ATTRIBUTE, s});
}
return spreadMethod;
}
private AffineTransform getGradientTransform(Element paintElement){
String s = new String();
//'gradientTransform' attribute - default is an Identity matrix
AffineTransform transform;
s = SVGUtilities.getChainableAttributeNS(paintElement, null, SVG_GRADIENT_TRANSFORM_ATTRIBUTE, ctx);
if (s.length() != 0) {
transform = SVGUtilities.convertTransform(paintElement, SVG_GRADIENT_TRANSFORM_ATTRIBUTE, s, ctx);
} else {
transform = new AffineTransform();
}
return transform;
}
/**
* Returns the stops elements of the specified gradient
* element. Stops can be children of the gradients or defined on
* one of its 'ancestor' (linked with the xlink:href attribute).
*
* @param paintElement the gradient element
* @param opacity the opacity
* @param ctx the bridge context to use
*/
protected List<Stop> extractStops(Element paintElement, float opacity, BridgeContext ctx) {
//List<Object> refs = new LinkedList<Object>();
for (;;) {
List<Stop> stops = extractLocalStop(paintElement, opacity, ctx);
if (stops != null) {
boolean zeroOffset = false;
boolean oneOffset = false;
boolean zeroOffsetAdded = false;
boolean oneOffsetAdded = false;
//Stops in svg dont have to have ending stops at 0.0 and 1.0
//but we need them so we add them ourselves if not present
for (Stop stop :stops){
if (stop.offset == 0.0){
zeroOffset = true;
}
if (stop.offset == 1.0){
oneOffset = true;
}
}
//Add a stop for beginning and end if not existant
if (!zeroOffset){
logger.debug("No offset at 0.0 location -> adding it.");
stops.add(0, new AbstractSVGGradientElementBridge.Stop(new java.awt.Color(0,0,0,0), 0.0f));
zeroOffsetAdded = true;
}
if (!oneOffset){
logger.debug("No offset at 1.0 location -> adding it.");
stops.add(stops.size(), new AbstractSVGGradientElementBridge.Stop(new java.awt.Color(0,0,0,0), 1.0f));
oneOffsetAdded = true;
}
//Sort stops by offset position from 0.0 to 1.0
List<GradientStop> gradientStops = new ArrayList<GradientStop>();
for (Stop stop :stops){
gradientStops.add(new GradientStop(stop.offset, stop.color));
}
GradientStop[] gradStopArr = gradientStops.toArray(new GradientStop[gradientStops.size()]);
HelperMethods.quicksort(gradStopArr);
//Create new, sorted stop list, clamp color from self created zero/one offsets to nearest color
List<Stop> sortedStopList = new ArrayList<Stop>();
for (int i = 0; i < gradStopArr.length; i++) {
GradientStop gradientStop = gradStopArr[i];
if (zeroOffsetAdded
&& i == 0
&& gradStopArr.length >= i+1
&& gradStopArr[i+1] != null
){
gradientStop.color = gradStopArr[i+1].color;
}
if (oneOffsetAdded
&& i == gradStopArr.length-1
//&& gradStopArr.length >= i+1
&& gradStopArr[i-1] != null
){
gradientStop.color = gradStopArr[i-1].color;
}
sortedStopList.add(new AbstractSVGGradientElementBridge.Stop(gradientStop.color, gradientStop.offset));
}
return sortedStopList;
// return stops; // stop elements found, exit
}
String uri = XLinkSupport.getXLinkHref(paintElement);
if (uri.length() == 0) {
return null; // no xlink:href found, exit
}
// check if there is circular dependencies
/*
String baseURI = XMLBaseSupport.getCascadedXMLBase(paintElement);
ParsedURL purl = new ParsedURL(baseURI, uri);
if (contains(refs, purl)) {
throw new BridgeException(paintElement,
ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
new Object[] {uri});
}
refs.add(purl);
*/
paintElement = ctx.getReferencedElement(paintElement, uri);
}
}
/**
* To compare stop offsets
* @author Chris
*/
private class GradientStop implements Comparable<GradientStop>{
float offset;
Color color;
public GradientStop(float offset, java.awt.Color color2){
this.offset = offset;
this.color = color2;
}
//@Override
public int compareTo(GradientStop o) {
if (this.offset < o.offset){
return -1;
}
else if(offset == o.offset){
return 0;
}
else if(this.offset > o.offset){
return 1;
}else{
return 0;
}
}
}
/**
* Returns a list of <tt>Stop</tt> elements, children of the
* specified paintElement can have or null if any.
*
* @param gradientElement the paint element
* @param opacity the opacity
* @param ctx the bridge context
*/
protected static List<Stop> extractLocalStop(Element gradientElement, float opacity, BridgeContext ctx) {
LinkedList<Stop> stops = null;
Stop previous = null;
for (Node n = gradientElement.getFirstChild(); n != null; n = n.getNextSibling()){
if ((n.getNodeType() != Node.ELEMENT_NODE)) {
continue;
}
Element e = (Element)n;
Bridge bridge = ctx.getBridge(e);
if (bridge == null || !(bridge instanceof SVGStopElementBridge)) {
continue;
}
Stop stop = ((SVGStopElementBridge)bridge).createStop(ctx, gradientElement, e, opacity);
if (stops == null) {
stops = new LinkedList<Stop>();
}
if (previous != null) {
if (stop.offset < previous.offset) {
stop.offset = previous.offset;
}
}
stops.add(stop);
previous = stop;
}
return stops;
}
private List<Float> getSVGLengthListAsFloat(SVGLengthList valueList){
List<Float> values = new ArrayList<Float>();
for (int i = 0; i < valueList.getNumberOfItems(); i++) {
values.add(valueList.getItem(i).getValue());
}
if (values.isEmpty()){
values.add(0f);
}
return values;
}
/**
* Tries to retrieve a css property as a float number.
* If it fails, it returns the provided defaultvalue.
*
* @param gfxElem the gfx elem
* @param queryProperty the query property
* @param defaultValue the default value
*
* @return the float
*/
private float queryPrimitiveFloatValue(SVGGraphicsElement gfxElem, String queryProperty, float defaultValue){
float returnValue = defaultValue;
CSSStyleDeclaration style = gfxElem.getOwnerSVGElement().getComputedStyle(gfxElem, "");
CSSValue cssValue = (CSSValue) style.getPropertyCSSValue(queryProperty);
// logger.debug("CSSValue.getCssText() of proerty " + queryProperty + ": " + cssValue.getCssText());
if (cssValue != null){
if (cssValue.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE){
try{
org.w3c.dom.css.CSSPrimitiveValue v = (org.w3c.dom.css.CSSPrimitiveValue)cssValue;
if (v.getCssValueType() == CSSPrimitiveValue.CSS_NUMBER){
returnValue = v.getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
// logger.debug(queryProperty + ": " + returnValue);
}
}catch(Exception e){
logger.error(e.getMessage());
}
}
}
return returnValue;
}
/**
* Creates a polygon from an SVGOMPolygonElement element.
*
* @param polyElem the poly elem
* @param noFill the no fill
* @param windingRule the winding rule
*
* @return the live polygon component
*/
private AbstractShape getLivePolygonComponent(SVGOMPolygonElement polyElem, boolean noFill, int windingRule){
AbstractShape returnComponent = null;
//Create Vertex array from points
SVGPointList pointList = polyElem.getPoints();
Vertex[] vertices = new Vertex[pointList.getNumberOfItems()];
for (int i = 0; i < pointList.getNumberOfItems(); i++) {
SVGPoint p = pointList.getItem(i);
vertices[i] = new Vertex(p.getX(), p.getY(),0);
}
//If polygon isnt closed, close it with the first vertex
if (!vertices[0].equalsVector(vertices[vertices.length-1])){
Vertex[] closedVertices = new Vertex[vertices.length+1];
System.arraycopy(vertices, 0, closedVertices, 0, vertices.length);
closedVertices[closedVertices.length-1] = (Vertex)vertices[0].getCopy();
vertices = closedVertices;
}
int convexity = ConvexityUtil.classifyPolygon2(vertices.length, vertices);
switch (convexity) {
case ConvexityUtil.NotConvexDegenerate:
case ConvexityUtil.NotConvex:
//If not filled, we dont worry about non-simple polygons
if (noFill){
returnComponent = createPoly(vertices);
}else{
ArrayList<Vertex[]> contours = new ArrayList<Vertex[]>();
contours.add(vertices);
// returnComponent = createStencilPoly(vertices, contours);
returnComponent = createComplexPoly(contours, windingRule);
}
break;
case ConvexityUtil.ConvexDegenerate:
case ConvexityUtil.ConvexCW:
case ConvexityUtil.ConvexCCW:
returnComponent = createPoly(vertices);
break;
default:
break;
}
return returnComponent;
}
/**
* Creates a polygon from a SVGOMPathElement.
*
* @param pathElem the path elem
* @param noFill the no fill
* @param windingRule the winding rule
*
* @return the live path component
*/
private AbstractShape getLivePathComponent(SVGOMPathElement pathElem, boolean noFill, int windingRule){
AbstractShape returnComponent = null;
CustomPathHandler pathHandler = new CustomPathHandler();
PathParser pathParser = new PathParser();
//pathHandler.setVerbose(true);
/*
SVGPathSegList pathSegList = pathElem.getPathSegList();
SVGPathSeg seg = pathSegList.getItem(pathSegList.getNumberOfItems()-1);
logger.debug(seg.getPathSegTypeAsLetter());
*/
//Parse the "d" attribute
String dAttValue = pathElem.getAttribute("d");
pathParser.setPathHandler(pathHandler);
pathParser.parse(dAttValue);
//Get the Vertices of the path
Vertex[] originalPointsArray = pathHandler.getPathPointsArray();
//Get Sub-Paths
ArrayList<Vertex[]> contours = pathHandler.getContours();
// For stencil-trick-polygons!! \\
// /*
//Get path vertices points
LinkedList<Vertex> pathPoints = pathHandler.getPathPoints();
if (pathHandler.getReverseMoveToStack().size() <= 1){
//nicht adden
}else{
pathPoints.addAll(pathHandler.getReverseMoveToStack());
}
Vertex[] pathVertsStencilPrepared = (Vertex[])pathPoints.toArray(new Vertex[pathPoints.size()]);
// */
//Check if path vertices are empty
if (originalPointsArray.length == 0){
logger.debug("Empty path vertex array -> aborting");
return null;
}
//TODO actually should calculate the real vertices from the vezier ones and then check
//for convexity..else there might be false positives
Vertex[] v;
// if (containsBeziers(v)){
// v = Tools3D.createVertexArrFromBezierArr(originalPointsArray, 11);
// }
v = originalPointsArray;
//Check for convexity
int convexity = ConvexityUtil.classifyPolygon2(v.length, v);
switch (convexity) {
case ConvexityUtil.NotConvexDegenerate:
// logger.debug("not Convex Degenerate");
case ConvexityUtil.NotConvex:
// logger.debug("not convex");
//If not filled, we can createa non stenciled polygon with no filling for better
//performance
if (noFill){
returnComponent = createPoly(originalPointsArray);
}else{
returnComponent = createComplexPoly(contours, windingRule);
}
break;
case ConvexityUtil.ConvexDegenerate:
// logger.debug("convex degenerate");
case ConvexityUtil.ConvexCW:
// logger.debug("convex clockwise");
case ConvexityUtil.ConvexCCW:
// logger.debug("convex counterclockwise");
returnComponent = createPoly(originalPointsArray);
break;
default:
break;
}
return returnComponent;
}
/**
* Creates a Stencil-Trick-Polygon.
*
* @param stencilPreparedVerts the stencil prepared verts
* @param subPaths the sub paths
*
* @return the abstract shape
*/
private AbstractShape createStencilPoly(Vertex[] stencilPreparedVerts, ArrayList<Vertex[]> subPaths) {
// logger.debug("Create stencil poly");
//Blow up vertex array, that will be used for picking etc
//to at least be of size == 3 for generating normals
if (stencilPreparedVerts.length <3){
Vertex[] newVerts = new Vertex[3];
if (stencilPreparedVerts.length == 2){
newVerts[0] = stencilPreparedVerts[0];
newVerts[1] = stencilPreparedVerts[1];
newVerts[2] = (Vertex)stencilPreparedVerts[1].getCopy();
stencilPreparedVerts = newVerts;
}else if (stencilPreparedVerts.length == 1){
newVerts[0] = stencilPreparedVerts[0];
newVerts[1] = (Vertex)stencilPreparedVerts[1].getCopy();
newVerts[2] = (Vertex)stencilPreparedVerts[1].getCopy();
stencilPreparedVerts = newVerts;
}else{
//ERROR
}
}
MTStencilPolygon newShape = new MTStencilPolygon(stencilPreparedVerts, subPaths, pa);
return newShape;
}
/**
* Creates a Complex (tesselated) polygon.
*
* @param contours the contours
* @param windingRule the winding rule
*
* @return the abstract shape
*/
private AbstractShape createComplexPoly(ArrayList<Vertex[]> contours, int windingRule) {
int segments = 10;
List<Vertex[]> bezierContours = ToolsGeometry.createVertexArrFromBezierVertexArrays(contours, segments);
GluTrianglulator triangulator = new GluTrianglulator(pa);
// MTTriangleMesh mesh = triangulator.toTriangleMesh(bezierContours, windingRule);
triangulator.tesselate(bezierContours, windingRule);
List<Vertex> tris = triangulator.getTriList();
Vertex[] verts = tris.toArray(new Vertex[tris.size()]);
GeometryInfo geom = new GeometryInfo(pa, verts);
// MTTriangleMesh mesh = new MTTriangleMesh(pa, geom);
MTTriangleMesh mesh = new SVGMesh(pa, geom);
//TODO put outline contourse in own class SVGMesh!
//not belonging in general mesh class
mesh.setOutlineContours(bezierContours);
triangulator.deleteTess(); //Delete triangulator (C++ object)
return mesh;
}
private class SvgPolygon extends MTPolygon{
public SvgPolygon(Vertex[] vertices, PApplet applet) {
super(vertices, applet);
}
protected IBoundingShape computeDefaultBounds() {
//Use z plane bounding rect instead default boundingsphere for svg!
return new BoundsZPlaneRectangle(this);
}
@Override
protected void setDefaultGestureActions() {
}
}
private class SVGMesh extends MTTriangleMesh{
public SVGMesh(PApplet applet, GeometryInfo geometryInfo) {
super(applet, geometryInfo, false);
}
@Override
protected IBoundingShape computeDefaultBounds() {
//Use z plane bounding rect instead default boundingsphere for svg!
return new BoundsZPlaneRectangle(this);
}
@Override
protected void setDefaultGestureActions() {
}
}
// /**
// * Creates a Complex (tesselated) polygon
// * @param contours
// * @param windingRule
// * @return
// */
// private AbstractShape createComplexPoly(ArrayList<Vertex[]> contours, int windingRule) {
//// logger.debug("Create createComplexPoly poly");
//// if (contours.get(0).length <3)
//// logger.error("<3");
//
// //Blow up first contour, that will be used for picking etc
// //to at least be of size == 3 for generating normals
// if (contours.get(0).length <3){
// if (contours.get(0).length == 2){
// Vertex[] v = new Vertex[3];
// v[0] = contours.get(0)[0];
// v[1] = contours.get(0)[1];
// v[2] = (Vertex)contours.get(0)[1].getCopy();
// Vertex[] c = contours.get(0);
// c = v;
// }else if (contours.get(0).length == 1){
// Vertex[] v = new Vertex[3];
// v[0] = contours.get(0)[0];
// v[1] = (Vertex)contours.get(0)[0].getCopy();
// v[2] = (Vertex)contours.get(0)[0].getCopy();
// Vertex[] c = contours.get(0);
// c = v;
// }else{
// //ERROR
// }
// }
//
// MTComplexPolygon newShape = new MTComplexPolygon(contours, pa);
// newShape.setWindingRule(windingRule);
// return newShape;
// }
/**
* Creates a plain, normal polygon.
*
* @param vertices the vertices
*
* @return the abstract shape
*/
private AbstractShape createPoly(Vertex[] vertices) {
// logger.debug("Create poly");
Vertex[] verts = vertices;
if (ToolsGeometry.containsBezierVertices(verts))
verts = ToolsGeometry.createVertexArrFromBezierArr(verts, 13);
//Blow up vertex array, that will be used for picking etc
//to at least be of size == 3 for generating normals
if (verts.length <3){
Vertex[] newVerts = new Vertex[3];
if (verts.length == 2){
newVerts[0] = verts[0];
newVerts[1] = verts[1];
newVerts[2] = (Vertex)verts[1].getCopy();
verts = newVerts;
}else if (verts.length == 1){
newVerts[0] = verts[0];
newVerts[1] = (Vertex)verts[0].getCopy();
newVerts[2] = (Vertex)verts[0].getCopy();
verts = newVerts;
}else{
//ERROR
}
}
//For lines or polygons do this
// MTPolygon poly = new MTPolygon(verts , pa);
MTPolygon poly = new SvgPolygon(verts,pa);
return poly;
}
/**
* Checks whether the given element is located inside a clip-path element.
* Used to determine whether to draw the component or not.
*
* @param element the element
*
* @return true, if checks if is under clip path
*/
private boolean isUnderClipPath(Node element){
if (element.getParentNode() == null)
return false;
while (element.getParentNode() != null ) {
Node parent = element.getParentNode();
if (parent.getNodeName().equals(SVG_CLIP_PATH_TAG))
return true;
element = parent;
}
return false;
}
/**
* Gets the inherited opacity.
*
* @param svgElem the svg elem
* @param element the element
*
* @return the inherited opacity
*/
private float getInheritedOpacity(SVGSVGElement svgElem, Node element){
float returnOpactiy = 1.0f;
if (element.getParentNode() == null)
return returnOpactiy;
//Get attribute of this element
try{
if (element instanceof SVGGraphicsElement){
SVGGraphicsElement gfx = (SVGGraphicsElement)element;
float opacity = ((CSSPrimitiveValue)svgElem.getComputedStyle(gfx, "").getPropertyCSSValue(("opacity"))).getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
returnOpactiy *= opacity;
logger.debug(gfx.getTagName() + ": found opacity: " + opacity);
}
}catch(Exception e){
e.printStackTrace();
}
while (element.getParentNode() != null ) {
Node parent = element.getParentNode();
try{
if (parent instanceof SVGGraphicsElement){
SVGGraphicsElement gfx = (SVGGraphicsElement)parent;
float opacity = ((CSSPrimitiveValue)svgElem.getComputedStyle(gfx, "").getPropertyCSSValue(("opacity"))).getFloatValue(CSSPrimitiveValue.CSS_NUMBER);
returnOpactiy *= opacity;
logger.debug(gfx.getTagName() + ": found opacity: " + opacity);
}
}catch(Exception e){
e.printStackTrace();
}
element = parent;
}
return returnOpactiy;
}
}