/******************************************************************************* * Copyright (c) 2000, 2010 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * TH4 SYSTEMS GmbH - Hairline handling *******************************************************************************/ package org.openscada.vi.ui.draw2d.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.draw2d.FigureUtilities; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.LineAttributes; import org.eclipse.swt.graphics.Path; import org.eclipse.swt.graphics.PathData; import org.eclipse.swt.graphics.Pattern; import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.graphics.TextStyle; import org.eclipse.swt.widgets.Display; /** * A Graphics object able to scale all operations based on the current scale * factor. */ public class ScaledGraphics extends Graphics { private static class FontHeightCache { Font font; int height; } static class FontKey { Font font; int height; protected FontKey () { } protected FontKey ( final Font font, final int height ) { this.font = font; this.height = height; } @Override public boolean equals ( final Object obj ) { return ( (FontKey)obj ).font.equals ( this.font ) && ( (FontKey)obj ).height == this.height; } @Override public int hashCode () { return this.font.hashCode () ^ this.height; } protected void setValues ( final Font font, final int height ) { this.font = font; this.height = height; } } /** * The internal state of the scaled graphics. */ protected static class State { private double appliedX; private double appliedY; private Font font; private float lineWidth; private double zoom; /** * Constructs a new, uninitialized State object. */ protected State () { } /** * Constructs a new State object and initializes the properties based on * the given values. * * @param zoom * the zoom factor * @param x * the x offset * @param y * the y offset * @param font * the font * @param lineWidth * the line width */ protected State ( final double zoom, final double x, final double y, final Font font, final int lineWidth ) { this ( zoom, x, y, font, (float)lineWidth ); } /** * Constructs a new State object and initializes the properties based on * the given values. * * @param zoom * the zoom factor * @param x * the x offset * @param y * the y offset * @param font * the font * @param lineWidth * the line width * * @since 3.5 */ protected State ( final double zoom, final double x, final double y, final Font font, final float lineWidth ) { this.zoom = zoom; this.appliedX = x; this.appliedY = y; this.font = font; this.lineWidth = lineWidth; } /** * Sets all the properties of the state object. * * @param zoom * the zoom factor * @param x * the x offset * @param y * the y offset * @param font * the font * @param lineWidth * the line width */ protected void setValues ( final double zoom, final double x, final double y, final Font font, final int lineWidth ) { setValues ( zoom, x, y, font, (float)lineWidth ); } /** * Sets all the properties of the state object. * * @param zoom * the zoom factor * @param x * the x offset * @param y * the y offset * @param font * the font * @param lineWidth * the line width * * @since 3.5 */ protected void setValues ( final double zoom, final double x, final double y, final Font font, final float lineWidth ) { this.zoom = zoom; this.appliedX = x; this.appliedY = y; this.font = font; this.lineWidth = lineWidth; } } private static int[][] intArrayCache = new int[8][]; private final Rectangle tempRECT = new Rectangle (); static { for ( int i = 0; i < intArrayCache.length; i++ ) { intArrayCache[i] = new int[i + 1]; } } private boolean allowText = true; // private static final Point PT = new Point(); private final Map fontCache = new HashMap (); private final Map fontDataCache = new HashMap (); private final FontKey fontKey = new FontKey (); private double fractionalX; private double fractionalY; private final Graphics graphics; private final FontHeightCache localCache = new FontHeightCache (); private Font localFont; private float localLineWidth; private final List stack = new ArrayList (); private int stackPointer = 0; private final FontHeightCache targetCache = new FontHeightCache (); double zoom = 1.0; /** * Constructs a new ScaledGraphics based on the given Graphics object. * * @param g * the base graphics object */ public ScaledGraphics ( final Graphics g ) { this.graphics = g; this.localFont = g.getFont (); this.localLineWidth = g.getLineWidthFloat (); } /** @see Graphics#clipRect(Rectangle) */ @Override public void clipRect ( final Rectangle r ) { this.graphics.clipRect ( zoomClipRect ( r ) ); } Font createFont ( final FontData data ) { return new Font ( Display.getCurrent (), data ); } /** * Scales given path by zoom factor * * @param path * Path to be scaled * @return Scaled path */ private Path createScaledPath ( final Path path ) { final PathData p = path.getPathData (); for ( int i = 0; i < p.points.length; i += 2 ) { p.points[i] = (float) ( p.points[i] * this.zoom + this.fractionalX ); p.points[i + 1] = (float) ( p.points[i + 1] * this.zoom + this.fractionalY ); } final Path scaledPath = new Path ( path.getDevice () ); int index = 0; for ( int i = 0; i < p.types.length; i++ ) { final byte type = p.types[i]; switch ( type ) { case SWT.PATH_MOVE_TO: scaledPath.moveTo ( p.points[index], p.points[index + 1] ); index += 2; break; case SWT.PATH_LINE_TO: scaledPath.lineTo ( p.points[index], p.points[index + 1] ); index += 2; break; case SWT.PATH_CUBIC_TO: scaledPath.cubicTo ( p.points[index], p.points[index + 1], p.points[index + 2], p.points[index + 3], p.points[index + 4], p.points[index + 5] ); index += 6; break; case SWT.PATH_QUAD_TO: scaledPath.quadTo ( p.points[index], p.points[index + 1], p.points[index + 2], p.points[index + 3] ); index += 4; break; case SWT.PATH_CLOSE: scaledPath.close (); break; } } return scaledPath; } /** @see Graphics#dispose() */ @Override public void dispose () { // Remove all states from the stack while ( this.stackPointer > 0 ) { popState (); } // Dispose fonts final Iterator iter = this.fontCache.values ().iterator (); while ( iter.hasNext () ) { final Font font = (Font)iter.next (); font.dispose (); } } /** @see Graphics#drawArc(int, int, int, int, int, int) */ @Override public void drawArc ( final int x, final int y, final int w, final int h, final int offset, final int sweep ) { final Rectangle z = zoomRect ( x, y, w, h ); if ( z.isEmpty () || sweep == 0 ) { return; } this.graphics.drawArc ( z, offset, sweep ); } /** @see Graphics#drawFocus(int, int, int, int) */ @Override public void drawFocus ( final int x, final int y, final int w, final int h ) { this.graphics.drawFocus ( zoomRect ( x, y, w, h ) ); } /** @see Graphics#drawImage(Image, int, int) */ @Override public void drawImage ( final Image srcImage, final int x, final int y ) { final org.eclipse.swt.graphics.Rectangle size = srcImage.getBounds (); this.graphics.drawImage ( srcImage, 0, 0, size.width, size.height, (int)Math.floor ( x * this.zoom + this.fractionalX ), (int)Math.floor ( y * this.zoom + this.fractionalY ), (int)Math.floor ( size.width * this.zoom + this.fractionalX ), (int)Math.floor ( size.height * this.zoom + this.fractionalY ) ); } /** @see Graphics#drawImage(Image, int, int, int, int, int, int, int, int) */ @Override public void drawImage ( final Image srcImage, final int sx, final int sy, final int sw, final int sh, final int tx, final int ty, final int tw, final int th ) { // "t" == target rectangle, "s" = source final Rectangle t = zoomRect ( tx, ty, tw, th ); if ( !t.isEmpty () ) { this.graphics.drawImage ( srcImage, sx, sy, sw, sh, t.x, t.y, t.width, t.height ); } } /** @see Graphics#drawLine(int, int, int, int) */ @Override public void drawLine ( final int x1, final int y1, final int x2, final int y2 ) { this.graphics.drawLine ( (int)Math.floor ( x1 * this.zoom + this.fractionalX ), (int)Math.floor ( y1 * this.zoom + this.fractionalY ), (int)Math.floor ( x2 * this.zoom + this.fractionalX ), (int)Math.floor ( y2 * this.zoom + this.fractionalY ) ); } /** @see Graphics#drawOval(int, int, int, int) */ @Override public void drawOval ( final int x, final int y, final int w, final int h ) { this.graphics.drawOval ( zoomRect ( x, y, w, h ) ); } /** @see Graphics#drawPath(Path) */ @Override public void drawPath ( final Path path ) { final Path scaledPath = createScaledPath ( path ); try { this.graphics.drawPath ( scaledPath ); } finally { scaledPath.dispose (); } } /** @see Graphics#drawPoint(int, int) */ @Override public void drawPoint ( final int x, final int y ) { this.graphics.drawPoint ( (int)Math.floor ( x * this.zoom + this.fractionalX ), (int)Math.floor ( y * this.zoom + this.fractionalY ) ); } /** * @see Graphics#drawPolygon(int[]) */ @Override public void drawPolygon ( final int[] points ) { this.graphics.drawPolygon ( zoomPointList ( points ) ); } /** @see Graphics#drawPolygon(PointList) */ @Override public void drawPolygon ( final PointList points ) { this.graphics.drawPolygon ( zoomPointList ( points.toIntArray () ) ); } /** * @see Graphics#drawPolyline(int[]) */ @Override public void drawPolyline ( final int[] points ) { this.graphics.drawPolyline ( zoomPointList ( points ) ); } /** @see Graphics#drawPolyline(PointList) */ @Override public void drawPolyline ( final PointList points ) { this.graphics.drawPolyline ( zoomPointList ( points.toIntArray () ) ); } /** @see Graphics#drawRectangle(int, int, int, int) */ @Override public void drawRectangle ( final int x, final int y, final int w, final int h ) { this.graphics.drawRectangle ( zoomRect ( x, y, w, h ) ); } /** @see Graphics#drawRoundRectangle(Rectangle, int, int) */ @Override public void drawRoundRectangle ( final Rectangle r, final int arcWidth, final int arcHeight ) { this.graphics.drawRoundRectangle ( zoomRect ( r.x, r.y, r.width, r.height ), (int) ( arcWidth * this.zoom ), (int) ( arcHeight * this.zoom ) ); } /** @see Graphics#drawString(String, int, int) */ @Override public void drawString ( final String s, final int x, final int y ) { if ( this.allowText ) { this.graphics.drawString ( s, zoomTextPoint ( x, y ) ); } } /** @see Graphics#drawText(String, int, int) */ @Override public void drawText ( final String s, final int x, final int y ) { if ( this.allowText ) { this.graphics.drawText ( s, zoomTextPoint ( x, y ) ); } } /** * @see Graphics#drawText(String, int, int, int) */ @Override public void drawText ( final String s, final int x, final int y, final int style ) { if ( this.allowText ) { this.graphics.drawText ( s, zoomTextPoint ( x, y ), style ); } } /** * @see Graphics#drawTextLayout(TextLayout, int, int, int, int, Color, * Color) */ @Override public void drawTextLayout ( final TextLayout layout, final int x, final int y, final int selectionStart, final int selectionEnd, final Color selectionForeground, final Color selectionBackground ) { final TextLayout scaled = zoomTextLayout ( layout ); if ( scaled == null ) { return; } try { this.graphics.drawTextLayout ( scaled, (int)Math.floor ( x * this.zoom + this.fractionalX ), (int)Math.floor ( y * this.zoom + this.fractionalY ), selectionStart, selectionEnd, selectionBackground, selectionForeground ); } finally { scaled.dispose (); } } /** @see Graphics#fillArc(int, int, int, int, int, int) */ @Override public void fillArc ( final int x, final int y, final int w, final int h, final int offset, final int sweep ) { final Rectangle z = zoomFillRect ( x, y, w, h ); if ( z.isEmpty () || sweep == 0 ) { return; } this.graphics.fillArc ( z, offset, sweep ); } /** @see Graphics#fillGradient(int, int, int, int, boolean) */ @Override public void fillGradient ( final int x, final int y, final int w, final int h, final boolean vertical ) { this.graphics.fillGradient ( zoomFillRect ( x, y, w, h ), vertical ); } /** @see Graphics#fillOval(int, int, int, int) */ @Override public void fillOval ( final int x, final int y, final int w, final int h ) { this.graphics.fillOval ( zoomFillRect ( x, y, w, h ) ); } /** @see Graphics#fillPath(Path) */ @Override public void fillPath ( final Path path ) { final Path scaledPath = createScaledPath ( path ); try { this.graphics.fillPath ( scaledPath ); } finally { scaledPath.dispose (); } } /** * @see Graphics#fillPolygon(int[]) */ @Override public void fillPolygon ( final int[] points ) { this.graphics.fillPolygon ( zoomPointList ( points ) ); } /** @see Graphics#fillPolygon(PointList) */ @Override public void fillPolygon ( final PointList points ) { this.graphics.fillPolygon ( zoomPointList ( points.toIntArray () ) ); } /** @see Graphics#fillRectangle(int, int, int, int) */ @Override public void fillRectangle ( final int x, final int y, final int w, final int h ) { this.graphics.fillRectangle ( zoomFillRect ( x, y, w, h ) ); } /** @see Graphics#fillRoundRectangle(Rectangle, int, int) */ @Override public void fillRoundRectangle ( final Rectangle r, final int arcWidth, final int arcHeight ) { this.graphics.fillRoundRectangle ( zoomFillRect ( r.x, r.y, r.width, r.height ), (int) ( arcWidth * this.zoom ), (int) ( arcHeight * this.zoom ) ); } /** @see Graphics#fillString(String, int, int) */ @Override public void fillString ( final String s, final int x, final int y ) { if ( this.allowText ) { this.graphics.fillString ( s, zoomTextPoint ( x, y ) ); } } /** @see Graphics#fillText(String, int, int) */ @Override public void fillText ( final String s, final int x, final int y ) { if ( this.allowText ) { this.graphics.fillText ( s, zoomTextPoint ( x, y ) ); } } /** * @see Graphics#getAbsoluteScale() */ @Override public double getAbsoluteScale () { return this.zoom * this.graphics.getAbsoluteScale (); } /** * @see Graphics#getAlpha() */ @Override public int getAlpha () { return this.graphics.getAlpha (); } /** * @see Graphics#getAntialias() */ @Override public int getAntialias () { return this.graphics.getAntialias (); } /** @see Graphics#getBackgroundColor() */ @Override public Color getBackgroundColor () { return this.graphics.getBackgroundColor (); } Font getCachedFont ( FontKey key ) { final Font font = (Font)this.fontCache.get ( key ); if ( font != null ) { return font; } key = new FontKey ( key.font, key.height ); final FontData data = key.font.getFontData ()[0]; data.setHeight ( key.height ); final Font zoomedFont = createFont ( data ); this.fontCache.put ( key, zoomedFont ); return zoomedFont; } FontData getCachedFontData ( final Font f ) { FontData data = (FontData)this.fontDataCache.get ( f ); if ( data == null ) { data = getLocalFont ().getFontData ()[0]; this.fontDataCache.put ( f, data ); } return data; } /** @see Graphics#getClip(Rectangle) */ @Override public Rectangle getClip ( final Rectangle rect ) { this.graphics.getClip ( rect ); final int x = (int) ( rect.x / this.zoom ); final int y = (int) ( rect.y / this.zoom ); /* * If the clip rectangle is queried, perform an inverse zoom, and take * the ceiling of the resulting double. This is necessary because * forward scaling essentially performs a floor() function. Without * this, figures will think that they don't need to paint when actually * they do. */ rect.width = (int)Math.ceil ( rect.right () / this.zoom ) - x; rect.height = (int)Math.ceil ( rect.bottom () / this.zoom ) - y; rect.x = x; rect.y = y; return rect; } /** * @see Graphics#getAdvanced() */ @Override public boolean getAdvanced () { return this.graphics.getAdvanced (); } /** * @see Graphics#getFillRule() */ @Override public int getFillRule () { return this.graphics.getFillRule (); } /** @see Graphics#getFont() */ @Override public Font getFont () { return getLocalFont (); } /** @see Graphics#getFontMetrics() */ @Override public FontMetrics getFontMetrics () { return FigureUtilities.getFontMetrics ( this.localFont ); } /** @see Graphics#getForegroundColor() */ @Override public Color getForegroundColor () { return this.graphics.getForegroundColor (); } /** * @see Graphics#getInterpolation() */ @Override public int getInterpolation () { return this.graphics.getInterpolation (); } /** * @see Graphics#getLineCap() */ @Override public int getLineCap () { return this.graphics.getLineCap (); } /** * @see Graphics#getLineJoin() */ @Override public int getLineJoin () { return this.graphics.getLineJoin (); } /** @see Graphics#getLineStyle() */ @Override public int getLineStyle () { return this.graphics.getLineStyle (); } /** @see Graphics#getLineMiterLimit() */ @Override public float getLineMiterLimit () { return this.graphics.getLineMiterLimit (); } /** @see Graphics#getLineWidth() */ @Override public int getLineWidth () { return (int)getLineWidthFloat (); } /** @see Graphics#getLineWidthFloat() */ @Override public float getLineWidthFloat () { return getLocalLineWidth (); } /** @see Graphics#getLineAttributes() */ @Override public LineAttributes getLineAttributes () { final LineAttributes a = this.graphics.getLineAttributes (); a.width = getLocalLineWidth (); return a; } private Font getLocalFont () { return this.localFont; } private float getLocalLineWidth () { return this.localLineWidth; } /** * @see Graphics#getTextAntialias() */ @Override public int getTextAntialias () { return this.graphics.getTextAntialias (); } /** @see Graphics#getXORMode() */ @Override public boolean getXORMode () { return this.graphics.getXORMode (); } /** @see Graphics#popState() */ @Override public void popState () { this.graphics.popState (); this.stackPointer--; restoreLocalState ( (State)this.stack.get ( this.stackPointer ) ); } /** @see Graphics#pushState() */ @Override public void pushState () { State s; if ( this.stack.size () > this.stackPointer ) { s = (State)this.stack.get ( this.stackPointer ); s.setValues ( this.zoom, this.fractionalX, this.fractionalY, getLocalFont (), this.localLineWidth ); } else { this.stack.add ( new State ( this.zoom, this.fractionalX, this.fractionalY, getLocalFont (), this.localLineWidth ) ); } this.stackPointer++; this.graphics.pushState (); } private void restoreLocalState ( final State state ) { this.fractionalX = state.appliedX; this.fractionalY = state.appliedY; setScale ( state.zoom ); setLocalFont ( state.font ); setLocalLineWidth ( state.lineWidth ); } /** @see Graphics#restoreState() */ @Override public void restoreState () { this.graphics.restoreState (); restoreLocalState ( (State)this.stack.get ( this.stackPointer - 1 ) ); } /** @see Graphics#rotate(float) */ @Override public void rotate ( final float degrees ) { this.graphics.rotate ( degrees ); } /** @see Graphics#scale(double) */ @Override public void scale ( final double amount ) { setScale ( this.zoom * amount ); } /** @see Graphics#setAdvanced(boolean) */ @Override public void setAdvanced ( final boolean advanced ) { this.graphics.setAdvanced ( advanced ); } /** * @see Graphics#setAlpha(int) */ @Override public void setAlpha ( final int alpha ) { this.graphics.setAlpha ( alpha ); } /** * @see Graphics#setAntialias(int) */ @Override public void setAntialias ( final int value ) { this.graphics.setAntialias ( value ); } /** @see Graphics#setBackgroundColor(Color) */ @Override public void setBackgroundColor ( final Color rgb ) { this.graphics.setBackgroundColor ( rgb ); } /** @see Graphics#setClip(Path) */ @Override public void setClip ( final Path path ) { final Path scaledPath = createScaledPath ( path ); try { this.graphics.setClip ( scaledPath ); } finally { scaledPath.dispose (); } } /** @see Graphics#setBackgroundPattern(Pattern) */ @Override public void setBackgroundPattern ( final Pattern pattern ) { this.graphics.setBackgroundPattern ( pattern ); } /** @see Graphics#setClip(Rectangle) */ @Override public void setClip ( final Rectangle r ) { this.graphics.setClip ( zoomClipRect ( r ) ); } /** * @see org.eclipse.draw2d.Graphics#clipPath(org.eclipse.swt.graphics.Path) */ @Override public void clipPath ( final Path path ) { final Path scaledPath = createScaledPath ( path ); try { this.graphics.clipPath ( scaledPath ); } finally { scaledPath.dispose (); } } /** * @see Graphics#setFillRule(int) */ @Override public void setFillRule ( final int rule ) { this.graphics.setFillRule ( rule ); } /** @see Graphics#setFont(Font) */ @Override public void setFont ( final Font f ) { setLocalFont ( f ); } /** @see Graphics#setForegroundColor(Color) */ @Override public void setForegroundColor ( final Color rgb ) { this.graphics.setForegroundColor ( rgb ); } /** @see Graphics#setForegroundPattern(Pattern) */ @Override public void setForegroundPattern ( final Pattern pattern ) { this.graphics.setForegroundPattern ( pattern ); } /** @see org.eclipse.draw2d.Graphics#setInterpolation(int) */ @Override public void setInterpolation ( final int interpolation ) { this.graphics.setInterpolation ( interpolation ); } /** * @see Graphics#setLineCap(int) */ @Override public void setLineCap ( final int cap ) { this.graphics.setLineCap ( cap ); } /** * @see Graphics#setLineDash(int[]) */ @Override public void setLineDash ( final int[] dash ) { this.graphics.setLineDash ( dash ); } /** * @see org.eclipse.draw2d.Graphics#setLineDash(float[]) */ @Override public void setLineDash ( final float[] dash ) { this.graphics.setLineDash ( dash ); } /** * @see Graphics#setLineJoin(int) */ @Override public void setLineJoin ( final int join ) { this.graphics.setLineJoin ( join ); } /** @see Graphics#setLineStyle(int) */ @Override public void setLineStyle ( final int style ) { this.graphics.setLineStyle ( style ); } /** @see Graphics#setLineMiterLimit(float) */ @Override public void setLineMiterLimit ( final float value ) { this.graphics.setLineMiterLimit ( value ); } /** @see Graphics#setLineWidth(int) */ @Override public void setLineWidth ( final int width ) { setLineWidthFloat ( width ); } /** @see Graphics#setLineWidthFloat(float) */ @Override public void setLineWidthFloat ( final float width ) { setLocalLineWidth ( width ); } /** @see Graphics#setLineAttributes(LineAttributes) */ @Override public void setLineAttributes ( final LineAttributes attributes ) { this.graphics.setLineAttributes ( attributes ); setLocalLineWidth ( attributes.width ); } private void setLocalFont ( final Font f ) { this.localFont = f; this.graphics.setFont ( zoomFont ( f ) ); } private void setLocalLineWidth ( final float width ) { this.localLineWidth = width; this.graphics.setLineWidthFloat ( zoomLineWidth ( width ) ); } public void setScale ( final double value ) { if ( this.zoom != value ) { this.zoom = value; this.graphics.setFont ( zoomFont ( getLocalFont () ) ); this.graphics.setLineWidthFloat ( zoomLineWidth ( this.localLineWidth ) ); } } /** * @see Graphics#setTextAntialias(int) */ @Override public void setTextAntialias ( final int value ) { this.graphics.setTextAntialias ( value ); } /** @see Graphics#setXORMode(boolean) */ @Override public void setXORMode ( final boolean b ) { this.graphics.setXORMode ( b ); } /** @see Graphics#translate(int, int) */ @Override public void translate ( final int dx, final int dy ) { // fractionalX/Y is the fractional part left over from previous // translates that gets lost in the integer approximation. final double dxFloat = dx * this.zoom + this.fractionalX; final double dyFloat = dy * this.zoom + this.fractionalY; this.fractionalX = dxFloat - Math.floor ( dxFloat ); this.fractionalY = dyFloat - Math.floor ( dyFloat ); this.graphics.translate ( (int)Math.floor ( dxFloat ), (int)Math.floor ( dyFloat ) ); } /** @see Graphics#translate(float, float) */ @Override public void translate ( final float dx, final float dy ) { final double dxFloat = dx * this.zoom + this.fractionalX; final double dyFloat = dy * this.zoom + this.fractionalY; this.fractionalX = dxFloat - Math.floor ( dxFloat ); this.fractionalY = dyFloat - Math.floor ( dyFloat ); this.graphics.translate ( (int)Math.floor ( dxFloat ), (int)Math.floor ( dyFloat ) ); } private Rectangle zoomClipRect ( final Rectangle r ) { this.tempRECT.x = (int)Math.floor ( r.x * this.zoom + this.fractionalX ); this.tempRECT.y = (int)Math.floor ( r.y * this.zoom + this.fractionalY ); this.tempRECT.width = (int)Math.ceil ( ( r.x + r.width ) * this.zoom + this.fractionalX ) - this.tempRECT.x; this.tempRECT.height = (int)Math.ceil ( ( r.y + r.height ) * this.zoom + this.fractionalY ) - this.tempRECT.y; return this.tempRECT; } private Rectangle zoomFillRect ( final int x, final int y, final int w, final int h ) { this.tempRECT.x = (int)Math.floor ( x * this.zoom + this.fractionalX ); this.tempRECT.y = (int)Math.floor ( y * this.zoom + this.fractionalY ); this.tempRECT.width = (int)Math.floor ( ( x + w - 1 ) * this.zoom + this.fractionalX ) - this.tempRECT.x + 1; this.tempRECT.height = (int)Math.floor ( ( y + h - 1 ) * this.zoom + this.fractionalY ) - this.tempRECT.y + 1; return this.tempRECT; } Font zoomFont ( Font f ) { if ( f == null ) { f = Display.getCurrent ().getSystemFont (); } final FontData data = getCachedFontData ( f ); final int zoomedFontHeight = zoomFontHeight ( data.getHeight () ); this.allowText = zoomedFontHeight > 0; this.fontKey.setValues ( f, zoomedFontHeight ); return getCachedFont ( this.fontKey ); } int zoomFontHeight ( final int height ) { return (int) ( this.zoom * height ); } float zoomLineWidth ( final float w ) { if ( w == 1.0f ) { return 1.0f; } return (float) ( w * this.zoom ); } private int[] zoomPointList ( final int[] points ) { int[] scaled = null; // Look in cache for a integer array with the same length as 'points' for ( int i = 0; i < intArrayCache.length; i++ ) { if ( intArrayCache[i].length == points.length ) { scaled = intArrayCache[i]; // Move this integer array up one notch in the array if ( i != 0 ) { final int[] temp = intArrayCache[i - 1]; intArrayCache[i - 1] = scaled; intArrayCache[i] = temp; } } } // If no match is found, take the one that is last and resize it. if ( scaled == null ) { intArrayCache[intArrayCache.length - 1] = new int[points.length]; scaled = intArrayCache[intArrayCache.length - 1]; } // Scale the points for ( int i = 0; i + 1 < points.length; i += 2 ) { scaled[i] = (int)Math.floor ( points[i] * this.zoom + this.fractionalX ); scaled[i + 1] = (int)Math.floor ( points[i + 1] * this.zoom + this.fractionalY ); } return scaled; } private Rectangle zoomRect ( final int x, final int y, final int w, final int h ) { this.tempRECT.x = (int)Math.floor ( x * this.zoom + this.fractionalX ); this.tempRECT.y = (int)Math.floor ( y * this.zoom + this.fractionalY ); this.tempRECT.width = (int)Math.floor ( ( x + w ) * this.zoom + this.fractionalX ) - this.tempRECT.x; this.tempRECT.height = (int)Math.floor ( ( y + h ) * this.zoom + this.fractionalY ) - this.tempRECT.y; return this.tempRECT; } private TextLayout zoomTextLayout ( final TextLayout layout ) { final TextLayout zoomed = new TextLayout ( Display.getCurrent () ); zoomed.setText ( layout.getText () ); int zoomWidth = -1; if ( layout.getWidth () != -1 ) { zoomWidth = (int) ( layout.getWidth () * this.zoom ); } if ( zoomWidth < -1 || zoomWidth == 0 ) { return null; } zoomed.setFont ( zoomFont ( layout.getFont () ) ); zoomed.setAlignment ( layout.getAlignment () ); zoomed.setAscent ( layout.getAscent () ); zoomed.setDescent ( layout.getDescent () ); zoomed.setOrientation ( layout.getOrientation () ); zoomed.setSegments ( layout.getSegments () ); zoomed.setSpacing ( layout.getSpacing () ); zoomed.setTabs ( layout.getTabs () ); zoomed.setWidth ( zoomWidth ); final int length = layout.getText ().length (); if ( length > 0 ) { int start = 0, offset = 1; TextStyle style = null, lastStyle = layout.getStyle ( 0 ); for ( ; offset <= length; offset++ ) { if ( offset != length && ( style = layout.getStyle ( offset ) ) == lastStyle ) { continue; } final int end = offset - 1; if ( lastStyle != null ) { final TextStyle zoomedStyle = new TextStyle ( zoomFont ( lastStyle.font ), lastStyle.foreground, lastStyle.background ); zoomedStyle.metrics = lastStyle.metrics; zoomedStyle.rise = lastStyle.rise; zoomedStyle.strikeout = lastStyle.strikeout; zoomedStyle.underline = lastStyle.underline; zoomed.setStyle ( zoomedStyle, start, end ); } lastStyle = style; start = offset; } } return zoomed; } private Point zoomTextPoint ( final int x, final int y ) { if ( this.localCache.font != this.localFont ) { // Font is different, re-calculate its height final FontMetrics metric = FigureUtilities.getFontMetrics ( this.localFont ); this.localCache.height = metric.getHeight () - metric.getDescent (); this.localCache.font = this.localFont; } if ( this.targetCache.font != this.graphics.getFont () ) { final FontMetrics metric = this.graphics.getFontMetrics (); this.targetCache.font = this.graphics.getFont (); this.targetCache.height = metric.getHeight () - metric.getDescent (); } return new Point ( (int)Math.floor ( x * this.zoom + this.fractionalX ), (int)Math.floor ( ( y + this.localCache.height - 1 ) * this.zoom - this.targetCache.height + 1 + this.fractionalY ) ); } }