982 lines
33 KiB
Java
982 lines
33 KiB
Java
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
|
||
}
|
||
}
|