diff --git a/core/src/processing/javafx/PGraphicsFX2D.java b/core/src/processing/javafx/PGraphicsFX2D.java
index 58b211feb..211a23168 100644
--- a/core/src/processing/javafx/PGraphicsFX2D.java
+++ b/core/src/processing/javafx/PGraphicsFX2D.java
@@ -27,6 +27,9 @@ import com.sun.javafx.geom.PathIterator;
import com.sun.javafx.geom.Shape;
import java.nio.IntBuffer;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.GraphicsContext;
@@ -41,6 +44,8 @@ import javafx.scene.paint.Paint;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
@@ -1266,157 +1271,178 @@ public class PGraphicsFX2D extends PGraphics {
-// //////////////////////////////////////////////////////////////
-//
-// // TEXT ATTRIBTUES
-//
-//
-// //public void textAlign(int align)
-//
-//
-// //public void textAlign(int alignX, int alignY)
-//
-//
-// @Override
-// public float textAscent() {
-// if (textFont == null) {
-// defaultFontOrDeath("textAscent");
-// }
-//
-// Font font = (Font) textFont.getNative();
-// if (font != null) {
-// return getFontMetrics(font).getAscent();
-// }
-// return super.textAscent();
-// }
-//
-//
-// @Override
-// public float textDescent() {
-// if (textFont == null) {
-// defaultFontOrDeath("textDescent");
-// }
-// Font font = (Font) textFont.getNative();
-// if (font != null) {
-// return getFontMetrics(font).getDescent();
-// }
-// return super.textDescent();
-// }
-//
-//
-// //public void textFont(PFont which)
-//
-//
-// //public void textFont(PFont which, float size)
-//
-//
-// //public void textLeading(float leading)
-//
-//
-// //public void textMode(int mode)
-//
-//
-// @Override
-// protected boolean textModeCheck(int mode) {
-// return mode == MODEL;
-// }
-//
-//
-// /**
-// * Same as parent, but override for native version of the font.
-// *
-// * Also gets called by textFont, so the metrics
-// * will get recorded properly.
-// */
-// @Override
-// public void textSize(float size) {
-// if (textFont == null) {
-// defaultFontOrDeath("textSize", size);
-// }
-//
-// // if a native version available, derive this font
-//// if (textFontNative != null) {
-//// textFontNative = textFontNative.deriveFont(size);
-//// g2.setFont(textFontNative);
-//// textFontNativeMetrics = g2.getFontMetrics(textFontNative);
-//// }
-// Font font = (Font) textFont.getNative();
-// //if (font != null && (textFont.isStream() || hints[ENABLE_NATIVE_FONTS])) {
-// if (font != null) {
-// Map map =
-// new HashMap();
-// map.put(TextAttribute.SIZE, size);
-// map.put(TextAttribute.KERNING,
-// TextAttribute.KERNING_ON);
-//// map.put(TextAttribute.TRACKING,
-//// TextAttribute.TRACKING_TIGHT);
-// font = font.deriveFont(map);
-// g2.setFont(font);
-// textFont.setNative(font);
-//
-//// Font dfont = font.deriveFont(size);
-////// Map attrs = dfont.getAttributes();
-////// for (TextAttribute ta : attrs.keySet()) {
-////// System.out.println(ta + " -> " + attrs.get(ta));
-////// }
-//// g2.setFont(dfont);
-//// textFont.setNative(dfont);
-// }
-//
-// // take care of setting the textSize and textLeading vars
-// // this has to happen second, because it calls textAscent()
-// // (which requires the native font metrics to be set)
-// super.textSize(size);
-// }
-//
-//
-// //public float textWidth(char c)
-//
-//
-// //public float textWidth(String str)
-//
-//
-// @Override
-// protected float textWidthImpl(char buffer[], int start, int stop) {
-// if (textFont == null) {
-// defaultFontOrDeath("textWidth");
-// }
-//
-// Font font = (Font) textFont.getNative();
-// //if (font != null && (textFont.isStream() || hints[ENABLE_NATIVE_FONTS])) {
-// if (font != null) {
-// // maybe should use one of the newer/fancier functions for this?
-// int length = stop - start;
-// FontMetrics metrics = getFontMetrics(font);
-// // Using fractional metrics makes the measurement worse, not better,
-// // at least on OS X 10.6 (November, 2010).
-// // TextLayout returns the same value as charsWidth().
-//// System.err.println("using native");
-//// g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
-//// RenderingHints.VALUE_FRACTIONALMETRICS_ON);
-//// float m1 = metrics.charsWidth(buffer, start, length);
-//// //float m2 = (float) metrics.getStringBounds(buffer, start, stop, g2).getWidth();
-//// TextLayout tl = new TextLayout(new String(buffer, start, length), font, g2.getFontRenderContext());
-//// float m2 = (float) tl.getBounds().getWidth();
-//// System.err.println(m1 + " " + m2);
-////// return m1;
-//// return m2;
-// return metrics.charsWidth(buffer, start, length);
-// }
-//// System.err.println("not native");
-// return super.textWidthImpl(buffer, start, stop);
-// }
-//
-//
-//// protected void beginTextScreenMode() {
-//// loadPixels();
-//// }
-//
-//
-//// protected void endTextScreenMode() {
-//// updatePixels();
-//// }
-//
+ //////////////////////////////////////////////////////////////
+
+ // TEXT ATTRIBTUES
+
+
+ protected FontCache fontCache = new FontCache();
+
+ // Is initialized when defaultFontOrDeath() is called
+ // and mirrors PGraphics.textFont field
+ protected FontInfo textFontInfo;
+
+ protected Text measuringText = new Text();
+
+ @Override
+ protected void defaultFontOrDeath(String method, float size) {
+ super.defaultFontOrDeath(method, size);
+ textFontInfo = fontCache.get(textFont.getName(), textFont.getSize());
+ }
+
+ @Override
+ protected boolean textModeCheck(int mode) {
+ return mode == MODEL;
+ }
+
+ @Override
+ public void textSize(float size) {
+ if (size <= 0) {
+ // Using System.err instead of showWarning to avoid running out of
+ // memory with a bunch of textSize() variants (cause of this bug is
+ // usually something done with map() or in a loop).
+ System.err.println("textSize(" + size + ") ignored: " +
+ "the text size must be larger than zero");
+ return;
+ }
+ if (textFont == null) {
+ defaultFontOrDeath("textSize", size);
+ }
+ textFontImpl(textFont, size);
+ }
+
+ @Override
+ public void textFont(PFont which) {
+ if (which != null) {
+ textFontImpl(which, which.getDefaultSize());
+ } else {
+ throw new RuntimeException(ERROR_TEXTFONT_NULL_PFONT);
+ }
+ }
+
+ @Override
+ public void textFont(PFont which, float size) {
+ if (which != null) {
+ if (size <= 0) {
+ // Using System.err instead of showWarning to avoid running out of
+ // memory with a bunch of textSize() variants (cause of this bug is
+ // usually something done with map() or in a loop).
+ System.err.println("textFont with size " + size + " ignored: " +
+ "the text size must be larger than zero");
+ size = textSize;
+ }
+ textFontImpl(which, size);
+ } else {
+ throw new RuntimeException(ERROR_TEXTFONT_NULL_PFONT);
+ }
+ }
+
+ /**
+ * Sets the font and size without doing any checks.
+ * Check the validity of args before calling.
+ *
+ * @param which font to set, not null
+ * @param size size to set, greater than zero
+ */
+ protected void textFontImpl(PFont which, float size) {
+ textFont = which;
+ textSize = size;
+
+ textFontInfo = fontCache.get(which.getName(), size);
+ context.setFont(textFontInfo.font);
+ measuringText.setFont(textFontInfo.font);
+ textLeading = (textFontInfo.ascent + textFontInfo.descent) * 1.275f;
+ }
+
+
+ @Override
+ public float textAscent() {
+ if (textFont == null) {
+ defaultFontOrDeath("textAscent");
+ }
+ return textFontInfo.ascent;
+ }
+
+
+ @Override
+ public float textDescent() {
+ if (textFont == null) {
+ defaultFontOrDeath("textDescent");
+ }
+ return textFontInfo.descent;
+ }
+
+
+ protected static final class FontInfo {
+ Font font;
+ float ascent;
+ float descent;
+ }
+
+
+ protected static final class FontCache {
+
+ private static final int CACHE_SIZE = 512;
+
+ private final LinkedHashMap cache = new LinkedHashMap(16, 0.75f, true) {
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > CACHE_SIZE;
+ }
+
+ };
+ private final Key retrievingKey = new Key();
+ private final Text measuringText = new Text();
+
+ private FontInfo get(String name, float size) {
+ retrievingKey.name = name;
+ retrievingKey.size = size;
+ FontInfo fontInfo = cache.get(retrievingKey);
+ if (fontInfo == null) {
+ fontInfo = new FontInfo();
+ fontInfo.font = new Font(name, size);
+
+ { // measure ascent and descent
+ measuringText.setFont(fontInfo.font);
+ measuringText.setText(" ");
+ float lineHeight = (float) measuringText.getLayoutBounds().getHeight();
+ fontInfo.ascent = (float) measuringText.getBaselineOffset();
+ fontInfo.descent = lineHeight - fontInfo.ascent;
+ }
+
+ { // create new key and add the info to the cache
+ Key key = new Key();
+ key.name = name;
+ key.size = size;
+ cache.put(key, fontInfo);
+ }
+ }
+ return fontInfo;
+ }
+
+ private static final class Key {
+ String name;
+ float size;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Key that = (Key) o;
+ if (Float.compare(that.size, size) != 0) return false;
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + (size != +0.0f ? Float.floatToIntBits(size) : 0);
+ return result;
+ }
+ }
+ }
+
//
// //////////////////////////////////////////////////////////////
//
@@ -1425,84 +1451,25 @@ public class PGraphicsFX2D extends PGraphics {
// // None of the variations of text() are overridden from PGraphics.
//
//
-//
-// //////////////////////////////////////////////////////////////
-//
-// // TEXT IMPL
-//
-//
-// //protected void textLineAlignImpl(char buffer[], int start, int stop,
-// // float x, float y)
-//
-//
-// @Override
-// protected void textLineImpl(char buffer[], int start, int stop,
-// float x, float y) {
-// Font font = (Font) textFont.getNative();
-//// if (font != null && (textFont.isStream() || hints[ENABLE_NATIVE_FONTS])) {
-// if (font != null) {
-// /*
-// // save the current setting for text smoothing. note that this is
-// // different from the smooth() function, because the font smoothing
-// // is controlled when the font is created, not now as it's drawn.
-// // fixed a bug in 0116 that handled this incorrectly.
-// Object textAntialias =
-// g2.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
-//
-// // override the current text smoothing setting based on the font
-// // (don't change the global smoothing settings)
-// g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
-// textFont.smooth ?
-// RenderingHints.VALUE_ANTIALIAS_ON :
-// RenderingHints.VALUE_ANTIALIAS_OFF);
-// */
-// Object antialias =
-// g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
-// if (antialias == null) {
-// // if smooth() and noSmooth() not called, this will be null (0120)
-// antialias = RenderingHints.VALUE_ANTIALIAS_DEFAULT;
-// }
-//
-// // override the current smoothing setting based on the font
-// // also changes global setting for antialiasing, but this is because it's
-// // not possible to enable/disable them independently in some situations.
-// g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-// textFont.smooth ?
-// RenderingHints.VALUE_ANTIALIAS_ON :
-// RenderingHints.VALUE_ANTIALIAS_OFF);
-//
-// g2.setColor(fillColorObject);
-//
-// int length = stop - start;
-// if (length != 0) {
-// g2.drawChars(buffer, start, length, (int) (x + 0.5f), (int) (y + 0.5f));
-// // better to use round here? also, drawChars now just calls drawString
-//// g2.drawString(new String(buffer, start, stop - start), Math.round(x), Math.round(y));
-//
-// // better to use drawString() with floats? (nope, draws the same)
-// //g2.drawString(new String(buffer, start, length), x, y);
-//
-// // this didn't seem to help the scaling issue, and creates garbage
-// // because of a fairly heavyweight new temporary object
-//// java.awt.font.GlyphVector gv =
-//// font.createGlyphVector(g2.getFontRenderContext(), new String(buffer, start, stop - start));
-//// g2.drawGlyphVector(gv, x, y);
-// }
-//
-// // return to previous smoothing state if it was changed
-// //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textAntialias);
-// g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias);
-//
-// } else { // otherwise just do the default
-// super.textLineImpl(buffer, start, stop, x, y);
-// }
-// }
-//
-//
-// @Override
-// public FontMetrics getFontMetrics(Font font) {
-// return (g2 != null) ? g2.getFontMetrics(font) : super.getFontMetrics(font);
-// }
+
+ //////////////////////////////////////////////////////////////
+
+ // TEXT IMPL
+
+
+ @Override
+ protected void textLineImpl(char[] buffer, int start, int stop, float x, float y) {
+ // TODO(Jakub): fallback to PGraphics if font not found
+ context.fillText(new String(buffer, start, stop - start), x, y);
+ }
+
+
+ @Override
+ protected float textWidthImpl(char[] buffer, int start, int stop) {
+ measuringText.setText(new String(buffer, start, stop - start));
+ return (float) measuringText.getLayoutBounds().getWidth();
+ }
+
//////////////////////////////////////////////////////////////