package com.sun.javafx.webkit.prism;
import com.sun.glass.ui.Screen;
import com.sun.javafx.font.FontStrike;
import com.sun.javafx.font.Metrics;
import com.sun.javafx.font.PGFont;
import com.sun.javafx.geom.*;
import com.sun.javafx.geom.transform.Affine2D;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.logging.PlatformLogger;
import com.sun.javafx.logging.PlatformLogger.Level;
import com.sun.javafx.scene.text.GlyphList;
import com.sun.javafx.scene.text.TextLayout;
import com.sun.javafx.sg.prism.*;
import com.sun.javafx.text.TextRun;
import com.sun.prism.*;
import com.sun.prism.paint.Color;
import com.sun.prism.paint.Gradient;
import com.sun.prism.paint.ImagePattern;
import com.sun.prism.paint.Paint;
import com.sun.scenario.effect.*;
import com.sun.scenario.effect.impl.prism.PrDrawable;
import com.sun.scenario.effect.impl.prism.PrEffectHelper;
import com.sun.scenario.effect.impl.prism.PrFilterContext;
import com.sun.webkit.graphics.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import static com.sun.scenario.effect.Blend.Mode.*;
import com.sun.scenario.effect.impl.Renderer;
import com.sun.scenario.effect.impl.prism.PrRenderer;
class WCGraphicsPrismContext extends WCGraphicsContext {
public enum Type {
PRIMARY,
DEDICATED
}
private final static PlatformLogger log =
PlatformLogger.getLogger(WCGraphicsPrismContext.class.getName());
private final static boolean DEBUG_DRAW_CLIP_SHAPE = Boolean.valueOf(
AccessController.doPrivileged((PrivilegedAction<String>) () ->
System.getProperty("com.sun.webkit.debugDrawClipShape", "false")));
Graphics baseGraphics;
private BaseTransform baseTransform;
private final List<ContextState> states = new ArrayList<ContextState>();
private ContextState state = new ContextState();
private Graphics cachedGraphics = null;
private int fontSmoothingType;
private boolean isRootLayerValid = false;
WCGraphicsPrismContext(Graphics g) {
state.setClip(g.getClipRect());
state.setAlpha(g.getExtraAlpha());
baseGraphics = g;
initBaseTransform(g.getTransformNoClone());
}
WCGraphicsPrismContext() {
}
public Type type() {
return Type.PRIMARY;
}
final void initBaseTransform(BaseTransform t) {
baseTransform = new Affine3D(t);
state.setTransform((Affine3D)baseTransform);
}
private void resetCachedGraphics() {
cachedGraphics = null;
}
@Override
public Object getPlatformGraphics() {
return getGraphics(false);
}
Graphics getGraphics(boolean checkClip) {
if (cachedGraphics == null) {
Layer l = state.getLayerNoClone();
cachedGraphics = (l != null)
? l.getGraphics()
: baseGraphics;
state.apply(cachedGraphics);
if (log.isLoggable(Level.FINE)) {
log.fine("getPlatformGraphics for " + this + " : " +
cachedGraphics);
}
}
Rectangle clip = cachedGraphics.getClipRectNoClone();
return (checkClip && clip!=null && clip.isEmpty())
? null
: cachedGraphics;
}
public void saveState()
{
state.markAsRestorePoint();
saveStateInternal();
}
private void saveStateInternal()
{
states.add(state);
state = state.clone();
}
private void startNewLayer(Layer layer) {
saveStateInternal();
Rectangle clip = state.getClipNoClone();
Affine3D newTr = new Affine3D(BaseTransform.getTranslateInstance(
-clip.x,
-clip.y));
newTr.concatenate(state.getTransformNoClone());
clip.x = 0;
clip.y = 0;
Graphics g = getGraphics(true);
if (g != null && g != baseGraphics) {
layer.init(g);
}
state.setTransform(newTr);
state.setLayer(layer);
resetCachedGraphics();
}
private void renderLayer(final Layer layer) {
WCTransform cur = getTransform();
setTransform(new WCTransform(
1.0, 0.0,
0.0, 1.0,
layer.getX(), layer.getY()));
Graphics g = getGraphics(true);
if (g != null) {
layer.render(g);
}
setTransform(cur);
}
private void restoreStateInternal() {
int size = states.size();
if (size == 0) {
assert false: "Unbalanced restoreState";
return;
}
Layer layer = state.getLayerNoClone();
state = states.remove(size - 1);
if (layer != state.getLayerNoClone()) {
renderLayer(layer);
layer.dispose();
if (log.isLoggable(Level.FINE)) {
log.fine("Popped layer " + layer);
}
} else {
resetCachedGraphics();
}
}
public void restoreState()
{
log.fine("restoring state");
do {
restoreStateInternal();
} while ( !state.isRestorePoint() );
}
private void flushAllLayers() {
if (state == null) {
return;
}
if (isRootLayerValid) {
log.fine("FlushAllLayers: root layer is valid, skipping");
return;
}
if (log.isLoggable(Level.FINE)) {
log.fine("FlushAllLayers");
}
ContextState currentState = state;
for (int i = states.size() - 1; i >=0; i--) {
Layer layer = state.getLayerNoClone();
state = states.get(i);
if (layer != state.getLayerNoClone()) {
renderLayer(layer);
} else {
resetCachedGraphics();
}
}
Layer layer = state.getLayerNoClone();
if (layer != null) {
renderLayer(layer);
}
state = currentState;
isRootLayerValid = true;
}
public void dispose() {
if (!states.isEmpty()) {
log.fine("Unbalanced saveState/restoreState");
}
for (ContextState state: states) {
if (state.getLayerNoClone() != null) {
state.getLayerNoClone().dispose();
}
}
states.clear();
if (state != null && state.getLayerNoClone() != null) {
state.getLayerNoClone().dispose();
}
state = null;
}
public void setClip(WCPath path, boolean isOut) {
Affine3D tr = new Affine3D(state.getTransformNoClone());
path.transform(
tr.getMxx(), tr.getMyx(),
tr.getMxy(), tr.getMyy(),
tr.getMxt(), tr.getMyt());
if (!isOut) {
WCRectangle pathBounds = path.getBounds();
int pixelX = (int) Math.floor(pathBounds.getX());
int pixelY = (int) Math.floor(pathBounds.getY());
int pixelW = (int) Math.ceil(pathBounds.getMaxX()) - pixelX;
int pixelH = (int) Math.ceil(pathBounds.getMaxY()) - pixelY;
state.clip(new Rectangle(pixelX, pixelY, pixelW, pixelH));
}
Rectangle clip = state.getClipNoClone();
if (isOut) {
path.addRect(clip.x, clip.y, clip.width, clip.height);
}
path.translate(-clip.x, -clip.y);
Layer layer = new ClipLayer(
getGraphics(false), clip, path, type() == Type.DEDICATED);
startNewLayer(layer);
if (log.isLoggable(Level.FINE)) {
log.fine("setClip(WCPath " + path.getID() + ")");
log.fine("Pushed layer " + layer);
}
}
private Rectangle transformClip(Rectangle localClip) {
if (localClip==null) {
return null;
}
float[] points = new float[] {
localClip.x, localClip.y,
localClip.x + localClip.width, localClip.y,
localClip.x, localClip.y + localClip.height,
localClip.x + localClip.width, localClip.y + localClip.height};
state.getTransformNoClone().transform(points, 0, points, 0, 4);
float minX = Math.min(
points[0], Math.min(
points[2], Math.min(
points[4], points[6])));
float maxX = Math.max(
points[0], Math.max(
points[2], Math.max(
points[4], points[6])));
float minY = Math.min(
points[1], Math.min(
points[3], Math.min(
points[5], points[7])));
float maxY = Math.max(
points[1], Math.max(
points[3], Math.max(
points[5], points[7])));
return new Rectangle(new RectBounds(minX, minY, maxX, maxY));
}
private void setClip(Rectangle shape) {
Affine3D tr = state.getTransformNoClone();
if (tr.getMxy() == 0 && tr.getMxz() == 0
&& tr.getMyx() == 0 && tr.getMyz() == 0
&& tr.getMzx() == 0 && tr.getMzy() == 0) {
state.clip(transformClip(shape));
if (log.isLoggable(Level.FINE)) {
log.fine("setClip({0})", shape);
}
if (DEBUG_DRAW_CLIP_SHAPE) {
Rectangle rc = state.getClipNoClone();
if (rc != null && rc.width >= 2 && rc.height >= 2) {
WCTransform cur = getTransform();
setTransform(new WCTransform(
1.0, 0.0,
0.0, 1.0,
0.0, 0.0));
Graphics g2d = getGraphics(true);
if (g2d != null) {
float fbase = (float)Math.random();
g2d.setPaint(new Color(
fbase,
1f - fbase,
0.5f,
0.1f));
g2d.setStroke(new BasicStroke());
g2d.fillRect(rc.x, rc.y, rc.width, rc.height);
g2d.setPaint(new Color(
1f - fbase,
fbase,
0.5f,
1f));
g2d.drawRect(rc.x, rc.y, rc.width, rc.height);
}
setTransform(cur);
state.clip(new Rectangle(rc.x+1, rc.y+1, rc.width-2, rc.height-2));
}
}
if (cachedGraphics != null) {
cachedGraphics.setClipRect(state.getClipNoClone());
}
} else {
WCPath path = new WCPathImpl();
path.addRect(shape.x, shape.y, shape.width, shape.height);
setClip(path, false);
}
}
public void setClip(int cx, int cy, int cw, int ch) {
setClip(new Rectangle(cx, cy, cw, ch));
}
public void setClip(WCRectangle c) {
setClip(new Rectangle((int)c.getX(), (int)c.getY(),
(int)c.getWidth(), (int)c.getHeight()));
}
public WCRectangle getClip() {
Rectangle r = state.getClipNoClone();
return r == null ? null : new WCRectangle(r.x, r.y, r.width, r.height);
}
protected Rectangle getClipRectNoClone() {
return state.getClipNoClone();
}
protected Affine3D getTransformNoClone() {
return state.getTransformNoClone();
}
public void translate(float x, float y) {
if (log.isLoggable(Level.FINE)) {
log.fine("translate({0},{1})", new Object[] {x, y});
}
state.translate(x, y);
if (cachedGraphics != null) {
cachedGraphics.translate(x, y);
}
}
public void scale(float sx, float sy) {
if (log.isLoggable(Level.FINE)) {
log.fine("scale(" + sx + " " + sy + ")");
}
state.scale(sx, sy);
if (cachedGraphics != null) {
cachedGraphics.scale(sx, sy);
}
}
public void rotate(float radians) {
if (log.isLoggable(Level.FINE)) {
log.fine("rotate(" + radians + ")");
}
state.rotate(radians);
if (cachedGraphics != null) {
cachedGraphics.setTransform(state.getTransformNoClone());
}
}
protected boolean shouldRenderRect(float x, float y, float w, float h,
DropShadow shadow, BasicStroke stroke)
{
return true;
}
protected boolean shouldRenderShape(Shape shape, DropShadow shadow, BasicStroke stroke) {
return true;
}
protected boolean shouldCalculateIntersection() {
return false;
}
@Override
public void fillRect(final float x, final float y, final float w, final float h, final Integer rgba) {
if (log.isLoggable(Level.FINE)) {
String format = (rgba != null)
? "fillRect(%f, %f, %f, %f, 0x%x)"
: "fillRect(%f, %f, %f, %f, null)";
log.fine(String.format(format, x, y, w, h, rgba));
}
if (!shouldRenderRect(x, y, w, h, state.getShadowNoClone(), null)) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
Paint paint = (rgba != null) ? createColor(rgba) : state.getPaintNoClone();
DropShadow shadow = state.getShadowNoClone();
if (shadow != null) {
final NGRectangle node = new NGRectangle();
node.updateRectangle(x, y, w, h, 0, 0);
render(g, shadow, paint, null, node);
} else {
g.setPaint(paint);
g.fillRect(x, y, w, h);
}
}
}.paint();
}
@Override
public void fillRoundedRect(final float x, final float y, final float w, final float h,
final float topLeftW, final float topLeftH, final float topRightW, final float topRightH,
final float bottomLeftW, final float bottomLeftH, final float bottomRightW, final float bottomRightH,
final int rgba)
{
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("fillRoundedRect(%f, %f, %f, %f, "
+ "%f, %f, %f, %f, %f, %f, %f, %f, 0x%x)",
x, y, w, h, topLeftW, topLeftH, topRightW, topRightH,
bottomLeftW, bottomLeftH, bottomRightW, bottomRightH, rgba));
}
if (!shouldRenderRect(x, y, w, h, state.getShadowNoClone(), null)) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
float arcW = (topLeftW + topRightW + bottomLeftW + bottomRightW) / 2;
float arcH = (topLeftH + topRightH + bottomLeftH + bottomRightH) / 2;
Paint paint = createColor(rgba);
DropShadow shadow = state.getShadowNoClone();
if (shadow != null) {
final NGRectangle node = new NGRectangle();
node.updateRectangle(x, y, w, h, arcW, arcH);
render(g, shadow, paint, null, node);
} else {
g.setPaint(paint);
g.fillRoundRect(x, y, w, h, arcW, arcH);
}
}
}.paint();
}
@Override
public void clearRect(final float x, final float y, final float w, final float h) {
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("clearRect(%f, %f, %f, %f)", x, y, w, h));
}
if (shouldCalculateIntersection()) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
g.clearQuad(x, y, x + w, y + h);
}
}.paint();
}
@Override
public void setFillColor(int rgba) {
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("setFillColor(0x%x)", rgba));
}
state.setPaint(createColor(rgba));
}
@Override
public void setFillGradient(WCGradient gradient) {
if (log.isLoggable(Level.FINE)) {
log.fine("setFillGradient(" + gradient + ")");
}
state.setPaint((Gradient) gradient.getPlatformGradient());
}
@Override
public void setTextMode(boolean fill, boolean stroke, boolean clip) {
if (log.isLoggable(Level.FINE)) {
log.fine("setTextMode(fill:" + fill + ",stroke:" + stroke + ",clip:" + clip + ")");
}
state.setTextMode(fill, stroke, clip);
}
@Override
public void setFontSmoothingType(int fontSmoothingType) {
this.fontSmoothingType = fontSmoothingType;
}
@Override
public int getFontSmoothingType() {
return fontSmoothingType;
}
@Override
public void setStrokeStyle(int style) {
if (log.isLoggable(Level.FINE)) {
log.fine("setStrokeStyle({0})", style);
}
state.getStrokeNoClone().setStyle(style);
}
@Override
public void setStrokeColor(int rgba) {
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("setStrokeColor(0x%x)", rgba));
}
state.getStrokeNoClone().setPaint(createColor(rgba));
}
@Override
public void setStrokeWidth(float width) {
if (log.isLoggable(Level.FINE)) {
log.fine("setStrokeWidth({0})", new Object[] { width });
}
state.getStrokeNoClone().setThickness(width);
}
@Override
public void setStrokeGradient(WCGradient gradient) {
if (log.isLoggable(Level.FINE)) {
log.fine("setStrokeGradient(" + gradient + ")");
}
state.getStrokeNoClone().setPaint((Gradient) gradient.getPlatformGradient());
}
@Override
public void setLineDash(float offset, float... sizes) {
if (log.isLoggable(Level.FINE)) {
StringBuilder s = new StringBuilder("[");
for (int i=0; i < sizes.length; i++) {
s.append(sizes[i]).append(',');
}
s.append(']');
log.fine("setLineDash({0},{1}", new Object[] {offset, s});
}
state.getStrokeNoClone().setDashOffset(offset);
if (sizes != null) {
boolean allZero = true;
for (int i = 0; i < sizes.length; i++) {
if (sizes[i] != 0) {
allZero = false;
break;
}
}
if (allZero) {
sizes = null;
}
}
state.getStrokeNoClone().setDashSizes(sizes);
}
@Override
public void setLineCap(int lineCap) {
if (log.isLoggable(Level.FINE)) {
log.fine("setLineCap(" + lineCap + ")");
}
state.getStrokeNoClone().setLineCap(lineCap);
}
@Override
public void setLineJoin(int lineJoin) {
if (log.isLoggable(Level.FINE)) {
log.fine("setLineJoin(" + lineJoin + ")");
}
state.getStrokeNoClone().setLineJoin(lineJoin);
}
@Override
public void setMiterLimit(float miterLimit) {
if (log.isLoggable(Level.FINE)) {
log.fine("setMiterLimit(" + miterLimit + ")");
}
state.getStrokeNoClone().setMiterLimit(miterLimit);
}
@Override
public void setShadow(float dx, float dy, float blur, int rgba) {
if (log.isLoggable(Level.FINE)) {
String format = "setShadow(%f, %f, %f, 0x%x)";
log.fine(String.format(format, dx, dy, blur, rgba));
}
state.setShadow(createShadow(dx, dy, blur, rgba));
}
@Override
public void drawPolygon(final WCPath path, final boolean shouldAntialias) {
if (log.isLoggable(Level.FINE)) {
log.fine("drawPolygon({0})",
new Object[] {shouldAntialias});
}
if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(), null,
state.getStrokeNoClone().getPlatformStroke()))
{
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
Path2D p2d = (Path2D) path.getPlatformPath();
g.setPaint(state.getPaintNoClone());
g.fill(p2d);
if (state.getStrokeNoClone().apply(g)) {
g.draw(p2d);
}
}
}.paint();
}
@Override
public void drawLine(final int x0, final int y0, final int x1, final int y1) {
if (log.isLoggable(Level.FINE)) {
log.fine("drawLine({0}, {1}, {2}, {3})",
new Object[] {x0, y0, x1, y1});
}
Line2D line = new Line2D(x0, y0, x1, y1);
if (!shouldRenderShape(line, null, state.getStrokeNoClone().getPlatformStroke())) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
if (state.getStrokeNoClone().apply(g)) {
g.drawLine(x0, y0, x1, y1);
}
}
}.paint();
}
@Override
public void drawPattern(
final WCImage texture,
final WCRectangle srcRect,
final WCTransform patternTransform,
final WCPoint phase,
final WCRectangle destRect)
{
if (log.isLoggable(Level.FINE)) {
log.fine("drawPattern({0}, {1}, {2}, {3})",
new Object[] {destRect.getIntX(), destRect.getIntY(),
destRect.getIntWidth(),
destRect.getIntHeight()});
}
if (!shouldRenderRect(destRect.getX(), destRect.getY(),
destRect.getWidth(), destRect.getHeight(), null, null))
{
return;
}
if (texture != null) {
new Composite() {
@Override void doPaint(Graphics g) {
float adjustedX = phase.getX()
+ srcRect.getX() * (float) patternTransform.getMatrix()[0];
float adjustedY = phase.getY()
+ srcRect.getY() * (float) patternTransform.getMatrix()[3];
float scaledTileWidth =
srcRect.getWidth() * (float) patternTransform.getMatrix()[0];
float scaledTileHeight =
srcRect.getHeight() * (float) patternTransform.getMatrix()[3];
Image img = ((PrismImage)texture).getImage();
if (!srcRect.contains(new WCRectangle(0, 0, texture.getWidth(), texture.getHeight()))) {
img = img.createSubImage(srcRect.getIntX(),
srcRect.getIntY(),
(int)Math.ceil(srcRect.getWidth()),
(int)Math.ceil(srcRect.getHeight()));
}
g.setPaint(new ImagePattern(
img,
adjustedX, adjustedY,
scaledTileWidth, scaledTileHeight,
false, false));
g.fillRect(destRect.getX(), destRect.getY(),
destRect.getWidth(), destRect.getHeight());
}
}.paint();
}
}
@Override
public void drawImage(final WCImage img,
final float dstx, final float dsty, final float dstw, final float dsth,
final float srcx, final float srcy, final float srcw, final float srch)
{
if (log.isLoggable(Level.FINE)){
log.fine("drawImage(img, dst({0},{1},{2},{3}), " +
"src({4},{5},{6},{7}))",
new Object[] {dstx, dsty, dstw, dsth,
srcx, srcy, srcw, srch});
}
if (!shouldRenderRect(dstx, dsty, dstw, dsth, state.getShadowNoClone(), null)) {
return;
}
if (img instanceof PrismImage) {
new Composite() {
@Override void doPaint(Graphics g) {
PrismImage pi = (PrismImage) img;
DropShadow shadow = state.getShadowNoClone();
if (shadow != null) {
NGImageView node = new NGImageView();
node.setImage(pi.getImage());
node.setX(dstx);
node.setY(dsty);
node.setViewport(srcx, srcy, srcw, srch, dstw, dsth);
node.setContentBounds(new RectBounds(dstx, dsty, dstx + dstw, dsty + dsth));
render(g, shadow, null, null, node);
} else {
pi.draw(g,
(int) dstx, (int) dsty,
(int) (dstx + dstw), (int) (dsty + dsth),
(int) srcx, (int) srcy,
(int) (srcx + srcw), (int) (srcy + srch));
}
}
}.paint();
}
}
@Override
public void drawBitmapImage(final ByteBuffer image, final int x, final int y, final int w, final int h) {
if (!shouldRenderRect(x, y, w, h, null, null)) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
image.order(ByteOrder.nativeOrder());
Image img = Image.fromByteBgraPreData(image, w, h);
ResourceFactory rf = g.getResourceFactory();
Texture txt = rf.createTexture(img, Texture.Usage.STATIC, Texture.WrapMode.REPEAT);
g.drawTexture(txt, x, y, x + w, y + h, 0, 0, w, h);
txt.dispose();
}
}.paint();
}
@Override
public void drawIcon(WCIcon icon, int x, int y) {
if (log.isLoggable(Level.FINE)) {
log.fine("UNIMPLEMENTED drawIcon ({0}, {1})",
new Object[] {x, y});
}
}
@Override
public void drawRect(final int x, final int y, final int w, final int h) {
if (log.isLoggable(Level.FINE)) {
log.fine("drawRect({0}, {1}, {2}, {3})",
new Object[]{x, y, w, h});
}
if (!shouldRenderRect(x, y, w, h,
null, state.getStrokeNoClone().getPlatformStroke()))
{
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
Paint c = state.getPaintNoClone();
if (c != null && c.isOpaque()) {
g.setPaint(c);
g.fillRect(x, y, w, h);
}
if (state.getStrokeNoClone().apply(g)) {
g.drawRect(x, y, w, h);
}
}
}.paint();
}
@Override
public void drawString(final WCFont f, final int[] glyphs,
final float[] advances, final float x, final float y)
{
if (log.isLoggable(Level.FINE)) {
log.fine(String.format(
"Drawing %d glyphs @(%.1f, %.1f)",
glyphs.length, x, y));
}
PGFont font = (PGFont)f.getPlatformFont();
TextRun gl = TextUtilities.createGlyphList(glyphs, advances, x, y);
DropShadow shadow = state.getShadowNoClone();
BasicStroke stroke = state.isTextStroke()
? state.getStrokeNoClone().getPlatformStroke()
: null;
final FontStrike strike = font.getStrike(getTransformNoClone(), getFontSmoothingType());
if (shouldCalculateIntersection()) {
Metrics m = strike.getMetrics();
gl.setMetrics(m.getAscent(), m.getDescent(), m.getLineGap());
if (!shouldRenderRect(x, y, gl.getWidth(), gl.getHeight(), shadow, stroke)) {
return;
}
}
new Composite() {
@Override void doPaint(Graphics g) {
Paint paint = state.isTextFill()
? state.getPaintNoClone()
: null;
if (shadow != null) {
final NGText span = new NGText();
span.setGlyphs(new GlyphList[] {gl});
span.setFont(font);
span.setFontSmoothingType(fontSmoothingType);
render(g, shadow, paint, stroke, span);
} else {
if (paint != null) {
g.setPaint(paint);
g.drawString(gl, strike, x, y, null, 0, 0);
}
if (stroke != null) {
paint = state.getStrokeNoClone().getPaint();
if (paint != null) {
g.setPaint(paint);
g.setStroke(stroke);
g.draw(strike.getOutline(gl, BaseTransform.getTranslateInstance(x, y)));
}
}
}
}
}.paint();
}
@Override public void drawString(WCFont f, String str, boolean rtl,
int from, int to, float x, float y)
{
if (log.isLoggable(Level.FINE)) {
log.fine(String.format(
"str='%s' (length=%d), from=%d, to=%d, rtl=%b, @(%.1f, %.1f)",
str, str.length(), from, to, rtl, x, y));
}
TextLayout layout = TextUtilities.createLayout(
str.substring(from, to), f.getPlatformFont());
int count = 0;
GlyphList[] runs = layout.getRuns();
for (GlyphList run: runs) {
count += run.getGlyphCount();
}
int[] glyphs = new int[count];
float[] adv = new float[count];
count = 0;
for (GlyphList run: layout.getRuns()) {
int gc = run.getGlyphCount();
for (int i = 0; i < gc; i++) {
glyphs[count] = run.getGlyphCode(i);
adv[count] = run.getPosX(i + 1) - run.getPosX(i);
count++;
}
}
if (rtl) {
x += (TextUtilities.getLayoutWidth(str.substring(from), f.getPlatformFont()) -
layout.getBounds().getWidth());
} else {
x += TextUtilities.getLayoutWidth(str.substring(0, from), f.getPlatformFont());
}
drawString(f, glyphs, adv, x, y);
}
@Override
public void setComposite(int composite) {
log.fine("setComposite({0})", composite);
state.setCompositeOperation(composite);
}
@Override
public void drawEllipse(final int x, final int y, final int w, final int h) {
if (log.isLoggable(Level.FINE)) {
log.fine("drawEllipse({0}, {1}, {2}, {3})",
new Object[] { x, y, w, h});
}
if (!shouldRenderRect(x, y, w, h,
null, state.getStrokeNoClone().getPlatformStroke()))
{
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
g.setPaint(state.getPaintNoClone());
g.fillEllipse(x, y, w, h);
if (state.getStrokeNoClone().apply(g)) {
g.drawEllipse(x, y, w, h);
}
}
}.paint();
}
private final static BasicStroke focusRingStroke =
new BasicStroke(1.1f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND, 0.0f,
new float[] {1.0f}, 0.0f);
@Override
public void drawFocusRing(final int x, final int y, final int w, final int h, final int rgba) {
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("drawFocusRing: %d, %d, %d, %d, 0x%x", x, y, w, h, rgba));
}
if (!shouldRenderRect(x, y, w, h, null, focusRingStroke)) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
g.setPaint(createColor(rgba));
BasicStroke stroke = g.getStroke();
g.setStroke(focusRingStroke);
g.drawRoundRect(x, y, w, h, 4, 4);
g.setStroke(stroke);
}
}.paint();
}
public void setAlpha(float alpha) {
log.fine("setAlpha({0})", alpha);
state.setAlpha(alpha);
if (null != cachedGraphics) {
cachedGraphics.setExtraAlpha(state.getAlpha());
}
}
public float getAlpha() {
return state.getAlpha();
}
@Override public void beginTransparencyLayer(float opacity) {
TransparencyLayer layer = new TransparencyLayer(
getGraphics(false), state.getClipNoClone(), opacity);
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("beginTransparencyLayer(%s)", layer));
}
state.markAsRestorePoint();
startNewLayer(layer);
}
@Override public void endTransparencyLayer() {
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("endTransparencyLayer(%s)", state.getLayerNoClone()));
}
restoreState();
}
@Override
public void drawWidget(final RenderTheme theme, final Ref widget, final int x, final int y) {
WCSize s = theme.getWidgetSize(widget);
if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
theme.drawWidget(WCGraphicsPrismContext.this, widget, x, y);
}
}.paint();
}
@Override
public void drawScrollbar(final ScrollBarTheme theme, final Ref widget, int x, int y,
int pressedPart, int hoveredPart)
{
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("drawScrollbar(%s, %s, x = %d, y = %d)", theme, widget, x, y));
}
WCSize s = theme.getWidgetSize(widget);
if (!shouldRenderRect(x, y, s.getWidth(), s.getHeight(), null, null)) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
theme.paint(WCGraphicsPrismContext.this, widget, x, y, pressedPart, hoveredPart);
}
}.paint();
}
private static Rectangle intersect(Rectangle what, Rectangle with) {
if (what == null) {
return with;
}
RectBounds b = what.toRectBounds();
b.intersectWith(with);
what.setBounds(b);
return what;
}
static Color createColor(int rgba) {
float a = (0xFF & (rgba >> 24)) / 255.0f;
float r = (0xFF & (rgba >> 16)) / 255.0f;
float g = (0xFF & (rgba >> 8)) / 255.0f;
float b = (0xFF & (rgba)) / 255.0f;
return new Color(r, g, b, a);
}
private static Color4f createColor4f(int rgba) {
float a = (0xFF & (rgba >> 24)) / 255.0f;
float r = (0xFF & (rgba >> 16)) / 255.0f;
float g = (0xFF & (rgba >> 8)) / 255.0f;
float b = (0xFF & (rgba)) / 255.0f;
return new Color4f(r, g, b, a);
}
private DropShadow createShadow(float dx, float dy, float blur, int rgba) {
if (dx == 0f && dy == 0f && blur == 0f) {
return null;
}
DropShadow shadow = new DropShadow();
shadow.setOffsetX((int) dx);
shadow.setOffsetY((int) dy);
shadow.setRadius((blur < 0f) ? 0f : (blur > 127f) ? 127f : blur);
shadow.setColor(createColor4f(rgba));
return shadow;
}
private void render(Graphics g, Effect effect, Paint paint, BasicStroke stroke, NGNode node) {
if (node instanceof NGShape) {
NGShape shape = (NGShape) node;
Shape realShape = shape.getShape();
Paint strokePaint = state.getStrokeNoClone().getPaint();
if ((stroke != null) && (strokePaint != null)) {
realShape = stroke.createStrokedShape(realShape);
shape.setDrawStroke(stroke);
shape.setDrawPaint(strokePaint);
shape.setMode((paint == null) ? NGShape.Mode.STROKE : NGShape.Mode.STROKE_FILL);
} else {
shape.setMode((paint == null) ? NGShape.Mode.EMPTY : NGShape.Mode.FILL);
}
shape.setFillPaint(paint);
shape.setContentBounds(realShape.getBounds());
}
boolean culling = g.hasPreCullingBits();
g.setHasPreCullingBits(false);
node.setEffect(effect);
node.render(g);
g.setHasPreCullingBits(culling);
}
private static final class ContextState {
private final WCStrokeImpl stroke = new WCStrokeImpl();
private Rectangle clip;
private Paint paint;
private float alpha;
private boolean textFill = true;
private boolean textStroke = false;
private boolean textClip = false;
private boolean restorePoint = false;
private DropShadow shadow;
private Affine3D xform;
private Layer layer;
private int compositeOperation;
private ContextState() {
clip = null;
paint = Color.BLACK;
stroke.setPaint(Color.BLACK);
alpha = 1.0f;
xform = new Affine3D();
compositeOperation = COMPOSITE_SOURCE_OVER;
}
private ContextState(ContextState state) {
stroke.copyFrom(state.getStrokeNoClone());
setPaint(state.getPaintNoClone());
clip = state.getClipNoClone();
if (clip != null) {
clip = new Rectangle(clip);
}
xform = new Affine3D(state.getTransformNoClone());
setShadow(state.getShadowNoClone());
setLayer(state.getLayerNoClone());
setAlpha(state.getAlpha());
setTextMode(state.isTextFill(), state.isTextStroke(), state.isTextClip());
setCompositeOperation(state.getCompositeOperation());
}
@Override
protected ContextState clone() {
return new ContextState(this);
}
private void apply(Graphics g) {
g.setTransform(getTransformNoClone());
g.setClipRect(getClipNoClone());
g.setExtraAlpha(getAlpha());
}
private int getCompositeOperation() {
return compositeOperation;
}
private void setCompositeOperation(int compositeOperation) {
this.compositeOperation = compositeOperation;
}
private WCStrokeImpl getStrokeNoClone() {
return stroke;
}
private Paint getPaintNoClone() {
return paint;
}
private void setPaint(Paint paint) {
this.paint = paint;
}
private Rectangle getClipNoClone() {
return clip;
}
private Layer getLayerNoClone() {
return layer;
}
private void setLayer(Layer layer) {
this.layer = layer;
}
private void setClip(Rectangle area) {
clip = area;
}
private void clip(Rectangle area) {
if (null == clip) {
clip = area;
} else {
clip.intersectWith(area);
}
}
private void setAlpha(float alpha) {
this.alpha = alpha;
}
private float getAlpha() {
return alpha;
}
private void setTextMode(boolean fill, boolean stroke, boolean clip) {
textFill = fill;
textStroke = stroke;
textClip = clip;
}
private boolean isTextFill() {
return textFill;
}
private boolean isTextStroke() {
return textStroke;
}
private boolean isTextClip() {
return textClip;
}
private void markAsRestorePoint() {
restorePoint = true;
}
private boolean isRestorePoint() {
return restorePoint;
}
private void setShadow(DropShadow shadow) {
this.shadow = shadow;
}
private DropShadow getShadowNoClone() {
return shadow;
}
private Affine3D getTransformNoClone() {
return xform;
}
private void setTransform(final Affine3D at) {
this.xform.setTransform(at);
}
private void concatTransform(Affine3D at) {
xform.concatenate(at);
}
private void translate(double dx, double dy) {
xform.translate(dx, dy);
}
private void scale(double sx, double sy) {
xform.scale(sx,sy);
}
private void rotate(double radians) {
xform.rotate(radians);
}
}
private abstract static class Layer {
FilterContext fctx;
PrDrawable buffer;
Graphics graphics;
final Rectangle bounds;
boolean permanent;
Layer(Graphics g, Rectangle bounds, boolean permanent) {
this.bounds = new Rectangle(bounds);
this.permanent = permanent;
int w = Math.max(bounds.width, 1);
int h = Math.max(bounds.height, 1);
fctx = getFilterContext(g);
if (permanent) {
ResourceFactory f = GraphicsPipeline.getDefaultResourceFactory();
RTTexture rtt = f.createRTTexture(w, h, Texture.WrapMode.CLAMP_NOT_NEEDED);
rtt.makePermanent();
buffer = ((PrRenderer)Renderer.getRenderer(fctx)).createDrawable(rtt);
} else {
buffer = (PrDrawable) Effect.getCompatibleImage(fctx, w, h);
}
}
Graphics getGraphics() {
if (graphics == null) {
graphics = buffer.createGraphics();
}
return graphics;
}
abstract void init(Graphics g);
abstract void render(Graphics g);
private void dispose() {
if (buffer != null) {
if (permanent) {
buffer.flush();
} else {
Effect.releaseCompatibleImage(fctx, buffer);
}
fctx = null;
buffer = null;
}
}
private double getX() { return (double) bounds.x; }
private double getY() { return (double) bounds.y; }
}
private final class TransparencyLayer extends Layer {
private final float opacity;
private TransparencyLayer(Graphics g, Rectangle bounds, float opacity) {
super(g, bounds, false);
this.opacity = opacity;
}
@Override void init(Graphics g) {
state.setCompositeOperation(COMPOSITE_SOURCE_OVER);
}
@Override void render(Graphics g) {
new Composite() {
@Override void doPaint(Graphics g) {
float op = g.getExtraAlpha();
g.setExtraAlpha(opacity);
Affine3D tx = new Affine3D(g.getTransformNoClone());
g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
g.drawTexture(buffer.getTextureObject(),
bounds.x, bounds.y, bounds.width, bounds.height);
g.setTransform(tx);
g.setExtraAlpha(op);
}
}.paint(g);
}
@Override public String toString() {
return String.format("TransparencyLayer[%d,%d + %dx%d, opacity %.2f]",
bounds.x, bounds.y, bounds.width, bounds.height, opacity);
}
}
private static final class ClipLayer extends Layer {
private final WCPath normalizedToClipPath;
private boolean srcover;
private ClipLayer(Graphics g, Rectangle bounds, WCPath normalizedToClipPath,
boolean permanent)
{
super(g, bounds, permanent);
this.normalizedToClipPath = normalizedToClipPath;
srcover = true;
}
@Override void init(Graphics g) {
RTTexture texture = null;
ReadbackGraphics readbackGraphics = null;
try {
readbackGraphics = (ReadbackGraphics) g;
texture = readbackGraphics.readBack(bounds);
getGraphics().drawTexture(texture, 0, 0, bounds.width, bounds.height);
} finally {
if (readbackGraphics != null && texture != null) {
readbackGraphics.releaseReadBackBuffer(texture);
}
}
srcover = false;
}
@Override void render(Graphics g) {
Path2D p2d = ((WCPathImpl)normalizedToClipPath).getPlatformPath();
PrDrawable bufferImg = (PrDrawable) Effect.getCompatibleImage(
fctx, bounds.width, bounds.height);
Graphics bufferGraphics = bufferImg.createGraphics();
bufferGraphics.setPaint(Color.BLACK);
bufferGraphics.fill(p2d);
if (g instanceof MaskTextureGraphics && ! (g instanceof PrinterGraphics)) {
MaskTextureGraphics mg = (MaskTextureGraphics) g;
if (srcover) {
mg.drawPixelsMasked(buffer.getTextureObject(),
bufferImg.getTextureObject(),
bounds.x, bounds.y, bounds.width, bounds.height,
0, 0, 0, 0);
} else {
mg.maskInterpolatePixels(buffer.getTextureObject(),
bufferImg.getTextureObject(),
bounds.x, bounds.y, bounds.width, bounds.height,
0, 0, 0, 0);
}
} else {
Blend blend = new Blend(Blend.Mode.SRC_IN,
new PassThrough(bufferImg, bounds.width, bounds.height),
new PassThrough(buffer, bounds.width, bounds.height));
Affine3D tx = new Affine3D(g.getTransformNoClone());
g.setTransform(BaseTransform.IDENTITY_TRANSFORM);
PrEffectHelper.render(blend, g, bounds.x, bounds.y, null);
g.setTransform(tx);
}
Effect.releaseCompatibleImage(fctx, bufferImg);
}
@Override public String toString() {
return String.format("ClipLayer[%d,%d + %dx%d, path %s]",
bounds.x, bounds.y, bounds.width, bounds.height,
normalizedToClipPath);
}
}
private abstract class Composite {
abstract void doPaint(Graphics g);
void paint() {
paint(getGraphics(true));
}
void paint(Graphics g) {
if (g != null) {
CompositeMode oldCompositeMode = g.getCompositeMode();
switch (state.getCompositeOperation()) {
case COMPOSITE_COPY:
g.setCompositeMode(CompositeMode.SRC);
doPaint(g);
g.setCompositeMode(oldCompositeMode);
break;
case COMPOSITE_SOURCE_OVER:
g.setCompositeMode(CompositeMode.SRC_OVER);
doPaint(g);
g.setCompositeMode(oldCompositeMode);
break;
default:
blend(g);
break;
}
isRootLayerValid = false;
}
}
private void blend(Graphics g) {
FilterContext fctx = getFilterContext(g);
PrDrawable dstImg = null;
PrDrawable srcImg = null;
ReadbackGraphics readBackGraphics = null;
RTTexture texture = null;
Rectangle clip = state.getClipNoClone();
WCImage image = getImage();
try {
if (image != null && image instanceof PrismImage) {
dstImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height);
Graphics dstG = dstImg.createGraphics();
((PrismImage) image).draw(dstG,
0, 0, clip.width, clip.height,
clip.x, clip.y, clip.width, clip.height);
} else {
readBackGraphics = (ReadbackGraphics) g;
texture = readBackGraphics.readBack(clip);
dstImg = PrDrawable.create(fctx, texture);
}
srcImg = (PrDrawable) Effect.getCompatibleImage(fctx, clip.width, clip.height);
Graphics srcG = srcImg.createGraphics();
state.apply(srcG);
doPaint(srcG);
g.clear();
PrEffectHelper.render(createEffect(dstImg, srcImg, clip.width, clip.height), g, 0, 0, null);
} finally {
if (srcImg != null) {
Effect.releaseCompatibleImage(fctx, srcImg);
}
if (dstImg != null) {
if (readBackGraphics != null && texture != null) {
readBackGraphics.releaseReadBackBuffer(texture);
} else {
Effect.releaseCompatibleImage(fctx, dstImg);
}
}
}
}
private Effect createBlend(Blend.Mode mode,
PrDrawable dstImg,
PrDrawable srcImg,
int width,
int height)
{
return new Blend(
mode,
new PassThrough(dstImg, width, height),
new PassThrough(srcImg, width, height));
}
private Effect createEffect(PrDrawable dstImg,
PrDrawable srcImg,
int width,
int height)
{
switch (state.getCompositeOperation()) {
case COMPOSITE_CLEAR:
case COMPOSITE_XOR:
return new Blend(
SRC_OVER,
createBlend(SRC_OUT, dstImg, srcImg, width, height),
createBlend(SRC_OUT, srcImg, dstImg, width, height)
);
case COMPOSITE_SOURCE_IN:
return createBlend(SRC_IN, dstImg, srcImg, width, height);
case COMPOSITE_SOURCE_OUT:
return createBlend(SRC_OUT, dstImg, srcImg, width, height);
case COMPOSITE_SOURCE_ATOP:
return createBlend(SRC_ATOP, dstImg, srcImg, width, height);
case COMPOSITE_DESTINATION_OVER:
return createBlend(SRC_OVER, srcImg, dstImg, width, height);
case COMPOSITE_DESTINATION_IN:
return createBlend(SRC_IN, srcImg, dstImg, width, height);
case COMPOSITE_DESTINATION_OUT:
return createBlend(SRC_OUT, srcImg, dstImg, width, height);
case COMPOSITE_DESTINATION_ATOP:
return createBlend(SRC_ATOP, srcImg, dstImg, width, height);
case COMPOSITE_HIGHLIGHT:
return createBlend(ADD, dstImg, srcImg, width, height);
default:
return createBlend(SRC_OVER, dstImg, srcImg, width, height);
}
}
}
private static final class PassThrough extends Effect {
private final PrDrawable img;
private final int width;
private final int height;
private PassThrough(PrDrawable img, int width, int height) {
this.img = img;
this.width = width;
this.height = height;
}
@Override public ImageData filter(
FilterContext fctx,
BaseTransform transform,
Rectangle outputClip,
Object renderHelper,
Effect defaultInput) {
img.lock();
ImageData imgData = new ImageData(fctx, img, new Rectangle(
(int) transform.getMxt(),
(int) transform.getMyt(),
width, height));
imgData.setReusable(true);
return imgData;
}
@Override public RectBounds getBounds(
BaseTransform transform,
Effect defaultInput) {
return null;
}
@Override public AccelType getAccelType(FilterContext fctx) {
return AccelType.INTRINSIC;
}
@Override
public boolean reducesOpaquePixels() {
return false;
}
@Override
public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
return null;
}
}
private static FilterContext getFilterContext(Graphics g) {
Screen screen = g.getAssociatedScreen();
if (screen == null) {
ResourceFactory factory = g.getResourceFactory();
return PrFilterContext.getPrinterContext(factory);
} else {
return PrFilterContext.getInstance(screen);
}
}
@Override
public void strokeArc(final int x, final int y, final int w, final int h,
final int startAngle, final int angleSpan)
{
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("strokeArc(%d, %d, %d, %d, %d, %d)",
x, y, w, h, startAngle, angleSpan));
}
Arc2D arc = new Arc2D(x, y, w, h, startAngle, angleSpan, Arc2D.OPEN);
if (state.getStrokeNoClone().isApplicable() &&
!shouldRenderShape(arc, null, state.getStrokeNoClone().getPlatformStroke()))
{
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
if (state.getStrokeNoClone().apply(g)) {
g.draw(arc);
}
}
}.paint();
}
@Override
public WCImage getImage() {
return null;
}
@Override
public void strokeRect(final float x, final float y, final float w, final float h,
final float lineWidth) {
if (log.isLoggable(Level.FINE)) {
log.fine(String.format("strokeRect_FFFFF(%f, %f, %f, %f, %f)",
x, y, w, h, lineWidth));
}
BasicStroke stroke = new BasicStroke(
lineWidth,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
Math.max(1.0f, lineWidth),
state.getStrokeNoClone().getDashSizes(),
state.getStrokeNoClone().getDashOffset());
if (!shouldRenderRect(x, y, w, h, null, stroke)) {
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
g.setStroke(stroke);
Paint paint = state.getStrokeNoClone().getPaint();
if (paint == null) {
paint = state.getPaintNoClone();
}
g.setPaint(paint);
g.drawRect(x, y, w, h);
}
}.paint();
}
@Override
public void strokePath(final WCPath path) {
log.fine("strokePath");
if (path != null) {
final BasicStroke stroke = state.getStrokeNoClone().getPlatformStroke();
final DropShadow shadow = state.getShadowNoClone();
final Path2D p2d = (Path2D)path.getPlatformPath();
if ((stroke == null && shadow == null) ||
!shouldRenderShape(p2d, shadow, stroke))
{
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
if (shadow != null) {
final NGPath node = new NGPath();
node.updateWithPath2d(p2d);
render(g, shadow, null, stroke, node);
} else if (stroke != null) {
Paint paint = state.getStrokeNoClone().getPaint();
if (paint == null) {
paint = state.getPaintNoClone();
}
g.setPaint(paint);
g.setStroke(stroke);
g.draw(p2d);
}
}
}.paint();
}
}
@Override
public void fillPath(final WCPath path) {
log.fine("fillPath");
if (path != null) {
if (!shouldRenderShape(((WCPathImpl)path).getPlatformPath(),
state.getShadowNoClone(), null))
{
return;
}
new Composite() {
@Override void doPaint(Graphics g) {
Path2D p2d = (Path2D) path.getPlatformPath();
Paint paint = state.getPaintNoClone();
DropShadow shadow = state.getShadowNoClone();
if (shadow != null) {
final NGPath node = new NGPath();
node.updateWithPath2d(p2d);
render(g, shadow, paint, null, node);
} else {
g.setPaint(paint);
g.fill(p2d);
}
}
}.paint();
}
}
public void setTransform(WCTransform tm) {
double m[] = tm.getMatrix();
Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5]));
if (state.getLayerNoClone() == null) {
at.preConcatenate(baseTransform);
}
state.setTransform(at);
resetCachedGraphics();
}
public WCTransform getTransform() {
Affine3D xf = state.getTransformNoClone();
return new WCTransform(xf.getMxx(), xf.getMyx(),
xf.getMxy(), xf.getMyy(),
xf.getMxt(), xf.getMyt());
}
public void concatTransform(WCTransform tm) {
double m[] = tm.getMatrix();
Affine3D at = new Affine3D(new Affine2D(m[0], m[1], m[2], m[3], m[4], m[5]));
state.concatTransform(at);
resetCachedGraphics();
}
@Override
public void flush() {
flushAllLayers();
}
@Override
public WCGradient createLinearGradient(WCPoint p1, WCPoint p2) {
return new WCLinearGradient(p1, p2);
}
@Override
public WCGradient createRadialGradient(WCPoint p1, float r1, WCPoint p2, float r2) {
return new WCRadialGradient(p1, r1, p2, r2);
}
}