Simplify textAnchor and textDecoration handling

This commit is contained in:
Mikael Sand
2017-07-23 20:11:01 +03:00
parent 2b3f251b8b
commit fc61c9dad2
7 changed files with 124 additions and 164 deletions

View File

@@ -18,18 +18,20 @@ import java.util.ArrayList;
import javax.annotation.Nullable;
import static com.horcrux.svg.TextShadowNode.KERNING;
import static com.horcrux.svg.TextShadowNode.TEXT_ANCHOR;
import static com.horcrux.svg.TextShadowNode.WORD_SPACING;
import static com.horcrux.svg.TextShadowNode.LETTER_SPACING;
import static com.horcrux.svg.TextShadowNode.TEXT_DECORATION;
import static com.facebook.react.uimanager.ViewProps.FONT_SIZE;
import static com.facebook.react.uimanager.ViewProps.FONT_STYLE;
import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY;
import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT;
// https://www.w3.org/TR/SVG/text.html#TSpanElement
class GlyphContext {
static final double DEFAULT_FONT_SIZE = 12d;
private static final String KERNING = "kerning";
private static final String FONT_SIZE = "fontSize";
private static final String FONT_STYLE = "fontStyle";
private static final String FONT_WEIGHT = "fontWeight";
private static final String FONT_FAMILY = "fontFamily";
private static final String WORD_SPACING = "wordSpacing";
private static final String LETTER_SPACING = "letterSpacing";
// Empty font context map
private static final WritableMap DEFAULT_MAP = Arguments.createMap();
@@ -236,20 +238,18 @@ class GlyphContext {
map.putDouble(FONT_SIZE, mFontSize);
put(FONT_STYLE, map, font, parent);
put(FONT_FAMILY, map, font, parent);
put(FONT_WEIGHT, map, font, parent);
put(FONT_STYLE, map, font, parent);
put(TEXT_ANCHOR, map, font, parent);
put(TEXT_DECORATION, map, font, parent);
// https://www.w3.org/TR/SVG11/text.html#SpacingProperties
// https://drafts.csswg.org/css-text-3/#spacing
// calculated values for units in: kerning, word-spacing, and, letter-spacing
// calculated values for units in: kerning, word-spacing, and, letter-spacing.
putD(KERNING, map, font, parent);
putD(WORD_SPACING, map, font, parent);
putD(LETTER_SPACING, map, font, parent);
}
@@ -428,7 +428,7 @@ class GlyphContext {
return mFontSize;
}
double nextX(double glyphWidth) {
double nextX(double advance) {
incrementIndices(mXIndices, mXsIndex);
int nextIndex = mXIndex + 1;
@@ -439,7 +439,7 @@ class GlyphContext {
mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize);
}
mX += glyphWidth;
mX += advance;
return mX;
}

View File

@@ -28,6 +28,11 @@ import javax.annotation.Nullable;
import static android.graphics.PathMeasure.POSITION_MATRIX_FLAG;
import static android.graphics.PathMeasure.TANGENT_MATRIX_FLAG;
import static com.facebook.react.uimanager.ViewProps.FONT_SIZE;
import static com.facebook.react.uimanager.ViewProps.FONT_STYLE;
import static com.facebook.react.uimanager.ViewProps.FONT_WEIGHT;
import static com.facebook.react.uimanager.ViewProps.FONT_FAMILY;
/**
* Shadow node for virtual TSpan view
*/
@@ -44,14 +49,6 @@ class TSpanShadowNode extends TextShadowNode {
private static final double DEFAULT_WORD_SPACING = 0d;
private static final double DEFAULT_LETTER_SPACING = 0d;
private static final String PROP_KERNING = "kerning";
private static final String PROP_FONT_SIZE = "fontSize";
private static final String PROP_FONT_STYLE = "fontStyle";
private static final String PROP_FONT_WEIGHT = "fontWeight";
private static final String PROP_FONT_FAMILY = "fontFamily";
private static final String PROP_WORD_SPACING = "wordSpacing";
private static final String PROP_LETTER_SPACING = "letterSpacing";
private Path mCache;
private @Nullable String mContent;
private TextPathShadowNode textPath;
@@ -98,10 +95,10 @@ class TSpanShadowNode extends TextShadowNode {
return mCache;
}
private double getTextAnchorShift(double width) {
private double getTextAnchorShift(double width, String textAnchor) {
double x = 0;
switch (getComputedTextAnchor()) {
switch (textAnchor) {
case TEXT_ANCHOR_MIDDLE:
x = -width / 2;
break;
@@ -144,7 +141,9 @@ class TSpanShadowNode extends TextShadowNode {
}
}
offset += getTextAnchorShift(textMeasure);
if (font.hasKey(TEXT_ANCHOR)) {
offset += getTextAnchorShift(textMeasure, font.getString(TEXT_ANCHOR));
}
double x;
double y;
@@ -180,16 +179,16 @@ class TSpanShadowNode extends TextShadowNode {
*
* */
final boolean hasKerning = font.hasKey(PROP_KERNING);
double kerning = hasKerning ? font.getDouble(PROP_KERNING) : DEFAULT_KERNING;
final boolean hasKerning = font.hasKey(KERNING);
double kerning = hasKerning ? font.getDouble(KERNING) : DEFAULT_KERNING;
final boolean autoKerning = !hasKerning;
final double wordSpacing = font.hasKey(PROP_WORD_SPACING) ?
font.getDouble(PROP_WORD_SPACING)
final double wordSpacing = font.hasKey(WORD_SPACING) ?
font.getDouble(WORD_SPACING)
: DEFAULT_WORD_SPACING;
final double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ?
font.getDouble(PROP_LETTER_SPACING)
final double letterSpacing = font.hasKey(LETTER_SPACING) ?
font.getDouble(LETTER_SPACING)
: DEFAULT_LETTER_SPACING;
for (int index = 0; index < length; index++) {
@@ -251,19 +250,29 @@ class TSpanShadowNode extends TextShadowNode {
private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) {
AssetManager assetManager = getThemedContext().getResources().getAssets();
double fontSize = font.getDouble(PROP_FONT_SIZE) * mScale;
double fontSize = font.getDouble(FONT_SIZE) * mScale;
boolean isBold = font.hasKey(PROP_FONT_WEIGHT) &&
BOLD.equals(font.getString(PROP_FONT_WEIGHT));
boolean isBold = font.hasKey(FONT_WEIGHT) &&
BOLD.equals(font.getString(FONT_WEIGHT));
boolean isItalic = font.hasKey(PROP_FONT_STYLE) &&
ITALIC.equals(font.getString(PROP_FONT_STYLE));
boolean isItalic = font.hasKey(FONT_STYLE) &&
ITALIC.equals(font.getString(FONT_STYLE));
int decoration = getTextDecoration();
boolean underlineText = false;
boolean strikeThruText = false;
boolean underlineText = decoration == TEXT_DECORATION_UNDERLINE;
if (font.hasKey(TEXT_DECORATION)) {
String decoration = font.getString(TEXT_DECORATION);
switch (decoration) {
case TEXT_DECORATION_UNDERLINE:
underlineText = true;
break;
boolean strikeThruText = decoration == TEXT_DECORATION_LINE_THROUGH;
case TEXT_DECORATION_LINE_THROUGH:
strikeThruText = true;
break;
}
}
int fontStyle;
if (isBold && isItalic) {
@@ -278,15 +287,15 @@ class TSpanShadowNode extends TextShadowNode {
Typeface typeface = null;
try {
String path = FONTS + font.getString(PROP_FONT_FAMILY) + OTF;
String path = FONTS + font.getString(FONT_FAMILY) + OTF;
typeface = Typeface.createFromAsset(assetManager, path);
} catch (Exception ignored) {
try {
String path = FONTS + font.getString(PROP_FONT_FAMILY) + TTF;
String path = FONTS + font.getString(FONT_FAMILY) + TTF;
typeface = Typeface.createFromAsset(assetManager, path);
} catch (Exception ignored2) {
try {
typeface = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle);
typeface = Typeface.create(font.getString(FONT_FAMILY), fontStyle);
} catch (Exception ignored3) {
}
}

View File

@@ -15,7 +15,6 @@ import android.graphics.Path;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nullable;
@@ -25,38 +24,31 @@ import javax.annotation.Nullable;
*/
class TextShadowNode extends GroupShadowNode {
// static final String INHERIT = "inherit";
private static final int TEXT_ANCHOR_AUTO = 0;
// static final int TEXT_ANCHOR_START = 1;
static final int TEXT_ANCHOR_MIDDLE = 2;
static final int TEXT_ANCHOR_END = 3;
// static final String TEXT_ANCHOR_AUTO = "auto";
// static final String TEXT_ANCHOR_START = "start";
static final String TEXT_ANCHOR_MIDDLE = "middle";
static final String TEXT_ANCHOR_END = "end";
private static final int TEXT_DECORATION_NONE = 0;
static final int TEXT_DECORATION_UNDERLINE = 1;
// static final int TEXT_DECORATION_OVERLINE = 2;
static final int TEXT_DECORATION_LINE_THROUGH = 3;
// static final int TEXT_DECORATION_BLINK = 4;
// static final String TEXT_DECORATION_NONE = "none";
static final String TEXT_DECORATION_UNDERLINE = "underline";
// static final String TEXT_DECORATION_OVERLINE = "overline";
static final String TEXT_DECORATION_LINE_THROUGH = "line-through";
// static final String TEXT_DECORATION_BLINK = "blink";
static final String KERNING = "kerning";
static final String TEXT_ANCHOR = "textAnchor";
static final String WORD_SPACING = "wordSpacing";
static final String LETTER_SPACING = "letterSpacing";
static final String TEXT_DECORATION = "textDecoration";
private int mTextAnchor = TEXT_ANCHOR_AUTO;
private int mTextDecoration = TEXT_DECORATION_NONE;
private @Nullable ReadableArray mPositionX;
private @Nullable ReadableArray mPositionY;
private @Nullable ReadableArray mRotate;
private @Nullable ReadableArray mDeltaX;
private @Nullable ReadableArray mDeltaY;
@ReactProp(name = "textAnchor")
public void setTextAnchor(int textAnchor) {
mTextAnchor = textAnchor;
markUpdated();
}
@ReactProp(name = "textDecoration")
public void setTextDecoration(int textDecoration) {
mTextDecoration = textDecoration;
markUpdated();
}
@ReactProp(name = "rotate")
public void setRotate(@Nullable ReadableArray rotate) {
mRotate = rotate;
@@ -112,52 +104,6 @@ class TextShadowNode extends GroupShadowNode {
return groupPath;
}
private int getTextAnchor() {
return mTextAnchor;
}
int getComputedTextAnchor() {
int anchor = mTextAnchor;
if (anchor != TEXT_ANCHOR_AUTO) {
return anchor;
}
ReactShadowNode shadowNode = this.getParent();
while (shadowNode instanceof GroupShadowNode) {
if (shadowNode instanceof TextShadowNode) {
anchor = ((TextShadowNode) shadowNode).getTextAnchor();
if (anchor != TEXT_ANCHOR_AUTO) {
break;
}
}
shadowNode = shadowNode.getParent();
}
return anchor;
}
int getTextDecoration() {
int decoration = mTextDecoration;
if (decoration != TEXT_DECORATION_NONE) {
return decoration;
}
ReactShadowNode shadowNode = this.getParent();
while (shadowNode instanceof GroupShadowNode) {
if (shadowNode instanceof TextShadowNode) {
decoration = ((TextShadowNode) shadowNode).getTextDecoration();
if (decoration != TEXT_DECORATION_NONE) {
break;
}
}
shadowNode = shadowNode.getParent();
}
return decoration;
}
void releaseCachedPath() {
traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) {

View File

@@ -16,7 +16,6 @@ export default class extends Shape {
...fontProps,
dx: numberProp,
dy: numberProp,
textAnchor: PropTypes.oneOf(['start', 'middle', 'end'])
};
static childContextTypes = {

View File

@@ -15,7 +15,6 @@ export default class extends Shape {
...fontProps,
dx: numberProp,
dy: numberProp,
textAnchor: PropTypes.oneOf(['start', 'middle', 'end'])
};
static childContextTypes = {

View File

@@ -8,21 +8,7 @@ const fontFamilySuffix = /[\s"']*$/;
const spaceReg = /\s+/;
const commaReg = /,/g;
const anchors = {
auto: 0,
start: 1,
middle: 2,
end: 3
};
const decorations = {
none: 0,
underline: 1,
overline: 2,
'line-through': 3,
blink: 4
};
let cachedFontObjectsFromString = {};
const cachedFontObjectsFromString = {};
function extractSingleFontFamily(fontFamilyString) {
// SVG on the web allows for multiple font-families to be specified.
@@ -37,40 +23,59 @@ function parseFontString(font) {
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
return cachedFontObjectsFromString[font];
}
let match = fontRegExp.exec(font);
const match = fontRegExp.exec(font);
if (!match) {
return null;
}
let fontFamily = extractSingleFontFamily(match[3]);
let fontSize = match[2] || "12";
let isBold = /bold/.exec(match[1]);
let isItalic = /italic/.exec(match[1]);
const fontFamily = extractSingleFontFamily(match[3]);
const fontSize = match[2] || "12";
const isBold = /bold/.exec(match[1]);
const isItalic = /italic/.exec(match[1]);
const fontWeight = isBold ? 'bold' : 'normal';
const fontStyle = isItalic ? 'italic' : 'normal';
cachedFontObjectsFromString[font] = {
fontSize,
fontFamily,
fontWeight: isBold ? 'bold' : 'normal',
fontStyle: isItalic ? 'italic' : 'normal'
fontWeight,
fontStyle,
};
return cachedFontObjectsFromString[font];
}
export function extractFont(props) {
let font = props.font;
const {
letterSpacing,
wordSpacing,
fontWeight,
fontStyle,
kerning,
textAnchor,
textDecoration,
} = props;
let {
font,
fontSize,
fontFamily,
} = props;
let ownedFont = {
fontFamily: extractSingleFontFamily(props.fontFamily),
letterSpacing: props.letterSpacing,
wordSpacing: props.wordSpacing,
fontWeight: props.fontWeight,
fontStyle: props.fontStyle,
fontSize: props.fontSize ? '' + props.fontSize : null,
kerning: props.kerning,
};
fontFamily = extractSingleFontFamily(fontFamily);
fontSize = fontSize ? '' + fontSize : null;
if (typeof props.font === 'string') {
font = parseFontString(props.font);
const ownedFont = _.pickBy({
fontFamily,
letterSpacing,
wordSpacing,
fontWeight,
fontStyle,
fontSize,
kerning,
textAnchor,
textDecoration,
}, prop => !_.isNil(prop));
if (typeof font === 'string') {
font = parseFontString(font);
}
ownedFont = _.pickBy(ownedFont, prop => !_.isNil(prop));
return _.defaults(ownedFont, font);
}
@@ -88,28 +93,27 @@ function parseSVGLengthList(delta) {
}
export default function(props, container) {
let {
const {
x,
y,
dx,
dy,
rotate,
method,
spacing,
textAnchor,
startOffset,
textDecoration
} = props;
let {
rotate,
children,
startOffset
} = props;
let positionX = parseSVGLengthList(x);
let positionY = parseSVGLengthList(y);
const positionX = parseSVGLengthList(x);
const positionY = parseSVGLengthList(y);
const deltaX = parseSVGLengthList(dx);
const deltaY = parseSVGLengthList(dy);
rotate = parseSVGLengthList(rotate);
let { children } = props;
let content = null;
if (typeof children === 'string' || typeof children === 'number') {
const childrenString = children.toString();
if (container) {
@@ -128,10 +132,11 @@ export default function(props, container) {
});
}
const font = extractFont(props);
startOffset = (startOffset || 0).toString();
return {
textDecoration: decorations[textDecoration] || 0,
textAnchor: anchors[textAnchor] || 0,
font: extractFont(props),
font,
children,
content,
positionX,
@@ -141,6 +146,6 @@ export default function(props, container) {
deltaY,
method,
spacing,
startOffset: (startOffset || 0).toString(),
startOffset,
};
}

View File

@@ -53,6 +53,8 @@ const fontProps = {
fontSize: numberProp,
fontWeight: numberProp,
fontStyle: PropTypes.string,
textAnchor: PropTypes.string,
textDecoration: PropTypes.string,
letterSpacing: PropTypes.string,
wordSpacing: PropTypes.string,
kerning: PropTypes.string,