简体   繁体   中英

How to measure word/caret position in Google Docs?

For those who haven't worked with the Google Docs editor here's a short explanation of how it works:

  • Google Docs has no visible editable textarea or contentEditable elements.
  • Google Docs listens for keydown/press/up in a separate iFrame where they place the OS cursor for event listening.
  • When the iFrame catches an event Google handles it by performing the equivalent operations on the visible document.
  • The "caret" in Google Docs is a DIV that is styled and scripted to look and act like an OS cursor.

With that out of the way, here's my request:

I'm working on a plugin that interacts with the Google Doc and I need to be able to do two things:

  • Highlight words with an opaque overlay DIV.
  • Determine cursor position inside a word.

I've been exhausting a lot of ideas about just how to handle this, but so far I've only manage to get a buggy solution for the latter problem (I perform a backspace, determine where the text changed and undo the backspace).

I'm looking for all the best ideas you can come up with to solve these problems. They don't need to be cross browser, but they do need to be able to be turned into something robust that will also handle things such as font size changed mid line.

A little bit of extra info explaining what a Google Doc looks like in HTML:

<wrapper> // Simplified wrapper containing margins, pagination and similar
  <div class="kix-paragraphrenderer"> // single DIV per page wrapping all content
    // Multiple paragraphs separated by linebreak created by Enter key:
    <div class="kix-paragraphrendeder">...</div>
    <div class="kix-paragraphrendeder">...</div>
    <div class="kix-paragraphrendeder">
      // Multiple wrapper divs created by Google's word wrapping:
      <div class="kix-lineview">...</div>
      <div class="kix-lineview">...</div>
      <div class="kix-lineview">
        // Single inner wrapper, still full width of first wrapper paragraph:
        <div class="kix-lineview-content">
          // Single wrapper SPAN containing full text of the line, but not display:block
          <span class="kix-lineview-text-block">
            // Multiple spans, one per new font change such as normal/bold text,
              // change in font size, indentation and similar:
            <span>This is normal text</span>
            <span style="font-size:40px; padding-left:4px;">This larger text.</span>
            <span style="font-weight:bold; padding-left:10px;">This is bold text</span>
            <span style="padding-left:4px;">More normal text</span>
          </span>
        </div>
      </div>
    </div>
  </div>
</wrapper>

After more tinkering I came to the conclusion that it is extremely troublesome - if not impossible - to try and programmatically determine cursor position with regard to a letter inside a <span> , simply because the <span> is the smallest element that is measurable (correct me if I am wrong).

So how to solve the problem? Here's what I ended up doing:

  • I create an offscreen positioned <div>
  • I get the text of the current paragraph ( <div class="kix-paragraphrenderer"> ) - I could get the entire text, but wanted to limit the computational load.
  • I extract each single character of the paragraph by looping through its children in the following way:
    • Loop through linveviews of the paragraph ( <div class="kix-lineview"> )
    • Get the lineview content ( <div class="kix-lineview-content"> )
    • Loop through text blocks of the lineview content ( <span class="kix-lineview-text-block"> )
    • Loop through <span> 's of the text block
    • Loop through innerText of the <span>
    • I append each character in my offscreen <div> with the currently applied style extracted from style.cssText of the current <span>
    • For each character appended I measure the width of the <div> and save this in an array. I now have a position of each single character.
  • I measure the position of the cursor relative to my widths and voila - I know where the cursor is positioned in the text.

This is obviously a bit simplied (I left out details about margins and paddings of the different elements), but it covers the idea behind how it's possible to get the cursor position.

It works quite well, but there are many pitfalls and a lot of measuring required. On top of that it's also required to post-parse the text if you want to use it for anything, since tabs, spaces and linebreaks aren't always included in innerText (depending on where these are in the text, Google may or may not make them through positioning of new elements).

I made something like Kix two years ago Google Docs. And for any HTML design and yes, for IE6 too:-) How? All we need is to compute letter absolute position. How? Replace textNode with inline element without layout, that's important, and then use Element.getClientRects I remember I also needed wrap just letter and compute its position via fast and reliable https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect

The trick how to detect lines and wraps for home and end keys was based on some vertical heuristic letter position change. Something like if base line is different, than stop caret walking. It was pretty fast and with any markup and without any caching. Holy grail:)

The only not resolvable problem was justified text, because letters were distributed randomly and spaces between them was not computable.

That project is dead http://webeena.com now. Bad management killed it (and me almost too).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM