Sök…
Ritningstext
Ritning till duk är inte bara begränsad till former och bilder. Du kan också rita text till duken.
För att rita text på duken, få en hänvisning till duken och ring fillText
metoden fillText
i sammanhanget.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.fillText("My text", 0, 0);
De tre nödvändiga argumenten som skickas till fillText
är:
- Texten som du vill visa
- Den horisontella positionen (x-axel)
- Den vertikala positionen (y-axeln)
Dessutom finns det ett fjärde valfritt argument, som du kan använda för att ange den maximala bredden på din text i pixlar. I exemplet nedan begränsar värdet 200
textens maximala bredd till 200px:
ctx.fillText("My text", 0, 0, 200);
Resultat:
Du kan också rita text utan fyllning och bara en kontur istället med hjälp av metoden strokeText
:
ctx.strokeText("My text", 0, 0);
Resultat:
Utan några tillämpade teckensnittformateringsegenskaper, gör duken text på 10px i sans-serif som standard, vilket gör det svårt att se skillnaden mellan resultatet av fillText
och strokeText
. Se Exempel på formateringstext för detaljer om hur du ökar textstorleken och tillämpar andra estetiska ändringar på texten.
Formatera text
Standardformatformatformatet som tillhandahålls med fillText
och strokeText
är inte mycket estetiskt tilltalande. Lyckligtvis innehåller canvas API egenskaper för formatering av text.
Med hjälp av font
du ange:
- typsnitt
- font-variant
- font-weight
- typstorlek / linjehöjd
- typsnittsfamilj
Till exempel:
ctx.font = "italic small-caps bold 40px Helvetica, Arial, sans-serif";
ctx.fillText("My text", 20, 50);
Resultat:
Med hjälp av egenskapen textAlign
du också ändra textjustering till endera:
- vänster
- Centrum
- rätt
- slut (samma som höger)
- start (samma som vänster)
Till exempel:
ctx.textAlign = "center";
Inpackning av text i stycken
Native Canvas API har ingen metod för att radera text på nästa rad när en önskad maximal bredd uppnås. Detta exempel lindar text i stycken.
function wrapText(text, x, y, maxWidth, fontSize, fontFace){
var firstY=y;
var words = text.split(' ');
var line = '';
var lineHeight=fontSize*1.286; // a good approx for 10-18px sizes
ctx.font=fontSize+" "+fontFace;
ctx.textBaseline='top';
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if(testWidth > maxWidth) {
ctx.fillText(line, x, y);
if(n<words.length-1){
line = words[n] + ' ';
y += lineHeight;
}
}
else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}
Rita textavsnitt i oregelbundna former
Det här exemplet drar textparagrafer i alla delar av duken som har ogenomskinliga pixlar.
Det fungerar genom att hitta nästa block med ogenomskinliga pixlar som är tillräckligt stora för att innehålla nästa specificerade ord och fylla det blocket med det angivna ordet.
De ogenomskinliga pixlarna kan komma från valfri källa: Ritningskommandon för banor och / eller bilder.
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; padding:10px; }
#canvas{border:1px solid red;}
</style>
<script>
window.onload=(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var fontsize=12;
var fontface='verdana';
var lineHeight=parseInt(fontsize*1.286);
var text='It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way; in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.';
var words=text.split(' ');
var wordWidths=[];
ctx.font=fontsize+'px '+fontface;
for(var i=0;i<words.length;i++){ wordWidths.push(ctx.measureText(words[i]).width); }
var spaceWidth=ctx.measureText(' ').width;
var wordIndex=0
var data=[];
// Demo: draw Heart
// Note: the shape can be ANY opaque drawing -- even an image
ctx.scale(3,3);
ctx.beginPath();
ctx.moveTo(75,40);
ctx.bezierCurveTo(75,37,70,25,50,25);
ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
ctx.bezierCurveTo(20,80,40,102,75,120);
ctx.bezierCurveTo(110,102,130,80,130,62.5);
ctx.bezierCurveTo(130,62.5,130,25,100,25);
ctx.bezierCurveTo(85,25,75,37,75,40);
ctx.fillStyle='red';
ctx.fill();
ctx.setTransform(1,0,0,1,0,0);
// fill heart with text
ctx.fillStyle='white';
var imgDataData=ctx.getImageData(0,0,cw,ch).data;
for(var i=0;i<imgDataData.length;i+=4){
data.push(imgDataData[i+3]);
}
placeWords();
// draw words sequentially into next available block of
// available opaque pixels
function placeWords(){
var sx=0;
var sy=0;
var y=0;
var wordIndex=0;
ctx.textBaseline='top';
while(y<ch && wordIndex<words.length){
sx=0;
sy=y;
var startingIndex=wordIndex;
while(sx<cw && wordIndex<words.length){
var x=getRect(sx,sy,lineHeight);
var available=x-sx;
var spacer=spaceWidth; // spacer=0 to have no left margin
var w=spacer+wordWidths[wordIndex];
while(available>=w){
ctx.fillText(words[wordIndex],spacer+sx,sy);
sx+=w;
available-=w;
spacer=spaceWidth;
wordIndex++;
w=spacer+wordWidths[wordIndex];
}
sx=x+1;
}
y=(wordIndex>startingIndex)?y+lineHeight:y+1;
}
}
// find a rectangular block of opaque pixels
function getRect(sx,sy,height){
var x=sx;
var y=sy;
var ok=true;
while(ok){
if(data[y*cw+x]<250){ok=false;}
y++;
if(y>=sy+height){
y=sy;
x++;
if(x>=cw){ok=false;}
}
}
return(x);
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Note: the shape must be closed and alpha>=250 inside</h4>
<canvas id="canvas" width=400 height=400></canvas>
</body>
</html>
Fyll text med en bild
Detta exempel fyller text med en specificerad bild.
Viktig! Den angivna bilden måste laddas fullständigt innan den här funktionen anropas, annars tecknas inte ritningen. Använd image.onload
att vara säker på att bilden är fulladdad.
function drawImageInsideText(canvas,x,y,img,text,font){
var c=canvas.cloneNode();
var ctx=c.getContext('2d');
ctx.font=font;
ctx.fillText(text,x,y);
ctx.globalCompositeOperation='source-atop';
ctx.drawImage(img,0,0);
canvas.getContext('2d').drawImage(c,0,0);
}
Framför text längs en båge.
Det här exemplet visar hur man återger text längs en båge. Det inkluderar hur du kan lägga till funktionalitet till CanvasRenderingContext2D
genom att utöka prototypen.
Exemplen härrör från svaret med övergångsflödet cirkulär text .
Exempel rendering
Exempelkod
Exemplet lägger till tre nya textåtergivningsfunktioner till 2D-kontextprototypen.
- ctx.fillCircleText (text, x, y, radie, start, slut, framåt);
- ctx.strokeCircleText (text, x, y, radie, start, slut, framåt);
- ctx.measureCircleText (text, radie);
(function(){
const FILL = 0; // const to indicate filltext render
const STROKE = 1;
var renderType = FILL; // used internal to set fill or stroke text
const multiplyCurrentTransform = true; // if true Use current transform when rendering
// if false use absolute coordinates which is a little quicker
// after render the currentTransform is restored to default transform
// measure circle text
// ctx: canvas context
// text: string of text to measure
// r: radius in pixels
//
// returns the size metrics of the text
//
// width: Pixel width of text
// angularWidth : angular width of text in radians
// pixelAngularSize : angular width of a pixel in radians
var measure = function(ctx, text, radius){
var textWidth = ctx.measureText(text).width; // get the width of all the text
return {
width : textWidth,
angularWidth : (1 / radius) * textWidth,
pixelAngularSize : 1 / radius
};
}
// displays text along a circle
// ctx: canvas context
// text: string of text to measure
// x,y: position of circle center
// r: radius of circle in pixels
// start: angle in radians to start.
// [end]: optional. If included text align is ignored and the text is
// scaled to fit between start and end;
// [forward]: optional default true. if true text direction is forwards, if false direction is backward
var circleText = function (ctx, text, x, y, radius, start, end, forward) {
var i, textWidth, pA, pAS, a, aw, wScale, aligned, dir, fontSize;
if(text.trim() === "" || ctx.globalAlpha === 0){ // dont render empty string or transparent
return;
}
if(isNaN(x) || isNaN(y) || isNaN(radius) || isNaN(start) || (end !== undefined && end !== null && isNaN(end))){ //
throw TypeError("circle text arguments requires a number for x,y, radius, start, and end.")
}
aligned = ctx.textAlign; // save the current textAlign so that it can be restored at end
dir = forward ? 1 : forward === false ? -1 : 1; // set dir if not true or false set forward as true
pAS = 1 / radius; // get the angular size of a pixel in radians
textWidth = ctx.measureText(text).width; // get the width of all the text
if (end !== undefined && end !== null) { // if end is supplied then fit text between start and end
pA = ((end - start) / textWidth) * dir;
wScale = (pA / pAS) * dir;
} else { // if no end is supplied correct start and end for alignment
// if forward is not given then swap top of circle text to read the correct direction
if(forward === null || forward === undefined){
if(((start % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2) > Math.PI){
dir = -1;
}
}
pA = -pAS * dir ;
wScale = -1 * dir;
switch (aligned) {
case "center": // if centered move around half width
start -= (pA * textWidth )/2;
end = start + pA * textWidth;
break;
case "right":// intentionally falls through to case "end"
case "end":
end = start;
start -= pA * textWidth;
break;
case "left": // intentionally falls through to case "start"
case "start":
end = start + pA * textWidth;
}
}
ctx.textAlign = "center"; // align for rendering
a = start; // set the start angle
for (var i = 0; i < text.length; i += 1) { // for each character
aw = ctx.measureText(text[i]).width * pA; // get the angular width of the text
var xDx = Math.cos(a + aw / 2); // get the yAxies vector from the center x,y out
var xDy = Math.sin(a + aw / 2);
if(multiplyCurrentTransform){ // transform multiplying current transform
ctx.save();
if (xDy < 0) { // is the text upside down. If it is flip it
ctx.transform(-xDy * wScale, xDx * wScale, -xDx, -xDy, xDx * radius + x, xDy * radius + y);
} else {
ctx.transform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y);
}
}else{
if (xDy < 0) { // is the text upside down. If it is flip it
ctx.setTransform(-xDy * wScale, xDx * wScale, -xDx, -xDy, xDx * radius + x, xDy * radius + y);
} else {
ctx.setTransform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y);
}
}
if(renderType === FILL){
ctx.fillText(text[i], 0, 0); // render the character
}else{
ctx.strokeText(text[i], 0, 0); // render the character
}
if(multiplyCurrentTransform){ // restore current transform
ctx.restore();
}
a += aw; // step to the next angle
}
// all done clean up.
if(!multiplyCurrentTransform){
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore the transform
}
ctx.textAlign = aligned; // restore the text alignment
}
// define fill text
var fillCircleText = function(text, x, y, radius, start, end, forward){
renderType = FILL;
circleText(this, text, x, y, radius, start, end, forward);
}
// define stroke text
var strokeCircleText = function(text, x, y, radius, start, end, forward){
renderType = STROKE;
circleText(this, text, x, y, radius, start, end, forward);
}
// define measure text
var measureCircleTextExt = function(text,radius){
return measure(this, text, radius);
}
// set the prototypes
CanvasRenderingContext2D.prototype.fillCircleText = fillCircleText;
CanvasRenderingContext2D.prototype.strokeCircleText = strokeCircleText;
CanvasRenderingContext2D.prototype.measureCircleText = measureCircleTextExt;
})();
Funktionsbeskrivningar
I det här exemplet läggs 3 funktioner till CanvasRenderingContext2D prototype
. fillCircleText
, strokeCircleText
och measureCircleText
CanvasRenderingContext2D.fillCircleText (text, x, y, radie, start, [slut, [framåt]]);
CanvasRenderingContext2D.strokeCircleText (text, x, y, radie, start, [slut, [framåt]]);
- text: Text som ska visas som sträng.
- x , y : Cirkelcentrets placering som siffror.
- radie: radie av cirkel i pixlar
- start: vinkel i radianer för att starta.
- [slut]: valfritt. Om ingår
ctx.textAlign
ignoreras och texten skalas för att passa mellan start och slut. - [framåt]: valfritt standard "sant". om sann textriktning är framåt, om 'falsk' riktning är bakåt.
Båda funktionerna använder textBaseline för att placera texten vertikalt runt radien. För de bästa resultaten använd ctx.TextBaseline
.
Funktioner kommer att kasta en TypeError
är något av de numeriska argumenten som NaN.
Om text
argument trim till en tom sträng eller ctx.globalAlpha = 0
funktionen sjunker bara igenom och gör ingenting.
CanvasRenderingContext2D.measureCircleText (text, radie);
- **text:** String of text to measure.
- **radius:** radius of circle in pixels.
Returnerar ett objekt som innehåller olika måttstorlek för rendering av cirkulär text
- **width:** Pixel width of text as it would normaly be rendered
- **angularWidth:** angular width of text in radians.
- **pixelAngularSize:** angular width of a pixel in radians.
Exempel på användning
const rad = canvas.height * 0.4;
const text = "Hello circle TEXT!";
const fontSize = 40;
const centX = canvas.width / 2;
const centY = canvas.height / 2;
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.font = fontSize + "px verdana";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillStyle = "#000";
ctx.strokeStyle = "#666";
// Text under stretched from Math.PI to 0 (180 - 0 deg)
ctx.fillCircleText(text, centX, centY, rad, Math.PI, 0);
// text over top centered at Math.PI * 1.5 ( 270 deg)
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);
// text under top centered at Math.PI * 1.5 ( 270 deg)
ctx.textBaseline = "top";
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);
// text over top centered at Math.PI * 1.5 ( 270 deg)
ctx.textBaseline = "middle";
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);
// Use measureCircleText to get angular size
var circleTextMetric = ctx.measureCircleText("Text to measure", rad);
console.log(circleTextMetric.width); // width of text if rendered normally
console.log(circleTextMetric.angularWidth); // angular width of text
console.log(circleTextMetric.pixelAngularSize); // angular size of a pixel
// Use measure text to draw a arc around the text
ctx.textBaseline = "middle";
var width = ctx.measureCircleText(text, rad).angularWidth;
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);
// render the arc around the text
ctx.strokeStyle= "red";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(centX, centY, rad + fontSize / 2,Math.PI * 1.5 - width/2,Math.PI*1.5 + width/2);
ctx.arc(centX, centY, rad - fontSize / 2,Math.PI * 1.5 + width/2,Math.PI*1.5 - width/2,true);
ctx.closePath();
ctx.stroke();
OBS! Den gjorda texten är bara en approximation av cirkulär text. Om till exempel två l är återgivna kommer de två linjerna inte att vara parallella, men om du gör en "H" kommer de två kanterna att vara parallella. Detta beror på att varje tecken återges så nära den önskade riktningen som möjligt, snarare än att varje pixel omvandlas korrekt för att skapa cirkulär text.
OBS:
const multiplyCurrentTransform = true;
definierad i detta exempel används för att ställa in den transformationsmetod som används. Om det ärfalse
är transformationen för cirkulär textframställning absolut och beror inte på det aktuella omvandlingsläget. Texten kommer inte att utföras av någon tidigare skala, rotera eller översätta transformationer. Detta kommer att öka renderingsfunktionens prestanda, efter att funktionen har kallats kommer transformationen att ställas in till standardsetTransform(1,0,0,1,0,0)
OmmultiplyCurrentTransform = true
(ställs in som standard i det här exemplet) kommer texten att använda den aktuella transformen så att texten kan skalas översatt, skev, roterad, etc men ändra den aktuella transformen för att kallafillCircleText
ochstrokeCircleText
. Beroende på det aktuella tillståndet för 2D-sammanhanget kan detta vara något långsammare änmultiplyCurrentTransform = false
Text på kurva, kubik och kvadratiska beziers
textOnCurve (text, offset, x1, y1, x2, y2, x3, y3, x4, y4)
Framför text på kvadratiska och kubiska kurvor.
-
text
är texten som ska återges -
offset
från kurvstart till text> = 0 -
x1,y1
-x3,y3
poäng kvadratisk kurva eller -
x1,y1
-x4,y4
poäng kubisk kurva eller
Exempel på användning:
textOnCurve("Hello world!",50,100,100,200,200,300,100); // draws text on quadratic curve
// 50 pixels from start of curve
textOnCurve("Hello world!",50,100,100,200,200,300,100,400,200);
// draws text on cubic curve
// 50 pixels from start of curve
Funktionen och kurvhjälparfunktionen
// pass 8 values for cubic bezier
// pass 6 values for quadratic
// Renders text from start of curve
var textOnCurve = function(text,offset,x1,y1,x2,y2,x3,y3,x4,y4){
ctx.save();
ctx.textAlign = "center";
var widths = [];
for(var i = 0; i < text.length; i ++){
widths[widths.length] = ctx.measureText(text[i]).width;
}
var ch = curveHelper(x1,y1,x2,y2,x3,y3,x4,y4);
var pos = offset;
var cpos = 0;
for(var i = 0; i < text.length; i ++){
pos += widths[i] / 2;
cpos = ch.forward(pos);
ch.tangent(cpos);
ctx.setTransform(ch.vect.x, ch.vect.y, -ch.vect.y, ch.vect.x, ch.vec.x, ch.vec.y);
ctx.fillText(text[i],0,0);
pos += widths[i] / 2;
}
ctx.restore();
}
Kurvhjälparfunktionen är utformad för att öka prestandan för att hitta punkter på ringen.
// helper function locates points on bezier curves.
function curveHelper(x1, y1, x2, y2, x3, y3, x4, y4){
var tx1, ty1, tx2, ty2, tx3, ty3, tx4, ty4;
var a,b,c,u;
var vec,currentPos,vec1,vect;
vec = {x:0,y:0};
vec1 = {x:0,y:0};
vect = {x:0,y:0};
quad = false;
currentPos = 0;
currentDist = 0;
if(x4 === undefined || x4 === null){
quad = true;
x4 = x3;
y4 = y3;
}
var estLen = Math.sqrt((x4 - x1) * (x4 - x1) + (y4 - y1) * (y4 - y1));
var onePix = 1 / estLen;
function posAtC(c){
tx1 = x1; ty1 = y1;
tx2 = x2; ty2 = y2;
tx3 = x3; ty3 = y3;
tx1 += (tx2 - tx1) * c;
ty1 += (ty2 - ty1) * c;
tx2 += (tx3 - tx2) * c;
ty2 += (ty3 - ty2) * c;
tx3 += (x4 - tx3) * c;
ty3 += (y4 - ty3) * c;
tx1 += (tx2 - tx1) * c;
ty1 += (ty2 - ty1) * c;
tx2 += (tx3 - tx2) * c;
ty2 += (ty3 - ty2) * c;
vec.x = tx1 + (tx2 - tx1) * c;
vec.y = ty1 + (ty2 - ty1) * c;
return vec;
}
function posAtQ(c){
tx1 = x1; ty1 = y1;
tx2 = x2; ty2 = y2;
tx1 += (tx2 - tx1) * c;
ty1 += (ty2 - ty1) * c;
tx2 += (x3 - tx2) * c;
ty2 += (y3 - ty2) * c;
vec.x = tx1 + (tx2 - tx1) * c;
vec.y = ty1 + (ty2 - ty1) * c;
return vec;
}
function forward(dist){
var step;
helper.posAt(currentPos);
while(currentDist < dist){
vec1.x = vec.x;
vec1.y = vec.y;
currentPos += onePix;
helper.posAt(currentPos);
currentDist += step = Math.sqrt((vec.x - vec1.x) * (vec.x - vec1.x) + (vec.y - vec1.y) * (vec.y - vec1.y));
}
currentPos -= ((currentDist - dist) / step) * onePix
currentDist -= step;
helper.posAt(currentPos);
currentDist += Math.sqrt((vec.x - vec1.x) * (vec.x - vec1.x) + (vec.y - vec1.y) * (vec.y - vec1.y));
return currentPos;
}
function tangentQ(pos){
a = (1-pos) * 2;
b = pos * 2;
vect.x = a * (x2 - x1) + b * (x3 - x2);
vect.y = a * (y2 - y1) + b * (y3 - y2);
u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
vect.x /= u;
vect.y /= u;
}
function tangentC(pos){
a = (1-pos)
b = 6 * a * pos;
a *= 3 * a;
c = 3 * pos * pos;
vect.x = -x1 * a + x2 * (a - b) + x3 * (b - c) + x4 * c;
vect.y = -y1 * a + y2 * (a - b) + y3 * (b - c) + y4 * c;
u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
vect.x /= u;
vect.y /= u;
}
var helper = {
vec : vec,
vect : vect,
forward : forward,
}
if(quad){
helper.posAt = posAtQ;
helper.tangent = tangentQ;
}else{
helper.posAt = posAtC;
helper.tangent = tangentC;
}
return helper
}
Motiverad text
Detta exempel ger rättfärdig text. Det lägger till extra funktionalitet till CanvasRenderingContext2D
genom att utöka sin prototyp eller som ett globalt objekt som är justifiedText
(valfritt se anmärkning A).
Exempel rendering.
Koden för att göra denna bild finns i användningsexemplen längst ner .
Exemplet
Funktionen som en anonym omedelbart åberopad funktion.
(function(){
const FILL = 0; // const to indicate filltext render
const STROKE = 1;
const MEASURE = 2;
var renderType = FILL; // used internal to set fill or stroke text
var maxSpaceSize = 3; // Multiplier for max space size. If greater then no justificatoin applied
var minSpaceSize = 0.5; // Multiplier for minimum space size
var renderTextJustified = function(ctx,text,x,y,width){
var words, wordsWidth, count, spaces, spaceWidth, adjSpace, renderer, i, textAlign, useSize, totalWidth;
textAlign = ctx.textAlign; // get current align settings
ctx.textAlign = "left";
wordsWidth = 0;
words = text.split(" ").map(word => {
var w = ctx.measureText(word).width;
wordsWidth += w;
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 = words.length;
spaces = count - 1;
spaceWidth = ctx.measureText(" ").width;
adjSpace = Math.max(spaceWidth * minSpaceSize, (width - wordsWidth) / spaces);
useSize = adjSpace > spaceWidth * maxSpaceSize ? spaceWidth : adjSpace;
totalWidth = wordsWidth + useSize * spaces
if(renderType === MEASURE){ // if measuring return size
ctx.textAlign = textAlign;
return totalWidth;
}
renderer = renderType === FILL ? ctx.fillText.bind(ctx) : ctx.strokeText.bind(ctx); // fill or stroke
switch(textAlign){
case "right":
x -= totalWidth;
break;
case "end":
x += width - totalWidth;
break;
case "center": // intentional fall through to default
x -= totalWidth / 2;
default:
}
if(useSize === spaceWidth){ // if space size unchanged
renderer(text,x,y);
} else {
for(i = 0; i < count; i += 1){
renderer(words[i].word,x,y);
x += words[i].width;
x += useSize;
}
}
ctx.textAlign = textAlign;
}
// 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;
if(num < 0){
num = defaultNum;
}
return num;
}
if(settings === undefined || settings === null){
return;
}
max = vetNumber(settings.maxSpaceSize, maxSpaceSize);
min = vetNumber(settings.minSpaceSize, minSpaceSize);
if(min > max){
return;
}
minSpaceSize = min;
maxSpaceSize = max;
}
// define fill text
var fillJustifyText = function(text, x, y, width, settings){
justifiedTextSettings(settings);
renderType = FILL;
renderTextJustified(this, text, x, y, width);
}
// define stroke text
var strokeJustifyText = function(text, x, y, width, settings){
justifiedTextSettings(settings);
renderType = STROKE;
renderTextJustified(this, text, x, y, width);
}
// define measure text
var measureJustifiedText = function(text, width, settings){
justifiedTextSettings(settings);
renderType = MEASURE;
return renderTextJustified(this, text, 0, 0, width);
}
// code point A
// set the prototypes
CanvasRenderingContext2D.prototype.fillJustifyText = fillJustifyText;
CanvasRenderingContext2D.prototype.strokeJustifyText = strokeJustifyText;
CanvasRenderingContext2D.prototype.measureJustifiedText = measureJustifiedText;
// code point B
// optional code if you do not wish to extend the CanvasRenderingContext2D prototype
/* Uncomment from here to the closing comment
window.justifiedText = {
fill : function(ctx, text, x, y, width, settings){
justifiedTextSettings(settings);
renderType = FILL;
renderTextJustified(ctx, text, x, y, width);
},
stroke : function(ctx, text, x, y, width, settings){
justifiedTextSettings(settings);
renderType = STROKE;
renderTextJustified(ctx, text, x, y, width);
},
measure : function(ctx, text, width, settings){
justifiedTextSettings(settings);
renderType = MEASURE;
return renderTextJustified(ctx, text, 0, 0, width);
}
}
to here*/
})();
Obs A: Om du inte vill förlänga prototypen
CanvasRenderingContext2D
Ta bort från exemplet all kod mellan// code point A
och// code point B
och avdela koden markerad/* Uncomment from here to the closing comment
Hur man använder
Tre funktioner läggs till i CanvasRenderingContext2D
och är tillgängliga för alla skapade 2D-kontextobjekt.
- ctx.fillJustifyText (text, x, y, bredd, [inställningar]);
- ctx.strokeJustifyText (text, x, y, bredd, [inställningar]);
- ctx.measureJustifiedText (text, bredd, [inställningar]);
Fyll och streck textfunktion fyll eller strök text och använd samma argument. measureJustifiedText
returnerar den faktiska bredden som texten skulle visas på. Detta kan vara lika, mindre, eller mer än argumentet width
beroende på aktuella inställningar.
Obs: Argument inom
[
och]
är valfria.
Funktionsargument
text: Sträng som innehåller texten som ska återges.
x, y: Koordinater för att återge texten på.
bredd: Bredden på den motiverade texten. Text ökar / minskar mellanrum mellan ord för att passa bredden. Om avståndet mellan orden är större än
maxSpaceSize
(standard = 6) gånger kommer normalt avstånd att användas och texten fyller inte önskad bredd. Om avståndet är mindre änminSpaceSize
(standard = 0,5) tid normalt avstånd användsminSpaceSize
och texten överskrider den begärda breddeninställningar: Valfritt. Objekt som innehåller min- och maxutrymme.
settings
är valfritt och om inte inkluderad text rendering använder den senaste inställningen definierade eller standard (visas nedan).
Både min och max är min- och maxstorlekarna för tecken som skiljer orden. Standard maxSpaceSize = 6
betyder att när utrymmet mellan tecken är> 63 * ctx.measureText ("") .breddstext kommer inte att motiveras. Om text som ska motiveras har mellanslag mindre än minSpaceSize = 0.5
(standardvärde 0,5) * ctx.measureText(" ").width
kommer ctx.measureText(" ").width
att ställas in på minSpaceSize * ctx.measureText(" ").width
och den resulterande texten kommer att överskridas den motiverande bredden.
Följande regler tillämpas, min och max måste vara siffror. Om inte kommer de associerade värdena inte att ändras. Om minSpaceSize
är större än maxSpaceSize
båda ingångsinställningarna ogiltiga och min max kommer inte att ändras.
Exempel på inställningsobjekt med standardvärden
settings = {
maxSpaceSize : 6; // Multiplier for max space size.
minSpaceSize : 0.5; // Multiplier for minimum space size
};
OBS: Dessa textfunktioner introducerar en subtil beteendeförändring för egenskapen
textAlign
i 2D-sammanhanget. 'Vänster', 'höger', 'mitt' och 'start' uppför sig som förväntat men 'slut' kommer inte att anpassas från höger om funktionsargumentetx
utan snarare från höger omx + width
Obs: inställningarna (min och max rymdstorlek) är globala för alla 2D-kontextobjekt.
ANVÄNDNING Exempel
var i = 0;
text[i++] = "This text is aligned from the left of the canvas.";
text[i++] = "This text is near the max spacing size";
text[i++] = "This text is way too short.";
text[i++] = "This text is too long for the space provied and will overflow#";
text[i++] = "This text is aligned using 'end' and starts at x + width";
text[i++] = "This text is near the max spacing size";
text[i++] = "This text is way too short.";
text[i++] = "#This text is too long for the space provied and will overflow";
text[i++] = "This is aligned with 'center' and is placed from the center";
text[i++] = "This text is near the max spacing size";
text[i++] = "This text is way too short.";
text[i++] = "This text is just too long for the space provied and will overflow";
// ctx is the 2d context
// canvas is the canvas
ctx.clearRect(0,0,w,h);
ctx.font = "25px arial";
ctx.textAlign = "center"
var left = 20;
var center = canvas.width / 2;
var width = canvas.width-left*2;
var y = 40;
var size = 16;
var i = 0;
ctx.fillText("Justified text examples.",center,y);
y+= 40;
ctx.font = "14px arial";
ctx.textAlign = "left"
var ww = ctx.measureJustifiedText(text[0], width);
var setting = {
maxSpaceSize : 6,
minSpaceSize : 0.5
}
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 = "red";
ctx.fillText("< 'left' aligned",left,y - size)
ctx.fillStyle = "black";
ctx.fillJustifyText(text[i++], left, y, width, setting); // settings is remembered
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
y += 2.3*size;
ctx.fillStyle = "red";
ctx.fillText("< 'end' aligned from x plus the width -------------------->",left,y - size)
ctx.fillStyle = "black";
ctx.textAlign = "end";
ctx.fillJustifyText(text[i++], left, y, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
y += 40;
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(center,y - size * 2);
ctx.lineTo(center, y + size * 5);
ctx.stroke();
ctx.textAlign = "center";
ctx.fillStyle = "red";
ctx.fillText("'center' aligned",center,y - size)
ctx.fillStyle = "black";
ctx.fillJustifyText(text[i++], center, y, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);
Motiverade stycken.
Gör text som motiverade stycken. Kräver exemplet Motiverad text
Exempel render
Översta stycket har setting.compact = true och botten falskt och linjeavståndet är 1.2 snarare än standard 1.5 . Tillhandahålls av kodanvändningsexempel längst ned i detta exempel.
Exempelkod
// 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;
})();
OBS
CanvasRenderingContext2D
Detta utökar prototypenCanvasRenderingContext2D
. Om du inte vill att detta ska ske, använd exemplet Justified text för att ta reda på hur du ändrar detta exempel för att vara en del av det globala namnområdet.
OBS ! Kasta en ReferenceError om detta exempel inte kan hitta funktionen
CanvasRenderingContext2D.prototype.fillJustifyText
Hur man använder
ctx.fillParaText(text, x, y, width, [settings]);
ctx.strokeParaText(text, x, y, width, [settings]);
Se Motiverad text för detaljer om argument. Argument mellan [
och ]
är valfria.
settings
har ytterligare två egenskaper.
- kompakt: Standard
true
. Om sant försöker packa så många ord som möjligt per rad. Om det är falskt försöker du få ordavståndet så nära det normala avståndet som möjligt. - lineSpacing Standard
1.5
. Utrymme per rad är standard1.5
avståndet från nätet till nästa när det gäller teckenstorlek
Egenskaper som saknas i inställningsobjektet kommer att vara standardvärdena eller de senaste giltiga värdena. Egenskaperna kommer endast att ändras om de nya värdena är giltiga. För compact
giltiga värden är endast booleaner true
eller false
Sannhetsvärden anses inte giltiga.
Returnera objektet
De två funktionerna returnerar ett objekt som innehåller information som hjälper dig att placera nästa stycke. Objektet innehåller följande egenskaper.
- nextLine Position på nästa rad efter styckepixlarna.
- fontSize typsnittets storlek. (Observera att du bara använder teckensnitt definierade i pixlar, t.ex.
14px arial
) - lineHeight Avstånd i pixlar från en rad till en nästa
I det här exemplet används en enkel algoritm som fungerar en rad åt gången för att hitta den bästa passningen för ett stycke. Detta betyder inte att det passar bäst (snarare algoritmens bästa). Du kanske vill förbättra algoritmen genom att skapa en algoritm för flera passlinjer över de genererade linjerna. Flytta ord från slutet på en rad till början på nästa eller från början tillbaka till slutet. Det bästa utseendet uppnås när avståndet över hela stycket har den minsta variationen och är närmast det normala textavståndet.
Eftersom det här exemplet är beroende av texten Exemplet är koden mycket lik. Du kanske vill flytta de två till en funktion. Byt ut funktionen justifiedTextSettings
i det andra exemplet med den som används i detta exempel. Kopiera sedan alla resten av koden från det här exemplet till det anonyma funktionsorganet i exemplet Justified text . Du behöver inte längre testa för beroende som finns på // Code point A
Det kan tas bort.
Exempel på användning
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);
Obs! För text som är inriktad till
left
eller för attstart
den sista raden i stycket har alltid normalt avstånd. För alla andra justeringar behandlas den sista raden som alla andra.
Obs! Du kan infoga början av stycket med mellanslag. Även om detta kanske inte överensstämmer från punkt till stycke. Det är alltid bra att lära sig vad en funktion gör och ändra den. En övning skulle vara att lägga till en inställning till inställningarna som indragar den första raden med ett fast belopp. Tips om medan slingan måste tillfälligt få det första ordet att visas större (+ indrag)
words[0].width += ?
och sedan när de återger linjer strecksats den första raden.