/**
* This file is part of the ReTeX library - https://github.com/himamis/ReTeX
*
* Copyright (C) 2015 Balazs Bencze
*
* 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 2 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.
*
* A copy of the GNU General Public License can be found in the file
* LICENSE.txt provided with the source distribution of this program (see
* the META-INF directory in the source jar). This license can also be
* found on the GNU website at http://www.gnu.org/licenses/gpl.html.
*
* If you did not receive a copy of the GNU General Public License along
* with this program, contact the lead developer, or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* Linking this library statically or dynamically with other modules
* is making a combined work based on this library. Thus, the terms
* and conditions of the GNU General Public License cover the whole
* combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce
* an executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under terms
* of your choice, provided that you also meet, for each linked independent
* module, the terms and conditions of the license of that module.
* An independent module is a module which is not derived from or based
* on this library. If you modify this library, you may extend this exception
* to your version of the library, but you are not obliged to do so.
* If you do not wish to do so, delete this exception statement from your
* version.
*
*/
package com.himamis.retex.renderer.web.graphics;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.CanvasElement;
import com.himamis.retex.renderer.share.platform.font.Font;
import com.himamis.retex.renderer.share.platform.font.FontLoader;
import com.himamis.retex.renderer.share.platform.font.FontRenderContext;
import com.himamis.retex.renderer.share.platform.geom.Line2D;
import com.himamis.retex.renderer.share.platform.geom.Rectangle2D;
import com.himamis.retex.renderer.share.platform.geom.RoundRectangle2D;
import com.himamis.retex.renderer.share.platform.graphics.Color;
import com.himamis.retex.renderer.share.platform.graphics.Graphics2DInterface;
import com.himamis.retex.renderer.share.platform.graphics.Image;
import com.himamis.retex.renderer.share.platform.graphics.Stroke;
import com.himamis.retex.renderer.share.platform.graphics.Transform;
import com.himamis.retex.renderer.web.DrawingFinishedCallback;
import com.himamis.retex.renderer.web.font.AsyncLoadedFont;
import com.himamis.retex.renderer.web.font.AsyncLoadedFont.FontLoadCallback;
import com.himamis.retex.renderer.web.font.DefaultFont;
import com.himamis.retex.renderer.web.font.FontW;
import com.himamis.retex.renderer.web.font.FontWrapper;
public class Graphics2DW implements Graphics2DInterface {
private JLMContext2d context;
private BasicStrokeW basicStroke;
private ColorW color;
private FontW font;
private DrawingFinishedCallback drawingFinishedCallback;
public Graphics2DW(Context2d context) {
this.context = (JLMContext2d) context;
this.context.initTransform();
initBasicStroke();
initColor();
initFont();
}
public Graphics2DW(Canvas canvas) {
this(canvas.getContext2d());
}
private void initBasicStroke() {
basicStroke = new BasicStrokeW(context.getLineWidth(),
context.getLineCap(), context.getLineJoin(),
context.getMiterLimit());
}
private void initColor() {
color = new ColorW(0, 0, 0);
context.setStrokeStyle(color.getCssColor());
}
private void initFont() {
font = new DefaultFont(context.getFont(), Font.PLAIN,
(int) Math.round(FontLoader.PIXELS_PER_POINT));
}
public Context2d getContext() {
return context;
}
@Override
public void setStroke(Stroke stroke) {
basicStroke = (BasicStrokeW) stroke;
context.setLineCap(basicStroke.getJSLineCap());
context.setLineJoin(basicStroke.getJSLineJoin());
context.setLineWidth(basicStroke.getWidth());
context.setMiterLimit(basicStroke.getMiterLimit());
}
@Override
public Stroke getStroke() {
return basicStroke;
}
@Override
public void setColor(Color color) {
this.color = (ColorW) color;
context.setStrokeStyle(this.color.getCssColor());
context.setFillStyle(this.color.getCssColor());
}
@Override
public ColorW getColor() {
return color;
}
@Override
public TransformW getTransform() {
return new TransformW(context.getScaleX(),context.getShearY(),context.getShearX(),context.getScaleY(),context.getTranslateX(),context.getTranslateY());
}
@Override
public void saveTransformation() {
context.saveTransform();
}
@Override
public void restoreTransformation() {
context.restoreTransform();
// these values are also restored on context.restore()
// so we have to re-set them
setFont(font);
setColor(color);
setStroke(basicStroke);
}
@Override
public FontW getFont() {
return font;
}
@Override
public void setFont(Font font) {
this.font = (FontW) font;
context.setFont(this.font.getCssFontString());
}
// Consider http://jsfiddle.net/9bMPD/357/ for rectangles!!
@Override
public void fillRect(int x, int y, int width, int height) {
context.fillRect(x, y, width, height);
}
@Override
public void fill(Rectangle2D rectangle) {
context.fillRect(rectangle.getX(), rectangle.getY(),
rectangle.getWidth(), rectangle.getHeight());
}
@Override
public void draw(Rectangle2D rectangle) {
context.rect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(),
rectangle.getHeight());
context.stroke();
}
@Override
public void draw(RoundRectangle2D rectangle) {
double x = rectangle.getX();
double y = rectangle.getY();
double w = rectangle.getWidth();
double h = rectangle.getHeight();
double arcW = rectangle.getArcW();
double arcH = rectangle.getArcH();
if (Math.abs(arcW - arcH) < 0.01) {
double radius = arcW / 2.0;
drawRoundRectangle(x, y, w, h, radius);
} else {
throw new UnsupportedOperationException(
"ArcW and ArcH must be equal.");
}
}
private void drawRoundRectangle(double x, double y, double width,
double height, double radius) {
context.beginPath();
context.moveTo(x + radius, y);
context.lineTo(x + width - radius, y);
context.quadraticCurveTo(x + width, y, x + width, y + radius);
context.lineTo(x + width, y + height - radius);
context.quadraticCurveTo(x + width, y + height, x + width - radius, y
+ height);
context.lineTo(x + radius, y + height);
context.quadraticCurveTo(x, y + height, x, y + height - radius);
context.lineTo(x, y + radius);
context.quadraticCurveTo(x, y, x + radius, y);
context.closePath();
context.stroke();
}
@Override
public void draw(Line2D line) {
context.beginPath();
context.moveTo(line.getX1(), line.getY1());
context.lineTo(line.getX2(), line.getY2());
context.stroke();
}
@Override
public void drawChars(char[] data, int offset, int length, int x, int y) {
if (length > 1) {
throw new UnsupportedOperationException(
"Cannot draw multiple chars");
}
String string = String.valueOf(data, offset, length);
drawText(string, x, y);
}
private static class FontDrawContext {
private Graphics2DW graphics;
private String text;
private int x;
private int y;
private TransformW transformCopy;
private FontW font;
private ColorW color;
public FontDrawContext(Graphics2DW graphics, String text, int x, int y) {
this.graphics = graphics;
this.text = text;
this.x = x;
this.y = y;
transformCopy = graphics.getTransform();
font = graphics.getFont();
color = graphics.getColor();
}
public void doDraw() {
FontW oldFont = graphics.getFont();
ColorW oldColor = graphics.getColor();
graphics.saveTransformation();
graphics.setFont(font);
graphics.setColor(color);
// TRAC-5353
// bad
// graphics.transform(transformCopy);
// good
graphics.setTransform(transformCopy);
graphics.fillTextInternal(text, x, y);
graphics.restoreTransformation();
graphics.setFont(oldFont);
graphics.setColor(oldColor);
}
}
private int charDrawingRequests = 0;
public void drawText(String text, int x, int y) {
if (!font.isLoaded()) {
final FontDrawContext fdc = new FontDrawContext(this, text, x, y);
// Javascript callback is needed after the drawing has finished.
// This would be straightforward in a synchronous library, but that
// is not the case here.
// This is the only asynchronous part in the JLaTeXMath API. (for
// now)
// A drawing request is created everytime a font is needed and is
// not loaded.
// The drawing request is executed after the font has been loaded.
// Count all the drawing requests which will be processed after the
// main drawing process has finished. After that in all the font
// callbacks (error or loaded) check if there are any requests
// waiting to be executed. If there aren't any, drawing has finished.
charDrawingRequests += 1;
font.addFontLoadedCallback(new FontLoadCallback() {
@Override
public void onFontLoaded(AsyncLoadedFont font) {
fdc.doDraw();
charDrawingRequests -= 1;
maybeNotifyDrawingFinishedCallback();
}
@Override
public void onFontError(AsyncLoadedFont font) {
GWT.log("Error loading font " + font);
charDrawingRequests -= 1;
maybeNotifyDrawingFinishedCallback();
}
});
} else {
fillTextInternal(text, x, y);
}
}
public void setDrawingFinishedCallback(DrawingFinishedCallback drawingFinishedCallback) {
this.drawingFinishedCallback = drawingFinishedCallback;
}
public void maybeNotifyDrawingFinishedCallback() {
if (!hasUnprocessedCharDrawingRequests()) {
notifyDrawingFinishedCallback();
}
}
private boolean hasUnprocessedCharDrawingRequests() {
return charDrawingRequests > 0;
}
private void notifyDrawingFinishedCallback() {
if (drawingFinishedCallback != null) {
drawingFinishedCallback.onDrawingFinished();
}
}
protected void fillTextInternal(String text, int x, int y) {
FontWrapper fontWrapper = font.getFontWrapper();
fontWrapper.drawGlyph(text, x, y, font.getSize(), context);
}
@Override
public void drawArc(int x, int y, int width, int height, int startAngle,
int arcAngle) {
doArcPath(x, y, width, height, startAngle, arcAngle);
context.stroke();
}
@Override
public void fillArc(int x, int y, int width, int height, int startAngle,
int arcAngle) {
doArcPath(x, y, width, height, startAngle, arcAngle);
context.fill();
}
private void doArcPath(int x, int y, int width, int height, int startAngle,
int arcAngle) {
context.saveTransform();
context.beginPath();
context.translate2(x, y);
context.scale2(width / 2.0, height / 2.0);
context.arc(1, 1, 1, startAngle, arcAngle);
context.restoreTransform();
}
@Override
public void translate(double x, double y) {
context.translate2(x, y);
}
@Override
public void scale(double x, double y) {
context.scale2(x, y);
}
@Override
public void rotate(double theta, double x, double y) {
translate(x, y);
rotate(theta);
translate(-x, -y);
}
@Override
public void rotate(double theta) {
context.rotate2(theta);
}
@Override
public void drawImage(Image image, int x, int y) {
ImageW impl = (ImageW) image;
Canvas imageCanvas = impl.getCanvas();
CanvasElement canvasElement = imageCanvas.getCanvasElement();
context.drawImage(canvasElement, x, y);
}
@Override
public void drawImage(Image image, Transform transform) {
context.saveTransform(transform.getScaleX(), transform.getShearY(),
transform.getShearX(), transform.getScaleY(),
transform.getTranslateX(), transform.getTranslateY());
transform((TransformW) transform);
drawImage(image, 0, 0);
context.restoreTransform();
}
/**
* Applies the transformation matrix to the context. Please ensure one call
* to graphics.save() before this method and graphics.restore() after this
* method.
*
* @param transform
* transformation matrix
*/
protected void transform(TransformW transform) {
context.transform(transform.getScaleX(), transform.getShearY(),
transform.getShearX(), transform.getScaleY(),
transform.getTranslateX(), transform.getTranslateY());
}
/**
* Sets the transformation matrix. Please ensure one call to graphics.save()
* before this method and graphics.restore() after this method.
*
* @param transform
* transformation matrix
*/
protected void setTransform(TransformW transform) {
double dp = context.getDevicePixelRatio();
context.setTransform2(transform.getScaleX() * dp, transform.getShearY() * dp,
transform.getShearX() * dp, transform.getScaleY() * dp,
transform.getTranslateX() * dp, transform.getTranslateY() * dp);
}
@Override
public FontRenderContext getFontRenderContext() {
return new FontRenderContextW(this);
}
@Override
public void setRenderingHint(int key, int value) {
// TODO Auto-generated method stub
}
@Override
public int getRenderingHint(int key) {
// TODO Auto-generated method stub
return 0;
}
@Override
public void dispose() {
// NO-OP
}
}