合理的段落
将文本呈现为合理的段落。需要示例 Justified 文本
示例渲染
顶部段落为 setting.compact = true ,底部为 false ,行间距为 1.2 而不是默认值 1.5 。通过此示例的代码用法示例底部呈现。
示例代码
// Requires justified text extensions
(function(){
// code point A
if(typeof CanvasRenderingContext2D.prototype.fillJustifyText !== "function"){
throw new ReferenceError("Justified Paragraph extension missing requiered CanvasRenderingContext2D justified text extension");
}
var maxSpaceSize = 3; // Multiplier for max space size. If greater then no justificatoin applied
var minSpaceSize = 0.5; // Multiplier for minimum space size
var compact = true; // if true then try and fit as many words as possible. If false then try to get the spacing as close as possible to normal
var lineSpacing = 1.5; // space between lines
const noJustifySetting = { // This setting forces justified text off. Used to render last line of paragraph.
minSpaceSize : 1,
maxSpaceSize : 1,
}
// Parse vet and set settings object.
var justifiedTextSettings = function(settings){
var min, max;
var vetNumber = (num, defaultNum) => {
num = num !== null && num !== null && !isNaN(num) ? num : defaultNum;
return num < 0 ? defaultNum : num;
}
if(settings === undefined || settings === null){ return; }
compact = settings.compact === true ? true : settings.compact === false ? false : compact;
max = vetNumber(settings.maxSpaceSize, maxSpaceSize);
min = vetNumber(settings.minSpaceSize, minSpaceSize);
lineSpacing = vetNumber(settings.lineSpacing, lineSpacing);
if(min > max){ return; }
minSpaceSize = min;
maxSpaceSize = max;
}
var getFontSize = function(font){ // get the font size.
var numFind = /[0-9]+/;
var number = numFind.exec(font)[0];
if(isNaN(number)){
throw new ReferenceError("justifiedPar Cant find font size");
}
return Number(number);
}
function justifiedPar(ctx, text, x, y, width, settings, stroke){
var spaceWidth, minS, maxS, words, count, lines, lineWidth, lastLineWidth, lastSize, i, renderer, fontSize, adjSpace, spaces, word, lineWords, lineFound;
spaceWidth = ctx.measureText(" ").width;
minS = spaceWidth * minSpaceSize;
maxS = spaceWidth * maxSpaceSize;
words = text.split(" ").map(word => { // measure all words.
var w = ctx.measureText(word).width;
return {
width : w,
word : word,
};
});
// count = num words, spaces = number spaces, spaceWidth normal space size
// adjSpace new space size >= min size. useSize Resulting space size used to render
count = 0;
lines = [];
// create lines by shifting words from the words array until the spacing is optimal. If compact
// true then will true and fit as many words as possible. Else it will try and get the spacing as
// close as possible to the normal spacing
while(words.length > 0){
lastLineWidth = 0;
lastSize = -1;
lineFound = false;
// each line must have at least one word.
word = words.shift();
lineWidth = word.width;
lineWords = [word.word];
count = 0;
while(lineWidth < width && words.length > 0){ // Add words to line
word = words.shift();
lineWidth += word.width;
lineWords.push(word.word);
count += 1;
spaces = count - 1;
adjSpace = (width - lineWidth) / spaces;
if(minS > adjSpace){ // if spacing less than min remove last word and finish line
lineFound = true;
words.unshift(word);
lineWords.pop();
}else{
if(!compact){ // if compact mode
if(adjSpace < spaceWidth){ // if less than normal space width
if(lastSize === -1){
lastSize = adjSpace;
}
// check if with last word on if its closer to space width
if(Math.abs(spaceWidth - adjSpace) < Math.abs(spaceWidth - lastSize)){
lineFound = true; // yes keep it
}else{
words.unshift(word); // no better fit if last word removes
lineWords.pop();
lineFound = true;
}
}
}
}
lastSize = adjSpace; // remember spacing
}
lines.push(lineWords.join(" ")); // and the line
}
// lines have been worked out get font size, render, and render all the lines. last
// line may need to be rendered as normal so it is outside the loop.
fontSize = getFontSize(ctx.font);
renderer = stroke === true ? ctx.strokeJustifyText.bind(ctx) : ctx.fillJustifyText.bind(ctx);
for(i = 0; i < lines.length - 1; i ++){
renderer(lines[i], x, y, width, settings);
y += lineSpacing * fontSize;
}
if(lines.length > 0){ // last line if left or start aligned for no justify
if(ctx.textAlign === "left" || ctx.textAlign === "start"){
renderer(lines[lines.length - 1], x, y, width, noJustifySetting);
ctx.measureJustifiedText("", width, settings);
}else{
renderer(lines[lines.length - 1], x, y, width);
}
}
// return details about the paragraph.
y += lineSpacing * fontSize;
return {
nextLine : y,
fontSize : fontSize,
lineHeight : lineSpacing * fontSize,
};
}
// define fill
var fillParagraphText = function(text, x, y, width, settings){
justifiedTextSettings(settings);
settings = {
minSpaceSize : minSpaceSize,
maxSpaceSize : maxSpaceSize,
};
return justifiedPar(this, text, x, y, width, settings);
}
// define stroke
var strokeParagraphText = function(text, x, y, width, settings){
justifiedTextSettings(settings);
settings = {
minSpaceSize : minSpaceSize,
maxSpaceSize : maxSpaceSize,
};
return justifiedPar(this, text, x, y, width, settings,true);
}
CanvasRenderingContext2D.prototype.fillParaText = fillParagraphText;
CanvasRenderingContext2D.prototype.strokeParaText = strokeParagraphText;
})();
注意这扩展了
CanvasRenderingContext2D
原型。如果你不希望发生这种情况,请使用示例 Justified 文本来确定如何将此示例更改为全局命名空间的一部分。
注意如果此示例找不到函数
CanvasRenderingContext2D.prototype.fillJustifyText
,则抛出 ReferenceError
如何使用
ctx.fillParaText(text, x, y, width, [settings]);
ctx.strokeParaText(text, x, y, width, [settings]);
有关参数的详细信息,请参阅有问题的文本。[
和 ]
之间的参数是可选的。
settings
参数有两个附加属性。
- 紧凑: 默认
true
。如果为 true,则尝试每行包含尽可能多的单词。如果为 false,则尝试使字间距尽可能接近正常间距。 - lineSpacing 默认
1.5
。每行空格默认1.5
字体大小的在线距离
设置对象中缺少的属性将默认为其默认值或上一个有效值。只有在新值有效时才会更改属性。对于 compact
,有效值仅为 booleans true
或 false
Truthy 值不被视为有效。
返回对象
这两个函数返回一个包含信息的对象,以帮助你放置下一个段落。该对象包含以下属性。
- nextLine 段落像素后的下一行的位置。
- fontSize 字体的大小。 (请注意仅使用以像素为单位定义的字体,例如
14px arial
) - lineHeight 从一行到下一行的像素距离
此示例使用一个简单的算法,该算法一次处理一行,以找到最适合段落的行。这并不意味着它最适合(而不是算法的最佳)你可能希望通过在生成的线上创建多传递线算法来改进算法。将单词从一行的结尾移动到下一行的开头,或从开始的结尾移动到结尾。当整个段落上的间距具有最小的变化并且最接近正常文本间距时,可以获得最佳外观。
由于此示例依赖于 Justified 文本示例,因此代码非常相似。你可能希望将这两个功能移动到一个功能中。将另一个示例中的函数 justifiedTextSettings
替换为本示例中使用的函数。然后将此示例中的所有其余代码复制到 Justified 文本示例的匿名函数体中。你将不再需要测试在// Code point A
找到的依赖项。它可以被删除。
用法示例
ctx.font = "25px arial";
ctx.textAlign = "center"
var left = 10;
var center = canvas.width / 2;
var width = canvas.width-left*2;
var y = 20;
var size = 16;
var i = 0;
ctx.fillText("Justified paragraph examples.",center,y);
y+= 30;
ctx.font = "14px arial";
ctx.textAlign = "left"
// set para settings
var setting = {
maxSpaceSize : 6,
minSpaceSize : 0.5,
lineSpacing : 1.2,
compact : true,
}
// Show the left and right bounds.
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(left,y - size * 2);
ctx.lineTo(left, y + size * 15);
ctx.moveTo(canvas.width - left,y - size * 2);
ctx.lineTo(canvas.width - left, y + size * 15);
ctx.stroke();
ctx.textAlign = "left";
ctx.fillStyle = "black";
// Draw paragraph
var line = ctx.fillParaText(para, left, y, width, setting); // settings is remembered
// Next paragraph
y = line.nextLine + line.lineHeight;
setting.compact = false;
ctx.fillParaText(para, left, y, width, setting);
注意: 对于文本对齐
left
或start
,段落的最后一行始终具有正常间距。对于所有其他对齐,最后一行与所有其他对齐一样。
注意: 你可以使用空格插入段落的开头。虽然从段落到段落可能不一致。了解函数正在做什么并对其进行修改总是一件好事。练习是为设置添加设置,将第一行缩进固定量。提示 while 循环将需要暂时使第一个单词显得更大(+缩进)
words[0].width += ?
然后当渲染行缩进第一行时。