How to wrap Chinese font in Canvas - html

I used canvas.fillText to draw Chinese font in canvas, but the words didn't wrap. I read the canvas tutorial here, but it splits words using space, which won't work for Chinese fonts. Could someone have an experience on this or just show me where to find the solution?
Thank you.

You will need to use the measureText() to measure line width by adding one and one glyph until you get a value exceeding the available space which is where you wrap the text.
For example - I made this loop which will wrap the line when there is no more space available:
ONLINE DEMO HERE
var txt = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
i = 0, ...;
/// loop trough the txt which holds the string
for(; i < txt.length; i++) {
/// measure current width
lw = ctx.measureText(line).width;
/// if within available space add a char to line
if (lw < w - ctx.measureText(txt[i]).width) {
line += txt[i];
} else {
/// didn't fit so draw what we have
ctx.fillText(line, x, y);
/// reset line with the left-over char
line = txt[i];
/// reset x (not used in demo) and increment y position
x = 0;
y += 50;
}
}
/// if anything was left in line draw it here
if (line.length > 0) ctx.fillText(line, x, y);
PS: I didn't have a Chinese font handy for the demo but you'll see the principle.

Related

Adding Letter Spacing in HTML Canvas

I've read a lot of StackOverflow answers and other pages talking about how to do letter spacing in Canvas. One of the more useful ones was Letter spacing in canvas element
As that other question said, 'I've got this canvas element that I'm drawing text to. I want to set the letter spacing similar to the CSS letter-spacing attribute. By that I mean increasing the amount of pixels between letters when a string is drawn.' Note that letter spacing is sometimes, and incorrectly, referred to as kerning.
I notice that the general approach seems to be to output the string on a letter by letter basis, using measureText(letter) to get the letter's width and then adding additional spacing. The problem with this is it doesn't take into account letter kerning pairs and the like. See the above link for an example of this and related comments.
Seems to me that the way to do it, for a line spacing of 'spacing', would be to do something like:
Start at position (X, Y).
Measure wAll, the width of the entire string using measureText()
Remove the first character from the string
Print the first character at position (X, Y) using fillText()
Measure wShorter, the width of the resulting shorter string using measureText().
Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
Increment X by wChar + spacing
wAll = wShorter
Repeat from step 3
Would this not take into account kerning? Am I missing something? Does measureText() add a load of padding that varies depending on the outermost character, or something, and if it does, would not fillText() use the same system to output the character, negating that issue? Someone in the link above mentioned 'pixel-aligned font hinting' but I don't see how that applies here. Can anyone advise either generally or specifically if this will work or if there are problems with it?
EDIT: This is not a duplicate of the other question - which it links to and refers to. The question is NOT about how to do 'letter spacing in canvas', per the proposed duplicate; this is proposing a possible solution (which as far as I know was not suggested by anyone else) to that and other questions, and asking if anyone can see or knows of any issues with that proposed solution - i.e. it's asking about the proposed solution and its points, including details of measureText(), fillText() and 'pixel-aligned font hinting'.
Well, I've written the code, based on the pseudocode above, and done a few comparisons by screenshotting and eyeballing it for differences (zoomed, using straight lines from eg clip boxes to compare X position and width for each character). Looks exactly the same for me, with spacing set at 0.
Here's the HTML:
<canvas id="Test1" width="800px" height="200px"><p>Your browser does not support canvas.</p></canvas>
Here's the code:
this.fillTextWithSpacing = function(context, text, x, y, spacing)
{
//Start at position (X, Y).
//Measure wAll, the width of the entire string using measureText()
wAll = context.measureText(text).width;
do
{
//Remove the first character from the string
char = text.substr(0, 1);
text = text.substr(1);
//Print the first character at position (X, Y) using fillText()
context.fillText(char, x, y);
//Measure wShorter, the width of the resulting shorter string using measureText().
if (text == "")
wShorter = 0;
else
wShorter = context.measureText(text).width;
//Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
wChar = wAll - wShorter;
//Increment X by wChar + spacing
x += wChar + spacing;
//wAll = wShorter
wAll = wShorter;
//Repeat from step 3
} while (text != "");
}
Code for demo/eyeball test:
element1 = document.getElementById("Test1");
textContext1 = element1.getContext('2d');
textContext1.font = "72px Verdana, sans-serif";
textContext1.textAlign = "left";
textContext1.textBaseline = "top";
textContext1.fillStyle = "#000000";
text = "Welcome to go WAVE";
this.fillTextWithSpacing(textContext1, text, 0, 0, 0);
textContext1.fillText(text, 0, 100);
Ideally I'd throw multiple random strings at it and do a pixel by pixel comparison. I'm also not sure how good Verdana's default kerning is, though I understand it's better than Arial - suggestions on other fonts to try gratefully accepted.
So... so far it looks good. In fact it looks perfect.
Still hoping that someone will point out any flaws in the process.
In the meantime I will put this here for others to see if they are looking for a solution on this.
My answer got deleted.
So, I'm using chrome and here is my complete code.
second_image = $('#block_id').first();
canvas = document.getElementById('canvas');
canvas.style.letterSpacing = '2px';
ctx = canvas.getContext('2d');
canvas.crossOrigin = "Anonymous";
canvasDraw = function(text, font_size, font_style, fill_or_stroke){
canvas.width = second_image.width();
canvas.height = second_image.height();
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(second_image.get(0), 0, 0, canvas.width, canvas.height);
//refill text
ctx.font = font_size +'px '+ font_style + ',Symbola';
$test = ctx.font;
ctx.textAlign = "center";
if(fill_or_stroke){
ctx.fillStyle = "#d2b76d";
ctx.strokeStyle = "#9d8a5e";
ctx.strokeText(text,canvas.width*$left,canvas.height*$top);
ctx.fillText(text,canvas.width*$left,canvas.height*$top);
}
else{
ctx.strokeStyle = "#888888";
ctx.strokeText(text,canvas.width*$left,canvas.height*$top);
}
};
And you don't need to use this function this.fillTextWithSpacing. I didn't use and it worked like a charm)

HTML draw arrows behind text

Can html5 canvas do the following? If yes, how...
Be places behind bottom layer place behind HTML text
Can you accurately find the coordinates specified HTML texts (perhaps identified with span ID) regardless the of browser zoom size, or line wrap
I am trying to create the following with HTML/CSS/JS:
(please excuse the green squiggly underlines)
The highlighted text could obviously be set with background-color:
The tricky part is connecting the highlighted text with arrows, I would think it might be able to be done with HTLM canvas, but I am open to any ideas.
Also nice little bonus would be the have highlighting/arrows appear on hover or maybe on off button.
PS a little background, the text is some simplified JCL (sort of scripting language for Mainframes) and the highlighted items are files. I am attempting to make it easier to trace the data flow through a job (script). This is pretty simple version but many jobs can be 100s of lines long with lot details that make it hard to trace the which steps related to each other. If there other ideas or tools to help trace the data flow in JCL let me know.
//COBLPGM EXEC PGM=COBLPGM
//INPUT DD DSN=&&SORT,DISP=(OLD,DELETE)
//NACHA DD DSN=NODE.OPER.COBLPGM.OUT(+1)
//SORT2 EXEC PGM=SORT
//SORTIN DD DSN=NODE.OPER.COBLPGM.OUT(+1)
//SORTOUT DD DSN=&&SORT2,DISP=(,PASS)
//SYSIN DD DSN=NODE.OPER.PROCLIB(MEM)
//UNRELATE EXEC PGM=UNPGM
//INPUT DD DSN=NODE.OPER.UNRELATED.FILE
//REPORT DD DSN=&&REPORT
//TSTEMPT1 EXEC PGM=SPOPNCLO
//IN DD DSN=&&SORT2,DISP=(OLD,DELETE)
// IF TSTEMPT1.RC=0 THEN
//SORT3 EXEC PGM=SORT
//SORTIN DD DSN=NODE.OPER.COBLPGM.OUT(+1)
//SORTOUT DD DSN=&&SORT3,DISP=(,PASS),LRECL=141
//SYSIN DD DSN=NODE.OPER.CNTRLCDS(PARM)
// ENDIF
This is just a "conceptual" answer showing that you can track HTML to synchronize a canvas element.
The following code has the text itself in a <pre> tag in HTML. There is a canvas in the background set with fixed size. The canvas is updated on scrolling so the boxes are drawn relative to page (it should also be updated on resize, not shown).
As we can track the text you can see we would also be able to place any other graphics relative to it as well such as arrows and lines. I have not shown this here as I feel it would be too broad, but you should get the gist of it as it shows how to calculate the text line and char positions.
The basis is:
Get absolute position of the <pre> tag
Count number of lines (be careful to place text right after the tag and not on a new line, as well as placing end-tag at the same line as the last text-line)
Dividing absolute height on number of lines will give the line-height in pixels for each line
Use measureText() of context to measure the width of each line by setting context to use the same font and size as the <pre> tag
Use the rectangle from previous pre-tag to offset x and y for the line position.
Each char is calculate using the chars preceding the current, with measureText() (cell being this position and that of the next char).
The text is kept selectable with the canvas marking areas in the background.
Note that special chars in the text-line may throw off measureText (such as && in the example text). These chars must be encoded or replaced before measuring. Replacing is not a problem with a monospaced font such as in this case.
Demo
var pre = document.querySelector("pre"), // get pre ele,ent
rect = pre.getBoundingClientRect(), // get its absolute position
lines = pre.innerHTML.split("\n"), // split text lines
count = lines.length, // count lines
lineH = rect.height / count, // line height
canvas = document.querySelector("canvas"), // setup canvas
ctx = canvas.getContext("2d");
canvas.width = window.innerWidth; // todo: update on resize
canvas.height = window.innerHeight;
ctx.font = "14px monospace"; // use same font in canvas as for pre
ctx.strokeStyle = "#d00";
ctx.translate(0.5, 0.5); // makes lines sharper for demo
window.onscroll = drawBoxes; // we need to track scrolling
drawBoxes();
function drawBoxes() { // render line boxes (y)
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < count; i++) {
var w = ctx.measureText(lines[i]).width;
if (w) ctx.strokeRect(rect.left, rect.top + i * lineH - window.scrollY, w, lineH - 1);
showChars(lines[i], rect.top + i * lineH - window.scrollY, lineH);
}
}
function showChars(line, y, h) { // render char lines (x)
ctx.beginPath();
for(var i = 0, ch, x, s = ""; ch = line[i]; i++) {
s += ch;
x = ctx.measureText(s).width;
ctx.moveTo(x, y); ctx.lineTo(x, y + h - 1);
}
ctx.globalAlpha = 0.2;
ctx.stroke();
ctx.globalAlpha = 1;
}
canvas {position:fixed;left:0;top:0;z-index:-1}
pre {font:14px monospace}
<canvas></canvas>
<pre>//COBLPGM EXEC PGM=COBLPGM
//INPUT DD DSN=SORT,DISP=(OLD,DELETE)
//NACHA DD DSN=NODE.OPER.COBLPGM.OUT(+1)
//SORT2 EXEC PGM=SORT
//SORTIN DD DSN=NODE.OPER.COBLPGM.OUT(+1)
//SORTOUT DD DSN=SORT2,DISP=(,PASS)
//SYSIN DD DSN=NODE.OPER.PROCLIB(MEM)
//UNRELATE EXEC PGM=UNPGM
//INPUT DD DSN=NODE.OPER.UNRELATED.FILE
//REPORT DD DSN=REPORT
//TSTEMPT1 EXEC PGM=SPOPNCLO
//IN DD DSN=SORT2,DISP=(OLD,DELETE)
// IF TSTEMPT1.RC=0 THEN
//SORT3 EXEC PGM=SORT
//SORTIN DD DSN=NODE.OPER.COBLPGM.OUT(+1)
//SORTOUT DD DSN=SORT3,DISP=(,PASS),LRECL=141
//SYSIN DD DSN=NODE.OPER.CNTRLCDS(PARM)
// ENDIF</pre>

Is there a way to convert "QRST-code" to longitude/latitude?

I did a lot of research for that topic - but it seems not enough, so I'm here asking for help :-)
Google Maps could use QRST-code for specifing a location. I've got a line like that:
trtqtqsss...
and so on. In some other forums I've found out that GM once used that in an URL-Syntax. But now it seems it doesn't work anymore - or at least I don't know how.
Here is an example of the link that won't work anymore:
kh0.google.com/kh?n=404&v=8&t=tq
kh1.google.com/kh?n=404&v=8&t=tr
In this URL, the quadrants are specified with the string after t=.
Is there a converter or something like that?
Thank you in advance!
Partial answer:
From what I gather, the long string of trtqtqss indicates, in essence, a binary search for the location. It roughly translates like this:
Start with the letter t. This gives you "the sholw world"
Look for your point on the map. If it's in the top left quadrant, add a q. If top right, add r. Bottom right, add s. Bottom left, add t.
Zoom in on the new quadrant. Repeat.
Every time you add a letter you halve the size of the tile, and find a new bottom left corner. If we think of the world map as a rectangle of width and height = 1, we can find a new corner for each character added. This is the essence of the algorithm you linked in your comment.
With that, plus the "Rosetta stone" (again from your link) of a known string-to-satellite image translation, I give you the following code. This will give you the Longitude/Latitude of a point based on your string. Compile it, then pass the string as argument to the executable:
#include <stdio.h>
#include <string.h>
#include <math.h>
double NormalToMercator(double y) {
double pi;
pi = 2 * asin(1);
y -= 0.5;
y *= 2 * pi;
y = exp( 2 * y );
y = ( y - 1 ) / ( y + 1 );
y = -asin( y );
return -y * 180 / pi;
}
int main(int argc, char* argv[]) {
double x=0, y=0, scale=1;
char buf[100]={' '};
int ii;
buf[0]=argv[1][0];
for(ii = 1; ii < strlen(argv[1]); ii++) {
buf[ii-1]=argv[1][ii];
scale *= 0.5;
switch (tolower(argv[1][ii])) {
case 'q':
y+=scale;
break;
case 'r':
y+=scale;
x+=scale;
break;
case 's':
x+=scale;
break;
case 't':
break;
default:
break;
}
printf("the string %s gets you to (x,y): %.9lf, %.9lf\n", \
buf, x, y);
}
printf("the final lat/long is %.5lf, %.5lf\n", 360.0 * (x - 0.5), NormalToMercator(y));
}
The intermediate printf statement is there to show you how the algorithm is slowly making its way to the right location. I tested this with the string from the link in your comment (tsrrtrsqsqqqrqrtsst), and got the coordinates 153.39935ºE 28.32372ºS (note - a negative number for longitude means "W", and a negative number for latitude means "S". I got 153.39935, -28.32372). When I entered those in Google maps, I got the picture of the hospital that you get when entering the link from blog post.

HTML5: Inverse text-color on canvas

I want to draw text on a canvas in the inverse color of the background (to make sure the text is readible no matter the background color). I believe in oldskool bitblt-ing, this was an XOR operation.
How to do this?
Update: most of the newer browsers now support the blending mode "difference" which can achieve the same result.
context.globalCompositeOperation = "difference";
Updated demo.
Old answer:
One should think that the XOR mode for composition would do this, but unfortunately canvas' XOR only XORs the alpha bits.
By applying the following code we can however receive a result such as this:
You can make an extension to the canvas like this:
CanvasRenderingContext2D.prototype.fillInversedText =
function(txt, x, y) {
//code - see below
}
Now you can call it on the context as the normal fillText, but with a slight change:
ctx.fillInversedText(txt, x, y);
For this to work we do the following first - measure text. Currently we can only calculate width of text and then assume the height. This may or may not work well as fonts can be very tall and so forth. Luckily this will change in the future, but for now:
var tw = this.measureText(txt).width;
var th = parseInt(ctx.font, '10');
th = (th === 0) ? 10 : th; //assume default if no font and size is set
Next thing we need to do is to setup an off-screen canvas to draw the text we want ot invert:
var co = document.createElement('canvas');
co.width = tw;
co.height = th;
Then draw the actual text. Color does not matter as we are only interested in the alpha channel for this canvas:
var octx = co.getContext('2d');
octx.font = this.font;
octx.textBaseline = 'top';
octx.fillText(txt, 0, 0);
Then we extract the pixel buffers for the area we want to draw the inverted text as well as all the pixels for the off-screen canvas which now contains our text:
var ddata = this.getImageData(x, y, tw, th);
var sdata = octx.getImageData(0, 0, tw, th);
var dd = ddata.data; //cache for increased speed
var ds = sdata.data;
var len = ds.length;
And then we invert each pixel where alpha channel for pixel is greater than 0.
for (var i = 0; i < len; i += 4) {
if (ds[i + 3] > 0) {
dd[i] = 255 - dd[i];
dd[i + 1] = 255 - dd[i + 1];
dd[i + 2] = 255 - dd[i + 2];
}
}
Finally put back the inverted image:
this.putImageData(ddata, x, y);
This may seem as a lot of operations, but it goes pretty fast.
Demo (warning if you are sensitive to flicker)
(the psychedelic background is just to have some variations as fiddle needs external images and most are prevented by CORS when we use pixel manipulation).
I've removed my old answer, as it did not solve the question. As of recently, there are new globalCompositeOperations that do all kinds of great things. I've created an example that shows how to obtain inverted text. In case that link breaks, the method is essentially this:
ctx.globalCompositeOperation = "difference";
ctx.fillStyle = "white";
//draw inverted things here
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation

What does the mask parameter do in the threshold method of the BitmapData class?

I'm trying to replace a color and colors near it in a bitmap.
threshold() seems to work but it seems to be that you have to specify the exact color "==" or all colors before or after the exact color "<" & ">" plus "<=" and ">=". I am hoping that the mask parameter will help me find a way to find a color and a dynamic range of colors before and after it to be replaced. What is its intended usage?
Per the comment below Example 1 and 2:
bit.threshold(bit, bit.rect, point, ">", 0xff000000, 0xffff0000, 0x00FF0000);
bit.threshold(bit, bit.rect, point, ">", 0xff000000, 0xffff0000, 0x00EE0000);
If you're trying to do a flood fill, I don't think the mask parameter will help you. The mask parameter lets you ignore parts of the color in the test. In your case, you want to take into account all the channels of the color, you just want the matching to be fuzzy.
e.g. If you want to replace all pixels where the red component is 0, you can set mask to 0x00FF0000, so it will ignore the other channels.
The implementation pseudo-code probably looks something like this:
input = readPixel()
value = input & mask
if(value operation threshold)
{
writePixel(color)
}
Neither of your samples will produce anything because the mask limits the values to be between 0x00000000 and 0x00FF0000, then tests if they're greater than 0xFF000000.
I have also done this and eventually, I have found it best to create my own threshold-method. You can find it below. Everything is explained in comment.
//_snapshot is a bitmapData-object
for(var i:int = 0; i <= _snapshot.width; i++)
{
for(var j:int = 0; j <= _snapshot.height; j++)
{
//We get the color of the current pixel.
var _color:uint = _snapshot.getPixel(i, j);
//If the color of the selected pixel is between certain values set by the user,
//set the filtered pixel data to green.
//Threshold is a number (can be quite high, up to 50000) to look for adjacent colors in the colorspace.
//_colorToCompare is the color you want to look for.
if((_colorToCompare - (100 * _threshold)) <= _color && _color <= (_colorToCompare + (100 * _threshold)))
{
//This sets the pixel value.
_snapshot.setPixel(i, j, 0x00ff00);
}
else
{
//If the pixel color is not within the desired range, set it's value to black.
_snapshot.setPixel(i, j, 0x000000);
}
}
}