Hi, I have a question about TexttoPoint() in p5.js.

Is there a way to set/limit the number of points that draw the letter?

For example, I’d like to draw both letters ‘A’ and ‘B’ only with 10 points but not sure how… (Using the option sampleFactor creates points based on path-length so ‘A’ and ‘B’ would different number of points). Or would there be a way to do this using something other than TexttoPoint()?

I’d really appreciate any help!

Hi, thanks for the reply but these options don’t achieve what I asked, or am I not understanding correctly? I’m new to programming so I’d appreciate if you could clarify?

``````pts = caslon.textToPoints('NYTimes', 0, 0, 120,{
sampleFactor: 0.1, //the higher the number, the more detail
simplifyThreshold: 0
});
``````

code reference (unmodified code)
https://editor.p5js.org/aferriss/sketches/B1BOfBdZX

Yes, I get that but ‘N’ and ‘Y’ from ‘NYTimes’ will still have different number of points, because sampleFactor changes the distance between the points rather than the number of points.

For example, I have ‘0’ and ‘4’ but they are drawn with different number of ellipses. How can I make sure both have the same number of ellipses? Please let me know if my question is unclear…

Hi,

You have two possible solutions :

• Take an arbitrary large number for the `sampleFactor` parameter like `100` for both letters and take the quantity you want out of those arrays :

``````// The textToPoints options
const options = {
sampleFactor: 100,
simplifyThreshold: 0
}

// The target number of points
const nPoints = 20;

// Use slice to get nPoints first elements of the array
const zero = font.textToPoints("0", 0, 0, 10, options).slice(0, nPoints);
const four = font.textToPoints("4", 0, 0, 10, options).slice(0, nPoints);

``````

I didn’t test it but it should work. The issue is that you need to compute a lot of points to maybe take 1/10 of it so it’s computationally ineffective.

• Otherwise look at the source code and try to implement your own function :

``````p5.Font.prototype.textToPoints = function(txt, x, y, fontSize, options) {
let xoff = 0;
const result = [];
const glyphs = this._getGlyphs(txt);

function isSpace(i) {
return (
(glyphs[i].name && glyphs[i].name === 'space') ||
(txt.length === glyphs.length && txt[i] === ' ') ||
(glyphs[i].index && glyphs[i].index === 3)
);
}

fontSize = fontSize || this.parent._renderer._textSize;

for (let i = 0; i < glyphs.length; i++) {
if (!isSpace(i)) {
// fix to #1817, #2069

const gpath = glyphs[i].getPath(x, y, fontSize),
paths = splitPaths(gpath.commands);

for (let j = 0; j < paths.length; j++) {
const pts = pathToPoints(paths[j], options);

for (let k = 0; k < pts.length; k++) {
pts[k].x += xoff;
result.push(pts[k]);
}
}
}

}

return result;
};
``````

Hi, Thank you for the reply! I tried the first code but nothing was showing up. Could it be that I have integrated it incorrectly to my code?

``````let grotesk;
let fontSize = 400;

let zeroArray;
let fourArray;

}

function setup() {
createCanvas(windowWidth,windowHeight);
textFont(grotesk);

const options = {
sampleFactor: 100,
simplifyThreshold: 0
}
const nPoints = 20;

zeroArray = grotesk.textToPoints("0",100,300,options).slice(0, nPoints);
fourArray = grotesk.textToPoints("4",500,300,options).slice(0, nPoints);
}

function draw() {
background(220);
textSize (fontSize);
noStroke();

zero();

}

function zero() {

for (let i = 0; i < zeroArray.length; i++){
fill (random(255));
ellipse(zeroArray[i].x,zeroArray[i].y,5,5);
}

for (let i = 0; i < fourArray.length; i++){
ellipse(fourArray[i].x,fourArray[i].y,5,5);
fill (random(20));
}

}
``````

Ok my bad, this is not the right solution but few things first :

• Be careful, you forgot the `fontSize` parameter in `textToPoints(txt, x, y, fontSize, [options])` so it wouldn’t work anyway.

• Calling `textSize(fontSize);` in the `draw()` function doesn’t affect the locations of the points generated by the `textToPoints` functions since we pass the font size in the parameters.

• If you intent is to draw points rather than circles or ellipses, I prefer using the `point()` function since it’s more clear and faster if you don’t need stroke.

So the code might go like this :

``````let grotesk;
const fontSize = 200;
const nPoints = 20;

let zeroArray;
let fourArray;

}

function setup() {
createCanvas(windowWidth, windowHeight);

textFont(grotesk);

const options = {
sampleFactor: 20,
simplifyThreshold: 0
}

zeroArray = grotesk.textToPoints("0", 100, 300, fontSize, options);
fourArray = grotesk.textToPoints("4", 500, 300, fontSize, options);

console.log(zeroArray.length);
console.log(fourArray.length);

zeroArray = zeroArray.slice(0, nPoints);
fourArray = fourArray.slice(0, nPoints);
}

function draw() {
background(220);

zero();

noLoop();
}

function zero() {
strokeWeight(5);

for (let i = 0; i < zeroArray.length; i++) {
const pt = zeroArray[i];

stroke(random(255));
point(pt.x, pt.y);
}

for (let i = 0; i < fourArray.length; i++) {
const pt = fourArray[i];

stroke(random(20));
point(pt.x, pt.y);
}
}
``````

As you can see, I printed the number of points generated by the `textToPoints` function. With a fontSize of `40` this is the result :

``````zeroArray.length = 2953
fourArray.length = 3402
``````

This is quite large! (`100` is too high for the detail)
And the output on the screen only shows two points at the same location.

But my solution is not right because if you take the first `20` points of an array of `2953`, you only get the beginning of the contour of the letter. Because the number of points is large, we only get a tiny portion of the first points.

So the way to go is to take 20 points at equal interval in the array of points :

``````/**
* Take nElements from array at constant interval
*/
function sampleFromArray(array, nElements) {
const result = [];
const increment = Math.ceil(array.length / nElements);

for (let i = 0; i < array.length; i += increment) {
result.push(array[i]);
}

return result;
}
``````

So we are an array and a number of elements to sample and we first compute the increment between each elements in the array, for example :

``````array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // length 10
nElements = 3;

increment = Math.ceil(10 / 3) = Math.ceil(3.333333...) = 4

// So we take
array[0], array[4], array[8]
``````

We use `Math.ceil()` because it takes the lowest integer superior or equal to a certain number. But we could also use `Math.floor()` or `Math.round()`.

Note that the function is not really exact since if we wanted to take `7` elements from that array, the increment would be `Math.ceil(10 / 7) = 2` so the result wouldbe an array of `8` elements.

It doesn’t matter since we are dealing with large arrays anyway…

So now you can call :

``````zeroArray = sampleFromArray(zeroArray, nPoints);
fourArray = sampleFromArray(fourArray, nPoints);
``````