text:
.. _doc_compile_android:

Compiling GDNative module for Android
=====================================

Requirements
^^^^^^^^^^^^

- Meson build system
- SCons build system
- Android NDK 21+.

Building `godot-cpp` static library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Open a Terminal and navigate to the `godot-cpp` subdirectory.

2. Apply provided patch for Android build:

::

    $ git apply ../patch_gdcpp_android.diff

3. Compile `godot-cpp` static libraries for required platforms, by typing:

::

    $ scons platform=android target=release android_arch=arm64v8 generate_bindings=yes
    $ scons platform=android target=release android_arch=armv7 generate_bindings=yes
    $ scons platform=android target=release android_arch=x86_64 generate_bindings=yes

Building `libgdtl` static library and built-in dependencies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Navigate to the root directory of the source.
2. Edit Android NDK path in the Meson cross compile files (`toolchains/android.{arch}.toolchain`).
3. Configure `libgdtl` static libraries, by typing:

::

    $ meson ./_android_arm8 -Dgodot-cpp-lib-name=libgodot-cpp.android.release.arm64v8 --buildtype=release --cross-file ./toolchains/android.arm64.toolchain
    $ meson ./_android_arm7 -Dgodot-cpp-lib-name=libgodot-cpp.android.release.armv7 --buildtype=release --cross-file ./toolchains/android.armv7.toolchain
    $ meson ./_android_x86_64 -Dgodot-cpp-lib-name=libgodot-cpp.android.release.x86_64 --buildtype=release --cross-file ./toolchains/android.x86_64.toolchain

4. Compile `libgdtl` static libraries, by typing:

::

    $ ninja -C ./_android_arm8
    $ ninja -C ./_android_arm7
    $ ninja -C ./_android_x86_64

5. Copy `.so` files to the `libgdtl/bin/android.{arch}/`.


text:
.. _doc_compile_desktop:

Compiling GDNative module for Windows, macOS and Linux
======================================================

Requirements
^^^^^^^^^^^^

- Meson build system
- SCons build system

Building `godot-cpp` static library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Open a Terminal and navigate to the `godot-cpp` subdirectory.

2. Compile `godot-cpp` static libraries for required platforms (`{os}` - `windows`, `osx` or `linux`), by typing:

::

    $ scons platform={os} target=release bits=64 generate_bindings=yes

Building `libgdtl` static library and built-in dependencies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Navigate to the root directory of the source.
2. For MinGW cross-build edit toolchain path in the Meson cross compile files (`toolchains/ming.{arch}.toolchain`).
3. Configure `libgdtl` static libraries, by typing:

::

    $ meson ./_{os_build_dir} -Dgodot-cpp-lib-name=libgodot-cpp.{os}.release.{bits} --buildtype=release

4. Compile `libgdtl` static libraries, by typing:

::

    $ ninja -C ./{os_build_dir}

5. Copy `.so`\`.dll`\`.dylib` files to the `libgdtl/bin/{os}.{arch}/`.

repo_language: C

mime_type: text/x-c


text:
.. _doc_rich_text_typesetting:

Rich text typesetting
=====================

Setting formatting for the range of text
----------------------------------------

1. Select desired attribute and its value in the attribute editor.
2. Enter start and end offsets of the range.
3. Click "Add" button to apply formatting.

Node: This is equivalent of the `add_attribute` function call.

Removing formatting
-------------------

1. Enter start and end offsets of the range in the attribute editor.
2. Click "Remove" button to clean all attributes from the range or click "Clean" to remove attributes from entire text.

Node: This is equivalent of the `remove_attributes`/`clear_attributes` function call.

Embedding child control into the text flow
------------------------------------------

1. Add control as child of the TLRichTextEdit.
2. Apply "Replacement Rect" attribute with desired size of the control to the single character of the text.
3. Apply "Replacement ID" attribute with the control name to the same character.
4. Use "Replacement VAlign" attribute to set inline alignment of the control.

.. image:: img/attribute_inspector.png


text:
.. _doc_installation:

Installation
============

Module
------

Use Godot editor and export templates compiled with the module.

Module is ready to use, no modification to the project is required.

GDNative
--------

GDNative plugin is intended to be used with official Godot editor and export templates.

To install plugin:

1. Create `addons` folder in the root of your project.

2. Copy the contents of archive to the `addons` folder. (Do not drag-and-drop it into `FileSystem` tab of the editor. Right-click `addons` folder, select `Open in File Manager` and use your OS file manager to copy/extract files.)

3. After installing plugin, your file system should look like this:

.. image:: img/plugin_files.png

4. Go to `Project Settings`, click on the `Plugins` tab and activate `gdtl` plugin.

.. image:: img/plugin_activate.png


text:
.. _doc_font_substitution:

Font substitution
=================

Font substitution is used to find a replacement for an unavailable character.

In addition to main list of the substitution fonts, you can specify preferred fonts for the script (writing system) and language.

Use `ISO 639-1 <https://en.wikipedia.org/wiki/ISO_639-1>`_ codes for the language names, and `ISO 15924 <https://en.wikipedia.org/wiki/ISO_15924>`_ for the script names.

Substitution lists have following priority: `Language`, `Script`, `Main List`. Providing script and language information is not required, module will try to detect script (but not the language) automatically, but detection is not always successful and correct. Manual config can improve shaping speed if large number of fonts used.

.. image:: img/font_inspector.png


text:
libgdtl documentation
=====================

.. toctree::
   :maxdepth: 1
   :caption: General
   :name: sec-general

   about/index

.. toctree::
   :maxdepth: 1
   :caption: Getting started
   :name: sec-learn

   getting_started/installation
   getting_started/font_substitution
   getting_started/rich_text_typesetting

.. toctree::
   :maxdepth: 1
   :caption: Development
   :name: sec-dev

   development/compiling_desktop
   development/compiling_android
   development/compiling_ios

.. toctree::
   :maxdepth: 1
   :caption: Class reference
   :name: sec-class-ref

   classes/index


text:
# -*- coding: utf-8 -*-

import sys
import os

# -- General configuration ------------------------------------------------

needs_sphinx = '1.3'

# Sphinx extension module names and templates location
sys.path.append(os.path.abspath('extensions'))
extensions = ['gdscript', 'sphinx.ext.imgmath']
templates_path = ['_templates']

# You can specify multiple suffix as a list of string: ['.rst', '.md']
source_suffix = '.rst'
source_encoding = 'utf-8-sig'

# The master toctree document
master_doc = 'index'

# General information about the project
project = 'libgdtl'
html_show_copyright = False
html_show_sourcelink = False

# Version info for the project, acts as replacement for |version| and |release|
# The short X.Y version
version = 'latest'
# The full version, including alpha/beta/rc tags
release = 'latest'

language = 'en'

exclude_patterns = ['_build']

from gdscript import GDScriptLexer
from sphinx.highlighting import lexers
lexers['gdscript'] = GDScriptLexer()

# Pygments (syntax highlighting) style to use
pygments_style = 'sphinx'
highlight_language = 'gdscript'

# -- Options for HTML output ----------------------------------------------

import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]

# Theme options
html_theme_options = {
    # 'typekit_id': 'hiw1hhg',
    # 'analytics_id': '',
    # 'sticky_navigation': True  # Set to False to disable the sticky nav while scrolling.
    'logo_only': True,  # if we have a html_logo below, this shows /only/ the logo with no title text
    'collapse_navigation': False,  # Collapse navigation (False makes it tree-like)
    # 'display_version': True,  # Display the docs version
    # 'navigation_depth': 4,  # Depth of the headers shown in the navigation bar
}

html_logo = 'img/docs_logo.png'

# Output file base name for HTML help builder
htmlhelp_basename = 'libgdtldoc'

# -- Options for reStructuredText parser ----------------------------------

# Enable directives that insert the contents of external files
file_insertion_enabled = False

# -- Options for LaTeX output ---------------------------------------------

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_engine = 'xelatex'
latex_elements = {
    'papersize': 'a4paper'
}

latex_show_urls = 'inline'
latex_documents = [
  (master_doc, 'libgdtl.tex', 'libgdtl documentation', '', 'manual'),
]

# -- Options for linkcheck builder ----------------------------------------

# disable checking urls with about.html#this_part_of_page anchors
linkcheck_anchors = False

linkcheck_timeout = 10


text:
{%- extends "sphinx_rtd_theme/breadcrumbs.html" %}

{% block breadcrumbs_aside %}
{% if not meta or meta.get('github_url') != 'hide' %}
{{ super() }}
{% endif %}
{% endblock %}


text:
.. _doc_about_intro:

libgdtl introduction
====================

BiDi, shaping and basic text layout for Godot Engine.

Dependencies
------------

- Godot 3.1+
- C++14 compiler
- Meson build system (for gdnative module build only)
- SCons build system

Compiling (as builtin module)
-----------------------------

Build options
^^^^^^^^^^^^^

+------------------+------------------------------------------------+---------------+
| Name             | Description                                    | Default value |
+==================+================================================+===============+
| builtin_runtime  | Use the built-in libraries                     | true          |
+------------------+------------------------------------------------+---------------+
| use_graphite2    | Enable SIL Graphite 2 complementary shaper     | true          |
+------------------+------------------------------------------------+---------------+
| use_font_wrapper | Enable Godot font wrapper for default controls | false         |
+------------------+------------------------------------------------+---------------+

If `use_font_wrapper` is enabled, apply `patch_font.diff` from the root of this repository to the Godot engine source first.

Building `libdgtl` module
^^^^^^^^^^^^^^^^^^^^^^^^^

Clone this repository (without `--recursive` flag) into Godot's `modules` subfolder as `godot_tl`.
Rebuild Godot engine as usual.

Compiling (as gdnative module)
------------------------------

Build options
^^^^^^^^^^^^^

+--------------------+------------------------------------------------------------------+---------------+
| Name               | Description                                                      | Default value |
+====================+==================================================================+===============+
| godot-cpp-lib-name | godot-cpp static library name (without `.a` or `.lib` extension) | libgodot-cpp  |
+--------------------+------------------------------------------------------------------+---------------+
| static-lib         | Build static library                                             | false         |
+--------------------+------------------------------------------------------------------+---------------+
| static-runtime     | Link libraries statically for better portability                 | false         |
+--------------------+------------------------------------------------------------------+---------------+
| builtin-runtime    | Use the built-in libraries                                       | false         |
+--------------------+------------------------------------------------------------------+---------------+
| use-graphite2      | Enable SIL Graphite 2 complementary shaper                       | true          |
+--------------------+------------------------------------------------------------------+---------------+

Building `godot-cpp` static library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

See `<https://github.com/GodotNativeTools/godot-cpp/blob/master/README.md#compiling-the-cpp-bindings-library>`_

Building `libdgtl` module
^^^^^^^^^^^^^^^^^^^^^^^^^

See :ref:`doc_compile_desktop`, :ref:`doc_compile_android` and :ref:`doc_compile_ios` for build instructions.

License
-------

- The source code of the **libgdtl** module is released under unlicense.

  For more information, see `<http://unlicense.org/>`_ or the accompanying UNLICENSE file.
- **Godot** and **GodotNativeTools** are licensed under MIT license.

  For more information, see `<https://github.com/godotengine/godot/blob/master/LICENSE.txt>`_.
- **HarfBuzz** is licensed under MIT-like License.

  For more information, see `<https://github.com/harfbuzz/harfbuzz/blob/master/COPYING>`_
- **ICU4C** is licensed under Unicode, Inc. License.

  For more information, see `<http://www.unicode.org/copyright.html#License>`_
- **FreeType** is licensed under FreeType License (BSD-like) or GNU General Public License (GPL), version 2.

  For more information, see `<https://www.freetype.org/license.html>`_
- **SIL Graphite engine** is licensed under GNU Lesser General Public License (LGPL), version 2.1+ or GNU General Public License (GPL), version 2 or Mozilla Public License.

  For more information, see `<https://github.com/silnrsi/graphite/blob/master/COPYING>`_

Demo data
---------

`Montserrat <https://github.com/JulietaUla/Montserrat/>`_, `Awami Nastaliq <https://software.sil.org/awami/download/>`_, `Comic Neue <http://comicneue.com/>`_ and `Noto <https://www.google.com/get/noto/>`_ fonts are published under the `SIL Open Font License, Version 1.1 <https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL>`_

`Material Design icons by Google <https://github.com/google/material-design-icons>`_ are published under the `Apache License Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0.txt>`_

Noto Color Emoji font is cut down to single glyph (U+1F604) using `glyphhanger <https://github.com/filamentgroup/glyphhanger>`_.


text:
.. _doc_compile_ios:

Compiling GDNative module for iOS
=================================

Requirements
^^^^^^^^^^^^

- Meson build system
- SCons build system
- Xcode 10.0 (or later) with the iOS (10.0) SDK and the command line tools.

Building `godot-cpp` static library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Open a Terminal and navigate to the `godot-cpp` subdirectory.

2. Apply provided patch for iOS build:

::

    $ git apply ../patch_gdcpp_ios.diff

3. Compile `godot-cpp` static libraries for required platforms (`arm64` and `armv7` for real devices and `x86_64` for simulator), by typing:

::

    $ scons platform=ios target=release ios_arch=arm64 generate_bindings=yes
    $ scons platform=ios target=release ios_arch=armv7 generate_bindings=yes
    $ scons platform=ios target=release ios_arch=x86_64 generate_bindings=yes

Building `libgdtl` static library and built-in dependencies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Navigate to the root directory of the source.
2. Edit iOS SDK and toolchain paths in the Meson cross compile files (`toolchains/ios.{arch}.toolchain`).
3. Configure `libgdtl` static libraries, by typing:

::

    $ meson ./_ios_arm8 -Dgodot-cpp-lib-name=libgodot-cpp.ios.release.arm64 --buildtype=release --cross-file ./toolchains/ios.arm64.toolchain
    $ meson ./_ios_arm7 -Dgodot-cpp-lib-name=libgodot-cpp.ios.release.armv7 --buildtype=release --cross-file ./toolchains/ios.armv7.toolchain
    $ meson ./_ios_x86_64 -Dgodot-cpp-lib-name=libgodot-cpp.ios.release.x86_64 --buildtype=release --cross-file ./toolchains/ios.x86_64.toolchain

4. Compile `libgdtl` static libraries, by typing:

::

    $ ninja -C ./_ios_arm8
    $ ninja -C ./_ios_arm7
    $ ninja -C ./_ios_x86_64

5. (Optional) Bundle `libgdtl` and it's dependencies into one file:

::

    $ libtool -static -o ./_ios_arm8/libgdtl_full.a ./_ios_arm8/libgdtl.a ./_ios_arm8/subprojects/freetype2/libfreetype2.a ./_ios_arm8/subprojects/graphite2/libgraphite2.a ./_ios_arm8/subprojects/harfbuzz/libharfbuzz.a ./_ios_arm8/subprojects/icu4c/libicu4c.a ./_ios_arm8/subprojects/libpng/liblibpng.a ./_ios_arm8/subprojects/zlib/libzlib.a ./godot-cpp/bin/libgodot-cpp.ios.release.arm64.a
    $ libtool -static -o ./_ios_arm7/libgdtl_full.a ./_ios_arm7/libgdtl.a ./_ios_arm7/subprojects/freetype2/libfreetype2.a ./_ios_arm7/subprojects/graphite2/libgraphite2.a ./_ios_arm7/subprojects/harfbuzz/libharfbuzz.a ./_ios_arm7/subprojects/icu4c/libicu4c.a ./_ios_arm7/subprojects/libpng/liblibpng.a ./_ios_arm7/subprojects/zlib/libzlib.a ./godot-cpp/bin/libgodot-cpp.ios.release.armv7.a
    $ libtool -static -o ./_ios_x86_64/libgdtl_full.a ./_ios_x86_64/libgdtl.a ./_ios_x86_64/subprojects/freetype2/libfreetype2.a ./_ios_x86_64/subprojects/graphite2/libgraphite2.a ./_ios_x86_64/subprojects/harfbuzz/libharfbuzz.a ./_ios_x86_64/subprojects/icu4c/libicu4c.a ./_ios_x86_64/subprojects/libpng/liblibpng.a ./_ios_x86_64/subprojects/zlib/libzlib.a ./godot-cpp/bin/libgodot-cpp.ios.release.x86_64.a

6. (Optional) Bundle static libs for all platforms into one "fat" file:

::

    $ lipo -create ./_ios_arm8/libgdtl_full.a ./_ios_arm7/libgdtl_full.a ./_ios_x86_64/libgdtl_full.a -output ./libgdtl/bin/libgdtl_ios_fat.a

Adding `libgdtl` static library to the exported Xcode project
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1. Copy `libgdtl` static library file(s) into exported project directory (alongside with `{app_name}.xcode` and `{app_name}.a` files).
2. Open exported project in the Xcode.
3. Select `Targets` > Exported App Name > `General` > `Frameworks, Libraries, and Embedded Content`
4. Drag-and-drop (or use `+` button) `libgdtl` fat (or individual platform) file(s) to the list.

.. image:: img/ios_adding_static_lib.png


text:
# -*- coding: utf-8 -*-
"""
    pygments.lexers.gdscript
    ~~~~~~~~~~~~~~~~~~~~~~

    Lexer for GDScript.

    :copyright: Copyright 2xxx by The Godot Engine Community
    :license: MIT.

    modified by Daniel J. Ramirez <djrmuv@gmail.com> based on the original python.py pygment
"""

import re

from pygments.lexer import Lexer, RegexLexer, include, bygroups, using, \
    default, words, combined, do_insertions
from pygments.util import get_bool_opt, shebang_matches
from pygments.token import Text, Comment, Operator, Keyword, Name, String, \
    Number, Punctuation, Generic, Other, Error
from pygments import unistring as uni

__all__ = ['GDScriptLexer']

line_re = re.compile('.*?\n')

class GDScriptLexer(RegexLexer):
    """
    For `Godot source code <https://www.godotengine.org>`_ source code.
    """

    name = 'GDScript'
    aliases = ['gdscript', 'gd']
    filenames = ['*.gd']
    mimetypes = ['text/x-gdscript', 'application/x-gdscript']

    def innerstring_rules(ttype):
        return [
            # the old style '%s' % (...) string formatting
            (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
             '[hlL]?[E-GXc-giorsux%]', String.Interpol),
            # backslashes, quotes and formatting signs must be parsed one at a time
            (r'[^\\\'"%\n]+', ttype),
            (r'[\'"\\]', ttype),
            # unhandled string formatting sign
            (r'%', ttype),
            # newlines are an error (use "nl" state)
        ]

    tokens = {
        'root': [
            (r'\n', Text),
            (r'^(\s*)([rRuUbB]{,2})("""(?:.|\n)*?""")',
             bygroups(Text, String.Affix, String.Doc)),
            (r"^(\s*)([rRuUbB]{,2})('''(?:.|\n)*?''')",
             bygroups(Text, String.Affix, String.Doc)),
            (r'[^\S\n]+', Text),
            (r'#.*$', Comment.Single),
            (r'[]{}:(),;[]', Punctuation),
            (r'\\\n', Text),
            (r'\\', Text),
            (r'(in|and|or|not)\b', Operator.Word),
            (r'!=|==|<<|>>|&&|\+=|-=|\*=|/=|%=|&=|\|=|\|\||[-~+/*%=<>&^.!|$]', Operator),
            include('keywords'),
            (r'(func)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'funcname'),
            (r'(class)((?:\s|\\\s)+)', bygroups(Keyword, Text), 'classname'),
            include('builtins'),
            ('([rR]|[uUbB][rR]|[rR][uUbB])(""")',
             bygroups(String.Affix, String.Double), 'tdqs'),
            ("([rR]|[uUbB][rR]|[rR][uUbB])(''')",
             bygroups(String.Affix, String.Single), 'tsqs'),
            ('([rR]|[uUbB][rR]|[rR][uUbB])(")',
             bygroups(String.Affix, String.Double), 'dqs'),
            ("([rR]|[uUbB][rR]|[rR][uUbB])(')",
             bygroups(String.Affix, String.Single), 'sqs'),
            ('([uUbB]?)(""")', bygroups(String.Affix, String.Double),
             combined('stringescape', 'tdqs')),
            ("([uUbB]?)(''')", bygroups(String.Affix, String.Single),
             combined('stringescape', 'tsqs')),
            ('([uUbB]?)(")', bygroups(String.Affix, String.Double),
             combined('stringescape', 'dqs')),
            ("([uUbB]?)(')", bygroups(String.Affix, String.Single),
             combined('stringescape', 'sqs')),
            include('name'),
            include('numbers'),
        ],
        'keywords': [
            (words((
                'and', 'in', 'not', 'or', 'as', 'breakpoint', 'class', 'class_name',
                'extends', 'is', 'func', 'setget', 'signal', 'tool', 'const',
                'enum', 'export', 'onready', 'static', 'var', 'break', 'continue',
                'if', 'elif', 'else', 'for', 'pass', 'return', 'match', 'while',
                'remote', 'master', 'puppet', 'remotesync', 'mastersync',
                'puppetsync'), suffix=r'\b'),
             Keyword),
        ],
        'builtins': [
            (words((
                'Color8', 'ColorN', 'abs', 'acos', 'asin', 'assert', 'atan', 'atan2',
                'bytes2var', 'ceil', 'char', 'clamp', 'convert', 'cos', 'cosh',
                'db2linear', 'decimals', 'dectime', 'deg2rad', 'dict2inst',
                'ease', 'exp', 'floor', 'fmod', 'fposmod', 'funcref', 'hash',
                'inst2dict', 'instance_from_id', 'is_inf', 'is_nan', 'lerp',
                'linear2db', 'load', 'log', 'max', 'min', 'nearest_po2', 'pow',
                'preload', 'print', 'print_stack', 'printerr', 'printraw',
                'prints', 'printt', 'rad2deg', 'rand_range', 'rand_seed',
                'randf', 'randi', 'randomize', 'range', 'round', 'seed', 'sign',
                'sin', 'sinh', 'sqrt', 'stepify', 'str', 'str2var', 'tan',
                'tan', 'tanh', 'type_exist', 'typeof', 'var2bytes', 'var2str',
                'weakref', 'yield'),
                prefix=r'(?<!\.)', suffix=r'\b'),
             Name.Builtin),
            (r'((?<!\.)(self|false|true)|(PI|TAU|NAN|INF)'
             r')\b', Name.Builtin.Pseudo),
            (words((
                'bool', 'int', 'float', 'String', 'NodePath'
                'Vector2', 'Rect2', 'Transform2D',
                'Vector3', 'Rect3', 'Plane', 'Quat', 'Basis', 'Transform',
                'Color', "RID", 'Object', 'NodePath', 'Dictionary',
                'Array', 'PoolByteArray', 'PoolIntArray', 'PoolRealArray',
                'PoolStringArray', 'PoolVector2Array', 'PoolVector3Array', 'PoolColorArray',
                'null',
            ), prefix=r'(?<!\.)', suffix=r'\b'), Name.Builtin.Type),
        ],
        'numbers': [
            (r'(\d+\.\d*|\d*\.\d+)([eE][+-]?[0-9]+)?j?', Number.Float),
            (r'\d+[eE][+-]?[0-9]+j?', Number.Float),
            (r'0[xX][a-fA-F0-9]+', Number.Hex),
            (r'\d+j?', Number.Integer)
        ],
        'name': [
            ('[a-zA-Z_]\w*', Name),
        ],
        'funcname': [
            ('[a-zA-Z_]\w*', Name.Function, '#pop'),
            default('#pop'),
        ],
        'classname': [
            ('[a-zA-Z_]\w*', Name.Class, '#pop')
        ],
        'stringescape': [
            (r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
             r'U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})', String.Escape)
        ],
        'strings-single': innerstring_rules(String.Single),
        'strings-double': innerstring_rules(String.Double),
        'dqs': [
            (r'"', String.Double, '#pop'),
            (r'\\\\|\\"|\\\n', String.Escape),  # included here for raw strings
            include('strings-double')
        ],
        'sqs': [
            (r"'", String.Single, '#pop'),
            (r"\\\\|\\'|\\\n", String.Escape),  # included here for raw strings
            include('strings-single')
        ],
        'tdqs': [
            (r'"""', String.Double, '#pop'),
            include('strings-double'),
            (r'\n', String.Double)
        ],
        'tsqs': [
            (r"'''", String.Single, '#pop'),
            include('strings-single'),
            (r'\n', String.Single)
        ],
    }

def setup(sphinx):
    sphinx.add_lexer('gdscript', GDScriptLexer())


text:
#!/usr/bin/env python3

import argparse
import os
import re
import xml.etree.ElementTree as ET
from collections import OrderedDict

#This file is based in part on Godot's BitmapFont implementation, licensed under MIT license.
#For more information, see https://github.com/godotengine/godot/blob/master/LICENSE.txt
#For original source, see https://github.com/godotengine/godot/blob/master/doc/tools/makerst.py

# Uncomment to do type checks. I have it commented out so it works below Python 3.5
#from typing import List, Dict, TextIO, Tuple, Iterable, Optional, DefaultDict, Any, Union

# http(s)://docs.godotengine.org/<langcode>/<tag>/path/to/page.html(#fragment-tag)
GODOT_DOCS_PATTERN = re.compile(r'^http(?:s)?://docs\.godotengine\.org/(?:[a-zA-Z0-9.\-_]*)/(?:[a-zA-Z0-9.\-_]*)/(.*)\.html(#.*)?$')

def print_error(error, state):  # type: (str, State) -> None
    print(error)
    state.errored = True

class TypeName:
    def __init__(self, type_name, enum=None):  # type: (str, Optional[str]) -> None
        self.type_name = type_name
        self.enum = enum

    def to_rst(self, state):  # type: ("State") -> str
        if self.enum is not None:
            return make_enum(self.enum, state)
        elif self.type_name == "void":
            return "void"
        else:
            return make_type(self.type_name, state)

    @classmethod
    def from_element(cls, element):  # type: (ET.Element) -> "TypeName"
        return cls(element.attrib["type"], element.get("enum"))

class PropertyDef:
    def __init__(self, name, type_name, setter, getter, text, default_value, overridden):  # type: (str, TypeName, Optional[str], Optional[str], Optional[str], Optional[str], Optional[bool]) -> None
        self.name = name
        self.type_name = type_name
        self.setter = setter
        self.getter = getter
        self.text = text
        self.default_value = default_value
        self.overridden = overridden

class ParameterDef:
    def __init__(self, name, type_name, default_value):  # type: (str, TypeName, Optional[str]) -> None
        self.name = name
        self.type_name = type_name
        self.default_value = default_value

class SignalDef:
    def __init__(self, name, parameters, description):  # type: (str, List[ParameterDef], Optional[str]) -> None
        self.name = name
        self.parameters = parameters
        self.description = description

class MethodDef:
    def __init__(self, name, return_type, parameters, description, qualifiers):  # type: (str, TypeName, List[ParameterDef], Optional[str], Optional[str]) -> None
        self.name = name
        self.return_type = return_type
        self.parameters = parameters
        self.description = description
        self.qualifiers = qualifiers

class ConstantDef:
    def __init__(self, name, value, text):  # type: (str, str, Optional[str]) -> None
        self.name = name
        self.value = value
        self.text = text

class EnumDef:
    def __init__(self, name):  # type: (str) -> None
        self.name = name
        self.values = OrderedDict()  # type: OrderedDict[str, ConstantDef]

class ThemeItemDef:
    def __init__(self, name, type_name, default_value):  # type: (str, TypeName, Optional[str]) -> None
        self.name = name
        self.type_name = type_name
        self.default_value = default_value

class ClassDef:
    def __init__(self, name):  # type: (str) -> None
        self.name = name
        self.constants = OrderedDict()  # type: OrderedDict[str, ConstantDef]
        self.enums = OrderedDict()  # type: OrderedDict[str, EnumDef]
        self.properties = OrderedDict()  # type: OrderedDict[str, PropertyDef]
        self.methods = OrderedDict()  # type: OrderedDict[str, List[MethodDef]]
        self.signals = OrderedDict()  # type: OrderedDict[str, SignalDef]
        self.inherits = None  # type: Optional[str]
        self.category = None  # type: Optional[str]
        self.brief_description = None  # type: Optional[str]
        self.description = None  # type: Optional[str]
        self.theme_items = None  # type: Optional[OrderedDict[str, List[ThemeItemDef]]]
        self.tutorials = []  # type: List[str]

class State:
    def __init__(self):  # type: () -> None
        # Has any error been reported?
        self.errored = False
        self.classes = OrderedDict()  # type: OrderedDict[str, ClassDef]
        self.current_class = ""  # type: str

    def parse_class(self, class_root):  # type: (ET.Element) -> None
        class_name = class_root.attrib["name"]

        class_def = ClassDef(class_name)
        self.classes[class_name] = class_def

        inherits = class_root.get("inherits")
        if inherits is not None:
            class_def.inherits = inherits

        category = class_root.get("category")
        if category is not None:
            class_def.category = category

        brief_desc = class_root.find("brief_description")
        if brief_desc is not None and brief_desc.text:
            class_def.brief_description = brief_desc.text

        desc = class_root.find("description")
        if desc is not None and desc.text:
            class_def.description = desc.text

        properties = class_root.find("members")
        if properties is not None:
            for property in properties:
                assert property.tag == "member"

                property_name = property.attrib["name"]
                if property_name in class_def.properties:
                    print_error("Duplicate property '{}', file: {}".format(property_name, class_name), self)
                    continue

                type_name = TypeName.from_element(property)
                setter = property.get("setter") or None  # Use or None so '' gets turned into None.
                getter = property.get("getter") or None
                default_value = property.get("default") or None
                overridden = property.get("override") or False

                property_def = PropertyDef(property_name, type_name, setter, getter, property.text, default_value, overridden)
                class_def.properties[property_name] = property_def

        methods = class_root.find("methods")
        if methods is not None:
            for method in methods:
                assert method.tag == "method"

                method_name = method.attrib["name"]
                qualifiers = method.get("qualifiers")

                return_element = method.find("return")
                if return_element is not None:
                    return_type = TypeName.from_element(return_element)

                else:
                    return_type = TypeName("void")

                params = parse_arguments(method)

                desc_element = method.find("description")
                method_desc = None
                if desc_element is not None:
                    method_desc = desc_element.text

                method_def = MethodDef(method_name, return_type, params, method_desc, qualifiers)
                if method_name not in class_def.methods:
                    class_def.methods[method_name] = []

                class_def.methods[method_name].append(method_def)

        constants = class_root.find("constants")
        if constants is not None:
            for constant in constants:
                assert constant.tag == "constant"

                constant_name = constant.attrib["name"]
                value = constant.attrib["value"]
                enum = constant.get("enum")
                constant_def = ConstantDef(constant_name, value, constant.text)
                if enum is None:
                    if constant_name in class_def.constants:
                        print_error("Duplicate constant '{}', file: {}".format(constant_name, class_name), self)
                        continue

                    class_def.constants[constant_name] = constant_def

                else:
                    if enum in class_def.enums:
                        enum_def = class_def.enums[enum]

                    else:
                        enum_def = EnumDef(enum)
                        class_def.enums[enum] = enum_def

                    enum_def.values[constant_name] = constant_def

        signals = class_root.find("signals")
        if signals is not None:
            for signal in signals:
                assert signal.tag == "signal"

                signal_name = signal.attrib["name"]

                if signal_name in class_def.signals:
                    print_error("Duplicate signal '{}', file: {}".format(signal_name, class_name), self)
                    continue

                params = parse_arguments(signal)

                desc_element = signal.find("description")
                signal_desc = None
                if desc_element is not None:
                    signal_desc = desc_element.text

                signal_def = SignalDef(signal_name, params, signal_desc)
                class_def.signals[signal_name] = signal_def

        theme_items = class_root.find("theme_items")
        if theme_items is not None:
            class_def.theme_items = OrderedDict()
            for theme_item in theme_items:
                assert theme_item.tag == "theme_item"

                theme_item_name = theme_item.attrib["name"]
                default_value = theme_item.get("default") or None
                theme_item_def = ThemeItemDef(theme_item_name, TypeName.from_element(theme_item), default_value)
                if theme_item_name not in class_def.theme_items:
                    class_def.theme_items[theme_item_name] = []
                class_def.theme_items[theme_item_name].append(theme_item_def)

        tutorials = class_root.find("tutorials")
        if tutorials is not None:
            for link in tutorials:
                assert link.tag == "link"

                if link.text is not None:
                    class_def.tutorials.append(link.text)

    def sort_classes(self):  # type: () -> None
        self.classes = OrderedDict(sorted(self.classes.items(), key=lambda t: t[0]))

def parse_arguments(root):  # type: (ET.Element) -> List[ParameterDef]
    param_elements = root.findall("argument")
    params = [None] * len(param_elements)  # type: Any
    for param_element in param_elements:
        param_name = param_element.attrib["name"]
        index = int(param_element.attrib["index"])
        type_name = TypeName.from_element(param_element)
        default = param_element.get("default")

        params[index] = ParameterDef(param_name, type_name, default)

    cast = params  # type: List[ParameterDef]

    return cast

def main():  # type: () -> None
    parser = argparse.ArgumentParser()
    parser.add_argument("path", nargs="+", help="A path to an XML file or a directory containing XML files to parse.")
    group = parser.add_mutually_exclusive_group()
    group.add_argument("--output", "-o", default=".", help="The directory to save output .rst files in.")
    group.add_argument("--dry-run", action="store_true", help="If passed, no output will be generated and XML files are only checked for errors.")
    args = parser.parse_args()

    file_list = []  # type: List[str]

    for path in args.path:
        # Cut off trailing slashes so os.path.basename doesn't choke.
        if path.endswith(os.sep):
            path = path[:-1]

        if os.path.basename(path) == 'modules':
            for subdir, dirs, _ in os.walk(path):
                if 'doc_classes' in dirs:
                    doc_dir = os.path.join(subdir, 'doc_classes')
                    class_file_names = (f for f in os.listdir(doc_dir) if f.endswith('.xml'))
                    file_list += (os.path.join(doc_dir, f) for f in class_file_names)

        elif os.path.isdir(path):
            file_list += (os.path.join(path, f) for f in os.listdir(path) if f.endswith('.xml'))

        elif os.path.isfile(path):
            if not path.endswith(".xml"):
                print("Got non-.xml file '{}' in input, skipping.".format(path))
                continue

            file_list.append(path)

    classes = {}  # type: Dict[str, ET.Element]
    state = State()

    for cur_file in file_list:
        try:
            tree = ET.parse(cur_file)
        except ET.ParseError as e:
            print_error("Parse error reading file '{}': {}".format(cur_file, e), state)
            continue
        doc = tree.getroot()

        if 'version' not in doc.attrib:
            print_error("Version missing from 'doc', file: {}".format(cur_file), state)
            continue

        name = doc.attrib["name"]
        if name in classes:
            print_error("Duplicate class '{}'".format(name), state)
            continue

        classes[name] = doc

    for name, data in classes.items():
        try:
            state.parse_class(data)
        except Exception as e:
            print_error("Exception while parsing class '{}': {}".format(name, e), state)

    state.sort_classes()

    for class_name, class_def in state.classes.items():
        state.current_class = class_name
        make_rst_class(class_def, state, args.dry_run, args.output)

    if state.errored:
        exit(1)

def make_rst_class(class_def, state, dry_run, output_dir):  # type: (ClassDef, State, bool, str) -> None
    class_name = class_def.name

    if dry_run:
        f = open(os.devnull, "w")
    else:
        f = open(os.path.join(output_dir, "class_" + class_name.lower() + '.rst'), 'w', encoding='utf-8')

    # Warn contributors not to edit this file directly
    f.write(":github_url: hide\n\n")
    f.write(".. Generated automatically by doc/tools/makerst.py in Godot's source tree.\n")
    f.write(".. DO NOT EDIT THIS FILE, but the " + class_name + ".xml source instead.\n")
    f.write(".. The source is found in doc/classes or modules/<name>/doc_classes.\n\n")

    f.write(".. _class_" + class_name + ":\n\n")
    f.write(make_heading(class_name, '='))

    # Inheritance tree
    # Ascendants
    if class_def.inherits:
        inh = class_def.inherits.strip()
        f.write('**Inherits:** ')
        first = True
        while inh in state.classes:
            if not first:
                f.write(" **<** ")
            else:
                first = False

            f.write(make_type(inh, state))
            inode = state.classes[inh].inherits
            if inode:
                inh = inode.strip()
            else:
                break
        f.write("\n\n")

    # Descendents
    inherited = []
    for c in state.classes.values():
        if c.inherits and c.inherits.strip() == class_name:
            inherited.append(c.name)

    if len(inherited):
        f.write('**Inherited By:** ')
        for i, child in enumerate(inherited):
            if i > 0:
                f.write(", ")
            f.write(make_type(child, state))
        f.write("\n\n")

    # Category
    if class_def.category is not None:
        f.write('**Category:** ' + class_def.category.strip() + "\n\n")

    # Brief description
    f.write(make_heading('Brief Description', '-'))
    if class_def.brief_description is not None:
        f.write(rstize_text(class_def.brief_description.strip(), state) + "\n\n")

    # Properties overview
    if len(class_def.properties) > 0:
        f.write(make_heading('Properties', '-'))
        ml = []  # type: List[Tuple[str, str, str]]
        for property_def in class_def.properties.values():
            type_rst = property_def.type_name.to_rst(state)
            default = property_def.default_value
            if property_def.overridden:
                ml.append((type_rst, property_def.name, "**O:** " + default))
            else:
                ref = ":ref:`{0}<class_{1}_property_{0}>`".format(property_def.name, class_name)
                ml.append((type_rst, ref, default))
        format_table(f, ml, True)

    # Methods overview
    if len(class_def.methods) > 0:
        f.write(make_heading('Methods', '-'))
        ml = []
        for method_list in class_def.methods.values():
            for m in method_list:
                ml.append(make_method_signature(class_def, m, True, state))
        format_table(f, ml)

    # Theme properties
    if class_def.theme_items is not None and len(class_def.theme_items) > 0:
        f.write(make_heading('Theme Properties', '-'))
        pl = []
        for theme_item_list in class_def.theme_items.values():
            for theme_item in theme_item_list:
                pl.append((theme_item.type_name.to_rst(state), theme_item.name, theme_item.default_value))
        format_table(f, pl, True)

    # Signals
    if len(class_def.signals) > 0:
        f.write(make_heading('Signals', '-'))
        index = 0

        for signal in class_def.signals.values():
            if index != 0:
                f.write('----\n\n')

            f.write(".. _class_{}_signal_{}:\n\n".format(class_name, signal.name))
            _, signature = make_method_signature(class_def, signal, False, state)
            f.write("- {}\n\n".format(signature))

            if signal.description is not None and signal.description.strip() != '':
                f.write(rstize_text(signal.description.strip(), state) + '\n\n')

            index += 1

    # Enums
    if len(class_def.enums) > 0:
        f.write(make_heading('Enumerations', '-'))
        index = 0

        for e in class_def.enums.values():
            if index != 0:
                f.write('----\n\n')

            f.write(".. _enum_{}_{}:\n\n".format(class_name, e.name))
            # Sphinx seems to divide the bullet list into individual <ul> tags if we weave the labels into it.
            # As such I'll put them all above the list. Won't be perfect but better than making the list visually broken.
            # As to why I'm not modifying the reference parser to directly link to the _enum label:
            # If somebody gets annoyed enough to fix it, all existing references will magically improve.
            for value in e.values.values():
                f.write(".. _class_{}_constant_{}:\n\n".format(class_name, value.name))

            f.write("enum **{}**:\n\n".format(e.name))
            for value in e.values.values():
                f.write("- **{}** = **{}**".format(value.name, value.value))
                if value.text is not None and value.text.strip() != '':
                    f.write(' --- ' + rstize_text(value.text.strip(), state))

                f.write('\n\n')

            index += 1

    # Constants
    if len(class_def.constants) > 0:
        f.write(make_heading('Constants', '-'))
        # Sphinx seems to divide the bullet list into individual <ul> tags if we weave the labels into it.
        # As such I'll put them all above the list. Won't be perfect but better than making the list visually broken.
        for constant in class_def.constants.values():
            f.write(".. _class_{}_constant_{}:\n\n".format(class_name, constant.name))

        for constant in class_def.constants.values():
            f.write("- **{}** = **{}**".format(constant.name, constant.value))
            if constant.text is not None and constant.text.strip() != '':
                f.write(' --- ' + rstize_text(constant.text.strip(), state))

            f.write('\n\n')

    # Class description
    if class_def.description is not None and class_def.description.strip() != '':
        f.write(make_heading('Description', '-'))
        f.write(rstize_text(class_def.description.strip(), state) + "\n\n")

    # Online tutorials
    if len(class_def.tutorials) > 0:
        f.write(make_heading('Tutorials', '-'))
        for t in class_def.tutorials:
            link = t.strip()
            f.write("- " + make_url(link) + "\n\n")

    # Property descriptions
    if any(not p.overridden for p in class_def.properties.values()) > 0:
        f.write(make_heading('Property Descriptions', '-'))
        index = 0

        for property_def in class_def.properties.values():
            if property_def.overridden:
                continue

            if index != 0:
                f.write('----\n\n')

            f.write(".. _class_{}_property_{}:\n\n".format(class_name, property_def.name))
            f.write('- {} **{}**\n\n'.format(property_def.type_name.to_rst(state), property_def.name))

            info = []
            if property_def.default_value is not None:
                info.append(("*Default*", property_def.default_value))
            if property_def.setter is not None and not property_def.setter.startswith("_"):
                info.append(("*Setter*", property_def.setter + '(value)'))
            if property_def.getter is not None and not property_def.getter.startswith("_"):
                info.append(('*Getter*', property_def.getter + '()'))

            if len(info) > 0:
                format_table(f, info)

            if property_def.text is not None and property_def.text.strip() != '':
                f.write(rstize_text(property_def.text.strip(), state) + '\n\n')

            index += 1

    # Method descriptions
    if len(class_def.methods) > 0:
        f.write(make_heading('Method Descriptions', '-'))
        index = 0

        for method_list in class_def.methods.values():
            for i, m in enumerate(method_list):
                if index != 0:
                    f.write('----\n\n')

                if i == 0:
                    f.write(".. _class_{}_method_{}:\n\n".format(class_name, m.name))

                ret_type, signature = make_method_signature(class_def, m, False, state)
                f.write("- {} {}\n\n".format(ret_type, signature))

                if m.description is not None and m.description.strip() != '':
                    f.write(rstize_text(m.description.strip(), state) + '\n\n')

                index += 1

def make_class_list(class_list, columns):  # type: (List[str], int) -> None
    # This function is no longer used.
    f = open('class_list.rst', 'w', encoding='utf-8')
    col_max = len(class_list) // columns + 1
    print(('col max is ', col_max))
    fit_columns = []  # type: List[List[str]]

    for _ in range(0, columns):
        fit_columns.append([])

    indexers = []  # type List[str]
    last_initial = ''

    for idx, name in enumerate(class_list):
        col = idx // col_max
        if col >= columns:
            col = columns - 1
        fit_columns[col].append(name)
        idx += 1
        if name[:1] != last_initial:
            indexers.append(name)
        last_initial = name[:1]

    row_max = 0
    f.write("\n")

    for n in range(0, columns):
        if len(fit_columns[n]) > row_max:
            row_max = len(fit_columns[n])

    f.write("| ")
    for n in range(0, columns):
        f.write(" | |")

    f.write("\n")
    f.write("+")
    for n in range(0, columns):
        f.write("--+-------+")
    f.write("\n")

    for r in range(0, row_max):
        s = '+ '
        for c in range(0, columns):
            if r >= len(fit_columns[c]):
                continue

            classname = fit_columns[c][r]
            initial = classname[0]
            if classname in indexers:
                s += '**' + initial + '** | '
            else:
                s += ' | '

            s += '[' + classname + '](class_' + classname.lower() + ') | '

        s += '\n'
        f.write(s)

    for n in range(0, columns):
        f.write("--+-------+")
    f.write("\n")

    f.close()

def rstize_text(text, state):  # type: (str, State) -> str
    # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock"
    pos = 0
    while True:
        pos = text.find('\n', pos)
        if pos == -1:
            break

        pre_text = text[:pos]
        indent_level = 0
        while text[pos + 1] == '\t':
            pos += 1
            indent_level += 1
        post_text = text[pos + 1:]

        # Handle codeblocks
        if post_text.startswith("[codeblock]"):
            end_pos = post_text.find("[/codeblock]")
            if end_pos == -1:
                print_error("[codeblock] without a closing tag, file: {}".format(state.current_class), state)
                return ""

            code_text = post_text[len("[codeblock]"):end_pos]
            post_text = post_text[end_pos:]

            # Remove extraneous tabs
            code_pos = 0
            while True:
                code_pos = code_text.find('\n', code_pos)
                if code_pos == -1:
                    break

                to_skip = 0
                while code_pos + to_skip + 1 < len(code_text) and code_text[code_pos + to_skip + 1] == '\t':
                    to_skip += 1

                if to_skip > indent_level:
                    print_error("Four spaces should be used for indentation within [codeblock], file: {}".format(state.current_class), state)

                if len(code_text[code_pos + to_skip + 1:]) == 0:
                    code_text = code_text[:code_pos] + "\n"
                    code_pos += 1
                else:
                    code_text = code_text[:code_pos] + "\n    " + code_text[code_pos + to_skip + 1:]
                    code_pos += 5 - to_skip

            text = pre_text + "\n[codeblock]" + code_text + post_text
            pos += len("\n[codeblock]" + code_text)

        # Handle normal text
        else:
            text = pre_text + "\n\n" + post_text
            pos += 2

    next_brac_pos = text.find('[')

    # Escape \ character, otherwise it ends up as an escape character in rst
    pos = 0
    while True:
        pos = text.find('\\', pos, next_brac_pos)
        if pos == -1:
            break
        text = text[:pos] + "\\\\" + text[pos + 1:]
        pos += 2

    # Escape * character to avoid interpreting it as emphasis
    pos = 0
    while True:
        pos = text.find('*', pos, next_brac_pos)
        if pos == -1:
            break
        text = text[:pos] + "\*" + text[pos + 1:]
        pos += 2

    # Escape _ character at the end of a word to avoid interpreting it as an inline hyperlink
    pos = 0
    while True:
        pos = text.find('_', pos, next_brac_pos)
        if pos == -1:
            break
        if not text[pos + 1].isalnum():  # don't escape within a snake_case word
            text = text[:pos] + "\_" + text[pos + 1:]
            pos += 2
        else:
            pos += 1

    # Handle [tags]
    inside_code = False
    inside_url = False
    url_has_name = False
    url_link = ""
    pos = 0
    tag_depth = 0
    previous_pos = 0
    while True:
        pos = text.find('[', pos)
        if inside_url and (pos > previous_pos):
            url_has_name = True
        if pos == -1:
            break

        endq_pos = text.find(']', pos + 1)
        if endq_pos == -1:
            break

        pre_text = text[:pos]
        post_text = text[endq_pos + 1:]
        tag_text = text[pos + 1:endq_pos]

        escape_post = False

        if tag_text in state.classes:
            if tag_text == state.current_class:
                # We don't want references to the same class
                tag_text = '``{}``'.format(tag_text)
            else:
                tag_text = make_type(tag_text, state)
            escape_post = True
        else:  # command
            cmd = tag_text
            space_pos = tag_text.find(' ')
            if cmd == '/codeblock':
                tag_text = ''
                tag_depth -= 1
                inside_code = False
                # Strip newline if the tag was alone on one
                if pre_text[-1] == '\n':
                    pre_text = pre_text[:-1]
            elif cmd == '/code':
                tag_text = '``'
                tag_depth -= 1
                inside_code = False
                escape_post = True
            elif inside_code:
                tag_text = '[' + tag_text + ']'
            elif cmd.find('html') == 0:
                param = tag_text[space_pos + 1:]
                tag_text = param
            elif cmd.startswith('method') or cmd.startswith('member') or cmd.startswith('signal') or cmd.startswith('constant'):
                param = tag_text[space_pos + 1:]

                if param.find('.') != -1:
                    ss = param.split('.')
                    if len(ss) > 2:
                        print_error("Bad reference: '{}', file: {}".format(param, state.current_class), state)
                    class_param, method_param = ss

                else:
                    class_param = state.current_class
                    method_param = param

                ref_type = ""
                if class_param in state.classes:
                    class_def = state.classes[class_param]
                    if cmd.startswith("method"):
                        if method_param not in class_def.methods:
                            print_error("Unresolved method '{}', file: {}".format(param, state.current_class), state)
                        ref_type = "_method"

                    elif cmd.startswith("member"):
                        if method_param not in class_def.properties:
                            print_error("Unresolved member '{}', file: {}".format(param, state.current_class), state)
                        ref_type = "_property"

                    elif cmd.startswith("signal"):
                        if method_param not in class_def.signals:
                            print_error("Unresolved signal '{}', file: {}".format(param, state.current_class), state)
                        ref_type = "_signal"

                    elif cmd.startswith("constant"):
                        found = False

                        # Search in the current class
                        search_class_defs = [class_def]

                        #if param.find('.') == -1:
                        #    # Also search in @GlobalScope as a last resort if no class was specified
                        #    search_class_defs.append(state.classes["@GlobalScope"])

                        for search_class_def in search_class_defs:
                            if method_param in search_class_def.constants:
                                class_param = search_class_def.name
                                found = True

                            else:
                                for enum in search_class_def.enums.values():
                                    if method_param in enum.values:
                                        class_param = search_class_def.name
                                        found = True
                                        break

                        if not found:
                            print_error("Unresolved constant '{}', file: {}".format(param, state.current_class), state)
                        ref_type = "_constant"

                else:
                    print_error("Unresolved type reference '{}' in method reference '{}', file: {}".format(class_param, param, state.current_class), state)

                repl_text = method_param
                if class_param != state.current_class:
                    repl_text = "{}.{}".format(class_param, method_param)
                tag_text = ':ref:`{}<class_{}{}_{}>`'.format(repl_text, class_param, ref_type, method_param)
                escape_post = True
            elif cmd.find('image=') == 0:
                tag_text = ""  # '![](' + cmd[6:] + ')'
            elif cmd.find('url=') == 0:
                url_link = cmd[4:]
                tag_text = '`'
                tag_depth += 1
                inside_url = True
                url_has_name = False
            elif cmd == '/url':
                tag_text = ('' if url_has_name else url_link) + " <" + url_link + ">`_"
                tag_depth -= 1
                escape_post = True
                inside_url = False
                url_has_name = False
            elif cmd == 'center':
                tag_depth += 1
                tag_text = ''
            elif cmd == '/center':
                tag_depth -= 1
                tag_text = ''
            elif cmd == 'codeblock':
                tag_depth += 1
                tag_text = '\n::\n'
                inside_code = True
            elif cmd == 'br':
                # Make a new paragraph instead of a linebreak, rst is not so linebreak friendly
                tag_text = '\n\n'
                # Strip potential leading spaces
                while post_text[0] == ' ':
                    post_text = post_text[1:]
            elif cmd == 'i' or cmd == '/i':
                if cmd == "/i":
                    tag_depth -= 1
                else:
                    tag_depth += 1
                tag_text = '*'
            elif cmd == 'b' or cmd == '/b':
                if cmd == "/b":
                    tag_depth -= 1
                else:
                    tag_depth += 1
                tag_text = '**'
            elif cmd == 'u' or cmd == '/u':
                if cmd == "/u":
                    tag_depth -= 1
                else:
                    tag_depth += 1
                tag_text = ''
            elif cmd == 'code':
                tag_text = '``'
                tag_depth += 1
                inside_code = True
            elif cmd.startswith('enum '):
                tag_text = make_enum(cmd[5:], state)
            else:
                tag_text = make_type(tag_text, state)
                escape_post = True

        # Properly escape things like `[Node]s`
        if escape_post and post_text and (post_text[0].isalnum() or post_text[0] == "("):  # not punctuation, escape
            post_text = '\ ' + post_text

        next_brac_pos = post_text.find('[', 0)
        iter_pos = 0
        while not inside_code:
            iter_pos = post_text.find('*', iter_pos, next_brac_pos)
            if iter_pos == -1:
                break
            post_text = post_text[:iter_pos] + "\*" + post_text[iter_pos + 1:]
            iter_pos += 2

        iter_pos = 0
        while not inside_code:
            iter_pos = post_text.find('_', iter_pos, next_brac_pos)
            if iter_pos == -1:
                break
            if not post_text[iter_pos + 1].isalnum():  # don't escape within a snake_case word
                post_text = post_text[:iter_pos] + "\_" + post_text[iter_pos + 1:]
                iter_pos += 2
            else:
                iter_pos += 1

        text = pre_text + tag_text + post_text
        pos = len(pre_text) + len(tag_text)
        previous_pos = pos

    if tag_depth > 0:
        print_error("Tag depth mismatch: too many/little open/close tags, file: {}".format(state.current_class), state)

    return text

def format_table(f, data, remove_empty_columns=False):  # type: (TextIO, Iterable[Tuple[str, ...]]) -> None
    if len(data) == 0:
        return

    column_sizes = [0] * len(data[0])
    for row in data:
        for i, text in enumerate(row):
            text_length = len(text or '')
            if text_length > column_sizes[i]:
                column_sizes[i] = text_length

    if len(data[0]) == 2:
        f.write('.. table::\n')
        f.write('   :class: longtable\n')
        f.write('   :widths: 7 20\n')
        f.write('\n')

    sep = ""
    if len(data[0]) == 2:
        sep = "   "
    for size in column_sizes:
        if size == 0 and remove_empty_columns:
            continue
        sep += "+" + "-" * (size + 2)
    sep += "+\n"
    f.write(sep)

    for row in data:
        row_text = "|"
        if len(data[0]) == 2:
            row_text = "   |"
        for i, text in enumerate(row):
            if column_sizes[i] == 0 and remove_empty_columns:
                continue
            row_text += " " + (text or '').ljust(column_sizes[i]) + " |"
        row_text += "\n"
        f.write(row_text)
        f.write(sep)
    f.write('\n')

def make_type(t, state):  # type: (str, State) -> str
    if t in state.classes:
        return ':ref:`{0}<class_{0}>`'.format(t)
    print_error("Unresolved type '{}', file: {}".format(t, state.current_class), state)
    return t

def make_enum(t, state):  # type: (str, State) -> str
    p = t.find(".")
    if p >= 0:
        c = t[0:p]
        e = t[p + 1:]
        # Variant enums live in GlobalScope but still use periods.
        if c == "Variant":
            c = "@GlobalScope"
            e = "Variant." + e
    else:
        c = state.current_class
        e = t
        if c in state.classes and e not in state.classes[c].enums:
            c = "@GlobalScope"

    if not c in state.classes and c.startswith("_"):
        c = c[1:] # Remove the underscore prefix

    if c in state.classes and e in state.classes[c].enums:
        return ":ref:`{0}<enum_{1}_{0}>`".format(e, c)
    print_error("Unresolved enum '{}', file: {}".format(t, state.current_class), state)
    return t

def make_method_signature(class_def, method_def, make_ref, state):  # type: (ClassDef, Union[MethodDef, SignalDef], bool, State) -> Tuple[str, str]
    ret_type = " "

    ref_type = "signal"
    if isinstance(method_def, MethodDef):
        ret_type = method_def.return_type.to_rst(state)
        ref_type = "method"

    out = ""

    if make_ref:
        out += ":ref:`{0}<class_{1}_{2}_{0}>` ".format(method_def.name, class_def.name, ref_type)
    else:
        out += "**{}** ".format(method_def.name)

    out += '**(**'
    for i, arg in enumerate(method_def.parameters):
        if i > 0:
            out += ', '
        else:
            out += ' '

        out += "{} {}".format(arg.type_name.to_rst(state), arg.name)

        if arg.default_value is not None:
            out += '=' + arg.default_value

    if isinstance(method_def, MethodDef) and method_def.qualifiers is not None and 'vararg' in method_def.qualifiers:
        if len(method_def.parameters) > 0:
            out += ', ...'
        else:
            out += ' ...'

    out += ' **)**'

    if isinstance(method_def, MethodDef) and method_def.qualifiers is not None:
        out += ' ' + method_def.qualifiers

    return ret_type, out

def make_heading(title, underline):  # type: (str, str) -> str
    return title + '\n' + (underline * len(title)) + "\n\n"

def make_url(link):  # type: (str) -> str
    match = GODOT_DOCS_PATTERN.search(link)
    if match:
        groups = match.groups()
        if match.lastindex == 2:
            # Doc reference with fragment identifier: emit direct link to section with reference to page, for example:
            # `#calling-javascript-from-script in Exporting For Web`
            return "`" + groups[1] + " <../" + groups[0] + ".html" + groups[1] + ">`_ in :doc:`../" + groups[0] + "`"
            # Commented out alternative: Instead just emit:
            # `Subsection in Exporting For Web`
            # return "`Subsection <../" + groups[0] + ".html" + groups[1] + ">`__ in :doc:`../" + groups[0] + "`"
        elif match.lastindex == 1:
            # Doc reference, for example:
            # `Math`
            return ":doc:`../" + groups[0] + "`"
    else:
        # External link, for example:
        # `http://enet.bespin.org/usergroup0.html`
        return "`" + link + " <" + link + ">`_"

if __name__ == '__main__':
    main()


text:
/*************************************************************************/
/*  tl_core.hpp                                                          */
/*************************************************************************/

#ifndef CORE_HPP
#define CORE_HPP

#include <cmath>
#include <cstdlib>
#include <cstring>
#include <list>

//#define DEBUG_DISPLAY_METRICS
//#define DEBUG_PRINT_RUNS

#ifdef GODOT_MODULE

namespace godot {};

#define GODOT_CLASS GDCLASS
#define GODOT_SUBCLASS GDCLASS
#define GLOBAL_CONST(m_text) m_text

#include "core/core_string_names.h"
#include "core/error_macros.h"

#ifndef ERR_PRINTS
#define ERR_PRINTS ERR_PRINT
#endif

#ifndef WARN_PRINTS
#define WARN_PRINTS WARN_PRINT
#endif

#define _CHANGED CoreStringNames::get_singleton()->changed

#else

#include <nativescript/godot_nativescript.h>
#include <GlobalConstants.hpp>
#include <Godot.hpp>
#include <OS.hpp>
#include <Reference.hpp>
#include <Resource.hpp>
#define _CHANGED "changed"
#define CharType wchar_t

#define GLOBAL_CONST(m_text) GlobalConstants::m_text
#define memnew(type) type::_new()
#define memdelete(obj) obj->free()

#ifndef MAX
#define MAX(a, b) (a > b ? a : b)
#endif

#ifndef MIN
#define MIN(a, b) (a < b ? a : b)
#endif

#ifndef CLAMP
#define CLAMP(m_a, m_min, m_max) (((m_a) < (m_min)) ? (m_min) : (((m_a) > (m_max)) ? m_max : m_a))
#endif

#ifdef WARN_PRINTS
#undef WARN_PRINTS
#endif

#define WARN_PRINTS(m_text) godot::api->godot_print_warning(String(m_text).utf8().get_data(), __FUNCTION__, __FILE__, __LINE__);

#ifdef ERR_PRINTS
#undef ERR_PRINTS
#endif

#define ERR_PRINTS(m_text) godot::api->godot_print_error(String(m_text).utf8().get_data(), __FUNCTION__, __FILE__, __LINE__);

#ifndef _ALWAYS_INLINE_
#if defined(__GNUC__) && (__GNUC__ >= 4)
#define _ALWAYS_INLINE_ __attribute__((always_inline)) inline
#elif defined(__llvm__)
#define _ALWAYS_INLINE_ __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
#define _ALWAYS_INLINE_ __forceinline
#else
#define _ALWAYS_INLINE_ inline
#endif
#endif

#endif

#endif /*CORE_HPP*/


text:
/*************************************************************************/
/*  tl_core.cpp                                                          */
/*************************************************************************/

#include "tl_core.hpp"

#include "resources/tl_bitmap_font.hpp"
#include "resources/tl_dynamic_font.hpp"
#include "resources/tl_font.hpp"
#include "resources/tl_font_family.hpp"

#include "resources/tl_shaped_attributed_string.hpp"
#include "resources/tl_shaped_string.hpp"

#include "resources/tl_shaped_paragraph.hpp"

#include "resources/tl_icu_data_loader.hpp"

#include "controls/tl_label.hpp"
#include "controls/tl_line_edit.hpp"
#include "controls/tl_rich_text_edit.hpp"

#include <unicode/uclean.h>
#include <unicode/udata.h>

using namespace godot;

#ifdef GODOT_MODULE

#ifdef HAVE_WRAPPER
#include "resources/tl_gd_font_wrapper.hpp"
#endif

#ifdef TOOLS_ENABLED

#include "editor/editor_node.h"
#include "tools/tl_editor_node.h"

void tl_init_callback() {

	EditorNode *editor = EditorNode::get_singleton();
	editor->add_child(memnew(TLEditorNode(editor)));
};

#endif

void register_godot_tl_types() {

	TLFontFace::initialize_hex_font();

	ClassDB::register_class<TLICUDataLoader>();
	ClassDB::register_class<TLFontFace>();
	ClassDB::register_class<TLBitmapFontFace>();
	ClassDB::register_class<TLDynamicFontFace>();
	ClassDB::register_class<TLFontFamily>();
	ClassDB::register_class<TLShapedString>();
	ClassDB::register_class<TLShapedAttributedString>();
	ClassDB::register_class<TLShapedParagraph>();
	ClassDB::register_class<TLRichTextEdit>();
	ClassDB::register_class<TLRichTextEditSelection>();
	ClassDB::register_class<TLLabel>();
	ClassDB::register_class<TLLineEdit>();
	ClassDB::register_class<TLFontIterator>();
#ifdef HAVE_WRAPPER
	ClassDB::register_class<TLGDFontWrapper>();
#endif

	//Register editor tools
#ifdef TOOLS_ENABLED
	EditorNode::add_init_callback(&tl_init_callback);
#endif
}

void unregister_godot_tl_types() {

	TLFontFace::finish_hex_font();
	TLICUDataLoader::unload();

	//Finish ICU
	u_cleanup();
}

#else

extern "C" void GDN_EXPORT gdtl_gdnative_init(godot_gdnative_init_options *p_options) {

	//Init GDNative
	Godot::gdnative_init(p_options);
}

extern "C" void GDN_EXPORT gdtl_gdnative_terminate(godot_gdnative_terminate_options *p_options) {

	TLFontFace::finish_hex_font();
	TLICUDataLoader::unload();

	//Finish ICU
	u_cleanup();

	//Finish GDNative
	Godot::gdnative_terminate(p_options);
}

extern "C" void GDN_EXPORT gdtl_nativescript_init(void *p_handle) {

	Godot::nativescript_init(p_handle);

	TLFontFace::initialize_hex_font();

	register_tool_class<TLICUDataLoader>();
	register_tool_class<TLFontFace>();
	register_tool_class<TLBitmapFontFace>();
	register_tool_class<TLDynamicFontFace>();
	register_tool_class<TLFontFamily>();
	register_tool_class<TLShapedString>();
	register_tool_class<TLShapedAttributedString>();
	register_tool_class<TLShapedParagraph>();
	register_tool_class<TLRichTextEdit>();
	register_tool_class<TLRichTextEditSelection>();
	register_tool_class<TLLabel>();
	register_tool_class<TLLineEdit>();
	register_tool_class<TLFontIterator>();
}

#endif


text:
#!/usr/bin/env python

Import('env')
Import('env_modules')

from compat import isbasestring

if env['tools']:
	SConscript('tools/icons/SCsub')

env_godot_tl = env_modules.Clone()

sources = [
	'tl_core.cpp',
	'resources/tl_font.cpp',
	'resources/tl_bitmap_font.cpp',
	'resources/tl_dynamic_font.cpp',
	'resources/tl_font_family.cpp',
	'resources/tl_shaped_string.cpp',
	'resources/tl_shaped_attributed_string.cpp',
	'resources/tl_shaped_paragraph.cpp',
	'resources/tl_icu_data_loader.cpp',
	'controls/tl_rich_text_edit.cpp',
	'controls/tl_label.cpp',
	'controls/tl_line_edit.cpp',
]

if env['use_font_wrapper']:
	sources += [
		'resources/tl_gd_font_wrapper.cpp'
	]
	env_godot_tl.Append(CXXFLAGS=['-DHAVE_WRAPPER'])

if env['use_graphite2']:
	env_godot_tl.Append(CXXFLAGS=['-DHAVE_GRAPHITE2'])

if env['tools']:
	sources += [
		'tools/tl_editor_node.cpp',
		'tools/tl_font_family_edit.cpp'
	]

env_godot_tl.Append(CPPPATH=['.', '../subprojects/graphite2/include', '../subprojects/harfbuzz/src', '../subprojects/icu4c/source/common', '#thirdparty/freetype/include', '#thirdparty/nanosvg/'])

env_godot_tl.Append(CXXFLAGS=[
	'-DGODOT_MODULE'
])

env_thirdparty = env_godot_tl.Clone()
lib = env_thirdparty.add_library("godot_tl_builtin", sources)

# Needs to be appended to arrive after libscene in the linker call,
# but we don't want it to arrive *after* system libs, so manual hack
# LIBS contains first SCons Library objects ("SCons.Node.FS.File object")
# and then plain strings for system library. We insert between the two.
inserted = False
for idx, linklib in enumerate(env["LIBS"]):
    if isbasestring(linklib): # first system lib such as "X11", otherwise SCons lib object
        env["LIBS"].insert(idx, lib)
        inserted = True
        break
if not inserted:
    env.Append(LIBS=[lib])