合理的段落

将文本呈现为合理的段落。需要示例 Justified 文本

示例渲染

StackOverflow 文档
顶部段落为 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 truefalse 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);

注意: 对于文本对齐 leftstart,段落的最后一行始终具有正常间距。对于所有其他对齐,最后一行与所有其他对齐一样。

注意: 你可以使用空格插入段落的开头。虽然从段落到段落可能不一致。了解函数正在做什么并对其进行修改总是一件好事。练习是为设置添加设置,将第一行缩进固定量。提示 while 循环将需要暂时使第一个单词显得更大(+缩进)words[0].width += ? 然后当渲染行缩进第一行时。