Word wrapping in JointJS - javascript

I am working on JointJS. I have various elements with text in it. However the element's width increases with increase in text. I want to dynamically set the size of element such that there is a maximum height and width that the box can attain and expands accordingly by text wrapping. If the text os unable to fit in the maximum height and width element, then the fontsize may be reduced dynamically.
I hav tried using style="word-wrap: break-word;" in my div id. However there is no effect.
<div id="myholder" style="word-wrap: break-word;"> </div>
My holder is defined in the JS file as follows:
var paper = new joint.dia.Paper({
el: $('#myholder'),
width: 1200,
height: 700,
model: graph
});
What strategy may I follow?

It is also possible (if you don't want to bother with extra shapes) to use the
joint.util.breakText()
utility. It works like this:
var wraptext = joint.util.breakText('My text here', {
width: holderElement.size.width,
height: optionalHeight
});
After that you can add wraptext to your holderElement as into attrs when creating it. Like this:
var holder = joint.shapes.basic.Rect({
//position, size blablabla
attrs: {
text: {
text: wraptext,
//text styling
}
}
});
I have to say it's a bit strange that your holder is an entire paper, but you can probably use it the same way, just put the attrs when you define it.

To get word wrap working you can use joint.shapes.basic.TextBlock.
Now, to work with TextBlock you are going to set a top level map entry for "content" (instead of including "text" inside of "attrs" => "text" map entry)
graph.addCell (
new joint.shapes.basic.TextBlock({
position: { x:100, y:100 },
size: { width: 100, height: 100 },
attrs: { rect: { fill: 'green' }},
content: "<p style='color:white;'>asdf asdf asdf asdf this needs to word wrap</p>"
})
);
As you can see, the "content" entry can be raw html and will be rendered as such.
For this to work your browser needs to have SVG ForeignObject support, which most browsers these days have. To first check that this is the case you can run this in your console:
document.implementation.hasFeature("w3.org/TR/SVG11/feature#Extensibility","1.1")

I made a javascript function to wrap words based on the Line size and Max size of the shape you want the sentence wrap in.
If the sentence is very long then the function trim it and put 3 duts instead of the rest of the sentence.
Every Line size of the sentence the function put a '\n' (newline ASCII).
var sentenceWrapped = function (sentence, lineSize, maxSize) {
var descriptionTrim = "";
if (sentence.length + 3 > maxSize) {
descriptionTrim = sentence.substring(0, maxSize - 3);
descriptionTrim = descriptionTrim + '...';
}
else {
descriptionTrim = sentence
}
var splitSentence = descriptionTrim.match(new RegExp('.{1,' + lineSize + '}', 'g'));
var sentenceWrapped = "";
for (i = 0; i < splitSentence.length; i++)
{
sentenceWrapped = sentenceWrapped + splitSentence[i] + '\n';
}
return sentenceWrapped;
}
LineSize = the max size of characters for every line you want inside
your shape
MaxSize = the max size of characters you want inside your
shape
sentence = description you want to put in your shape

If you are interested in creating custom element you can create like this
joint.shapes.devs.Model = joint.shapes.basic.TextBlock.extend( {
markup: ['>',
joint.env.test('svgforeignobject') ? '' : '',
''].join(''),
defaults: joint.util.deepSupplement({
content: 'A content of the Activity'
}});

Related

How do I get a DIV to follow a transform

Long story short, I'm trying to put text inside a box that is animated with javascript.
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(document.body);
const card = two.makeRoundedRectangle(230/2, two.height/2, 230, 130, 10);
const div = document.createElement("div"); // THIS IS THE IMPORTANT STUFF
const text = document.createTextNode("This is text."); //
div.appendChild(text); // THIS IS THE IMPORTANT STUFF
two.update(); // this needs to be here to add to the dom
card._renderer.elem.style.cssText = 'position:relative;width:230px;height:130px;'; // THIS IS THE IMPORTANT STUFF
div.style.cssText = 'position:absolute;width:200px;height:200px;'; //
card._renderer.elem.appendChild(div); // THIS IS THE IMPORTANT STUFF
two.bind('update', function (framNum)
{
card.translation.x += 1
if (card.translation.x > two.width - 230/2)
{
card.translation.x = 230/2
}
}).play()
<script src="https://two.js.org/third-party/two.js"></script>
here's what I've tried. I'm trying to put document.createTextNode("This is text."); inside a parent that is moving
I was expecting the to see the text, or even a div with the inspector, but its not highlighting.
Any help would be greatly appreciated
You need to use two's text functionality instead of using DOM related functions. It appears that in this context, two renders to an SVG format, thus adding HTML tags won't work.
var two = new Two({fullscreen: true, autostart: true}).appendTo(document.body);
const card = two.makeRoundedRectangle(230/2, two.height/2, 230, 130, 10);
var styles = { size: 24, family: 'Calibri' };
var text = new Two.Text("This is text.", 120, 100, styles);
var group = two.makeGroup(card, text);
two.update(); // this needs to be here to add to the dom
card._renderer.elem.style.cssText = 'position:relative;width:230px;height:130px;';
two.bind('update', function (framNum)
{
group.translation.x += 1
if (group.translation.x > two.width - 230/2)
{
group.translation.x = 230/2
}
}).play()
<script src="https://two.js.org/third-party/two.js"></script>

Fabric.js: How to keep text from blurring as group grows larger

In fabricjs, version 1.7.20, I noticed that whenever a group grows beyond 1152 pixels (12in) then the text objects in the group begin to blur. I'm programmatically setting the group size using the set() method when I need to grow the group.
Is there a way to keep them crisp/normal? Should I be calling some other method on the text object or group so that the text remains readable? I realize that the fonts are vectors but I'm not sure how to keep the font size constant regardless of the group size.
In this particular case I can't scale the group as I need the text object to stay the same size regardless of the group size.
Here is an example of the blurring effect:
https://jsfiddle.net/c7fn1e08/
Here is some example code as well:
var c = new fabric.Canvas('c');
var lbl = new fabric.Text('RESZING GROUP', { fontSize: 12 });
var g = new fabric.Group([lbl],{ top: 0, left: 0});
c.add(g);
c.renderAll();
var resizeGroup = function(g, sz) {
g.set({ height: fabric.util.parseUnit(sz), width: fabric.util.parseUnit(sz)});
var l = g._objects[0];
l.set({ top: -(g.height/2), left: -(g.width/2), text: 'GROUP SIZE '+sz}).setCoords();
g.set({ dirty: true });
g.canvas.renderAll();
}
//resizeGroup(g, '12in'); // looks good
//resizeGroup(g, '24in'); // looks bad
resizeGroup(g, '36in'); // looks really bad

Set width of ace editor instance according to the length of characters in it

I am working on the project where I have created a custom Rich Text Editor using contenteditable attribute. In this rich text editor I want insert single line ace editor instance of which width will be set according to the number of characters in it.
For restricting the ace editor instance to single line I have handled the "Enter" key event which does not let the ace instance to insert new line.
var editor = ace.edit(script_editor);
editor.commands.on("exec", function (e) {
editor.container.querySelector(".ace_content").style.transform = "none";
if (e.args && e.args.charCodeAt(0) == 10) {
e.preventDefault();
e.stopPropagation();
console.log("vdgscript-mode.js")
}
});
Now, the problem I am facing is that I want the ace instance width to adjust according to the number of character in it instead to have full width.
For that I am taking a canvas object and calculating the width of the text. But the problem with this code is, it is giving me the expected width on every key press but the css left property of the ace editor does not stay '0px' which makes the text in the ace editor instance to hide at the left side.
Code for setting the width is as follows:
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
ctx.font = "15px sans-serif";
var width = ctx.measureText(code).width;
editor.container.style.width = (width + 3) + "px";
Actual Result: .
Expected Result: .
The black in the below image the ace instance in which I have entered an array.
you can use a method similar to the one used by the tree rename editor in cloud9 https://github.com/c9/core/blob/master/plugins/node_modules/ace_tree/lib/ace_tree/edit.js
<style>
#inlineEditor {
display: inline-block;
vertical-align: middle;
}
</style>
<div>inline editor <span id=inlineEditor>txt</span></div>
<script src=https://ajaxorg.github.io/ace-builds/src-noconflict/ace.js>
</script>
<script>
var inlineEditor = ace.edit("inlineEditor", {
maxLines: 1,
showGutter: false,
showPrintMargin: false,
theme: "ace/theme/solarized_light",
})
// make cursor movement nicer for
inlineEditor.renderer.screenToTextCoordinates = function(x, y) {
var pos = this.pixelToScreenCoordinates(x, y);
return this.session.screenToDocumentPosition(
Math.min(this.session.getScreenLength() - 1, Math.max(pos.row, 0)),
Math.max(pos.column, 0)
);
};
inlineEditor.renderer.on("beforeRender", updateSize)
function updateSize(e, renderer) {
var text = renderer.session.getLine(0);
var chars = renderer.session.$getStringScreenWidth(text)[0];
var width = Math.max(chars, 2) * renderer.characterWidth // text size
+ 2 * renderer.$padding // padding
+ 2 // little extra for the cursor
+ 0 // add border width if needed
// update container size
renderer.container.style.width = width + "px";
// update computed size stored by the editor
renderer.onResize(false, 0, width, renderer.$size.height);
}
updateSize(null, inlineEditor.renderer)
</script>

How to update the x/y position of a popper.js element?

How can I move the popper.js element to follow specific coords?
I was able to get (I think) the caret position in a textarea but now I need to make Popper.js follow it.
I tried, update and onUpdate on the root and in the modifiers. I do not understand at all the documentation.
I created a codepen to show what I was able to achieve so far:
https://codepen.io/anon/pen/gzGvvG
const refEl = document.getElementById('ref');
const popEl = document.getElementById('pop');
new Popper(refEl, popEl, {
placement: 'auto',
modifiers: {
offset: {
enabled: true,
offset: '0,10'
},
flip: {
behavior: ['left', 'bottom', 'top']
},
preventOverflow: {
enabled: true,
padding: 10,
escapeWithReference: false,
}
},
});
document.getElementById("ref").onkeyup = function() {
var xy = getCursorXY(refEl, refEl.selectionEnd)
document.getElementById("log").innerText = `X: ${xy.x}, Y: ${xy.y}`;
}
The getcursorXY function I got from Medium: https://medium.com/#jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
It's not a complete answer to your question ... as it needs a few tweaks to get it to work like how I think you are trying to get it to work. But, here's javascript that i modified from your CodePen which is believe is a step in the right direction for what you are looking to achieve.
const refEl = document.getElementById('ref');
const popEl = document.getElementById('pop');
function update_popper(x, y) {
new Popper(refEl, popEl, {
placement: 'auto',
modifiers: {
offset: {
enabled: true,
offset: (x - 150) + ',' + (-1 * (y - 140))
},
flip: {
behavior: ['left', 'bottom', 'top']
},
preventOverflow: {
enabled: true,
padding: 10,
escapeWithReference: false,
}
},
});
}
document.getElementById("ref").onkeyup = function () {
var xy = getCursorXY(refEl, refEl.selectionEnd)
document.getElementById("log").innerText = `X: ${xy.x}, Y: ${xy.y}`;
update_popper(xy.x, xy.y);
}
const getCursorXY = (input, selectionPoint) => {
const {
offsetLeft: inputX,
offsetTop: inputY,
} = input
// create a dummy element that will be a clone of our input
const div = document.createElement('div')
// get the computed style of the input and clone it onto the dummy element
const copyStyle = getComputedStyle(input)
for (const prop of copyStyle) {
div.style[prop] = copyStyle[prop]
}
// we need a character that will replace whitespace when filling our dummy element if it's a single line <input/>
const swap = '.'
const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value
// set the div content to that of the textarea up until selection
const textContent = inputValue.substr(0, selectionPoint)
// set the text content of the dummy element div
div.textContent = textContent
if (input.tagName === 'TEXTAREA') div.style.height = 'auto'
// if a single line input then the div needs to be single line and not break out like a text area
if (input.tagName === 'INPUT') div.style.width = 'auto'
// create a marker element to obtain caret position
const span = document.createElement('span')
// give the span the textContent of remaining content so that the recreated dummy element is as close as possible
span.textContent = inputValue.substr(selectionPoint) || '.'
// append the span marker to the div
div.appendChild(span)
// append the dummy element to the body
document.body.appendChild(div)
// get the marker position, this is the caret position top and left relative to the input
const { offsetLeft: spanX, offsetTop: spanY } = span
// lastly, remove that dummy element
// NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered
document.body.removeChild(div)
// return an object with the x and y of the caret. account for input positioning so that you don't need to wrap the input
return {
x: inputX + spanX,
y: inputY + spanY,
}
}
what I have done here is to move your Popper() into a function called update popper, which will rebuild the popper new each time with a new x,y offset. You don't have to worry much about creating the new Popper() over and over, as it is mostly all just math, with very little weight.
Anyways, i think that should help get you closer to your goal .. good luck!

Size of character in pixels

I am trying to create text box in the HTML5 canvas, I know you can't actually do this, so I am creating a box and creating some text at the same location. But, I want to make sure the text stays in the box so I need to know when part of the text is extending out of the box. I figure I should be able to measure the text in terms of pixels and then compare it to the box. My question is, using javascript, how can I measure the size of the characters for any given font?
You can use context.measureText to get the width of your specified text:
// set the font
context.font = "14px verdana";
// use measureText to get the text width
var textWidth = context.measureText("Measure this!").width;
Text wrap would look something like this:
function wrapText(context, text, x, y, maxWidth, fontSizeFace) {
var words = text.split(' ');
var line = '';
var lineHeight=measureTextHeight(fontSizeFace);
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if(testWidth > maxWidth) {
context.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
context.fillText(line, x, y);
}
You can use as markE mention in his answer the measureText() function.
The specifications defines its result (a TextMetrix):
interface TextMetrics {
// x-direction
readonly attribute double width; // advance width
readonly attribute double actualBoundingBoxLeft;
readonly attribute double actualBoundingBoxRight;
// y-direction
readonly attribute double fontBoundingBoxAscent;
readonly attribute double fontBoundingBoxDescent;
readonly attribute double actualBoundingBoxAscent;
readonly attribute double actualBoundingBoxDescent;
readonly attribute double emHeightAscent;
readonly attribute double emHeightDescent;
readonly attribute double hangingBaseline;
readonly attribute double alphabeticBaseline;
readonly attribute double ideographicBaseline;
};
The problem however is that only width is implemented in the major browsers so you cannot get the height (ascent + descent) with this function yet (and I wouldn't be surprised if a canvas based word processor from at least one of the "major 3" shows up right before this gets fully implemented... but that's a regression and a speculation :-) ).
In order to measure the font you will have to use a DOM element, and this little trick will allow you to measure a font's width and height:
Online demo here (open console to see result).
function measureText(font, txt) {
var el = document.createElement('div'),
cs, res;
el.style.cssText = 'position:fixed;left:-4000px;top:-4000px;padding:0;margin:0;font:' + font;
el.innerHTML = txt;
document.body.appendChild(el);
cs = getComputedStyle(el);
res = {width: cs.getPropertyValue('width'),
height: cs.getPropertyValue('height')};
document.body.removeChild(el);
return res;
}
The function creates a div element, applies some basic styles to it to place it outside window. This is because we have to attach it to the DOM tree in order to use getComputedStyle() - we also have to get the property values before we remove the element again.
Pass arguments for font as you would with the context (ie. 20px sans-serif) and the text.
It comes with a small performance penalty obviously (though using fixed positioned elements won't cause any re-flow so it's not so bad) so use sparsely.

Categories

Resources