Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrap_with_tspans method omits single word at end #1

Open
willw-git opened this issue May 21, 2014 · 3 comments
Open

wrap_with_tspans method omits single word at end #1

willw-git opened this issue May 21, 2014 · 3 comments

Comments

@willw-git
Copy link

(About to report a number of bugs. Would first of all like to say thanks for a great effort - I really like the idea, and it is just what I need. Unfortunately...)

Using the wrap_with_tspans method with IE11, if a single word should be wrapped to a line by itself, then this word is missed out.

This capture shows this happening on the demo page:

wrap bug1

Adding an extra word to the text fixes it.

@willw-git
Copy link
Author

Version of wrap_with_tspans() below

  1. Fixes single word on line issue, described above. Essentially the main loop 'forgot' the last word on a line by itself.
  2. Fixes text outdenting problem I described here Text alignment in IE11 #2 I simply ignored all your complex line length calcs and bodged the text to start at the same place.
  3. I guess - untried - fixes the text appearing in wrong box issue Demo shows all text in pink box in IE11 #3 - I think cause by failure to include the bounding box's (x,y) when calculating the start position for the text.

Excuse me not attempting a reverse fork merge thingy - I don't speak git.

            // wrap with tspans if foreignObject is undefined
            var wrap_with_tspans = function(item) {
                // operate on the first text item in the selection
                var text_node = item[0];
                var parent = text_node.parentNode;
                var text_node_selected = d3.select(text_node);
                // measure initial size of the text node as rendered
                var text_node_height = text_node.getBBox().height;
                var text_node_width = text_node.getBBox().width;
                // figure out the line height, either from rendered height
                // of the font or attached styling
                var line_height;
                var rendered_line_height = text_node_height;
                var styled_line_height = text_node_selected.style('line-height');
                if(
                    (styled_line_height) &&
                    (parseInt(styled_line_height))
                ) {
                    line_height = parseInt(styled_line_height.replace('px', ''));
                } else {
                    line_height = rendered_line_height;
                }
                // only fire the rest of this if the text content
                // overflows the desired dimensions
                if(text_node_width > bounds.width) {
                    // store whatever is inside the text node
                    // in a variable and then zero out the
                    // initial content; we'll reinsert in a moment
                    // using tspan elements.
                    var text_to_wrap = text_node_selected.text();
                    text_node_selected.text('');
                    if(text_to_wrap) {
                        // keep track of whether we are splitting by spaces
                        // so we know whether to reinsert those spaces later
                        var break_delimiter;
                        // split at spaces to create an array of individual words
                        var text_to_wrap_array;
                        if(text_to_wrap.indexOf(' ') !== -1) {
                            var break_delimiter = ' ';
                            text_to_wrap_array = text_to_wrap.split(' ');
                        } else {
                            // if there are no spaces, figure out the split
                            // points by comparing rendered text width against
                            // bounds and translating that into character position
                            // cuts
                            break_delimiter = '';
                            var string_length = text_to_wrap.length;
                            var number_of_substrings = Math.ceil(text_node_width / bounds.width);
                            var splice_interval = Math.floor(string_length / number_of_substrings);
                            if(
                                !(splice_interval * number_of_substrings >= string_length)
                            ) {
                                number_of_substrings++;
                            }
                            var text_to_wrap_array = [];
                            var substring;
                            var start_position;
                            for(var i = 0; i < number_of_substrings; i++) {
                                start_position = i * splice_interval;
                                substring = text_to_wrap.substr(start_position, splice_interval);
                                text_to_wrap_array.push(substring);
                            }
                        }

                        // new array where we'll store the words re-assembled into
                        // substrings that have been tested against the desired
                        // maximum wrapping width
                        var substrings = [];
                        // computed text length is arguably incorrectly reported for
                        // all tspans after the first one, in that they will include
                        // the width of previous separate tspans. to compensate we need
                        // to manually track the computed text length of all those
                        // previous tspans and substrings, and then use that to offset
                        // the miscalculation. this then gives us the actual correct
                        // position we want to use in rendering the text in the SVG.
                        var total_offset = 0;
                        // object for storing the results of text length computations later
                        var temp = {};
                        // loop through the words and test the computed text length
                        // of the string against the maximum desired wrapping width
                        for(var i = 0; i < text_to_wrap_array.length; i++) {
                            var word = text_to_wrap_array[i];
                            var previous_string = text_node_selected.text();
                            var previous_width = text_node.getComputedTextLength();
                            // initialize the current word as the first word
                            // or append to the previous string if one exists
                            var new_string;
                            if(previous_string) {
                                new_string = previous_string + break_delimiter + word;
                            } else {
                                new_string = word;
                            }
                            // add the newest substring back to the text node and
                            // measure the length
                            text_node_selected.text(new_string);
                            var new_width = text_node.getComputedTextLength();
                            // adjust the length by the offset we've tracked
                            // due to the misreported length discussed above
                            var test_width = new_width - total_offset;
                            // if our latest version of the string is too
                            // big for the bounds, use the previous
                            // version of the string (without the newest word
                            // added) and use the latest word to restart the
                            // process with a new tspan
                            if(new_width > bounds.width) {
                                if(
                                    (previous_string) &&
                                    (previous_string !== '')
                                ) {
                                    total_offset = total_offset + previous_width;
                                    temp = {string: previous_string, width: previous_width, offset: total_offset};
                                    substrings.push(temp);
                                    text_node_selected.text('');
                                    text_node_selected.text(word);
                                    // Handle case where there is just one more word to be wrapped
                                    if(i == text_to_wrap_array.length - 1) {
                                        new_string = word;
                                        text_node_selected.text(new_string);
                                        new_width = text_node.getComputedTextLength();
                                    }
                                }
                            }
                            // if we're up to the last word in the array,
                            // get the computed length as is without
                            // appending anything further to it
                            if(i == text_to_wrap_array.length - 1) {
                                text_node_selected.text('');
                                var final_string = new_string;
                                if(
                                    (final_string) &&
                                    (final_string !== '')
                                ) {
                                    if((new_width - total_offset) > 0) {new_width = new_width - total_offset}
                                    temp = {string: final_string, width: new_width, offset: total_offset};
                                    substrings.push(temp);
                                }
                            }
                        }

                        // append each substring as a tspan
                        var current_tspan;
                        var tspan_count;
                        // double check that the text content has been removed
                        // before we start appending tspans
                        text_node_selected.text('');
                        for(var i = 0; i < substrings.length; i++) {
                            var substring = substrings[i].string;
                            if(i > 0) {
                                var previous_substring = substrings[i - 1];
                            }
                            // only append if we're sure it won't make the tspans
                            // overflow the bounds.
                            if((i) * line_height < bounds.height - (line_height * 1.5)) {
                                current_tspan = text_node_selected.append('tspan')
                                    .text(substring);
                                // vertical shift to all tspans after the first one
                                current_tspan
                                    .attr('dy', function(d) {
                                        if(i > 0) {
                                            return line_height;
                                        }
                                    });
                                // shift left from default position, which
                                // is probably based on the full length of the
                                // text string until we make this adjustment
                                current_tspan
                                    .attr('x', function() {
                                        var x_offset = bounds.x;
                                        if(padding) {x_offset += padding;}
                                        return x_offset;
                                    });
//                                    .attr('dx', function() {
//                                        if(i == 0) {
//                                            var render_offset = 0;
//                                        } else if(i > 0) {
//                                            render_offset = substrings[i - 1].width;
//                                            render_offset = render_offset * -1;
//                                        }
//                                        return render_offset;
//                                    });
                            }
                        }
                    }
                }
                // position the overall text node, whether wrapped or not
                text_node_selected.attr('y', function() {
                    var y_offset = bounds.y;
                    // shift by line-height to move the baseline into
                    // the bounds – otherwise the text baseline would be
                    // at the top of the bounds
                    if(line_height) {y_offset += line_height;}
                    // shift by padding, if it's there
                    if(padding) {y_offset += padding;}
                    return y_offset;
                });
                // shift to the right by the padding value
                text_node_selected.attr('x', function() {
                    var x_offset = bounds.x;
                    if(padding) {x_offset += padding;}
                    return x_offset;
                });


                // assign our modified text node with tspans
                // to the return value
                return_value = d3.select(parent).selectAll('text');
            }

@vijithassar
Copy link
Owner

Wonderful! I'll take a closer look over the next few days and hopefully patch this bug shortly. The delay thus far has just been because I don't currently have an IE11 computer with which to test, so thanks for taking that off my hands.

@willw-git
Copy link
Author

Fair enough. One thing I know still is not handled correctly: if you have a word which is so long that it does not fit on a line, then the div based solution clips it, whereas the IE version just lets it splurge out through the RH margin. I don't care - but you might ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants