Wednesday, December 26, 2012

Dynamic Sentence Formatting for Blogger

Prior to this blog, I wrote an article on formatting sentences with HTML and CSS.  And that article itself has been formatted so that the viewer can dynamically control the sentence spacing.

Now I've made my first pass at doing the same thing in Blogger.  At the right, you should see controls for adjusting the size of the added sentence spacing.  And in my very limited testing, it works for me on Safari 5.

In a perfect world, which I'm working towards, but we're still not there, the editing process would automatically mark each sentence in it's own span, which is what I did by hand in that older article.  But I haven't had time to rewrite Blogger's post editor yet.  What I've done instead is relied on my so-called typewriter habit, and Blogger's idiosyncratic method of preserving extra spaces by trading them for non-breaking space entities (" ").

I use javascript at page load time to look for sequences of period-space-&nbsp, and replace them with a span that wraps the space between sentences.  Ideally I'd mark each sentence, but this was a bit easier to code.  Then I modify the word-spacing just for those spans.  The controller at the right finds the appropriate value in the style sheet and modifies it.

It's not perfect.  The lines don't seem to re-wrap based on this change, and when the spacing is changed too much you'll end up with the right margin broken.  I think this could be a Safari or WebKit bug, because my original code in that older article would reflow as needed, but in that case I wasn't using justified text [update: reflow does work in Firefox][update 2: working now in Safari, see comments].  Another problem is that it could miss some sentences if you haven't been careful about where you place your tags at sentence boundaries: if there's a tag inserted between the space and the nbsp, it won't account for that (it can handle a single tag between the period and the space though).  An additional problem is that if you had for example some alt text of an image that had a period and a space and an nbsp, it would probably muck up your alt text.  But neither of the last two problems is at all common in practice (and this should still be considered an early attempt).

This method could easily be extended to any web pages besides blogger.  If you have a two space habit, the script could be modified to look for period-space-space instead of period-space-&nbsp.  This works because even though HTML doesn't preserve your spaces for display purposes, the browser does seem to preserve them internally.

In fact you could extend this further and attempt to modify any web content, regardless of the author's typing habits, and look for sentence boundaries with any period and space.  Unfortunately as I've discussed elsewhere, this is prone to errors, and you'd have to do significantly more checking (for abbreviations, initials, etc.) just to make it close to accurate.  Only full grammar parsing would do a truly acceptable job.

Let me also note that I originally tried to implement this as a Google gadget, and while that would simplify the process of others adding this feature to their blogs, I hit a little snag that the gadgets are remotely loaded, and the browsers frown upon javascript from one domain monkeying around with the DOM for parts of the document that come from some other domain.  The only way I found around this was to add the HTML directly.  If there's another way to accomplish this please let me know.

So how can you add this to your Blogger page too?

First, you have to add a bit of CSS.  Go to your blog's dashboard, select "Template" and then "Customize".  Then "Advanced", and in the list that appears to the right, scroll down to the bottom and select "Add CSS".  In the window that you get add the following:
.sntc_space { word-spacing: 0.25em; }
And then click "Apply to Blog". Next, you have to add the HTML-based gadget. Go back to the Blogger dashboard, and click layout. Click "Add a gadget" in the spot where you want the control to appear. From the list of gadgets select the one called "HTML/Javascript". Set the title to whatever text you want above your control (e.g. "My blog's sentence spacing"), and then past the following into the content:


<script>
var className='post-body';
var oldbox=null;
function init_sentence_spacing() {
  oldbox=document.getElementById('quarter_em_sentence')
  var spaced_elements;
  if (document.getElementsByClassName == undefined) {
    spaced_elements = [];
    var allElements = document.getElementsByTagName("*");
    var element;
    for (var i = 0; (element = allElements[i]) != null; i++) {
      var elementClass = element.className;
      if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass))
      spaced_Elements.push(element);
    }
  } else {
    spaced_elements = document.getElementsByClassName(className);
  }
  var elem;
  for (var i=0; (elem = spaced_elements[i]) != null; i++) {
    var foo=elem.innerHTML;
    foo=foo.replace(/([.!?][")]*) &nbsp;([A-Z<("])/g,"$1<span class=sntc_space> </span>$2");
    foo=foo.replace(/([.!?][")]*)(<\/[^<>]*>) &nbsp;([A-Z<("])/g,"$1$2<span class=sntc_space> </span>$3");
    elem.innerHTML=foo;
  }
}
function change_sentence_spacing(value,box) {
  var CSSRules
  if (oldbox) {
    oldbox.style.backgroundColor="white";
  }
  box.style.backgroundColor="#40ff40";
  oldbox=box;
  if (document.all) {
    CSSRules='rules';
  } else if (document.getElementById) {
    CSSRules='cssRules';
  }
  for (var j=0; j<document.styleSheets.length; ++j) {
    if (document.styleSheets[j].href==null) {
      for (var i=0; i<document.styleSheets[j][CSSRules].length; ++i) {
        if (document.styleSheets[j][CSSRules][i].selectorText == 'span.sntc_space') {
          document.styleSheets[j][CSSRules][i].style['wordSpacing'] = value;
        }
      }
    }
  }
}
if(window.addEventListener) {
  window.addEventListener('load', init_sentence_spacing, false);
} else {
  window.attachEvent('onload', init_sentence_spacing);
}
</script>

<table rules=all cellpadding=2px>
<tr>
<td onclick="change_sentence_spacing('0em',this);">None</td>
<td onclick="change_sentence_spacing('0.125em',this);">Tiny</td>
<td id=quarter_em_sentence bgcolor="#40ff40" onclick="change_sentence_spacing('0.25em',this);">Small</td>
<td onclick="change_sentence_spacing('0.50em',this);">Medium</td>
<td onclick="change_sentence_spacing('0.75em',this);">Large</td>
<td onclick="change_sentence_spacing('1.25em',this);">Huge</td>
</tr>
</table>


Totally and perfectly simple, right?  Save the gadget contents and you're all set.  Now just press the space bar twice between sentences in the Blogger post editor and your readers will be able to control how they view your sentence formatting.

Note that with this setup, the default extra space is a quarter em (very conservative), and that this corresponds to the "Small" button in the control.  If you want to change this default, pick a different value for ".sntc_space" in the CSS, and move the "id=quarter_em_sentence" piece to the correct table element for the value you've made your default (but don't rename that id unless you also fix the code that looks for it; I guess that was a poor choice to name that id but I don't feel like correcting it at the moment).  You can also change the values or labels in the table to anything you'd like.

3 comments:

  1. Works in Chrome for me but neither in Firefox nor IE9. The spacing controls appear and I can select one of them, but the spacing does not change.

    (Mobile Safari doesn't work either, but that's because the entire right sidebar is missing in Blogger's mobile view.)

    ReplyDelete
  2. OK, I've fixed that. Apparently if you try to access another domain's stylesheets in Safari you get null, but in in Firefox it throws an error. I changed it to check the stylesheet's href first, and if it is null (local), then try to access it.

    Also, I see that things reflow properly on Firefox, so the lack of reflow I'm seeing must be a Safari bug.

    Thanks!

    ReplyDelete
  3. I've fixed the Safari problem where the text doesn't reflow. This is apparently linked to the optimizeLegibility setting I was using. Even though it isn't supported in Safari as far as I can tell, it was the cause of this failure to re-wrap the lines when spacing was changed. I've now removed it and the problem in Safari is gone.

    I've submitted a bug report to Apple on this, FWIW.

    ReplyDelete