Danil Gholtsman Danil Gholtsman - 3 months ago 12
AngularJS Question

Javascript and SVG. Is there a way to detect has text rendered?

I got some amount of

svg
elements, which generated dynamically. It generated based on what text in
input
field.

Each break means new group element structured like that

<g>
<svg>
<text/>
<rect/>
</svg>
</g>


Also there is added font changing option. So, after type of font changes I need to re-draw
rect
under text, which sizes are depends on what I get on
text.getBBox()
data.

The problem is, when I change font for the first time (there is event fires, after you select other font type in
select
dropdown list) my rects are not re-draws, because of text has not rendered inside
<text>
elements, so is there way to check, is there all text rendered inside all
<text>
elements?

Here is illustartion of the problem

enter image description here

UPD 1:

This is not worked for me

someSvgTextBlocks.ready(function() {
//call redraw rects functions here
})


UPD 2:

Sadly, but Raphael's answer not helped me either:
enter image description here

UPD 3

I use Angularjs framework, so here's the code in angular way (i guess)

Directives view for svg stuff:

<svg ng-repeat="line in svgConfig.text track by $index">
<g>
<rect x="0" y="0"
ng-show="svgConfig.rectConfig[$index].active"
ng-attr-height="{{svgConfig.rectConfig[$index].height}}"
ng-attr-width="{{svgConfig.rectConfig[$index].width}}"
>
</rect>

<text ng-attr-font-family="{{svgConfig.textConfig.fontFamily}}"
ng-attr-font-size="{{svgConfig.textConfig.fontSize}}"
ng-attr-fill="{{svgConfig.textConfig.fontColor}}"
>
{{line}}
</text>

</g>

</svg>


Directive itself:

app.directive('imageTxtSvgDirective', ['imageTxtSvgService', 'svgUtilsService', function(imageTxtSvgService, svgUtilsService) {

/**
* Set event bindings
*/
var setDomBindings = function($scope, $element, $attrs){
/**
* Sets watch to detect changes in text, fontsize, font family to recalculate binded svg-data
*/
$scope.$watchGroup(['svgConfig.text', 'svgConfig.textConfig.fontSize', 'svgConfig.textConfig.fontFamily', 'svgConfig.extras', 'svgConfig.rectsVisible'], function() {
var domText = $element.find('text'),
textExampleList = domText,
textConfig = $scope.svgConfig.textConfig,
font = textConfig.fontFamily,
size = textConfig.fontSize;

document.fonts.load(''+size + 'px ' + font+'').then(
function(){
$scope.setSvgRectanglesConfig(textExampleList);
}
);
});
}

/*
* Retruns initialized DOM element
*/
return {
restrict: 'E',
templateUrl: './app/shared/imageTextEditor/imageTxtSvgView.html',
controller: 'imageTxtSvgController',
transclude: true,
link: setDomBindings
};
}]);


Controller:

app.controller('imageTxtSvgController', ['$scope', 'imageTxtSvgService', '$filter', 'textConfigEnum', function($scope, imageTxtSvgService, $filter, textConfigEnum){

/**
* Returns config for each text line rect
*/
$scope.setSvgRectanglesConfig = function(textAreaList){
var me = this,
numberOfElements = (textAreaList) ? textAreaList.length : 0;

if (numberOfElements <= 0) {
return;
}

$scope.svgConfig.rectConfig = imageTxtSvgService.getSvgRectListData(textAreaList, $scope.svgConfig.rectsVisible, $scope.svgConfig);
};

$scope.init = function(){
// Fonts data
$scope.textFonts = textConfigEnum.data;

// Container for svg settings
$scope.svgConfig = {
text:'',
textConfig: {
fontFamily: $filter('getTextConfigByType')(textConfigEnum.info.Arial).fontFamily,
fontSize: 20,
fontDecoration: null,
fontWeigth: null,
fontColor:'black'
},
rectsVisible: true,
rectConfig: [],

};
};

$scope.init();
}]);


Service:

app.service('imageTxtSvgService', ['$rootScope', 'svgUtilsService', function($rootScope, svgUtilsService){

this.getSvgRectObject = function(data){
var me = this,
rectObject = {
height: 0,
width: 0,
fillColor: '#A8A8A8',
outlineColor: '#A8A8A8',
active: false
};


return angular.merge({}, rectObject, data);
}

/**
* Handles svg text creation
*/
this.getSvgText = function(data){
var me = this,
text = data.text,
stringArray = text.split('\n');

if(text === ""){
return null;
}

return stringArray;
},

/**
* Get data for rect object
*/
this.getSvgRectData = function(textArea, isActive, prevConfig){
var me = this,
box = textArea.getBBox(),
defaultRectConfig = {
height: box.height,
width: box.width,
active: isActive
},
rectConfig = angular.merge({}, prevConfig, defaultRectConfig);

return me.getSvgRectObject(rectConfig);
},

/**
*
* @returns {Array}
*/
this.getSvgRectListData = function(textAreaList, isActive, previousConfig){
var me = this,
active = isActive,
previousRectConfig,
result = [];

angular.forEach(textAreaList, function(textArea, index) {
if(previousConfig.rectConfig[index]){
previousRectConfig = previousConfig.rectConfig[index];
}

result.push(me.getSvgRectData(textArea, active, previousRectConfig));
});

return result;
}
}]);


UPD 4

Seems I've got the solution.

Instead of this

var domText = $element.find('text'),
textExampleList = domText,
textConfig = $scope.svgConfig.textConfig,
font = textConfig.fontFamily,
size = textConfig.fontSize;

document.fonts.load(''+size + 'px ' + font+'').then(
function(){
$scope.setSvgRectanglesConfig(textExampleList);
}
);


It should be something like this:

var textConfig = $scope.svgConfig.textConfig,
font = textConfig.fontFamily,
size = textConfig.fontSize;

document.fonts.load(''+size + 'px ' + font+'').then(
function(){
var textExampleList = $element.find('text');

$scope.setSvgRectanglesConfig(textExampleList);
$scope.$apply();
}
);


I should have get dom data after font is loaded, but in old version I used old version of dom data. Also, I forgot about applying changes.

enter image description here

Answer

I think you can use the Font Loading API for this:

var text = 'Text to display';
var font = 'Font to use';
document.fonts.load('12px "'+font+'"', text).then(function() {
  // Here we can be certain the font is available
  // Measure the size now…
});

Example:

var button = document.querySelector('button');
var result = document.querySelector('div');

button.addEventListener('click', function(event) {
  var text = 'The text to rénder';
  // This loads the font (unless already available)
  document.fonts.load('12px "Baloo Paaji"', text).then(function() {
    // Here we can be certain the font is available
    result.style.fontFamily = '"Baloo Paaji"';
    result.textContent = text;
  });
}, false);
<html>

<head>
  <link href="https://fonts.googleapis.com/css?family=Baloo+Paaji" rel="stylesheet">
</head>

<body>
  <button>Load Font</button>
  <div></div>
</body>

</html>

Alternatively, you could just try to have all fonts pre-loaded by including a small snippet of text for each font somewhere (hidden) in the page.