Ren'py Custom Text Tags Script For Renpy 6.18

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
Message
Author
FortOyer
Newbie
Posts: 6
Joined: Tue May 20, 2014 12:50 pm
Contact:

Ren'py Custom Text Tags Script For Renpy 6.18

#1 Post by FortOyer »

Exactly the same as the old 6.6 script from the wiki.

Just edited for Ren'py 6.18 compatibility.

Code: Select all

init -1 python:
        # This dictionary holds all the custom text tags to be processed. Each element is a tuple in the form
        #  tag_name => (reqs_close, function) where:
        #  tag_name (the dictionary key) is the name for the tag, exactly as it should appear when used (string).
        #  reqs_close is a boolean defining whether the tag requires a matching closing tag or not.
        #  function is the actual function to be called. It will always be called with two positional arguments:
        #  The first argument 'arg' is the argument provided in the form {tag=arg} within the tag, or None if there was no argument.
        #  The second argument 'text' is the text enclosed in the form {tag}text{/tag} by the tag, or None for empty tags.
        #  Note that for non-empty tags that enclose no text (like in "{b}{/b}"), an empty string ("") is passed, rather than None.
        custom_text_tags = {}
        
        # This function is the heart of the module: it takes a string argument and returns that string with all the custom tags
        #  already parsed. It can be easily called from any python block; from Ren'py code there is a Ren'py mechanism to get it
        #  called for ALL say and menu statements: just assign it to the proper config variable and Ren'py will do everything else:
        # $ config.say_menu_text_filter = cttfilter
        def cttfilter(what): # stands for Custom Text-Tags filter
            """ A custom filter for say/menu statements that allows custom text tags in an extensible way.
                Note that this only enables text-manipulation tags (ie: tags that transform the text into some other text).
                It is posible for a tag's implementation to rely on other tags (like the money relying on color).
            """
            # Interpolation should take place before any text tag is processed.
            # We will not make custom text tags an exception to this rule, so let's start by interpolating:
            what = what%globals() # That will do the entire interpolation work, but there is a subtle point:
            # If a %% was included to represent a literal '%', we've already replaced it, and will be later
            #  missinterpreted by renpy itself; so we have to fix that:
            what = what.replace("%", "%%")
            
            # Ok, now let's go to the juicy part: find and process the text tags.
            # Rather than reinventing the wheel, we'll rely on renpy's own tokenizer to tokenize the text.
            # However, before doing that, we should make sure we don't mess up with built-in tags, so we'll need to identify them:
            # We'll add them to the list of custom tags, having None as their function:
            global custom_text_tags
            for k, v in renpy.text.extras.text_tags.iteritems():
                # (Believe me, I know Ren'py has the list of text tags there :P )
                custom_text_tags[k] = (v, None)
            # This also makes sure none of the built-in tags is overriden.
            # Note that we cannot call None and expect it to magically reconstruct the tag.
            #  Rather than that, we'll check for that special value to avoid messing with these tags, one by one.
            # This one will be used for better readability:
            def Split(s): # Splits a tag token into a tuple that makes sense for us
                tag, arg, closing = None, None, False
                if s[0]=="/":
                    closing, s = True, s[1:]
                if s.find("=") != -1:
                    if closing:
                        raise Exception("A closing tag cannot provide arguments. Tag given: \"{/%s}\"."%s)
                    else:
                        tag, arg = s.split("=", 1)
                else:
                    tag = s
                return (tag, arg, closing)
            # We will need to keep track of what we are doing:
            # tagstack, textstack, argstack, current_text = [], [], [], ""
            # stack is a list (stack) of tuples in the form (tag, arg, preceeding text)
            stack, current_text = [], ""
            for token in renpy.text.textsupport.tokenize(unicode(what)):
                if token[0] == 2:
                    tag, arg, closing = Split(token[1])
                    if closing: # closing tag
                        if len(stack)==0:
                            raise Exception("Closing tag {/%s} was found without any tag currently open. (Did you define the {%s} tag as empty?)"%(tag, tag))
                        stag, sarg, stext = stack.pop()
                        if tag==stag: # good nesting, no need to crash yet
                            if custom_text_tags[tag][1] is None: # built-in tag
                                if sarg is None: # The tag didn't take any argument
                                    current_text = "%s{%s}%s{/%s}"%(stext, tag, current_text, tag) # restore and go on
                                else: # the tag had an argument which must be restored as well
                                    current_text = "%s{%s=%s}%s{/%s}"%(stext, tag, sarg, current_text, tag) # restore and go on
                            else: # custom tag
                                current_text = "%s%s"%(stext, custom_text_tags[tag][1](sarg, current_text)) # process the tag and go on
                        else: # bad nesting, crash for good
                            raise Exception("Closing tag %s doesn't match currently open tag %s."%(tagdata[0], tagstack[-1]))
                    else: # not closing
                        if tag in custom_text_tags: # the tag exists, good news
                            if custom_text_tags[tag][0]: # the tag requires closing: just stack and it will be handled once closed
                                stack.append((tag, arg, current_text))
                                current_text = ""
                            else: # empty tag: parse without stacking
                                if custom_text_tags[tag][1] is None: # built-in tag
                                    if arg is None: # no argument
                                        current_text = "%s{%s}"%(current_text, tag)
                                    else: # there is an argument that also must be kept
                                        current_text = "%s{%s=%s}"%(current_text, tag, arg)
                                else: # custom tag
                                    current_text = "%s%s"%(current_text, custom_text_tags[tag][1](arg, None))
                        else: # the tag doesn't exist: crash accordingly
                            raise Exception("Unknown text tag \"{%s}\"."%tag)
                else: # no tag: just accumulate the text
                    current_text+=token[1]
            return current_text
        # This line tells Ren'py to replace the text ('what') of each say and menu by the result of calling cttfilter(what)
        config.say_menu_text_filter = cttfilter

User avatar
PyTom
Ren'Py Creator
Posts: 16096
Joined: Mon Feb 02, 2004 10:58 am
Completed: Moonlight Walks
Projects: Ren'Py
IRC Nick: renpytom
Github: renpytom
itch: renpytom
Location: Kings Park, NY
Contact:

Re: Ren'py Custom Text Tags Script For Renpy 6.18

#2 Post by PyTom »

FYI, 6.99 will include features to make this much nicer.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom
Software > Drama • https://www.patreon.com/renpytom

User avatar
Tayruu
Regular
Posts: 141
Joined: Sat Jul 05, 2014 7:57 pm

Re: Ren'py Custom Text Tags Script For Renpy 6.18

#3 Post by Tayruu »

Maybe I'm misunderstanding something, but I've been producing custom tags perfectly fine, like this:

Code: Select all

init:
    style green:
        color "#58b000"

Code: Select all

            nv "{=green}Green text{/green}."
Although so far I haven't tried making any tags that are more than just colour (the style is to keep swatches consistent), creating a style like this seems to create custom tags fine.

The only problem is that I can't have tags inherit from the surrounding content rather than their own parent, so sometimes I end up doing things like this too:

Code: Select all

    style tyl is tiplist_button_text:
        take yello
So maybe that's where this comes in.

Post Reply

Who is online

Users browsing this forum: Andredron