#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}; | |
| } | |
| } |