Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add basic support for RTL text direction. #1637

Merged
merged 6 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ var CONTEXT_PROPERTIES = [
'shadowBlur',
'shadowOffsetX',
'shadowOffsetY',
'letterSpacing',
'lineCap',
'lineDashOffset',
'lineJoin',
'lineWidth',
'miterLimit',
'direction',
'font',
'textAlign',
'textBaseline',
Expand All @@ -91,6 +93,11 @@ var CONTEXT_PROPERTIES = [
] as const;

const traceArrMax = 100;

interface ExtendedCanvasRenderingContext2D extends CanvasRenderingContext2D {
letterSpacing: string;
}

/**
* Konva wrapper around native 2d canvas context. It has almost the same API of 2d context with some additional functions.
* With core Konva shapes you don't need to use this object. But you will use it if you want to create
Expand Down Expand Up @@ -763,7 +770,7 @@ export class Context {

// supported context properties
type CanvasContextProps = Pick<
CanvasRenderingContext2D,
ExtendedCanvasRenderingContext2D,
(typeof CONTEXT_PROPERTIES)[number]
>;

Expand Down
39 changes: 38 additions & 1 deletion src/shapes/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function stringToArray(string: string) {
}

export interface TextConfig extends ShapeConfig {
direction?: string;
text?: string;
fontFamily?: string;
fontSize?: number;
Expand All @@ -41,11 +42,13 @@ export interface TextConfig extends ShapeConfig {
var AUTO = 'auto',
//CANVAS = 'canvas',
CENTER = 'center',
INHERIT = 'inherit',
JUSTIFY = 'justify',
CHANGE_KONVA = 'Change.konva',
CONTEXT_2D = '2d',
DASH = '-',
LEFT = 'left',
LTR = 'ltr',
TEXT = 'text',
TEXT_UPPER = 'Text',
TOP = 'top',
Expand All @@ -55,11 +58,13 @@ var AUTO = 'auto',
PX_SPACE = 'px ',
SPACE = ' ',
RIGHT = 'right',
RTL = 'rtl',
WORD = 'word',
CHAR = 'char',
NONE = 'none',
ELLIPSIS = '…',
ATTR_CHANGE_LIST = [
'direction',
'fontFamily',
'fontSize',
'fontStyle',
Expand Down Expand Up @@ -132,6 +137,7 @@ function checkDefaultFill(config?: TextConfig) {
* @memberof Konva
* @augments Konva.Shape
* @param {Object} config
* @param {String} [config.direction] default is inherit
* @param {String} [config.fontFamily] default is Arial
* @param {Number} [config.fontSize] in pixels. Default is 12
* @param {String} [config.fontStyle] can be 'normal', 'italic', or 'bold', '500' or even 'italic bold'. 'normal' is the default.
Expand Down Expand Up @@ -185,6 +191,7 @@ export class Text extends Shape<TextConfig> {
fontSize = this.fontSize(),
lineHeightPx = this.lineHeight() * fontSize,
verticalAlign = this.verticalAlign(),
direction = this.direction(),
alignY = 0,
align = this.align(),
totalWidth = this.getWidth(),
Expand All @@ -194,19 +201,26 @@ export class Text extends Shape<TextConfig> {
shouldUnderline = textDecoration.indexOf('underline') !== -1,
shouldLineThrough = textDecoration.indexOf('line-through') !== -1,
n;

direction = direction === INHERIT ? context.direction : direction;

var translateY = 0;
var translateY = lineHeightPx / 2;

var lineTranslateX = 0;
var lineTranslateY = 0;

if (direction === RTL) {
context.setAttr('direction', direction);
}

context.setAttr('font', this._getContextFont());

context.setAttr('textBaseline', MIDDLE);

context.setAttr('textAlign', LEFT);


// handle vertical alignment
if (verticalAlign === MIDDLE) {
alignY = (this.getHeight() - textArrLen * lineHeightPx - padding * 2) / 2;
Expand Down Expand Up @@ -282,7 +296,10 @@ export class Text extends Shape<TextConfig> {
context.stroke();
context.restore();
}
if (letterSpacing !== 0 || align === JUSTIFY) {
// As `letterSpacing` isn't supported on Safari, we use this polyfill.
// The exception is for RTL text, which we rely on native as it cannot
// be supported otherwise.
if (direction !== RTL && (letterSpacing !== 0 || align === JUSTIFY)) {
// var words = text.split(' ');
spacesNumber = text.split(' ').length - 1;
var array = stringToArray(text);
Expand All @@ -303,6 +320,9 @@ export class Text extends Shape<TextConfig> {
lineTranslateX += this.measureSize(letter).width + letterSpacing;
}
} else {
if (letterSpacing !== 0) {
context.setAttr('letterSpacing', `${letterSpacing}px`);
}
this._partialTextX = lineTranslateX;
this._partialTextY = translateY + lineTranslateY;
this._partialText = text;
Expand Down Expand Up @@ -615,6 +635,7 @@ export class Text extends Shape<TextConfig> {
return super._useBufferCanvas();
}

direction: GetSet<string, this>;
fontFamily: GetSet<string, this>;
fontSize: GetSet<number, this>;
fontStyle: GetSet<string, this>;
Expand Down Expand Up @@ -682,6 +703,22 @@ Factory.overWriteSetter(Text, 'width', getNumberOrAutoValidator());

Factory.overWriteSetter(Text, 'height', getNumberOrAutoValidator());


/**
* get/set direction
* @name Konva.Text#direction
* @method
* @param {String} direction
* @returns {String}
* @example
* // get direction
* var direction = text.direction();
*
* // set direction
* text.direction('rtl');
*/
Factory.addGetterSetter(Text, 'direction', INHERIT);

/**
* get/set font family
* @name Konva.Text#fontFamily
Expand Down
59 changes: 59 additions & 0 deletions test/unit/Text-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1654,4 +1654,63 @@ describe('Text', function () {

assert.equal(layer.getContext().getTrace(), trace);
});

it('sets ltr text direction', function () {
var stage = addStage();
var layer = new Konva.Layer();

stage.add(layer);
var text = new Konva.Text({
text: 'ltr text',
direction: 'ltr',
});

layer.add(text);
layer.draw();

var trace =
'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(ltr text,0,6);restore();restore();';

assert.equal(layer.getContext().getTrace(), trace);
});


it('sets rtl text direction', function () {
var stage = addStage();
var layer = new Konva.Layer();

stage.add(layer);
var text = new Konva.Text({
text: 'rtl text',
direction: 'rtl',
});

layer.add(text);
layer.draw();

var trace =
'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);direction=rtl;font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();fillStyle=black;fillText(rtl text,0,6);restore();restore();';

assert.equal(layer.getContext().getTrace(), trace);
});

it('sets rtl text direction with letterSpacing', function () {
var stage = addStage();
var layer = new Konva.Layer();

stage.add(layer);
var text = new Konva.Text({
text: 'rtl text',
direction: 'rtl',
letterSpacing: 2,
});

layer.add(text);
layer.draw();

var trace =
'clearRect(0,0,578,200);clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);direction=rtl;font=normal normal 12px Arial;textBaseline=middle;textAlign=left;translate(0,0);save();letterSpacing=2px;fillStyle=black;fillText(rtl text,0,6);restore();restore();';

assert.equal(layer.getContext().getTrace(), trace);
});
});
Loading