Improve variable font weight support

Refactor SVGLengthUnitType, AbsoluteFontWeight
This commit is contained in:
Mikael Sand
2019-07-20 20:08:15 +03:00
parent 08295b17ba
commit bf0adb4a82
9 changed files with 260 additions and 165 deletions
@@ -103,7 +103,7 @@ class Brush {
private double getVal(SVGLength length, double relative, float scale, float textSize) {
return PropHelper.fromRelative(length, relative, 0, mUseObjectBoundingBox &&
length.unit == SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER ? relative : scale, textSize);
length.unit == SVGLength.UnitType.NUMBER ? relative : scale, textSize);
}
void setupPaint(Paint paint, RectF pathBoundingBox, float scale, float opacity) {
@@ -9,69 +9,70 @@ import static com.facebook.react.uimanager.ViewProps.FONT_STYLE;
import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT;
import static com.horcrux.svg.TextProperties.*;
class AbsoluteFontWeight {
static int normal = 400;
private static final FontWeight[] WEIGHTS = new FontWeight[]{
FontWeight.w100,
FontWeight.w100,
FontWeight.w200,
FontWeight.w300,
FontWeight.Normal,
FontWeight.w500,
FontWeight.w600,
FontWeight.Bold,
FontWeight.w800,
FontWeight.w900,
FontWeight.w900,
};
static FontWeight nearestFontWeight(int absoluteFontWeight) {
return WEIGHTS[Math.round(absoluteFontWeight / 100f)];
}
private static final int[] absoluteFontWeights = new int[]{
400, 700, 100, 200, 300, 400, 500, 600, 700, 800, 900
};
// https://drafts.csswg.org/css-fonts-4/#relative-weights
static int from(FontWeight fontWeight, FontData parent) {
if (fontWeight == FontWeight.Bolder) {
return bolder(parent.absoluteFontWeight);
} else if (fontWeight == FontWeight.Lighter) {
return lighter(parent.absoluteFontWeight);
} else {
return absoluteFontWeights[fontWeight.ordinal()];
}
}
private static int bolder(int inherited) {
if (inherited < 350) {
return 400;
} else if (inherited < 550) {
return 700;
} else if (inherited < 900) {
return 900;
} else {
return inherited;
}
}
private static int lighter(int inherited) {
if (inherited < 100) {
return inherited;
} else if (inherited < 550) {
return 100;
} else if (inherited < 750) {
return 400;
} else {
return 700;
}
}
}
class FontData {
static class AbsoluteFontWeight {
static final int normal = 400;
private static final FontWeight[] WEIGHTS = new FontWeight[]{
FontWeight.w100,
FontWeight.w100,
FontWeight.w200,
FontWeight.w300,
FontWeight.Normal,
FontWeight.w500,
FontWeight.w600,
FontWeight.Bold,
FontWeight.w800,
FontWeight.w900,
FontWeight.w900,
};
static FontWeight nearestFontWeight(int absoluteFontWeight) {
return WEIGHTS[Math.round(absoluteFontWeight / 100f)];
}
private static final int[] absoluteFontWeights = new int[]{
400, 700, 100, 200, 300, 400, 500, 600, 700, 800, 900
};
// https://drafts.csswg.org/css-fonts-4/#relative-weights
static int from(FontWeight fontWeight, FontData parent) {
if (fontWeight == FontWeight.Bolder) {
return bolder(parent.absoluteFontWeight);
} else if (fontWeight == FontWeight.Lighter) {
return lighter(parent.absoluteFontWeight);
} else {
return absoluteFontWeights[fontWeight.ordinal()];
}
}
private static int bolder(int inherited) {
if (inherited < 350) {
return 400;
} else if (inherited < 550) {
return 700;
} else if (inherited < 900) {
return 900;
} else {
return inherited;
}
}
private static int lighter(int inherited) {
if (inherited < 100) {
return inherited;
} else if (inherited < 550) {
return 100;
} else if (inherited < 750) {
return 400;
} else {
return 700;
}
}
}
static final double DEFAULT_FONT_SIZE = 12d;
private static final double DEFAULT_KERNING = 0d;
@@ -186,42 +186,42 @@ class PropHelper {
if (length == null) {
return offset;
}
SVGLengthUnitType unitType = length.unit;
SVGLength.UnitType unitType = length.unit;
double value = length.value;
double unit = 1;
switch (unitType) {
case SVG_LENGTHTYPE_NUMBER:
case SVG_LENGTHTYPE_PX:
case NUMBER:
case PX:
break;
case SVG_LENGTHTYPE_PERCENTAGE:
case PERCENTAGE:
return value / 100 * relative + offset;
case SVG_LENGTHTYPE_EMS:
case EMS:
unit = fontSize;
break;
case SVG_LENGTHTYPE_EXS:
case EXS:
unit = fontSize / 2;
break;
case SVG_LENGTHTYPE_CM:
case CM:
unit = 35.43307;
break;
case SVG_LENGTHTYPE_MM:
case MM:
unit = 3.543307;
break;
case SVG_LENGTHTYPE_IN:
case IN:
unit = 90;
break;
case SVG_LENGTHTYPE_PT:
case PT:
unit = 1.25;
break;
case SVG_LENGTHTYPE_PC:
case PC:
unit = 15;
break;
default:
case SVG_LENGTHTYPE_UNKNOWN:
case UNKNOWN:
return value * scale + offset;
}
return value * unit * scale + offset;
@@ -5,41 +5,41 @@ import com.facebook.react.bridge.ReadableArray;
import java.util.ArrayList;
// https://www.w3.org/TR/SVG/types.html#InterfaceSVGLength
enum SVGLengthUnitType {
SVG_LENGTHTYPE_UNKNOWN,
SVG_LENGTHTYPE_NUMBER,
SVG_LENGTHTYPE_PERCENTAGE,
SVG_LENGTHTYPE_EMS,
SVG_LENGTHTYPE_EXS,
SVG_LENGTHTYPE_PX,
SVG_LENGTHTYPE_CM,
SVG_LENGTHTYPE_MM,
SVG_LENGTHTYPE_IN,
SVG_LENGTHTYPE_PT,
SVG_LENGTHTYPE_PC,
}
class SVGLength {
// https://www.w3.org/TR/SVG/types.html#InterfaceSVGLength
public enum UnitType {
UNKNOWN,
NUMBER,
PERCENTAGE,
EMS,
EXS,
PX,
CM,
MM,
IN,
PT,
PC,
}
final double value;
final SVGLengthUnitType unit;
final UnitType unit;
private SVGLength() {
value = 0;
unit = SVGLengthUnitType.SVG_LENGTHTYPE_UNKNOWN;
unit = UnitType.UNKNOWN;
}
SVGLength(double number) {
value = number;
unit = SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER;
unit = UnitType.NUMBER;
}
private SVGLength(String length) {
length = length.trim();
int stringLength = length.length();
int percentIndex = stringLength - 1;
if (stringLength == 0 || length.equals("normal")) {
unit = SVGLengthUnitType.SVG_LENGTHTYPE_UNKNOWN;
unit = UnitType.UNKNOWN;
value = 0;
} else if (length.codePointAt(percentIndex) == '%') {
unit = SVGLengthUnitType.SVG_LENGTHTYPE_PERCENTAGE;
unit = UnitType.PERCENTAGE;
value = Double.valueOf(length.substring(0, percentIndex));
} else {
int twoLetterUnitIndex = stringLength - 2;
@@ -48,43 +48,43 @@ class SVGLength {
int end = twoLetterUnitIndex;
switch (lastTwo) {
case "px":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER;
unit = UnitType.NUMBER;
break;
case "em":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_EMS;
unit = UnitType.EMS;
break;
case "ex":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_EXS;
unit = UnitType.EXS;
break;
case "pt":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_PT;
unit = UnitType.PT;
break;
case "pc":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_PC;
unit = UnitType.PC;
break;
case "mm":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_MM;
unit = UnitType.MM;
break;
case "cm":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_CM;
unit = UnitType.CM;
break;
case "in":
unit = SVGLengthUnitType.SVG_LENGTHTYPE_IN;
unit = UnitType.IN;
break;
default:
unit = SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER;
unit = UnitType.NUMBER;
end = stringLength;
}
value = Double.valueOf(length.substring(0, end));
} else {
unit = SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER;
unit = UnitType.NUMBER;
value = Double.valueOf(length);
}
}
@@ -165,7 +165,7 @@ class TSpanView extends TextView {
}
paint.setLetterSpacing((float)(letterSpacing / (font.fontSize * mScale)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
paint.setFontVariationSettings("'wght' " + font.absoluteFontWeight + ", " + font.fontVariationSettings);
paint.setFontVariationSettings("'wght' " + font.absoluteFontWeight + font.fontVariationSettings);
}
}
@@ -347,7 +347,7 @@ class TSpanView extends TextView {
paint.setFontFeatureSettings(defaultFeatures + disableDiscretionaryLigatures + font.fontFeatureSettings);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
paint.setFontVariationSettings("'wght' " + font.absoluteFontWeight + ", " + font.fontVariationSettings);
paint.setFontVariationSettings("'wght' " + font.absoluteFontWeight + font.fontVariationSettings);
}
}
// OpenType.js font data
@@ -1000,22 +1000,48 @@ class TSpanView extends TextView {
}
Typeface typeface = null;
int weight = font.absoluteFontWeight;
final String fontFamily = font.fontFamily;
try {
String path = FONTS + fontFamily + OTF;
typeface = Typeface.createFromAsset(assetManager, path);
} catch (Exception ignored) {
String otfpath = FONTS + fontFamily + OTF;
String ttfpath = FONTS + fontFamily + TTF;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Typeface.Builder builder = new Typeface.Builder(assetManager, otfpath);
builder.setFontVariationSettings("'wght' " + weight);
builder.setWeight(weight);
builder.setItalic(isItalic);
typeface = builder.build();
if (typeface == null) {
builder = new Typeface.Builder(assetManager, ttfpath);
builder.setFontVariationSettings("'wght' " + weight);
builder.setWeight(weight);
builder.setItalic(isItalic);
typeface = builder.build();
}
} else {
try {
String path = FONTS + fontFamily + TTF;
typeface = Typeface.createFromAsset(assetManager, path);
} catch (Exception ignored2) {
typeface = Typeface.createFromAsset(assetManager, otfpath);
} catch (Exception ignored) {
try {
typeface = ReactFontManager.getInstance().getTypeface(fontFamily, fontStyle, assetManager);
} catch (Exception ignored3) {
typeface = Typeface.createFromAsset(assetManager, ttfpath);
} catch (Exception ignored2) {
}
}
}
if (typeface == null) {
try {
typeface = ReactFontManager.getInstance().getTypeface(fontFamily, fontStyle, weight, assetManager);
} catch (Exception ignored) {
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
typeface = Typeface.create(typeface, weight, isItalic);
} else {
typeface = Typeface.create(typeface, fontStyle);
}
// NB: if the font family is null / unsupported, the default one will be used
paint.setTypeface(typeface);
paint.setTextSize((float) fontSize);
@@ -345,30 +345,30 @@ abstract public class VirtualView extends ReactViewGroup {
}
double relativeOnWidth(SVGLength length) {
SVGLengthUnitType unit = length.unit;
if (unit == SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER){
SVGLength.UnitType unit = length.unit;
if (unit == SVGLength.UnitType.NUMBER){
return length.value * mScale;
} else if (unit == SVGLengthUnitType.SVG_LENGTHTYPE_PERCENTAGE){
} else if (unit == SVGLength.UnitType.PERCENTAGE){
return length.value / 100 * getCanvasWidth();
}
return fromRelativeFast(length);
}
double relativeOnHeight(SVGLength length) {
SVGLengthUnitType unit = length.unit;
if (unit == SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER){
SVGLength.UnitType unit = length.unit;
if (unit == SVGLength.UnitType.NUMBER){
return length.value * mScale;
} else if (unit == SVGLengthUnitType.SVG_LENGTHTYPE_PERCENTAGE){
} else if (unit == SVGLength.UnitType.PERCENTAGE){
return length.value / 100 * getCanvasHeight();
}
return fromRelativeFast(length);
}
double relativeOnOther(SVGLength length) {
SVGLengthUnitType unit = length.unit;
if (unit == SVGLengthUnitType.SVG_LENGTHTYPE_NUMBER){
SVGLength.UnitType unit = length.unit;
if (unit == SVGLength.UnitType.NUMBER){
return length.value * mScale;
} else if (unit == SVGLengthUnitType.SVG_LENGTHTYPE_PERCENTAGE){
} else if (unit == SVGLength.UnitType.PERCENTAGE){
return length.value / 100 * getCanvasDiagonal();
}
return fromRelativeFast(length);
@@ -384,26 +384,26 @@ abstract public class VirtualView extends ReactViewGroup {
private double fromRelativeFast(SVGLength length) {
double unit;
switch (length.unit) {
case SVG_LENGTHTYPE_EMS:
case EMS:
unit = getFontSizeFromContext();
break;
case SVG_LENGTHTYPE_EXS:
case EXS:
unit = getFontSizeFromContext() / 2;
break;
case SVG_LENGTHTYPE_CM:
case CM:
unit = 35.43307;
break;
case SVG_LENGTHTYPE_MM:
case MM:
unit = 3.543307;
break;
case SVG_LENGTHTYPE_IN:
case IN:
unit = 90;
break;
case SVG_LENGTHTYPE_PT:
case PT:
unit = 1.25;
break;
case SVG_LENGTHTYPE_PC:
case PC:
unit = 15;
break;
+99 -7
View File
@@ -134,13 +134,105 @@
}
fontFamily = fontFamilyFound ? fontFamily : nil;
return (__bridge CTFontRef)[RCTFont updateFont:nil
withFamily:fontFamily
size:fontSize
weight:fontWeight
style:fontStyle
variant:nil
scaleMultiplier:1.0];
UIFont *font = [RCTFont updateFont:nil
withFamily:fontFamily
size:fontSize
weight:fontWeight
style:fontStyle
variant:nil
scaleMultiplier:1.0];
CTFontRef ref = (__bridge CTFontRef)font;
int weight = topFont_->absoluteFontWeight;
if (weight == 400) {
return ref;
}
CFArrayRef cgAxes = CTFontCopyVariationAxes(ref);
CFIndex cgAxisCount = CFArrayGetCount(cgAxes);
CFNumberRef wght_id = 0;
for (CFIndex i = 0; i < cgAxisCount; ++i) {
CFTypeRef cgAxis = CFArrayGetValueAtIndex(cgAxes, i);
if (CFGetTypeID(cgAxis) != CFDictionaryGetTypeID()) {
continue;
}
CFDictionaryRef cgAxisDict = (CFDictionaryRef)cgAxis;
CFTypeRef axisName = CFDictionaryGetValue(cgAxisDict, kCTFontVariationAxisNameKey);
CFTypeRef axisId = CFDictionaryGetValue(cgAxisDict, kCTFontVariationAxisIdentifierKey);
if (!axisName || CFGetTypeID(axisName) != CFStringGetTypeID()) {
continue;
}
CFStringRef axisNameString = (CFStringRef)axisName;
NSString *axisNameNSString = (__bridge NSString *)(axisNameString);
if (![@"Weight" isEqualToString:axisNameNSString]) {
continue;
}
if (!axisId || CFGetTypeID(axisId) != CFNumberGetTypeID()) {
continue;
}
wght_id = (CFNumberRef)axisId;
break;
/*
int axisIdInt;
if (!CFNumberGetValue(wght_id, kCFNumberIntType, &axisIdInt))
{
continue;
}
CFTypeRef axisDefaultValue = CFDictionaryGetValue(cgAxisDict,
kCTFontVariationAxisDefaultValueKey);
CFTypeRef axisMinValue = CFDictionaryGetValue(cgAxisDict,
kCTFontVariationAxisMinimumValueKey);
CFTypeRef axisMaxValue = CFDictionaryGetValue(cgAxisDict,
kCTFontVariationAxisMaximumValueKey);
if (!axisDefaultValue || CFGetTypeID(axisDefaultValue) != CFNumberGetTypeID()) {
break;
}
CFNumberRef axisDefaultValueNumber = (CFNumberRef)axisDefaultValue;
double axisDefaultValueDouble;
if (!CFNumberGetValue(axisDefaultValueNumber, kCFNumberDoubleType, &axisDefaultValueDouble))
{
break;
}
if (!axisMinValue || CFGetTypeID(axisMinValue) != CFNumberGetTypeID()) {
break;
}
CFNumberRef axisMinValueNumber = (CFNumberRef)axisMinValue;
double axisMinValueDouble;
if (!CFNumberGetValue(axisMinValueNumber, kCFNumberDoubleType, &axisMinValueDouble))
{
break;
}
if (!axisMaxValue || CFGetTypeID(axisMaxValue) != CFNumberGetTypeID()) {
break;
}
CFNumberRef axisMaxValueNumber = (CFNumberRef)axisMaxValue;
double axisMaxValueDouble;
if (!CFNumberGetValue(axisMaxValueNumber, kCFNumberDoubleType, &axisMaxValueDouble))
{
break;
}
RCTLog(@"name: %@ min: %f max: %f def: %f id: %i", axisNameNSString, axisMinValueDouble, axisMaxValueDouble, axisDefaultValueDouble, axisIdInt);
*/
}
if (wght_id == 0) {
return ref;
}
UIFontDescriptor *uifd = font.fontDescriptor;
CTFontDescriptorRef ctfd = (__bridge CTFontDescriptorRef)(uifd);
CTFontDescriptorRef newfd = CTFontDescriptorCreateCopyWithVariation(ctfd, wght_id, (CGFloat)weight);
CTFontRef newfont = CTFontCreateCopyWithAttributes(ref, (CGFloat)[fontSize doubleValue], nil, newfd);
return newfont;
}
- (void)pushIndices
-24
View File
@@ -157,18 +157,6 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI;
{
[attrs setObject:kernAttr forKey:(id)kCTKernAttributeName];
}
int weight = font->absoluteFontWeight;
if (weight != 400) {
// https://github.com/WebKit/webkit/blob/73b06fb2cc31aaff91119718d9abdc7be703d41b/Source/WebCore/platform/graphics/cocoa/FontCacheCoreText.cpp#L429-L444
float denormalizedWeight = (weight + 109.3) / 523.7;
CFMutableDictionaryRef variationDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
long long bitwiseTag = 'w' << 24 | 'g' << 16 | 'h' << 8 | 't';
CFNumberRef tagNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &bitwiseTag);
CFNumberRef valueNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &denormalizedWeight);
CFDictionarySetValue(variationDictionary, tagNumber, valueNumber);
CFDictionaryAddValue(attributes, kCTFontVariationAttribute, variationDictionary);
}
CFStringRef string = (__bridge CFStringRef)str;
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
@@ -341,18 +329,6 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI;
[attrs setObject:noAutoKern forKey:(id)kCTKernAttributeName];
}
}
int weight = font->absoluteFontWeight;
if (weight != 400) {
// https://github.com/WebKit/webkit/blob/73b06fb2cc31aaff91119718d9abdc7be703d41b/Source/WebCore/platform/graphics/cocoa/FontCacheCoreText.cpp#L429-L444
float denormalizedWeight = (weight + 109.3) / 523.7;
CFMutableDictionaryRef variationDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
long long bitwiseTag = 'w' << 24 | 'g' << 16 | 'h' << 8 | 't';
CFNumberRef tagNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &bitwiseTag);
CFNumberRef valueNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &denormalizedWeight);
CFDictionarySetValue(variationDictionary, tagNumber, valueNumber);
CFDictionaryAddValue(attributes, kCTFontVariationAttribute, variationDictionary);
}
CFStringRef string = (__bridge CFStringRef)str;
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
+2 -2
View File
@@ -68,7 +68,7 @@ RCT_CUSTOM_VIEW_PROPERTY(baselineShift, id, RNSVGText)
RCT_EXPORT_VIEW_PROPERTY(lengthAdjust, NSString)
RCT_EXPORT_VIEW_PROPERTY(alignmentBaseline, NSString)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, id, RNSVGGroup)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, id, RNSVGText)
{
if ([json isKindOfClass:[NSString class]]) {
NSString *stringValue = (NSString *)json;
@@ -80,7 +80,7 @@ RCT_CUSTOM_VIEW_PROPERTY(fontSize, id, RNSVGGroup)
}
}
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, id, RNSVGGroup)
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, id, RNSVGText)
{
if ([json isKindOfClass:[NSString class]]) {
NSString *stringValue = (NSString *)json;