net.codecrete.qrbill.canvas.FontMetrics Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of qrbill-generator Show documentation
Show all versions of qrbill-generator Show documentation
Java library for generating Swiss QR bills
//
// Swiss QR Bill Generator
// Copyright (c) 2017 Manuel Bleichenbacher
// Licensed under MIT License
// https://opensource.org/licenses/MIT
//
package net.codecrete.qrbill.canvas;
import java.util.ArrayList;
import java.util.Locale;
/**
* Simple font metrics class, independent of graphics subsystems and
* installed fonts.
*
*
* It supports Helvetica, Arial, Frutiger, Liberation Sans. Kerning and ligatures are not supported.
*
*/
public class FontMetrics {
private static final double PT_TO_MM = 25.4 / 72;
private final String fontFamilyList;
private final String firstFontFamily;
private final char[] charWidthx20x7F;
private final char[] charWidthxA0xFF;
private final char charDefaultWidth;
private final FontMetrics boldMetrics;
public FontMetrics(String fontFamilyList) {
this.fontFamilyList = fontFamilyList;
firstFontFamily = getFirstFontFamily(fontFamilyList);
String family = firstFontFamily.toLowerCase(Locale.US);
final char[] boldCharWidthx20x7F;
final char[] boldCharWidthxA0xFF;
final char boldCharDefaultWidth;
if (family.contains("arial")) {
charWidthx20x7F = CharWidthData.ARIAL_NORMAL_20_7F;
charWidthxA0xFF = CharWidthData.ARIAL_NORMAL_A0_FF;
charDefaultWidth = CharWidthData.ARIAL_NORMAL_DEFAULT_WIDTH;
boldCharWidthx20x7F = CharWidthData.ARIAL_BOLD_20_7F;
boldCharWidthxA0xFF = CharWidthData.ARIAL_BOLD_A0_FF;
boldCharDefaultWidth = CharWidthData.ARIAL_BOLD_DEFAULT_WIDTH;
} else if (family.contains("liberation") && family.contains("sans")) {
charWidthx20x7F = CharWidthData.LIBERATION_SANS_NORMAL_20_7F;
charWidthxA0xFF = CharWidthData.LIBERATION_SANS_NORMAL_A0_FF;
charDefaultWidth = CharWidthData.LIBERATION_SANS_NORMAL_DEFAULT_WIDTH;
boldCharWidthx20x7F = CharWidthData.LIBERATION_SANS_BOLD_20_7F;
boldCharWidthxA0xFF = CharWidthData.LIBERATION_SANS_BOLD_A0_FF;
boldCharDefaultWidth = CharWidthData.LIBERATION_SANS_BOLD_DEFAULT_WIDTH;
} else if (family.contains("frutiger")) {
charWidthx20x7F = CharWidthData.FRUTIGER_NORMAL_20_7F;
charWidthxA0xFF = CharWidthData.FRUTIGER_NORMAL_A0_FF;
charDefaultWidth = CharWidthData.FRUTIGER_NORMAL_DEFAULT_WIDTH;
boldCharWidthx20x7F = CharWidthData.FRUTIGER_BOLD_20_7F;
boldCharWidthxA0xFF = CharWidthData.FRUTIGER_BOLD_A0_FF;
boldCharDefaultWidth = CharWidthData.FRUTIGER_BOLD_DEFAULT_WIDTH;
} else {
charWidthx20x7F = CharWidthData.HELVETICA_NORMAL_20_7F;
charWidthxA0xFF = CharWidthData.HELVETICA_NORMAL_A0_FF;
charDefaultWidth = CharWidthData.HELVETICA_NORMAL_DEFAULT_WIDTH;
boldCharWidthx20x7F = CharWidthData.HELVETICA_BOLD_20_7F;
boldCharWidthxA0xFF = CharWidthData.HELVETICA_BOLD_A0_FF;
boldCharDefaultWidth = CharWidthData.HELVETICA_BOLD_DEFAULT_WIDTH;
}
boldMetrics = new FontMetrics(boldCharWidthx20x7F, boldCharWidthxA0xFF, boldCharDefaultWidth);
}
private FontMetrics(char[] charWidthx20x7F, char[] charWidthxA0xFF, char charDefaultWidth) {
fontFamilyList = null;
firstFontFamily = null;
this.charWidthx20x7F = charWidthx20x7F;
this.charWidthxA0xFF = charWidthxA0xFF;
this.charDefaultWidth = charDefaultWidth;
this.boldMetrics = null;
}
/**
* Gets the font family list.
*
* @return font family list (comma separated)
*/
public String getFontFamilyList() {
return fontFamilyList;
}
/**
* Gets the first font family (from the font family list).
*
* @return first font family
*/
public String getFirstFontFamily() {
return firstFontFamily;
}
/**
* Distance between baseline and top of highest letter.
*
* @param fontSize the font size (in pt)
* @return the distance (in mm)
*/
public double getAscender(int fontSize) {
return fontSize * 0.8 * PT_TO_MM;
}
/**
* Distance between baseline and bottom of letter extending the farthest below the
* baseline.
*
* @param fontSize the font size (in pt)
* @return the distance (in mm)
*/
public double getDescender(int fontSize) {
return fontSize * 0.2 * PT_TO_MM;
}
/**
* Distance between the baselines of two consecutive text lines.
*
* @param fontSize the font size (in pt)
* @return the distance (in mm)
*/
public double getLineHeight(int fontSize) {
return fontSize * PT_TO_MM;
}
/**
* Splits the text into lines.
*
* If a line would exceed the specified maximum length, line breaks are
* inserted. Newlines are treated as fixed line breaks.
*
*
* @param text the text
* @param maxLength the maximum line length (in pt)
* @param fontSize the font size (in pt)
* @return an array of text lines
*/
public String[] splitLines(String text, double maxLength, int fontSize) {
/* Yes, this code has a cognitive complexity of 37. Deal with it. */
ArrayList lines = new ArrayList<>();
int max = (int) (maxLength * 1000 / fontSize);
int len = text.length(); // length of line
int pos = 0; // current position (0 ..< end)
int lineStartPos = 0; // start position of current line
int lineWidth = 0; // current line width (in AFM metric)
boolean addEmptyLine = true; // flag if an empty line should be added as the last line
// iterate over all characters
while (pos < len) {
// get current character
char ch = text.charAt(pos);
// skip leading white space at start of current line
if (ch == ' ' && pos == lineStartPos) {
lineStartPos++;
pos++;
continue;
}
// add width of character
lineWidth += getCharWidth(ch);
addEmptyLine = false;
// line break is need if the maximum width has been reached
// or if an explicit line break has been encountered
if (ch == '\n' || lineWidth > max) {
// find the position for the line break
int breakPos;
if (ch == '\n') {
breakPos = pos;
} else {
// locate the previous space on the line
int spacePos = pos - 1;
while (spacePos > lineStartPos) {
if (text.charAt(spacePos) == ' ')
break;
spacePos--;
}
// if space was found, it's the break position
if (spacePos > lineStartPos) {
breakPos = spacePos;
} else {
// if no space was found, forcibly break word
if (pos > lineStartPos)
breakPos = pos;
else
breakPos = lineStartPos + 1; // at least one character
}
}
// add line to result
addResultLine(lines, text, lineStartPos, breakPos);
// setup start of new line
lineStartPos = breakPos;
if (ch == '\n') {
lineStartPos = breakPos + 1;
addEmptyLine = true;
}
pos = lineStartPos;
lineWidth = 0;
} else {
// no line break needed; progress one character
pos++;
}
}
// complete the last line
if (pos > lineStartPos) {
addResultLine(lines, text, lineStartPos, pos);
} else if (addEmptyLine) {
lines.add("");
}
return lines.toArray(new String[0]);
}
/**
* Add the specified text range to the resulting lines.
*
* Trim trailing white space
*
*
* @param lines resulting lines array
* @param text text
* @param start start of text range (including)
* @param end end of text range (excluding)
*/
private static void addResultLine(ArrayList lines, String text, int start, int end) {
while (end > start && text.charAt(end - 1) == ' ')
end--;
lines.add(text.substring(start, end));
}
/**
* Returns the width of the specified text for the specified font size
*
* @param text text
* @param fontSize font size (in pt)
* @param isBold indicates if the text is in bold or regular weight
* @return width (in mm)
*/
public double getTextWidth(CharSequence text, int fontSize, boolean isBold) {
if (isBold)
return boldMetrics.getTextWidth(text, fontSize, false);
double width = 0;
int len = text.length();
for (int i = 0; i < len; i++)
width += getCharWidth(text.charAt(i));
return width * fontSize / 1000 * PT_TO_MM;
}
/**
* Returns the width of the specified character.
*
* The width is given in 0.0001 pt for a font size of 1 pt. So to get the
* effective width in pt (1/72 in), it must be multiplied with the font size and
* divided by 1000.
*
*
* The method only supports characters as defined in "Swiss Implementation
* Guidelines for Credit Transfer Initiation". For all other characters, a
* default width is returned.
*
*
* @param ch the character
* @return the width of the character
*/
private double getCharWidth(char ch) {
char width = 0;
if (ch >= 0x20 && ch <= 0x7f)
width = charWidthx20x7F[ch - 0x20];
else if (ch >= 0xa0 && ch <= 0xff) {
width = charWidthxA0xFF[ch - 0xa0];
}
if (width == 0)
width = charDefaultWidth;
return width;
}
private static String getFirstFontFamily(String fontFamilyList) {
int index = fontFamilyList.indexOf(',');
if (index < 0)
return fontFamilyList;
String fontFamily = fontFamilyList.substring(0, index).trim();
if (fontFamily.startsWith("\""))
fontFamily = fontFamily.substring(1);
if (fontFamily.endsWith(("\"")))
fontFamily = fontFamily.substring(0, fontFamily.length() - 1);
return fontFamily;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy