first step of TSpan

This commit is contained in:
Horcrux
2016-08-30 09:37:38 +08:00
parent e78e5db092
commit bd26c04a1d
10 changed files with 400 additions and 137 deletions

View File

@@ -8,7 +8,8 @@ import Svg, {
Stop,
Defs,
Path,
G
G,
TSpan
} from 'react-native-svg';
class TextExample extends Component{
@@ -148,6 +149,33 @@ class TextPath extends Component{
}
}
class TSpanExample extends Component{
static title = 'TSpan nest';
render() {
return <Svg
height="150"
width="300"
>
<Text y="20" dx="5 5">
<TSpan x="10" textAnchor="middle">tspan line 1</TSpan>
<TSpan x="10" dy="15">tspan line 2</TSpan>
<TSpan x="10" dx="10" dy="15">tspan line 3</TSpan>
</Text>
<Text x="10" y="60" fill="red" fontSize="14">
<TSpan dy="5 10 20" >12345</TSpan>
<TSpan fill="blue" dy="15" dx="0 5 5">
<TSpan>6</TSpan>
<TSpan>7</TSpan>
</TSpan>
<TSpan dx="0 10 20" dy="0 20" fontWeight="bold" fontSize="12">89a</TSpan>
</Text>
<Text y="140" dx="0 5 5" dy="0 -5 -5">delta on text</Text>
</Svg>;
}
}
const icon = <Svg
height="20"
width="20"
@@ -168,7 +196,8 @@ const samples = [
TextRotate,
TextStroke,
TextFill,
TextPath
TextPath,
TSpanExample
];
export {

40
elements/TSpan.js Normal file
View File

@@ -0,0 +1,40 @@
import React, {PropTypes} from 'react';
import createReactNativeComponentClass from 'react/lib/createReactNativeComponentClass';
import {numberProp, pathProps} from '../lib/props';
import {TSpanAttributes} from '../lib/attributes';
import extractFrame from '../lib/extract/extractFrame';
import Shape from './Shape';
class TSpan extends Shape {
static displayName = 'TSpan';
static propTypes = {
...pathProps,
dx: numberProp,
dy: numberProp,
textAnchor: PropTypes.oneOf(['start', 'middle', 'end']),
fontFamily: PropTypes.string,
fontSize: numberProp,
fontWeight: PropTypes.string,
fontStyle: PropTypes.string,
font: PropTypes.object
};
setNativeProps = (...args) => {
this.root.setNativeProps(...args);
};
render() {
return <RNSVGTSpan
ref={ele => {this.root = ele;}}
{...extractFrame(this.props)}
/>;
}
}
const RNSVGTSpan = createReactNativeComponentClass({
validAttributes: TSpanAttributes,
uiViewClassName: 'RNSVGTSpan'
});
export default TSpan;

View File

@@ -13,18 +13,11 @@ class Text extends Shape {
dx: numberProp,
dy: numberProp,
textAnchor: PropTypes.oneOf(['start', 'middle', 'end']),
path: PropTypes.string,
fontFamily: PropTypes.string,
fontSize: numberProp,
fontWeight: PropTypes.string,
fontStyle: PropTypes.string,
font: PropTypes.object,
lines: numberProp
};
static defaultProps = {
dx: 0,
dy: 0
font: PropTypes.object
};
setNativeProps = (...args) => {
@@ -33,19 +26,9 @@ class Text extends Shape {
render() {
let props = this.props;
let x = 0;
if (props.x) {
x = props.dx ? +props.x + (+props.dx) : +props.x;
}
let y = 0;
if (props.y) {
y = props.dy ? +props.y + (+props.dy) : +props.y;
}
return <RNSVGText
ref={ele => {this.root = ele;}}
{...this.extractProps({...props, x, y})}
{...this.extractProps({...props})}
{...extractText(props)}
/>;
}

19
ios/Text/RNSVGTSpan.h Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-present, Horcrux.
* All rights reserved.
*
* This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import "RNSVGPath.h"
#import "RNSVGTextFrame.h"
@interface RNSVGText : RNSVGPath
@property (nonatomic, assign) CTTextAlignment alignment;
@property (nonatomic, assign) RNSVGTextFrame textFrame;
@property (nonatomic, assign) NSString
@end

136
ios/Text/RNSVGTSpan.m Normal file
View File

@@ -0,0 +1,136 @@
/**
* Copyright (c) 2015-present, Horcrux.
* All rights reserved.
*
* This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RNSVGText.h"
#import "RNSVGBezierPath.h"
#import <CoreText/CoreText.h>
@implementation RNSVGText
static void RNSVGFreeTextFrame(RNSVGTextFrame frame)
{
if (frame.count) {
// We must release each line before freeing up this struct
for (int i = 0; i < frame.count; i++) {
CFRelease(frame.lines[i]);
}
free(frame.lines);
free(frame.widths);
}
}
- (void)setAlignment:(CTTextAlignment)alignment
{
[self invalidate];
_alignment = alignment;
}
- (void)setTextFrame:(RNSVGTextFrame)textFrame
{
RNSVGFreeTextFrame(_textFrame);
[self invalidate];
_textFrame = textFrame;
}
- (void)setPath:(NSArray *)path
{
if (path == _path) {
return;
}
[self invalidate];
_path = path;
}
- (void)dealloc
{
RNSVGFreeTextFrame(_textFrame);
}
- (CGPathRef)getPath:(CGContextRef)context
{
CGMutablePathRef path = CGPathCreateMutable();
RNSVGTextFrame frame = self.textFrame;
for (int i = 0; i < frame.count; i++) {
CGFloat shift;
CGFloat width = frame.widths[i];
switch (self.alignment) {
case kCTTextAlignmentRight:
shift = width;
break;
case kCTTextAlignmentCenter:
shift = width / 2;
break;
default:
shift = 0;
break;
}
// We should consider snapping this shift to device pixels to improve rendering quality
// when a line has subpixel width.
CGAffineTransform offset = CGAffineTransformMakeTranslation(-shift, frame.baseLine + frame.lineHeight * i + (self.path ? -frame.lineHeight : 0));
CGMutablePathRef line = [self setLinePath:frame.lines[i]];
CGPathAddPath(path, &offset, line);
CGPathRelease(line);
}
return (CGPathRef)CFAutorelease(path);
}
- (CGMutablePathRef)setLinePath:(CTLineRef)line
{
CGAffineTransform upsideDown = CGAffineTransformMakeScale(1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable();
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, 0);
CFIndex runGlyphCount = CTRunGetGlyphCount(run);
CGPoint positions[runGlyphCount];
CGGlyph glyphs[runGlyphCount];
// Grab the glyphs, positions, and font
CTRunGetPositions(run, CFRangeMake(0, 0), positions);
CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs);
CFDictionaryRef attributes = CTRunGetAttributes(run);
CTFontRef runFont = CFDictionaryGetValue(attributes, kCTFontAttributeName);
RNSVGBezierPath *bezierPath = [[RNSVGBezierPath alloc] initWithBezierCurves:self.path];
for(CFIndex i = 0; i < runGlyphCount; ++i) {
CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyphs[i], nil);
CGPoint point = positions[i];
if (letter) {
CGAffineTransform transform;
// draw glyphs along path
if (self.path) {
transform = [bezierPath transformAtDistance:point.x];
// break loop if line reaches the end of the Path.
if (!transform.a || !transform.d) {
CGPathRelease(letter);
break;
}
transform = CGAffineTransformScale(transform, 1.0, -1.0);
} else {
transform = CGAffineTransformTranslate(upsideDown, point.x, point.y);
}
CGPathAddPath(path, &transform, letter);
}
CGPathRelease(letter);
}
return path;
}
@end

View File

@@ -17,30 +17,6 @@ function arrayDiffer(a, b) {
return false;
}
function fontAndLinesDiffer(a, b) {
if (a === b) {
return false;
}
if (a.font !== b.font) {
if (a.font === null) {
return true;
}
if (b.font === null) {
return true;
}
if (
a.font.fontFamily !== b.font.fontFamily ||
a.font.fontSize !== b.font.fontSize ||
a.font.fontWeight !== b.font.fontWeight ||
a.font.fontStyle !== b.font.fontStyle
) {
return true;
}
}
return arrayDiffer(a.lines, b.lines);
}
const ViewBoxAttributes = {
minX: true,
minY: true,
@@ -104,16 +80,22 @@ const PathAttributes = merge({
}
}, RenderableAttributes);
const TextAttributes = merge({
alignment: true,
frame: {
diff: fontAndLinesDiffer
},
path: {
diff: arrayDiffer
}
const FontAttributes = merge({
fontFamily: true,
fontSize: true,
fontWeight: true,
fontStyle: true
}, RenderableAttributes);
const TSpanAttributes = merge({
line: true
}, FontAttributes);
const TextAttributes = merge({
alignment: true
}, FontAttributes);
const ClipPathAttributes = {
name: true
};
@@ -184,6 +166,7 @@ const RectAttributes = merge({
export {
PathAttributes,
TextAttributes,
TSpanAttributes,
GroupAttributes,
ClipPathAttributes,
CircleAttributes,

View File

@@ -0,0 +1,21 @@
import extractSpan from './extractSpan';
import extractTextContent from './extractTextContent';
export default function (props) {
let {children, line} = props;
let extractedProps = extractSpan(props);
if (typeof children === 'string') {
line = children;
children = null;
} else {
children = extractTextContent(props.children);
line = null;
}
console.log(extractedProps);
return {
...extractedProps,
children,
line
};
};

View File

@@ -0,0 +1,76 @@
import React, {
Children
} from 'react';
import TSpan from '../../elements/TSpan';
import extractTextContent from './extractTextContent';
import SerializablePath from '../SerializablePath';
import _ from 'lodash';
const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm%]*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i;
const fontFamilyPrefix = /^[\s"']*/;
const fontFamilySuffix = /[\s"']*$/;
const spaceReg = /\s+/;
const commaReg = /,/;
let cachedFontObjectsFromString = {};
function extractSingleFontFamily(fontFamilyString) {
// SVG on the web allows for multiple font-families to be specified.
// For compatibility, we extract the first font-family, hoping
// we'll get a match.
return fontFamilyString ? fontFamilyString.split(commaReg)[0]
.replace(fontFamilyPrefix, '')
.replace(fontFamilySuffix, '') : null;
}
function parseFontString(font) {
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
return cachedFontObjectsFromString[font];
}
let 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]);
cachedFontObjectsFromString[font] = {
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: isBold ? 'bold' : 'normal',
fontStyle: isItalic ? 'italic' : 'normal'
};
return cachedFontObjectsFromString[font];
}
function extractFont(props) {
let font = props.font;
let fontSize = +props.fontSize;
let ownedFont = {
fontFamily: extractSingleFontFamily(props.fontFamily),
fontSize: isNaN(fontSize) ? null : fontSize,
fontWeight: props.fontWeight,
fontStyle: props.fontStyle
};
if (typeof props.font === 'string') {
font = parseFontString(props.font);
}
ownedFont = _.pickBy(ownedFont, prop => !_.isNil(prop));
return _.defaults(ownedFont, font);
}
function parseDelta(delta) {
return delta.toString().split(spaceReg);
}
export default function(props) {
return {
dx: parseDelta(props.dx || ''),
dy: parseDelta(props.dy || ''),
...extractFont(props)
};
}

View File

@@ -1,93 +1,43 @@
import SerializablePath from '../SerializablePath';
import _ from 'lodash';
const newLine = /\n/g;
const defaultFontFamily = '"Helvetica Neue", "Helvetica", Arial';
const fontRegExp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm%]*(?:\s*\/.*?)?\s+)?\s*"?([^"]*)/i;
const fontFamilyPrefix = /^[\s"']*/;
const fontFamilySuffix = /[\s"']*$/;
let cachedFontObjectsFromString = {};
import extractTextContent from './extractTextContent';
import extractSpan from './extractSpan';
function childrenAsString(children) {
if (!children) {
return '';
}
if (typeof children === 'string') {
return children;
}
if (children.length) {
return children.join('\n');
}
return '';
}
function extractFontAndLines(font, text) {
return {
font: extractFont(font),
lines: text.split(newLine)
};
}
function extractSingleFontFamily(fontFamilyString = defaultFontFamily) {
// SVG on the web allows for multiple font-families to be specified.
// For compatibility, we extract the first font-family, hoping
// we'll get a match.
return fontFamilyString.split(',')[0]
.replace(fontFamilyPrefix, '')
.replace(fontFamilySuffix, '');
}
function parseFontString(font) {
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
return cachedFontObjectsFromString[font];
}
let 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]);
cachedFontObjectsFromString[font] = {
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: isBold ? 'bold' : 'normal',
fontStyle: isItalic ? 'italic' : 'normal'
};
return cachedFontObjectsFromString[font];
}
function extractFont(font) {
if (_.isNil(font)) {
return null;
}
if (typeof font === 'string') {
return parseFontString(font);
}
let fontFamily = extractSingleFontFamily(font.fontFamily);
let fontSize = +font.fontSize || 12;
return {
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: font.fontWeight,
fontStyle: font.fontStyle
};
}
const anchord = {
const anchors = {
end: 1,
middle: 2,
start: 0
};
export default function(props) {
let children = extractTextContent(props.children);
let extractedProps = extractSpan(props);
let firstSpan = children[0];
let alignment;
let {dx, dy} = extractedProps;
let maxDeltaLength = Math.max(dx.length, dy.length);
if (firstSpan && firstSpan.props.hasOwnProperty('textAnchor')) {
alignment = anchors[firstSpan.props.textAnchor];
} else if (anchors[props.textAnchor]) {
alignment = anchors[props.textAnchor];
}
if (!alignment) {
alignment = 0;
}
for (let i = 0; i < maxDeltaLength; i++) {
console.log(i);
}
console.log(extractedProps);
return {
alignment: anchord[props.textAnchor] || 0,
frame: extractFontAndLines(
props,
childrenAsString(props.children)
),
path: props.path ? new SerializablePath(props.path).toJSON() : undefined
};
alignment,
children,
fontFamily: 'Helvetica Neue',
fontSize: 12,
fontStyle: 'normal',
fontWeight: 'normal',
...extractedProps
}
}

View File

@@ -0,0 +1,26 @@
import React, {
Children
} from 'react';
import TSpan from '../../elements/TSpan';
const newLine = /\n/g;
export default function(children) {
let spans = [];
Children.forEach(children, function (child = '') {
let span;
if (typeof child === 'string') {
span = <TSpan>{child.replace(newLine, ' ')}</TSpan>;
} else if (child.type === TSpan) {
span = child;
} else {
// give warning about the illegal child type
return;
}
spans.push(span);
});
return spans;
}