#Fitting Text into a Box
This is a JavaScript implementation for some simple code that:
- scales,
- wraps or
- scales & wraps
text into a box of given/limited dimensions!
This demonstration is build with and depends on D3.js.
<!DOCTYPE html> | |
<html> | |
<head> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="text_object.js"></script> | |
<title>Fitting Text into a Box</title> | |
<style> | |
#box{ | |
fill:none; | |
stroke:#000; | |
stroke-width:2; | |
text-anchor="start"; | |
} | |
#svg_container{ | |
display:block; | |
} | |
.info{ | |
background-color:#ccc; | |
display:inline-block; | |
vertical-align:top; | |
} | |
p:first-child{ | |
font-size:25px; | |
margin:5px; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Fitting Text into a Box</h1> | |
<div class='info'> | |
<p>Main attributes</p> | |
<input name='text' id='width' size=5 value=300 onChange='run()'>Width</input> | |
<input name='text' id='height' size=5 value=300 onChange='run()'>Height</input><br> | |
<input name='text' id='fontSize' size=5 value=50 onChange='run()'>FontSize (pt)</input><br> | |
<t id='newFS'>Applied FontSize: </t><br> | |
<select id='selectFF' onChange='run()'> | |
<option value="Arial" selected>Arial</option> | |
<option value="Times New Roman">Times New Roman</option> | |
<option value="Courier New">Courier New</option> | |
</select> | |
<t>FontFamily</t> | |
</div> | |
<div class='info'> | |
<p>Text attributes</p> | |
<input name='text' id='text_input' size=35 placeholder='Type in any text!' onChange='run()'>Input</input><br> | |
<form> | |
<input type="radio" id='scale' name="trans" value='scale' onChange='run()'>Scale</input><br> | |
<input type="radio" id='wrap' name="trans" value='wrap' onChange='run()'>Wrap</input><br> | |
<input type="radio" id='wrapNscale' name="trans" value='wrapNscale' onChange='run()'>Wrap and Scale</input><br> | |
<input type="radio" id='reset' name="trans" value='reset' onChange='run()'>Reset</input><br> | |
</form> | |
</div> | |
<div> | |
<p>Output:</p> | |
<svg id='svg_container'></svg> | |
</div> | |
</body> | |
<script> | |
//initialize all interactive elements using D3.js | |
//******* | |
var svg = d3.select('#svg_container'); | |
var box = {x:d3.select('#width').attr('value'),y:d3.select('#height').attr('value'),offset:0} | |
var rect = svg.append('rect').attr('x',box.offset).attr('y',box.offset).attr('height',box.y).attr('width',box.x).attr('id','box') | |
var text_rep = svg.append('text').attr('id','text_rep').attr('x',0).attr('y',0).attr('text-anchor',"start"); | |
var itp = d3.select('#text_input').attr('value','This is some text that should be wrapped and scaled! And it is very, very long! But be aware of too long words ... they can make everything very small!'); | |
var iwp = d3.select('#width'); | |
var ihp = d3.select('#height'); | |
var ifsp = d3.select('#fontSize'); | |
var ifspN = d3.select('#newFS'); | |
var csp_f = d3.select('#scale'); | |
var cwp_c = d3.select('#wrap'); | |
var cwp_cNs = d3.select('#wrapNscale'); | |
var text = new text_object(text_rep); | |
//******* | |
//Visualise initial output | |
run(); | |
//main function for interactions | |
function run(){ | |
console.log() | |
//define the box | |
box = {x:iwp.node().value,y:ihp.node().value,offset:0} | |
svg.attr('height',box.y).attr('width',box.x); | |
rect.attr('x',box.offset).attr('y',box.offset).attr('height',box.y).attr('width',box.x).attr('id','box') | |
//define the values of the text object | |
text.setContent(itp.node().value); | |
text.setFontFamily(document.getElementById("selectFF").value); | |
text.resetText(ifsp.node().value); | |
//differentiation between...scaling the text... | |
if(csp_f.node().checked==true){ | |
text.scaleFontSizeToBox(box.x, box.y); | |
} | |
//wrapping the text, or... | |
else if(cwp_c.node().checked==true){ | |
text.wrap(box.x) | |
} | |
//...scale and wrap the text | |
else if (cwp_cNs.node().checked==true){ | |
text.wrap_and_scale(box) | |
} | |
ifspN.text('Applied FontSize: '+text.getFontSize() + ' pt') | |
} | |
</script> | |
</html> |
//!!!Getting the width and height from the d3.node() attribute does not work in Firefox | |
//!!!USE: document.getElementById("dummy_text").getBBox() for the measurements!!! | |
function text_object(text_rep){ | |
//initial variables | |
this.str_ = '', this.font_size = 20, this.text_object = text_rep, this.scale_=null, this.dy = '.88em'; | |
var text_height=0, text_width =0, lines = 1, fontFamily = 'Arial'; | |
//BASIC FUNCTIONS | |
//+++++++++++++++ | |
this.setContent = function(content){ | |
this.str_ = content; | |
this.text_object.text(this.str_) | |
} | |
this.getContent = function(){return this.str_} | |
this.getFontSize = function(){return this.font_size} | |
this.setFontFamily = function(fontFamily_){ | |
fontFamily=fontFamily_; | |
this.text_object.attr('font-family', fontFamily) | |
} | |
this.getHeight = function(){return text_height} | |
this.setFontSize=function(fontsize){ | |
this.font_size = fontsize; | |
this.text_object.attr('font-size',this.font_size+'pt').attr('dy',this.dy).attr('y',0) | |
this.check_text_dimensions(); | |
} | |
//+++++++++++++++ | |
//TRANSFORMING FUNCTIONS | |
//+++++++++++++++ | |
this.scale=function(width_, height_){ | |
//get the scaling parameters | |
this.scale_ = this.getScale(width_, height_); | |
//transform (scale) svg-text element | |
this.text_object.attr('transform',"scale("+this.scale_.a+","+this.scale_.b+")"); | |
} | |
this.scaleFontSizeToBox=function(width_, height_){ | |
//get the scaling parameters | |
this.scale_ = this.getScale(width_, height_); | |
//scale font size to scaling parameter a | |
this.setFontSize(Math.round(this.font_size * this.scale_.a)); //has to be rounded as 'pt'-values are Integers only | |
//cause of rounding...it can happen that font-size is still too big...so we have to decrease for one pt | |
if(document.getElementById("text_rep").getBBox().width > width_){ | |
this.setFontSize(this.font_size -1); | |
} | |
} | |
this.wrap=function(width){ | |
//split the text to get only the words | |
var rawText = this.str_.split(' '); | |
//select the svg container | |
var svg_ = d3.select('svg'); | |
//initialize the measurements | |
var counter = 0, length = 0, final_text = ['']; | |
//loop all words | |
for(var i =0; i<rawText.length; i++){ | |
var part = rawText[i]; | |
//make a dummy representative object | |
var letter = svg_.append('text').attr('font-size',this.font_size+'pt').attr('id','dummy_text').attr('font-family', fontFamily).text(part + '.'); //BUG: needs the '.' to work | |
//calculate the sum of this and the previous words (that fitted into the box) | |
length = length + document.getElementById("dummy_text").getBBox().width; | |
//console.log(length, part) | |
//if the text is now too long for the box ... 'wrap' it | |
if(length > width){ | |
length = document.getElementById("dummy_text").getBBox().width; | |
counter = counter + 1; | |
final_text[counter] = part + ' '; | |
} | |
//if it fits into the box ... add it to the privious words | |
else{ | |
final_text[counter] = final_text[counter] + part + ' '; | |
} | |
//remove the dummy representation | |
letter.remove(); | |
} | |
//visualise the wrapped text | |
text_rep.text(final_text[0]) | |
for(var j=1;j<final_text.length;j++){ | |
this.text_object.append('tspan').attr('x',0).attr('dy',this.dy).text(final_text[j]).attr('font-family', fontFamily); | |
} | |
} | |
this.wrap_and_scale=function(box_){ | |
this.scale_ = this.getScale(box_.x, box_.y); | |
//find best font_size by iterating over all possible font_sizes, beginning with the maximum related to scale | |
for(var i = parseInt(this.font_size * this.scale_.b); i>1;i--){ | |
this.scale_ = this.getScale(box_.x, box_.y); //calculate the scaling | |
//console.log('1:', text_width, text_height, this.scale_, this.font_size) | |
//1st - set the current font_size | |
this.setFontSize(i); | |
//2nd - wrap the text with the new font_size | |
this.wrap(box.x); | |
this.scale_ = this.getScale(box_.x, box_.y); //calculate the scale again and... | |
if(this.scale_.b>1 && this.scale_.a>1)i=0; //...check if we can stop iterating | |
//console.log('2:', text_width, text_height, this.scale_, this.font_size) | |
} | |
} | |
this.resetText=function(fontsize){ | |
this.text_object.attr('transform',"scale(1,1)"); | |
this.setFontSize(fontsize); | |
} | |
//+++++++++++++++ | |
//CALCULATING FUNCTIONS | |
//+++++++++++++++ | |
this.check_text_dimensions = function(){ | |
text_width = document.getElementById("text_rep").getBBox().width; //this.text_object.node().offsetWidth; | |
text_height = document.getElementById("text_rep").getBBox().height; //this.text_object.node().offsetHeight; | |
} | |
this.getScale=function (width_, height_){ | |
this.check_text_dimensions(); | |
var a = width_/text_width; | |
var b = height_/text_height; | |
return {a:a, b:b}; | |
} | |
} |