Forum Archive

Locating the exact position of a piece of text

mikael

I would like to place some active elements (like a checkbox) on top of a TextView, based on where some specific piece of text is located. In a WebView, I could wrap the text in a span element and locate that, but I do not want to use a WebView here.

I tried to use ui.measure_string, but I am not getting exact enough results even for the y coordinate, and do not even have an idea how I could use it for the x.

Would anyone have any ideas how to accomplish this?

cvp

@mikael try this one

import ImageFont
import ui
text = 'test'
font = ImageFont.truetype('Courier',20)
print(font.getsize(text))
print(ui.measure_string(text,font=('Courier',20)))
mikael

@cvp, thanks. I was not familiar with ImageFont.

The two do return significantly different values in your example:

  • ImageFont: (64, 17)
  • measure_string: (48.01, 20.00)

... and thus the first might be more accurate. But I do not see any way in ImageFont to restrict the horizontal size to get the right height for multiline text.

cvp

@mikael this?

import ImageFont
text = 'test'
max_width = 35
font_size = 20
while True:
    font = ImageFont.truetype('Courier',font_size)
    w,h = font.getsize(text)
    print(font_size,w,h)
    if w <= max_width:
        break
    font_size = font_size - 1
mikael

@cvp, looks like you are making the font size smaller until the text fits in the max_width. The issue here was that with a longer piece of text, TextView will split it into multiple lines using ”soft” line breaks, which will move the location of the target piece of text down in the view. ImageFont is not useful here since it only understands measuring the text as a single line plus any ”hard” line breaks.

In other views, I found caretRectForPosition for UITextView, which will probably be the solution for my issue.

cvp

@mikael Sorry, I didn't understand correctly your request (it happens frequently for me 😢)
and whaaaa for caretRectForPosition, never seen it

cvp

@mikael something like

cvp

@mikael Please, try this

import ui
from objc_util import *
tv = ui.TextView()
tv.font = ('<System-Bold>',32)
tv.text = 'thid is a sample but could be longer'
tv.frame = (0,0,200,200)
tv.present('sheet')
tvo = ObjCInstance(tv)
#print(dir(tvo))
txt = 'coul'
i = tv.text.find(txt)
p1 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i)
p2 = tvo.positionFromPosition_offset_(tvo.beginningOfDocument(), i+len(txt))
rge = tvo.textRangeFromPosition_toPosition_(p1,p2)
rect = tvo.firstRectForRange_(rge)  # CGRect
x,y = rect.origin.x,rect.origin.y
w,h = rect.size.width,rect.size.height
print(x,y,w,h)
l = ui.Label()
l.frame = (x,y,w,h)
l.background_color = (1,0,0,0.5)
tv.add_subview(l)

Or use a button instead a label

tv.text = 'this is a sample but could be longer'
...
txt = 'ampl'
...
l = ui.Button()
l.frame = (x,y,w,h)
l.background_color = (1,0,0,0.5)
l.corner_radius = 10
l.border_width = 1
def button_action(sender):
    if l.background_color == (1,0,0,0.5):
        sender.background_color = (0,0,1,0.5)
    else:
        sender.background_color = (1,0,0,0.5)
l.action = button_action

mikael

@cvp, heh, parallel evolution. Here’s mine, providing an exact match:

#coding: utf-8
from ui import *

f = ('<system>', 14)
v = TextView(font=f)
v.text = 'Some text including the text to find the position of'
v.present()

vo = v.objc_instance
caret_pos = vo.selectedTextRange().start()

s = 'text to find'
pos = v.text.find(s)
caret_pos.setOffset_(pos)
rect = vo.caretRectForPosition_(caret_pos)
(x,y) = rect.origin.x, rect.origin.y
(w,h) = measure_string(s, font=f)

l = View()
l.frame = (x,y,w,h)
l.background_color = (1,0,0,0.5)
v.add_subview(l)
cvp

@mikael you can use rect.size to avoid measure_string

cvp

@mikael and if the searched text is splitted on several lines, a more complex code has to be written, but anyway, I'm happy to have learned something new, as usual 😀

mikael

@cvp, rect.size is in this case just the size of the caret, i.e. roughly right height but very thin.

mikael

This came out pretty rad, if I may use the term.

Editing in Markdown, I can just type [x], and a checkbox pops out to hover over it. Tapping the checkbox changes the text underneath to [ ] and back. I can select and delete the checkbox because it is actually the underlying text that gets deleted.

Other ideas for ”active text” that would go beyond Apple’s data recognizers for phone numbers etc.?

robopu

mikael, i'm trying to follow what you're trying to do here. if i wanted to select text in a markdown document, hover over the selection and then use it to "wrap" (ie insert double colons on either end of the text selection which is somewhat similar perhaps to you inserting and maybe toggling a checkbox i think???) the text, would your code be beneficial. i'm not sure i understand what it does ;) thx

basically i love editorial but there are a few customizations i would like to make that aren't possible so i'm wondering if i might be able to simply "mark" my md documents by baking my own simple md editor in pythonista that only provides the basic extra but stripped-down functionality i need. in sum, i like to highlight specific words and phrases in documents for learning purposes. highlight means toggle on / off wrapping in pairs of my own custom tags. i would also be able to change the default background color for the wrapped pair text.

i've never done any gui programming before. just trying to get some insights where to start. thx

mikael

@robopu, no, it sounds to me like this topic is not what you are after although you will need to use objc_util for that as well.

For adding characters around highlighted text, I would recommend using additional keys above the keyboard. As a sample, check MarkdownView. There, look at create_accessory_toolbar method. As a sample of adding specific characters around the selected text, look at insert_character method.

Changing the background of selected text uses "attributed strings" - there are threads on that, but below is an example for the background color. Check this thread for other formatting options.

import ui
from objc_util import *

@on_main_thread
def change_background():
  red = UIColor.yellowColor()
  mystring = ObjCClass('NSMutableAttributedString').alloc() 
  teststring = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt facilisis dapibus.'
  mystring.initWithString_(teststring)
  mystring.setAttributes_range_({'NSBackgroundColor': red}, NSRange(28, 11)) # start, length
  tv = ui.TextView()
  tvc = ObjCInstance(tv)
  tvc.attributedText = mystring 
  tv.present()

change_background()
robopu

hey thanks @mikael. markdownview looks like it has the kind of functionality i'm looking for. i'll have to explore that. i'm coming over from editorial and haven't quite figured out yet what the best way is to download these scripts into pythonista on my iphone. once i do, i'll check this out. thx for the help