/*
* Copyright 2008 Oliver Zoran
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gwt.canvas.client;
import java.util.ArrayList;
import java.util.Stack;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Image;
/**
* @see http://msdn2.microsoft.com/en-us/library/bb250524(VS.85).aspx
* @see http://en.wikipedia.org/wiki/Transformation_matrix
* @see http://code.google.com/p/explorercanvas/
*/
public class CanvasImplIE extends CanvasImpl {
private abstract class Path {}
private class ClosePath extends Path {
public String toString() {
return " x";
}
}
private class MoveTo extends Path {
protected float x;
protected float y;
public MoveTo(float x, float y) {
this.x = x;
this.y = y;
currentX = x;
currentY = y;
}
public String toString() {
return " m " + getCoordX(x, y) + "," + getCoordY(x, y);
}
}
private class LineTo extends MoveTo {
public LineTo(float x, float y) {
super(x, y);
}
public String toString() {
return " l " + getCoordX(x, y) + "," + getCoordY(x, y);
}
}
private class BezierCurveTo extends MoveTo {
protected float c1x;
protected float c1y;
protected float c2x;
protected float c2y;
public BezierCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) {
super(x, y);
c1x = cp1x;
c1y = cp1y;
c2x = cp2x;
c2y = cp2y;
}
public String toString() {
return " c " +
getCoordX(c1x, c1y) + "," + getCoordY(c1x, c1y) + "," +
getCoordX(c2x, c2y) + "," + getCoordY(c2x, c2y) + "," +
getCoordX(x, y) + "," + getCoordY(x, y);
}
}
private class Arc extends Path {
protected String type;
protected float x;
protected float y;
protected float ar;
protected float startX;
protected float startY;
protected float endX;
protected float endY;
public Arc(float x, float y, float radius, float startAngle, float endAngle, boolean anticlockwise) {
this.x = x;
this.y = y;
if (anticlockwise) {
type = " at ";
} else {
type = " wa ";
}
ar = radius * 10;
startX = x + ((float) Math.cos(startAngle) * ar) - 5;
startY = y + ((float) Math.sin(startAngle) * ar) - 5;
endX = x + ((float) Math.cos(endAngle) * ar) - 5;
endY = y + ((float) Math.sin(endAngle) * ar) - 5;
if (startX == endX && !anticlockwise) {
startX += 0.125f;
}
}
public String toString() {
int cx = getCoordX(x, y);
int cy = getCoordY(x, y);
float arcX = context.arcScaleX * ar;
float arcY = context.arcScaleY * ar;
return type +
Math.round(cx - arcX) + "," +
Math.round(cy - arcY) + " " +
Math.round(cx + arcX) + "," +
Math.round(cy + arcY) + " " +
getCoordX(startX, startY) + "," +
getCoordY(startX, startY) + " " +
getCoordX(endX, endY) + "," +
getCoordY(endX, endY);
}
}
public final static String SOURCE_OVER = "beforeEnd";
public final static String DESTINATION_OVER = "afterBegin";
public final static String BUTT = "flat";
private ArrayList<Integer> clearRectIndices = new ArrayList<Integer>();
private Stack<VMLContext> contextStack = new Stack<VMLContext>();
private Stack<Path> pathStack = new Stack<Path>();
private Canvas canvas;
private VMLContext context;
private float[] matrix;
private float currentX = 0.0f;
private float currentY = 0.0f;
public void init(Canvas canvas) {
this.element = canvas.getElement();
this.canvas = canvas;
DOM.setStyleAttribute(element, "position", "relative");
DOM.setStyleAttribute(element, "display", "inline-block");
DOM.setStyleAttribute(element, "overflow", "hidden");
init();
context = new VMLContext();
matrix = context.matrix;
}
protected native void init() /*-{
if (!$doc.namespaces["v"]) {
$doc.namespaces.add("v", "urn:schemas-microsoft-com:vml");
$doc.createStyleSheet().cssText = "v\\:*{behavior:url(#default#VML);}";
}
}-*/;
public void setHeight(int height) {
DOM.setInnerHTML(element, "");
DOM.setStyleAttribute(element, "height", height + "px");
}
public void setWidth(int width) {
DOM.setInnerHTML(element, "");
DOM.setStyleAttribute(element, "width", width + "px");
}
public void setBackgroundColor(String color) {
super.setBackgroundColor(color);
for (Integer index : clearRectIndices) {
Element elem = DOM.getChild(element, index);
DOM.setElementAttribute(elem, "fillcolor", color);
}
}
private native void insert(String gco, String html) /*-{
this.@gwt.canvas.client.CanvasImpl::element.insertAdjacentHTML(gco, html);
}-*/;
private int getCoordX(float x, float y) {
return Math.round(10 * (matrix[0] * x + matrix[3] * y + matrix[6]) - 5);
}
private int getCoordY(float x, float y) {
return Math.round(10 * (matrix[1] * x + matrix[4] * y + matrix[7]) - 5);
}
/////////////////////////////////////////////////////////////////
// CANVAS STATE METHODS
/////////////////////////////////////////////////////////////////
public void restore() {
if (!contextStack.isEmpty()) {
context = contextStack.pop();
matrix = context.matrix;
}
}
public void save() {
contextStack.push(context);
context = new VMLContext(context);
matrix = context.matrix;
}
public void rotate(float angle) {
float s = (float) Math.sin(angle);
float c = (float) Math.cos(angle);
float a = matrix[0];
float b = matrix[3];
matrix[0] = a * c + b * s;
matrix[3] = -a * s + b * c;
a = matrix[1];
b = matrix[4];
matrix[1] = a * c + b * s;
matrix[4] = -a * s + b * c;
}
public void scale(float x, float y) {
context.arcScaleX *= x;
context.arcScaleY *= y;
matrix[0] *= x;
matrix[1] *= x;
matrix[3] *= y;
matrix[4] *= y;
}
public void translate(float x, float y) {
matrix[6] += matrix[0] * x + matrix[3] * y;
matrix[7] += matrix[1] * x + matrix[4] * y;
}
public void transform(float m11, float m12, float m21, float m22, float dx, float dy) {
float a = matrix[0];
float b = matrix[3];
matrix[0] = a * m11 + b * m12;
matrix[3] = a * m21 + b * m22;
matrix[6] += a * dx + b * dy;
a = matrix[1];
b = matrix[4];
matrix[1] = a * m11 + b * m12;
matrix[4] = a * m21 + b * m22;
matrix[7] += a * dx + b * dy;
}
public void setTransform(float m11, float m12, float m21, float m22, float dx, float dy) {
matrix[0] = m11; matrix[1] = m12;
matrix[3] = m21; matrix[4] = m22;
matrix[6] = dx; matrix[7] = dy;
}
/////////////////////////////////////////////////////////////////
// WORKING WITH PATHS
/////////////////////////////////////////////////////////////////
public void arc(float x, float y, float radius, float startAngle, float endAngle, boolean anticlockwise) {
pathStack.push(new Arc(x, y, radius, startAngle, endAngle, anticlockwise));
}
public void cubicCurveTo(float cp1x, float cp1y, float cp2x, float cp2y, float x, float y) {
pathStack.push(new BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y));
}
public void quadraticCurveTo(float cpx, float cpy, float x, float y) {
float cp1x = currentX + 2.0f / 3.0f * (cpx - currentX);
float cp1y = currentY + 2.0f / 3.0f * (cpy - currentY);
float cp2x = cp1x + (x - currentX) / 3.0f;
float cp2y = cp1y + (y - currentY) / 3.0f;
pathStack.push(new BezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y));
}
public void beginPath() {
pathStack.clear();
}
public void closePath() {
pathStack.push(new ClosePath());
}
public void moveTo(float x, float y) {
pathStack.push(new MoveTo(x, y));
}
public void lineTo(float x, float y) {
pathStack.push(new LineTo(x, y));
}
public void rect(float x, float y, float w, float h) {
pathStack.push(new MoveTo(x , y ));
pathStack.push(new LineTo(x + w, y ));
pathStack.push(new LineTo(x + w, y + h));
pathStack.push(new LineTo(x , y + h));
pathStack.push(new ClosePath());
}
/////////////////////////////////////////////////////////////////
// STROKING AND FILLING
/////////////////////////////////////////////////////////////////
public void clear() {
pathStack.clear();
clearRectIndices.clear();
DOM.setInnerHTML(element, "");
}
public void fill() {
if (pathStack.isEmpty()) {
return;
}
StringBuffer strbuf = new StringBuffer(
"<v:shape style=\"position:absolute;width:1;height:1;\" coordsize=\"10,10\" fillcolor=\"" +
context.fillStyle + "\" stroked=\"f\" path=\""
);
for (Path path : pathStack) {
strbuf.append(path);
}
strbuf.append(
" e\"><v:fill opacity=\"" + (context.globalAlpha * context.fillAlpha) +
"\"></v:fill></v:shape>"
);
insert(context.globalCompositeOperation, strbuf.toString());
pathStack.clear();
}
public void stroke() {
if (pathStack.isEmpty()) {
return;
}
StringBuffer strbuf = new StringBuffer(
"<v:shape style=\"position:absolute;width:1;height:1;\" coordsize=\"10,10\" filled=\"f\" strokecolor=\"" +
context.strokeStyle + "\" strokeweight=\"" + context.lineWidth + "px\" path=\""
);
for (Path path : pathStack) {
strbuf.append(path);
}
strbuf.append(
" e\"><v:stroke opacity=\"" + (context.globalAlpha * context.strokeAlpha) +
"\" miterlimit=\"" + context.miterLimit +
"\" joinstyle=\"" + context.lineJoin +
"\" endcap=\"" + context.lineCap +
"\"></v:stroke></v:shape>"
);
insert(context.globalCompositeOperation, strbuf.toString());
pathStack.clear();
}
public void clearRect(float x, float y, float w, float h) {
pathStack.clear();
int index = DOM.getChildCount(element);
if (index <= 0) {
return;
}
w += x;
h += y;
if (Math.round(matrix[0] * x + matrix[3] * y + matrix[6]) <= 0 &&
Math.round(matrix[1] * x + matrix[4] * y + matrix[7]) <= 0 &&
Math.round(matrix[0] * w + matrix[3] * h + matrix[6]) >= canvas.getWidth() &&
Math.round(matrix[1] * w + matrix[4] * h + matrix[7]) >= canvas.getHeight())
{
clearRectIndices.clear();
DOM.setInnerHTML(element, "");
return;
}
clearRectIndices.add(index);
insert( context.globalCompositeOperation,
"<v:shape style=\"position:absolute;width:1;height:1;\" coordsize=\"10,10\" fillcolor=\"" +
backgroundColor + "\" stroked=\"f\" path=\"m " +
getCoordX(x, y) + "," + getCoordY(x, y) + " l " +
getCoordX(x, h) + "," + getCoordY(x, h) + " " +
getCoordX(w, h) + "," + getCoordY(w, h) + " " +
getCoordX(w, y) + "," + getCoordY(w, y) + " x e\"><v:fill opacity=\"1\"></v:fill></v:shape>"
);
}
public void fillRect(float x, float y, float w, float h) {
w += x;
h += y;
insert( context.globalCompositeOperation,
"<v:shape style=\"position:absolute;width:1;height:1;\" coordsize=\"10,10\" fillcolor=\"" +
context.fillStyle + "\" stroked=\"f\" path=\"m " +
getCoordX(x, y) + "," + getCoordY(x, y) + " l " +
getCoordX(x, h) + "," + getCoordY(x, h) + " " +
getCoordX(w, h) + "," + getCoordY(w, h) + " " +
getCoordX(w, y) + "," + getCoordY(w, y) + " x e\"><v:fill opacity=\"" +
(context.globalAlpha * context.fillAlpha) + "\"></v:fill></v:shape>"
);
pathStack.clear();
}
public void strokeRect(float x, float y, float w, float h) {
w += x;
h += y;
insert( context.globalCompositeOperation,
"<v:shape style=\"position:absolute;width:1;height:1;\" coordsize=\"10,10\" filled=\"f\" strokecolor=\"" +
context.strokeStyle + "\" strokeweight=\"" + context.lineWidth + "px\" path=\"m " +
getCoordX(x, y) + "," + getCoordY(x, y) + " l " +
getCoordX(x, h) + "," + getCoordY(x, h) + " " +
getCoordX(w, h) + "," + getCoordY(w, h) + " " +
getCoordX(w, y) + "," + getCoordY(w, y) + " x e\"><v:stroke opacity=\"" +
(context.globalAlpha * context.strokeAlpha) + "\" miterlimit=\"" +
context.miterLimit + "\" joinstyle=\"" +
context.lineJoin + "\" endcap=\"" +
context.lineCap + "\"></v:stroke></v:shape>"
);
pathStack.clear();
}
/////////////////////////////////////////////////////////////////
// GRADIENT AND PATTERN STYLES
/////////////////////////////////////////////////////////////////
public native void createLinearGradient(float x0, float y0, float x1, float y1) /*-{
// TODO implement and test
}-*/;
public native void createPattern(Image image, String repetition) /*-{
// TODO implement and test
}-*/;
public native void createRadialGradient(float x0, float y0, float r0, float x1, float y1, float r1) /*-{
// TODO implement and test
}-*/;
/////////////////////////////////////////////////////////////////
// DRAWING IMAGES
/////////////////////////////////////////////////////////////////
public native void drawImage(Image image, float x, float y) /*-{
// TODO implement and test
}-*/;
public native void drawImage(Image image, float x, float y, float width, float height) /*-{
// TODO implement and test
}-*/;
public native void drawImage(Image image, float sx, float sy, float swidth, float sheight,
float dx, float dy, float dwidth, float dheight)
/*-{
// TODO implement and test
}-*/;
/////////////////////////////////////////////////////////////////
// SETTERS AND GETTERS
/////////////////////////////////////////////////////////////////
public void setGlobalAlpha(float globalAlpha) {
context.globalAlpha = globalAlpha;
}
public float getGlobalAlpha() {
return context.globalAlpha;
}
public void setGlobalCompositeOperation(String gco) {
gco = gco.trim();
if (gco.equalsIgnoreCase(Canvas.SOURCE_OVER)) {
context.globalCompositeOperation = CanvasImplIE.SOURCE_OVER;
} else if (gco.equalsIgnoreCase(Canvas.DESTINATION_OVER)) {
context.globalCompositeOperation = CanvasImplIE.DESTINATION_OVER;
}
}
public String getGlobalCompositeOperation() {
if (context.globalCompositeOperation == CanvasImplIE.DESTINATION_OVER) {
return Canvas.DESTINATION_OVER;
} else {
return Canvas.SOURCE_OVER;
}
}
public void setStrokeStyle(String strokeStyle) {
strokeStyle = strokeStyle.trim();
if (strokeStyle.startsWith("rgba(")) {
int end = strokeStyle.indexOf(")", 12);
if (end > -1) {
String[] guts = strokeStyle.substring(5, end).split(",");
if (guts.length == 4) {
context.strokeAlpha = Float.parseFloat(guts[3]);
context.strokeStyle = "rgb(" + guts[0] + "," + guts[1] + "," + guts[2] + ")";
context.strokeStyle_ = strokeStyle;
}
}
} else {
context.strokeAlpha = 1.0f;
context.strokeStyle = strokeStyle;
context.strokeStyle_ = null;
}
}
public String getStrokeStyle() {
if (context.strokeStyle_ == null) {
return context.strokeStyle;
} else {
s="c"> return context.strokeStyle_;
}
}
public void setFillStyle(String fillStyle) {
fillStyle = fillStyle.trim();
if (fillStyle.startsWith("rgba(")) {
int end = fillStyle.indexOf(")", 12);
if (end > -1) {
String[] guts = fillStyle.substring(5, end).split(",");
if (guts.length == 4) {
context.fillAlpha = Float.parseFloat(guts[3]);
context.fillStyle = "rgb(" + guts[0] + "," + guts[1] + "," + guts[2] + ")";
context.fillStyle_ = fillStyle;
}
}
} else {
context.fillAlpha = 1.0f;
context.fillStyle = fillStyle;
context.fillStyle_ = null;
}
}
public String getFillStyle() {
if (context.fillStyle_ == null) {
return context.fillStyle;
} else {
return context.fillStyle_;
}
}
public void setLineWidth(float lineWidth) {
context.lineWidth = lineWidth;
}
public float getLineWidth() {
return context.lineWidth;
}
public void setLineCap(String lineCap) {
if (lineCap.trim().equalsIgnoreCase(Canvas.BUTT)) {
context.lineCap = CanvasImplIE.BUTT;
} else {
context.lineCap = lineCap;
}
}
public String getLineCap() {
if (context.lineCap == CanvasImplIE.BUTT) {
return Canvas.BUTT;
}
return context.lineCap;
}
public void setLineJoin(String lineJoin) {
context.lineJoin = lineJoin;
}
public String getLineJoin() {
return context.lineJoin;
}
public void setMiterLimit(float miterLimit) {
context.miterLimit = miterLimit;
}
public float getMiterLimit() {
return context.miterLimit;
}
}