Browse Source

python: expose the new text renderer APIs.

Which means removing the old ones. No backwards compatibility in place
this time either.
next
Vladimír Vondruš 1 year ago
parent
commit
6649b6c3f3
  1. 79
      doc/python/magnum.text.rst
  2. 4
      doc/python/pages/changelog.rst
  3. 63
      src/python/magnum/test/test_text.py
  4. 166
      src/python/magnum/test/test_text_gl.py
  5. 622
      src/python/magnum/text.cpp

79
doc/python/magnum.text.rst

@ -88,3 +88,82 @@
:ref:`glyph_count` :ref:`glyph_count`
.. py:function:: magnum.text.AbstractFont.fill_glyph_cache .. py:function:: magnum.text.AbstractFont.fill_glyph_cache
:raise AssertionError: If no file is opened :raise AssertionError: If no file is opened
.. py:function:: magnum.text.AbstractFont.create_shaper
:raise AssertionError: If no file is opened
.. TODO remove the copies once the base class methods don't leak to subclasses
.. py:property:: magnum.text.RendererCore.cursor
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.Renderer.cursor
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.RendererGL.cursor
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.RendererCore.alignment
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.Renderer.alignment
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.RendererGL.alignment
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.RendererCore.line_advance
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.Renderer.line_advance
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.RendererGL.line_advance
:raise AssertionError: If setting this property while rendering is in
progress
.. py:property:: magnum.text.RendererGL.index_type
:raise AssertionError: If setting this property while rendering is in
progress
.. py:function:: magnum.text.RendererCore.add
:raise AssertionError: If :p:`shaper` font isn't present in
:ref:`glyph_cache`
.. py:function:: magnum.text.Renderer.add
:raise AssertionError: If :p:`shaper` font isn't present in
:ref:`glyph_cache`
.. py:function:: magnum.text.RendererGL.add
:raise AssertionError: If :p:`shaper` font isn't present in
:ref:`glyph_cache`
.. py:function:: magnum.text.RendererGL.render(self, shaper: magnum.text.AbstractShaper, size: float, text: str, features: list[magnum.text.FeatureRange])
:raise AssertionError: If :p:`shaper` font isn't present in
:ref:`glyph_cache`
.. py:enum:: magnum.text.Feature
The equivalent to C++ :dox:`Text::feature()` is passing the four-character
code to the constructor:
..
>>> from magnum import text
.. code:: pycon
>>> feature = text.Feature('kern')
>>> feature.name
'KERNING'
.. py:enum:: magnum.text.Script
The equivalent to C++ :dox:`Text::script()` is passing the four-character
code to the constructor:
..
>>> from magnum import text
.. code:: pycon
>>> script = text.Script('Latn')
>>> script.name
'LATIN'

4
doc/python/pages/changelog.rst

@ -146,6 +146,10 @@ Changelog
:ref:`platform.glfw.Application.main_loop_iteration` :ref:`platform.glfw.Application.main_loop_iteration`
- Exposed :ref:`platform.sdl2.Application.cursor` and - Exposed :ref:`platform.sdl2.Application.cursor` and
:ref:`platform.sdl2.Application.warp_cursor`, same for GLFW :ref:`platform.sdl2.Application.warp_cursor`, same for GLFW
- Exposed the new :ref:`text.AbstractShaper`, :ref:`text.RendererCore`,
:ref:`text.Renderer`, :ref:`text.RendererGL` classes as well as the new
:ref:`text.Feature`, :ref:`text.Script` enums and the
:ref:`text.FeatureRange` helper
- Exposed :ref:`trade.AbstractImporter.features` and - Exposed :ref:`trade.AbstractImporter.features` and
:ref:`trade.AbstractImporter.flags` and corresponding enums :ref:`trade.AbstractImporter.flags` and corresponding enums
- Exposed a basic interface of :ref:`trade.AbstractImageConverter` and - Exposed a basic interface of :ref:`trade.AbstractImageConverter` and

63
src/python/magnum/test/test_text.py

@ -68,6 +68,8 @@ class Font(unittest.TestCase):
font.glyph_advance(0) font.glyph_advance(0)
# fill_glyph_cache() not tested as it needs a GL context; verified in # fill_glyph_cache() not tested as it needs a GL context; verified in
# test_text_gl instead # test_text_gl instead
with self.assertRaisesRegex(AssertionError, "no file opened"):
font.create_shaper()
def test_open_failed(self): def test_open_failed(self):
font = text.FontManager().load_and_instantiate('StbTrueTypeFont') font = text.FontManager().load_and_instantiate('StbTrueTypeFont')
@ -120,3 +122,64 @@ class Font(unittest.TestCase):
font.glyph_size(671) font.glyph_size(671)
with self.assertRaises(IndexError): with self.assertRaises(IndexError):
font.glyph_advance(671) font.glyph_advance(671)
# Creating a shaper and accessing its properties is tested in Shaper below
class Feature(unittest.TestCase):
def test_custom(self):
feature = text.Feature('kern')
self.assertEqual(feature, text.Feature.KERNING)
def test_custom_invalid(self):
with self.assertRaisesRegex(AssertionError, "expected a four-character code, got ss999"):
text.Feature('ss999')
class FeatureRange(unittest.TestCase):
def test(self):
a = text.FeatureRange(text.Feature.SMALL_CAPITALS)
self.assertEqual(a.feature, text.Feature.SMALL_CAPITALS)
self.assertTrue(a.is_enabled)
self.assertEqual(a.value, 1)
b = text.FeatureRange(text.Feature.KERNING, 0)
self.assertEqual(b.feature, text.Feature.KERNING)
self.assertFalse(b.is_enabled)
self.assertEqual(b.value, 0)
# It should be possible to create it from a tuple also, to support
# implicit conversion when passed to renderer add() or render()
c1 = text.FeatureRange((text.Feature.KERNING, 1))
c2 = text.FeatureRange((text.Feature.KERNING, True))
self.assertEqual(c1.feature, text.Feature.KERNING)
self.assertEqual(c2.feature, text.Feature.KERNING)
self.assertTrue(c1.is_enabled)
self.assertTrue(c2.is_enabled)
self.assertEqual(c1.value, 1)
self.assertEqual(c2.value, 1)
class Script(unittest.TestCase):
def test_custom(self):
han = text.Script('Hani')
self.assertEqual(han, text.Script.HAN)
def test_custom_invalid(self):
with self.assertRaisesRegex(AssertionError, "expected a four-character code, got Hello"):
text.Script('Hello')
class Shaper(unittest.TestCase):
def test(self):
font = text.FontManager().load_and_instantiate('StbTrueTypeFont')
font.open_file(os.path.join(os.path.dirname(__file__), 'Oxygen.ttf'), 16.0)
font_refcount = sys.getrefcount(font)
shaper = font.create_shaper()
self.assertIs(shaper.font, font)
self.assertEqual(sys.getrefcount(font), font_refcount + 1)
# StbTrueTypeFont doesn't support setting any of these so it should
# return false
self.assertFalse(shaper.set_script(text.Script.LATIN))
self.assertFalse(shaper.set_language("en"))
self.assertFalse(shaper.set_direction(text.ShapeDirection.LEFT_TO_RIGHT))
del shaper
self.assertEqual(sys.getrefcount(font), font_refcount)

166
src/python/magnum/test/test_text_gl.py

@ -91,17 +91,171 @@ class DistanceFieldGlyphCacheGL(GLTestCase):
self.assertEqual(cache.size, (1024, 1024, 1)) self.assertEqual(cache.size, (1024, 1024, 1))
self.assertEqual(cache.padding, (2, 2)) self.assertEqual(cache.padding, (2, 2))
class Renderer2D(GLTestCase): class RendererGL(GLTestCase):
@unittest.skipIf(magnum.TARGET_GLES2, "Needs OES_mapbuffer on ES2 and extension queries are not exposed to Python yet")
def test(self): def test(self):
font = text.FontManager().load_and_instantiate('StbTrueTypeFont') font = text.FontManager().load_and_instantiate('StbTrueTypeFont')
font.open_file(os.path.join(os.path.dirname(__file__), 'Oxygen.ttf'), 16.0) font.open_file(os.path.join(os.path.dirname(__file__), 'Oxygen.ttf'), 16.0)
cache = text.GlyphCacheGL(PixelFormat.R8_UNORM, (128, 128)) cache = text.GlyphCacheGL(PixelFormat.R8_UNORM, (128, 128))
cache_refcount = sys.getrefcount(cache)
font.fill_glyph_cache(cache, "hello") font.fill_glyph_cache(cache, "hello")
renderer = text.Renderer2D(font, cache, 1.0) shaper = font.create_shaper()
renderer.reserve(16) shaper_refcount = sys.getrefcount(cache)
renderer.render("hello")
# Passing the cache to the renderer should increase its refcount
renderer = text.RendererGL(cache)
renderer_refcount = sys.getrefcount(renderer)
self.assertEqual(sys.getrefcount(cache), cache_refcount + 1)
# OTOH the cache isn't owned by the renderer so the returned value
# doesn't increase the renderer refcount
cache_from_renderer = renderer.glyph_cache
self.assertIs(cache_from_renderer, cache)
self.assertEqual(sys.getrefcount(cache), cache_refcount + 2)
self.assertEqual(sys.getrefcount(renderer), renderer_refcount)
del cache_from_renderer
self.assertEqual(sys.getrefcount(cache), cache_refcount + 1)
self.assertEqual(sys.getrefcount(renderer), renderer_refcount)
self.assertEqual(renderer.glyph_count, 0)
self.assertEqual(renderer.glyph_capacity, 0)
self.assertEqual(renderer.glyph_index_capacity, 0)
self.assertEqual(renderer.glyph_vertex_capacity, 0)
self.assertEqual(renderer.run_count, 0)
self.assertEqual(renderer.run_capacity, 0)
self.assertFalse(renderer.is_rendering)
self.assertEqual(renderer.rendering_glyph_count, 0)
self.assertEqual(renderer.rendering_run_count, 0)
self.assertEqual(renderer.cursor, Vector2())
self.assertEqual(renderer.alignment, text.Alignment.MIDDLE_CENTER)
self.assertEqual(renderer.line_advance, 0.0)
self.assertEqual(renderer.index_type, MeshIndexType.UNSIGNED_SHORT)
renderer.reserve(16, 3)
self.assertEqual(renderer.glyph_capacity, 16)
self.assertEqual(renderer.glyph_index_capacity, 16)
self.assertEqual(renderer.glyph_vertex_capacity, 16)
self.assertEqual(renderer.run_capacity, 3)
# Passing a shaper to add() should not increase its refcount
renderer.add(shaper, 1.0, "he")
self.assertEqual(sys.getrefcount(shaper), shaper_refcount)
self.assertEqual(renderer.glyph_count, 0)
self.assertEqual(renderer.run_count, 0)
self.assertTrue(renderer.is_rendering)
self.assertEqual(renderer.rendering_glyph_count, 2)
self.assertEqual(renderer.rendering_run_count, 1)
# Verifying the overload with a feature list. All variants should
# implicitly convert to text.FeatureRange, the StbTrueTypeFont doesn't
# make use of either.
renderer.add(shaper, 1.0, "ll", [
text.Feature.KERNING,
(text.Feature.SMALL_CAPITALS, False),
(text.Feature.ACCESS_ALL_ALTERNATES, 33),
])
# Neither to render() it should
rectangle, runs = renderer.render(shaper, 1.0, "o")
self.assertEqual(sys.getrefcount(shaper), shaper_refcount)
self.assertEqual(rectangle, Range2D((-1.25364, -0.666667), (1.25364, 0.666667)))
self.assertEqual(runs, Range1Dui(0, 3))
self.assertEqual(renderer.glyph_count, 5)
self.assertEqual(renderer.run_count, 3)
self.assertFalse(renderer.is_rendering)
self.assertEqual(renderer.rendering_glyph_count, 5)
self.assertEqual(renderer.rendering_run_count, 3)
# Returned mesh references the renderer to ensure it doesn't get GC'd
# before the mesh instances
mesh = renderer.mesh
self.assertEqual(sys.getrefcount(renderer), renderer_refcount + 1)
self.assertEqual(mesh.count, 5*6)
# Deleting the mesh should decrease renderer refcount again
del mesh
self.assertEqual(sys.getrefcount(renderer), renderer_refcount)
renderer.cursor = (-15.0, 37.0)
renderer.alignment = text.Alignment.LINE_LEFT
renderer.line_advance = -7.0
renderer.index_type = MeshIndexType.UNSIGNED_BYTE
self.assertEqual(renderer.cursor, Vector2(-15.0, 37.0))
self.assertEqual(renderer.alignment, text.Alignment.LINE_LEFT)
self.assertEqual(renderer.line_advance, -7.0)
self.assertEqual(renderer.index_type, MeshIndexType.UNSIGNED_BYTE)
# Clear keeps the cursor, alignment etc properties
renderer.clear()
self.assertEqual(renderer.glyph_count, 0)
self.assertEqual(renderer.run_count, 0)
self.assertEqual(renderer.cursor, Vector2(-15.0, 37.0))
self.assertEqual(renderer.alignment, text.Alignment.LINE_LEFT)
self.assertEqual(renderer.line_advance, -7.0)
self.assertEqual(renderer.index_type, MeshIndexType.UNSIGNED_BYTE)
# Testing the argument-less render() also
renderer.add(shaper, 1.0, "hello")
rectangle2, runs2 = renderer.render()
# The rectangle is different due to the different cursor & alignment
self.assertEqual(rectangle2, Range2D((-15.0, 36.7299), (-12.4927, 38.0632)))
self.assertEqual(runs2, Range1Dui(0, 1))
self.assertEqual(renderer.glyph_count, 5)
self.assertEqual(renderer.run_count, 1)
self.assertFalse(renderer.is_rendering)
# Only the index type stays after reset
renderer.reset()
self.assertEqual(renderer.glyph_count, 0)
self.assertEqual(renderer.run_count, 0)
self.assertEqual(renderer.cursor, Vector2())
self.assertEqual(renderer.alignment, text.Alignment.MIDDLE_CENTER)
self.assertEqual(renderer.line_advance, 0.0)
self.assertEqual(renderer.index_type, MeshIndexType.UNSIGNED_BYTE)
# Deleting the renderer should decrease cache refcount again
del renderer
self.assertEqual(sys.getrefcount(cache), cache_refcount)
def test_setters_rendering_in_progress(self):
font = text.FontManager().load_and_instantiate('StbTrueTypeFont')
font.open_file(os.path.join(os.path.dirname(__file__), 'Oxygen.ttf'), 16.0)
cache = text.GlyphCacheGL(PixelFormat.R8_UNORM, (128, 128))
font.fill_glyph_cache(cache, "abcd")
shaper = font.create_shaper()
renderer = text.RendererGL(cache)
renderer.add(shaper, 1.0, "a")
self.assertTrue(renderer.is_rendering)
with self.assertRaisesRegex(AssertionError, "rendering in progress"):
renderer.cursor = Vector2()
with self.assertRaisesRegex(AssertionError, "rendering in progress"):
renderer.alignment = text.Alignment.LINE_RIGHT
with self.assertRaisesRegex(AssertionError, "rendering in progress"):
renderer.line_advance = 0.0
with self.assertRaisesRegex(AssertionError, "rendering in progress"):
renderer.index_type = MeshIndexType.UNSIGNED_INT
def test_add_render_font_not_in_cache(self):
font1 = text.FontManager().load_and_instantiate('StbTrueTypeFont')
font2 = text.FontManager().load_and_instantiate('StbTrueTypeFont')
font3 = text.FontManager().load_and_instantiate('StbTrueTypeFont')
font1.open_file(os.path.join(os.path.dirname(__file__), 'Oxygen.ttf'), 16.0)
font2.open_file(os.path.join(os.path.dirname(__file__), 'Oxygen.ttf'), 16.0)
font3.open_file(os.path.join(os.path.dirname(__file__), 'Oxygen.ttf'), 16.0)
cache = text.GlyphCacheGL(PixelFormat.R8_UNORM, (128, 128))
font1.fill_glyph_cache(cache, "abcd")
font3.fill_glyph_cache(cache, "abcd")
shaper = font2.create_shaper()
renderer = text.RendererGL(cache)
self.assertEqual(renderer.rectangle, Range2D((0.0, -0.270134), (2.50727, 1.0632))) with self.assertRaisesRegex(AssertionError, "shaper font not found among 2 fonts in associated glyph cache"):
renderer.add(shaper, 1.0, "a")
with self.assertRaisesRegex(AssertionError, "shaper font not found among 2 fonts in associated glyph cache"):
renderer.render(shaper, 1.0, "a")

622
src/python/magnum/text.cpp

@ -25,14 +25,24 @@
*/ */
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
#include <Corrade/Containers/ArrayViewStl.h> /** @todo drop once we have our list casters */
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/PairStl.h> /** @todo drop once we have our pair casters */
#include <Corrade/Containers/PointerStl.h>
#include <Corrade/Containers/StringStl.h> /** @todo drop once we have our string casters */ #include <Corrade/Containers/StringStl.h> /** @todo drop once we have our string casters */
#include <Magnum/ImageView.h> #include <Magnum/ImageView.h>
#include <Magnum/GL/Buffer.h> #include <Magnum/GL/Buffer.h>
#include <Magnum/GL/Mesh.h> #include <Magnum/GL/Mesh.h>
#include <Magnum/GL/Texture.h> #include <Magnum/GL/Texture.h>
#include <Magnum/Math/Range.h>
#include <Magnum/Text/AbstractFont.h> #include <Magnum/Text/AbstractFont.h>
#include <Magnum/Text/AbstractShaper.h>
#include <Magnum/Text/Alignment.h>
#include <Magnum/Text/Direction.h>
#include <Magnum/Text/DistanceFieldGlyphCacheGL.h> #include <Magnum/Text/DistanceFieldGlyphCacheGL.h>
#include <Magnum/Text/Renderer.h> #include <Magnum/Text/Feature.h>
#include <Magnum/Text/RendererGL.h>
#include <Magnum/Text/Script.h>
#include "corrade/pluginmanager.h" #include "corrade/pluginmanager.h"
#include "magnum/bootstrap.h" #include "magnum/bootstrap.h"
@ -112,8 +122,485 @@ void text(py::module_& m) {
py::class_<Text::DistanceFieldGlyphCacheGL, Text::GlyphCacheGL>{m, "DistanceFieldGlyphCacheGL", "OpenGL glyph cache with distance field rendering"} py::class_<Text::DistanceFieldGlyphCacheGL, Text::GlyphCacheGL>{m, "DistanceFieldGlyphCacheGL", "OpenGL glyph cache with distance field rendering"}
.def(py::init<const Vector2i&, const Vector2i&, UnsignedInt>(), "Constructor", py::arg("size"), py::arg("processed_size"), py::arg("radius")); .def(py::init<const Vector2i&, const Vector2i&, UnsignedInt>(), "Constructor", py::arg("size"), py::arg("processed_size"), py::arg("radius"));
/* Font */ py::enum_<Text::Feature>{m, "Feature", "Open Type typographic feature"}
.value("ACCESS_ALL_ALTERNATES", Text::Feature::AccessAllAlternates)
.value("ABOVE_BASE_FORMS", Text::Feature::AboveBaseForms)
.value("ABOVE_BASE_MARK_POSITIONING", Text::Feature::AboveBaseMarkPositioning)
.value("ABOVE_BASE_SUBSTITUTIONS", Text::Feature::AboveBaseSubstitutions)
.value("ALTERNATIVE_FRACTIONS", Text::Feature::AlternativeFractions)
.value("AKHAND", Text::Feature::Akhand)
.value("KERNING_FOR_ALTERNATE_PROPORTIONAL_WIDTHS", Text::Feature::KerningForAlternateProportionalWidths)
.value("BELOW_BASE_FORMS", Text::Feature::BelowBaseForms)
.value("BELOW_BASE_MARK_POSITIONING", Text::Feature::BelowBaseMarkPositioning)
.value("BELOW_BASE_SUBSTITUTIONS", Text::Feature::BelowBaseSubstitutions)
.value("CONTEXTUAL_ALTERNATES", Text::Feature::ContextualAlternates)
.value("CASE_SENSITIVE_FORMS", Text::Feature::CaseSensitiveForms)
.value("GLYPH_COMPOSITION_DECOMPOSITION", Text::Feature::GlyphCompositionDecomposition)
.value("CONJUNCT_FORM_AFTER_RO", Text::Feature::ConjunctFormAfterRo)
.value("CONTEXTUAL_HALF_WIDTH_SPACING", Text::Feature::ContextualHalfWidthSpacing)
.value("CONJUNCT_FORMS", Text::Feature::ConjunctForms)
.value("CONTEXTUAL_LIGATURES", Text::Feature::ContextualLigatures)
.value("CENTERED_CJK_PUNCTUATION", Text::Feature::CenteredCjkPunctuation)
.value("CAPITAL_SPACING", Text::Feature::CapitalSpacing)
.value("CONTEXTUAL_SWASH", Text::Feature::ContextualSwash)
.value("CURSIVE_POSITIONING", Text::Feature::CursivePositioning)
.value("CHARACTER_VARIANTS1", Text::Feature::CharacterVariants1)
.value("CHARACTER_VARIANTS2", Text::Feature::CharacterVariants2)
.value("CHARACTER_VARIANTS3", Text::Feature::CharacterVariants3)
.value("CHARACTER_VARIANTS4", Text::Feature::CharacterVariants4)
.value("CHARACTER_VARIANTS5", Text::Feature::CharacterVariants5)
.value("CHARACTER_VARIANTS6", Text::Feature::CharacterVariants6)
.value("CHARACTER_VARIANTS7", Text::Feature::CharacterVariants7)
.value("CHARACTER_VARIANTS8", Text::Feature::CharacterVariants8)
.value("CHARACTER_VARIANTS9", Text::Feature::CharacterVariants9)
.value("CHARACTER_VARIANTS10", Text::Feature::CharacterVariants10)
.value("CHARACTER_VARIANTS11", Text::Feature::CharacterVariants11)
.value("CHARACTER_VARIANTS12", Text::Feature::CharacterVariants12)
.value("CHARACTER_VARIANTS13", Text::Feature::CharacterVariants13)
.value("CHARACTER_VARIANTS14", Text::Feature::CharacterVariants14)
.value("CHARACTER_VARIANTS15", Text::Feature::CharacterVariants15)
.value("CHARACTER_VARIANTS16", Text::Feature::CharacterVariants16)
.value("CHARACTER_VARIANTS17", Text::Feature::CharacterVariants17)
.value("CHARACTER_VARIANTS18", Text::Feature::CharacterVariants18)
.value("CHARACTER_VARIANTS19", Text::Feature::CharacterVariants19)
.value("CHARACTER_VARIANTS20", Text::Feature::CharacterVariants20)
.value("CHARACTER_VARIANTS21", Text::Feature::CharacterVariants21)
.value("CHARACTER_VARIANTS22", Text::Feature::CharacterVariants22)
.value("CHARACTER_VARIANTS23", Text::Feature::CharacterVariants23)
.value("CHARACTER_VARIANTS24", Text::Feature::CharacterVariants24)
.value("CHARACTER_VARIANTS25", Text::Feature::CharacterVariants25)
.value("CHARACTER_VARIANTS26", Text::Feature::CharacterVariants26)
.value("CHARACTER_VARIANTS27", Text::Feature::CharacterVariants27)
.value("CHARACTER_VARIANTS28", Text::Feature::CharacterVariants28)
.value("CHARACTER_VARIANTS29", Text::Feature::CharacterVariants29)
.value("CHARACTER_VARIANTS30", Text::Feature::CharacterVariants30)
.value("CHARACTER_VARIANTS31", Text::Feature::CharacterVariants31)
.value("CHARACTER_VARIANTS32", Text::Feature::CharacterVariants32)
.value("CHARACTER_VARIANTS33", Text::Feature::CharacterVariants33)
.value("CHARACTER_VARIANTS34", Text::Feature::CharacterVariants34)
.value("CHARACTER_VARIANTS35", Text::Feature::CharacterVariants35)
.value("CHARACTER_VARIANTS36", Text::Feature::CharacterVariants36)
.value("CHARACTER_VARIANTS37", Text::Feature::CharacterVariants37)
.value("CHARACTER_VARIANTS38", Text::Feature::CharacterVariants38)
.value("CHARACTER_VARIANTS39", Text::Feature::CharacterVariants39)
.value("CHARACTER_VARIANTS40", Text::Feature::CharacterVariants40)
.value("CHARACTER_VARIANTS41", Text::Feature::CharacterVariants41)
.value("CHARACTER_VARIANTS42", Text::Feature::CharacterVariants42)
.value("CHARACTER_VARIANTS43", Text::Feature::CharacterVariants43)
.value("CHARACTER_VARIANTS44", Text::Feature::CharacterVariants44)
.value("CHARACTER_VARIANTS45", Text::Feature::CharacterVariants45)
.value("CHARACTER_VARIANTS46", Text::Feature::CharacterVariants46)
.value("CHARACTER_VARIANTS47", Text::Feature::CharacterVariants47)
.value("CHARACTER_VARIANTS48", Text::Feature::CharacterVariants48)
.value("CHARACTER_VARIANTS49", Text::Feature::CharacterVariants49)
.value("CHARACTER_VARIANTS50", Text::Feature::CharacterVariants50)
.value("CHARACTER_VARIANTS51", Text::Feature::CharacterVariants51)
.value("CHARACTER_VARIANTS52", Text::Feature::CharacterVariants52)
.value("CHARACTER_VARIANTS53", Text::Feature::CharacterVariants53)
.value("CHARACTER_VARIANTS54", Text::Feature::CharacterVariants54)
.value("CHARACTER_VARIANTS55", Text::Feature::CharacterVariants55)
.value("CHARACTER_VARIANTS56", Text::Feature::CharacterVariants56)
.value("CHARACTER_VARIANTS57", Text::Feature::CharacterVariants57)
.value("CHARACTER_VARIANTS58", Text::Feature::CharacterVariants58)
.value("CHARACTER_VARIANTS59", Text::Feature::CharacterVariants59)
.value("CHARACTER_VARIANTS60", Text::Feature::CharacterVariants60)
.value("CHARACTER_VARIANTS61", Text::Feature::CharacterVariants61)
.value("CHARACTER_VARIANTS62", Text::Feature::CharacterVariants62)
.value("CHARACTER_VARIANTS63", Text::Feature::CharacterVariants63)
.value("CHARACTER_VARIANTS64", Text::Feature::CharacterVariants64)
.value("CHARACTER_VARIANTS65", Text::Feature::CharacterVariants65)
.value("CHARACTER_VARIANTS66", Text::Feature::CharacterVariants66)
.value("CHARACTER_VARIANTS67", Text::Feature::CharacterVariants67)
.value("CHARACTER_VARIANTS68", Text::Feature::CharacterVariants68)
.value("CHARACTER_VARIANTS69", Text::Feature::CharacterVariants69)
.value("CHARACTER_VARIANTS70", Text::Feature::CharacterVariants70)
.value("CHARACTER_VARIANTS71", Text::Feature::CharacterVariants71)
.value("CHARACTER_VARIANTS72", Text::Feature::CharacterVariants72)
.value("CHARACTER_VARIANTS73", Text::Feature::CharacterVariants73)
.value("CHARACTER_VARIANTS74", Text::Feature::CharacterVariants74)
.value("CHARACTER_VARIANTS75", Text::Feature::CharacterVariants75)
.value("CHARACTER_VARIANTS76", Text::Feature::CharacterVariants76)
.value("CHARACTER_VARIANTS77", Text::Feature::CharacterVariants77)
.value("CHARACTER_VARIANTS78", Text::Feature::CharacterVariants78)
.value("CHARACTER_VARIANTS79", Text::Feature::CharacterVariants79)
.value("CHARACTER_VARIANTS80", Text::Feature::CharacterVariants80)
.value("CHARACTER_VARIANTS81", Text::Feature::CharacterVariants81)
.value("CHARACTER_VARIANTS82", Text::Feature::CharacterVariants82)
.value("CHARACTER_VARIANTS83", Text::Feature::CharacterVariants83)
.value("CHARACTER_VARIANTS84", Text::Feature::CharacterVariants84)
.value("CHARACTER_VARIANTS85", Text::Feature::CharacterVariants85)
.value("CHARACTER_VARIANTS86", Text::Feature::CharacterVariants86)
.value("CHARACTER_VARIANTS87", Text::Feature::CharacterVariants87)
.value("CHARACTER_VARIANTS88", Text::Feature::CharacterVariants88)
.value("CHARACTER_VARIANTS89", Text::Feature::CharacterVariants89)
.value("CHARACTER_VARIANTS90", Text::Feature::CharacterVariants90)
.value("CHARACTER_VARIANTS91", Text::Feature::CharacterVariants91)
.value("CHARACTER_VARIANTS92", Text::Feature::CharacterVariants92)
.value("CHARACTER_VARIANTS93", Text::Feature::CharacterVariants93)
.value("CHARACTER_VARIANTS94", Text::Feature::CharacterVariants94)
.value("CHARACTER_VARIANTS95", Text::Feature::CharacterVariants95)
.value("CHARACTER_VARIANTS96", Text::Feature::CharacterVariants96)
.value("CHARACTER_VARIANTS97", Text::Feature::CharacterVariants97)
.value("CHARACTER_VARIANTS98", Text::Feature::CharacterVariants98)
.value("CHARACTER_VARIANTS99", Text::Feature::CharacterVariants99)
.value("PETITE_CAPITALS_FROM_CAPITALS", Text::Feature::PetiteCapitalsFromCapitals)
.value("SMALL_CAPITALS_FROM_CAPITALS", Text::Feature::SmallCapitalsFromCapitals)
.value("DISTANCES", Text::Feature::Distances)
.value("DISCRETIONARY_LIGATURES", Text::Feature::DiscretionaryLigatures)
.value("DENOMINATORS", Text::Feature::Denominators)
.value("DOTLESS_FORMS", Text::Feature::DotlessForms)
.value("EXPERT_FORMS", Text::Feature::ExpertForms)
.value("FINAL_GLYPH_ON_LINE_ALTERNATES", Text::Feature::FinalGlyphOnLineAlternates)
.value("TERMINAL_FORMS", Text::Feature::TerminalForms)
.value("TERMINAL_FORMS2", Text::Feature::TerminalForms2)
.value("TERMINAL_FORMS3", Text::Feature::TerminalForms3)
.value("FLATTENED_ACCENT_FORMS", Text::Feature::FlattenedAccentForms)
.value("FRACTIONS", Text::Feature::Fractions)
.value("FULL_WIDTHS", Text::Feature::FullWidths)
.value("HALF_FORMS", Text::Feature::HalfForms)
.value("HALANT_FORMS", Text::Feature::HalantForms)
.value("ALTERNATE_HALF_WIDTHS", Text::Feature::AlternateHalfWidths)
.value("HISTORICAL_FORMS", Text::Feature::HistoricalForms)
.value("HORIZONTAL_KANA_ALTERNATES", Text::Feature::HorizontalKanaAlternates)
.value("HISTORICAL_LIGATURES", Text::Feature::HistoricalLigatures)
.value("HANGUL", Text::Feature::Hangul)
.value("HOJO_KANJI_FORMS", Text::Feature::HojoKanjiForms)
.value("HALF_WIDTHS", Text::Feature::HalfWidths)
.value("INITIAL_FORMS", Text::Feature::InitialForms)
.value("ISOLATED_FORMS", Text::Feature::IsolatedForms)
.value("ITALICS", Text::Feature::Italics)
.value("JUSTIFICATION_ALTERNATES", Text::Feature::JustificationAlternates)
.value("JIS78_FORMS", Text::Feature::Jis78Forms)
.value("JIS83_FORMS", Text::Feature::Jis83Forms)
.value("JIS90_FORMS", Text::Feature::Jis90Forms)
.value("JIS2004_FORMS", Text::Feature::Jis2004Forms)
.value("KERNING", Text::Feature::Kerning)
.value("LEFT_BOUNDS", Text::Feature::LeftBounds)
.value("STANDARD_LIGATURES", Text::Feature::StandardLigatures)
.value("LEADING_JAMO_FORMS", Text::Feature::LeadingJamoForms)
.value("LINING_FIGURES", Text::Feature::LiningFigures)
.value("LOCALIZED_FORMS", Text::Feature::LocalizedForms)
.value("LEFT_TO_RIGHT_ALTERNATES", Text::Feature::LeftToRightAlternates)
.value("LEFT_TO_RIGHT_MIRRORED_FORMS", Text::Feature::LeftToRightMirroredForms)
.value("MARK_POSITIONING", Text::Feature::MarkPositioning)
.value("MEDIAL_FORMS", Text::Feature::MedialForms)
.value("MEDIAL_FORMS2", Text::Feature::MedialForms2)
.value("MATHEMATICAL_GREEK", Text::Feature::MathematicalGreek)
.value("MARK_TO_MARK_POSITIONING", Text::Feature::MarkToMarkPositioning)
.value("MARK_POSITIONING_VIA_SUBSTITUTION", Text::Feature::MarkPositioningViaSubstitution)
.value("ALTERNATE_ANNOTATION_FORMS", Text::Feature::AlternateAnnotationForms)
.value("NLC_KANJI_FORMS", Text::Feature::NlcKanjiForms)
.value("NUKTA_FORMS", Text::Feature::NuktaForms)
.value("NUMERATORS", Text::Feature::Numerators)
.value("OLDSTYLE_FIGURES", Text::Feature::OldstyleFigures)
.value("OPTICAL_BOUNDS", Text::Feature::OpticalBounds)
.value("ORDINALS", Text::Feature::Ordinals)
.value("ORNAMENTS", Text::Feature::Ornaments)
.value("PROPORTIONAL_ALTERNATE_WIDTHS", Text::Feature::ProportionalAlternateWidths)
.value("PETITE_CAPITALS", Text::Feature::PetiteCapitals)
.value("PROPORTIONAL_KANA", Text::Feature::ProportionalKana)
.value("PROPORTIONAL_FIGURES", Text::Feature::ProportionalFigures)
.value("PRE_BASE_FORMS", Text::Feature::PreBaseForms)
.value("PRE_BASE_SUBSTITUTIONS", Text::Feature::PreBaseSubstitutions)
.value("POST_BASE_FORMS", Text::Feature::PostBaseForms)
.value("POST_BASE_SUBSTITUTIONS", Text::Feature::PostBaseSubstitutions)
.value("PROPORTIONAL_WIDTHS", Text::Feature::ProportionalWidths)
.value("QUARTER_WIDTHS", Text::Feature::QuarterWidths)
.value("RANDOMIZE", Text::Feature::Randomize)
.value("REQUIRED_CONTEXTUAL_ALTERNATES", Text::Feature::RequiredContextualAlternates)
.value("RAKAR_FORMS", Text::Feature::RakarForms)
.value("REQUIRED_LIGATURES", Text::Feature::RequiredLigatures)
.value("REPH_FORMS", Text::Feature::RephForms)
.value("RIGHT_BOUNDS", Text::Feature::RightBounds)
.value("RIGHT_TO_LEFT_ALTERNATES", Text::Feature::RightToLeftAlternates)
.value("RIGHT_TO_LEFT_MIRRORED_FORMS", Text::Feature::RightToLeftMirroredForms)
.value("RUBY_NOTATION_FORMS", Text::Feature::RubyNotationForms)
.value("REQUIRED_VARIATION_ALTERNATES", Text::Feature::RequiredVariationAlternates)
.value("STYLISTIC_ALTERNATES", Text::Feature::StylisticAlternates)
.value("SCIENTIFIC_INFERIORS", Text::Feature::ScientificInferiors)
.value("OPTICAL_SIZE", Text::Feature::OpticalSize)
.value("SMALL_CAPITALS", Text::Feature::SmallCapitals)
.value("SIMPLIFIED_FORMS", Text::Feature::SimplifiedForms)
.value("STYLISTIC_SET1", Text::Feature::StylisticSet1)
.value("STYLISTIC_SET2", Text::Feature::StylisticSet2)
.value("STYLISTIC_SET3", Text::Feature::StylisticSet3)
.value("STYLISTIC_SET4", Text::Feature::StylisticSet4)
.value("STYLISTIC_SET5", Text::Feature::StylisticSet5)
.value("STYLISTIC_SET6", Text::Feature::StylisticSet6)
.value("STYLISTIC_SET7", Text::Feature::StylisticSet7)
.value("STYLISTIC_SET8", Text::Feature::StylisticSet8)
.value("STYLISTIC_SET9", Text::Feature::StylisticSet9)
.value("STYLISTIC_SET10", Text::Feature::StylisticSet10)
.value("STYLISTIC_SET11", Text::Feature::StylisticSet11)
.value("STYLISTIC_SET12", Text::Feature::StylisticSet12)
.value("STYLISTIC_SET13", Text::Feature::StylisticSet13)
.value("STYLISTIC_SET14", Text::Feature::StylisticSet14)
.value("STYLISTIC_SET15", Text::Feature::StylisticSet15)
.value("STYLISTIC_SET16", Text::Feature::StylisticSet16)
.value("STYLISTIC_SET17", Text::Feature::StylisticSet17)
.value("STYLISTIC_SET18", Text::Feature::StylisticSet18)
.value("STYLISTIC_SET19", Text::Feature::StylisticSet19)
.value("STYLISTIC_SET20", Text::Feature::StylisticSet20)
.value("MATH_SCRIPT_STYLE_ALTERNATES", Text::Feature::MathScriptStyleAlternates)
.value("STRETCHING_GLYPH_DECOMPOSITION", Text::Feature::StretchingGlyphDecomposition)
.value("SUBSCRIPT", Text::Feature::Subscript)
.value("SUPERSCRIPT", Text::Feature::Superscript)
.value("SWASH", Text::Feature::Swash)
.value("TITLING", Text::Feature::Titling)
.value("TRAILING_JAMO_FORMS", Text::Feature::TrailingJamoForms)
.value("TRADITIONAL_NAME_FORMS", Text::Feature::TraditionalNameForms)
.value("TABULAR_FIGURES", Text::Feature::TabularFigures)
.value("TRADITIONAL_FORMS", Text::Feature::TraditionalForms)
.value("THIRD_WIDTHS", Text::Feature::ThirdWidths)
.value("UNICASE", Text::Feature::Unicase)
.value("ALTERNATE_VERTICAL_METRICS", Text::Feature::AlternateVerticalMetrics)
.value("VATTU_VARIANTS", Text::Feature::VattuVariants)
.value("KERNING_FOR_ALTERNATE_PROPORTIONAL_VERTICAL_METRICS", Text::Feature::KerningForAlternateProportionalVerticalMetrics)
.value("VERTICAL_CONTEXTUAL_HALF_WIDTH_SPACING", Text::Feature::VerticalContextualHalfWidthSpacing)
.value("VERTICAL_WRITING", Text::Feature::VerticalWriting)
.value("ALTERNATE_VERTICAL_HALF_METRICS", Text::Feature::AlternateVerticalHalfMetrics)
.value("VOWEL_JAMO_FORMS", Text::Feature::VowelJamoForms)
.value("VERTICAL_KANA_ALTERNATES", Text::Feature::VerticalKanaAlternates)
.value("VERTICAL_KERNING", Text::Feature::VerticalKerning)
.value("PROPORTIONAL_ALTERNATE_VERTICAL_METRICS", Text::Feature::ProportionalAlternateVerticalMetrics)
.value("VERTICAL_ALTERNATES_AND_ROTATION", Text::Feature::VerticalAlternatesAndRotation)
.value("VERTICAL_ALTERNATES_FOR_ROTATION", Text::Feature::VerticalAlternatesForRotation)
.value("SLASHED_ZERO", Text::Feature::SlashedZero)
/** @todo drop std::string in favor of our own string caster */
.def(py::init([](const std::string& fourCC) {
if(fourCC.size() != 4) {
PyErr_Format(PyExc_AssertionError, "expected a four-character code, got %s", fourCC.data());
throw py::error_already_set{};
}
return Text::feature(fourCC);
}));
py::class_<Text::FeatureRange>{m, "FeatureRange", "OpenType feature for a text range"}
/** @todo add a begin/end variant once it's clear whether a byte index
or a "python char" index would be more useful */
.def(py::init<Text::Feature, UnsignedInt>(), "Construct for the whole text", py::arg("feature"), py::arg("value") = true)
/* To support the implicit conversion below */
.def(py::init([](const std::pair<Text::Feature, UnsignedInt>& value){
return Text::FeatureRange{value.first, value.second};
}), "Construct for the whole text")
.def_property_readonly("feature", &Text::FeatureRange::feature, "Feature to control")
.def_property_readonly("is_enabled", &Text::FeatureRange::isEnabled, "Whether to enable the feature")
.def_property_readonly("value", &Text::FeatureRange::value, "Feature value to set");
/* For convenient passing as a list to renderer.add() and render() */
py::implicitly_convertible<Text::Feature, Text::FeatureRange>();
py::implicitly_convertible<std::pair<Text::Feature, UnsignedInt>, Text::FeatureRange>();
py::enum_<Text::Script>{m, "Script", "Script a text is written in"}
.value("UNSPECIFIED", Text::Script::Unspecified)
.value("INHERITED", Text::Script::Inherited)
.value("MATH", Text::Script::Math)
.value("COMMON", Text::Script::Common)
.value("UNKNOWN", Text::Script::Unknown)
.value("ADLAM", Text::Script::Adlam)
.value("CAUCASIAN_ALBANIAN", Text::Script::CaucasianAlbanian)
.value("AHOM", Text::Script::Ahom)
.value("ARABIC", Text::Script::Arabic)
.value("IMPERIAL_ARAMAIC", Text::Script::ImperialAramaic)
.value("ARMENIAN", Text::Script::Armenian)
.value("AVESTAN", Text::Script::Avestan)
.value("BALINESE", Text::Script::Balinese)
.value("BAMUM", Text::Script::Bamum)
.value("BASSA_VAH", Text::Script::BassaVah)
.value("BATAK", Text::Script::Batak)
.value("BENGALI", Text::Script::Bengali)
.value("BHAIKSUKI", Text::Script::Bhaiksuki)
.value("BOPOMOFO", Text::Script::Bopomofo)
.value("BRAHMI", Text::Script::Brahmi)
.value("BRAILLE", Text::Script::Braille)
.value("BUGINESE", Text::Script::Buginese)
.value("BUHID", Text::Script::Buhid)
.value("CHAKMA", Text::Script::Chakma)
.value("CANADIAN_ABORIGINAL", Text::Script::CanadianAboriginal)
.value("CARIAN", Text::Script::Carian)
.value("CHAM", Text::Script::Cham)
.value("CHEROKEE", Text::Script::Cherokee)
.value("CHORASMIAN", Text::Script::Chorasmian)
.value("COPTIC", Text::Script::Coptic)
.value("CYPRO_MINOAN", Text::Script::CyproMinoan)
.value("CYPRIOT", Text::Script::Cypriot)
.value("CYRILLIC", Text::Script::Cyrillic)
.value("DEVANAGARI", Text::Script::Devanagari)
.value("DIVES_AKURU", Text::Script::DivesAkuru)
.value("DOGRA", Text::Script::Dogra)
.value("DESERET", Text::Script::Deseret)
.value("DUPLOYAN", Text::Script::Duployan)
.value("EGYPTIAN_HIEROGLYPHS", Text::Script::EgyptianHieroglyphs)
.value("ELBASAN", Text::Script::Elbasan)
.value("ELYMAIC", Text::Script::Elymaic)
.value("ETHIOPIC", Text::Script::Ethiopic)
.value("GARAY", Text::Script::Garay)
.value("GEORGIAN", Text::Script::Georgian)
.value("GLAGOLITIC", Text::Script::Glagolitic)
.value("GUNJALA_GONDI", Text::Script::GunjalaGondi)
.value("MASARAM_GONDI", Text::Script::MasaramGondi)
.value("GOTHIC", Text::Script::Gothic)
.value("GRANTHA", Text::Script::Grantha)
.value("GREEK", Text::Script::Greek)
.value("GUJARATI", Text::Script::Gujarati)
.value("GURUNG_KHEMA", Text::Script::GurungKhema)
.value("GURMUKHI", Text::Script::Gurmukhi)
.value("HANGUL", Text::Script::Hangul)
.value("HAN", Text::Script::Han)
.value("HANUNOO", Text::Script::Hanunoo)
.value("HATRAN", Text::Script::Hatran)
.value("HEBREW", Text::Script::Hebrew)
.value("HIRAGANA", Text::Script::Hiragana)
.value("ANATOLIAN_HIEROGLYPHS", Text::Script::AnatolianHieroglyphs)
.value("PAHAWH_HMONG", Text::Script::PahawhHmong)
.value("NYIAKENG_PUACHUE_HMONG", Text::Script::NyiakengPuachueHmong)
.value("OLD_HUNGARIAN", Text::Script::OldHungarian)
.value("OLD_ITALIC", Text::Script::OldItalic)
.value("JAVANESE", Text::Script::Javanese)
.value("KAYAH_LI", Text::Script::KayahLi)
.value("KATAKANA", Text::Script::Katakana)
.value("KAWI", Text::Script::Kawi)
.value("KHAROSHTHI", Text::Script::Kharoshthi)
.value("KHMER", Text::Script::Khmer)
.value("KHOJKI", Text::Script::Khojki)
.value("KHITAN_SMALL_SCRIPT", Text::Script::KhitanSmallScript)
.value("KANNADA", Text::Script::Kannada)
.value("KIRAT_RAI", Text::Script::KiratRai)
.value("KAITHI", Text::Script::Kaithi)
.value("TAI_THAM", Text::Script::TaiTham)
.value("LAO", Text::Script::Lao)
.value("LATIN", Text::Script::Latin)
.value("LEPCHA", Text::Script::Lepcha)
.value("LIMBU", Text::Script::Limbu)
.value("LINEARA", Text::Script::LinearA)
.value("LINEARB", Text::Script::LinearB)
.value("LISU", Text::Script::Lisu)
.value("LYCIAN", Text::Script::Lycian)
.value("LYDIAN", Text::Script::Lydian)
.value("MAHAJANI", Text::Script::Mahajani)
.value("MAKASAR", Text::Script::Makasar)
.value("MANDAIC", Text::Script::Mandaic)
.value("MANICHAEAN", Text::Script::Manichaean)
.value("MARCHEN", Text::Script::Marchen)
.value("MEDEFAIDRIN", Text::Script::Medefaidrin)
.value("MENDE_KIKAKUI", Text::Script::MendeKikakui)
.value("MEROITIC_CURSIVE", Text::Script::MeroiticCursive)
.value("MEROITIC_HIEROGLYPHS", Text::Script::MeroiticHieroglyphs)
.value("MALAYALAM", Text::Script::Malayalam)
.value("MODI", Text::Script::Modi)
.value("MONGOLIAN", Text::Script::Mongolian)
.value("MRO", Text::Script::Mro)
.value("MEETEI_MAYEK", Text::Script::MeeteiMayek)
.value("MULTANI", Text::Script::Multani)
.value("MYANMAR", Text::Script::Myanmar)
.value("NAG_MUNDARI", Text::Script::NagMundari)
.value("NANDINAGARI", Text::Script::Nandinagari)
.value("OLD_NORTH_ARABIAN", Text::Script::OldNorthArabian)
.value("NABATAEAN", Text::Script::Nabataean)
.value("NEWA", Text::Script::Newa)
.value("N_KO", Text::Script::NKo)
.value("NUSHU", Text::Script::Nushu)
.value("OGHAM", Text::Script::Ogham)
.value("OL_CHIKI", Text::Script::OlChiki)
.value("OL_ONAL", Text::Script::OlOnal)
.value("OLD_TURKIC", Text::Script::OldTurkic)
.value("ORIYA", Text::Script::Oriya)
.value("OSAGE", Text::Script::Osage)
.value("OSMANYA", Text::Script::Osmanya)
.value("OLD_UYGHUR", Text::Script::OldUyghur)
.value("PALMYRENE", Text::Script::Palmyrene)
.value("PAU_CIN_HAU", Text::Script::PauCinHau)
.value("OLD_PERMIC", Text::Script::OldPermic)
.value("PHAGS_PA", Text::Script::PhagsPa)
.value("INSCRIPTIONAL_PAHLAVI", Text::Script::InscriptionalPahlavi)
.value("PSALTER_PAHLAVI", Text::Script::PsalterPahlavi)
.value("PHOENICIAN", Text::Script::Phoenician)
.value("MIAO", Text::Script::Miao)
.value("INSCRIPTIONAL_PARTHIAN", Text::Script::InscriptionalParthian)
.value("REJANG", Text::Script::Rejang)
.value("HANIFI_ROHINGYA", Text::Script::HanifiRohingya)
.value("RUNIC", Text::Script::Runic)
.value("SAMARITAN", Text::Script::Samaritan)
.value("OLD_SOUTH_ARABIAN", Text::Script::OldSouthArabian)
.value("SAURASHTRA", Text::Script::Saurashtra)
.value("SIGN_WRITING", Text::Script::SignWriting)
.value("SHAVIAN", Text::Script::Shavian)
.value("SHARADA", Text::Script::Sharada)
.value("SIDDHAM", Text::Script::Siddham)
.value("KHUDAWADI", Text::Script::Khudawadi)
.value("SINHALA", Text::Script::Sinhala)
.value("SOGDIAN", Text::Script::Sogdian)
.value("OLD_SOGDIAN", Text::Script::OldSogdian)
.value("SORA_SOMPENG", Text::Script::SoraSompeng)
.value("SOYOMBO", Text::Script::Soyombo)
.value("SUNDANESE", Text::Script::Sundanese)
.value("SUNUWAR", Text::Script::Sunuwar)
.value("SYLOTI_NAGRI", Text::Script::SylotiNagri)
.value("SYRIAC", Text::Script::Syriac)
.value("TAGBANWA", Text::Script::Tagbanwa)
.value("TAKRI", Text::Script::Takri)
.value("TAI_LE", Text::Script::TaiLe)
.value("NEW_TAI_LUE", Text::Script::NewTaiLue)
.value("TAMIL", Text::Script::Tamil)
.value("TANGUT", Text::Script::Tangut)
.value("TAI_VIET", Text::Script::TaiViet)
.value("TELUGU", Text::Script::Telugu)
.value("TIFINAGH", Text::Script::Tifinagh)
.value("TAGALOG", Text::Script::Tagalog)
.value("THAANA", Text::Script::Thaana)
.value("THAI", Text::Script::Thai)
.value("TIBETAN", Text::Script::Tibetan)
.value("TIRHUTA", Text::Script::Tirhuta)
.value("TANGSA", Text::Script::Tangsa)
.value("TODHRI", Text::Script::Todhri)
.value("TOTO", Text::Script::Toto)
.value("TULU_TIGALARI", Text::Script::TuluTigalari)
.value("UGARITIC", Text::Script::Ugaritic)
.value("VAI", Text::Script::Vai)
.value("VITHKUQI", Text::Script::Vithkuqi)
.value("WARANG_CITI", Text::Script::WarangCiti)
.value("WANCHO", Text::Script::Wancho)
.value("OLD_PERSIAN", Text::Script::OldPersian)
.value("CUNEIFORM", Text::Script::Cuneiform)
.value("YEZIDI", Text::Script::Yezidi)
.value("YI", Text::Script::Yi)
.value("ZANABAZAR_SQUARE", Text::Script::ZanabazarSquare)
/** @todo drop std::string in favor of our own string caster */
.def(py::init([](const std::string& fourCC) {
if(fourCC.size() != 4) {
PyErr_Format(PyExc_AssertionError, "expected a four-character code, got %s", fourCC.data());
throw py::error_already_set{};
}
return Text::script(fourCC);
}));
py::enum_<Text::ShapeDirection>{m, "ShapeDirection", "Direction a text is shaped in"}
.value("UNSPECIFIED", Text::ShapeDirection::Unspecified)
.value("LEFT_TO_RIGHT", Text::ShapeDirection::LeftToRight)
.value("RIGHT_TO_LEFT", Text::ShapeDirection::RightToLeft)
.value("TOP_TO_BOTTOM", Text::ShapeDirection::TopToBottom)
.value("BOTTOM_TO_TOP", Text::ShapeDirection::BottomToTop);
/* Font. Returned by AbstractShaper, so has to be declared before, but the
font is returning AbstractShaper as well, so the method definitions are
after AbstractShaper. */
py::class_<Text::AbstractFont, PluginManager::PyPluginHolder<Text::AbstractFont>, PluginManager::AbstractPlugin> abstractFont{m, "AbstractFont", "Interface for font plugins"}; py::class_<Text::AbstractFont, PluginManager::PyPluginHolder<Text::AbstractFont>, PluginManager::AbstractPlugin> abstractFont{m, "AbstractFont", "Interface for font plugins"};
/* Shaper */
py::class_<Text::AbstractShaper>{m, "AbstractShaper", "Base for text shapers"}
.def_property_readonly("font", static_cast<Text::AbstractFont&(Text::AbstractShaper::*)()>(&Text::AbstractShaper::font), "Font owning this shaper instance")
/* Not using a property for these because it may be useful to know
whether setting these actually did anything in given plugin */
.def("set_script", &Text::AbstractShaper::setScript, "Set text script", py::arg("script"))
.def("set_language", [](Text::AbstractShaper& self, const std::string& language) {
return self.setLanguage(language);
}, "Set text language", py::arg("language"))
.def("set_direction", &Text::AbstractShaper::setDirection, "Set direction the text is meant to be shaped in", py::arg("direction"))
/** @todo glyph count, script, language getters together with glyph
data getters once it makes sense to use shape() and such
directly */
;
/* Font methods */
abstractFont abstractFont
/** @todo features */ /** @todo features */
.def_property_readonly("is_opened", &Text::AbstractFont::isOpened, "Whether any file is opened") .def_property_readonly("is_opened", &Text::AbstractFont::isOpened, "Whether any file is opened")
@ -160,8 +647,17 @@ void text(py::module_& m) {
return self.fillGlyphCache(cache, characters); return self.fillGlyphCache(cache, characters);
}, "Fill glyph cache with given character set", py::arg("cache"), py::arg("characters")) }, "Fill glyph cache with given character set", py::arg("cache"), py::arg("characters"))
/** @todo createGlyphCache() */ /** @todo createGlyphCache() */
/** @todo layout and AbstractLayouter, once needed for anything */ .def("create_shaper", [](Text::AbstractFont& self) {
; if(!self.isOpened()) {
PyErr_SetString(PyExc_AssertionError, "no file opened");
throw py::error_already_set{};
}
return std::unique_ptr<Text::AbstractShaper>(self.createShaper());
}, "Create an instance of this font shaper implementation",
/* Keeps the font (index 1) alive for as long as the return value
(index 0) exists */
py::keep_alive<0, 1>{});
corrade::plugin(abstractFont); corrade::plugin(abstractFont);
py::class_<PluginManager::Manager<Text::AbstractFont>, PluginManager::AbstractManager> fontManager{m, "FontManager", "Manager for font plugins"}; py::class_<PluginManager::Manager<Text::AbstractFont>, PluginManager::AbstractManager> fontManager{m, "FontManager", "Manager for font plugins"};
@ -182,21 +678,113 @@ void text(py::module_& m) {
.value("MIDDLE_CENTER_INTEGRAL", Text::Alignment::MiddleCenterIntegral) .value("MIDDLE_CENTER_INTEGRAL", Text::Alignment::MiddleCenterIntegral)
.value("MIDDLE_RIGHT_INTEGRAL", Text::Alignment::MiddleRightIntegral); .value("MIDDLE_RIGHT_INTEGRAL", Text::Alignment::MiddleRightIntegral);
/** @todo any reason to expose a 3D renderer? it isn't any different /* RendererCore, Renderer, RendererGL */
currently */ py::class_<Text::RendererCore>{m, "RendererCore", "Text renderer core"}
py::class_<Text::Renderer2D> renderer2D{m, "Renderer2D", "2D text renderer"}; /** @todo expose constructors once the class is directly useful for
renderer2D anything */
.def(py::init<Text::AbstractFont&, const Text::GlyphCacheGL&, Float, Text::Alignment>(), "Constructor", py::arg("font"), py::arg("cache"), py::arg("size"), py::arg("alignment") = Text::Alignment::LineLeft) /* In this case the glyph cache isn't owned by the renderer so the
.def_property_readonly("capacity", &Text::Renderer2D::capacity, "Capacity for rendered glyphs") returned object doesn't increase the renderer refcount. This is
.def_property_readonly("rectangle", &Text::Renderer2D::rectangle, "Rectangle spanning the rendered text") verified in test_text_gl.py to be extra sure. */
/** @todo are the buffers useful for anything? */ .def_property_readonly("glyph_cache", &Text::RendererCore::glyphCache, "Glyph cache associated with the renderer")
/** @todo expose flags once accessing the glyph data is useful for
anything */
.def_property_readonly("glyph_count", &Text::RendererCore::glyphCount, "Total count of rendered glyphs")
.def_property_readonly("glyph_capacity", &Text::RendererCore::glyphCapacity, "Glyph capacity")
.def_property_readonly("run_count", &Text::RendererCore::runCount, "Total count of rendered runs")
.def_property_readonly("run_capacity", &Text::RendererCore::runCapacity, "Run capacity")
.def_property_readonly("is_rendering", &Text::RendererCore::isRendering, "Whether text rendering is currently in progress")
.def_property_readonly("rendering_glyph_count", &Text::RendererCore::renderingGlyphCount, "Total count of glyphs including current in-progress rendering")
.def_property_readonly("rendering_run_count", &Text::RendererCore::renderingRunCount, "Total count of runs including current in-progress rendering")
.def_property("cursor", &Text::RendererCore::cursor, [](Text::RendererCore& self, const Vector2& cursor) {
if(self.isRendering()) {
PyErr_SetString(PyExc_AssertionError, "rendering in progress");
throw py::error_already_set{};
}
self.setCursor(cursor);
}, "Cursor position")
.def_property("alignment", &Text::RendererCore::alignment, [](Text::RendererCore& self, Text::Alignment alignment) {
if(self.isRendering()) {
PyErr_SetString(PyExc_AssertionError, "rendering in progress");
throw py::error_already_set{};
}
self.setAlignment(alignment);
}, "Alignment")
.def_property("line_advance", &Text::RendererCore::lineAdvance, [](Text::RendererCore& self, const Float advance) {
if(self.isRendering()) {
PyErr_SetString(PyExc_AssertionError, "rendering in progress");
throw py::error_already_set{};
}
self.setLineAdvance(advance);
}, "Cursor position")
/** @todo layout direction once there's more than one value allowed */
/** @todo reserve, clear, reset once it's possible to use RendererCore
directly */
/** @todo add a begin/end variant once it's clear whether a byte index
or a "python char" index would be more useful */
/** @todo drop std::string in favor of our own string caster */
/** @todo drop std::vector in favor of our own list caster */
.def("add", [](Text::RendererCore& self, Text::AbstractShaper& shaper, Float size, const std::string& text, const std::vector<Text::FeatureRange>& features) {
if(!self.glyphCache().findFont(shaper.font())) {
PyErr_Format(PyExc_AssertionError, "shaper font not found among %u fonts in associated glyph cache", self.glyphCache().fontCount());
throw py::error_already_set{};
}
self.add(shaper, size, text, features);
}, "Add a whole string to the currently rendered text", py::arg("shaper"), py::arg("size"), py::arg("text"), py::arg("features") = std::vector<Text::FeatureRange>{})
/** @todo render once it's possible to use RendererCore directly */
;
py::class_<Text::Renderer, Text::RendererCore>{m, "Renderer", "Text renderer"}
/** @todo expose flags once accessing the glyph data is useful for
anything */
.def_property_readonly("glyph_index_capacity", &Text::Renderer::glyphIndexCapacity, "Glyph index capacity")
.def_property_readonly("glyph_vertex_capacity", &Text::Renderer::glyphVertexCapacity, "Glyph vertex capacity")
/** @todo index_type, reserve, clear, reset, render once it's possible
to use Renderer directly */
;
py::class_<Text::RendererGL, Text::Renderer> rendererGL{m, "RendererGL", "OpenGL text renderer"};
rendererGL
/** @todo expose flags once accessing the glyph data is useful for
anything */
.def(py::init<const Text::AbstractGlyphCache&>(), "Constructor", py::arg("cache"),
/* Keeps the cache (index 2) alive for as long as the renderer
(index 1) exists */
py::keep_alive<1, 2>{})
/* The default behavior when returning a reference seems to be that it /* The default behavior when returning a reference seems to be that it
increfs the originating instance and decrefs it again after the increfs the originating instance and decrefs it again after the
variable gets deleted. This is verified in test_text.py to be extra variable gets deleted. This is verified in test_text_gl.py to be
sure. */ extra sure. */
.def_property_readonly("mesh", &Text::Renderer2D::mesh, "Mesh") .def_property_readonly("mesh", static_cast<GL::Mesh&(Text::RendererGL::*)()>(&Text::RendererGL::mesh), "Mesh containing the rendered index and vertex data")
.def("reserve", &Text::Renderer2D::reserve, "Reserve capacity for renderered glyphs", py::arg("glyph_count"), py::arg("vertex_buffer_usage") = GL::BufferUsage::StaticDraw, py::arg("index_buffer_usage") = GL::BufferUsage::StaticDraw) .def_property("index_type", &Text::RendererGL::indexType, [](Text::RendererGL& self, MeshIndexType atLeast) {
.def("render", static_cast<void(Text::Renderer2D::*)(const std::string&)>(&Text::Renderer2D::render), "Render text", py::arg("text")); if(self.isRendering()) {
PyErr_SetString(PyExc_AssertionError, "rendering in progress");
throw py::error_already_set{};
}
self.setIndexType(atLeast);
}, "Index type")
.def("reserve", [](Text::RendererGL& self, UnsignedInt glyphCapacity, UnsignedInt runCapacity) {
/* Using a lambda to ignore the method chaining return type */
self.reserve(glyphCapacity, runCapacity);
}, "Reserve capacity for given glyph and run count", py::arg("glyph_capacity"), py::arg("run_capacity"))
.def("clear", [](Text::RendererGL& self) {
/* Using a lambda to ignore the method chaining return type */
self.clear();
}, "Clear rendered glyphs, runs and vertices")
.def("reset", [](Text::RendererGL& self) {
/* Using a lambda to ignore the method chaining return type */
self.reset();
}, "Reset internal renderer state")
/** @todo drop std::pair in favor of our own string caster */
.def("render", [](Text::RendererGL& self) {
return std::pair<Range2D, Range1Dui>(self.render());
}, "Wrap up rendering of all text added so far")
.def("render", [](Text::RendererGL& self, Text::AbstractShaper& shaper, Float size, const std::string& text, const std::vector<Text::FeatureRange>& features) {
if(!self.glyphCache().findFont(shaper.font())) {
PyErr_Format(PyExc_AssertionError, "shaper font not found among %u fonts in associated glyph cache", self.glyphCache().fontCount());
throw py::error_already_set{};
}
return std::pair<Range2D, Range1Dui>(self.render(shaper, size, text, features));
}, "Render a whole text at once", py::arg("shaper"), py::arg("size"), py::arg("text"), py::arg("features") = std::vector<Text::FeatureRange>{});
} }
} }

Loading…
Cancel
Save