cherry-box
Advanced tools
+7
| { | ||
| "env": { | ||
| "test": { | ||
| "plugins": ["@babel/plugin-transform-modules-commonjs"] | ||
| } | ||
| } | ||
| } |
| /** | ||
| * Get total width of multiple fonts at once | ||
| * @param {ctx} ctx Canvas context | ||
| * @param {object} text Text to be displayed in the text box | ||
| * @param {int} fontSize Maximum font size of the text | ||
| * @param {int} spaceWidth Width of the space between words | ||
| * @returns {int} Width of the text | ||
| */ | ||
| function getTotalWidth(ctx, text, fontSize, spaceWidth=0) { | ||
| let width = 0; | ||
| for (let i = 0; i < text.length; i++) { | ||
| if (text[i].modifier == null) text[i].modifier = ''; | ||
| ctx.font = `${text[i].modifier} ${fontSize}px ${text[i].font}`; | ||
| width += ctx.measureText(text[i].text).width + spaceWidth; | ||
| } | ||
| return width - spaceWidth; | ||
| } | ||
| export { getTotalWidth }; |
| import paintText from './paintText.js'; | ||
| import { getTotalWidth } from './textAttr.js'; | ||
| /** | ||
| * Get lines of text that fit in the box | ||
| * @param {ctx} ctx Canvas context | ||
| * @param {object} text Text to be displayed in the text box | ||
| * @param {number} fontSize Font size of the text | ||
| * @param {number} width Width of the text | ||
| * @returns | ||
| */ | ||
| function getLines(ctx, text, width, fontSize) { | ||
| let spaceWidth = fontSize / 4; | ||
| // Generate the lines | ||
| let lines = []; | ||
| let line = []; | ||
| for (let i = 0; i < text.length; i++) { | ||
| let splitText = text[i].text.split(' '); | ||
| for (let j = 0; j < splitText.length; j++) { | ||
| let textRich = text[i]; | ||
| textRich.text = splitText[j]; | ||
| // Remove unnecessary info in the object | ||
| textRich = JSON.parse(JSON.stringify(textRich)); | ||
| let lineWidth = getTotalWidth(ctx, line, fontSize, spaceWidth); | ||
| let textWidth = getTotalWidth(ctx, [textRich], fontSize, spaceWidth); | ||
| if (lineWidth + textWidth + spaceWidth > width) { | ||
| lines.push(line); | ||
| line = []; | ||
| } | ||
| line.push(textRich); | ||
| } | ||
| } | ||
| lines.push(line); | ||
| return lines; | ||
| } | ||
| /** | ||
| * Text that adjusts to the size of the canvas, but it wraps | ||
| * @param {ctx} ctx Canvas context | ||
| * @param {int} x X coordinate of the text box | ||
| * @param {int} y Y coordinate of the text box | ||
| * @param {int} width Width of the text box | ||
| * @param {object} text Text to be displayed in the text box | ||
| * @param {int} fontSize Maximum font size of the text | ||
| * @param {string} align Alignment of the text | ||
| */ | ||
| function wrapText(ctx, x, y, width, text, fontSize, align=1) { | ||
| ctx.save(); | ||
| let lines = getLines(ctx, text, width, fontSize); | ||
| let spaceWidth = fontSize / 4; | ||
| for (let i = 0; i < lines.length; i++) { | ||
| let textWidth = getTotalWidth(ctx, lines[i], fontSize, spaceWidth); | ||
| switch (align) { | ||
| case 1: | ||
| x += (width - textWidth) / 2; | ||
| break; | ||
| case 2: | ||
| x += width - textWidth; | ||
| break; | ||
| case 3: | ||
| let rawTextWidth = getTotalWidth(ctx, lines[i], fontSize); | ||
| spaceWidth = (width - rawTextWidth) / (lines[i].length - 1); | ||
| if (spaceWidth < 4) spaceWidth = 4; | ||
| if (spaceWidth > 30) spaceWidth = 10; | ||
| break; | ||
| } | ||
| paintText(ctx, lines[i], x, y+fontSize*(i+1), fontSize, spaceWidth); | ||
| } | ||
| ctx.restore(); | ||
| } | ||
| export { wrapText, getLines }; |
| import { getTotalHeight, getTotalWidth } from "../lib/textAttr"; | ||
| import Canvas from "canvas"; | ||
| const canvas = Canvas.createCanvas(100, 100); | ||
| const ctx = canvas.getContext("2d"); | ||
| test("getTotalWidth", () => { | ||
| const text = [{ text: "test", font: "Arial" }]; | ||
| expect( getTotalWidth(ctx, text, 20, 0) ).toBeCloseTo(32, -0.5); | ||
| expect( getTotalWidth(ctx, text, 40, 0) ).toBeCloseTo(64, -0.5); | ||
| expect( getTotalWidth(ctx, text, 60, 0) ).toBeCloseTo(96, -1); | ||
| expect( getTotalWidth(ctx, text, 80, 0) ).toBeCloseTo(128, -1); | ||
| expect( getTotalWidth(ctx, text, 0.1, 0) ).toBeCloseTo(0, 0); | ||
| }); |
| import { getLines } from "../lib/wrapText"; | ||
| import Canvas from "canvas"; | ||
| const canvas = Canvas.createCanvas(100, 100); | ||
| const ctx = canvas.getContext("2d"); | ||
| test("getLines", () => { | ||
| let text = [{ | ||
| text: "I'd like to interject for a moment, what you reffered to as Linux was in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called \"Linux\", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. The Free Software Foundation obviously intends Linux to be free software like any other free program, and whoever works for the Free Software Foundation will do whatever it takes to ensure that the GNU system remains free. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called \"Linux\" distributions are really distributions of GNU/Linux.", | ||
| font: 'Arial', | ||
| }] | ||
| let lines = getLines(ctx, text, 1000, 20); | ||
| expect(lines.length).toBe(13); | ||
| expect(lines[0]).toStrictEqual([ | ||
| { text: "I'd", font: 'Arial' }, | ||
| { text: 'like', font: 'Arial' }, | ||
| { text: 'to', font: 'Arial' }, | ||
| { text: 'interject', font: 'Arial' }, | ||
| { text: 'for', font: 'Arial' }, | ||
| { text: 'a', font: 'Arial' }, | ||
| { text: 'moment,', font: 'Arial' }, | ||
| { text: 'what', font: 'Arial' }, | ||
| { text: 'you', font: 'Arial' }, | ||
| { text: 'reffered', font: 'Arial' }, | ||
| { text: 'to', font: 'Arial' }, | ||
| { text: 'as', font: 'Arial' }, | ||
| { text: 'Linux', font: 'Arial' }, | ||
| { text: 'was', font: 'Arial' }, | ||
| { text: 'in', font: 'Arial' }, | ||
| { text: 'fact,', font: 'Arial' }, | ||
| { text: 'GNU/Linux,', font: 'Arial' }, | ||
| { text: 'or', font: 'Arial' }, | ||
| { text: 'as', font: 'Arial' }, | ||
| { text: "I've", font: 'Arial' }, | ||
| { text: 'recently', font: 'Arial' }, | ||
| { text: 'taken', font: 'Arial' }, | ||
| { text: 'to', font: 'Arial' } | ||
| ]); | ||
| }); |
@@ -20,3 +20,3 @@ import { textBox, wrapText } from "cherry-box"; | ||
| ]; | ||
| textBox(ctx, 0, 0, canvas.width, canvas.height-20, upperText, 80, ["bottom", "center"]); | ||
| textBox(ctx, 0, 0, canvas.width, canvas.height-20, upperText, 80, [2, 1]); | ||
@@ -36,2 +36,2 @@ let wrapTextBox = [ | ||
| ] | ||
| wrapText(ctx, 25, 25, canvas.width-50, wrapTextBox, 20, 'justify'); | ||
| wrapText(ctx, 25, 25, canvas.width-50, wrapTextBox, 20, 3); |
@@ -39,3 +39,3 @@ import { textBox } from "cherry-box"; | ||
| //Draw text | ||
| textBox(ctx, 0, 0, canvas.width, 80, upperText, 80, ["middle", "center"]); | ||
| textBox(ctx, 0, canvas.height-80, canvas.width, 80, lowerText, 80, ["middle", "center"]); | ||
| textBox(ctx, 0, 0, canvas.width, 80, upperText, 80, [1, 1]); | ||
| textBox(ctx, 0, canvas.height-80, canvas.width, 80, lowerText, 80, [1, 1]); |
+3
-2
@@ -1,2 +0,3 @@ | ||
| import { textBox, wrapText } from './lib/textBox.js'; | ||
| export { textBox, wrapText }; | ||
| import { textBox } from './lib/textBox.js'; | ||
| import { wrapText, getLines } from './lib/wrapText.js'; | ||
| export { textBox, wrapText, getLines }; |
+17
-16
| /** | ||
| * | ||
| * Paint text using all the options provided. | ||
| * @param {ctx} ctx Canvas context | ||
@@ -14,22 +14,23 @@ * @param {string} text Text to be displayed | ||
| text.forEach(({ text, color, shadow, font, modifier }) => { | ||
| for (let i = 0; i < text.length; i++) { | ||
| ctx.fillStyle = color; | ||
| ctx.fillStyle = text[i].color; | ||
| if (modifier == undefined) modifier = ''; | ||
| ctx.font = `${modifier} ${fontSize}px ${font}`; | ||
| if (shadow) { | ||
| ctx.shadowColor = shadow.color; | ||
| ctx.shadowOffsetX = shadow.offset[0] * fontSize / 100; | ||
| ctx.shadowOffsetY = shadow.offset[1] * fontSize / 100; | ||
| ctx.shadowBlur = shadow.blur; | ||
| if (text[i].modifier == undefined) text[i].modifier = ''; | ||
| ctx.font = `${text[i].modifier} ${fontSize}px ${text[i].font}`; | ||
| if (text[i].shadow) { | ||
| ctx.shadowColor = text[i].shadow.color; | ||
| ctx.shadowOffsetX = text[i].shadow.offset[0] * fontSize / 100; | ||
| ctx.shadowOffsetY = text[i].shadow.offset[1] * fontSize / 100; | ||
| ctx.shadowBlur = text[i].shadow.blur; | ||
| } else ctx.shadowColor = "transparent"; | ||
| ctx.fillText(text, x, y); | ||
| x += ctx.measureText(text).width + spaceWidth; | ||
| }); | ||
| ctx.fillText(text[i].text, x, y); | ||
| x += ctx.measureText(text[i].text).width + spaceWidth; | ||
| } | ||
| ctx.restore(); | ||
@@ -36,0 +37,0 @@ } |
+29
-125
| import paintText from './paintText.js'; | ||
| import { getTotalWidth } from './textAttr.js'; | ||
| /** | ||
| * | ||
| * Text that adjusts to the size of the canvas | ||
| * @param {ctx} ctx Canvas context | ||
| * @param {object} text Text to be displayed in the text box | ||
| * @param {int} fontSize Maximum font size of the text | ||
| * @param {int} spaceWidth Width of the space between words | ||
| * @returns {int} Width of the text | ||
| */ | ||
| function getTotalWidth(ctx, text, fontSize, spaceWidth=0) { | ||
| let width = 0; | ||
| text.forEach(({ text, font, modifier }) => { | ||
| if (modifier == undefined) modifier = ''; | ||
| ctx.font = `${modifier} ${fontSize}px ${font}`; | ||
| width += ctx.measureText(text).width + spaceWidth; | ||
| }); | ||
| return width - spaceWidth; | ||
| } | ||
| /** | ||
| * | ||
| * @param {ctx} ctx Canvas context | ||
| * @param {object} text Text to be displayed in the text box | ||
| * @param {int} fontSize Maximum font size of the text | ||
| * @returns {int} Height of the text | ||
| */ | ||
| function getTotalHeight(ctx, text, fontSize) { | ||
| let wordsTotal = ""; | ||
| text.forEach(({ text, font }) => { | ||
| ctx.font = fontSize + "px " + font; | ||
| wordsTotal += text; | ||
| }); | ||
| const metrics = ctx.measureText(wordsTotal); | ||
| return metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; | ||
| } | ||
| /** | ||
| * | ||
| * @param {ctx} ctx Canvas context | ||
| * @param {x} x X coordinate of the text box | ||
@@ -55,3 +15,3 @@ * @param {y} y Y coordinate of the text box | ||
| */ | ||
| function textBox (ctx, x, y, width, height, text, fontSize, align=['middle', 'center']) { | ||
| function textBox(ctx, x, y, width, height, text, fontSize, align=[1, 1]) { | ||
@@ -64,94 +24,38 @@ ctx.save(); | ||
| let textHeight = getTotalHeight(ctx, text, fontSize); | ||
| textWidth = getTotalWidth(ctx, text, fontSize); | ||
| // Calculate the position of the text | ||
| if (align[0] === 'top') y += textHeight | ||
| if (align[0] === 'bottom') y += height; | ||
| if (align[0] === 'middle') y += (textHeight + height) / 2; | ||
| if (align[1] === 'center') x += width / 2 - textWidth / 2; | ||
| if (align[1] === 'right') x += width - textWidth; | ||
| if (align[1] === 'left') x += 0; | ||
| paintText(ctx, text, x, y, fontSize); | ||
| ctx.restore() | ||
| } | ||
| /** | ||
| * | ||
| * @param {ctx} ctx Canvas context | ||
| * @param {int} x X coordinate of the text box | ||
| * @param {int} y Y coordinate of the text box | ||
| * @param {int} width Width of the text box | ||
| * @param {object} text Text to be displayed in the text box | ||
| * @param {int} fontSize Maximum font size of the text | ||
| * @param {string} align Alignment of the text | ||
| */ | ||
| function wrapText(ctx, x, y, width, text, fontSize, align='left') { | ||
| ctx.save(); | ||
| let lines = []; | ||
| let line = []; | ||
| let spaceWidth = 10; | ||
| for (let i in text) { | ||
| let splitText = text[i].text.split(' '); | ||
| let j = 0; | ||
| for (j in splitText) { | ||
| let textRich = { | ||
| color: text[i].color, | ||
| text: splitText[j], | ||
| font: text[i].font, | ||
| modifier: text[i].modifier, | ||
| shadow: text[i].shadow | ||
| }; | ||
| let lineWidth = getTotalWidth(ctx, line, fontSize, spaceWidth); | ||
| let textWidth = getTotalWidth(ctx, [textRich], fontSize, spaceWidth); | ||
| // TODO: Fix this | ||
| if (lineWidth + textWidth + 10 > width) { | ||
| lines.push(line); | ||
| line = []; | ||
| } | ||
| line.push(textRich); | ||
| } | ||
| switch (align[0]) { | ||
| case 0: | ||
| y += fontSize; | ||
| break; | ||
| case 1: | ||
| y += (fontSize + height) / 2; | ||
| break; | ||
| case 2: | ||
| y += height; | ||
| break; | ||
| } | ||
| lines.push(line); | ||
| for (let i in lines) { | ||
| let textWidth = getTotalWidth(ctx, lines[i], fontSize, spaceWidth); | ||
| let x_ = x; | ||
| if (align === 'center') x_ += width / 2 - textWidth / 2; | ||
| if (align === 'right') x_ += width - textWidth; | ||
| if (align === 'left') x_ += 0; | ||
| if (align === 'justify') { | ||
| let rawTextWidth = getTotalWidth(ctx, lines[i], fontSize); | ||
| spaceWidth = (width - rawTextWidth) / (lines[i].length - 1); | ||
| if (spaceWidth < 4) spaceWidth = 4; | ||
| if (spaceWidth > 50) spaceWidth = 10; | ||
| } | ||
| paintText(ctx, lines[i], x_, y+fontSize*(i)+fontSize, fontSize, spaceWidth); | ||
| switch (align[1]) { | ||
| case 1: | ||
| x += (width - textWidth) / 2; | ||
| break; | ||
| case 2: | ||
| x += width - textWidth; | ||
| break; | ||
| } | ||
| paintText(ctx, text, x, y, fontSize); | ||
| ctx.restore(); | ||
| return { | ||
| x: x, | ||
| y: y, | ||
| width: textWidth, | ||
| height: fontSize, | ||
| }; | ||
| } | ||
| export { textBox, getTotalWidth, getTotalHeight, wrapText }; | ||
| export { textBox }; |
+7
-5
| { | ||
| "name": "cherry-box", | ||
| "version": "1.2.3", | ||
| "version": "1.3.0", | ||
| "description": "A node-canvas package filled with utilities, manage text boxes easily and more", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "test": "node test.js" | ||
| "test": "jest" | ||
| }, | ||
@@ -16,9 +16,11 @@ "keywords": [ | ||
| "type": "module", | ||
| "dependencies": { | ||
| "canvas": "^2.9.1" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/CheryX/cherry-box.git" | ||
| }, | ||
| "devDependencies": { | ||
| "@babel/plugin-transform-modules-commonjs": "^7.17.9", | ||
| "canvas": "^2.9.1", | ||
| "jest": "^28.1.0" | ||
| } | ||
| } |
+16
-10
@@ -25,3 +25,3 @@ # About | ||
| ] | ||
| textBox(800, 600, textSchema, 200, ['middle', 'center']); | ||
| textBox(800, 600, textSchema, 200, [1, 1]); | ||
| ``` | ||
@@ -88,12 +88,18 @@ | ||
| ### textBox Errors | ||
| Code | Description | ||
| --- | --- | ||
| 601 | Text is too big to be displayed | ||
| ### Align values | ||
| 1. Horizontal: `left`, `center` or `right` | ||
| 2. Vertical: `top`, `middle` or `bottom` | ||
| 1. Horizontal: left `0`, center `1`, right `2` | ||
| 2. Vertical: top `0`, middle `1`, bottom `2` | ||
| Example: `['top', 'middle']` | ||
| Example: `[1,1]` | ||
| Example use of textBox in your code: | ||
| ```js | ||
| textBox(ctx, 0, 0, canvas.width, canvas.height-20, upperText, 80, ["bottom", "center"]); | ||
| textBox(ctx, 0, 0, canvas.width, canvas.height-20, upperText, 80, [2,1]); | ||
| ``` | ||
@@ -119,10 +125,10 @@ | ||
| * `left` | ||
| * `center` | ||
| * `right` | ||
| * `justify` | ||
| * left `0` | ||
| * center `1` | ||
| * right `2` | ||
| * justify `3` | ||
| Example use of `wrapText` in your code: | ||
| ```js | ||
| wrapText(ctx, 0, 0, canvas.width, wrapTextBox, 20, 'justify'); | ||
| wrapText(ctx, 0, 0, canvas.width, wrapTextBox, 20, 3); | ||
| ``` |
| function paintText(t,e,o,l,i,n=0){t.save(),e.forEach(({text:e,color:r,shadow:a,font:f,modifier:h})=>{t.fillStyle=r,null==h&&(h=""),t.font=`${h} ${i}px ${f}`,a?(t.shadowColor=a.color,t.shadowOffsetX=a.offset[0]*i/100,t.shadowOffsetY=a.offset[1]*i/100,t.shadowBlur=a.blur):t.shadowColor="transparent",t.fillText(e,o,l),o+=t.measureText(e).width+n}),t.restore()}function getTotalWidth(t,e,o,l=0){let i=0;return e.forEach(({text:e,font:n,modifier:r})=>{null==r&&(r=""),t.font=`${r} ${o}px ${n}`,i+=t.measureText(e).width+l}),i-l}function getTotalHeight(t,e,o){let l="";e.forEach(({text:e,font:i})=>{t.font=o+"px "+i,l+=e});const i=t.measureText(l);return i.actualBoundingBoxAscent+i.actualBoundingBoxDescent}function textBox(t,e,o,l,i,n,r,a=["middle","center"]){t.save();let f=getTotalWidth(t,n,r);f>l&&(r*=l/f);let h=getTotalHeight(t,n,r);f=getTotalWidth(t,n,r),"top"===a[0]&&(o+=h),"bottom"===a[0]&&(o+=i),"middle"===a[0]&&(o+=(h+i)/2),"center"===a[1]&&(e+=l/2-f/2),"right"===a[1]&&(e+=l-f),"left"===a[1]&&(e+=0),paintText(t,n,e,o,r),t.restore()}function wrapText(t,e,o,l,i,n,r="left"){t.save();let a=[],f=[],h=10;for(let e in i){let o=i[e].text.split(" "),r=0;for(r in o){let s={color:i[e].color,text:o[r],font:i[e].font,modifier:i[e].modifier,shadow:i[e].shadow},d=getTotalWidth(t,f,n,h),x=getTotalWidth(t,[s],n,h);d+x+10>l&&(a.push(f),f=[]),f.push(s)}}a.push(f);for(let i in a){let f=getTotalWidth(t,a[i],n,h),s=e;if("center"===r&&(s+=l/2-f/2),"right"===r&&(s+=l-f),"left"===r&&(s+=0),"justify"===r){let e=getTotalWidth(t,a[i],n);h=(l-e)/(a[i].length-1),h<4&&(h=4),h>50&&(h=10)}paintText(t,a[i],s,o+n*i+n,n,h)}t.restore()}export{textBox,getTotalWidth,getTotalHeight,wrapText,paintText}; |
-42
| //import { textBox, wrapText } from "./lib/textBox.js"; | ||
| import { textBox, wrapText } from "./index.min.js"; | ||
| import Canvas from "canvas"; | ||
| import fs from "fs"; | ||
| const canvas = new Canvas.createCanvas(800, 600); | ||
| const ctx = canvas.getContext("2d"); | ||
| let upperText = [ | ||
| { | ||
| text: "POV: Linux user", | ||
| color: "white", | ||
| modifier: "bold", | ||
| font: "ubuntu", | ||
| shadow: { | ||
| color: "red", | ||
| offset: [0, 0], | ||
| blur: 10 | ||
| } | ||
| } | ||
| ]; | ||
| textBox(ctx, 0, 0, canvas.width, canvas.height-20, upperText, 80, ["bottom", "center"]); | ||
| let wrapTextBox = [ | ||
| { | ||
| text: "I'd like to interject for a moment, what you reffered to as Linux was in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called \"Linux\", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. The Free Software Foundation obviously intends Linux to be free software like any other free program, and whoever works for the Free Software Foundation will do whatever it takes to ensure that the GNU system remains free. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called \"Linux\" distributions are really distributions of GNU/Linux.", | ||
| color: "yellow", | ||
| font: 'Arial', | ||
| modifier: "bold", | ||
| shadow: { | ||
| color: "red", | ||
| offset: [10, 10], | ||
| blur: 10 | ||
| } | ||
| } | ||
| ] | ||
| wrapText(ctx, 25, 25, canvas.width-50, wrapTextBox, 20, 'justify'); | ||
| // Save canvas to file | ||
| let out = fs.createWriteStream("./out.png"); | ||
| let stream = canvas.createPNGStream(); | ||
| stream.pipe(out); |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
0
-100%16
23.08%281
11.51%1
-50%131
4.8%2
-33.33%21078
-0.09%3
Infinity%1
Infinity%- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed