Implement default and required css 3 fonts features, fontFeatureSettings, sub-/super- script baseline-shift.

Activate OpenType localized forms and features required for proper display of composed characters and marks.
This commit is contained in:
Mikael Sand
2017-08-07 18:26:45 +03:00
parent f603e7191c
commit a1b57d3f4f
5 changed files with 181 additions and 35 deletions
@@ -20,6 +20,7 @@ class FontData {
private static final String WORD_SPACING = "wordSpacing"; private static final String WORD_SPACING = "wordSpacing";
private static final String LETTER_SPACING = "letterSpacing"; private static final String LETTER_SPACING = "letterSpacing";
private static final String TEXT_DECORATION = "textDecoration"; private static final String TEXT_DECORATION = "textDecoration";
private static final String FONT_FEATURE_SETTINGS = "fontFeatureSettings";
private static final String FONT_VARIANT_LIGATURES = "fontVariantLigatures"; private static final String FONT_VARIANT_LIGATURES = "fontVariantLigatures";
final double fontSize; final double fontSize;
@@ -27,6 +28,7 @@ class FontData {
final FontStyle fontStyle; final FontStyle fontStyle;
final ReadableMap fontData; final ReadableMap fontData;
final FontWeight fontWeight; final FontWeight fontWeight;
final String fontFeatureSettings;
final FontVariantLigatures fontVariantLigatures; final FontVariantLigatures fontVariantLigatures;
final TextAnchor textAnchor; final TextAnchor textAnchor;
@@ -45,6 +47,7 @@ class FontData {
fontFamily = ""; fontFamily = "";
fontStyle = FontStyle.normal; fontStyle = FontStyle.normal;
fontWeight = FontWeight.Normal; fontWeight = FontWeight.Normal;
fontFeatureSettings = "";
fontVariantLigatures = FontVariantLigatures.normal; fontVariantLigatures = FontVariantLigatures.normal;
textAnchor = TextAnchor.start; textAnchor = TextAnchor.start;
@@ -88,6 +91,7 @@ class FontData {
fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily; fontFamily = font.hasKey(FONT_FAMILY) ? font.getString(FONT_FAMILY) : parent.fontFamily;
fontStyle = font.hasKey(FONT_STYLE) ? FontStyle.valueOf(font.getString(FONT_STYLE)) : parent.fontStyle; fontStyle = font.hasKey(FONT_STYLE) ? FontStyle.valueOf(font.getString(FONT_STYLE)) : parent.fontStyle;
fontWeight = font.hasKey(FONT_WEIGHT) ? FontWeight.getEnum(font.getString(FONT_WEIGHT)) : parent.fontWeight; fontWeight = font.hasKey(FONT_WEIGHT) ? FontWeight.getEnum(font.getString(FONT_WEIGHT)) : parent.fontWeight;
fontFeatureSettings = font.hasKey(FONT_FEATURE_SETTINGS) ? font.getString(FONT_FEATURE_SETTINGS) : parent.fontFeatureSettings;
fontVariantLigatures = font.hasKey(FONT_VARIANT_LIGATURES) ? FontVariantLigatures.valueOf(font.getString(FONT_VARIANT_LIGATURES)) : parent.fontVariantLigatures; fontVariantLigatures = font.hasKey(FONT_VARIANT_LIGATURES) ? FontVariantLigatures.valueOf(font.getString(FONT_VARIANT_LIGATURES)) : parent.fontVariantLigatures;
textAnchor = font.hasKey(TEXT_ANCHOR) ? TextAnchor.valueOf(font.getString(TEXT_ANCHOR)) : parent.textAnchor; textAnchor = font.hasKey(TEXT_ANCHOR) ? TextAnchor.valueOf(font.getString(TEXT_ANCHOR)) : parent.textAnchor;
@@ -223,12 +223,41 @@ class TSpanShadowNode extends TextShadowNode {
Media: visual Media: visual
Computed value: as specified Computed value: as specified
Animatable: no Animatable: no
https://drafts.csswg.org/css-fonts-3/#default-features
7.1. Default features
For OpenType fonts, user agents must enable the default features defined in the OpenType
documentation for a given script and writing mode.
Required ligatures, common ligatures and contextual forms must be enabled by default
(OpenType features: rlig, liga, clig, calt),
along with localized forms (OpenType feature: locl),
and features required for proper display of composed characters and marks
(OpenType features: ccmp, mark, mkmk).
These features must always be enabled, even when the value of the font-variant and
font-feature-settings properties is normal.
Individual features are only disabled when explicitly overridden by the author,
as when font-variant-ligatures is set to no-common-ligatures.
TODO For handling complex scripts such as Arabic, Mongolian or Devanagari additional features
are required.
TODO For upright text within vertical text runs,
vertical alternates (OpenType feature: vert) must be enabled.
*/ */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String required = "'rlig', 'liga', 'clig', 'calt', 'locl', 'ccmp', 'mark', 'mkmk',";
String defaultFeatures = required + "'kern', ";
if (allowOptionalLigatures) { if (allowOptionalLigatures) {
paint.setFontFeatureSettings("'kern', 'liga', 'clig', 'dlig', 'hlig', 'cala', 'rlig'"); String additionalLigatures = "'hlig', 'cala', ";
paint.setFontFeatureSettings(defaultFeatures + additionalLigatures + font.fontFeatureSettings);
} else { } else {
paint.setFontFeatureSettings("'kern', 'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, 'rlig'"); String disableDiscretionaryLigatures = "'liga' 0, 'clig' 0, 'dlig' 0, 'hlig' 0, 'cala' 0, ";
paint.setFontFeatureSettings(defaultFeatures + disableDiscretionaryLigatures + font.fontFeatureSettings);
} }
} }
// OpenType.js font data // OpenType.js font data
@@ -601,10 +630,32 @@ class TSpanShadowNode extends TextShadowNode {
switch (baselineShiftString) { switch (baselineShiftString) {
case "sub": case "sub":
// TODO // TODO
if (fontData != null && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) {
int unitsPerEm = fontData.getInt("unitsPerEm");
ReadableMap tables = fontData.getMap("tables");
if (tables.hasKey("os2")) {
ReadableMap os2 = tables.getMap("os2");
if (os2.hasKey("ySubscriptYOffset")) {
double subOffset = os2.getDouble("ySubscriptYOffset");
baselineShift += fontSize * subOffset / unitsPerEm;
}
}
}
break; break;
case "super": case "super":
// TODO // TODO
if (fontData != null && fontData.hasKey("tables") && fontData.hasKey("unitsPerEm")) {
int unitsPerEm = fontData.getInt("unitsPerEm");
ReadableMap tables = fontData.getMap("tables");
if (tables.hasKey("os2")) {
ReadableMap os2 = tables.getMap("os2");
if (os2.hasKey("ySuperscriptYOffset")) {
double superOffset = os2.getDouble("ySuperscriptYOffset");
baselineShift -= fontSize * superOffset / unitsPerEm;
}
}
}
break; break;
case "baseline": case "baseline":
@@ -625,9 +676,6 @@ class TSpanShadowNode extends TextShadowNode {
final float[] startPointMatrixData = new float[9]; final float[] startPointMatrixData = new float[9];
final float[] endPointMatrixData = new float[9]; final float[] endPointMatrixData = new float[9];
String previous = "";
double previousCharWidth = 0;
for (int index = 0; index < length; index++) { for (int index = 0; index < length; index++) {
char currentChar = chars[index]; char currentChar = chars[index];
String current = String.valueOf(currentChar); String current = String.valueOf(currentChar);
@@ -638,23 +686,21 @@ class TSpanShadowNode extends TextShadowNode {
advances horizontally when the glyph is drawn using horizontal text layout). advances horizontally when the glyph is drawn using horizontal text layout).
*/ */
boolean hasLigature = false; boolean hasLigature = false;
if (allowOptionalLigatures) { if (alreadyRenderedGraphemeCluster) {
if (alreadyRenderedGraphemeCluster) { current = "";
current = ""; } else {
} else { int nextIndex = index;
int nextIndex = index; while (++nextIndex < length) {
while (++nextIndex < length) { float nextWidth = advances[nextIndex];
float nextWidth = advances[nextIndex]; if (nextWidth > 0) {
if (nextWidth > 0) { break;
break; }
} String nextLigature = current + String.valueOf(chars[nextIndex]);
String nextLigature = current + String.valueOf(chars[nextIndex]); boolean hasNextLigature = PaintCompat.hasGlyph(paint, nextLigature);
boolean hasNextLigature = PaintCompat.hasGlyph(paint, nextLigature); if (hasNextLigature) {
if (hasNextLigature) { ligature[nextIndex] = true;
ligature[nextIndex] = true; current = nextLigature;
current = nextLigature; hasLigature = true;
hasLigature = true;
}
} }
} }
} }
@@ -670,16 +716,8 @@ class TSpanShadowNode extends TextShadowNode {
using the user agent's distance along the path algorithm. using the user agent's distance along the path algorithm.
*/ */
if (autoKerning) { if (autoKerning) {
if (allowOptionalLigatures) { double kerned = advances[index] * scaleSpacingAndGlyphs;
double kerned = advances[index] * scaleSpacingAndGlyphs; kerning = kerned - charWidth;
kerning = kerned - charWidth;
} else {
double bothCharsWidth = paint.measureText(previous + current) * scaleSpacingAndGlyphs;
double kerned = bothCharsWidth - previousCharWidth;
kerning = kerned - charWidth;
previousCharWidth = charWidth;
previous = current;
}
} }
boolean isWordSeparator = currentChar == ' '; boolean isWordSeparator = currentChar == ' ';
@@ -699,8 +737,8 @@ class TSpanShadowNode extends TextShadowNode {
continue; continue;
} }
advance = advance * side; advance *= side;
charWidth = charWidth * side; charWidth *= side;
double cursor = offset + (x + dx) * side; double cursor = offset + (x + dx) * side;
double startPoint = cursor - advance; double startPoint = cursor - advance;
+2 -1
View File
@@ -32,7 +32,8 @@ function fontDiffer(a, b) {
a.wordSpacing !== b.wordSpacing || a.wordSpacing !== b.wordSpacing ||
a.kerning !== b.kerning || a.kerning !== b.kerning ||
a.fontVariantLigatures !== b.fontVariantLigatures || a.fontVariantLigatures !== b.fontVariantLigatures ||
a.fontData !== b.fontData a.fontData !== b.fontData ||
a.fontFeatureSettings !== b.fontFeatureSettings
); );
} }
+2
View File
@@ -56,6 +56,7 @@ export function extractFont(props) {
wordSpacing, wordSpacing,
kerning, kerning,
fontVariantLigatures, fontVariantLigatures,
fontFeatureSettings,
} = props; } = props;
let { let {
fontSize, fontSize,
@@ -80,6 +81,7 @@ export function extractFont(props) {
wordSpacing, wordSpacing,
kerning, kerning,
fontVariantLigatures, fontVariantLigatures,
fontFeatureSettings,
}, prop => !_.isNil(prop)); }, prop => !_.isNil(prop));
if (typeof font === 'string') { if (typeof font === 'string') {
+101
View File
@@ -260,6 +260,106 @@ const alignmentBaseline = PropTypes.oneOf(['baseline', 'text-bottom', 'alphabeti
*/ */
const baselineShift = PropTypes.oneOfType([PropTypes.oneOf(['sub', 'super', 'baseline']), PropTypes.arrayOf(numberProp), PropTypes.string]); const baselineShift = PropTypes.oneOfType([PropTypes.oneOf(['sub', 'super', 'baseline']), PropTypes.arrayOf(numberProp), PropTypes.string]);
/*
6.12. Low-level font feature settings control: the font-feature-settings property
Name: font-feature-settings
Value: normal | <feature-tag-value> #
Initial: normal
Applies to: all elements
Inherited: yes
Percentages: N/A
Media: visual
Computed value: as specified
Animatable: no
This property provides low-level control over OpenType font features.
It is intended as a way of providing access to font features
that are not widely used but are needed for a particular use case.
Authors should generally use font-variant and its related subproperties
whenever possible and only use this property for special cases where its use
is the only way of accessing a particular infrequently used font feature.
enable small caps and use second swash alternate
font-feature-settings: "smcp", "swsh" 2;
A value of normal means that no change in glyph selection or positioning occurs due to this property.
Feature tag values have the following syntax:
<feature-tag-value> = <string> [ <integer> | on | off ]?
The <string> is a case-sensitive OpenType feature tag. As specified in the OpenType specification,
feature tags contain four ASCII characters.
Tag strings longer or shorter than four characters,
or containing characters outside the U+207E codepoint range are invalid.
Feature tags need only match a feature tag defined in the font,
so they are not limited to explicitly registered OpenType features.
Fonts defining custom feature tags should follow the tag name rules
defined in the OpenType specification [OPENTYPE-FEATURES].
Feature tags not present in the font are ignored;
a user agent must not attempt to synthesize fallback behavior based on these feature tags.
The one exception is that user agents may synthetically support the kern feature with fonts
that contain kerning data in the form of a kern table but lack kern feature support in the GPOS table.
In general, authors should use the font-kerning property to explicitly enable or disable kerning
since this property always affects fonts with either type of kerning data.
If present, a value indicates an index used for glyph selection.
An <integer> value must be 0 or greater.
A value of 0 indicates that the feature is disabled.
For boolean features, a value of 1 enables the feature.
For non-boolean features, a value of 1 or greater enables the feature and indicates the feature selection index.
A value of on is synonymous with 1 and off is synonymous with 0.
If the value is omitted, a value of 1 is assumed.
font-feature-settings: "dlig" 1; /* dlig=1 enable discretionary ligatures * /
font-feature-settings: "smcp" on; /* smcp=1 enable small caps * /
font-feature-settings: 'c2sc'; /* c2sc=1 enable caps to small caps * /
font-feature-settings: "liga" off; /* liga=0 no common ligatures * /
font-feature-settings: "tnum", 'hist'; /* tnum=1, hist=1 enable tabular numbers and historical forms * /
font-feature-settings: "tnum" "hist"; /* invalid, need a comma-delimited list * /
font-feature-settings: "silly" off; /* invalid, tag too long * /
font-feature-settings: "PKRN"; /* PKRN=1 enable custom feature * /
font-feature-settings: dlig; /* invalid, tag must be a string * /
When values greater than the range supported by the font are specified, the behavior is explicitly undefined.
For boolean features, in general these will enable the feature.
For non-boolean features, out of range values will in general be equivalent to a 0 value.
However, in both cases the exact behavior will depend upon the way the font is designed
(specifically, which type of lookup is used to define the feature).
Although specifically defined for OpenType feature tags,
feature tags for other modern font formats that support font features may be added in the future.
Where possible, features defined for other font formats
should attempt to follow the pattern of registered OpenType tags.
The Japanese text below will be rendered with half-width kana characters:
body { font-feature-settings: "hwid"; /* Half-width OpenType feature * / }
<p>毎日カレー食べてるのに飽きない</p>
https://drafts.csswg.org/css-fonts-3/#propdef-font-feature-settings
https://developer.mozilla.org/en/docs/Web/CSS/font-feature-settings
*/
const fontFeatureSettings = PropTypes.string;
const textSpecificProps = { const textSpecificProps = {
...pathProps, ...pathProps,
...fontProps, ...fontProps,
@@ -269,6 +369,7 @@ const textSpecificProps = {
lengthAdjust, lengthAdjust,
textLength, textLength,
fontData: PropTypes.object, fontData: PropTypes.object,
fontFeatureSettings,
}; };
// https://svgwg.org/svg2-draft/text.html#TSpanAttributes // https://svgwg.org/svg2-draft/text.html#TSpanAttributes