Calculating path length of test [closed] - javascript

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I am trying to solve a problem where i need to know the length of the path of the text typed by the user. Example a user Types "Hello World" i need to check what will be the length of the path of Hello world when typed in a certain font with a certain font size(in pt).
The length that i will get i need to convert it into meters to i can use it in real world to build the Neon.
How should i approach the problem are there any libraries that can help me do this?
Thanks

I don't have any specific libraries to recommend, but here is a snippet of how you might go about doing what you want. The conversion from pixels to centimeters is approximate here, it's just a demo after all.
const getRuler = element => {
const ctx = document.createElement('canvas').getContext('2d');
const style = window.getComputedStyle(element);
const fontSize = style.getPropertyValue('font-size');
const fontFamily = style.getPropertyValue('font-family');
ctx.font = `${fontSize} ${fontFamily}`;
return ctx;
}
const element = document.querySelector('#my-element-with-text');
function updateLength(e) {
const element = e.target
const text = element.innerText;
const ruler = getRuler(element);
const length = ruler.measureText(text).width;
const result = document.querySelector('#length');
result.innerText = (length * 0.026458).toFixed(4) + ' centimeters';
}
element.addEventListener('keyup', updateLength);
body {
height: 100%;
width: 100%;
margin: 0;
}
main {
margin-top: 15vh;
margin-left: 10vw;
width: 85%;
}
#my-element-with-text {
border: 1px solid black;
border-radius: 3px;
height: 2em;
font-size: 5vw;
width: fit-content;
min-width: 200px;
line-height: 2em;
padding: 0 12px;
font-family: 'Helvetica Neue', sans-serif;
}
<main>
<div>Enter text here</div>
<div id='my-element-with-text' contenteditable='true'></div>
<div id='length'></div>
</main>

This is a multistep process, and for now I don't have all the answers.
Load the font to use as a webfont. Normally, this means you go to a provider like Google fonts and get a link to a stylesheet that will do the work for you, including deciding which font format your browser will understand. But in this case, you need the full address of the final font file. If you provide the font file on your own server, this would be trivial.
Since you ultimately need the file content itself, the best strategy is probably to load the font file via an AJAX request and then both insert it into the document font resources and to load it also into a special parser that gives access to the font outline data. This could be opentype.js - note it is a major piece of software with 162kB minified. As soon as the font is loaded, you can start to look for the text you want to measure:
window.fetch(fontUrl).then(response => {
// read response into an ArrayBuffer
return response.arrayBuffer();
}).then(buffer => {
// load into parser
const parsedFont = opentype.parse(buffer);
// load into browser resources, family is the name for CSS reference
const fontFace = new FontFace(family, buffer);
document.fonts.add(fontFace);
// add an event listener to your text input
document.querySelector('#textToMeasure').addEventListener('change', event => {
textToOutline(parsedFont, event.target.value);
});
});
Each time the text input changes, you now render that text to an SVG path. For that, you need to have an (invisible) SVG snippet in your page:
<svg width="0px" height="0px"><path id="outlinePath" d="" /></svg>
You get the path data by providing the text and the font size to the font object and then set them for your SVG <path>:
function textToOutline (font, text) {
// size means font size in pixels
const pathData = font.getPath(text, 0, 0, size).toPathData(4);
// write to path
const path = document.querySelector('#outlinePath');
path.setAttribute('d', pathData);
measureOutline(path);
}
Now the text outline is in a format that the browser can measure with its own API.
function measureOutline (path) {
// length in pixels
const pathLength = path.getTotalLength();
// factor would be the conversion factor to your intended unit,
// and unit the unit name
document.querySelector('#lengthResult').innerText = (pathLength * factor) + unit;
}

Related

Measure text width according to CSS without rendering it

I want to measure the theoretical width of some to-be-rendered text.
I like this method for getting text width, because it doesn't mutate the DOM
function getTextWidth(text, font) {
// re-use canvas object for better performance
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
context.font = font;
var metrics = context.measureText(text);
return metrics.width;
}
I have a CSS description of the font
.dx-widget {
color: #333;
font-weight: 400;
font-size: 14px;
font-family: "Helvetica Neue","Segoe UI",helvetica,verdana,sans-serif;
line-height: 1.35715;
}
How can I get in JavaScript the width that different strings would be according to these CSS rules?
I don't think I can apply this CSS class to the canvas to make it work.
Ok. I created a snippet.
By assigning a dummy element style (or of a real element) to a variable it is much easier to correct the code. In the console in the Developer Tools of any browser you can enter the name of the variable and see all properties.
Test and if it works for you- use it ;)
Or as Stephen P suggested in the comments- create a new empty span and you do not have to risc changing some real visible element :)
Vote his comment. It is great improvement.
var stylee=document.getElementById('test_element').style;
stylee.color="#333";
stylee.fontWeight="400";
stylee.fontSize="14px";
stylee.fontFamily='"Helvetica Neue","Segoe UI",helvetica,verdana,sans-serif';
stylee.lineHeight="1.35715";
function getTextWidth(text, font) {
// re-use canvas object for better performance
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
context.font = font;
var metrics = context.measureText(text);
return metrics.width;
}
document.getElementById('result').innerText=getTextWidth('some text or variable', stylee);
<span id="test_element"></span>
<div id="result"></div>
By reading https://developer.mozilla.org/en-US/docs/Web/CSS/font I have understood that .font can actually specify all of the information from that CSS.
line-height must immediately follow font-size, preceded by "/", like this: "16px/3"
font-weight must precede font-size
font-family must be the last value specified
So this CSS
font-weight: 400;
font-size: 14px;
font-family: "Helvetica Neue","Segoe UI",helvetica,verdana,sans-serif;
line-height: 1.35715;
can be compressed into
font: 400 14px/1.35715 "Helvetica Neue","Segoe UI",helvetica,verdana,sans-serif;
and that property value can be used in the original canvas getTextWidth function.

HTML text inside Canvas

I need to render an image on which I have put some HTML text on it and then the user to be able to download the image with the text as an image or even better as a pdf.
The certText element is taken from a TinyMCE variable, where the user each time uses different HTML tags within the text.
So it could be
"Hello user, how are you"
or
"Hi there, great that you succeed on the certification".
Starting from the image option I have created below code but Canvas do no translate the HTML tags.
I have read that with Canvas it's not possible to render the HTML tags, but is there any other alternative to succeed this.
The code is used on a Laravel project that I have made.
var p = document.getElementById("certificate");
var ptx = p.getContext("2d");
var imgp = document.getElementById("certificateEmpty");
var txtp = document.getElementById("certText").innerHTML;
imgp.onload = function(){
ptx.drawImage(imgp, 0, 0);
ptx.font = "48px Nunito";
ptx.fillText(txtp, 150, 1000);
};
imgp.src = "{{$attendees[0]->certBLocation}}";
What are you trying to achive when you say "render html tags"?
Do you mean you want to style the text that is drawn on the canvas?
If so, you have to use canvas functions to set what the text will look like.
In the below example using your code, I use "fillStyle" to set the drawing fill colour in which the text will be drawn.
var p = document.getElementById("certificate");
var ptx = p.getContext("2d");
var imgp = document.getElementById("certificateEmpty");
var txt = document.getElementById("certText");
var txtp = txt.innerHTML;
imgp.onload = function(){
ptx.drawImage(imgp, 0, 0, p.width, p.height);
var styles = window.getComputedStyle(txt);
ptx.fillStyle = styles.color;
//ptx.font = styles.font; // Doesn't seem to work in firefox.
ptx.font = styles['font-weight']+" "+styles['font-size']+" "+styles.fontFamily; // Access properties array style because of hyphens.
ptx.fillText(txtp, 0, 100);
};
imgp.src = "https://www.gravatar.com/avatar/7b67c827ee1671ba3b43f4aebf6794fb?s=200";
img, canvas {
width: 200px;
height: 200px;
}
#certText {
color: #ff0000;
font-weight: bold;
font-size: 40px;
}
<img id="certificateEmpty"/>
<canvas id="certificate"></canvas>
<div id="certText">
some text
</div>
Text drawn on the canvas looks squashed on my screen (Ubuntu/Chrome), not sure why.

Can't get a canvas to display a custom font

Canvas.filltext won't render custom font (though body text will, and canvas.filltext will render local fonts)
I want to paint some text on a canvas, using a custom font called jelleebold. Here's an abbreviated version of the function that I hoped would do that:
function paintLinkNameOnCanvas(linkName, canvas){
let context = canvas.getContext('2d');
context.font = "25px jelleebold";
context.fillText(linkName, 50, 50);
return canvas;
}
However, the font that gets used is whatever the browser (Chrome) uses as its fallback (Times New Roman, I think). Here's the html link
<link href="./CSS/stylesheet.css" rel="stylesheet" type="text/css">
and the css, downloaded from fontsquirrel, and modified to suit my local directory structure:
#font-face {
font-family: jelleebold;
src: url('/fonts/jellee-roman-webfont.woff2') format('woff2'),
url('/fonts/jellee-roman-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
I believe that this has worked, and Jelleebold has loaded successfully, because index.php contains:
<body style ="font-size: 50px; font-family: 'jelleebold'">
<div id= "output" >
test
</div>
etc.
and the word 'test' gets printed in jelleebold.
In contrast, if the paintLinkNameOnCanvas function specifies a font that is installed on the local machine (such as context.font = "25px 'Balford Base'"), the linkname does get painted in that font.
So why isn't the custom font being used to paint the linkname on the canvas?
And after a very helpful suggestion from Renato Bibiano, I've now produced an updated version of the function (shown below). The commented lines are a hint about the next problem; how do I get it to work with both woff- and woff2-formatted fonts? It works with either one, but not the other.
function paintLinkNameOnCanvas(linkName, canvas){
let earl = "";
// earl += "url('/fonts/jellee-roman-webfont.woff' ) format('woff' ";
// earl += ", ";
earl += "url('/fonts/jellee-roman-webfont.woff2')format('woff2')";
let f = new FontFace("jelleebold", earl);
f.load().then(function() {
let context = canvas.getContext('2d');
context.font = '23px jelleebold';
context.fillText('Hey, world', 0, 100);
});
return canvas;
}
I feel that taking the comment slashes out should produce something sensible, but if both specifications (woff and woff2) are included, the ouput reverts to the default font, TNR.

Set Font Awesome icons as cursor - is this possible?

Font Awesome has a very good collection of icons to be used in web projects. I want to use one of those icons as cursor (custom cursor).
In my understanding, Custom Cursors need an image url, but I am unable to find the image urls for Font Awesome icons.
Got it!
Create a canvas
Draw the fa icon on it
Change it into a base-64 image url
Apply the image on the cursor css style
And I made a demo: http://jsfiddle.net/rqq8B/2/
// http://stackoverflow.com/questions/13761472/how-to-render-glyphs-from-fontawesome-on-a-canvas-element
// http://stackoverflow.com/questions/13932291/css-cursor-using-data-uri
$(function() {
var canvas = document.createElement("canvas");
canvas.width = 24;
canvas.height = 24;
//document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#000000";
ctx.font = "24px FontAwesome";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("\uf002", 12, 12);
var dataURL = canvas.toDataURL('image/png')
$('body').css('cursor', 'url('+dataURL+'), auto');
});
body {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
In the end, I couldn't get #fish_ball's code working reliably, so I just downloaded the images, used gimp to crop and edit them to 32×32px, and used them like this:
.myClass { cursor: url('/static/img/pencil30_32x32.png') 1 30, crosshair }
The 1 30 part sets the mouse pointer 'hotspot' 1px from the left and 30px from the top of the image.
There is jQuery Awesome Cursor, where you can add font-awesome icons to your cursor by calling only one simple code:
$('body').awesomeCursor('pencil');
Or passing it some options:
$('body').awesomeCursor('pencil', {
/* your options here */
size: 22,
color: 'orange',
flip: 'horizontal'
});
Disclaimer: I am NOT the author of this library I have just found it.
The canvas method mentioned results in blurry cursors.
Using SVG offers better results:
Download the SVG for the icon you want to use from Encharm's Font-Awesome-SVG-PNG.
Edit the SVG using a text editor and specify the width and height you'd like to use
e.g.: 24 x 24.
Keep in mind there's usually a maximum size in the browser (128 x 128 in Firefox.)
Declare the cursor in CSS, e.g.: cursor: url( '/assets/img/volume-up.svg' ), pointer;
For anyone using Vue.js, there is an easier approach:
Importing font awesome icon as a component:
import { library } from '#fortawesome/fontawesome-svg-core';
import { fas } from '#fortawesome/free-solid-svg-icons';
import { fab } from '#fortawesome/free-brands-svg-icons';
import { far } from '#fortawesome/free-regular-svg-icons';
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome';
library.add(fas);
library.add(fab);
library.add(far);
Vue.component('fa-icon', FontAwesomeIcon);
and using it directly in the HTML of a page:
<fa-icon icon="copy" #click="yourFunction"/>
and surrounding your tag with <span>:
<span class="hover"><fa-icon icon="copy" #click="yourFunction"/></span>
and adding to your css:
span.hover {
cursor: pointer;
}
This will add a pointer on all <span> tags with class hover in your code (and thus your font awesome icons !)

How to know if a font (#font-face) has already been loaded?

I'm using Font-Awesome, but while the font files are not loaded, the icons appear with .
So, I want these icons to have display:none while files are not loaded.
#font-face {
font-family: "FontAwesome";
src: url('../font/fontawesome-webfont.eot');
src: url('../font/fontawesome-webfont.eot?#iefix') format('eot'), url('../font/fontawesome-webfont.woff') format('woff'), url('../font/fontawesome-webfont.ttf') format('truetype'), url('../font/fontawesome-webfont.svg#FontAwesome') format('svg');
font-weight: normal;
font-style: normal;
}
How do I know that these files have been loaded and I'm finally able to show the icons?
Edit:
I'm not talking when the page is loaded (onload), because the font could be loaded before the whole page.
Now on GitHub: https://github.com/patrickmarabeas/jQuery-FontSpy.js
Essentially the method works by comparing the width of a string in two different fonts. We are using Comic Sans as the font to test against, because it is the most different of the web safe fonts and hopefully different enough to any custom font you will be using. Additionally we are using a very large font-size so even small differences will be apparent. When the width of the Comic Sans string has been calculated, the font-family is changed to your custom font, with a fallback to Comic Sans. When checked, if the string element width is the same, the fallback font of Comic Sans is still in use. If not, your font should be operational.
I rewrote the method of font load detection into a jQuery plugin designed to give the developer the ability to style elements based upon whether the font has been loaded or not. A fail safe timer has been added so the user isn’t left without content if the custom font fails to load. That’s just bad usability.
I have also added greater control over what happens during font loading and on fail with the inclusion of classes addition and removal. You can now do whatever you like to the font. I would only recommend modifying the fonts size, line spacing, etc to get your fall back font as close to the custom as possible so your layout stays intact, and users get an expected experience.
Here's a demo: http://patrickmarabeas.github.io/jQuery-FontSpy.js
Throw the following into a .js file and reference it.
(function($) {
$.fontSpy = function( element, conf ) {
var $element = $(element);
var defaults = {
font: $element.css("font-family"),
onLoad: '',
onFail: '',
testFont: 'Comic Sans MS',
testString: 'QW#HhsXJ',
delay: 50,
timeOut: 2500
};
var config = $.extend( defaults, conf );
var tester = document.createElement('span');
tester.style.position = 'absolute';
tester.style.top = '-9999px';
tester.style.left = '-9999px';
tester.style.visibility = 'hidden';
tester.style.fontFamily = config.testFont;
tester.style.fontSize = '250px';
tester.innerHTML = config.testString;
document.body.appendChild(tester);
var fallbackFontWidth = tester.offsetWidth;
tester.style.fontFamily = config.font + ',' + config.testFont;
function checkFont() {
var loadedFontWidth = tester.offsetWidth;
if (fallbackFontWidth === loadedFontWidth){
if(config.timeOut < 0) {
$element.removeClass(config.onLoad);
$element.addClass(config.onFail);
console.log('failure');
}
else {
$element.addClass(config.onLoad);
setTimeout(checkFont, config.delay);
config.timeOut = config.timeOut - config.delay;
}
}
else {
$element.removeClass(config.onLoad);
}
}
checkFont();
};
$.fn.fontSpy = function(config) {
return this.each(function() {
if (undefined == $(this).data('fontSpy')) {
var plugin = new $.fontSpy(this, config);
$(this).data('fontSpy', plugin);
}
});
};
})(jQuery);
Apply it to your project
.bannerTextChecked {
font-family: "Lobster";
/* don't specify fallback font here, do this in onFail class */
}
$(document).ready(function() {
$('.bannerTextChecked').fontSpy({
onLoad: 'hideMe',
onFail: 'fontFail anotherClass'
});
});
Remove that FOUC!
.hideMe {
visibility: hidden !important;
}
.fontFail {
visibility: visible !important;
/* fall back font */
/* necessary styling so fallback font doesn't break your layout */
}
EDIT: FontAwesome compatibility removed as it didn't work properly and ran into issues with different versions. A hacky fix can be found here: https://github.com/patrickmarabeas/jQuery-FontFaceSpy.js/issues/1
Try WebFont Loader (github repo), developed by Google and Typekit.
This example first displays the text in the default serif font; then after the fonts have loaded it displays the text in the specified font. (This code reproduces Firefox's default behavior in all other modern browsers.)
Actually, there is a good way to understand all fonts begin to download or loaded completely or not and fall into some errors, but it is not just for a specific font, pay attention to the following code:
document.fonts.onloading = () => {
// do someting when fonts begin to download
};
document.fonts.onloadingdone = () => {
// do someting when fonts are loaded completely
};
document.fonts.onloading = () => {
// do someting when fonts fall into some error
};
And also there is an option that returns Promise and it could handle with .then function:
document.fonts.ready
.then(() => console.log('do someting at the final with each status'))
Here is a different approach to the solutions from others.
I'm using FontAwesome 4.1.0 to build WebGL textures. That gave me the idea to use a tiny canvas to render a fa-square to, then check a pixel in that canvas to test whether it has loaded:
function waitForFontAwesome( callback ) {
var retries = 5;
var checkReady = function() {
var canvas, context;
retries -= 1;
canvas = document.createElement('canvas');
canvas.width = 20;
canvas.height = 20;
context = canvas.getContext('2d');
context.fillStyle = 'rgba(0,0,0,1.0)';
context.fillRect( 0, 0, 20, 20 );
context.font = '16pt FontAwesome';
context.textAlign = 'center';
context.fillStyle = 'rgba(255,255,255,1.0)';
context.fillText( '\uf0c8', 10, 18 );
var data = context.getImageData( 2, 10, 1, 1 ).data;
if ( data[0] !== 255 && data[1] !== 255 && data[2] !== 255 ) {
console.log( "FontAwesome is not yet available, retrying ..." );
if ( retries > 0 ) {
setTimeout( checkReady, 200 );
}
} else {
console.log( "FontAwesome is loaded" );
if ( typeof callback === 'function' ) {
callback();
}
}
}
checkReady();
};
As it uses a canvas it requires a fairly modern browser, but it might work on IE8 as well with the polyfill.
Here's another way of knowing if a #font-face has already been loaded without having to use timers at all: utilize a "scroll" event to receive an instantaneous event when the size of a carefully crafted element is changed.
I wrote a blog post about how it's done and have published the library on Github.
Try something like
$(window).bind("load", function() {
$('#text').addClass('shown');
});
and then do
#text {visibility: hidden;}
#text.shown {visibility: visible;}
The load event should fire after the fonts are loaded.
alternatively, you could add font-display: block to your #font-face declaration.
this instructs browsers to render the fallback font as invisible until your font is loaded, no need for display: none or any javascript load font detection
Solution for Typescript, Angular.
If you are working with Angular, you can use this module in order to do a font check.
// document.fonts.check extension
import type {} from 'css-font-loading-module';
ngOnInit() {
this.onFontLoad();
}
public onFontLoad() {
let myTimer = setInterval(() => {
if (document.fonts.check('14px MyFont')) {
console.log('Font is loaded!');
clearInterval(myTimer);
} else {
console.log('Font is loading');
}
}, 1);
}
Also, some fonts are extremely heavy. Therefore, you can add a loading screen while the font is loading and remove the loading screen when the font is loaded. I believe this is a better approach rather than changing your CSS class to display: none, merely because it might take 3-4+ seconds to download some fonts if the user has slow internet.
This is an alternate approach that will at least ensure that font-awesome is loaded, NOT a complete solution to the OP. Original code found in the wordpress forums here https://wordpress.stackexchange.com/a/165358/40636.
It's agnostic and will work with any font style resource like font-awesome where a font-family can be checked. With a little more thought I bet this could be applied to much more...
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
(function($){
var faSpan = $('<span class="fa" style="display:none"></span>').appendTo('body');
if (faSpan .css('fontFamily') !== 'FontAwesome' ) {
// Fallback Link
$('head').append('<link href="/css/font-awesome.min.css" rel="stylesheet">');
}
faSpan.remove();
})(jQuery);
</script>
Use the below code:
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<canvas id="canvasFont" width="40px" height="40px" style="position: absolute; display: none;"></canvas>
<script>
function IsLoadedFonts()
{
var Args = arguments;
var obj = document.getElementById('canvasFont');
var ctx = obj.getContext("2d");
var baseFont = (/chrome/i.test(navigator.userAgent))?'tims new roman':'arial';
//................
function getImg(fon)
{
ctx.clearRect(0, 0, (obj).width, (obj).height);
ctx.fillStyle = 'rgba(0,0,0,1.0)';
ctx.fillRect( 0, 0, 40, 40 );
ctx.font = '20px '+ fon;
ctx.textBaseline = "top";
ctx.fillStyle = 'rgba(255,255,255,1.0)';
ctx.fillText( '\u0630', 18, 5 );
return ctx.getImageData( 0, 0, 40, 40 );
};
//..............
for(var i1=0; i1<Args.length; i1++)
{
data1 = getImg(Args[i1]);
data2 = getImg(baseFont);
var isLoaded = false;
//...........
for (var i=0; i<data1.data.length; i++)
{
if(data1.data[i] != data2.data[i])
{isLoaded = true; break;}
}
//..........
if(!isLoaded)
return false;
}
return true;
};
setTimeout(function(){alert(IsLoadedFonts('myfont'));},100);
</script>
</body>
Can check many fonts:
setTimeout(function(){alert(IsLoadedFonts('font1','font2','font3'));},100);
The below code works in opera only but is easy:
if(!document.defaultView.getComputedStyle(document.getElementById('mydiv'))['fontFamily'].match(/myfont/i))
alert("font do not loaded ");

Categories

Resources