view dwtx/draw2d/ScaledGraphics.d @ 192:c3583c6ec027

Added missing default cases for switch statements
author Frank Benoit <benoit@tionex.de>
date Mon, 03 Nov 2008 22:52:26 +0100
parents 2d6540440fe6
children
line wrap: on
line source

/*******************************************************************************
 * Copyright (c) 2000, 2008 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
 * Port to the D programming language:
 *     Frank Benoit <benoit@tionex.de>
 *******************************************************************************/
module dwtx.draw2d.ScaledGraphics;

import dwt.dwthelper.utils;
import dwtx.dwtxhelper.Collection;

import dwt.DWT;
import dwt.graphics.Color;
import dwt.graphics.Font;
import dwt.graphics.FontData;
import dwt.graphics.FontMetrics;
import dwt.graphics.Image;
import dwt.graphics.Path;
import dwt.graphics.PathData;
import dwt.graphics.TextLayout;
import dwt.graphics.TextStyle;
import dwt.widgets.Display;
static import dwt.graphics.Rectangle;
import dwtx.draw2d.geometry.Point;
import dwtx.draw2d.geometry.PointList;
import dwtx.draw2d.geometry.Rectangle;
import dwtx.draw2d.Graphics;
import dwtx.draw2d.FigureUtilities;

/**
 * A Graphics object able to scale all operations based on the current scale factor.
 */
public class ScaledGraphics
    : Graphics
{
alias Graphics.translate translate;
alias Graphics.fillRectangle fillRectangle;

private static class FontHeightCache {
    Font font;
    int height;
}

static class FontKey {
    Font font;
    int height;
    protected this() { }
    protected this(Font font, int height) {
        this.font = font;
        this.height = height;
    }

    public override int opEquals(Object obj) {
        return ((cast(FontKey)obj).font.opEquals(font)
                && (cast(FontKey)obj).height is height);
    }

    public override hash_t toHash() {
        return font.toHash() ^ height;
    }

    protected void setValues(Font font, 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 int lineWidth;
    private double zoom;

    /**
     * Constructs a new, uninitialized State object.
     */
    protected this() { }

    /**
     * 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 this(double zoom, double x, double y, Font font, int 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(double zoom, double x, double y,
                                Font font, int lineWidth) {
        this.zoom = zoom;
        this.appliedX = x;
        this.appliedY = y;
        this.font = font;
        this.lineWidth = lineWidth;
    }
}

private static int[][] intArrayCache_;
private static int[][] intArrayCache(){
    if( intArrayCache_ is null ){
        synchronized( ScaledGraphics.classinfo ){
            if( intArrayCache_ is null ){
                intArrayCache_ = new int[][](8);
                for (int i = 0; i < intArrayCache_.length; i++)
                    intArrayCache_[i] = new int[i + 1];
            }
        }
    }
    return intArrayCache_;
}
private const Rectangle tempRECT;

private bool allowText = true;
//private static final Point PT = new Point();
private Map fontCache;
private Map fontDataCache;
private FontKey fontKey;
private double fractionalX;
private double fractionalY;
private Graphics graphics;
private FontHeightCache localCache;
private Font localFont;
private int localLineWidth;
private List stack;
private int stackPointer = 0;
private FontHeightCache targetCache;

double zoom = 1.0;

/**
 * Constructs a new ScaledGraphics based on the given Graphics object.
 * @param g the base graphics object
 */
public this(Graphics g) {
    // instance init
    tempRECT = new Rectangle();
    localCache = new FontHeightCache();
    targetCache = new FontHeightCache();
    stack = new ArrayList();
    fontKey = new FontKey();
    fontDataCache = new HashMap();
    fontCache = new HashMap();

    graphics = g;
    localFont = g.getFont();
    localLineWidth = g.getLineWidth();
}

/** @see Graphics#clipRect(Rectangle) */
public void clipRect(Rectangle r) {
    graphics.clipRect(zoomClipRect(r));
}

Font createFont(FontData data) {
    return new Font(Display.getCurrent(), data);
}

private Path createScaledPath(Path path) {
    PathData p = path.getPathData();
    for (int i = 0; i < p.points.length; i += 2) {
        p.points[i] = cast(float) (p.points[i] * zoom + fractionalX);
        p.points[i + 1] = cast(float) (p.points[i + 1] * zoom + fractionalY);
    }
    Path scaledPath = new Path(path.getDevice());
    int index = 0;
    for (int i = 0; i < p.types.length; i++) {
        byte type = p.types[i];
        switch (type) {
            case DWT.PATH_MOVE_TO:
                scaledPath.moveTo(p.points[index], p.points[index + 1]);
                index += 2;
                break;
            case DWT.PATH_LINE_TO:
                scaledPath.lineTo(p.points[index], p.points[index + 1]);
                index += 2;
                break;
            case DWT.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 DWT.PATH_QUAD_TO:
                scaledPath.quadTo(p.points[index], p.points[index + 1],
                        p.points[index + 2], p.points[index + 3]);
                index += 4;
                break;
            case DWT.PATH_CLOSE:
                scaledPath.close();
                break;
            default:
        }
    }
    return scaledPath;
}

/** @see Graphics#dispose() */
public void dispose() {
    //Remove all states from the stack
    while (stackPointer > 0) {
        popState();
    }

    //Dispose fonts
    Iterator iter = fontCache.values().iterator();
    while (iter.hasNext()) {
        Font font = (cast(Font)iter.next());
        font.dispose();
    }

}

/** @see Graphics#drawArc(int, int, int, int, int, int) */
public void drawArc(int x, int y, int w, int h, int offset, int sweep) {
    Rectangle z = zoomRect(x, y, w, h);
    if (z.isEmpty() || sweep is 0)
        return;
    graphics.drawArc(z, offset, sweep);
}

/** @see Graphics#drawFocus(int, int, int, int) */
public void drawFocus(int x, int y, int w, int h) {
    graphics.drawFocus(zoomRect(x, y, w, h));
}

/** @see Graphics#drawImage(Image, int, int) */
public void drawImage(Image srcImage, int x, int y) {
    dwt.graphics.Rectangle.Rectangle size = srcImage.getBounds();
    graphics.drawImage(srcImage, 0, 0, size.width, size.height,
        cast(int)(Math.floor((x * zoom + fractionalX))),
        cast(int)(Math.floor((y * zoom + fractionalY))),
        cast(int)(Math.floor((size.width * zoom + fractionalX))),
        cast(int)(Math.floor((size.height * zoom + fractionalY))));
}

/** @see Graphics#drawImage(Image, int, int, int, int, int, int, int, int) */
public void drawImage(Image srcImage, int sx, int sy, int sw, int sh,
                                        int tx, int ty, int tw, int th) {
    //"t" is target rectangle, "s" = source

    Rectangle t = zoomRect(tx, ty, tw, th);
    if (!t.isEmpty())
        graphics.drawImage(srcImage, sx, sy, sw, sh, t.x, t.y, t.width, t.height);
}

/** @see Graphics#drawLine(int, int, int, int) */
public void drawLine(int x1, int y1, int x2, int y2) {
    graphics.drawLine(
        cast(int)(Math.floor((x1 * zoom + fractionalX))),
        cast(int)(Math.floor((y1 * zoom + fractionalY))),
        cast(int)(Math.floor((x2 * zoom + fractionalX))),
        cast(int)(Math.floor((y2 * zoom + fractionalY))));
}

/** @see Graphics#drawOval(int, int, int, int) */
public void drawOval(int x, int y, int w, int h) {
    graphics.drawOval(zoomRect(x, y, w, h));
}

/** @see Graphics#drawPath(Path) */
public void drawPath(Path path) {
    Path scaledPath = createScaledPath(path);
    try {
        graphics.drawPath(scaledPath);
    } finally {
        scaledPath.dispose();
    }
}

/** @see Graphics#drawPoint(int, int) */
public void drawPoint(int x, int y) {
    graphics.drawPoint(cast(int)Math.floor(x * zoom + fractionalX),
            cast(int)Math.floor(y * zoom + fractionalY));
}

/**
 * @see Graphics#drawPolygon(int[])
 */
public void drawPolygon(int[] points) {
    graphics.drawPolygon(zoomPointList(points));
}

/** @see Graphics#drawPolygon(PointList) */
public void drawPolygon(PointList points) {
    graphics.drawPolygon(zoomPointList(points.toIntArray()));
}

/**
 * @see Graphics#drawPolyline(int[])
 */
public void drawPolyline(int[] points) {
    graphics.drawPolyline(zoomPointList(points));
}

/** @see Graphics#drawPolyline(PointList) */
public void drawPolyline(PointList points) {
    graphics.drawPolyline(zoomPointList(points.toIntArray()));
}

/** @see Graphics#drawRectangle(int, int, int, int) */
public void drawRectangle(int x, int y, int w, int h) {
    graphics.drawRectangle(zoomRect(x, y, w, h));
}

/** @see Graphics#drawRoundRectangle(Rectangle, int, int) */
public void drawRoundRectangle(Rectangle r, int arcWidth, int arcHeight) {
    graphics.drawRoundRectangle(zoomRect(r.x, r.y, r.width, r.height),
        cast(int)(arcWidth * zoom),
        cast(int)(arcHeight * zoom));
}

/** @see Graphics#drawString(String, int, int) */
public void drawString(String s, int x, int y) {
    if (allowText)
        graphics.drawString(s, zoomTextPoint(x, y));
}

/** @see Graphics#drawText(String, int, int) */
public void drawText(String s, int x, int y) {
    if (allowText)
        graphics.drawText(s, zoomTextPoint(x, y));
}

/**
 * @see Graphics#drawText(String, int, int, int)
 */
public void drawText(String s, int x, int y, int style) {
    if (allowText)
        graphics.drawText(s, zoomTextPoint(x, y), style);
}

/**
 * @see Graphics#drawTextLayout(TextLayout, int, int, int, int, Color, Color)
 */
public void drawTextLayout(TextLayout layout, int x, int y, int selectionStart,
        int selectionEnd, Color selectionForeground, Color selectionBackground) {
    TextLayout scaled = zoomTextLayout(layout);
    graphics.drawTextLayout(scaled,
            cast(int)Math.floor(x * zoom + fractionalX),
            cast(int)Math.floor(y * zoom + fractionalY),
            selectionStart, selectionEnd, selectionBackground, selectionForeground);
    scaled.dispose();
}

/** @see Graphics#fillArc(int, int, int, int, int, int) */
public void fillArc(int x, int y, int w, int h, int offset, int sweep) {
    Rectangle z = zoomFillRect(x, y, w, h);
    if (z.isEmpty() || sweep is 0)
        return;
    graphics.fillArc(z, offset, sweep);
}

/** @see Graphics#fillGradient(int, int, int, int, bool) */
public void fillGradient(int x, int y, int w, int h, bool vertical) {
    graphics.fillGradient(zoomFillRect(x, y, w, h), vertical);
}

/** @see Graphics#fillOval(int, int, int, int) */
public void fillOval(int x, int y, int w, int h) {
    graphics.fillOval(zoomFillRect(x, y, w, h));
}

/** @see Graphics#fillPath(Path) */
public void fillPath(Path path) {
    Path scaledPath = createScaledPath(path);
    try {
        graphics.fillPath(scaledPath);
    } finally {
        scaledPath.dispose();
    }
}

/**
 * @see Graphics#fillPolygon(int[])
 */
public void fillPolygon(int[] points) {
    graphics.fillPolygon(zoomPointList(points));
}

/** @see Graphics#fillPolygon(PointList) */
public void fillPolygon(PointList points) {
    graphics.fillPolygon(zoomPointList(points.toIntArray()));
}

/** @see Graphics#fillRectangle(int, int, int, int) */
public void fillRectangle(int x, int y, int w, int h) {
    graphics.fillRectangle(zoomFillRect(x, y, w, h));
}

/** @see Graphics#fillRoundRectangle(Rectangle, int, int) */
public void fillRoundRectangle(Rectangle r, int arcWidth, int arcHeight) {
    graphics.fillRoundRectangle(zoomFillRect(r.x, r.y, r.width, r.height),
        cast(int)(arcWidth * zoom),
        cast(int)(arcHeight * zoom));
}

/** @see Graphics#fillString(String, int, int) */
public void fillString(String s, int x, int y) {
    if (allowText)
        graphics.fillString(s, zoomTextPoint(x, y));
}

/** @see Graphics#fillText(String, int, int) */
public void fillText(String s, int x, int y) {
    if (allowText)
        graphics.fillText(s, zoomTextPoint(x, y));
}

/**
 * @see Graphics#getAbsoluteScale()
 */
public double getAbsoluteScale() {
    return zoom * graphics.getAbsoluteScale();
}

/**
 * @see Graphics#getAlpha()
 */
public int getAlpha() {
    return graphics.getAlpha();
}

/**
 * @see Graphics#getAntialias()
 */
public int getAntialias() {
    return graphics.getAntialias();
}

/** @see Graphics#getBackgroundColor() */
public Color getBackgroundColor() {
    return graphics.getBackgroundColor();
}

Font getCachedFont(FontKey key) {
    Font font = cast(Font)fontCache.get(key);
    if (font !is null)
        return font;
    key = new FontKey(key.font, key.height);
    FontData data = key.font.getFontData()[0];
    data.setHeight(key.height);
    Font zoomedFont = createFont(data);
    fontCache.put(key, zoomedFont);
    return zoomedFont;
}

FontData getCachedFontData(Font f) {
    FontData data = cast(FontData)fontDataCache.get(f);
    if (data !is null)
        return data;
    data = getLocalFont().getFontData()[0];
    fontDataCache.put(f, data);
    return data;
}

/** @see Graphics#getClip(Rectangle) */
public Rectangle getClip(Rectangle rect) {
    graphics.getClip(rect);
    int x = cast(int)(rect.x / zoom);
    int y = cast(int)(rect.y / 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 = cast(int)Math.ceil(rect.right() / zoom) - x;
    rect.height = cast(int)Math.ceil(rect.bottom() / zoom) - y;
    rect.x = x;
    rect.y = y;
    return rect;
}

/**
 * @see Graphics#getFillRule()
 */
public int getFillRule() {
    return graphics.getFillRule();
}

/** @see Graphics#getFont() */
public Font getFont() {
    return getLocalFont();
}

/** @see Graphics#getFontMetrics() */
public FontMetrics getFontMetrics() {
    return FigureUtilities.getFontMetrics(localFont);
}

/** @see Graphics#getForegroundColor() */
public Color getForegroundColor() {
    return graphics.getForegroundColor();
}

/**
 * @see Graphics#getInterpolation()
 */
public int getInterpolation() {
    return graphics.getInterpolation();
}

/**
 * @see Graphics#getLineCap()
 */
public int getLineCap() {
    return graphics.getLineCap();
}

/**
 * @see Graphics#getLineJoin()
 */
public int getLineJoin() {
    return graphics.getLineJoin();
}

/** @see Graphics#getLineStyle() */
public int getLineStyle() {
    return graphics.getLineStyle();
}

/** @see Graphics#getLineWidth() */
public int getLineWidth() {
    return getLocalLineWidth();
}

private Font getLocalFont() {
    return localFont;
}

private int getLocalLineWidth() {
    return localLineWidth;
}

/**
 * @see Graphics#getTextAntialias()
 */
public int getTextAntialias() {
    return graphics.getTextAntialias();
}

/** @see Graphics#getXORMode() */
public bool getXORMode() {
    return graphics.getXORMode();
}

/** @see Graphics#popState() */
public void popState() {
    graphics.popState();
    stackPointer--;
    restoreLocalState(cast(State)stack.get(stackPointer));
}

/** @see Graphics#pushState() */
public void pushState() {
    State s;
    if (stack.size() > stackPointer) {
        s = cast(State)stack.get(stackPointer);
        s.setValues(zoom, fractionalX, fractionalY, getLocalFont(), localLineWidth);
    } else {
        stack.add(new State(zoom, fractionalX, fractionalY, getLocalFont(),
                                localLineWidth));
    }
    stackPointer++;

    graphics.pushState();
}

private void restoreLocalState(State state) {
    this.fractionalX = state.appliedX;
    this.fractionalY = state.appliedY;
    setScale(state.zoom);
    setLocalFont(state.font);
    setLocalLineWidth(state.lineWidth);
}

/** @see Graphics#restoreState() */
public void restoreState() {
    graphics.restoreState();
    restoreLocalState(cast(State)stack.get(stackPointer - 1));
}

/** @see Graphics#scale(double) */
public void scale(double amount) {
    setScale(zoom * amount);
}

/**
 * @see Graphics#setAlphacast(int)
 */
public void setAlpha(int alpha) {
    graphics.setAlpha(alpha);
}

/**
 * @see Graphics#setAntialiascast(int)
 */
public void setAntialias(int value) {
    graphics.setAntialias(value);
}

/** @see Graphics#setBackgroundColor(Color) */
public void setBackgroundColor(Color rgb) {
    graphics.setBackgroundColor(rgb);
}

/** @see Graphics#setClip(Path) */
public void setClip(Path path) {
    Path scaledPath = createScaledPath(path);
    try {
        graphics.setClip(scaledPath);
    } finally {
        scaledPath.dispose();
    }
}

/** @see Graphics#setClip(Rectangle) */
public void setClip(Rectangle r) {
    graphics.setClip(zoomClipRect(r));
}

/**
 * @see Graphics#setFillRulecast(int)
 */
public void setFillRule(int rule) {
    graphics.setFillRule(rule);
}

/** @see Graphics#setFontcast(Font) */
public void setFont(Font f) {
    setLocalFont(f);
}

/** @see Graphics#setForegroundColor(Color) */
public void setForegroundColor(Color rgb) {
    graphics.setForegroundColor(rgb);
}

/**
 * @see dwtx.draw2d.Graphics#setInterpolationcast(int)
 */
public void setInterpolation(int interpolation) {
    graphics.setInterpolation(interpolation);
}

/**
 * @see Graphics#setLineCapcast(int)
 */
public void setLineCap(int cap) {
    graphics.setLineCap(cap);
}

/**
 * @see Graphics#setLineDash(int[])
 */
public void setLineDash(int[] dash) {
    graphics.setLineDash(dash);
}

/**
 * @see Graphics#setLineJoincast(int)
 */
public void setLineJoin(int join) {
    graphics.setLineJoin(join);
}

/** @see Graphics#setLineStylecast(int) */
public void setLineStyle(int style) {
    graphics.setLineStyle(style);
}

/** @see Graphics#setLineWidthcast(int) */
public void setLineWidth(int width) {
    setLocalLineWidth(width);
}

private void setLocalFont(Font f) {
    localFont = f;
    graphics.setFont(zoomFont(f));
}

private void setLocalLineWidth(int width) {
    localLineWidth = width;
    graphics.setLineWidth(zoomLineWidth(width));
}

void setScale(double value) {
    if (zoom is value)
        return;
    this.zoom = value;
    graphics.setFont(zoomFont(getLocalFont()));
    graphics.setLineWidth(zoomLineWidth(localLineWidth));
}

/**
 * @see Graphics#setTextAntialiascast(int)
 */
public void setTextAntialias(int value) {
    graphics.setTextAntialias(value);
}

/** @see Graphics#setXORMode(bool) */
public void setXORMode(bool b) {
    graphics.setXORMode(b);
}

/** @see Graphics#translate(int, int) */
public void translate(int dx, int dy) {
    // fractionalX/Y is the fractional part left over from previous
    // translates that gets lost in the integer approximation.
    double dxFloat = dx * zoom + fractionalX;
    double dyFloat = dy * zoom + fractionalY;
    fractionalX = dxFloat - Math.floor(dxFloat);
    fractionalY = dyFloat - Math.floor(dyFloat);
    graphics.translate(cast(int)Math.floor(dxFloat), cast(int)Math.floor(dyFloat));
}

/** @see Graphics#translate(float, float) */
public void translate(float dx, float dy) {
    double dxFloat = dx * zoom + fractionalX;
    double dyFloat = dy * zoom + fractionalY;
    fractionalX = dxFloat - Math.floor(dxFloat);
    fractionalY = dyFloat - Math.floor(dyFloat);
    graphics.translate(cast(int)Math.floor(dxFloat), cast(int)Math.floor(dyFloat));
}

private Rectangle zoomClipRect(Rectangle r) {
    tempRECT.x = cast(int)(Math.floor(r.x * zoom + fractionalX));
    tempRECT.y = cast(int)(Math.floor(r.y * zoom + fractionalY));
    tempRECT.width = cast(int)(Math.ceil(((r.x + r.width) * zoom + fractionalX))) - tempRECT.x;
    tempRECT.height = cast(int)(Math.ceil(((r.y + r.height) * zoom + fractionalY))) - tempRECT.y;
    return tempRECT;
}

private Rectangle zoomFillRect(int x, int y, int w, int h) {
    tempRECT.x = cast(int)(Math.floor((x * zoom + fractionalX)));
    tempRECT.y = cast(int)(Math.floor((y * zoom + fractionalY)));
    tempRECT.width = cast(int)(Math.floor(((x + w - 1) * zoom + fractionalX))) - tempRECT.x + 1;
    tempRECT.height = cast(int)(Math.floor(((y + h - 1) * zoom + fractionalY))) - tempRECT.y + 1;
    return tempRECT;
}

Font zoomFont(Font f) {
    if (f is null)
        f = Display.getCurrent().getSystemFont();
    FontData data = getCachedFontData(f);
    int zoomedFontHeight = zoomFontHeight(data.getHeight());
    allowText = zoomedFontHeight > 0;
    fontKey.setValues(f, zoomedFontHeight);
    return getCachedFont(fontKey);
}

int zoomFontHeight(int height) {
    return cast(int)(zoom * height);
}

int zoomLineWidth(int w) {
    return w;
}

private int[] zoomPointList(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 is points.length) {
            scaled = intArrayCache[i];

            // Move this integer array up one notch in the array
            if (i !is 0) {
                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 is 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] = cast(int)(Math.floor((points[i] * zoom + fractionalX)));
        scaled[i + 1] = cast(int)(Math.floor((points[i + 1] * zoom + fractionalY)));
    }
    return scaled;
}

private Rectangle zoomRect(int x, int y, int w, int h) {
    tempRECT.x = cast(int)(Math.floor(x * zoom + fractionalX));
    tempRECT.y = cast(int)(Math.floor(y * zoom + fractionalY));
    tempRECT.width = cast(int)(Math.floor(((x + w) * zoom + fractionalX))) - tempRECT.x;
    tempRECT.height = cast(int)(Math.floor(((y + h) * zoom + fractionalY))) - tempRECT.y;
    return tempRECT;
}

private TextLayout zoomTextLayout(TextLayout layout) {
    TextLayout zoomed = new TextLayout(Display.getCurrent());
    zoomed.setText(layout.getText());

    int zoomWidth = -1;

    if (layout.getWidth() !is -1)
        zoomWidth = (cast(int)(layout.getWidth() * zoom));

    if (zoomWidth < -1 || zoomWidth is 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);
    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 !is length
                    && (style = layout.getStyle(offset)) is lastStyle)
                continue;
            int end = offset - 1;

            if (lastStyle !is null) {
                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(int x, int y) {
    if (localCache.font !is localFont) {
        //Font is different, re-calculate its height
        FontMetrics metric = FigureUtilities.getFontMetrics(localFont);
        localCache.height = metric.getHeight() - metric.getDescent();
        localCache.font = localFont;
    }
    if (targetCache.font !is graphics.getFont()) {
        FontMetrics metric = graphics.getFontMetrics();
        targetCache.font = graphics.getFont();
        targetCache.height = metric.getHeight() - metric.getDescent();
    }
    return new Point((cast(int)(Math.floor((x * zoom) + fractionalX))),
                        cast(int)(Math.floor((y + localCache.height - 1) * zoom
                                            - targetCache.height + 1 + fractionalY)));
}

}