mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-11-21 22:27:56 +01:00
Improve text render on multi-line
This commit is contained in:
parent
bfc043ebbe
commit
74d147b6f6
@ -59,6 +59,7 @@ class EditorComponent extends Events {
|
||||
resize: 'none',
|
||||
overflow: 'hidden',
|
||||
padding: '0px 0px 0px 0px',
|
||||
lineHeight: '100%',
|
||||
});
|
||||
|
||||
result.append(textareaElem);
|
||||
@ -121,7 +122,7 @@ class EditorComponent extends Events {
|
||||
});
|
||||
}
|
||||
|
||||
private resize(text?: string) {
|
||||
private resize(text?: string): void {
|
||||
// Force relayout ...
|
||||
EventBus.instance.fireEvent('forceLayout');
|
||||
|
||||
@ -132,15 +133,12 @@ class EditorComponent extends Events {
|
||||
|
||||
const textValue = text || this.getTextAreaText();
|
||||
const textElem = this.getTextareaElem();
|
||||
const lines = textValue.split('\n');
|
||||
|
||||
let maxLineLength = 1;
|
||||
lines.forEach((line: string) => {
|
||||
maxLineLength = Math.max(line.length, maxLineLength);
|
||||
});
|
||||
const rows = [...textValue].filter((x) => x === '\n').length + 1;
|
||||
const maxLineLength = Math.max(...textValue.split('\n').map((l) => l.length));
|
||||
|
||||
textElem.attr('cols', maxLineLength);
|
||||
textElem.attr('rows', lines.length);
|
||||
textElem.attr('rows', rows);
|
||||
|
||||
this._containerElem.css({
|
||||
width: `${maxLineLength + 2}em`,
|
||||
|
@ -1233,7 +1233,7 @@ abstract class Topic extends NodeGraph {
|
||||
textShape.setFontName(fontFamily);
|
||||
|
||||
const text = this.getText();
|
||||
textShape.setText(text.trim());
|
||||
textShape.setText(text);
|
||||
|
||||
// Update outer shape style ...
|
||||
const outerShape = this.getOuterShape();
|
||||
|
@ -47,12 +47,9 @@ abstract class AbstractBasicSorter extends ChildrenSorterStrategy {
|
||||
if (children.length === 0 || node.areChildrenShrunken()) {
|
||||
result = height;
|
||||
} else {
|
||||
let childrenHeight = 0;
|
||||
|
||||
children.forEach((child) => {
|
||||
childrenHeight += this._computeChildrenHeight(treeSet, child, heightCache);
|
||||
});
|
||||
|
||||
const childrenHeight = children
|
||||
.map((child) => this._computeChildrenHeight(treeSet, child, heightCache))
|
||||
.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
|
||||
result = Math.max(height, childrenHeight);
|
||||
}
|
||||
|
||||
@ -63,7 +60,7 @@ abstract class AbstractBasicSorter extends ChildrenSorterStrategy {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected _getSortedChildren(treeSet: RootedTreeSet, node: Node) {
|
||||
protected _getSortedChildren(treeSet: RootedTreeSet, node: Node): Node[] {
|
||||
const result = treeSet.getChildren(node);
|
||||
result.sort((a, b) => a.getOrder() - b.getOrder());
|
||||
return result;
|
||||
|
@ -148,7 +148,7 @@ class BalancedSorter extends AbstractBasicSorter {
|
||||
}
|
||||
}
|
||||
|
||||
computeOffsets(treeSet: RootedTreeSet, node: Node) {
|
||||
computeOffsets(treeSet: RootedTreeSet, node: Node): Map<number, PositionType> {
|
||||
$assert(treeSet, 'treeSet can no be null.');
|
||||
$assert(node, 'node can no be null.');
|
||||
|
||||
@ -180,7 +180,7 @@ class BalancedSorter extends AbstractBasicSorter {
|
||||
let ysum = 0;
|
||||
|
||||
// Calculate the offsets ...
|
||||
const result = {};
|
||||
const result = new Map<number, PositionType>();
|
||||
for (let i = 0; i < heights.length; i++) {
|
||||
const direction = heights[i].order % 2 ? -1 : 1;
|
||||
|
||||
@ -202,7 +202,7 @@ class BalancedSorter extends AbstractBasicSorter {
|
||||
$assert(!Number.isNaN(xOffset), 'xOffset can not be null');
|
||||
$assert(!Number.isNaN(yOffset), 'yOffset can not be null');
|
||||
|
||||
result[heights[i].id] = { x: xOffset, y: yOffset };
|
||||
result.set(heights[i].id, { x: xOffset, y: yOffset });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import PositionType from '../PositionType';
|
||||
abstract class ChildrenSorterStrategy {
|
||||
abstract computeChildrenIdByHeights(treeSet: RootedTreeSet, node: Node): Map<number, number>;
|
||||
|
||||
abstract computeOffsets(treeSet: RootedTreeSet, node: Node): void;
|
||||
abstract computeOffsets(treeSet: RootedTreeSet, node: Node): Map<number, PositionType>;
|
||||
|
||||
abstract insert(treeSet: RootedTreeSet, parent: Node, child: Node, order: number): void;
|
||||
|
||||
|
@ -145,7 +145,6 @@ class Node {
|
||||
|
||||
/** */
|
||||
setSize(size: SizeType) {
|
||||
$assert($defined(size), 'Size can not be null');
|
||||
this._setProperty('size', { ...size });
|
||||
}
|
||||
|
||||
@ -177,14 +176,7 @@ class Node {
|
||||
|
||||
setPosition(position: PositionType) {
|
||||
// This is a performance improvement to avoid movements that really could be avoided.
|
||||
const currentPos = this.getPosition();
|
||||
if (
|
||||
currentPos == null ||
|
||||
Math.abs(currentPos.x - position.x) > 2 ||
|
||||
Math.abs(currentPos.y - position.y) > 2
|
||||
) {
|
||||
this._setProperty('position', position);
|
||||
}
|
||||
this._setProperty('position', position);
|
||||
}
|
||||
|
||||
_setProperty(key: string, value) {
|
||||
|
@ -81,15 +81,17 @@ class OriginalLayout {
|
||||
// Calculate all node heights ...
|
||||
const sorter = node.getSorter();
|
||||
const heightById = sorter.computeChildrenIdByHeights(this._treeSet, node);
|
||||
this._layoutChildren(node, heightById);
|
||||
this._fixOverlapping(node, heightById);
|
||||
|
||||
this.layoutChildren(node, heightById);
|
||||
// this.fixOverlapping(node, heightById);
|
||||
});
|
||||
}
|
||||
|
||||
private _layoutChildren(node: Node, heightById: Map<number, number>): void {
|
||||
private layoutChildren(node: Node, heightById: Map<number, number>): void {
|
||||
const nodeId = node.getId();
|
||||
const children = this._treeSet.getChildren(node);
|
||||
const parent = this._treeSet.getParent(node);
|
||||
|
||||
const childrenOrderMoved = children.some((child) => child.hasOrderChanged());
|
||||
const childrenSizeChanged = children.some((child) => child.hasSizeChanged());
|
||||
|
||||
@ -99,43 +101,24 @@ class OriginalLayout {
|
||||
|
||||
const parentHeightChanged = parent ? parent._heightChanged : false;
|
||||
const heightChanged = node._branchHeight !== newBranchHeight;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
node._heightChanged = heightChanged || parentHeightChanged;
|
||||
|
||||
if (childrenOrderMoved || childrenSizeChanged || heightChanged || parentHeightChanged) {
|
||||
const sorter = node.getSorter();
|
||||
const offsetById = sorter.computeOffsets(this._treeSet, node);
|
||||
const parentPosition = node.getPosition();
|
||||
const me = this;
|
||||
|
||||
children.forEach((child) => {
|
||||
const offset = offsetById[child.getId()];
|
||||
|
||||
const childFreeDisplacement = child.getFreeDisplacement();
|
||||
const direction = node.getSorter().getChildDirection(me._treeSet, child);
|
||||
|
||||
if (
|
||||
(direction > 0 && childFreeDisplacement.x < 0) ||
|
||||
(direction < 0 && childFreeDisplacement.x > 0)
|
||||
) {
|
||||
child.resetFreeDisplacement();
|
||||
child.setFreeDisplacement({
|
||||
x: -childFreeDisplacement.x,
|
||||
y: childFreeDisplacement.y,
|
||||
});
|
||||
}
|
||||
|
||||
offset.x += child.getFreeDisplacement().x;
|
||||
offset.y += child.getFreeDisplacement().y;
|
||||
const offset = offsetById.get(child.getId())!;
|
||||
|
||||
const parentX = parentPosition.x;
|
||||
const parentY = parentPosition.y;
|
||||
|
||||
const newPos = {
|
||||
x: parentX + offset.x,
|
||||
y: parentY + offset.y + me._calculateAlignOffset(node, child, heightById),
|
||||
y: parentY + offset.y + this.calculateAlignOffset(node, child, heightById),
|
||||
};
|
||||
me._treeSet.updateBranchPosition(child, newPos);
|
||||
this._treeSet.updateBranchPosition(child, newPos);
|
||||
});
|
||||
|
||||
node._branchHeight = newBranchHeight;
|
||||
@ -143,15 +126,11 @@ class OriginalLayout {
|
||||
|
||||
// Continue reordering the children nodes ...
|
||||
children.forEach((child) => {
|
||||
this._layoutChildren(child, heightById);
|
||||
this.layoutChildren(child, heightById);
|
||||
});
|
||||
}
|
||||
|
||||
private _calculateAlignOffset(node: Node, child: Node, heightById: Map<number, number>): number {
|
||||
if (child.isFree()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private calculateAlignOffset(node: Node, child: Node, heightById: Map<number, number>): number {
|
||||
let offset = 0;
|
||||
|
||||
const nodeHeight = node.getSize().height;
|
||||
@ -192,14 +171,11 @@ class OriginalLayout {
|
||||
);
|
||||
}
|
||||
|
||||
private _fixOverlapping(node: Node, heightById: Map<number, number>): void {
|
||||
private fixOverlapping(node: Node, heightById: Map<number, number>): void {
|
||||
const children = this._treeSet.getChildren(node);
|
||||
|
||||
if (node.isFree()) {
|
||||
this._shiftBranches(node, heightById);
|
||||
}
|
||||
children.forEach((child) => {
|
||||
this._fixOverlapping(child, heightById);
|
||||
this.fixOverlapping(child, heightById);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -334,9 +334,8 @@ class RootedTreeSet {
|
||||
const yOffset = oldPos.y - position.y;
|
||||
|
||||
const children = this.getChildren(node);
|
||||
const me = this;
|
||||
children.forEach((child) => {
|
||||
me.shiftBranchPosition(child, xOffset, yOffset);
|
||||
this.shiftBranchPosition(child, xOffset, yOffset);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -198,25 +198,11 @@ class SymmetricSorter extends AbstractBasicSorter {
|
||||
node.setOrder(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param treeSet
|
||||
* @param node
|
||||
* @throws will throw an error if treeSet is null or undefined
|
||||
* @throws will throw an error if node is null or undefined
|
||||
* @throws will throw an error if the calculated x offset cannot be converted to a numeric
|
||||
* value, is null or undefined
|
||||
* @throws will throw an error if the calculated y offset cannot be converted to a numeric
|
||||
* value, is null or undefined
|
||||
* @return offsets
|
||||
*/
|
||||
computeOffsets(treeSet: RootedTreeSet, node: Node) {
|
||||
$assert(treeSet, 'treeSet can no be null.');
|
||||
$assert(node, 'node can no be null.');
|
||||
|
||||
computeOffsets(treeSet: RootedTreeSet, node: Node): Map<number, PositionType> {
|
||||
const children = this._getSortedChildren(treeSet, node);
|
||||
|
||||
// Compute heights ...
|
||||
const heights = children
|
||||
const sizeById = children
|
||||
.map((child) => ({
|
||||
id: child.getId(),
|
||||
order: child.getOrder(),
|
||||
@ -227,30 +213,26 @@ class SymmetricSorter extends AbstractBasicSorter {
|
||||
.reverse();
|
||||
|
||||
// Compute the center of the branch ...
|
||||
let totalHeight = 0;
|
||||
heights.forEach((elem) => {
|
||||
totalHeight += elem.height;
|
||||
});
|
||||
const totalHeight = sizeById
|
||||
.map((e) => e.height)
|
||||
.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
|
||||
let ysum = totalHeight / 2;
|
||||
|
||||
// Calculate the offsets ...
|
||||
const result = {};
|
||||
for (let i = 0; i < heights.length; i++) {
|
||||
ysum -= heights[i].height;
|
||||
const childNode = treeSet.find(heights[i].id);
|
||||
const result = new Map<number, PositionType>();
|
||||
for (let i = 0; i < sizeById.length; i++) {
|
||||
ysum -= sizeById[i].height;
|
||||
const childNode = treeSet.find(sizeById[i].id);
|
||||
const direction = this.getChildDirection(treeSet, childNode);
|
||||
|
||||
const yOffset = ysum + heights[i].height / 2;
|
||||
const yOffset = ysum + sizeById[i].height / 2;
|
||||
const xOffset =
|
||||
direction *
|
||||
(heights[i].width / 2 +
|
||||
(sizeById[i].width / 2 +
|
||||
node.getSize().width / 2 +
|
||||
SymmetricSorter.INTERNODE_HORIZONTAL_PADDING);
|
||||
|
||||
$assert(!Number.isNaN(xOffset), 'xOffset can not be null');
|
||||
$assert(!Number.isNaN(yOffset), 'yOffset can not be null');
|
||||
|
||||
result[heights[i].id] = { x: xOffset, y: yOffset };
|
||||
result.set(sizeById[i].id, { x: xOffset, y: yOffset });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -96,8 +96,7 @@ class Text extends WorkspaceElement<TextPeer> {
|
||||
}
|
||||
|
||||
getFontHeight(): number {
|
||||
const lines = this.peer.getText().split('\n').length;
|
||||
return this.getShapeHeight() / lines;
|
||||
return this.getShapeHeight() / this.peer.getTextLines().length;
|
||||
}
|
||||
|
||||
getPosition(): PositionType {
|
||||
|
@ -20,53 +20,77 @@ import FontPeer, { FontStyle } from './FontPeer';
|
||||
import ElementPeer from './ElementPeer';
|
||||
import { getPosition } from '../utils/DomUtils';
|
||||
import SizeType from '../../SizeType';
|
||||
import PositionType from '../../PositionType';
|
||||
|
||||
class TextPeer extends ElementPeer {
|
||||
private _position: { x: number; y: number };
|
||||
private _font: FontPeer;
|
||||
private _textAlign: any;
|
||||
private _text: string | undefined;
|
||||
private _textAlign: string;
|
||||
private _text: string;
|
||||
|
||||
constructor(fontPeer: FontPeer) {
|
||||
const svgElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
super(svgElement);
|
||||
this._position = { x: 0, y: 0 };
|
||||
this._font = fontPeer;
|
||||
this._text = '';
|
||||
this._textAlign = 'left';
|
||||
}
|
||||
|
||||
append(element: ElementPeer) {
|
||||
append(element: ElementPeer): void {
|
||||
this._native.appendChild(element._native);
|
||||
}
|
||||
|
||||
setTextAlignment(align: string) {
|
||||
setTextAlignment(align: string): void {
|
||||
this._textAlign = align;
|
||||
}
|
||||
|
||||
getTextAlignment() {
|
||||
return $defined(this._textAlign) ? this._textAlign : 'left';
|
||||
getTextAlignment(): 'left' | 'right' | 'center' {
|
||||
return this._textAlign ? (this._textAlign as 'left' | 'right' | 'center') : 'left';
|
||||
}
|
||||
|
||||
setText(text: string) {
|
||||
this._text = text;
|
||||
|
||||
// Remove all previous nodes ...
|
||||
while (this._native.firstChild) {
|
||||
this._native.removeChild(this._native.firstChild);
|
||||
}
|
||||
|
||||
this._text = text;
|
||||
if (text) {
|
||||
const lines = text.split('\n');
|
||||
lines.forEach((line) => {
|
||||
const tspan = window.document.createElementNS(ElementPeer.svgNamespace, 'tspan');
|
||||
tspan.setAttribute('dy', '1em');
|
||||
tspan.setAttribute('x', String(this.getPosition().x));
|
||||
// Add nodes ...
|
||||
this.getTextLines().forEach((l) => {
|
||||
// Append a new line ...
|
||||
const tspan = window.document.createElementNS(ElementPeer.svgNamespace, 'tspan');
|
||||
tspan.setAttribute('dy', '1em');
|
||||
tspan.setAttribute('x', this.getPosition().x.toFixed(1));
|
||||
|
||||
tspan.textContent = line.length === 0 ? ' ' : line;
|
||||
this._native.appendChild(tspan);
|
||||
});
|
||||
}
|
||||
// Add new line ...
|
||||
tspan.textContent = l || ' ';
|
||||
this._native.appendChild(tspan);
|
||||
});
|
||||
}
|
||||
|
||||
getText(): any {
|
||||
getTextLines(): string[] {
|
||||
const result: string[] = [];
|
||||
if (this._text) {
|
||||
const text = this._text;
|
||||
let line = '';
|
||||
let i = 0;
|
||||
do {
|
||||
const c = text[i];
|
||||
if (c === '\n' || i === text.length) {
|
||||
result.push(line);
|
||||
line = '';
|
||||
} else {
|
||||
line = line + c;
|
||||
}
|
||||
i = i + 1;
|
||||
} while (i < text.length + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getText(): string {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
@ -81,20 +105,20 @@ class TextPeer extends ElementPeer {
|
||||
});
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
getPosition(): PositionType {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
getNativePosition() {
|
||||
getNativePosition(): { left: number; top: number } {
|
||||
return getPosition(this._native);
|
||||
}
|
||||
|
||||
setFont(fontName: string, size: number, style: string, weight: string): void {
|
||||
if ($defined(fontName)) {
|
||||
if (fontName) {
|
||||
this._font = new FontPeer(fontName);
|
||||
}
|
||||
|
||||
if ($defined(style)) {
|
||||
if (style) {
|
||||
this._font.setStyle(style);
|
||||
}
|
||||
if ($defined(weight)) {
|
||||
@ -103,10 +127,10 @@ class TextPeer extends ElementPeer {
|
||||
if ($defined(size)) {
|
||||
this._font.setSize(size);
|
||||
}
|
||||
this._updateFontStyle();
|
||||
this.updateFontStyle();
|
||||
}
|
||||
|
||||
_updateFontStyle() {
|
||||
private updateFontStyle() {
|
||||
this._native.setAttribute('font-family', this._font.getFontName());
|
||||
this._native.setAttribute('font-size', this._font.getGraphSize());
|
||||
this._native.setAttribute('font-style', this._font.getStyle());
|
||||
@ -123,17 +147,17 @@ class TextPeer extends ElementPeer {
|
||||
|
||||
setTextSize(size: number) {
|
||||
this._font.setSize(size);
|
||||
this._updateFontStyle();
|
||||
this.updateFontStyle();
|
||||
}
|
||||
|
||||
setStyle(style: string) {
|
||||
this._font.setStyle(style);
|
||||
this._updateFontStyle();
|
||||
this.updateFontStyle();
|
||||
}
|
||||
|
||||
setWeight(weight: string) {
|
||||
this._font.setWeight(weight);
|
||||
this._updateFontStyle();
|
||||
this.updateFontStyle();
|
||||
}
|
||||
|
||||
setFontName(fontName: string): void {
|
||||
@ -142,7 +166,7 @@ class TextPeer extends ElementPeer {
|
||||
this._font.setSize(oldFont.getSize());
|
||||
this._font.setStyle(oldFont.getStyle());
|
||||
this._font.setWeight(oldFont.getWeight());
|
||||
this._updateFontStyle();
|
||||
this.updateFontStyle();
|
||||
}
|
||||
|
||||
getFontStyle(): FontStyle {
|
||||
@ -159,7 +183,7 @@ class TextPeer extends ElementPeer {
|
||||
|
||||
setFontSize(size: number): void {
|
||||
this._font.setSize(size);
|
||||
this._updateFontStyle();
|
||||
this.updateFontStyle();
|
||||
}
|
||||
|
||||
getShapeWidth(): number {
|
||||
|
Loading…
Reference in New Issue
Block a user