diff --git a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java index 96658a95567d..85d300ac067b 100644 --- a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java +++ b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java @@ -330,4 +330,8 @@ public class UISettings extends SimpleModificationTracker implements PersistentS public static void setupComponentAntialiasing(JComponent component) { component.putClientProperty(SwingUtilities2.AA_TEXT_PROPERTY_KEY, AntialiasingType.getAAHintForSwingComponent()); } + + public static void setupEditorAntialiasing(JComponent component) { + component.putClientProperty(SwingUtilities2.AA_TEXT_PROPERTY_KEY, getInstance().EDITOR_AA_TYPE.getTextInfo()); + } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java index 7869d851a036..63ccff477f3f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java @@ -35,6 +35,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.font.FontRenderContext; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -116,10 +117,14 @@ public class ParameterHintsPresentationManager implements Disposable { if (metrics != null) { Font font = metrics.getFont(); if (!familyName.equals(font.getFamily()) || size != font.getSize()) metrics = null; + else { + FontRenderContext currentContext = FontInfo.getFontRenderContext(editor.getContentComponent()); + if (currentContext.equals(metrics.metrics.getFontRenderContext())) metrics = null; + } } if (metrics == null) { Font font = new Font(familyName, Font.PLAIN, size); - metrics = new MyFontMetrics(font); + metrics = new MyFontMetrics(editor, font); editor.putUserData(HINT_FONT_METRICS, metrics); } return metrics; @@ -129,11 +134,10 @@ public class ParameterHintsPresentationManager implements Disposable { private final FontMetrics metrics; private final int lineHeight; - private MyFontMetrics(Font font) { - metrics = FontInfo.createReferenceGraphics().getFontMetrics(font); + private MyFontMetrics(Editor editor, Font font) { + metrics = editor.getContentComponent().getFontMetrics(font); // We assume this will be a better approximation to a real line height for a given font - lineHeight = (int)Math.ceil(metrics.getFont().createGlyphVector(metrics.getFontRenderContext(), "Ap") - .getVisualBounds().getHeight()); + lineHeight = (int)Math.ceil(font.createGlyphVector(metrics.getFontRenderContext(), "Ap").getVisualBounds().getHeight()); } private Font getFont() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java index bad786150f4a..2f0d1188e08f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupCellRenderer.java @@ -458,7 +458,7 @@ public class LookupCellRenderer implements ListCellRenderer { Set fonts = ContainerUtil.newHashSet(); FontPreferences fontPreferences = myLookup.getFontPreferences(); for (int i = 0; i < sampleString.length(); i++) { - fonts.add(ComplementaryFontsRegistry.getFontAbleToDisplay(sampleString.charAt(i), Font.PLAIN, fontPreferences).getFont()); + fonts.add(ComplementaryFontsRegistry.getFontAbleToDisplay(sampleString.charAt(i), Font.PLAIN, fontPreferences, null).getFont()); } eachFont: for (Font font : fonts) { diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/richcopy/TextWithMarkupProcessor.java b/platform/lang-impl/src/com/intellij/openapi/editor/richcopy/TextWithMarkupProcessor.java index b57c2e645d8a..80149bd4645c 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/richcopy/TextWithMarkupProcessor.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/richcopy/TextWithMarkupProcessor.java @@ -857,7 +857,7 @@ public class TextWithMarkupProcessor extends CopyPastePostProcessor ourFontNames; private static final Map[]> ourStyledFontMap = new HashMap<>(); - private static final LinkedHashMap ourUsedFonts; - private static FontKey ourSharedKeyInstance = new FontKey("", 0, Font.PLAIN, false); - private static FontInfo ourSharedDefaultFont; + private static final LinkedHashMap ourUsedFonts; + public static final FallBackInfo DEFAULT_FONT_INFO = new FallBackInfo("", Font.PLAIN, Font.PLAIN); + private static FontFaceKey ourSharedKeyInstance = new FontFaceKey("", Font.PLAIN); + private static FallBackInfo ourSharedFallBackInfo = DEFAULT_FONT_INFO; private static final TIntHashSet ourUndisplayableChars = new TIntHashSet(); - private static boolean ourOldUseAntialiasing; // This matches style detection in JDK (class sun.font.Font2D) private static final String[] BOLD_NAMES = {"bold", "demibold", "demi-bold", "demi bold", "negreta", "demi" }; @@ -66,63 +64,9 @@ public class ComplementaryFontsRegistry { FONT_NAME_TO_STYLE.put("Hasklig-BoldIt", Font.BOLD | Font.ITALIC); } - static { - final UISettings settings = UISettings.getInstance(); - ourOldUseAntialiasing = !AntialiasingType.OFF.equals(settings.EDITOR_AA_TYPE); - - // Reset font info on 'use antialiasing' setting change. - // Assuming that the listener is notified from the EDT only. - ApplicationManager.getApplication().getMessageBus().connect().subscribe(UISettingsListener.TOPIC, new UISettingsListener() { - @Override - public void uiSettingsChanged(UISettings uiSettings) { - if (ourOldUseAntialiasing ^ !AntialiasingType.OFF.equals(settings.EDITOR_AA_TYPE)) { - ourOldUseAntialiasing = !AntialiasingType.OFF.equals(settings.EDITOR_AA_TYPE); - for (FontInfo fontInfo : ourUsedFonts.values()) { - fontInfo.reset(); - } - ourUsedFonts.clear(); - } - } - }); - } - private ComplementaryFontsRegistry() { } - private static class FontKey { - private String myFamilyName; - private int mySize; - private int myStyle; - private boolean myUseLigatures; - - public FontKey(@NotNull String familyName, final int size, @JdkConstants.FontStyle int style, boolean useLigatures) { - myFamilyName = familyName; - mySize = size; - myStyle = style; - myUseLigatures = useLigatures; - } - - public boolean equals(final Object o) { - if (this == o) return true; - final FontKey fontKey = (FontKey)o; - - if (mySize != fontKey.mySize) return false; - if (myStyle != fontKey.myStyle) return false; - if (myUseLigatures != fontKey.myUseLigatures) return false; - return myFamilyName.equals(fontKey.myFamilyName); - } - - public int hashCode() { - int result = myFamilyName.hashCode(); - result = 29 * result + mySize; - result = 29 * result + myStyle; - if (myUseLigatures) { - result = 29 * result + 1; - } - return result; - } - } - @NonNls private static final String BOLD_SUFFIX = ".bold"; @NonNls private static final String ITALIC_SUFFIX = ".italic"; @@ -185,8 +129,21 @@ public class ComplementaryFontsRegistry { return Font.PLAIN; } + /** + * @deprecated Use {{@link #getFontAbleToDisplay(int, int, FontPreferences, FontRenderContext)}} instead. + */ @NotNull public static FontInfo getFontAbleToDisplay(int codePoint, @JdkConstants.FontStyle int style, @NotNull FontPreferences preferences) { + return getFontAbleToDisplay(codePoint, style, preferences, null); + } + + /** + * If you intend to use font metrics from returned {@link FontInfo} object, + * pass not-null correct {@link FontRenderContext} to this method. + */ + @NotNull + public static FontInfo getFontAbleToDisplay(int codePoint, @JdkConstants.FontStyle int style, @NotNull FontPreferences preferences, + FontRenderContext context) { boolean tryDefaultFont = true; List fontFamilies = preferences.getEffectiveFontFamilies(); boolean useLigatures = preferences.useLigatures(); @@ -194,7 +151,7 @@ public class ComplementaryFontsRegistry { //noinspection ForLoopReplaceableByForEach for (int i = 0, len = fontFamilies.size(); i < len; ++i) { // avoid foreach, it instantiates ArrayList$Itr, this traversal happens very often final String fontFamily = fontFamilies.get(i); - result = doGetFontAbleToDisplay(codePoint, preferences.getSize(fontFamily), style, fontFamily, useLigatures); + result = doGetFontAbleToDisplay(codePoint, preferences.getSize(fontFamily), style, fontFamily, useLigatures, context); if (result != null) { return result; } @@ -205,30 +162,43 @@ public class ComplementaryFontsRegistry { size = preferences.getSize(fontFamilies.get(0)); } if (tryDefaultFont) { - result = doGetFontAbleToDisplay(codePoint, size, style, FontPreferences.DEFAULT_FONT_NAME, useLigatures); + result = doGetFontAbleToDisplay(codePoint, size, style, FontPreferences.DEFAULT_FONT_NAME, useLigatures, context); if (result != null) { return result; } } - result = doGetFontAbleToDisplay(codePoint, size, style, useLigatures); + result = doGetFontAbleToDisplay(codePoint, size, style, useLigatures, context); if (LOG.isDebugEnabled()) { LOG.debug("Fallback font: " + result.getFont().getFontName()); } return result; } - + + /** + * @deprecated Use {{@link #getFontAbleToDisplay(int, int, int, String, FontRenderContext)}} + */ @NotNull public static FontInfo getFontAbleToDisplay(int codePoint, int size, @JdkConstants.FontStyle int style, @NotNull String defaultFontFamily) { - FontInfo result = doGetFontAbleToDisplay(codePoint, size, style, defaultFontFamily, false); + return getFontAbleToDisplay(codePoint, size, style, defaultFontFamily, null); + } + + /** + * If you intend to use font metrics from returned {@link FontInfo} object, + * pass not-null correct {@link FontRenderContext} to this method. + */ + @NotNull + public static FontInfo getFontAbleToDisplay(int codePoint, int size, @JdkConstants.FontStyle int style, @NotNull String defaultFontFamily, + FontRenderContext context) { + FontInfo result = doGetFontAbleToDisplay(codePoint, size, style, defaultFontFamily, false, context); if (result != null) { return result; } - return doGetFontAbleToDisplay(codePoint, size, style, false); + return doGetFontAbleToDisplay(codePoint, size, style, false, context); } @Nullable private static FontInfo doGetFontAbleToDisplay(int codePoint, int size, @JdkConstants.FontStyle int originalStyle, - @NotNull String defaultFontFamily, boolean useLigatures) { + @NotNull String defaultFontFamily, boolean useLigatures, FontRenderContext context) { synchronized (lock) { @JdkConstants.FontStyle int style = originalStyle; if (Patches.JDK_MAC_FONT_STYLE_DETECTION_WORKAROUND && style > 0 && style < 4) { @@ -238,69 +208,177 @@ public class ComplementaryFontsRegistry { style = replacement[style].second; } } - if (ourSharedKeyInstance.mySize == size && - ourSharedKeyInstance.myStyle == style && - ourSharedKeyInstance.myFamilyName != null && + FallBackInfo fallBackInfo; + if (ourSharedKeyInstance.myStyle == style && ourSharedKeyInstance.myFamilyName.equals(defaultFontFamily) && - ourSharedKeyInstance.myUseLigatures == useLigatures && - ourSharedDefaultFont != null && - ( codePoint < 128 || - ourSharedDefaultFont.canDisplay(codePoint) - ) - ) { - return ourSharedDefaultFont; - } - - ourSharedKeyInstance.myFamilyName = defaultFontFamily; - ourSharedKeyInstance.mySize = size; - ourSharedKeyInstance.myStyle = style; - ourSharedKeyInstance.myUseLigatures = useLigatures; - - FontInfo defaultFont = ourUsedFonts.get(ourSharedKeyInstance); - if (defaultFont == null) { - defaultFont = new FontInfo(defaultFontFamily, size, style, originalStyle, useLigatures); - ourUsedFonts.put(ourSharedKeyInstance, defaultFont); - ourSharedKeyInstance = new FontKey("", 0, Font.PLAIN, false); - } - - ourSharedDefaultFont = defaultFont; - if (codePoint < 128 || defaultFont.canDisplay(codePoint)) { - return defaultFont; + ourSharedFallBackInfo.canDisplay(codePoint)) { + fallBackInfo = ourSharedFallBackInfo; } else { - return null; + ourSharedKeyInstance.myFamilyName = defaultFontFamily; + ourSharedKeyInstance.myStyle = style; + + fallBackInfo = ourUsedFonts.get(ourSharedKeyInstance); + if (fallBackInfo == null) { + fallBackInfo = new FallBackInfo(defaultFontFamily, style, originalStyle); + ourUsedFonts.put(ourSharedKeyInstance.clone(), fallBackInfo); + } } + ourSharedFallBackInfo = fallBackInfo; + return fallBackInfo.canDisplay(codePoint) ? fallBackInfo.getFontInfo(size, useLigatures, context) : null; } } @NotNull - private static FontInfo doGetFontAbleToDisplay(int codePoint, int size, @JdkConstants.FontStyle int style, boolean useLigatures) { + private static FontInfo doGetFontAbleToDisplay(int codePoint, int size, @JdkConstants.FontStyle int style, boolean useLigatures, + FontRenderContext context) { synchronized (lock) { - if (ourUndisplayableChars.contains(codePoint)) return ourSharedDefaultFont; - - final Collection descriptors = ourUsedFonts.values(); - for (FontInfo font : descriptors) { - if (font.getSize() == size && - font.getStyle() == style && - font.areLigaturesEnabled() == useLigatures && - font.canDisplay(codePoint)) { - return font; + FallBackInfo fallBackInfo = DEFAULT_FONT_INFO; + if (!ourUndisplayableChars.contains(codePoint)) { + final Collection descriptors = ourUsedFonts.values(); + for (FallBackInfo info : descriptors) { + if (info.myOriginalStyle == style && + info.canDisplay(codePoint)) { + fallBackInfo = info; + break; + } + } + if (fallBackInfo == DEFAULT_FONT_INFO) { + for (int i = 0; i < ourFontNames.size(); i++) { + String name = ourFontNames.get(i); + FallBackInfo info = new FallBackInfo(name, style, style); + if (info.canDisplay(codePoint)) { + ourUsedFonts.put(new FontFaceKey(name, style), info); + ourFontNames.remove(i); + fallBackInfo = info; + break; + } + } + if (fallBackInfo == DEFAULT_FONT_INFO) { + ourUndisplayableChars.add(codePoint); + } } } + return fallBackInfo.getFontInfo(size, useLigatures, context); + } + } - for (int i = 0; i < ourFontNames.size(); i++) { - String name = ourFontNames.get(i); - FontInfo font = new FontInfo(name, size, style, style, useLigatures); - if (font.canDisplay(codePoint)) { - ourUsedFonts.put(new FontKey(name, size, style, useLigatures), font); - ourFontNames.remove(i); - return font; - } + private static class FontFaceKey implements Cloneable { + private String myFamilyName; + private int myStyle; + + private FontFaceKey(String familyName, int style) { + myFamilyName = familyName; + myStyle = style; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FontFaceKey key = (FontFaceKey)o; + + if (myStyle != key.myStyle) return false; + if (!myFamilyName.equals(key.myFamilyName)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myFamilyName.hashCode(); + result = 31 * result + myStyle; + return result; + } + + @Override + protected FontFaceKey clone() { + try { + return (FontFaceKey)super.clone(); } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + } - ourUndisplayableChars.add(codePoint); + private static class FontKey implements Cloneable { + private int mySize; + private boolean myUseLigatures; + private FontRenderContext myContext; - return ourSharedDefaultFont; + private FontKey(int size, boolean useLigatures, FontRenderContext context) { + mySize = size; + myUseLigatures = useLigatures; + myContext = context; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FontKey key = (FontKey)o; + + if (mySize != key.mySize) return false; + if (myUseLigatures != key.myUseLigatures) return false; + if (myContext != null ? !myContext.equals(key.myContext) : key.myContext != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = mySize; + result = 31 * result + (myUseLigatures ? 1 : 0); + result = 31 * result + (myContext != null ? myContext.hashCode() : 0); + return result; + } + + @Override + protected FontKey clone() { + try { + return (FontKey)super.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + } + + private static class FallBackInfo { + private final Font myBaseFont; + private final int myOriginalStyle; + private final Map myFontInfoMap = new HashMap<>(); + private final FontKey myLastFontKey = new FontKey(-1, false, new FontRenderContext(null, false, false)); + private FontInfo myLastFontInfo; + + private FallBackInfo(String familyName, @JdkConstants.FontStyle int style, int originalStyle) { + myBaseFont = new Font(familyName, style, 1); + myOriginalStyle = originalStyle; + } + + private boolean canDisplay(int codePoint) { + return codePoint < 128 || myBaseFont.canDisplay(codePoint); + } + + private FontInfo getFontInfo(int size, boolean useLigatures, FontRenderContext fontRenderContext) { + if (myLastFontKey.mySize == size && + myLastFontKey.myUseLigatures == useLigatures && + Objects.equals(myLastFontKey.myContext, fontRenderContext)) { + return myLastFontInfo; + } + myLastFontKey.mySize = size; + myLastFontKey.myUseLigatures = useLigatures; + myLastFontKey.myContext = fontRenderContext; + FontInfo fontInfo = myFontInfoMap.get(myLastFontKey); + if (fontInfo == null) { + fontInfo = new FontInfo(myBaseFont.getName(), size, myBaseFont.getStyle(), myOriginalStyle, useLigatures, fontRenderContext); + myFontInfoMap.put(myLastFontKey.clone(), fontInfo); + } + myLastFontInfo = fontInfo; + return fontInfo; } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java index a37dd3289233..f5e8a75718d2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java @@ -20,6 +20,7 @@ import gnu.trove.TObjectIntHashMap; import org.intellij.lang.annotations.JdkConstants; import java.awt.*; +import java.awt.font.FontRenderContext; /** * Not thread-safe. Performs caching of char widths, so cache reset must be invoked (via {@link #clearSymbolWidthCache()} method) when @@ -46,6 +47,7 @@ public class DefaultEditorTextRepresentationHelper implements EditorTextRepresen * {@link Editor#getColorsScheme()} often due to contention in 'assert read access'. */ private final Editor myEditor; + private FontRenderContext myFontRenderContext; public DefaultEditorTextRepresentationHelper(Editor editor) { myEditor = editor; @@ -67,7 +69,8 @@ public class DefaultEditorTextRepresentationHelper implements EditorTextRepresen return result; } Key key = mySharedKey.clone(); - FontInfo font = ComplementaryFontsRegistry.getFontAbleToDisplay(c, key.fontType, myEditor.getColorsScheme().getFontPreferences()); + FontInfo font = ComplementaryFontsRegistry.getFontAbleToDisplay(c, key.fontType, myEditor.getColorsScheme().getFontPreferences(), + myFontRenderContext); result = font.charWidth(c); if (mySymbolWidthCache.size() >= MAX_SYMBOLS_WIDTHS_CACHE_SIZE) { // Don't expect to be here. @@ -81,6 +84,12 @@ public class DefaultEditorTextRepresentationHelper implements EditorTextRepresen mySymbolWidthCache.clear(); } + public void updateContext() { + FontRenderContext oldContext = myFontRenderContext; + myFontRenderContext = FontInfo.getFontRenderContext(myEditor.getContentComponent()); + if (!myFontRenderContext.equals(oldContext)) clearSymbolWidthCache(); + } + private static class Key { @JdkConstants.FontStyle private int fontType; private int c; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorComponentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorComponentImpl.java index acbed3d0bbba..47eb7d4a044c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorComponentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorComponentImpl.java @@ -363,6 +363,7 @@ public class EditorComponentImpl extends JTextComponent implements Scrollable, D // work. We do however need to provide a TextUI implementation since some // screen reader support code will invoke it setUI(new EditorAccessibilityTextUI()); + UISettings.setupEditorAntialiasing(this); invalidate(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index e9e37464cebe..784176094e85 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -147,8 +147,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private static final Cursor EMPTY_CURSOR; static { - ComplementaryFontsRegistry.getFontAbleToDisplay(' ', 0, Font.PLAIN, UIManager.getFont("Label.font").getFamily()); // load costly font info - Cursor emptyCursor = null; if (!GraphicsEnvironment.isHeadless()) { try { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FontInfo.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FontInfo.java index 13a107c10a66..3c576f85ee2c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/FontInfo.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/FontInfo.java @@ -17,20 +17,18 @@ package com.intellij.openapi.editor.impl; import com.intellij.Patches; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.ex.util.EditorUIUtil; import com.intellij.openapi.editor.impl.view.FontLayoutService; import com.intellij.openapi.util.registry.Registry; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.UIUtil; import gnu.trove.TIntHashSet; import org.intellij.lang.annotations.JdkConstants; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import sun.font.FontDesignMetrics; import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.font.TextAttribute; -import java.awt.image.BufferedImage; import java.io.File; import java.io.FilenameFilter; import java.util.*; @@ -43,30 +41,47 @@ public class FontInfo { private static final Logger LOG = Logger.getInstance(FontInfo.class); private static final boolean USE_ALTERNATIVE_CAN_DISPLAY_PROCEDURE = Registry.is("ide.mac.fix.font.fallback"); - private static final FontRenderContext DUMMY_CONTEXT = new FontRenderContext(null, false, false); + private static final FontRenderContext DEFAULT_CONTEXT = new FontRenderContext(null, false, false); + private static final Font DUMMY_FONT = new Font(null); private final Font myFont; private final int mySize; @JdkConstants.FontStyle private final int myStyle; private final boolean myUseLigatures; private final TIntHashSet mySafeCharacters = new TIntHashSet(); + private final FontRenderContext myContext; private FontMetrics myFontMetrics = null; + /** + * @deprecated Use {@link #FontInfo(String, int, int, boolean, FontRenderContext)} instead. + */ public FontInfo(final String familyName, final int size, @JdkConstants.FontStyle int style) { - this(familyName, size, style, false); + this(familyName, size, style, style, false, null); } - + + /** + * @deprecated Use {@link #FontInfo(String, int, int, boolean, FontRenderContext)} instead. + */ public FontInfo(final String familyName, final int size, @JdkConstants.FontStyle int style, boolean useLigatures) { - this(familyName, size, style, style, useLigatures); + this(familyName, size, style, useLigatures, null); } - - FontInfo(final String familyName, final int size, - @JdkConstants.FontStyle int style, @JdkConstants.FontStyle int realStyle, boolean useLigatures) { + + /** + * To get valid font metrics from this {@link FontInfo} instance, pass valid {@link FontRenderContext} here as a parameter. + */ + public FontInfo(final String familyName, final int size, @JdkConstants.FontStyle int style, boolean useLigatures, + FontRenderContext fontRenderContext) { + this(familyName, size, style, style, useLigatures, fontRenderContext); + } + + FontInfo(final String familyName, final int size, + @JdkConstants.FontStyle int style, @JdkConstants.FontStyle int realStyle, boolean useLigatures, FontRenderContext context) { mySize = size; myStyle = style; myUseLigatures = useLigatures; Font font = new Font(familyName, style, size); myFont = useLigatures ? getFontWithLigaturesEnabled(font, realStyle) : font; + myContext = context; } @NotNull @@ -155,7 +170,7 @@ public class FontInfo { private boolean canDisplayImpl(int codePoint) { if (!Character.isValidCodePoint(codePoint)) return false; if (USE_ALTERNATIVE_CAN_DISPLAY_PROCEDURE) { - return myFont.createGlyphVector(DUMMY_CONTEXT, new String(new int[]{codePoint}, 0, 1)).getGlyphCode(0) > 0; + return myFont.createGlyphVector(DEFAULT_CONTEXT, new String(new int[]{codePoint}, 0, 1)).getGlyphCode(0) > 0; } else { return myFont.canDisplay(codePoint); @@ -176,27 +191,20 @@ public class FontInfo { return FontLayoutService.getInstance().charWidth2D(metrics, codePoint); } - public FontMetrics fontMetrics() { + public synchronized FontMetrics fontMetrics() { if (myFontMetrics == null) { - // We need to use antialising-aware font metrics because we've alrady encountered a situation when non-antialiased symbol - // width is not equal to the antialiased one (IDEA-81539). - final Graphics graphics = createReferenceGraphics(); - graphics.setFont(myFont); - myFontMetrics = graphics.getFontMetrics(); + myFontMetrics = FontDesignMetrics.getMetrics(myFont, myContext == null ? getFontRenderContext(null) : myContext); } return myFontMetrics; } - public static Graphics2D createReferenceGraphics() { - Graphics2D graphics = (Graphics2D)UIUtil.createImage(1, 1, BufferedImage.TYPE_INT_RGB).getGraphics(); - EditorUIUtil.setupAntialiasing(graphics); - return graphics; + public static FontRenderContext getFontRenderContext(Component component) { + if (component == null) { + return DEFAULT_CONTEXT; + } + return component.getFontMetrics(DUMMY_FONT).getFontRenderContext(); } - - void reset() { - myFontMetrics = null; - } - + public int getSize() { return mySize; } @@ -209,7 +217,11 @@ public class FontInfo { public boolean areLigaturesEnabled() { return myUseLigatures; } - + + public FontRenderContext getFontRenderContext() { + return myContext; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java index 96e7ecb26fe0..60dd68cedb02 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/softwrap/mapping/SoftWrapApplianceManager.java @@ -24,10 +24,7 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.ScrollingModelEx; import com.intellij.openapi.editor.ex.util.EditorUtil; -import com.intellij.openapi.editor.impl.DocumentImpl; -import com.intellij.openapi.editor.impl.EditorImpl; -import com.intellij.openapi.editor.impl.SoftWrapModelImpl; -import com.intellij.openapi.editor.impl.TextChangeImpl; +import com.intellij.openapi.editor.impl.*; import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType; import com.intellij.openapi.editor.impl.softwrap.SoftWrapImpl; import com.intellij.openapi.editor.impl.softwrap.SoftWrapPainter; @@ -245,6 +242,10 @@ public class SoftWrapApplianceManager implements Dumpable { myContext.reset(); myOffset2fontType.clear(); myOffset2widthInPixels.clear(); + EditorTextRepresentationHelper editorTextRepresentationHelper = SoftWrapModelImpl.getEditorTextRepresentationHelper(myEditor); + if (editorTextRepresentationHelper instanceof DefaultEditorTextRepresentationHelper) { + ((DefaultEditorTextRepresentationHelper)editorTextRepresentationHelper).updateContext(); + } // Define start of the visual line that holds target range start. final int start = event.getStartOffset(); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/ComplexTextFragment.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/ComplexTextFragment.java index dcb47eca287d..abfa5e16022d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/ComplexTextFragment.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/ComplexTextFragment.java @@ -16,12 +16,12 @@ package com.intellij.openapi.editor.impl.view; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.util.BitUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; -import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.Rectangle2D; import java.util.Arrays; @@ -32,21 +32,21 @@ import java.util.Arrays; class ComplexTextFragment extends TextFragment { private static final Logger LOG = Logger.getInstance(ComplexTextFragment.class); private static final double CLIP_MARGIN = 1e4; - + @NotNull private final GlyphVector myGlyphVector; @Nullable private final short[] myCodePoint2Offset; // Start offset of each Unicode code point in the fragment // (null if each code point takes one char). // We expect no more than 1025 chars in a fragment, so 'short' should be enough. - - ComplexTextFragment(@NotNull char[] lineChars, int start, int end, boolean isRtl, - @NotNull Font font, @NotNull FontRenderContext fontRenderContext) { + + ComplexTextFragment(@NotNull char[] lineChars, int start, int end, boolean isRtl, @NotNull FontInfo fontInfo) { super(end - start); assert start >= 0; assert end <= lineChars.length; assert start < end; - myGlyphVector = FontLayoutService.getInstance().layoutGlyphVector(font, fontRenderContext, lineChars, start, end, isRtl); + myGlyphVector = FontLayoutService.getInstance().layoutGlyphVector(fontInfo.getFont(), fontInfo.getFontRenderContext(), + lineChars, start, end, isRtl); int numChars = end - start; int numGlyphs = myGlyphVector.getNumGlyphs(); float totalWidth = (float)myGlyphVector.getGlyphPosition(numGlyphs).getX(); @@ -106,7 +106,7 @@ class ComplexTextFragment extends TextFragment { } } } - + private void setCharPosition(int logicalCharIndex, float x, boolean isRtl, int numChars) { int charPosition = isRtl ? numChars - logicalCharIndex - 2 : logicalCharIndex; if (charPosition >= 0 && charPosition < numChars - 1) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java index 33aa86d53126..7969846c0d8e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/EditorView.java @@ -22,6 +22,8 @@ import com.intellij.openapi.editor.FoldRegion; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.colors.EditorFontType; +import com.intellij.openapi.editor.event.VisibleAreaEvent; +import com.intellij.openapi.editor.event.VisibleAreaListener; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.editor.ex.util.EditorUtil; @@ -36,7 +38,10 @@ import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; +import javax.swing.*; import java.awt.*; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; import java.awt.font.FontRenderContext; import java.text.Bidi; @@ -46,7 +51,7 @@ import java.text.Bidi; * * Also contains a cache of several font-related quantities (line height, space width, etc). */ -public class EditorView implements TextDrawingCallback, Disposable, Dumpable { +public class EditorView implements TextDrawingCallback, Disposable, Dumpable, HierarchyListener, VisibleAreaListener { private static Key FOLD_REGION_TEXT_LAYOUT = Key.create("text.layout"); private final EditorImpl myEditor; @@ -76,7 +81,6 @@ public class EditorView implements TextDrawingCallback, Disposable, Dumpable { private final Object myLock = new Object(); public EditorView(EditorImpl editor) { - setFontRenderContext(); myEditor = editor; myDocument = editor.getDocument(); @@ -86,7 +90,10 @@ public class EditorView implements TextDrawingCallback, Disposable, Dumpable { myTextLayoutCache = new TextLayoutCache(this); myLogicalPositionCache = new LogicalPositionCache(this); myTabFragment = new TabFragment(this); - + + myEditor.getContentComponent().addHierarchyListener(this); + myEditor.getScrollingModel().addVisibleAreaListener(this); + Disposer.register(this, myLogicalPositionCache); Disposer.register(this, myTextLayoutCache); Disposer.register(this, mySizeManager); @@ -122,8 +129,21 @@ public class EditorView implements TextDrawingCallback, Disposable, Dumpable { @Override public void dispose() { + myEditor.getScrollingModel().removeVisibleAreaListener(this); + myEditor.getContentComponent().removeHierarchyListener(this); } + @Override + public void hierarchyChanged(HierarchyEvent e) { + if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && e.getComponent().isShowing()) { + checkFontRenderContext(); + } + } + + @Override + public void visibleAreaChanged(VisibleAreaEvent e) { + checkFontRenderContext(); + } public int yToVisualLine(int y) { return myMapper.yToVisualLine(y); } @@ -481,12 +501,15 @@ public class EditorView implements TextDrawingCallback, Disposable, Dumpable { } private void setFontRenderContext() { - Graphics2D g = FontInfo.createReferenceGraphics(); - try { - myFontRenderContext = g.getFontRenderContext(); - } - finally { - g.dispose(); + JComponent component = myEditor.getContentComponent(); + myFontRenderContext = FontInfo.getFontRenderContext(component); + } + + private void checkFontRenderContext() { + FontRenderContext oldContext = myFontRenderContext; + setFontRenderContext(); + if (!myFontRenderContext.equals(oldContext)) { + myTextLayoutCache.resetToDocumentSize(false); } } @@ -514,7 +537,7 @@ public class EditorView implements TextDrawingCallback, Disposable, Dumpable { int getBidiFlags() { return myBidiFlags; } - + private static void assertIsDispatchThread() { ApplicationManager.getApplication().assertIsDispatchThread(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java index d9ff57e01bb0..e0c23165d74a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java @@ -266,7 +266,7 @@ abstract class LineLayout { char c = text[i]; if (c == '\t' && tabFragment != null) { assert run.level == 0; - addTextFragmentIfNeeded(chunk, text, currentIndex, i, currentFontInfo, fontRenderContext, false); + addTextFragmentIfNeeded(chunk, text, currentIndex, i, currentFontInfo, false); chunk.fragments.add(tabFragment); currentFontInfo = null; currentIndex = i + 1; @@ -281,24 +281,23 @@ abstract class LineLayout { surrogatePair = true; } } - FontInfo fontInfo = ComplementaryFontsRegistry.getFontAbleToDisplay(codePoint, fontStyle, fontPreferences); + FontInfo fontInfo = ComplementaryFontsRegistry.getFontAbleToDisplay(codePoint, fontStyle, fontPreferences, fontRenderContext); if (!fontInfo.equals(currentFontInfo)) { - addTextFragmentIfNeeded(chunk, text, currentIndex, i, currentFontInfo, fontRenderContext, run.isRtl()); + addTextFragmentIfNeeded(chunk, text, currentIndex, i, currentFontInfo, run.isRtl()); currentFontInfo = fontInfo; currentIndex = i; } if (surrogatePair) i++; } } - addTextFragmentIfNeeded(chunk, text, currentIndex, end, currentFontInfo, fontRenderContext, run.isRtl()); + addTextFragmentIfNeeded(chunk, text, currentIndex, end, currentFontInfo, run.isRtl()); assert !chunk.fragments.isEmpty(); } - private static void addTextFragmentIfNeeded(Chunk chunk, char[] chars, int from, int to, FontInfo fontInfo, - FontRenderContext fontRenderContext, boolean isRtl) { + private static void addTextFragmentIfNeeded(Chunk chunk, char[] chars, int from, int to, FontInfo fontInfo, boolean isRtl) { if (to > from) { assert fontInfo != null; - chunk.fragments.add(TextFragmentFactory.createTextFragment(chars, from, to, isRtl, fontInfo, fontRenderContext)); + chunk.fragments.add(TextFragmentFactory.createTextFragment(chars, from, to, isRtl, fontInfo)); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextFragmentFactory.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextFragmentFactory.java index c15036f93079..f3e625312e15 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextFragmentFactory.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/TextFragmentFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,10 @@ import com.intellij.openapi.editor.impl.FontInfo; import org.jetbrains.annotations.NotNull; import sun.swing.SwingUtilities2; -import java.awt.font.FontRenderContext; - class TextFragmentFactory { - public static TextFragment createTextFragment(@NotNull char[] lineChars, int start, int end, boolean isRtl, - @NotNull FontInfo fontInfo, @NotNull FontRenderContext fontRenderContext) { + public static TextFragment createTextFragment(@NotNull char[] lineChars, int start, int end, boolean isRtl, @NotNull FontInfo fontInfo) { if (isRtl || fontInfo.getFont().hasLayoutAttributes() || isComplexText(lineChars, start, end)) { - return new ComplexTextFragment(lineChars, start, end, isRtl, fontInfo.getFont(), fontRenderContext); + return new ComplexTextFragment(lineChars, start, end, isRtl, fontInfo); } else { return new SimpleTextFragment(lineChars, start, end, fontInfo); diff --git a/platform/platform-impl/src/com/intellij/ui/SuitableFontProviderImpl.java b/platform/platform-impl/src/com/intellij/ui/SuitableFontProviderImpl.java index 3e760cf2fefd..70537869de9a 100644 --- a/platform/platform-impl/src/com/intellij/ui/SuitableFontProviderImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/SuitableFontProviderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,6 @@ import java.awt.*; public class SuitableFontProviderImpl implements SuitableFontProvider { @Override public Font getFontAbleToDisplay(char c, int size, @JdkConstants.FontStyle int style, @NotNull String defaultFontFamily) { - return ComplementaryFontsRegistry.getFontAbleToDisplay(c, size, style, defaultFontFamily).getFont(); + return ComplementaryFontsRegistry.getFontAbleToDisplay(c, size, style, defaultFontFamily, null).getFont(); } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/view/ComplexTextFragmentTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/view/ComplexTextFragmentTest.java index 61150248f5d4..008f33e42af6 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/view/ComplexTextFragmentTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/view/ComplexTextFragmentTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package com.intellij.openapi.editor.impl.view; +import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.testFramework.AbstractMockGlyphVector; import com.intellij.testFramework.MockFontLayoutService; import com.intellij.util.ArrayUtil; @@ -100,8 +101,9 @@ public class ComplexTextFragmentTest { try { int length = gv.getNumChars(); char[] text = new char[length]; + FontInfo fontInfo = new FontInfo(Font.MONOSPACED, 1, Font.PLAIN, false, null); ComplexTextFragment fragment = new ComplexTextFragment(text, 0, length, (gv.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) != 0, - new Font(null), new FontRenderContext(null, false, false)); + fontInfo); int[] charPositions = new int[length]; for (int i = 0; i < length; i++) { charPositions[i] = (int)fragment.visualColumnToX(0, i + 1); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/FontUtil.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/FontUtil.java index 2e471e506c41..4a75bc2a5e71 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/FontUtil.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ui/FontUtil.java @@ -24,7 +24,7 @@ import java.awt.*; public class FontUtil { @NotNull public static Font getFontAbleToDisplay(char c, int size, int style, @NotNull String family) { - return ComplementaryFontsRegistry.getFontAbleToDisplay(c, size, style, family).getFont(); + return ComplementaryFontsRegistry.getFontAbleToDisplay(c, size, style, family, null).getFont(); } @NotNull diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerInlayUtil.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerInlayUtil.java index 2afe3530d5c3..948ddbc909d2 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerInlayUtil.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebuggerInlayUtil.java @@ -88,7 +88,8 @@ public class XDebuggerInlayUtil { FontPreferences fontPreferences = colorsScheme.getFontPreferences(); TextAttributes attributes = editor.getColorsScheme().getAttributes(DebuggerColors.INLINED_VALUES_EXECUTION_LINE); int fontStyle = attributes == null ? Font.PLAIN : attributes.getFontType(); - return ComplementaryFontsRegistry.getFontAbleToDisplay('a', fontStyle, fontPreferences); + return ComplementaryFontsRegistry.getFontAbleToDisplay('a', fontStyle, fontPreferences, + FontInfo.getFontRenderContext(editor.getContentComponent())); } @Override diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/JBTerminalPanel.java b/plugins/terminal/src/org/jetbrains/plugins/terminal/JBTerminalPanel.java index 2ece3599baf8..72422034bea0 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/JBTerminalPanel.java +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/JBTerminalPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -293,7 +293,8 @@ public class JBTerminalPanel extends TerminalPanel implements FocusListener, Ter } public FontInfo fontForChar(final char c, @JdkConstants.FontStyle int style) { - return ComplementaryFontsRegistry.getFontAbleToDisplay(c, style, mySettingsProvider.getColorScheme().getConsoleFontPreferences()); + return ComplementaryFontsRegistry.getFontAbleToDisplay(c, style, mySettingsProvider.getColorScheme().getConsoleFontPreferences(), + null); } @Override