Skip to content

Instantly share code, notes, and snippets.

@milkbread
Last active December 23, 2015 06:19
Show Gist options
  • Save milkbread/6593240 to your computer and use it in GitHub Desktop.
Save milkbread/6593240 to your computer and use it in GitHub Desktop.
TextFitting2

#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};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment