Rogue101/src/inf101/v18/gfx/textmode/TextFont.java
Anya Helene Bagge c1ad21da8c ready!
2018-02-28 23:51:40 +01:00

982 lines
33 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package inf101.v18.gfx.textmode;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javafx.geometry.Point2D;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.text.Font;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
/**
* TextFont for grid-based text / character graphics
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*/
public class TextFont {
public static final int ATTR_INVERSE = 0x01;
public static final int ATTR_ITALIC = 0x02;
public static final int ATTR_BOLD = 0x04;
public static final int ATTR_OUTLINE = 0x08;
public static final int ATTR_UNDERLINE = 0x10;
public static final int ATTR_OVERLINE = 0x20;
public static final int ATTR_LINE_THROUGH = 0x40;
public static final int ATTR_OVERSTRIKE = 0x80;
public static final int ATTR_CLIP = 0x80;
public static final int ATTR_NO_FAKE_CHARS = 0x100;
public static final int ATTR_BLINK = 0x200; // NOT IMPLEMENTED
public static final int ATTR_FAINT = 0x400; // NOT IMPLEMENTED
public static final int ATTR_BRIGHT = 0x800;
private static final String[] searchPath = { "", "../", "../fonts/" };
private static final Map<String, String> loadedFonts = new HashMap<>();
private static final double thin = 2.0, thick = 4.0;
private static final String[] boxDrawingShapes = { // lines
"--..", "**..", "..--", "..**",
// dashed lines
"3--..", "3**..", "3..--", "3..**", "4--..", "4**..", "4..--", "4..**",
// corners
".-.-", ".*.-", ".-.*", ".*.*", "-..-", "*..-", "-..*", "*..*", ".--.", ".*-.", ".-*.", ".**.", "-.-.",
"*.-.", "-.*.", "*.*.",
// |-
".---", ".*--", ".-*- ", ".--* ", ".-**", ".**-", ".*-*", ".***", "-.--", "*.--", "-.*-", "-.-*", "-.**",
"*.*-", "*.-*", "*.**",
// T
"--.-", "*-.-", "-*.-", "**.-", "--.*", "*-.*", "-*.*", "**.*", "---.", "*--. ", "-*-.", "**-.", "--*.",
"*-*.", "-**.", "***.",
// +
"----", "*---", "-*--", "**--", "--*-", "---*", "--**", "*-*-", "-**-", "*--*", "-*-*", "***-", "**-*",
"*-**", "-***", "****",
// dashes
"2--..", "2**..", "2..--", "2..**",
// double lines
"==..", "..==", ".=.-", ".-.=", ".N.N", "=..-", "-..=", "M..M", ".=-.", ".-=.", ".MM.", "=.-.", "-.=.",
"N.N.", ".=--", ".s==", ".zMN", "=.--", "s.==", "z.NM", "==.s", "--.=", "MN.z", "==s.", "--=.", "NMz.",
"==--", "--==", "zzzz",
// round corners
"0.-.-", "0-..-", "0-.-.", "0.--.",
// diagonals
"////", "\\\\\\\\", "//\\\\\\",
// short lines
"-...", "..-.", ".-..", "...-", "*...", "..*.", ".*..", "...*",
// thin/thick lines
"-*..", "..-*", "*-..", "..*-" };
public static void drawMasked(GraphicsContext ctx, Image src, Image mask, double x, double y, boolean invert,
boolean blend) {
int w = (int) src.getWidth();
int h = (int) src.getHeight();
PixelReader pixelSrc = src.getPixelReader();
PixelReader pixelMask = mask.getPixelReader();
PixelWriter pixelWriter = ctx.getPixelWriter();
Affine transform = ctx.getTransform();
Point2D point = transform.transform(x, y);
int dx = (int) point.getX(), dy = (int) point.getY();
for (int px = 0; px < w; px++) {
for (int py = 0; py < h; py++) {
int a = pixelMask.getArgb(px, py) >>> 24;
int rgb = pixelSrc.getArgb(px, py);
if (invert)
a = ~a & 0xff;
if (blend)
a = ((rgb >>> 24) * a) >>> 8;
pixelWriter.setArgb(px + dx, py + dy, (a << 24) | rgb);
}
}
}
public static void fillInverse(GraphicsContext ctx, Image img, double x, double y) {
int w = (int) img.getWidth();
int h = (int) img.getHeight();
PixelReader pixelReader = img.getPixelReader();
PixelWriter pixelWriter = ctx.getPixelWriter();
Affine transform = ctx.getTransform();
Point2D point = transform.transform(x, y);
int dx = (int) point.getX(), dy = (int) point.getY();
Color c = ctx.getFill() instanceof Color ? (Color) ctx.getFill() : Color.BLACK;
int rgb = ((int) (c.getRed() * 255)) << 16 | ((int) (c.getGreen() * 255)) << 8 | ((int) (c.getBlue() * 255));
for (int px = 0; px < w; px++) {
for (int py = 0; py < h; py++) {
int a = (~pixelReader.getArgb(px, py) & 0xff000000);
// if(a != 0)
pixelWriter.setArgb(px + dx, py + dy, a | rgb);
}
}
}
/**
* Load a font.
*
* Will first try to use the provided string as a file name, and load the font
* from a font file (should be in a format supported by JavaFX). If loading from
* file fails, we assume we have been given the name of a font which is passed
* directly to JavaFX for loading. A fallback font is used if this fails.
*
* When looking for files, his method will search for the file in a search path
* starting with the folder containing the {@link TextFont} class file (using
* Java's standard {@link Class#getResourceAsStream(String)} system). The search
* path also includes ".." (parent directory) and "../fonts".
*
* The loaded font will be cached, so that additional calls with the same file
* name will not cause the file to be loaded again.
*
* If the font cannot be loaded, a default font will be substituted. You may
* check which font you got using {@link Font#getName()} or
* {@link Font#getFamily()}.
*
* @param name
* Font name, or relative path to the file
* @param size
* Desired point size of the font
* @return A JavaFX font
*/
public static final Font findFont(String name, double size) {
return findFont(name, size, TextFont.class);
}
/**
* Load a font.
*
* Will first try to use the provided string as a file name, and load the font
* from a font file (should be in a format supported by JavaFX). If loading from
* file fails, we assume we have been given the name of a font which is passed
* directly to JavaFX for loading. A fallback font is used if this fails.
*
* When looking for files, this method will search for the file in a search path
* starting with the folder containing the provided Java class (using Java's
* standard {@link Class#getResourceAsStream(String)} system). The search path
* also includes ".." (parent directory) and "../fonts".
*
* The loaded font will be cached, so that additional calls with the same file
* name will not cause the file to be loaded again.
*
* If the font cannot be loaded, a default font will be substituted. You may
* check which font you got using {@link Font#getName()} or
* {@link Font#getFamily()}.
*
* @param name
* Name of a font, or relative path to the font file
* @param size
* Desired point size of the font
* @param clazz
* A class for finding files relative to
* @return A JavaFX font
*/
public static final Font findFont(String name, double size, Class<?> clazz) {
if (name == null || size < 0)
throw new IllegalArgumentException();
if (loadedFonts.containsKey(name))
return Font.font(loadedFonts.get(name), size);
for (String path : searchPath) {
try (InputStream stream = clazz.getResourceAsStream(path + name)) {
Font font = Font.loadFont(stream, size);
if (font != null) {
loadedFonts.put(name, font.getName());
// System.err.println("Found: " + font.getName());
return font;
}
} catch (FileNotFoundException e) {
// we'll just try the next alternative in the search path
} catch (IOException e) {
e.printStackTrace();
}
}
Font font = Font.font(name, size);
if (font == null)
font = Font.font(size);
if (font == null)
throw new RuntimeException("Even the default font seems to be unavailable this shouldn't happen! :(");
if (font.getName().equals(Font.getDefault().getName())) {
System.err.println("TextFont: Default font '" + font.getName() + "' substituted for '" + name + "'");
}
// System.err.println("Found: " + name + "=" + font.getName());
loadedFonts.put(name, name);
return font;
}
private Canvas tmpCanvas;
private final WritableImage img;
/**
* The JavaFX font
*/
private Font font;
/** Font size */
private final double size;
/**
* Horizontal positioning of letters.
*
* Each letter should be approximately centered within its available
* square-shaped space.
*/
private final double xTranslate;
/**
* Vertical positioning of letters.
*
* Each letter should be positioned on the baseline so that ascenders and
* descenders fall within its available square-shaped space.
*/
private final double yTranslate;
/**
* Horizontal scaling factor (1.0 means no scaling)
*
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit
* a square shape.
*/
private final double xScale;
/**
* Vertical scaling factor (1.0 means no scaling)
*/
private final double yScale;
/**
* Width and height of the square-shaped space each letter should fit within
*/
private double squareSize;
private String fileName;
SnapshotParameters snapshotParameters = new SnapshotParameters();
{
snapshotParameters.setFill(Color.TRANSPARENT);
}
/**
* Create a new TextFont.
*
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size
* Point size of the font.
* @param squareSize
* The width and height of a square defining the bounds of letters
* @param xTranslate
* Horizontal positioning of letters
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
*/
public TextFont(Font font, double squareSize, double xTranslate, double yTranslate, double xScale, double yScale) {
super();
this.fileName = font.getName();
this.font = font;
this.size = font.getSize();
this.squareSize = squareSize;
this.xTranslate = xTranslate;
this.yTranslate = yTranslate;
this.xScale = xScale;
this.yScale = yScale;
this.img = new WritableImage((int) squareSize, (int) squareSize);
}
/**
* Create a new TextFont.
*
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size
* Point size of the font.
* @param squareSize
* The width and height of a square defining the bounds of letters
* @param xTranslate
* Horizontal positioning of letters
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
*/
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
double xScale, double yScale) {
super();
this.fileName = fileName;
this.font = findFont(fileName, size);
this.size = size;
this.squareSize = squareSize;
this.xTranslate = xTranslate;
this.yTranslate = yTranslate;
this.xScale = xScale;
this.yScale = yScale;
this.img = new WritableImage((int) squareSize, (int) squareSize);
}
/**
* Create a new TextFont.
*
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size
* Point size of the font.
* @param squareSize
* The width and height of a square defining the bounds of letters
* @param xTranslate
* Horizontal positioning of letters
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
* @param deferLoading
* True if the font file shouldn't be loaded before the font is
* actually used
*/
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
double xScale, double yScale, boolean deferLoading) {
super();
this.fileName = fileName;
this.font = deferLoading ? null : findFont(fileName, size);
this.size = size;
this.squareSize = squareSize;
this.xTranslate = xTranslate;
this.yTranslate = yTranslate;
this.xScale = xScale;
this.yScale = yScale;
this.img = new WritableImage((int) squareSize, (int) squareSize);
}
/**
* Create a copy of this font, with the given adjustments to the translation and
* scaling.
*
* @param deltaXTranslate
* @param deltaYTranslate
* @param deltaXScale
* @param deltaYScale
* @return
*/
public TextFont adjust(double size, double deltaXTranslate, double deltaYTranslate, double deltaXScale,
double deltaYScale) {
if (size == 0.0) {
return new TextFont(fileName, this.size, squareSize, xTranslate + deltaXTranslate,
yTranslate + deltaYTranslate, xScale + deltaXScale, yScale + deltaYScale);
} else {
return new TextFont(fileName, this.size + size, squareSize, xTranslate + deltaXTranslate,
yTranslate + deltaYTranslate, xScale + deltaXScale, yScale + deltaYScale);
}
}
/**
* Draw the given text at position (0,0).
*
* The <code>ctx</code> should normally be translated to the appropriate text
* position before calling this method.
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param text
* string to be printed
*/
public void drawText(GraphicsContext ctx, String text) {
textAt(ctx, 0.0, 0.0, text, 1.0, true, true, ctx.getFill(), null, 0, null);
}
/**
* Draw the given text at position (0,0), with horizontal scaling.
*
* The <code>ctx</code> should normally be translated to the appropriate text
* position before calling this method.
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param text
* string to be printed
* @param xScaleFactor
* a horizontal scaling factor
*/
public void drawText(GraphicsContext ctx, String text, double xScaleFactor) {
textAt(ctx, 0.0, 0.0, text, xScaleFactor, true, false, ctx.getFill(), null, 0, null);
}
/**
* Draw the given text at position (x,y).
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param x
* X-position of the lower left corner of the text
* @param y
* Y-position of the lower left corner of the text
* @param text
* string to be printed
*/
public void drawTextAt(GraphicsContext ctx, double x, double y, String text) {
textAt(ctx, x, y, text, 1.0, true, false, ctx.getFill(), null, 0, null);
}
/**
* Draw the given text at position (x, y), with horizontal scaling.
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param x
* X-position of the lower left corner of the text
* @param y
* Y-position of the lower left corner of the text
* @param text
* string to be printed
* @param xScaleFactor
* a horizontal scaling factor
*/
public void drawTextAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, int mode,
Paint bg) {
textAt(ctx, x, y, text, xScaleFactor, true, false, ctx.getFill(), null, mode, bg);
}
private void fakeBlockElement(GraphicsContext ctx, String text, double xScaleFactor) {
text.codePoints().forEach((int c) -> {
// System.out.printf("%s %x%n", text, c);
if (c >= 0x2596 && c <= 0x259f) { // quadrants
int bits = BlocksAndBoxes.unicodeBlocksString.indexOf(c);
if ((bits & 1) > 0) { // lower right
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize / 2, (squareSize / 2) * xScaleFactor,
squareSize / 2);
}
if ((bits & 2) > 0) { // lower left
ctx.fillRect(0, -squareSize / 2, (squareSize / 2) * xScaleFactor, squareSize / 2);
}
if ((bits & 4) > 0) { // upper right
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize, (squareSize / 2) * xScaleFactor,
squareSize / 2);
}
if ((bits & 8) > 0) { // upper left
ctx.fillRect(0, -squareSize, (squareSize / 2) * xScaleFactor, squareSize / 2);
}
} else if (c == 0x2580) { // upper half
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize / 2);
} else if (c > 0x2580 && c <= 0x2588) { // x/8 block
int height = c - 0x2580;
ctx.fillRect(0, -(height * squareSize) / 8, (squareSize) * xScaleFactor, (height * squareSize) / 8);
} else if (c >= 0x2589 && c <= 0x258f) { // x/8 block
int width = 8 - (c - 0x2588);
ctx.fillRect(0, -squareSize, ((width * squareSize) / 8) * xScaleFactor, squareSize);
} else if (c == 0x2590) { // right half
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize, (squareSize / 2) * xScaleFactor, squareSize);
} else if (c == 0x2591) { // light shade
ctx.save();
ctx.setGlobalAlpha(0.25);
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
ctx.restore();
} else if (c == 0x2592) { // medium shade
ctx.save();
ctx.setGlobalAlpha(0.5);
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
ctx.restore();
} else if (c == 0x2593) { // dark shade
ctx.save();
ctx.setGlobalAlpha(0.75);
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
ctx.restore();
} else if (c == 0x2594) { // upper eighth
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize / 8);
} else if (c == 0x2595) { // right eighth
ctx.fillRect(((7 * squareSize) / 8) * xScaleFactor, -squareSize, (squareSize / 8) * xScaleFactor,
squareSize);
} else if (c == 0x2571) {
ctx.save();
ctx.setLineWidth(2.0);
ctx.strokeLine(0, 0, squareSize * xScaleFactor, -squareSize);
ctx.restore();
} else if (c == 0x2572) {
ctx.save();
ctx.setLineWidth(2.0);
ctx.strokeLine(0, -squareSize, squareSize * xScaleFactor, 0);
ctx.restore();
} else if (c == 0x2573) {
ctx.save();
ctx.setLineWidth(2.0);
ctx.strokeLine(0, 0, squareSize * xScaleFactor, -squareSize);
ctx.strokeLine(0, -squareSize, squareSize * xScaleFactor, 0);
ctx.restore();
} else if (c >= 0x2500 && c <= 0x257f) {
ctx.save();
ctx.setLineCap(StrokeLineCap.BUTT);
String spec = boxDrawingShapes[c - 0x2500];
int i = 0;
double extraThickness = 0.0;
if (Character.isDigit(spec.charAt(i))) {
extraThickness = setContextFromChar(ctx, spec.charAt(i++));
}
char s;
double hThickness = Math.max(setContextFromChar(ctx, spec.charAt(i)),
setContextFromChar(ctx, spec.charAt(i + 1))) + extraThickness;
double vThickness = Math.max(setContextFromChar(ctx, spec.charAt(i + 2)),
setContextFromChar(ctx, spec.charAt(i + 3))) + extraThickness;
if (c >= 0x2550 || spec.charAt(i) != spec.charAt(i + 1)) {
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, -1, 0, s, vThickness);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, 1, 0, s, vThickness);
} else {
s = spec.charAt(i++);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeFullLine(ctx, xScaleFactor, 1, 0, s);
}
if (c >= 0x2550 || spec.charAt(i) != spec.charAt(i + 1)) {
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, 0, -1, s, hThickness);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, 0, 1, s, hThickness);
} else {
s = spec.charAt(i++);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeFullLine(ctx, xScaleFactor, 0, 1, s);
}
ctx.restore();
}
});
}
/**
* @return the font
*/
public Font getFont() {
if (font == null)
font = findFont(fileName, size);
return font;
}
/**
* @return the size
*/
public double getSize() {
return size;
}
/**
* Width and height of the square-shaped space each letter should fit within
*
* @return the squareSize
*/
public double getSquareSize() {
return squareSize;
}
private Canvas getTmpCanvas() {
if (tmpCanvas == null) {
tmpCanvas = new Canvas(squareSize, squareSize);
} else {
tmpCanvas.getGraphicsContext2D().clearRect(0, 0, squareSize, squareSize);
}
return tmpCanvas;
}
/**
* Horizontal scaling factor (1.0 means no scaling)
*
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit
* a square shape.
*
* @return the xScale
*/
public double getxScale() {
return xScale;
}
/**
* Horizontal positioning of letters.
*
* Each letter should be approximately centered within its available
* square-shaped space.
*
* @return the xTranslate
*/
public double getxTranslate() {
return xTranslate;
}
/**
* Vertical scaling factor (1.0 means no scaling)
*
* @return the yScale
*/
public double getyScale() {
return yScale;
}
/**
* /** Vertical positioning of letters.
*
* Each letter should be positioned on the baseline so that ascenders and
* descenders fall within its available square-shaped space.
*
* @return the yTranslate
*/
public double getyTranslate() {
return yTranslate;
}
private double setContextFromChar(GraphicsContext ctx, char c) {
switch (c) {
case '0':
return -2 * thin;
case '2':
ctx.setLineDashes(new double[] { 14.75, 2.5 });
break;
case '3':
ctx.setLineDashes(new double[] { 9, 2.5 });
break;
case '4':
ctx.setLineDashes(new double[] { 6.125, 2.5 });
break;
case '.':
return 0.0;
case '-':
case 's':
ctx.setLineWidth(thin);
return thin;
case '*':
ctx.setLineWidth(thick);
return thick;
case '=':
case 'N':
case 'M':
case 'z':
ctx.setLineWidth(thin);
return thin;
}
return 0.0;
}
/**
* Set up a graphics context for drawing with this font.
*
* Caller should call {@link GraphicsContext#save()} first, and then
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
* transformation matrix (i.e., translation, scaling).
*
* The GraphicsContext should be translated to the coordinates where the text
* should appear <em>before</em> calling this method, since this method will
* modify the coordinate system.
*
* @param ctx
* A GraphicsContext
*/
public void setGraphicsContext(GraphicsContext ctx) {
ctx.setFont(getFont());
ctx.translate(xTranslate, yTranslate);
ctx.scale(xScale, yScale);
}
/**
* Set up a graphics context for drawing with this font.
*
* Caller should call {@link GraphicsContext#save()} first, and then
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
* transformation matrix (i.e., translation, scaling).
*
* The GraphicsContext should be translated to the coordinates where the text
* should appear <em>before</em> calling this method, since this method will
* modify the coordinate system.
*
* @param ctx
* A GraphicsContext
* @param xScaleFactor
* Additional horizontal scaling, normally 0.5 (for half-width
* characters)
*/
public void setGraphicsContext(GraphicsContext ctx, double xScaleFactor) {
ctx.setFont(getFont());
ctx.translate(xTranslate * xScaleFactor, yTranslate);
ctx.scale(xScale * xScaleFactor, yScale);
}
private void strokeFullLine(GraphicsContext ctx, double xScaleFactor, double xDir, double yDir, char c) {
double x = squareSize * xScaleFactor / 2, y = -squareSize / 2;
if (c != '.') {
// System.out.printf("(%4.1f %4.1f %4.1f %4.1f) w=%1.4f%n", x * yDir, y * xDir,
// x * yDir + xDir * squareSize,
// y * xDir + yDir * squareSize, ctx.getLineWidth());
if (c == '=') {
ctx.setLineWidth(2.0);
ctx.strokeLine((x - 2) * yDir, (y - 2) * xDir, (x - 2) * yDir + xDir * squareSize * xScaleFactor,
(y - 2) * xDir + yDir * -squareSize);
ctx.strokeLine((x + 2) * yDir, (y + 2) * xDir, (x + 2) * yDir + xDir * squareSize * xScaleFactor,
(y + 2) * xDir + yDir * -squareSize);
} else {
ctx.strokeLine(x * yDir, y * xDir, x * yDir + xDir * squareSize * xScaleFactor,
y * xDir + yDir * -squareSize);
}
}
}
private void strokeHalfLine(GraphicsContext ctx, double xScaleFactor, double xDir, double yDir, char c,
double otherWidth) {
if (c != '.') {
double factor = 1.0, dblFactor = 0.0;
if (c == 's' || c == 'z')
factor = -1;
if (c == 'N')
dblFactor = -2;
if (c == 'M')
dblFactor = 2;
double x = squareSize * xScaleFactor / 2, y = -squareSize / 2;
x -= xDir * factor * (otherWidth / 2);
y -= yDir * factor * (otherWidth / 2);
if (c == '=' || c == 'z' || c == 'N' || c == 'M') {
ctx.setLineWidth(2.0);
double x0 = x - 2 * yDir;
double y0 = y - 2 * xDir;
x0 += dblFactor * xDir * (otherWidth / 2);
y0 += dblFactor * yDir * (otherWidth / 2);
ctx.strokeLine(x0, y0, x0 + xDir * squareSize * xScaleFactor, y0 + yDir * squareSize);
double x1 = x + 2 * yDir;
double y1 = y + 2 * xDir;
x1 -= dblFactor * xDir * (otherWidth / 2);
y1 -= dblFactor * yDir * (otherWidth / 2);
ctx.strokeLine(x1, y1, x1 + xDir * squareSize * xScaleFactor, y1 + yDir * squareSize);
} else {
ctx.strokeLine(x, y, x + xDir * squareSize * xScaleFactor, y + yDir * squareSize);
}
}
}
/**
* Draw text at the given position.
*
* For most cases, the simpler {@link #drawText(GraphicsContext, String)} or
* {@link #drawText(GraphicsContext, String, double)} will be easier to use.
*
* If <code>clip</code> is true, the graphics context's current path will be
* overwritten.
*
* @param ctx
* A GraphicsContext
* @param x
* X-position of the lower left corner of the text
* @param y
* Y-position of the lower left corner of the text
* @param text
* The text to be printed
* @param xScaleFactor
* Horizontal scaling factor, normally 1.0 (full width) or 0.5 (half
* width)
* @param clear
* True if the area should be cleared (to transparency) before
* drawing; normally true.
* @param clip
* True if the text drawing should be clipped to fit the expected
* printing area; normally true.
* @param fill
* True if the letter shapes should be filled; normally true.
* @param stroke
* True if the letter shapes should be stroked (outlined); normally
* false.
*/
public void textAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, boolean clear,
boolean clip, Paint fill, Paint stroke, int mode, Paint bg) {
if ((mode & ATTR_BRIGHT) != 0) {
fill = fill instanceof Color ? ((Color) fill).deriveColor(0.0, 1.0, 3.0, 1.0) : fill;
stroke = stroke instanceof Color ? ((Color) stroke).deriveColor(0.0, 1.0, 3.0, 1.0) : stroke;
}
if ((mode & ATTR_FAINT) != 0) {
fill = fill instanceof Color ? ((Color) fill).deriveColor(0.0, 1.0, 1.0, .5) : fill;
stroke = stroke instanceof Color ? ((Color) stroke).deriveColor(0.0, 1.0, 1.0, .5) : stroke;
}
GraphicsContext target = ctx;
int width = text.codePointCount(0, text.length());
ctx.save(); // save 1
ctx.setFill(fill);
ctx.setStroke(stroke);
ctx.translate(x, y);
if (clear && (mode & ATTR_INVERSE) == 0 && (mode & ATTR_OVERSTRIKE) == 0) {
ctx.clearRect(0 + 0.5, -squareSize + 0.5, squareSize * width * xScaleFactor - 1, squareSize - 1);
}
if (bg != null && bg != Color.TRANSPARENT) {
ctx.save(); // save 2
ctx.setFill(bg);
ctx.fillRect(0, -squareSize, squareSize * width * xScaleFactor, squareSize);
ctx.restore(); // restore 2
}
boolean drawIndirect = clip || (mode & (ATTR_INVERSE | ATTR_CLIP)) != 0;
Canvas tmpCanvas = getTmpCanvas();
if (drawIndirect) {
target = tmpCanvas.getGraphicsContext2D();
target.save(); // save 2 if drawIndirect
target.translate(0, squareSize);
target.setFill((mode & ATTR_INVERSE) != 0 ? Color.BLACK : fill);
target.setStroke((mode & ATTR_INVERSE) != 0 ? Color.BLACK : stroke);
}
if (text.length() > 0 && text.charAt(0) >= 0x2500 && text.charAt(0) <= 0x259f
&& (mode & ATTR_NO_FAKE_CHARS) == 0) {
target.save(); // save 3
target.setStroke(target.getFill());
fakeBlockElement(target, text, xScaleFactor);
target.restore(); // restore 3
} else {
target.save(); // save 3
if ((mode & ATTR_ITALIC) != 0) {
target.translate(-0.2, 0);
target.transform(new Affine(Transform.shear(-0.2, 0)));
}
setGraphicsContext(target, xScaleFactor);
if (fill != null)
target.fillText(text, 0.0, 0.0);
if ((mode & ATTR_BOLD) != 0) {
// System.err.println("stroke2");
target.save(); // save 4
target.setLineWidth(thin);
target.setStroke(target.getFill());
target.strokeText(text, 0.0, 0.0);
target.restore(); // restore 4
}
if (stroke != null || (mode & ATTR_OUTLINE) != 0) {
// System.err.println("stroke2");
target.setLineWidth(((mode & ATTR_BOLD) != 0) ? thin : thin / 2);
target.strokeText(text, 0.0, 0.0);
}
target.restore(); // restore 3
}
if ((mode & ATTR_UNDERLINE) != 0) {
target.fillRect(0, yTranslate - 2, width * squareSize * xScaleFactor, thin);
}
if ((mode & ATTR_OVERLINE) != 0) {
target.fillRect(0, -squareSize + 2, width * squareSize * xScaleFactor, thin);
}
if ((mode & ATTR_LINE_THROUGH) != 0) {
target.fillRect(0, -squareSize / 2 + 2, width * squareSize * xScaleFactor, thin);
}
if (drawIndirect) {
target.restore(); // restore 2 if drawIndirect
tmpCanvas.snapshot(snapshotParameters, img);
if ((mode & ATTR_INVERSE) != 0) {
fillInverse(ctx, img, 0, -squareSize);
} else
ctx.drawImage(img, 0, -squareSize);
}
ctx.restore(); // restore 1
}
}