Repository: suragch/AndroidFontMetrics Branch: master Commit: 94aa4362f684 Files: 35 Total size: 49.1 KB Directory structure: gitextract_mj1ve3yt/ ├── .gitignore ├── .idea/ │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── markdown-exported-files.xml │ ├── markdown-navigator/ │ │ └── profiles_settings.xml │ ├── markdown-navigator.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── net/ │ │ └── studymongolian/ │ │ └── fontmetrics/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── net/ │ │ │ └── studymongolian/ │ │ │ └── fontmetrics/ │ │ │ ├── FontMetricsView.java │ │ │ └── MainActivity.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── net/ │ └── studymongolian/ │ └── fontmetrics/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures .externalNativeBuild ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/markdown-exported-files.xml ================================================ ================================================ FILE: .idea/markdown-navigator/profiles_settings.xml ================================================ ================================================ FILE: .idea/markdown-navigator.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ 1.8 ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 suragch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Android FontMetrics An Android app for measuring and testing FontMetrics ![App preview](/Xi2UG.png?raw=true) This app was originally created and used for these Stack Overflow questions: - http://stackoverflow.com/questions/27631736/meaning-of-top-ascent-baseline-descent-bottom-and-leading-in-androids-font - http://stackoverflow.com/a/42091739/ I decided to put the project on GitHub to make it easier to experiment with, and also so that other people will improve it. ## TODO If you want to help, here are some improvements that are needed: - Color code the checkbox items and the lines in the custom view - Keyboard popping up when first entering app isn't necessary - Add a few custom (free) fonts and add an option to select different fonts. ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "net.studymongolian.fontmetrics" minSdkVersion 9 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.1.1' testCompile 'junit:junit:4.12' } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/yonghu/Android/Sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: app/src/androidTest/java/net/studymongolian/fontmetrics/ExampleInstrumentedTest.java ================================================ package net.studymongolian.fontmetrics; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("net.studymongolian.fontmetrics", appContext.getPackageName()); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/net/studymongolian/fontmetrics/FontMetricsView.java ================================================ package net.studymongolian.fontmetrics; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.text.TextPaint; import android.util.AttributeSet; import android.view.View; import android.widget.Toast; public class FontMetricsView extends View { public final static int DEFAULT_FONT_SIZE_PX = 200; //private static final int PURPLE = Color.parseColor("#9315db"); //private static final int ORANGE = Color.parseColor("#ff8a00"); private static final float STROKE_WIDTH = 5.0f; private String mText; private int mTextSize; private Paint mAscentPaint; private Paint mTopPaint; private Paint mBaselinePaint; private Paint mDescentPaint; private Paint mBottomPaint; private Paint mMeasuredWidthPaint; private Paint mTextBoundsPaint; private TextPaint mTextPaint; private Paint mLinePaint; private Paint mRectPaint; private Rect mBounds; private boolean mIsTopVisible; private boolean mIsAscentVisible; private boolean mIsBaselineVisible; private boolean mIsDescentVisible; private boolean mIsBottomVisible; private boolean mIsBoundsVisible; private boolean mIsWidthVisible; public FontMetricsView(Context context) { super(context); init(); } public FontMetricsView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { mText = "My text line"; mTextSize = DEFAULT_FONT_SIZE_PX; mTextPaint = new TextPaint(); mTextPaint.setAntiAlias(true); mTextPaint.setTextSize(mTextSize); mTextPaint.setColor(Color.BLACK); mLinePaint = new Paint(); mLinePaint.setColor(Color.RED); mLinePaint.setStrokeWidth(STROKE_WIDTH); mAscentPaint = new Paint(); mAscentPaint.setColor(getResources().getColor(R.color.ascent)); mAscentPaint.setStrokeWidth(STROKE_WIDTH); mTopPaint = new Paint(); mTopPaint.setColor(getResources().getColor(R.color.top)); mTopPaint.setStrokeWidth(STROKE_WIDTH); mBaselinePaint = new Paint(); mBaselinePaint.setColor(getResources().getColor(R.color.baseline)); mBaselinePaint.setStrokeWidth(STROKE_WIDTH); mBottomPaint = new Paint(); mBottomPaint.setColor(getResources().getColor(R.color.bottom)); mBottomPaint.setStrokeWidth(STROKE_WIDTH); mDescentPaint = new Paint(); mDescentPaint.setColor(getResources().getColor(R.color.descent)); mDescentPaint.setStrokeWidth(STROKE_WIDTH); mMeasuredWidthPaint = new Paint(); mMeasuredWidthPaint.setColor(getResources().getColor(R.color.measured_width)); mMeasuredWidthPaint.setStrokeWidth(STROKE_WIDTH); mTextBoundsPaint = new Paint(); mTextBoundsPaint.setColor(getResources().getColor(R.color.text_bounds)); mTextBoundsPaint.setStrokeWidth(STROKE_WIDTH); mTextBoundsPaint.setStyle(Paint.Style.STROKE); mRectPaint = new Paint(); mRectPaint.setColor(Color.BLACK); mRectPaint.setStrokeWidth(STROKE_WIDTH); mRectPaint.setStyle(Paint.Style.STROKE); mBounds = new Rect(); mIsTopVisible = true; mIsAscentVisible = true; mIsBaselineVisible = true; mIsDescentVisible = true; mIsBottomVisible = true; mIsBoundsVisible = true; mIsWidthVisible = true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // center the text baseline vertically int verticalAdjustment = this.getHeight() / 2; canvas.translate(0, verticalAdjustment); float startX = getPaddingLeft(); float startY = 0; float stopX = this.getMeasuredWidth(); float stopY = 0; // draw text canvas.drawText(mText, startX, startY, mTextPaint); // x=0, y=0 // draw lines startX = 0; if (mIsTopVisible) { startY = mTextPaint.getFontMetrics().top; stopY = startY; canvas.drawLine(startX, startY, stopX, stopY, mTopPaint); } if (mIsAscentVisible) { startY = mTextPaint.getFontMetrics().ascent; stopY = startY; //mLinePaint.setColor(Color.GREEN); canvas.drawLine(startX, startY, stopX, stopY, mAscentPaint); } if (mIsBaselineVisible) { startY = 0; stopY = startY; canvas.drawLine(startX, startY, stopX, stopY, mBaselinePaint); } if (mIsDescentVisible) { startY = mTextPaint.getFontMetrics().descent; stopY = startY; //mLinePaint.setColor(Color.BLUE); canvas.drawLine(startX, startY, stopX, stopY, mDescentPaint); } if (mIsBottomVisible) { startY = mTextPaint.getFontMetrics().bottom; stopY = startY; // mLinePaint.setColor(ORANGE); mLinePaint.setColor(Color.RED); canvas.drawLine(startX, startY, stopX, stopY, mBaselinePaint); } if (mIsBoundsVisible) { mTextPaint.getTextBounds(mText, 0, mText.length(), mBounds); float dx = getPaddingLeft(); canvas.drawRect(mBounds.left + dx, mBounds.top, mBounds.right + dx, mBounds.bottom, mTextBoundsPaint); } if (mIsWidthVisible) { // get measured width float width = mTextPaint.measureText(mText); // get bounding width so that we can compare them mTextPaint.getTextBounds(mText, 0, mText.length(), mBounds); // draw vertical line just before the left bounds startX = getPaddingLeft() + mBounds.left - (width - mBounds.width()) / 2; stopX = startX; startY = -verticalAdjustment; stopY = startY + this.getHeight(); canvas.drawLine(startX, startY, stopX, stopY, mMeasuredWidthPaint); // draw vertical line just after the right bounds startX = startX + width; stopX = startX; canvas.drawLine(startX, startY, stopX, stopY, mMeasuredWidthPaint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 200; int height = 200; int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthRequirement = MeasureSpec.getSize(widthMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { width = widthRequirement; } else if (widthMode == MeasureSpec.AT_MOST && width > widthRequirement) { width = widthRequirement; } int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightRequirement = MeasureSpec.getSize(heightMeasureSpec); if (heightMode == MeasureSpec.EXACTLY) { height = heightRequirement; } else if (heightMode == MeasureSpec.AT_MOST && width > heightRequirement) { height = heightRequirement; } setMeasuredDimension(width, height); } // getters public Paint.FontMetrics getFontMetrics() { return mTextPaint.getFontMetrics(); } public Rect getTextBounds() { mTextPaint.getTextBounds(mText, 0, mText.length(), mBounds); return mBounds; } public float getMeasuredTextWidth() { return mTextPaint.measureText(mText); } // setters public void setText(String text) { mText = text; invalidate(); requestLayout(); } public void setTextSizeInPixels(int pixels) { mTextSize = pixels; mTextPaint.setTextSize(mTextSize); invalidate(); requestLayout(); } public void setTopVisible(boolean isVisible) { mIsTopVisible = isVisible; invalidate(); } public void setAscentVisible(boolean isVisible) { mIsAscentVisible = isVisible; invalidate(); } public void setBaselineVisible(boolean isVisible) { mIsBaselineVisible = isVisible; invalidate(); } public void setDescentVisible(boolean isVisible) { mIsDescentVisible = isVisible; invalidate(); } public void setBottomVisible(boolean isVisible) { mIsBottomVisible = isVisible; invalidate(); } public void setBoundsVisible(boolean isVisible) { mIsBoundsVisible = isVisible; invalidate(); } public void setWidthVisible(boolean isVisible) { mIsWidthVisible = isVisible; invalidate(); } } ================================================ FILE: app/src/main/java/net/studymongolian/fontmetrics/MainActivity.java ================================================ package net.studymongolian.fontmetrics; import android.content.Context; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.CheckBox; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ FontMetricsView myFontMetricsView; // custom view EditText mTextStringEditText; EditText mFontSizeEditText; CheckBox cbTop; CheckBox cbAscent; CheckBox cbBaseline; CheckBox cbDescent; CheckBox cbBottom; CheckBox cbBounds; CheckBox cbMeasuredWidth; TextView tvTop; TextView tvAscent; TextView tvBaseline; TextView tvDescent; TextView tvBottom; TextView tvBounds; TextView tvMeasuredWidth; TextView tvLeading; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myFontMetricsView = (FontMetricsView) findViewById(R.id.viewWindow); mTextStringEditText = (EditText) findViewById(R.id.etTextString); mFontSizeEditText = (EditText) findViewById(R.id.etFontSize); mTextStringEditText.setText("My text line"); mFontSizeEditText.setText("200"); findViewById(R.id.updateButton).setOnClickListener(this); cbTop = (CheckBox) findViewById(R.id.cbTop); cbAscent = (CheckBox) findViewById(R.id.cbAscent); cbBaseline = (CheckBox) findViewById(R.id.cbBaseline); cbDescent = (CheckBox) findViewById(R.id.cbDescent); cbBottom = (CheckBox) findViewById(R.id.cbBottom); cbBounds = (CheckBox) findViewById(R.id.cbTextBounds); cbMeasuredWidth = (CheckBox) findViewById(R.id.cbWidth); cbTop.setOnClickListener(this); cbAscent.setOnClickListener(this); cbBaseline.setOnClickListener(this); cbDescent.setOnClickListener(this); cbBottom.setOnClickListener(this); cbBounds.setOnClickListener(this); cbMeasuredWidth.setOnClickListener(this); tvTop = (TextView) findViewById(R.id.tvTop); tvAscent = (TextView) findViewById(R.id.tvAscent); tvBaseline = (TextView) findViewById(R.id.tvBaseline); tvDescent = (TextView) findViewById(R.id.tvDescent); tvBottom = (TextView) findViewById(R.id.tvBottom); tvBounds = (TextView) findViewById(R.id.tvTextBounds); tvMeasuredWidth = (TextView) findViewById(R.id.tvWidth); tvLeading = (TextView) findViewById(R.id.tvLeadingValue); updateTextViews(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.updateButton: myFontMetricsView.setText(mTextStringEditText.getText().toString()); int fontSize; try { fontSize = Integer.valueOf(mFontSizeEditText.getText().toString()); }catch (NumberFormatException e) { fontSize = FontMetricsView.DEFAULT_FONT_SIZE_PX; } myFontMetricsView.setTextSizeInPixels(fontSize); updateTextViews(); hideKeyboard(getCurrentFocus()); break; case R.id.cbTop: myFontMetricsView.setTopVisible(cbTop.isChecked()); break; case R.id.cbAscent: myFontMetricsView.setAscentVisible(cbAscent.isChecked()); break; case R.id.cbBaseline: myFontMetricsView.setBaselineVisible(cbBaseline.isChecked()); break; case R.id.cbDescent: myFontMetricsView.setDescentVisible(cbDescent.isChecked()); break; case R.id.cbBottom: myFontMetricsView.setBottomVisible(cbBottom.isChecked()); break; case R.id.cbTextBounds: myFontMetricsView.setBoundsVisible(cbBounds.isChecked()); break; case R.id.cbWidth: myFontMetricsView.setWidthVisible(cbMeasuredWidth.isChecked()); break; } } public void updateTextViews() { tvTop.setText(String.valueOf(myFontMetricsView.getFontMetrics().top)); tvAscent.setText(String.valueOf(myFontMetricsView.getFontMetrics().ascent)); tvBaseline.setText(String.valueOf(0f)); tvDescent.setText(String.valueOf(myFontMetricsView.getFontMetrics().descent)); tvBottom.setText(String.valueOf(myFontMetricsView.getFontMetrics().bottom)); tvBounds.setText("w = " + String.valueOf(myFontMetricsView.getTextBounds().width() + " h = " + String.valueOf(myFontMetricsView.getTextBounds().height()))); tvMeasuredWidth.setText(String.valueOf(myFontMetricsView.getMeasuredTextWidth())); tvLeading.setText(String.valueOf(myFontMetricsView.getFontMetrics().leading)); } private void hideKeyboard(View view) { if (view != null) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } } ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================