package org.geogebra.web.html5.awt; import org.geogebra.common.awt.GAffineTransform; import org.geogebra.common.awt.GBasicStroke; import org.geogebra.common.awt.GBufferedImage; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GComposite; import org.geogebra.common.awt.GDimension; import org.geogebra.common.awt.GFont; import org.geogebra.common.awt.GFontRenderContext; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GPaint; import org.geogebra.common.awt.GPathIterator; import org.geogebra.common.awt.GShape; import org.geogebra.common.awt.MyImage; import org.geogebra.common.euclidian.GeneralPathClipped; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.View; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; import org.geogebra.ggbjdk.java.awt.geom.AffineTransform; import org.geogebra.ggbjdk.java.awt.geom.GeneralPath; import org.geogebra.ggbjdk.java.awt.geom.Path2D; import org.geogebra.ggbjdk.java.awt.geom.Shape; import org.geogebra.web.html5.gawt.GBufferedImageW; import org.geogebra.web.html5.main.MyImageW; import org.geogebra.web.html5.util.ImageLoadCallback; import org.geogebra.web.html5.util.ImageWrapper; import com.google.gwt.canvas.client.Canvas; import com.google.gwt.canvas.dom.client.CanvasPattern; import com.google.gwt.canvas.dom.client.Context2d; import com.google.gwt.canvas.dom.client.Context2d.Repetition; import com.google.gwt.canvas.dom.client.ImageData; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayNumber; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ImageElement; import com.himamis.retex.renderer.web.graphics.JLMContext2d; public class GGraphics2DW implements GGraphics2D { protected final Canvas canvas; private final JLMContext2d context; private GFontW currentFont = new GFontW("normal"); private GColor color = GColor.newColor(255, 255, 255, 255); private double[] dash_array = null; GPaint currentPaint = GColor.newColor(255, 255, 255, 255); private JsArrayNumber jsarrn; private View view; /** * the pixel ratio of the canvas. */ public double devicePixelRatio = 1; /** * @param canvas */ public GGraphics2DW(Canvas canvas) { this.canvas = canvas; setDirection(); this.context = (JLMContext2d) canvas.getContext2d(); this.context.initTransform(); preventContextMenu(canvas.getElement()); } /* * GGB-1780 special method for SVG export this.canvas not set */ public GGraphics2DW(Context2d ctx) { // HACK this.canvas = null; // could also try this if necessary // this.canvas = Canvas.createIfSupported(); this.context = (JLMContext2d) ctx; this.context.initTransform(); } /** * @param view * The view associated with this instance of GGraphics2DW */ public void setView(View view) { this.view = view; } /** * @return The view associated with this instance of GGraphics2DW, null if * no view has been set */ public View getView() { return view; } public void setImageInterpolation(boolean b) { setImageInterpolationNative(canvas.getContext2d(), b); } private native void setImageInterpolationNative(Context2d ctx, boolean b) /*-{ ctx['imageSmoothingEnabled'] = b; ctx['mozImageSmoothingEnabled'] = b; // IE11+ only ctx['msImageSmoothingEnabled'] = b; ctx['oImageSmoothingEnabled'] = b; // deprecated // https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/Ud3cV1mj35s/Ssat21OeRqYJ // ctx['webkitImageSmoothingEnabled'] = b; }-*/; /** * If we allow right-to left direction * checkboxes have their labels to the * right * labels are drawn to the right, hence the check to fit in screen * will probably fail * labels are malformed, eg )A=(1,2 */ private void setDirection() { this.canvas.getElement().setDir("ltr"); } public GGraphics2DW(Canvas canvas, boolean resetColor) { this(canvas); if (resetColor) { updateCanvasColor(); } } private native void preventContextMenu(Element canvas) /*-{ canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); e.stopPropagation(); return false; }); }-*/; private double[] coords = new double[6]; @Override public void drawStraightLine(double x1, double y1, double x2, double y2) { int width = (int) context.getLineWidth(); context.beginPath(); if (dash_array == null || nativeDashUsed) { if (MyDouble.isOdd(width)) { context.moveTo(Math.floor(x1) + 0.5, Math.floor(y1) + 0.5); context.lineTo(Math.floor(x2) + 0.5, Math.floor(y2) + 0.5); } else { context.moveTo(Math.round(x1), Math.round(y1)); context.lineTo(Math.round(x2), Math.round(y2)); } } else { drawStraightDashedLine(x1, y1, x2, y2, jsarrn, context, MyDouble.isOdd(width)); } context.stroke(); } protected void doDrawShape(Shape shape, boolean enableDashEmulation) { context.beginPath(); GPathIterator it = shape.getPathIterator(null); // see #1718 // boolean enableDashEmulation = true;//nativeDashUsed || // App.isFullAppGui(); while (!it.isDone()) { int cu = it.currentSegment(coords); switch (cu) { default: // do nothing break; case GPathIterator.SEG_MOVETO: context.moveTo(coords[0], coords[1]); if (enableDashEmulation) { setLastCoords(coords[0], coords[1]); } break; case GPathIterator.SEG_LINETO: if (dash_array == null || !enableDashEmulation) { context.lineTo(coords[0], coords[1]); } else { if (nativeDashUsed) { context.lineTo(coords[0], coords[1]); } else { drawDashedLine(pathLastX, pathLastY, coords[0], coords[1], jsarrn, context); } } setLastCoords(coords[0], coords[1]); break; case GPathIterator.SEG_CUBICTO: context.bezierCurveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); if (enableDashEmulation) { setLastCoords(coords[4], coords[5]); } break; case GPathIterator.SEG_QUADTO: context.quadraticCurveTo(coords[0], coords[1], coords[2], coords[3]); if (enableDashEmulation) { setLastCoords(coords[2], coords[3]); } break; case GPathIterator.SEG_CLOSE: context.closePath(); } it.next(); } // this.closePath(); } private double pathLastX; private double pathLastY; private void setLastCoords(double x, double y) { pathLastX = x; pathLastY = y; } // @Override public void drawString(String str, int x, int y) { context.fillText(str, x, y); } @Override public void drawString(String str, double x, double y) { context.fillText(str, x, y); } @Override public void setComposite(GComposite comp) { context.setGlobalAlpha(((GAlphaCompositeW) comp).getAlpha()); // if (comp != null) { // float alpha= ((AlphaComposite) comp).getAlpha(); // if (alpha >= 0f && alpha < 1f) { // context.setGlobalAlpha(alpha); // } // context.setGlobalAlpha(0.5d); // context.restore(); // } } @Override public void setPaint(final GPaint paint) { if (paint instanceof GColor) { setColor((GColor) paint); } else if (paint instanceof GGradientPaintW) { context.setFillStyle(((GGradientPaintW) paint).getGradient(context)); currentPaint = new GGradientPaintW((GGradientPaintW) paint); color = null; } else if (paint instanceof GTexturePaintW) { try {// bug in Firefox // https://groups.google.com/forum/#!msg/craftyjs/3qRwn_cW1gs/DdPTaCD81ikJ // NS_ERROR_NOT_AVAILABLE: Component is not available // https://bugzilla.mozilla.org/show_bug.cgi?id=574330 final GBufferedImageW bi = ((GTexturePaintW) paint).getImg(); CanvasPattern ptr; if (bi.hasCanvas()) { currentPaint = new GTexturePaintW((GTexturePaintW) paint); ptr = context.createPattern(bi.getCanvas() .getCanvasElement(), Repetition.REPEAT); context.setFillStyle(ptr); color = null; } else if (bi.isLoaded()) { currentPaint = new GTexturePaintW((GTexturePaintW) paint); ptr = context.createPattern(bi.getImageElement(), Repetition.REPEAT); context.setFillStyle(ptr); color = null; } else { ImageWrapper.nativeon(bi.getImageElement(), "load", new ImageLoadCallback() { @Override public void onLoad() { currentPaint = new GTexturePaintW( (GTexturePaintW) paint); CanvasPattern ptr1 = context.createPattern( bi.getImageElement(), Repetition.REPEAT); context.setFillStyle(ptr1); color = null; } }); } } catch (Throwable e) { Log.error(e.getMessage()); } } else { Log.error("unknown paint type"); } } @Override public void setStroke(GBasicStroke stroke) { if (stroke != null) { context.setLineWidth(((GBasicStrokeW) stroke).getLineWidth()); context.setLineCap(((GBasicStrokeW) stroke).getEndCapString()); context.setLineJoin(((GBasicStrokeW) stroke).getLineJoinString()); double[] dasharr = ((GBasicStrokeW) stroke).getDashArray(); if (dasharr != null) { jsarrn = JavaScriptObject.createArray().cast(); jsarrn.setLength(dasharr.length); for (int i = 0; i < dasharr.length; i++) { jsarrn.set(i, dasharr[i]); } setStrokeDash(context, jsarrn); } else { setStrokeDash(context, null); } dash_array = dasharr; } } private boolean nativeDashUsed = false; private int canvasWidth; private int canvasHeight; public native void setStrokeDash(Context2d ctx, JsArrayNumber dasharray) /*-{ if (dasharray === undefined || dasharray === null) { dasharray = []; } if (typeof ctx.setLineDash === 'function') { ctx.setLineDash(dasharray); this.@org.geogebra.web.html5.awt.GGraphics2DW::nativeDashUsed = true; } else if (typeof ctx.mozDash !== 'undefined') { ctx.mozDash = dasharray; this.@org.geogebra.web.html5.awt.GGraphics2DW::nativeDashUsed = true; } else if (typeof ctx.webkitLineDash !== 'undefined') { ctx.webkitLineDash = dasharray; this.@org.geogebra.web.html5.awt.GGraphics2DW::nativeDashUsed = true; } }-*/; @Override public void setRenderingHint(int hintKey, int hintValue) { // nothing to do } @Override public void translate(double tx, double ty) { context.translate2(tx, ty); } @Override public void scale(double sx, double sy) { context.scale2(sx, sy); } @Override public void transform(GAffineTransform Tx) { context.transform2(Tx.getScaleX(), Tx.getShearY(), Tx.getShearX(), Tx.getScaleY(), ((AffineTransform) Tx).getTranslateX(), ((AffineTransform) Tx).getTranslateY()); } private void setTransform(GAffineTransform Tx) { context.setDevicePixelRatio(devicePixelRatio); context.setTransform2(devicePixelRatio * Tx.getScaleX(), devicePixelRatio * Tx.getShearY(), devicePixelRatio * Tx.getShearX(), devicePixelRatio * Tx.getScaleY(), devicePixelRatio * ((AffineTransform) Tx).getTranslateX(), devicePixelRatio * ((AffineTransform) Tx).getTranslateY()); } @Override public GComposite getComposite() { return new GAlphaCompositeW(context.getGlobalAlpha()); // context.save(); // //just to not return null; // return new AlphaComposite(0, 0) { // }; } @Override public GColor getBackground() { return GColor.WHITE; } @Override public GBasicStroke getStroke() { return new GBasicStrokeW(context.getLineWidth(), GBasicStrokeW.getCap(context.getLineCap()), GBasicStrokeW.getJoin(context.getLineJoin()), 0, dash_array); } @Override public GFontRenderContext getFontRenderContext() { return new GFontRenderContextW(context); } @Override public GColor getColor() { return color; } @Override public GFontW getFont() { return currentFont; } private int physicalPX(int logicalPX) { return (int) (logicalPX * devicePixelRatio); } public void setCoordinateSpaceSize(int width, int height) { canvas.setCoordinateSpaceWidth(physicalPX(width)); canvas.setCoordinateSpaceHeight(physicalPX(height)); context.resetTransform(devicePixelRatio); setWidth(width); setHeight(height); this.updateCanvasColor(); } public void setCoordinateSpaceSizeNoTransformNoColor(int width, int height) { canvas.setCoordinateSpaceWidth(physicalPX(width)); canvas.setCoordinateSpaceHeight(physicalPX(height)); setWidth(width); setHeight(height); } public int getOffsetWidth() { int width = canvas.getOffsetWidth(); return width == 0 ? canvasWidth : width; } public int getOffsetHeight() { int height = canvas.getOffsetHeight(); return height == 0 ? canvasHeight : height; } public int getCoordinateSpaceWidth() { return canvas.getCoordinateSpaceWidth(); } public int getCoordinateSpaceHeight() { return canvas.getCoordinateSpaceHeight(); } public int getAbsoluteTop() { return canvas.getAbsoluteTop(); } public int getAbsoluteLeft() { return canvas.getAbsoluteLeft(); } @Override public void setFont(GFont font) { if (font instanceof GFontW) { currentFont = (GFontW) font; // TODO: pass other parameters here as well try { context.setFont(currentFont.getFullFontString()); } catch (Throwable t) { Log.error("problem setting font: " + currentFont.getFullFontString()); } } } @Override public void setColor(GColor fillColor) { // checking for the same color here speeds up axis drawing by 25% if (fillColor.equals(color)) { return; } // but it seems that setColor is not only for setting "color", // but also for setFillStyle and setStrokeStyle, // and it seems that this is necessary to run, // until a better solution is found - see ticket #4291 this.color = fillColor; updateCanvasColor(); this.currentPaint = fillColor; } @Override public void updateCanvasColor() { if (color == null || context == null) { return; } String colorStr = "rgba(" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + "," + (color.getAlpha() / 255d) + ")"; context.setStrokeStyle(colorStr); context.setFillStyle(colorStr); } @Override public void fillRect(int x, int y, int w, int h) { context.fillRect(x, y, w, h); } @Override public void clearRect(int x, int y, int w, int h) { context.saveTransform(); context.setTransform2(1, 0, 0, 1, 0, 0); context.clearRect(x, y, w, h); context.restoreTransform(); } @Override public void drawLine(int x1, int y1, int x2, int y2) { /* * TODO: there is some differences between the result of * geogebra.awt.Graphics.drawLine(...) function. Here is an attempt to * make longer the vertical and horizontal lines: * * int x_1 = Math.min(x1,x2); int y_1 = Math.min(y1,y2); int x_2 = * Math.max(x1,x2); int y_2 = Math.max(y1,y2); * * if(x1==x2){ y_1--; y_2++; } else if(y1==y2){ x_1--; x_2++; } * * context.beginPath(); context.moveTo(x_1, y_1); context.lineTo(x_2, * y_2); context.closePath(); context.stroke(); */ context.beginPath(); context.moveTo(x1, y1); context.lineTo(x2, y2); context.closePath(); context.stroke(); } @Override public void setClip(GShape shape) { if (shape == null) { Log.warn("Set clip should not be called with null, use resetClip instead"); resetClip(); return; } Shape shape2 = (Shape) shape; doDrawShape(shape2, false); // we should call this only if no clip was set or just after another // clip to overwrite // in this case we don't want to double-clip something so let's // restore the context context.restoreTransform(); context.saveTransform(); context.clip(); } @Override public void draw(GShape shape) { if (shape == null) { Log.error("Error in EuclidianView.draw"); return; } if (shape instanceof GeneralPathClipped) { doDrawShape((Shape) ((GeneralPathClipped) shape).getGeneralPath(), true); } else { doDrawShape((Shape) shape, true); } context.stroke(); } @Override public void fill(GShape gshape) { if (gshape == null) { Log.error("Error in EuclidianView.draw"); return; } Shape shape; if (gshape instanceof GeneralPathClipped) { shape = (Shape) ((GeneralPathClipped) gshape).getGeneralPath(); } else { shape = (Shape) gshape; } doDrawShape(shape, false); /* * App.debug((shape instanceof GeneralPath)+""); App.debug((shape * instanceof GeneralPathClipped)+""); * App.debug((shape.getClass().toString())+""); */ // default winding rule changed for ggb50 (for Polygons) #3983 if (shape instanceof GeneralPath) { GeneralPath gp = (GeneralPath) shape; int rule = gp.getWindingRule(); if (rule == Path2D.WIND_EVEN_ODD) { context.fill("evenodd"); } else { // context.fill("") differs between browsers context.fill(); } } else { context.fill(); } } @Override public void drawRect(int x, int y, int width, int height) { context.beginPath(); context.rect(x, y, width, height); context.stroke(); } @Override public void setClip(int x, int y, int width, int height) { double[] dash_array_save = dash_array; dash_array = null; GShape sh = AwtFactory.getPrototype().newRectangle(x, y, width, height); setClip(sh); dash_array = dash_array_save; /* * alternative: makes clipping bad, see #3212 * * //context.save(); context.beginPath(); context.moveTo(x, y); * context.lineTo(x + width, y); context.lineTo(x + width, y + height); * context.lineTo(x , y + height); context.lineTo(x , y); * //context.closePath(); context.clip(); */ } public void setWidth(int w) { this.canvasWidth = w; canvas.setWidth(w + "px"); } public void setHeight(int h) { this.canvasHeight = h; canvas.setHeight(h + "px"); } public void setPreferredSize(GDimension preferredSize) { setWidth(Math.max(0, preferredSize.getWidth())); setHeight(Math.max(0, preferredSize.getHeight())); // do not use getOffsetWidth here, // as it is prepared by the browser and not yet ready... // if preferredSize can be negative, have a check for it instead setCoordinateSpaceSize( (preferredSize.getWidth() >= 0) ? preferredSize.getWidth() : 0, (preferredSize.getHeight() >= 0) ? preferredSize.getHeight() : 0); } public Canvas getCanvas() { return this.canvas; } @Override public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { roundRect(x, y, width, height, arcHeight - arcHeight / 2); context.stroke(); } /** * Using arc, because arc to has buggy implementation in some browsers * * @param x * @param y * @param w * @param h * @param r */ private void roundRect(int x, int y, int w, int h, int r) { context.beginPath(); int ey = y + h; int ex = x + w; double r2d = Math.PI / 180; context.moveTo(x + r, y); context.lineTo(ex - r, y); context.arc(ex - r, y + r, r, r2d * 270, r2d * 360, false); context.lineTo(ex, ey - r); context.arc(ex - r, ey - r, r, r2d * 0, r2d * 90, false); context.lineTo(x + r, ey); context.arc(x + r, ey - r, r, r2d * 90, r2d * 180, false); context.lineTo(x, y + r); context.arc(x + r, y + r, r, r2d * 180, r2d * 270, false); context.closePath(); } @Override public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { roundRect(x, y, width, height, arcHeight - arcHeight / 2); context.fill("evenodd"); } private native void drawStraightDashedLine(double fromX, double fromY, double toX, double toY, JsArrayNumber pattern, Context2d ctx, boolean odd) /*-{ // Our growth rate for our line can be one of the following: // (+,+), (+,-), (-,+), (-,-) // Because of this, our algorithm needs to understand if the x-coord and // y-coord should be getting smaller or larger and properly cap the values // based on (x,y). // make sure we don't get an infinite loop drawing eg //y = -7.85046229341888E-17x var EPSILON = 0.00000001; var lt = function(a, b) { return a <= b + EPSILON; }; var gt = function(a, b) { return a >= b - EPSILON; }; var capmin = function(a, b) { return $wnd.Math.min(a, b); }; var capmax = function(a, b) { return $wnd.Math.max(a, b); }; var checkX = { thereYet : gt, cap : capmin }; var checkY = { thereYet : gt, cap : capmin }; if (fromY - toY > 0) { checkY.thereYet = lt; checkY.cap = capmax; } if (fromX - toX > 0) { checkX.thereYet = lt; checkX.cap = capmax; } //ctx.moveTo(fromX, fromY); var offsetX = fromX; var offsetY = fromY; var idx = 0, dash = true; var ang = $wnd.Math.atan2(toY - fromY, toX - fromX); var cos = $wnd.Math.cos(ang); var sin = $wnd.Math.sin(ang); while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) { var len = pattern[idx]; offsetX = checkX.cap(toX, offsetX + (cos * len)); offsetY = checkY.cap(toY, offsetY + (sin * len)); if (dash) ctx.lineTo(odd ? $wnd.Math.floor(offsetX) + 0.5 : $wnd.Math .round(offsetX), odd ? $wnd.Math.floor(offsetY) + 0.5 : Math.round(offsetY)); else ctx.moveTo(odd ? $wnd.Math.floor(offsetX) + 0.5 : $wnd.Math .round(offsetX), odd ? $wnd.Math.floor(offsetY) + 0.5 : Math.round(offsetY)); idx = (idx + 1) % pattern.length; dash = !dash; } }-*/; private native void drawDashedLine(double fromX, double fromY, double toX, double toY, JsArrayNumber pattern, Context2d ctx) /*-{ // Our growth rate for our line can be one of the following: // (+,+), (+,-), (-,+), (-,-) // Because of this, our algorithm needs to understand if the x-coord and // y-coord should be getting smaller or larger and properly cap the values // based on (x,y). // make sure we don't get an infinite loop drawing eg //y = -7.85046229341888E-17x var EPSILON = 0.00000001; var lt = function(a, b) { return a <= b + EPSILON; }; var gt = function(a, b) { return a >= b - EPSILON; }; var capmin = function(a, b) { return $wnd.Math.min(a, b); }; var capmax = function(a, b) { return $wnd.Math.max(a, b); }; var checkX = { thereYet : gt, cap : capmin }; var checkY = { thereYet : gt, cap : capmin }; if (fromY - toY > 0) { checkY.thereYet = lt; checkY.cap = capmax; } if (fromX - toX > 0) { checkX.thereYet = lt; checkX.cap = capmax; } //ctx.moveTo(fromX, fromY); var offsetX = fromX; var offsetY = fromY; var idx = 0, dash = true; var ang = $wnd.Math.atan2(toY - fromY, toX - fromX); var cos = $wnd.Math.cos(ang); var sin = $wnd.Math.sin(ang); while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) { var len = pattern[idx]; offsetX = checkX.cap(toX, offsetX + (cos * len)); offsetY = checkY.cap(toY, offsetY + (sin * len)); if (dash) ctx.lineTo(offsetX, offsetY); else ctx.moveTo(offsetX, offsetY); idx = (idx + 1) % pattern.length; dash = !dash; } }-*/; public ImageData getImageData(int x, int y, int width, int height) { return context.getImageData(x, y, width, height); } /** * @param data * Imagedata to put on the canvas */ public void putImageData(ImageData data, double x, double y) { context.putImageData(data, x, y); } @Override public void setAntialiasing() { // not needed } @Override public void setTransparent() { setComposite(GAlphaCompositeW.Src); } public void fillWith(GColor color) { this.setColor(color); this.fillRect(0, 0, getOffsetWidth(), getOffsetHeight()); } private boolean lastDebugOk = false; private boolean lastDebugException = false; public void debug() { // TODO Auto-generated method stub String physical = context.getFillStyle().toString().toUpperCase(); String logical = "null"; if (color != null) { logical = color.getAlpha() < 255 ? "RGBA(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ", 0." + (int) (1000000 * color.getAlpha() / 255d) + ")" : "#" + StringUtil.toHexString(color).toUpperCase(); } if (color == null && physical.contains("OBJ")) { System.out.println(hashCode() + ": not colors"); lastDebugOk = false; lastDebugException = false; } else if (!logical.equals(physical)) { if (!lastDebugException) { Log.printStacktrace( hashCode() + ": " + logical.replace(".0", "") + " / " + physical.replace(".0", "")); } lastDebugOk = false; lastDebugException = true; } else if (!lastDebugOk) { System.out.println(hashCode() + ": ok"); lastDebugOk = true; lastDebugException = false; } } public double getScale() { return devicePixelRatio; } public JLMContext2d getContext() { return context; } @Override public Object setInterpolationHint(boolean needsInterpolationRenderingHint) { this.setImageInterpolation(needsInterpolationRenderingHint); return null; } @Override public void resetInterpolationHint(Object oldInterpolationHint) { this.setImageInterpolation(true); this.color = null; } @Override public void drawImage(GBufferedImage img, int x, int y) { GBufferedImageW bi = (GBufferedImageW) img; if (bi == null) { return; } try { if (bi.hasCanvas()) { if (bi.getCanvas().getCoordinateSpaceWidth() > 0) { context.drawImage(bi.getCanvas().getCanvasElement(), 0, 0, bi.getCanvas().getCoordinateSpaceWidth(), bi.getCanvas().getCoordinateSpaceHeight(), x, y, this.getOffsetWidth(), this.getOffsetHeight()); } // zero width canvas throws error in FF } else { context.drawImage(bi.getImageElement(), 0, 0, bi.getWidth(), bi.getHeight(), x, y, this.getOffsetWidth(), this.getOffsetHeight()); } } catch (Exception e) { Log.error("error in context.drawImage.4 method"); } } public void drawImage(ImageElement img, int x, int y) { try { context.drawImage(img, x, y); } catch (Exception e) { Log.error("error in context.drawImage.3 method"); } } @Override public void drawImage(MyImage img, int x, int y) { context.drawImage(((MyImageW) img).getImage(), x, y); } @Override public void saveTransform() { context.saveTransform(); } @Override public void restoreTransform() { context.restoreTransform(); } public boolean setAltText(String altStr) { boolean ret = !(canvas.getElement().getInnerText() + "").equals(altStr); canvas.getElement().setInnerText(altStr); return ret; } public String getAltText() { return canvas.getElement().getInnerText(); } public void forceResize() { int width = canvas.getOffsetWidth(); int height = canvas.getOffsetHeight(); if (width > 0) { setCoordinateSpaceSize(width - 1, height); setCoordinateSpaceSize(width, height); } } @Override public void resetClip() { context.restoreTransform(); } }