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 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 // https://www.w3.org/TR/SVG/text.html#TSpanElement
class GlyphContext { class GlyphContext {
static final double DEFAULT_FONT_SIZE = 12d; 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 // Empty font context map
private static final WritableMap DEFAULT_MAP = Arguments.createMap(); private static final WritableMap DEFAULT_MAP = Arguments.createMap();
@@ -236,20 +238,18 @@ class GlyphContext {
map.putDouble(FONT_SIZE, mFontSize); map.putDouble(FONT_SIZE, mFontSize);
put(FONT_STYLE, map, font, parent);
put(FONT_FAMILY, map, font, parent); put(FONT_FAMILY, map, font, parent);
put(FONT_WEIGHT, 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://www.w3.org/TR/SVG11/text.html#SpacingProperties
// https://drafts.csswg.org/css-text-3/#spacing // 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(KERNING, map, font, parent);
putD(WORD_SPACING, map, font, parent); putD(WORD_SPACING, map, font, parent);
putD(LETTER_SPACING, map, font, parent); putD(LETTER_SPACING, map, font, parent);
} }
@@ -428,7 +428,7 @@ class GlyphContext {
return mFontSize; return mFontSize;
} }
double nextX(double glyphWidth) { double nextX(double advance) {
incrementIndices(mXIndices, mXsIndex); incrementIndices(mXIndices, mXsIndex);
int nextIndex = mXIndex + 1; int nextIndex = mXIndex + 1;
@@ -439,7 +439,7 @@ class GlyphContext {
mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize); mX = PropHelper.fromRelative(string, mWidth, 0, mScale, mFontSize);
} }
mX += glyphWidth; mX += advance;
return mX; 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.POSITION_MATRIX_FLAG;
import static android.graphics.PathMeasure.TANGENT_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 * 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_WORD_SPACING = 0d;
private static final double DEFAULT_LETTER_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 Path mCache;
private @Nullable String mContent; private @Nullable String mContent;
private TextPathShadowNode textPath; private TextPathShadowNode textPath;
@@ -98,10 +95,10 @@ class TSpanShadowNode extends TextShadowNode {
return mCache; return mCache;
} }
private double getTextAnchorShift(double width) { private double getTextAnchorShift(double width, String textAnchor) {
double x = 0; double x = 0;
switch (getComputedTextAnchor()) { switch (textAnchor) {
case TEXT_ANCHOR_MIDDLE: case TEXT_ANCHOR_MIDDLE:
x = -width / 2; x = -width / 2;
break; 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 x;
double y; double y;
@@ -180,16 +179,16 @@ class TSpanShadowNode extends TextShadowNode {
* *
* */ * */
final boolean hasKerning = font.hasKey(PROP_KERNING); final boolean hasKerning = font.hasKey(KERNING);
double kerning = hasKerning ? font.getDouble(PROP_KERNING) : DEFAULT_KERNING; double kerning = hasKerning ? font.getDouble(KERNING) : DEFAULT_KERNING;
final boolean autoKerning = !hasKerning; final boolean autoKerning = !hasKerning;
final double wordSpacing = font.hasKey(PROP_WORD_SPACING) ? final double wordSpacing = font.hasKey(WORD_SPACING) ?
font.getDouble(PROP_WORD_SPACING) font.getDouble(WORD_SPACING)
: DEFAULT_WORD_SPACING; : DEFAULT_WORD_SPACING;
final double letterSpacing = font.hasKey(PROP_LETTER_SPACING) ? final double letterSpacing = font.hasKey(LETTER_SPACING) ?
font.getDouble(PROP_LETTER_SPACING) font.getDouble(LETTER_SPACING)
: DEFAULT_LETTER_SPACING; : DEFAULT_LETTER_SPACING;
for (int index = 0; index < length; index++) { for (int index = 0; index < length; index++) {
@@ -251,19 +250,29 @@ class TSpanShadowNode extends TextShadowNode {
private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) { private void applyTextPropertiesToPaint(Paint paint, ReadableMap font) {
AssetManager assetManager = getThemedContext().getResources().getAssets(); 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) && boolean isBold = font.hasKey(FONT_WEIGHT) &&
BOLD.equals(font.getString(PROP_FONT_WEIGHT)); BOLD.equals(font.getString(FONT_WEIGHT));
boolean isItalic = font.hasKey(PROP_FONT_STYLE) && boolean isItalic = font.hasKey(FONT_STYLE) &&
ITALIC.equals(font.getString(PROP_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; int fontStyle;
if (isBold && isItalic) { if (isBold && isItalic) {
@@ -278,15 +287,15 @@ class TSpanShadowNode extends TextShadowNode {
Typeface typeface = null; Typeface typeface = null;
try { try {
String path = FONTS + font.getString(PROP_FONT_FAMILY) + OTF; String path = FONTS + font.getString(FONT_FAMILY) + OTF;
typeface = Typeface.createFromAsset(assetManager, path); typeface = Typeface.createFromAsset(assetManager, path);
} catch (Exception ignored) { } catch (Exception ignored) {
try { try {
String path = FONTS + font.getString(PROP_FONT_FAMILY) + TTF; String path = FONTS + font.getString(FONT_FAMILY) + TTF;
typeface = Typeface.createFromAsset(assetManager, path); typeface = Typeface.createFromAsset(assetManager, path);
} catch (Exception ignored2) { } catch (Exception ignored2) {
try { try {
typeface = Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle); typeface = Typeface.create(font.getString(FONT_FAMILY), fontStyle);
} catch (Exception ignored3) { } catch (Exception ignored3) {
} }
} }

View File

@@ -15,7 +15,6 @@ import android.graphics.Path;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -25,38 +24,31 @@ import javax.annotation.Nullable;
*/ */
class TextShadowNode extends GroupShadowNode { class TextShadowNode extends GroupShadowNode {
// static final String INHERIT = "inherit";
private static final int TEXT_ANCHOR_AUTO = 0; // static final String TEXT_ANCHOR_AUTO = "auto";
// static final int TEXT_ANCHOR_START = 1; // static final String TEXT_ANCHOR_START = "start";
static final int TEXT_ANCHOR_MIDDLE = 2; static final String TEXT_ANCHOR_MIDDLE = "middle";
static final int TEXT_ANCHOR_END = 3; static final String TEXT_ANCHOR_END = "end";
private static final int TEXT_DECORATION_NONE = 0; // static final String TEXT_DECORATION_NONE = "none";
static final int TEXT_DECORATION_UNDERLINE = 1; static final String TEXT_DECORATION_UNDERLINE = "underline";
// static final int TEXT_DECORATION_OVERLINE = 2; // static final String TEXT_DECORATION_OVERLINE = "overline";
static final int TEXT_DECORATION_LINE_THROUGH = 3; static final String TEXT_DECORATION_LINE_THROUGH = "line-through";
// static final int TEXT_DECORATION_BLINK = 4; // 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 mPositionX;
private @Nullable ReadableArray mPositionY; private @Nullable ReadableArray mPositionY;
private @Nullable ReadableArray mRotate; private @Nullable ReadableArray mRotate;
private @Nullable ReadableArray mDeltaX; private @Nullable ReadableArray mDeltaX;
private @Nullable ReadableArray mDeltaY; 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") @ReactProp(name = "rotate")
public void setRotate(@Nullable ReadableArray rotate) { public void setRotate(@Nullable ReadableArray rotate) {
mRotate = rotate; mRotate = rotate;
@@ -112,52 +104,6 @@ class TextShadowNode extends GroupShadowNode {
return groupPath; 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() { void releaseCachedPath() {
traverseChildren(new NodeRunnable() { traverseChildren(new NodeRunnable() {
public void run(VirtualNode node) { public void run(VirtualNode node) {

View File

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

View File

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

View File

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

View File

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