Thanks for your help and patience. All the best.
Hi! You must be tired of me.
But I found weird behavior of lineHeight.
These labels have the same lineHeight, but the one with the wider lineWidth has smaller text.
Also, maybe splitWords doesn't work too.
This is just a report.
You don't necessarily need to fix it, since I'm using custom code.
Have a good holiday! ![]()
<!-- zimjs.com - JavaScript Canvas Framework -->
<script type="module">
import zim from "https://zimjs.org/cdn/019/zim";
new Frame(FIT, 1920, 1080, light, dark, ready);
function ready() {
let A; let B; let C;
function test() {
if (A != null) {
A.removeFrom();
A = null;
}
if (B != null) {
B.removeFrom();
B = null;
}
let variableText = "I'm sorry to bother you again."
A = new Label({
text: variableText,
labelWidth: 1400,
labelHeight: 350,
align: "center",
valign: "middle",
splitWords: true,
font: "Yusei Magic",
}).center(S).mov(0, -250).outline();
A.lineHeight = A.size * 1.25
zog(A.lineHeight)
B = new Label({
text: variableText,
labelWidth: 1000,
labelHeight: 350,
align: "center",
valign: "middle",
splitWords: true,
font: "Yusei Magic"
}).center(S).mov(0, 250).outline();
B.lineHeight = B.size * 1.25
zog(B.lineHeight)
}
test()
}
</script>
<meta name="viewport" content="width=device-width, user-scalable=no" />
I think all is good.
When setting labelWidth and labelHeight, the font size is no longer used. It gets calculated by what fits in the labelWidth and labelHeight. The lineHeight does not change the font size directly, but it can as it changes the height of the text that fits inside the dimensions.
The reason why your second label looks like it is outside the outline is that the outline was made before applying the labelHeight. So use B.outline() after applying the labelHeight. An outline() is a snapshot in time.

And the splitWords only works on a word if that single word is wider than the lableWidth.

Oh, and just a couple code tips...

A.dispose() would be better than A.removeFrom(). If you are going to use it again, then use removeFrom(), if you want it gone out of memory, use dispose().
And .center(S) is not needed - just .center() will do that.
Cheers.
I just think the second one behaves better, because in the first one, the text suddenly becomes smaller once it reaches a certain width.
Sorry to interrupt you!
And thank you for your good tips!
<script type="module">
import zim from "https://zimjs.org/cdn/019/zim";
new Frame(FULL, null, null, light, dark, ready);
function ready() {
let A; let B; let C;
function test() {
if (A != null) {
A.removeFrom();
A = null;
}
if (B != null) {
B.removeFrom();
B = null;
}
let variableText = "γγγ«γ‘γ―γζγγΎγγ¦γγγ§γ¨γγγγγΎγγγγγ«γ‘γ―γζγγΎγγ¦γγγ§γ¨γγγγγΎγγγγγ«γ‘γ―γζγγΎγγ¦γγγ§γ¨γγγγγΎγγ"
A = new Label({
text: variableText,
labelWidth: W * 0.8,
labelHeight: 300,
align: "center",
valign: "middle",
splitWords: true,
font: "Yusei Magic",
}).center(S).mov(0, -250);
A.lineHeight = A.size * 1.25
B = generateFittedText({
text: variableText,
maxWidth: W * 0.8,
maxHeight: 300,
textAlign: "center",
splitByChar: true,
fontName: "Yusei Magic",
lineHeightRatio: 1.5
}).center(S).mov(0, 250);
}
test()
F.on("resize", () => {
test()
});
function generateFittedText({
text = "Text",
maxWidth = 600,
maxHeight = 600,
fontName,
color,
lineHeightRatio = 1.25,
textAlign = "center",
splitByChar = true, // Set to false for English (word wrap), true for CJK (char wrap)
maxFontSize = 80,
minFontSize = 10
}) {
// Initialize the Zim Label
const label = new Label({
text: "",
size: maxFontSize,
font: fontName,
color: color,
align: textAlign,
valign: "middle"
});
// Access the underlying CreateJS Text object for precise alignment
const internalText = label.label;
internalText.textAlign = textAlign;
internalText.textBaseline = "middle";
// Invisible text object used purely for measuring width
const measureTool = new createjs.Text("", "", color);
/**
* wrapByChar: Wraps text character by character (Good for CJK or breaking long strings)
*/
function wrapByChar(measureObj, rawString, limitWidth) {
const lines = [];
let currentLine = "";
for (let i = 0; i < rawString.length; i++) {
const ch = rawString[i];
measureObj.text = currentLine + ch;
if (currentLine !== "" && measureObj.getMeasuredWidth() > limitWidth) {
lines.push(currentLine);
currentLine = ch;
} else {
currentLine += ch;
}
}
if (currentLine !== "") lines.push(currentLine);
return lines;
}
/**
* wrapByWord: Wraps text by words (Good for English/Latin languages)
* Falls back to wrapByChar if a single word is wider than the container.
*/
function wrapByWord(measureObj, rawString, limitWidth) {
// Split by words, keeping trailing spaces
const tokens = rawString.match(/\S+\s*/g) || [""];
const lines = [];
let currentLine = "";
for (const tokenRaw of tokens) {
const token = tokenRaw;
measureObj.text = currentLine + token;
// Check if adding the next token exceeds width
if (currentLine !== "" && measureObj.getMeasuredWidth() > limitWidth) {
// Push the current line (remove trailing whitespace)
lines.push(currentLine.replace(/\s+$/g, ""));
// Handle the next token (remove leading whitespace for the new line)
let nextToken = token.replace(/^\s+/g, "");
measureObj.text = nextToken;
// Edge Case: If the single word is still too wide for the box
if (nextToken !== "" && measureObj.getMeasuredWidth() > limitWidth) {
// Force split the long word by character
const charLines = wrapByChar(measureObj, nextToken, limitWidth);
if (charLines.length > 1) {
lines.push(...charLines.slice(0, -1));
currentLine = charLines[charLines.length - 1] || "";
} else {
currentLine = charLines[0] || "";
}
} else {
currentLine = nextToken;
}
} else {
currentLine += token;
}
}
if (currentLine !== "") lines.push(currentLine.replace(/\s+$/g, ""));
return lines;
}
/**
* calculateLayout: Handles paragraph splitting (\n) and calls the appropriate wrapper
*/
function calculateLayout(measureObj, rawString, limitWidth, rowHeight, isCharSplit) {
const paragraphs = rawString.split('\n');
let allLines = [];
for (const paragraph of paragraphs) {
if (paragraph === "") {
allLines.push("");
continue;
}
const lines = isCharSplit
? wrapByChar(measureObj, paragraph, limitWidth)
: wrapByWord(measureObj, paragraph, limitWidth);
allLines.push(...lines);
}
return {
wrappedString: allLines.join("\n"),
totalHeight: allLines.length * rowHeight
};
}
// --- Main Loop: Try sizes from Max down to Min ---
for (let size = maxFontSize; size >= minFontSize; size--) {
const fontString = `${size}px ${fontName}`;
const rowHeight = size * lineHeightRatio;
measureTool.font = fontString;
const result = calculateLayout(measureTool, text, maxWidth, rowHeight, splitByChar);
// If it fits vertically, apply and return
if (result.totalHeight <= maxHeight) {
internalText.font = fontString;
internalText.lineHeight = rowHeight;
label.text = result.wrappedString;
// Horizontal Alignment Positioning
if (textAlign === "center") {
label.x = maxWidth / 2;
} else if (textAlign === "right") {
label.x = maxWidth;
} else {
label.x = 0;
}
// Vertical Center Alignment
label.y = (maxHeight - result.totalHeight) / 2;
return label;
}
}
// --- Fallback: If it still doesn't fit at minFontSize ---
// Apply the minimum font size and force wrap
internalText.font = `${minFontSize}px ${fontName}`;
internalText.lineHeight = minFontSize * lineHeightRatio;
measureTool.font = `${minFontSize}px ${fontName}`;
const finalResult = calculateLayout(measureTool, text, maxWidth, minFontSize * lineHeightRatio, splitByChar);
label.text = finalResult.wrappedString;
if (textAlign === "center") {
label.x = maxWidth / 2;
} else if (textAlign === "right") {
label.x = maxWidth;
} else {
label.x = 0;
}
// Top align if overflowing, or set to 0
label.y = 0;
return label;
}
}
</script>
<meta name="viewport" content="width=device-width, user-scalable=no" />
Will have a study, thanks.
