From 96e7b288a50501021b48972192f2dc76032e03d7 Mon Sep 17 00:00:00 2001 From: Emanuel Krollmann Date: Fri, 10 Oct 2025 18:03:12 +0200 Subject: [PATCH] Added(terminal): add separate italic, bold and boldItalic font files --- .../TermuxTerminalSessionActivityClient.java | 15 +- .../com/termux/view/TerminalRenderer.java | 188 ++++++++++++++---- .../java/com/termux/view/TerminalView.java | 31 +-- .../termux/shared/termux/TermuxConstants.java | 12 ++ 4 files changed, 195 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java index cd38163116..a43bc6a95f 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java @@ -31,6 +31,7 @@ import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSessionClient; import com.termux.terminal.TextStyle; +import com.termux.view.TerminalRenderer; import java.io.File; import java.io.FileInputStream; @@ -494,7 +495,6 @@ String toToastTitle(TerminalSession session) { public void checkForFontAndColors() { try { File colorsFile = TermuxConstants.TERMUX_COLOR_PROPERTIES_FILE; - File fontFile = TermuxConstants.TERMUX_FONT_FILE; final Properties props = new Properties(); if (colorsFile.isFile()) { @@ -510,13 +510,22 @@ public void checkForFontAndColors() { } updateBackgroundColor(); - final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE; - mActivity.getTerminalView().setTypeface(newTypeface); + setTypeface(TermuxConstants.TERMUX_FONT_FILE, TerminalRenderer.TypefaceStyle.NORMAL); + setTypeface(TermuxConstants.TERMUX_ITALIC_FONT_FILE, TerminalRenderer.TypefaceStyle.ITALIC); + setTypeface(TermuxConstants.TERMUX_BOLD_FONT_FILE, TerminalRenderer.TypefaceStyle.BOLD); + setTypeface(TermuxConstants.TERMUX_BOLD_ITALIC_FONT_FILE, TerminalRenderer.TypefaceStyle.BOLD_ITALIC); } catch (Exception e) { Logger.logStackTraceWithMessage(LOG_TAG, "Error in checkForFontAndColors()", e); } } + private void setTypeface(File fontFile, TerminalRenderer.TypefaceStyle style) { + if (fontFile.exists() && fontFile.length() > 0) { + final Typeface newTypeface = Typeface.createFromFile(fontFile); + mActivity.getTerminalView().setTypeface(newTypeface, style); + } + } + public void updateBackgroundColor() { if (!mActivity.isVisible()) return; TerminalSession session = mActivity.getCurrentSession(); diff --git a/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java b/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java index a4bef7d37c..28947d4ce1 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java @@ -17,39 +17,53 @@ * Saves font metrics, so needs to be recreated each time the typeface or font size changes. */ public final class TerminalRenderer { - - final int mTextSize; - final Typeface mTypeface; + int mTextSize; private final Paint mTextPaint = new Paint(); - + /* These arrays are indexed by style e.g mTypefaceByStyle[TypefaceStyle.ITALIC]; */ + private final Typeface[] mTypefaceByStyle = new Typeface[4]; /** The width of a single mono spaced character obtained by {@link Paint#measureText(String)} on a single 'X'. */ - final float mFontWidth; + private final float[] mFontWidthByStyle = new float[4]; /** The {@link Paint#getFontSpacing()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */ - final int mFontLineSpacing; + private final int[] mFontLineSpacingByStyle = new int[4]; /** The {@link Paint#ascent()}. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */ - private final int mFontAscent; - /** The {@link #mFontLineSpacing} + {@link #mFontAscent}. */ - final int mFontLineSpacingAndAscent; - + private final int[] mFontAscentByStyle = new int[4]; + /** The {@link #mFontLineSpacingByStyle} + {@link #mFontAscentByStyle}. */ + private final int[] mFontLineSpacingAndAscentByStyle = new int[4]; private final float[] asciiMeasures = new float[127]; - public TerminalRenderer(int textSize, Typeface typeface) { + public TerminalRenderer(int textSize) { mTextSize = textSize; - mTypeface = typeface; + mTextPaint.setTextSize(textSize); + } + + public void setTextSize(int textSize) { + mTextSize = textSize; + mTextPaint.setTextSize(textSize); + + for (int i = 0; i < 4; i++) { + setTypeface(mTypefaceByStyle[i], TypefaceStyle.values()[i]); + } + } + + public void setTypeface(Typeface typeface, TypefaceStyle style) { + int idx = style.ordinal(); + + mTypefaceByStyle[idx] = typeface; mTextPaint.setTypeface(typeface); mTextPaint.setAntiAlias(true); - mTextPaint.setTextSize(textSize); - mFontLineSpacing = (int) Math.ceil(mTextPaint.getFontSpacing()); - mFontAscent = (int) Math.ceil(mTextPaint.ascent()); - mFontLineSpacingAndAscent = mFontLineSpacing + mFontAscent; - mFontWidth = mTextPaint.measureText("X"); + mFontLineSpacingByStyle[idx] = (int) Math.ceil(mTextPaint.getFontSpacing()); + mFontAscentByStyle[idx] = (int) Math.ceil(mTextPaint.ascent()); + mFontLineSpacingAndAscentByStyle[idx] = mFontLineSpacingByStyle[idx] + mFontAscentByStyle[idx]; + mFontWidthByStyle[idx] = mTextPaint.measureText("X"); - StringBuilder sb = new StringBuilder(" "); - for (int i = 0; i < asciiMeasures.length; i++) { - sb.setCharAt(0, (char) i); - asciiMeasures[i] = mTextPaint.measureText(sb, 0, 1); + if (style == TypefaceStyle.NORMAL) { + StringBuilder sb = new StringBuilder(" "); + for (int i = 0; i < asciiMeasures.length; i++) { + sb.setCharAt(0, (char) i); + asciiMeasures[i] = mTextPaint.measureText(sb, 0, 1); + } } } @@ -69,9 +83,13 @@ public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, if (reverseVideo) canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC); - float heightOffset = mFontLineSpacingAndAscent; + int fontLineSpacingAndAscent = getFontLineSpacingAndAscent(); + int fontLineSpacing = getFontLineSpacing(); + float fontWidth = getFontWidth(); + + float heightOffset = fontLineSpacingAndAscent; for (int row = topRow; row < endRow; row++) { - heightOffset += mFontLineSpacing; + heightOffset += fontLineSpacing; final int cursorX = (row == cursorRow && cursorVisible) ? cursorCol : -1; int selx1 = -1, selx2 = -1; @@ -109,7 +127,7 @@ public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, // If this is detected, we draw this code point scaled to match what wcwidth() expects. final float measuredCodePointWidth = (codePoint < asciiMeasures.length) ? asciiMeasures[codePoint] : mTextPaint.measureText(line, currentCharIndex, charsForCodePoint); - final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / mFontWidth - codePointWcWidth) > 0.01; + final boolean fontWidthMismatch = Math.abs(measuredCodePointWidth / fontWidth - codePointWcWidth) > 0.01; if (style != lastRunStyle || insideCursor != lastRunInsideCursor || insideSelection != lastRunInsideSelection || fontWidthMismatch || lastRunFontWidthMismatch) { if (column == 0) { @@ -156,6 +174,66 @@ public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, } } + /** + * Prepare the {@link #mTextPaint} for drawing the specified style. + * If there is no Typeface supplied for the style, we will use a + * fallback and return it. + **/ + private TypefaceStyle prepareStyleOrFallback(TypefaceStyle desiredStyle) { + TypefaceStyle fallbackStyle = desiredStyle; + switch (desiredStyle) { + case NORMAL: + mTextPaint.setTextSkewX(0.f); + mTextPaint.setFakeBoldText(false); + break; + case ITALIC: + if (mTypefaceByStyle[desiredStyle.ordinal()] == null) { + fallbackStyle = TypefaceStyle.NORMAL; + mTextPaint.setTextSkewX(-0.35f); + } else { + mTextPaint.setTextSkewX(0.f); + } + mTextPaint.setFakeBoldText(false); + break; + case BOLD: + if (mTypefaceByStyle[desiredStyle.ordinal()] == null) { + fallbackStyle = TypefaceStyle.NORMAL; + mTextPaint.setFakeBoldText(true); + } else { + mTextPaint.setFakeBoldText(false); + } + mTextPaint.setTextSkewX(0.f); + break; + case BOLD_ITALIC: + if (mTypefaceByStyle[desiredStyle.ordinal()] == null) { + // italic has priority over bold here since fake bold + // is not as bad as skewed text which may be to big + // for the cell + if (mTypefaceByStyle[TypefaceStyle.ITALIC.ordinal()] != null) { + fallbackStyle = TypefaceStyle.ITALIC; + mTextPaint.setFakeBoldText(true); + mTextPaint.setTextSkewX(0.f); + } else if (mTypefaceByStyle[TypefaceStyle.BOLD.ordinal()] != null) { + fallbackStyle = TypefaceStyle.BOLD; + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(-0.35f); + } else { + fallbackStyle = TypefaceStyle.NORMAL; + mTextPaint.setFakeBoldText(true); + mTextPaint.setTextSkewX(-0.35f); + } + } else { + mTextPaint.setFakeBoldText(false); + mTextPaint.setTextSkewX(0.f); + } + break; + } + + mTextPaint.setTypeface(mTypefaceByStyle[fallbackStyle.ordinal()]); + + return fallbackStyle; + } + private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int startColumn, int runWidthColumns, int startCharIndex, int runWidthChars, float mes, int cursor, int cursorStyle, long textStyle, boolean reverseVideo) { @@ -168,6 +246,21 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int final boolean strikeThrough = (effect & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0; final boolean dim = (effect & TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0; + TypefaceStyle style = TypefaceStyle.NORMAL; + if (italic && bold) { + style = prepareStyleOrFallback(TypefaceStyle.BOLD_ITALIC); + } else if (italic) { + style = prepareStyleOrFallback(TypefaceStyle.ITALIC); + } else if (bold) { + style = prepareStyleOrFallback(TypefaceStyle.BOLD); + } else { + prepareStyleOrFallback(style); + } + + float fontWidth = getFontWidth(style); + int fontAscent = getFontAscent(style); + int fontLineSpacingAndAscent = getFontLineSpacingAndAscent(style); + if ((foreColor & 0xff000000) != 0xff000000) { // Let bold have bright colors if applicable (one of the first 8): if (bold && foreColor >= 0 && foreColor < 8) foreColor += 8; @@ -186,10 +279,10 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int backColor = tmp; } - float left = startColumn * mFontWidth; - float right = left + runWidthColumns * mFontWidth; + float left = startColumn * fontWidth; + float right = left + runWidthColumns * fontWidth; - mes = mes / mFontWidth; + mes = mes / fontWidth; boolean savedMatrix = false; if (Math.abs(mes - runWidthColumns) > 0.01) { canvas.save(); @@ -202,12 +295,12 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) { // Only draw non-default background. mTextPaint.setColor(backColor); - canvas.drawRect(left, y - mFontLineSpacingAndAscent + mFontAscent, right, y, mTextPaint); + canvas.drawRect(left, y - fontLineSpacingAndAscent + fontAscent, right, y, mTextPaint); } if (cursor != 0) { mTextPaint.setColor(cursor); - float cursorHeight = mFontLineSpacingAndAscent - mFontAscent; + float cursorHeight = fontLineSpacingAndAscent - fontAscent; if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.; else if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.; canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint); @@ -226,24 +319,49 @@ private void drawTextRun(Canvas canvas, char[] text, int[] palette, float y, int foreColor = 0xFF000000 + (red << 16) + (green << 8) + blue; } - mTextPaint.setFakeBoldText(bold); mTextPaint.setUnderlineText(underline); - mTextPaint.setTextSkewX(italic ? -0.35f : 0.f); mTextPaint.setStrikeThruText(strikeThrough); mTextPaint.setColor(foreColor); // The text alignment is the default Paint.Align.LEFT. - canvas.drawTextRun(text, startCharIndex, runWidthChars, startCharIndex, runWidthChars, left, y - mFontLineSpacingAndAscent, false, mTextPaint); + canvas.drawTextRun(text, startCharIndex, runWidthChars, startCharIndex, runWidthChars, left, y - fontLineSpacingAndAscent, false, mTextPaint); } if (savedMatrix) canvas.restore(); } - public float getFontWidth() { - return mFontWidth; + public float getFontWidth(TypefaceStyle style) { + return mFontWidthByStyle[style.ordinal()]; + } + + public int getFontAscent(TypefaceStyle style) { + return mFontAscentByStyle[style.ordinal()]; + } + public int getFontLineSpacing(TypefaceStyle style) { + return mFontLineSpacingByStyle[style.ordinal()]; } + public int getFontLineSpacingAndAscent(TypefaceStyle style) { + return mFontLineSpacingAndAscentByStyle[style.ordinal()]; + } + + public float getFontWidth() { + return mFontWidthByStyle[TypefaceStyle.NORMAL.ordinal()]; + } + public int getFontAscent() { + return mFontAscentByStyle[TypefaceStyle.NORMAL.ordinal()]; + } public int getFontLineSpacing() { - return mFontLineSpacing; + return mFontLineSpacingByStyle[TypefaceStyle.NORMAL.ordinal()]; + } + public int getFontLineSpacingAndAscent() { + return mFontLineSpacingAndAscentByStyle[TypefaceStyle.NORMAL.ordinal()]; + } + + public enum TypefaceStyle { + NORMAL, + ITALIC, + BOLD, + BOLD_ITALIC, } } diff --git a/terminal-view/src/main/java/com/termux/view/TerminalView.java b/terminal-view/src/main/java/com/termux/view/TerminalView.java index 435c102515..35fa60f69d 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -178,8 +178,8 @@ public boolean onScroll(MotionEvent e, float distanceX, float distanceY) { } else { scrolledWithFinger = true; distanceY += mScrollRemainder; - int deltaRows = (int) (distanceY / mRenderer.mFontLineSpacing); - mScrollRemainder = distanceY - deltaRows * mRenderer.mFontLineSpacing; + int deltaRows = (int) (distanceY / mRenderer.getFontLineSpacing()); + mScrollRemainder = distanceY - deltaRows * mRenderer.getFontLineSpacing(); doScroll(e, deltaRows); } return true; @@ -512,12 +512,17 @@ public void onContextMenuClosed(Menu menu) { * @param textSize the new font size, in density-independent pixels. */ public void setTextSize(int textSize) { - mRenderer = new TerminalRenderer(textSize, mRenderer == null ? Typeface.MONOSPACE : mRenderer.mTypeface); + if (mRenderer == null) { + mRenderer = new TerminalRenderer(textSize); + mRenderer.setTypeface(Typeface.MONOSPACE, TerminalRenderer.TypefaceStyle.NORMAL); + } else { + mRenderer.setTextSize(textSize); + } updateSize(); } - public void setTypeface(Typeface newTypeface) { - mRenderer = new TerminalRenderer(mRenderer.mTextSize, newTypeface); + public void setTypeface(Typeface newTypeface, TerminalRenderer.TypefaceStyle style) { + mRenderer.setTypeface(newTypeface, style); updateSize(); invalidate(); } @@ -544,8 +549,8 @@ public boolean isOpaque() { * @return Array with the column and row. */ public int[] getColumnAndRow(MotionEvent event, boolean relativeToScroll) { - int column = (int) (event.getX() / mRenderer.mFontWidth); - int row = (int) ((event.getY() - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing); + int column = (int) (event.getX() / mRenderer.getFontWidth()); + int row = (int) ((event.getY() - mRenderer.getFontLineSpacingAndAscent()) / mRenderer.getFontLineSpacing()); if (relativeToScroll) { row += mTopRow; } @@ -985,8 +990,8 @@ public void updateSize() { if (viewWidth == 0 || viewHeight == 0 || mTermSession == null) return; // Set to 80 and 24 if you want to enable vttest. - int newColumns = Math.max(4, (int) (viewWidth / mRenderer.mFontWidth)); - int newRows = Math.max(4, (viewHeight - mRenderer.mFontLineSpacingAndAscent) / mRenderer.mFontLineSpacing); + int newColumns = Math.max(4, (int) (viewWidth / mRenderer.getFontWidth())); + int newRows = Math.max(4, (viewHeight - mRenderer.getFontLineSpacingAndAscent()) / mRenderer.getFontLineSpacing()); if (mEmulator == null || (newColumns != mEmulator.mColumns || newRows != mEmulator.mRows)) { mTermSession.updateSize(newColumns, newRows, (int) mRenderer.getFontWidth(), mRenderer.getFontLineSpacing()); @@ -1030,22 +1035,22 @@ private CharSequence getText() { } public int getCursorX(float x) { - return (int) (x / mRenderer.mFontWidth); + return (int) (x / mRenderer.getFontWidth()); } public int getCursorY(float y) { - return (int) (((y - 40) / mRenderer.mFontLineSpacing) + mTopRow); + return (int) (((y - 40) / mRenderer.getFontLineSpacing()) + mTopRow); } public int getPointX(int cx) { if (cx > mEmulator.mColumns) { cx = mEmulator.mColumns; } - return Math.round(cx * mRenderer.mFontWidth); + return Math.round(cx * mRenderer.getFontWidth()); } public int getPointY(int cy) { - return Math.round((cy - mTopRow) * mRenderer.mFontLineSpacing); + return Math.round((cy - mTopRow) * mRenderer.getFontLineSpacing()); } public int getTopRow() { diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 534936ffca..d0e1bfd4e4 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -771,6 +771,18 @@ public final class TermuxConstants { public static final String TERMUX_FONT_FILE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/font.ttf"; // Default: "/data/data/com.termux/files/home/.termux/font.ttf" /** Termux app and Termux:Styling font.ttf file */ public static final File TERMUX_FONT_FILE = new File(TERMUX_FONT_FILE_PATH); + /** Termux app and Termux:Styling font-italic.ttf file path */ + public static final String TERMUX_ITALIC_FONT_FILE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/font-italic.ttf"; // Default: "/data/data/com.termux/files/home/.termux/font-italic.ttf" + /** Termux app and Termux:Styling font-italic.ttf file */ + public static final File TERMUX_ITALIC_FONT_FILE = new File(TERMUX_ITALIC_FONT_FILE_PATH); + /** Termux app and Termux:Styling font-bold.ttf file path */ + public static final String TERMUX_BOLD_FONT_FILE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/font-bold.ttf"; // Default: "/data/data/com.termux/files/home/.termux/font-bold.ttf" + /** Termux app and Termux:Styling font-bold.ttf file */ + public static final File TERMUX_BOLD_FONT_FILE = new File(TERMUX_BOLD_FONT_FILE_PATH); + /** Termux app and Termux:Styling font-bold-italic.ttf file path */ + public static final String TERMUX_BOLD_ITALIC_FONT_FILE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/font-bold-italic.ttf"; // Default: "/data/data/com.termux/files/home/.termux/font-bold-italic.ttf" + /** Termux app and Termux:Styling font-bold-italic.ttf file */ + public static final File TERMUX_BOLD_ITALIC_FONT_FILE = new File(TERMUX_BOLD_ITALIC_FONT_FILE_PATH); /** Termux app and plugins crash log file path */