合理的段落
將文字呈現為合理的段落。需要示例 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 += ?
然後當渲染行縮排第一行時。