diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java b/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java new file mode 100644 index 000000000000..c25fb1943b80 --- /dev/null +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2023 JetBrains s.r.o. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.jetbrains.desktop; + +import com.jetbrains.internal.JBRApi; + +import java.awt.*; +import java.lang.invoke.MethodHandles; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public class FontExtensions { + private interface FontExtension { + FontExtension INSTANCE = (FontExtension) JBRApi.internalServiceBuilder(MethodHandles.lookup()) + .withStatic("getFeatures", "getFeatures", "java.awt.Font") + .withStatic("isComplexRendering", "isComplexRendering", "java.awt.Font") + .withStatic("isKerning", "isKerning", "java.awt.Font") + .build(); + + TreeMap getFeatures(Font font); + boolean isComplexRendering(Font font); + boolean isKerning(Font font); + } + + public static String[] featuresToStringArray(Map features) { + return features.entrySet().stream().map(feature -> (feature.getKey() + "=" + feature.getValue())). + toArray(String[]::new); + } + + public static TreeMap getFeatures(Font font) { + return FontExtension.INSTANCE.getFeatures(font); + } + + public static boolean isComplexRendering(Font font) { + return FontExtension.INSTANCE.isComplexRendering(font); + } + + public static boolean isKerning(Font font) { + return FontExtension.INSTANCE.isKerning(font); + } +} diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java index aa187c4afcca..2d62aa55b5f0 100644 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java @@ -61,7 +61,6 @@ public class JBRApiModule { .withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities") .withStatic("deriveFontWithFeatures", "deriveFont", "java.awt.Font") .withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font") - .withStatic("getEnabledFeatures", "getEnabledFeatures", "java.awt.Font") .service("com.jetbrains.FontOpenTypeFeatures") .withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font") .clientProxy("java.awt.Font$Features", "com.jetbrains.FontExtensions$Features") diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java index ac5e525f60a1..3a1f25b78fd6 100644 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java @@ -6,7 +6,6 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.awt.*; -import java.util.Objects; public class JBRFileDialog implements Serializable { @@ -26,19 +25,28 @@ public static JBRFileDialog get(FileDialog dialog) { return (JBRFileDialog) getter.get(dialog); } + /** + * Whether to select files, directories or both (used when common file dialogs are enabled on Windows, or on macOS) + */ @Native public static final int SELECT_FILES_HINT = 1, SELECT_DIRECTORIES_HINT = 2; + /** + * Whether to allow creating directories or not (used on macOS) + */ @Native public static final int CREATE_DIRECTORIES_HINT = 4; - public static final String OPEN_FILE_BUTTON_KEY = "jbrFileDialogOpenFile"; - public static final String OPEN_DIRECTORY_BUTTON_KEY = "jbrFileDialogSelectDir"; - public static final String ALL_FILES_COMBO_KEY = "jbrFileDialogAllFiles"; - public int hints = CREATE_DIRECTORIES_HINT; + + /** + * Text for "Open" button (used when common file dialogs are enabled on + * Windows). + */ public String openButtonText; + + /** + * Text for "Select Folder" button (used when common file dialogs are + * enabled on Windows). + */ public String selectFolderButtonText; - public String allFilesFilterDescription; - public String fileFilterDescription; - public String[] fileFilterExtensions; public void setHints(int hints) { this.hints = hints; @@ -47,23 +55,9 @@ public int getHints() { return hints; } - public void setLocalizationString(String key, String text) { - Objects.requireNonNull(key); - switch (key) { - case OPEN_FILE_BUTTON_KEY -> openButtonText = text; - case OPEN_DIRECTORY_BUTTON_KEY -> selectFolderButtonText = text; - case ALL_FILES_COMBO_KEY -> allFilesFilterDescription = text; - default -> throw new IllegalArgumentException("unrecognized key: " + key); - } - } - public void setLocalizationStrings(String openButtonText, String selectFolderButtonText) { - setLocalizationString(OPEN_FILE_BUTTON_KEY, openButtonText); - setLocalizationString(OPEN_DIRECTORY_BUTTON_KEY, selectFolderButtonText); + this.openButtonText = openButtonText; + this.selectFolderButtonText = selectFolderButtonText; } - public void setFileFilterExtensions(String fileFilterDescription, String[] fileFilterExtensions) { - this.fileFilterDescription = fileFilterDescription; - this.fileFilterExtensions = fileFilterExtensions; - } } diff --git a/src/java.desktop/share/classes/java/awt/Font.java b/src/java.desktop/share/classes/java/awt/Font.java index bf3445fded08..5b542288d3c6 100644 --- a/src/java.desktop/share/classes/java/awt/Font.java +++ b/src/java.desktop/share/classes/java/awt/Font.java @@ -48,11 +48,17 @@ import java.security.PrivilegedExceptionAction; import java.text.AttributedCharacterIterator.Attribute; import java.text.CharacterIterator; -import java.util.*; +import java.util.EventListener; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; import sun.awt.ComponentFactory; import sun.font.AttributeMap; import sun.font.AttributeValues; +import sun.font.CompositeFont; import sun.font.CoreMetrics; import sun.font.CreatedFontTracker; import sun.font.Font2D; @@ -278,28 +284,6 @@ public boolean isCreatedFont(Font font) { public FontPeer getFontPeer(final Font font) { return font.getFontPeer(); } - - @Override - public String[] getFeatures(Font font) { - int size = font.features == null ? 0 : font.features.length; - String[] fs = new String[size + 3]; - if (size > 0) System.arraycopy(font.features, 0, fs, 0, size); - fs[size++] = KERN_FEATURE + "=" + font.getAttributeValues().getKerning(); - fs[size++] = LIGA_FEATURE + "=" + font.getAttributeValues().getLigatures(); - fs[size] = CALT_FEATURE + "=" + font.getAttributeValues().getLigatures(); - return fs; - } - - @Override - public boolean isComplexRendering(Font font) { - return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 || - font.values.getBaselineTransform() != null)) || font.features != null; - } - - @Override - public boolean isKerning(Font font) { - return font.values != null && (font.values.getKerning() != 0); - } } static { @@ -471,9 +455,10 @@ public boolean isKerning(Font font) { * Ordered map choose intentionally as field's type. It allows to correctly comparing two Font objects * * @serial + * @see #getFeatures * @see #deriveFont(Font, Features) */ - private String[] features; + private TreeMap features = new TreeMap(); /** * The platform specific font information. @@ -576,6 +561,10 @@ private Font2D getFont2D() { return font2DHandle.font2D; } + private boolean anyEnabledFeatures() { + return features.values().stream().anyMatch(x -> x != 0); + } + /** * Creates a new {@code Font} from the specified name, style and * point size. @@ -639,7 +628,7 @@ public Font(String name, int style, int size) { this.pointSize = size; } - private Font(String name, int style, float sizePts, String[] features) { + private Font(String name, int style, float sizePts, TreeMap features) { this.name = (name != null) ? name : "Default"; this.style = (style & ~0x03) == 0 ? style : 0; this.size = (int)(sizePts + 0.5); @@ -650,7 +639,7 @@ private Font(String name, int style, float sizePts, String[] features) { /* This constructor is used by deriveFont when attributes is null */ private Font(String name, int style, float sizePts, boolean created, boolean withFallback, - Font2DHandle handle, boolean useOldHandle, String[] features) { + Font2DHandle handle, boolean useOldHandle, TreeMap features) { this(name, style, sizePts, features); this.createdFont = created; this.withFallback = withFallback; @@ -715,7 +704,7 @@ private Font(File fontFile, int fontFormat, */ private Font(AttributeValues values, String oldName, int oldStyle, boolean created, boolean withFallback, - Font2DHandle handle, boolean useOldHandle, String[] features) { + Font2DHandle handle, boolean useOldHandle, TreeMap features) { this.features = features; this.createdFont = created; @@ -779,11 +768,6 @@ public Font(Map attributes) { * @since 1.6 */ protected Font(Font font) { - this(font, font.features); - } - - private Font(Font font, String[] features) { - this.features = features; if (font.values != null) { initFromValues(font.getAttributeValues().clone()); } else { @@ -795,6 +779,12 @@ private Font(Font font, String[] features) { this.font2DHandle = font.font2DHandle; this.createdFont = font.createdFont; this.withFallback = font.withFallback; + this.features = font.features; + } + + private Font(Font font, TreeMap features) { + this(font); + this.features = features; } /** @@ -844,7 +834,7 @@ private void initFromValues(AttributeValues values) { if (values.getPosture() >= .2f) this.style |= ITALIC; // not == .2f this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK); - this.hasLayoutAttributes = this.features != null || values.anyNonDefault(LAYOUT_MASK); + this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK); } /** @@ -925,7 +915,7 @@ public static Font getFont(Map attributes) { values.merge(attributes, SECONDARY_MASK); return new Font(values, font.name, font.style, font.createdFont, font.withFallback, - font.font2DHandle, false, null); + font.font2DHandle, false, new TreeMap()); } return new Font(attributes); } @@ -937,7 +927,7 @@ public static Font getFont(Map attributes) { values.merge(attributes, SECONDARY_MASK); return new Font(values, font.name, font.style, font.createdFont, font.withFallback, - font.font2DHandle, false, null); + font.font2DHandle, false, new TreeMap()); } return font; @@ -1658,7 +1648,17 @@ public boolean isTransformed() { * @since 1.6 */ public boolean hasLayoutAttributes() { - return hasLayoutAttributes; + return anyEnabledFeatures() || hasLayoutAttributes; + } + + private static TreeMap getFeatures(Font font) { + TreeMap res = new TreeMap<>(); + res.putAll(font.features); + res.put(KERN_FEATURE, font.getAttributeValues().getKerning()); + res.put(LIGA_FEATURE, font.getAttributeValues().getLigatures()); + res.put(CALT_FEATURE, font.getAttributeValues().getLigatures()); + + return res; } /** @@ -1866,7 +1866,7 @@ public static Font getFont(String nm, Font font) { */ public int hashCode() { if (hash == 0) { - hash = name.hashCode() ^ style ^ size ^ Arrays.hashCode(features); + hash = name.hashCode() ^ style ^ size ^ features.hashCode(); /* It is possible many fonts differ only in transform. * So include the transform in the hash calculation. * nonIdentityTx is set whenever there is a transform in @@ -1905,7 +1905,7 @@ public boolean equals(Object obj) { pointSize == font.pointSize && withFallback == font.withFallback && name.equals(font.name) && - Arrays.equals(features, font.features)) { + features.equals(font.features)) { /* 'values' is usually initialized lazily, except when * the font is constructed from a Map, or derived using @@ -2029,13 +2029,17 @@ private void readObject(java.io.ObjectInputStream s) } values = getAttributeValues().merge(extras); this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK); - this.hasLayoutAttributes = this.features != null || values.anyNonDefault(LAYOUT_MASK); + this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK); } catch (Throwable t) { throw new IOException(t); } finally { fRequestedAttributes = null; // don't need it any more } } + + if (features == null) { + features = new TreeMap<>(); + } } /** @@ -2266,48 +2270,7 @@ private interface Features { } private static Font deriveFont(Font font, Features features) { - TreeMap map = features.getAsTreeMap(); - String[] array = new String[map.size()]; - int i = 0; - for (Map.Entry e : map.entrySet()) { - validateFeature(array[i++] = e.getKey() + "=" + e.getValue()); - } - return new Font(font, array); - } - - private static void validateFeature(String f) { - int len = f.length(); - invalid:if ((len == 4 || len > 5) && - Character.isLetterOrDigit(f.charAt(0)) && - Character.isLetterOrDigit(f.charAt(1)) && - Character.isLetterOrDigit(f.charAt(2)) && - Character.isLetterOrDigit(f.charAt(3))) { - if (len > 5 && f.charAt(4) == '=') { - for (int i = 5; i < len; i++) { - if (!Character.isDigit(f.charAt(i))) break invalid; - } - if (f.startsWith("kern")) throw new IllegalArgumentException( - "\"kern\" feature is not allowed here, use java.awt.font.TextAttribute.KERNING instead"); - if (f.startsWith("liga") || f.startsWith("calt")) throw new IllegalArgumentException( - "\"liga\" and \"calt\" features are not allowed here, use java.awt.font.TextAttribute.LIGATURES instead"); - return; - } - } - throw new IllegalArgumentException("Invalid feature: \"" + f + "\", allowed syntax: \"kern\", or \"aalt=2\""); - } - - private static Font deriveFont(Font font, String... features) { - if (features == null || features.length == 0) return new Font(font, null); - for (String f : features) validateFeature(f); - return new Font(font, Arrays.copyOf(features, features.length)); - } - - /** - * Returns a list of OpenType's features enabled on the current Font. - * @return array of enabled OpenType's features - */ - private static String[] getEnabledFeatures(Font font) { - return font.features == null ? new String[0] : Arrays.copyOf(font.features, font.features.length); + return new Font(font, features.getAsTreeMap()); } /** @@ -2761,10 +2724,19 @@ public Rectangle2D getStringBounds(char [] chars, return metrics.charsBounds(chars, beginIndex, limit - beginIndex); } + private static boolean isComplexRendering(Font font) { + return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 || + font.values.getBaselineTransform() != null)) || font.anyEnabledFeatures(); + } + + private static boolean isKerning(Font font) { + return font.values != null && (font.values.getKerning() != 0); + } + /** * Returns a list of OpenType's features supported by current Font. * Implementation of such logic goes to HarfBuzz library. - * @return set of available OpenType's features + * @return list of OpenType's features concatenated to String */ private static Set getAvailableFeatures(Font font) { return SunLayoutEngine.getAvailableFeatures(FontUtilities.getFont2D(font)); diff --git a/src/java.desktop/share/classes/sun/font/FontAccess.java b/src/java.desktop/share/classes/sun/font/FontAccess.java index 5e7cc3a769d0..70359e237809 100644 --- a/src/java.desktop/share/classes/sun/font/FontAccess.java +++ b/src/java.desktop/share/classes/sun/font/FontAccess.java @@ -27,7 +27,6 @@ import java.awt.Font; import java.awt.peer.FontPeer; -import java.util.TreeMap; public abstract class FontAccess { @@ -48,7 +47,4 @@ public static synchronized FontAccess getFontAccess() { public abstract void setWithFallback(Font f); public abstract boolean isCreatedFont(Font f); public abstract FontPeer getFontPeer(Font f); - public abstract String[] getFeatures(Font font); - public abstract boolean isComplexRendering(Font font); - public abstract boolean isKerning(Font font); } diff --git a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java index 8db22d3af6a3..844c16b1f6f2 100644 --- a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java +++ b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java @@ -43,6 +43,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import com.jetbrains.desktop.FontExtensions; import sun.java2d.Disposer; import sun.java2d.DisposerRecord; @@ -480,7 +481,7 @@ private Rectangle2D.Float dataBounds(Object data, int off, int len) { assert (data instanceof String || data instanceof char[]); float width = 0; - if (overrider == null && FontAccess.getFontAccess().isComplexRendering(font) && len > 0) { + if (overrider == null && FontExtensions.isComplexRendering(font) && len > 0) { return textLayoutBounds(data, off, len); } @@ -491,7 +492,7 @@ private Rectangle2D.Float dataBounds(Object data, int off, int len) { return new Rectangle2D.Float(0f, -ascent, width, height); } - boolean isKerning = FontAccess.getFontAccess().isKerning(font); + boolean isKerning = FontExtensions.isKerning(font); float consecutiveDoubleCharacterWidth = 0f; char prev = 0; for (int i = off; i < off + len; i++) { @@ -607,7 +608,7 @@ public int getHeight() { return height; } - private static final class Accessor { + private static class Accessor { // used by JBR API // Keeping metrics instances here prevents them from being garbage collected // and being re-created by FontDesignMetrics.getMetrics method private final Set PINNED_METRICS = new HashSet<>(); @@ -646,7 +647,7 @@ void removeAllOverrides() { } } - private interface Overrider { + private interface Overrider { // used by JBR API float charWidth(int codePoint); } } diff --git a/src/java.desktop/share/classes/sun/font/GlyphLayout.java b/src/java.desktop/share/classes/sun/font/GlyphLayout.java index c940891f836b..cc0cc8acfedb 100644 --- a/src/java.desktop/share/classes/sun/font/GlyphLayout.java +++ b/src/java.desktop/share/classes/sun/font/GlyphLayout.java @@ -68,6 +68,8 @@ package sun.font; +import com.jetbrains.desktop.FontExtensions; + import java.lang.ref.SoftReference; import java.awt.Font; import java.awt.font.FontRenderContext; @@ -174,7 +176,7 @@ public static interface LayoutEngine { * leave pt and the gvdata unchanged. */ public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int gmask, int baseIndex, TextRecord text, - boolean ltrDirection, String[] features, Point2D.Float pt, GVData data); + boolean ltrDirection, Map features, Point2D.Float pt, GVData data); } /** @@ -450,7 +452,7 @@ public StandardGlyphVector layout(Font font, FontRenderContext frc, EngineRecord er = _erecords.get(ix); for (;;) { try { - er.layout(ltrDirection, FontAccess.getFontAccess().getFeatures(font)); + er.layout(ltrDirection, FontExtensions.getFeatures(font)); break; } catch (IndexOutOfBoundsException e) { @@ -656,7 +658,7 @@ void init(int start, int limit, Font2D font, int script, int lang, int gmask) { this.engine = _lef.getEngine(key); // flags? } - void layout(boolean ltrDirection, String[] features) { + void layout(boolean ltrDirection, Map features) { _textRecord.start = start; _textRecord.limit = limit; engine.layout(_sd, _mat, ptSize, gmask, start - _offset, _textRecord, diff --git a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java index 82a317181a81..072d0f62d835 100644 --- a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java +++ b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java @@ -30,6 +30,7 @@ package sun.font; +import com.jetbrains.desktop.FontExtensions; import sun.font.GlyphLayout.*; import sun.java2d.Disposer; import sun.java2d.DisposerRecord; @@ -173,7 +174,7 @@ public static Set getAvailableFeatures(Font2D font) { } public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask, - int baseIndex, TextRecord tr, boolean ltrDirection, String[] features, + int baseIndex, TextRecord tr, boolean ltrDirection, Map features, Point2D.Float pt, GVData data) { Font2D font = key.font(); FontStrike strike = font.getStrike(desc); @@ -182,7 +183,7 @@ public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask, shape(font, strike, ptSize, mat, pFace, tr.text, data, key.script(), tr.start, tr.limit, baseIndex, pt, - ltrDirection, features, gmask); + ltrDirection, FontExtensions.featuresToStringArray(features), gmask); } } diff --git a/test/jdk/jb/java/api/frontend/FontExtensionsTest.java b/test/jdk/jb/java/api/frontend/FontExtensionsTest.java index 58382f36b7ec..def59ae417ad 100644 --- a/test/jdk/jb/java/api/frontend/FontExtensionsTest.java +++ b/test/jdk/jb/java/api/frontend/FontExtensionsTest.java @@ -42,6 +42,9 @@ import java.util.Map; import java.util.Set; +import static com.jetbrains.desktop.FontExtensions.featuresToStringArray; +import static com.jetbrains.desktop.FontExtensions.getFeatures; + public class FontExtensionsTest { @Retention(RetentionPolicy.RUNTIME) private @interface JBRTest {} @@ -93,6 +96,10 @@ private static Boolean textDrawingEquals(Font font1, Font font2, String text) { return isImageEquals(image1, image2); } + private static String[] fontFeaturesAsString(Font font) { + return featuresToStringArray(getFeatures(font)); + } + @JBRTest private static Boolean testFeatureFromString() { return FontExtensions.FeatureTag.getFeatureTag("frac").isPresent() && @@ -101,24 +108,23 @@ private static Boolean testFeatureFromString() { FontExtensions.FeatureTag.getFeatureTag("tttt").isEmpty(); } -// TODO uncomment these tests when API for retrieving enabled font features is availbale -// @JBRTest -// private static Boolean testFeaturesToString1() { -// Font font = JBR.getFontExtensions().deriveFontWithFeatures(BASE_FONT, new FontExtensions.Features(Map.of( -// FontExtensions.FeatureTag.ZERO, FontExtensions.FEATURE_ON, -// FontExtensions.FeatureTag.SALT, 123, -// FontExtensions.FeatureTag.FRAC, FontExtensions.FEATURE_OFF))); -// String[] features = {"calt=0", "frac=0", "kern=0", "liga=0", "salt=123", "zero=1"}; -// return Arrays.equals(fontFeaturesAsString(font), features); -// } -// -// @JBRTest -// private static Boolean testFeaturesToString2() { -// Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON, -// TextAttribute.KERNING, TextAttribute.KERNING_ON)); -// String[] features = {"calt=1", "kern=1", "liga=1"}; -// return Arrays.equals(fontFeaturesAsString(font), features); -// } + @JBRTest + private static Boolean testFeaturesToString1() { + Font font = JBR.getFontExtensions().deriveFontWithFeatures(BASE_FONT, new FontExtensions.Features(Map.of( + FontExtensions.FeatureTag.ZERO, FontExtensions.FEATURE_ON, + FontExtensions.FeatureTag.SALT, 123, + FontExtensions.FeatureTag.FRAC, FontExtensions.FEATURE_OFF))); + String[] features = {"calt=0", "frac=0", "kern=0", "liga=0", "salt=123", "zero=1"}; + return Arrays.equals(fontFeaturesAsString(font), features); + } + + @JBRTest + private static Boolean testFeaturesToString2() { + Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON, + TextAttribute.KERNING, TextAttribute.KERNING_ON)); + String[] features = {"calt=1", "kern=1", "liga=1"}; + return Arrays.equals(fontFeaturesAsString(font), features); + } @JBRTest private static Boolean testDisablingLigatureByDefault() { @@ -251,7 +257,7 @@ public static void main(final String[] args) { } } } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("JBR: internal error during testing", e); + throw new RuntimeException("JBR: internal error during testing"); } if (!error.isEmpty()) {