Zoeken…
Tekst tekenen
Tekenen op canvas beperkt zich niet alleen tot vormen en afbeeldingen. U kunt ook tekst naar het canvas tekenen.
Als u tekst op het canvas wilt tekenen, fillText
u een verwijzing naar het canvas en roept u de methode fillText
aan in de context.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.fillText("My text", 0, 0);
De drie vereiste argumenten die worden doorgegeven aan fillText
zijn:
- De tekst die u wilt weergeven
- De horizontale (x-as) positie
- De verticale (y-as) positie
Bovendien is er een vierde optioneel argument, dat u kunt gebruiken om de maximale breedte van uw tekst in pixels op te geven. In het onderstaande voorbeeld beperkt de waarde van 200
de maximale breedte van de tekst tot 200
px:
ctx.fillText("My text", 0, 0, 200);
Resultaat:
U kunt ook tekst tekenen zonder vulling, en in plaats daarvan alleen een omtrek, met behulp van de methode strokeText
:
ctx.strokeText("My text", 0, 0);
Resultaat:
Zonder toegepaste eigenschappen voor lettertype-opmaak, geeft het canvas tekst standaard 10px in sans-serif weer, waardoor het moeilijk is om het verschil te zien tussen het resultaat van de methoden fillText
en strokeText
. Zie het voorbeeld Tekst opmaken voor meer informatie over het vergroten van de tekstgrootte en het toepassen van andere esthetische wijzigingen op tekst.
Tekst opmaken
De standaard lettertype-opmaak van de methoden fillText
en strokeText
is niet erg esthetisch aantrekkelijk. Gelukkig biedt de canvas-API eigenschappen voor het opmaken van tekst.
Met de eigenschap font
kunt u het volgende opgeven:
- lettertype
- tekenvariabele
- lettertype dikte
- lettergrootte / regelhoogte
- font-family
Bijvoorbeeld:
ctx.font = "italic small-caps bold 40px Helvetica, Arial, sans-serif";
ctx.fillText("My text", 20, 50);
Resultaat:
Met de eigenschap textAlign
kunt u de textAlign
ook wijzigen in:
- links
- centrum
- Rechtsaf
- einde (hetzelfde als rechts)
- start (hetzelfde als links)
Bijvoorbeeld:
ctx.textAlign = "center";
Tekst in alinea's verpakken
Native Canvas API heeft geen methode om tekst op de volgende regel te plaatsen wanneer een gewenste maximale breedte is bereikt. Dit voorbeeld verpakt tekst in alinea's.
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);
}
Teken tekstalinea's in onregelmatige vormen
In dit voorbeeld worden tekstalinea's in alle delen van het canvas getekend met dekkende pixels.
Het werkt door het volgende blok dekkende pixels te vinden dat groot genoeg is om het volgende opgegeven woord te bevatten en dat blok met het opgegeven woord te vullen.
De ondoorzichtige pixels kunnen uit elke bron komen: opdrachten voor padtekening en / of afbeeldingen.
<!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>
Vul tekst met een afbeelding in
In dit voorbeeld wordt tekst gevuld met een opgegeven afbeelding.
Belangrijk! De opgegeven afbeelding moet volledig zijn geladen voordat deze functie wordt aangeroepen, anders mislukt de tekening. Gebruik image.onload
om te controleren of de afbeelding volledig is geladen.
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);
}
Tekst langs een boog renderen.
Dit voorbeeld laat zien hoe tekst langs een boog kan worden weergegeven. Het omvat hoe u functionaliteit kunt toevoegen aan de CanvasRenderingContext2D
door het prototype uit te breiden.
Dit voorbeeld is afgeleid van het antwoord Circular Text .
Voorbeeldweergave
Voorbeeld code
Het voorbeeld voegt 3 nieuwe functies voor tekstweergave toe aan het 2D-contextprototype.
- ctx.fillCircleText (tekst, x, y, straal, start, einde, vooruit);
- ctx.strokeCircleText (tekst, x, y, straal, start, einde, vooruit);
- ctx.measureCircleText (tekst, straal);
(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;
})();
Functie beschrijvingen
Dit voorbeeld voegt 3 functies toe aan het CanvasRenderingContext2D prototype
. fillCircleText
, strokeCircleText
en measureCircleText
CanvasRenderingContext2D.fillCircleText (tekst, x, y, radius, start, [end, [forward]]);
CanvasRenderingContext2D.strokeCircleText (tekst, x, y, radius, start, [end, [forward]]);
- tekst: tekst die als tekenreeks moet worden weergegeven.
- x , y : positie van het middelpunt van de cirkel als getallen.
- radius: cirkelradius in pixels
- start: hoek in radialen om te starten.
- [einde]: optioneel. Indien opgenomen wordt
ctx.textAlign
genegeerd en wordt de tekst geschaald zodat deze tussen begin en einde past. - [vooruit]: optionele standaard 'true'. als ware tekstrichting vooruit is, als 'valse' richting achteruit is.
Beide functies gebruiken de textBaseline om de tekst verticaal rond de straal te plaatsen. Gebruik ctx.TextBaseline
voor de beste resultaten.
Functies geven een TypeError
is een van de numerieke argumenten als NaN.
Als de text
argument trims op een lege tekenreeks of ctx.globalAlpha = 0
de functie alleen maar daalt door en doet niets.
CanvasRenderingContext2D.measureCircleText (tekst, straal);
- **text:** String of text to measure.
- **radius:** radius of circle in pixels.
Retourneert een object met verschillende groottemetingen voor het weergeven van circulaire tekst
- **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.
Gebruik voorbeelden
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();
OPMERKING: de weergegeven tekst is slechts een benadering van cirkelvormige tekst. Als bijvoorbeeld twee l's worden weergegeven, zijn de twee lijnen niet parallel, maar als u een "H" geeft, zijn de twee randen evenwijdig. Dit komt omdat elk teken zo dicht mogelijk bij de vereiste richting wordt weergegeven, in plaats van dat elke pixel correct wordt getransformeerd om cirkelvormige tekst te maken.
OPMERKING:
const multiplyCurrentTransform = true;
gedefinieerd in dit voorbeeld wordt gebruikt om de gebruikte transformatiemethode in te stellen. Indienfalse
de transformatie voor circulaire tekstweergave absoluut en niet afhankelijk van de huidige transformatiestatus. De tekst wordt niet beïnvloed door eerdere transformaties op schaal, roteren of vertalen. Dit verhoogt de prestaties van desetTransform(1,0,0,1,0,0)
, nadat de functie wordt aangeroepen, wordt de transformatie ingesteld op de standaardsetTransform(1,0,0,1,0,0)
AlsmultiplyCurrentTransform = true
(in dit voorbeeld als standaard ingesteld), gebruikt de tekst de huidige transformatie zodat de tekst kan worden vertaald, vertaald, scheefgetrokken, geroteerd, enz. Maar de huidige transformatie wordt gewijzigd voordat de functiesfillCircleText
enstrokeCircleText
. Afhankelijk van de huidige status van de 2D-context kan dit iets langzamer zijn danmultiplyCurrentTransform = false
Tekst op curve, kubieke en kwadratische beziers
textOnCurve (tekst, offset, x1, y1, x2, y2, x3, y3, x4, y4)
Geeft tekst weer op kwadratische en kubieke curven.
-
text
is de tekst die moet worden weergegeven -
offset
afstand vanaf het begin van de curve naar tekst> = 0 -
x1,y1
-x3,y3
punten van kwadratische curve of -
x1,y1
-x4,y4
punten van kubieke curve of
Voorbeeld gebruik:
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
De functie en curver helperfunctie
// 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();
}
De curve helper-functie is ontworpen om de prestaties van het vinden van punten op de bezier te verbeteren.
// 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
}
Gerechtvaardigde tekst
In dit voorbeeld wordt gerechtvaardigde tekst weergegeven. Het voegt extra functionaliteit toe aan de CanvasRenderingContext2D
door het prototype uit te breiden of als een globaal object justifiedText
(optioneel zie opmerking A).
Voorbeeldweergave.
Code om deze afbeelding weer te geven staat in de gebruiksvoorbeelden onderaan .
Het voorbeeld
De functie als een anonieme onmiddellijk opgeroepen functie.
(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*/
})();
Opmerking A: Als u het
CanvasRenderingContext2D
prototype niet wilt uitbreiden, verwijdert u uit het voorbeeld alle code tussen// code point A
en// code point B
en geeft u de code met de markering/* Uncomment from here to the closing comment
Uncomment/* Uncomment from here to the closing comment
Hoe te gebruiken
Drie functies zijn toegevoegd aan de CanvasRenderingContext2D
en zijn beschikbaar voor alle gemaakte 2D- CanvasRenderingContext2D
.
- ctx.fillJustifyText (tekst, x, y, breedte, [instellingen]);
- ctx.strokeJustifyText (tekst, x, y, breedte, [instellingen]);
- ctx.measureJustifiedText (tekst, breedte, [instellingen]);
Tekst vullen en lijnfunctie vul tekst of lijn en gebruik dezelfde argumenten. measureJustifiedText
retourneert de werkelijke breedte waarin tekst zou worden weergegeven. Dit kan gelijk, kleiner of groter dan het argument width
afhankelijk van de huidige instellingen.
Opmerking: argumenten binnen
[
en]
zijn optioneel.
Functie argumenten
text: String die de weer te geven tekst bevat.
x, y: Coördinaten om de tekst weer te geven op.
breedte: breedte van de uitgevulde tekst. Tekst vergroot / verkleint de spaties tussen woorden zodat ze in de breedte passen. Als de ruimte tussen woorden groter is dan
maxSpaceSize
(standaard = 6), wordt de normale afstand gebruikt en vult de tekst niet de vereiste breedte. Als de afstand kleiner is danminSpaceSize
(standaard = 0,5) in tijd normale afstand, wordt de minimale ruimtegrootte gebruikt en overschrijdt de tekst de gevraagde breedteinstellingen: optioneel. Object met minimale en maximale ruimtegroottes.
Het settings
is optioneel en als het niet wordt opgenomen, wordt de laatst gedefinieerde instelling of de standaardinstelling (hieronder weergegeven) gebruikt.
Zowel min als max zijn de min en max grootte voor het [spatie] karakter dat woorden scheidt. De standaard maxSpaceSize = 6
betekent dat wanneer de spatie tussen tekens> 63 * ctx.measureText ("") is. Breedte-tekst niet gerechtvaardigd is. Als de te rechtvaardigen tekst spaties heeft kleiner dan minSpaceSize = 0.5
(standaardwaarde 0,5) * ctx.measureText(" ").width
de afstand ingesteld op minSpaceSize * ctx.measureText(" ").width
en de resulterende tekst wordt overschreden de rechtvaardigende breedte.
De volgende regels zijn van toepassing, min en max moeten getallen zijn. Zo niet, dan worden de bijbehorende waarden niet gewijzigd. Als minSpaceSize
groter is dan maxSpaceSize
beide invoerinstellingen ongeldig en wordt min max niet gewijzigd.
Voorbeeld van een object met standaardinstellingen
settings = {
maxSpaceSize : 6; // Multiplier for max space size.
minSpaceSize : 0.5; // Multiplier for minimum space size
};
OPMERKING: Deze tekstfuncties introduceren een subtiele gedragsverandering voor de eigenschap
textAlign
van de 2D-context. 'Links', 'rechts', 'midden' en 'start' gedragen zich zoals verwacht, maar 'einde' zal niet uitlijnen rechts van het functieargumentx
maar eerder rechts vanx + width
Opmerking: instellingen (minimale en maximale ruimtegrootte) zijn algemeen voor alle 2D contextobjecten.
GEBRUIK Voorbeelden
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);
Gerechtvaardigde paragrafen.
Geeft tekst weer als gerechtvaardigde alinea's. VEREIST het voorbeeld Uitgevulde tekst
Voorbeeld render
De bovenste alinea heeft setting.compact = true en de onderkant false en de regelafstand is 1,2 in plaats van de standaard 1.5 . Weergegeven door voorbeeld van codegebruik onderaan dit voorbeeld.
Voorbeeld code
// 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;
})();
LET OP hiermee wordt het
CanvasRenderingContext2D
prototype uitgebreid. Als u niet wilt dat dit gebeurt, gebruikt u de voorbeeld- uitgevulde tekst om uit te werken hoe u dit voorbeeld kunt wijzigen om deel uit te maken van de globale naamruimte.
OPMERKING Gooit een ReferenceError als dit voorbeeld de functie
CanvasRenderingContext2D.prototype.fillJustifyText
niet kan vinden
Hoe te gebruiken
ctx.fillParaText(text, x, y, width, [settings]);
ctx.strokeParaText(text, x, y, width, [settings]);
Zie gerechtvaardigde tekst voor details over argumenten. Argumenten tussen [
en ]
zijn optioneel.
Het argument settings
heeft twee extra eigenschappen.
- compact: standaard
true
. Als true probeert zoveel mogelijk woorden per regel in te pakken. Indien onwaar probeert het woordafstand zo dicht mogelijk bij de normale afstand te krijgen. - lineSpacing Standaard
1.5
. Ruimte per regel standaard1.5
de afstand van online naar de volgende in termen van lettergrootte
Eigenschappen die ontbreken in het instellingenobject zullen standaard hun standaardwaarden of de laatste geldige waarden zijn. De eigenschappen worden alleen gewijzigd als de nieuwe waarden geldig zijn. Voor compact
geldige waarden zijn alleen Booleans true
of false
Waarheden worden niet als geldig beschouwd.
Retourneer object
De twee functies retourneren een object met informatie om u te helpen de volgende alinea te plaatsen. Het object bevat de volgende eigenschappen.
- nextLine Positie van de volgende regel na de alineapixels .
- fontSize Grootte van het lettertype. (let op: gebruik alleen lettertypen die zijn gedefinieerd in pixels, bijvoorbeeld 14
14px arial
) - lineHeight Afstand in pixels van de ene regel naar de volgende
In dit voorbeeld wordt een eenvoudig algoritme gebruikt dat één regel tegelijk werkt om de beste pasvorm voor een alinea te vinden. Dit betekent niet dat het het beste past (liever het beste van het algoritme). Misschien wilt u het algoritme verbeteren door een multi-pass lijnalgoritme over de gegenereerde lijnen te maken. Woorden verplaatsen van het einde van de ene regel naar het begin van de volgende, of van het begin terug naar het einde. De beste look wordt bereikt wanneer de afstand over de hele alinea de kleinste variatie heeft en het dichtst bij de normale tekstafstand ligt.
Omdat dit voorbeeld afhankelijk is van het voorbeeld van de rechtvaardigde tekst, lijkt de code erg op elkaar. Misschien wilt u de twee naar één functie verplaatsen. Vervang in het andere voorbeeld de functie justifiedTextSettings
Tekstextensies door die in dit voorbeeld. Kopieer vervolgens de rest van de code uit dit voorbeeld naar de anonieme functie van het voorbeeld van de tekst Justified . U hoeft niet langer te testen op afhankelijkheden gevonden op // Code point A
Het kan worden verwijderd.
Gebruiksvoorbeeld
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);
Opmerking: voor tekst
left
uitgelijnd ofstart
de laatste regel van de alinea altijd een normale afstand hebben. Voor alle andere uitlijningen wordt de laatste regel behandeld als alle andere.
Opmerking: u kunt het begin van de alinea met spaties invoegen. Hoewel dit mogelijk niet consistent is van paragraaf tot paragraaf. Het is altijd goed om te weten wat een functie doet en deze te wijzigen. Een oefening zou zijn om een instelling toe te voegen aan de instellingen die de eerste regel inspringt met een vast bedrag. Hint dat de while-lus het eerste woord tijdelijk groter moet laten lijken (+ inspringen)
words[0].width += ?
en dan bij het renderen van lijnen de eerste regel inspringen.