pyglet 2.1.2__py3-none-any.whl → 2.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglet/__init__.py +21 -9
- pyglet/__init__.pyi +3 -1
- pyglet/app/cocoa.py +6 -3
- pyglet/app/xlib.py +1 -1
- pyglet/display/cocoa.py +2 -2
- pyglet/display/win32.py +17 -18
- pyglet/display/xlib.py +2 -2
- pyglet/display/xlib_vidmoderestore.py +1 -1
- pyglet/extlibs/earcut.py +2 -2
- pyglet/font/__init__.py +3 -3
- pyglet/font/base.py +118 -51
- pyglet/font/dwrite/__init__.py +1381 -0
- pyglet/font/dwrite/d2d1_lib.py +637 -0
- pyglet/font/dwrite/d2d1_types_lib.py +60 -0
- pyglet/font/dwrite/dwrite_lib.py +1577 -0
- pyglet/font/fontconfig.py +79 -16
- pyglet/font/freetype.py +252 -77
- pyglet/font/freetype_lib.py +234 -125
- pyglet/font/harfbuzz/__init__.py +275 -0
- pyglet/font/harfbuzz/harfbuzz_lib.py +212 -0
- pyglet/font/quartz.py +432 -112
- pyglet/font/user.py +18 -11
- pyglet/font/win32.py +9 -1
- pyglet/gl/wgl.py +94 -87
- pyglet/gl/wglext_arb.py +472 -218
- pyglet/gl/wglext_nv.py +410 -188
- pyglet/gui/frame.py +4 -4
- pyglet/gui/widgets.py +6 -1
- pyglet/image/__init__.py +0 -2
- pyglet/image/codecs/bmp.py +3 -5
- pyglet/image/codecs/dds.py +1 -1
- pyglet/image/codecs/gdiplus.py +28 -9
- pyglet/image/codecs/wic.py +198 -489
- pyglet/image/codecs/wincodec_lib.py +413 -0
- pyglet/input/base.py +3 -2
- pyglet/input/linux/x11_xinput.py +3 -3
- pyglet/input/linux/x11_xinput_tablet.py +2 -2
- pyglet/input/macos/darwin_hid.py +28 -2
- pyglet/input/win32/directinput.py +3 -2
- pyglet/input/win32/wintab.py +1 -1
- pyglet/input/win32/xinput.py +10 -9
- pyglet/lib.py +14 -2
- pyglet/libs/darwin/cocoapy/cocoalibs.py +74 -3
- pyglet/libs/darwin/coreaudio.py +0 -2
- pyglet/libs/win32/__init__.py +4 -2
- pyglet/libs/win32/com.py +65 -12
- pyglet/libs/win32/constants.py +1 -0
- pyglet/libs/win32/dinput.py +1 -9
- pyglet/libs/win32/types.py +72 -8
- pyglet/math.py +5 -5
- pyglet/media/codecs/coreaudio.py +1 -0
- pyglet/media/codecs/wmf.py +93 -72
- pyglet/media/devices/win32.py +5 -4
- pyglet/media/drivers/directsound/lib_dsound.py +4 -4
- pyglet/media/drivers/xaudio2/interface.py +21 -17
- pyglet/media/drivers/xaudio2/lib_xaudio2.py +42 -25
- pyglet/model/__init__.py +78 -57
- pyglet/shapes.py +1 -1
- pyglet/text/document.py +7 -53
- pyglet/text/formats/attributed.py +3 -1
- pyglet/text/formats/plaintext.py +1 -1
- pyglet/text/formats/structured.py +1 -1
- pyglet/text/layout/base.py +76 -68
- pyglet/text/layout/incremental.py +38 -8
- pyglet/text/layout/scrolling.py +1 -1
- pyglet/text/runlist.py +2 -114
- pyglet/window/__init__.py +11 -8
- pyglet/window/win32/__init__.py +1 -3
- pyglet/window/xlib/__init__.py +2 -2
- {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/METADATA +2 -3
- {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/RECORD +73 -67
- pyglet/font/directwrite.py +0 -2798
- {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/LICENSE +0 -0
- {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/WHEEL +0 -0
pyglet/text/layout/base.py
CHANGED
|
@@ -33,6 +33,7 @@ from pyglet.gl import (
|
|
|
33
33
|
)
|
|
34
34
|
from pyglet.graphics import Group
|
|
35
35
|
from pyglet.text import runlist
|
|
36
|
+
from pyglet.font.base import GlyphPosition
|
|
36
37
|
|
|
37
38
|
if TYPE_CHECKING:
|
|
38
39
|
from pyglet.customtypes import AnchorX, AnchorY, ContentVAlign, HorizontalAlign
|
|
@@ -102,7 +103,7 @@ layout_fragment_source = """#version 330 core
|
|
|
102
103
|
|
|
103
104
|
void main()
|
|
104
105
|
{
|
|
105
|
-
final_colors =
|
|
106
|
+
final_colors = texture(text, texture_coords) * text_colors;
|
|
106
107
|
if (scissor == true) {
|
|
107
108
|
if (vert_position.x < scissor_area[0]) discard; // left
|
|
108
109
|
if (vert_position.y < scissor_area[1]) discard; // bottom
|
|
@@ -410,25 +411,26 @@ class _AbstractBox(ABC):
|
|
|
410
411
|
class _GlyphBox(_AbstractBox):
|
|
411
412
|
owner: Texture
|
|
412
413
|
font: Font
|
|
413
|
-
glyphs: list[tuple[int, Glyph]]
|
|
414
|
+
glyphs: list[tuple[int, Glyph, GlyphPosition]]
|
|
414
415
|
advance: int
|
|
415
416
|
vertex_lists: list[_LayoutVertexList]
|
|
416
417
|
|
|
417
|
-
def __init__(self, owner: Texture, font: Font, glyphs: list[tuple[int, Glyph]], advance: int) -> None:
|
|
418
|
+
def __init__(self, owner: Texture, font: Font, glyphs: list[tuple[int, Glyph, GlyphPosition]], advance: int) -> None:
|
|
418
419
|
"""Create a run of glyphs sharing the same texture.
|
|
419
420
|
|
|
420
|
-
:
|
|
421
|
-
|
|
421
|
+
Args:
|
|
422
|
+
owner:
|
|
422
423
|
Texture of all glyphs in this run.
|
|
423
|
-
|
|
424
|
+
font:
|
|
424
425
|
Font of all glyphs in this run.
|
|
425
|
-
|
|
426
|
+
glyphs:
|
|
426
427
|
Pairs of ``(kern, glyph)``, where ``kern`` gives horizontal
|
|
427
428
|
displacement of the glyph in pixels (typically 0).
|
|
428
|
-
|
|
429
|
+
advance:
|
|
429
430
|
Width of glyph run; must correspond to the sum of advances
|
|
430
431
|
and kerns in the glyph list.
|
|
431
|
-
|
|
432
|
+
offsets:
|
|
433
|
+
A list of all position transformations done to each glyph.
|
|
432
434
|
"""
|
|
433
435
|
super().__init__(font.ascent, font.descent, advance, len(glyphs))
|
|
434
436
|
assert owner
|
|
@@ -446,7 +448,7 @@ class _GlyphBox(_AbstractBox):
|
|
|
446
448
|
rotation: float, visible: bool, anchor_x: float, anchor_y: float, context: _LayoutContext) -> None:
|
|
447
449
|
# Creates the initial attributes and vertex lists of the glyphs.
|
|
448
450
|
# line_x/line_y are calculated when lines shift. To prevent having to destroy and recalculate the layout
|
|
449
|
-
# everytime
|
|
451
|
+
# everytime it moves, they are merged into the vertices. This way the translation can be moved directly.
|
|
450
452
|
assert self.glyphs
|
|
451
453
|
assert not self.vertex_lists
|
|
452
454
|
try:
|
|
@@ -463,17 +465,19 @@ class _GlyphBox(_AbstractBox):
|
|
|
463
465
|
for start, end, baseline_ in context.baseline_iter.ranges(i, i + n_glyphs):
|
|
464
466
|
baseline = layout._parse_distance(baseline_) # noqa: SLF001
|
|
465
467
|
assert len(self.glyphs[start - i:end - i]) == end - start
|
|
466
|
-
for kern, glyph in self.glyphs[start - i:end - i]:
|
|
468
|
+
for (kern, glyph, glyph_pos) in self.glyphs[start - i:end - i]:
|
|
467
469
|
x1 += kern
|
|
468
470
|
v0, v1, v2, v3 = glyph.vertices
|
|
469
|
-
v0 += x1
|
|
470
|
-
v2 += x1
|
|
471
|
-
v1 += line_y + baseline
|
|
472
|
-
v3 += line_y + baseline
|
|
471
|
+
v0 += x1 + glyph_pos.x_offset
|
|
472
|
+
v2 += x1 + glyph_pos.x_offset
|
|
473
|
+
v1 += line_y + baseline + glyph_pos.y_offset
|
|
474
|
+
v3 += line_y + baseline + glyph_pos.y_offset
|
|
473
475
|
vertices.extend(map(round, [v0, v1, 0, v2, v1, 0, v2, v3, 0, v0, v3, 0]))
|
|
474
476
|
t = glyph.tex_coords
|
|
475
477
|
tex_coords.extend(t)
|
|
476
|
-
x1 += glyph.advance
|
|
478
|
+
x1 += glyph.advance + glyph_pos.x_advance
|
|
479
|
+
v1 += glyph_pos.y_advance
|
|
480
|
+
v3 += glyph_pos.y_advance
|
|
477
481
|
|
|
478
482
|
# Text color
|
|
479
483
|
colors = []
|
|
@@ -519,8 +523,8 @@ class _GlyphBox(_AbstractBox):
|
|
|
519
523
|
for start, end, decoration in context.decoration_iter.ranges(i, i + n_glyphs):
|
|
520
524
|
bg, underline = decoration
|
|
521
525
|
x2 = x1
|
|
522
|
-
for kern, glyph in self.glyphs[start - i:end - i]:
|
|
523
|
-
x2 += glyph.advance + kern
|
|
526
|
+
for (kern, glyph, glyph_pos) in self.glyphs[start - i:end - i]:
|
|
527
|
+
x2 += glyph.advance + kern + glyph_pos.x_advance
|
|
524
528
|
|
|
525
529
|
if bg is not None:
|
|
526
530
|
if len(bg) != 4:
|
|
@@ -619,19 +623,19 @@ class _GlyphBox(_AbstractBox):
|
|
|
619
623
|
|
|
620
624
|
def get_point_in_box(self, position: int) -> int:
|
|
621
625
|
x = 0
|
|
622
|
-
for (kern, glyph) in self.glyphs:
|
|
626
|
+
for (kern, glyph, offset) in self.glyphs:
|
|
623
627
|
if position == 0:
|
|
624
628
|
break
|
|
625
629
|
position -= 1
|
|
626
|
-
x += glyph.advance + kern
|
|
630
|
+
x += glyph.advance + kern + offset.x_advance
|
|
627
631
|
return x
|
|
628
632
|
|
|
629
633
|
def get_position_in_box(self, x: float) -> int:
|
|
630
634
|
position = 0
|
|
631
635
|
last_glyph_x = 0
|
|
632
|
-
for kern, glyph in self.glyphs:
|
|
636
|
+
for (kern, glyph, offset) in self.glyphs:
|
|
633
637
|
last_glyph_x += kern
|
|
634
|
-
if last_glyph_x + glyph.advance // 2 > x:
|
|
638
|
+
if last_glyph_x + glyph.advance + offset.x_advance // 2 > x:
|
|
635
639
|
return position
|
|
636
640
|
position += 1
|
|
637
641
|
last_glyph_x += glyph.advance
|
|
@@ -813,6 +817,9 @@ class TextDecorationGroup(Group):
|
|
|
813
817
|
self.program.stop()
|
|
814
818
|
|
|
815
819
|
|
|
820
|
+
# Just have one object for empty positions in layout. It won't be modified.
|
|
821
|
+
_empty_pos = GlyphPosition(0, 0, 0, 0)
|
|
822
|
+
|
|
816
823
|
class TextLayout:
|
|
817
824
|
"""Lay out and display documents.
|
|
818
825
|
|
|
@@ -978,11 +985,6 @@ class TextLayout:
|
|
|
978
985
|
self.group_cache.clear()
|
|
979
986
|
self._update()
|
|
980
987
|
|
|
981
|
-
@property
|
|
982
|
-
def dpi(self) -> float:
|
|
983
|
-
"""Get DPI used by this layout."""
|
|
984
|
-
return self._dpi
|
|
985
|
-
|
|
986
988
|
@property
|
|
987
989
|
def document(self) -> AbstractDocument:
|
|
988
990
|
"""Document to display.
|
|
@@ -1370,17 +1372,12 @@ class TextLayout:
|
|
|
1370
1372
|
self._update()
|
|
1371
1373
|
|
|
1372
1374
|
@property
|
|
1373
|
-
def dpi(self):
|
|
1374
|
-
"""Get DPI used by this layout.
|
|
1375
|
-
|
|
1376
|
-
Read-only.
|
|
1377
|
-
|
|
1378
|
-
:type: float
|
|
1379
|
-
"""
|
|
1375
|
+
def dpi(self) -> float:
|
|
1376
|
+
"""Get DPI used by this layout."""
|
|
1380
1377
|
return self._dpi
|
|
1381
1378
|
|
|
1382
1379
|
@dpi.setter
|
|
1383
|
-
def dpi(self, value):
|
|
1380
|
+
def dpi(self, value: float) -> None:
|
|
1384
1381
|
self._dpi = value
|
|
1385
1382
|
self._update()
|
|
1386
1383
|
|
|
@@ -1438,9 +1435,10 @@ class TextLayout:
|
|
|
1438
1435
|
|
|
1439
1436
|
def _get_lines(self) -> list[_Line]:
|
|
1440
1437
|
len_text = len(self._document.text)
|
|
1441
|
-
glyphs = self._get_glyphs()
|
|
1442
|
-
owner_runs =
|
|
1443
|
-
|
|
1438
|
+
glyphs, offsets = self._get_glyphs()
|
|
1439
|
+
owner_runs = runlist.RunList(len_text, None)
|
|
1440
|
+
self._get_owner_runs(owner_runs, glyphs, 0, len_text)
|
|
1441
|
+
lines = list(self._flow_glyphs(glyphs, offsets, owner_runs, 0, len_text))
|
|
1444
1442
|
self._content_width = 0
|
|
1445
1443
|
self._line_count = len(lines)
|
|
1446
1444
|
self._flow_lines(lines, 0, self._line_count)
|
|
@@ -1620,8 +1618,9 @@ class TextLayout:
|
|
|
1620
1618
|
else:
|
|
1621
1619
|
self._init_document()
|
|
1622
1620
|
|
|
1623
|
-
def _get_glyphs(self) -> list[_InlineElementBox | Glyph]:
|
|
1621
|
+
def _get_glyphs(self) -> tuple[list[_InlineElementBox | Glyph], list[tuple[int, int]]]:
|
|
1624
1622
|
glyphs = []
|
|
1623
|
+
offsets = []
|
|
1625
1624
|
runs = runlist.ZipRunIterator((
|
|
1626
1625
|
self._document.get_font_runs(dpi=self._dpi),
|
|
1627
1626
|
self._document.get_element_runs()))
|
|
@@ -1629,28 +1628,33 @@ class TextLayout:
|
|
|
1629
1628
|
for start, end, (font, element) in runs.ranges(0, len(text)):
|
|
1630
1629
|
if element:
|
|
1631
1630
|
glyphs.append(_InlineElementBox(element))
|
|
1631
|
+
offsets.append(_empty_pos)
|
|
1632
1632
|
else:
|
|
1633
|
-
|
|
1634
|
-
|
|
1633
|
+
char_glyphs, char_offsets = font.get_glyphs(text[start:end])
|
|
1634
|
+
glyphs.extend(char_glyphs)
|
|
1635
|
+
offsets.extend(char_offsets)
|
|
1636
|
+
|
|
1637
|
+
return glyphs, offsets
|
|
1635
1638
|
|
|
1636
|
-
def _get_owner_runs(self, glyphs: list[_InlineElementBox | Glyph]
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1639
|
+
def _get_owner_runs(self, owner_runs: runlist.RunList, glyphs: list[_InlineElementBox | Glyph], start: int,
|
|
1640
|
+
end: int) -> None:
|
|
1641
|
+
owner = glyphs[start].owner
|
|
1642
|
+
run_start = start
|
|
1640
1643
|
|
|
1641
|
-
|
|
1644
|
+
# TODO avoid glyph slice on non-incremental
|
|
1645
|
+
for i, glyph in enumerate(glyphs[start:end]):
|
|
1642
1646
|
if owner != glyph.owner:
|
|
1643
|
-
owner_runs.
|
|
1647
|
+
owner_runs.set_run(run_start, i + start, owner)
|
|
1644
1648
|
owner = glyph.owner
|
|
1645
|
-
run_start = i
|
|
1646
|
-
owner_runs.
|
|
1647
|
-
return owner_runs
|
|
1649
|
+
run_start = i + start
|
|
1650
|
+
owner_runs.set_run(run_start, end, owner)
|
|
1648
1651
|
|
|
1649
|
-
def _flow_glyphs_wrap(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1652
|
+
def _flow_glyphs_wrap(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1653
|
+
offsets: list[GlyphPosition],
|
|
1654
|
+
owner_runs: runlist.RunList, start: int,
|
|
1650
1655
|
end: int) -> Iterator[_Line]:
|
|
1651
1656
|
# Word-wrap styled text into lines of fixed width.
|
|
1652
1657
|
# Fits glyphs in range start to end into Lines which are then yielded.
|
|
1653
|
-
|
|
1654
1658
|
owner_iterator = owner_runs.get_run_iterator().ranges(start, end)
|
|
1655
1659
|
|
|
1656
1660
|
font_iterator = self._document.get_font_runs(dpi=self._dpi)
|
|
@@ -1721,7 +1725,7 @@ class TextLayout:
|
|
|
1721
1725
|
# Iterate over glyphs in this owner run. `text` is the
|
|
1722
1726
|
# corresponding character data for the glyph, and is used to find
|
|
1723
1727
|
# whitespace and newlines.
|
|
1724
|
-
for (text, glyph) in zip(self.document.text[start:end], glyphs[start:end]):
|
|
1728
|
+
for (text, glyph, offset) in zip(self.document.text[start:end], glyphs[start:end], offsets[start:end]):
|
|
1725
1729
|
if nokern:
|
|
1726
1730
|
kern = 0
|
|
1727
1731
|
nokern = False
|
|
@@ -1747,15 +1751,15 @@ class TextLayout:
|
|
|
1747
1751
|
tab_stop = (((x + line.margin_left) // tab) + 1) * tab
|
|
1748
1752
|
kern = int(tab_stop - x - line.margin_left - glyph.advance)
|
|
1749
1753
|
|
|
1750
|
-
owner_accum.append((kern, glyph))
|
|
1754
|
+
owner_accum.append((kern, glyph, offset))
|
|
1751
1755
|
owner_accum_commit.extend(owner_accum)
|
|
1752
|
-
owner_accum_commit_width += owner_accum_width + glyph.advance + kern
|
|
1753
|
-
eol_ws += glyph.advance + kern
|
|
1756
|
+
owner_accum_commit_width += owner_accum_width + glyph.advance + kern + offset.x_advance
|
|
1757
|
+
eol_ws += glyph.advance + kern + offset.x_advance
|
|
1754
1758
|
|
|
1755
1759
|
owner_accum = []
|
|
1756
1760
|
owner_accum_width = 0
|
|
1757
1761
|
|
|
1758
|
-
x += glyph.advance + kern
|
|
1762
|
+
x += glyph.advance + kern + offset.x_advance
|
|
1759
1763
|
index += 1
|
|
1760
1764
|
|
|
1761
1765
|
# The index at which the next line will begin (the
|
|
@@ -1765,7 +1769,7 @@ class TextLayout:
|
|
|
1765
1769
|
else:
|
|
1766
1770
|
new_paragraph = text in "\n\u2029"
|
|
1767
1771
|
new_line = (text == "\u2028") or new_paragraph
|
|
1768
|
-
if (wrap and self._wrap_lines and x + kern + glyph.advance >= width) or new_line:
|
|
1772
|
+
if (wrap and self._wrap_lines and x + kern + glyph.advance + offset.x_advance >= width) or new_line:
|
|
1769
1773
|
# Either the pending runs have overflowed the allowed
|
|
1770
1774
|
# line width or a newline was encountered. Either
|
|
1771
1775
|
# way, the current line must be flushed.
|
|
@@ -1827,11 +1831,11 @@ class TextLayout:
|
|
|
1827
1831
|
# Remove kern from first glyph of line
|
|
1828
1832
|
if run_accum and hasattr(run_accum, "glyphs") and run_accum.glyphs:
|
|
1829
1833
|
k, g = run_accum[0].glyphs[0]
|
|
1830
|
-
run_accum[0].glyphs[0] = (0, g)
|
|
1834
|
+
run_accum[0].glyphs[0] = (0, g, _empty_pos)
|
|
1831
1835
|
run_accum_width -= k
|
|
1832
1836
|
elif owner_accum:
|
|
1833
|
-
k, g = owner_accum[0]
|
|
1834
|
-
owner_accum[0] = (0, g)
|
|
1837
|
+
k, g, _ = owner_accum[0]
|
|
1838
|
+
owner_accum[0] = (0, g, _empty_pos)
|
|
1835
1839
|
owner_accum_width -= k
|
|
1836
1840
|
else:
|
|
1837
1841
|
nokern = True
|
|
@@ -1843,8 +1847,8 @@ class TextLayout:
|
|
|
1843
1847
|
if isinstance(glyph, _AbstractBox):
|
|
1844
1848
|
# Glyph is already in a box. XXX Ignore kern?
|
|
1845
1849
|
run_accum.append(glyph)
|
|
1846
|
-
run_accum_width += glyph.advance
|
|
1847
|
-
x += glyph.advance
|
|
1850
|
+
run_accum_width += glyph.advance + offset.x_advance
|
|
1851
|
+
x += glyph.advance + offset.x_advance
|
|
1848
1852
|
elif new_paragraph:
|
|
1849
1853
|
# New paragraph started, update wrap style
|
|
1850
1854
|
wrap = wrap_iterator[next_start]
|
|
@@ -1854,9 +1858,9 @@ class TextLayout:
|
|
|
1854
1858
|
elif not new_line:
|
|
1855
1859
|
# If the glyph was any non-whitespace, non-newline
|
|
1856
1860
|
# character, add it to the pending run.
|
|
1857
|
-
owner_accum.append((kern, glyph))
|
|
1858
|
-
owner_accum_width += glyph.advance + kern
|
|
1859
|
-
x += glyph.advance + kern
|
|
1861
|
+
owner_accum.append((kern, glyph, offset))
|
|
1862
|
+
owner_accum_width += glyph.advance + kern + offset.x_advance
|
|
1863
|
+
x += glyph.advance + kern + offset.x_advance
|
|
1860
1864
|
index += 1
|
|
1861
1865
|
eol_ws = 0
|
|
1862
1866
|
|
|
@@ -1882,7 +1886,9 @@ class TextLayout:
|
|
|
1882
1886
|
|
|
1883
1887
|
yield line
|
|
1884
1888
|
|
|
1885
|
-
def _flow_glyphs_single_line(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1889
|
+
def _flow_glyphs_single_line(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1890
|
+
offsets: list[GlyphPosition],
|
|
1891
|
+
owner_runs: runlist.RunList,
|
|
1886
1892
|
start: int, end: int) -> Iterator[_Line]:
|
|
1887
1893
|
owner_iterator = owner_runs.get_run_iterator().ranges(start, end)
|
|
1888
1894
|
font_iterator = self.document.get_font_runs(dpi=self._dpi)
|
|
@@ -1905,9 +1911,11 @@ class TextLayout:
|
|
|
1905
1911
|
owner_glyphs = []
|
|
1906
1912
|
for kern_start, kern_end, kern in kern_iterator.ranges(start, end):
|
|
1907
1913
|
gs = glyphs[kern_start:kern_end]
|
|
1914
|
+
os = offsets[kern_start:kern_end]
|
|
1908
1915
|
width += sum([g.advance for g in gs])
|
|
1909
1916
|
width += kern * (kern_end - kern_start)
|
|
1910
|
-
|
|
1917
|
+
width += sum([o.x_advance for o in os])
|
|
1918
|
+
owner_glyphs.extend(zip([kern] * (kern_end - kern_start), gs, os))
|
|
1911
1919
|
if owner is None:
|
|
1912
1920
|
# Assume glyphs are already boxes.
|
|
1913
1921
|
for kern, glyph in owner_glyphs:
|
|
@@ -3,8 +3,10 @@ from __future__ import annotations
|
|
|
3
3
|
import sys
|
|
4
4
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
5
|
|
|
6
|
+
import unicodedata
|
|
7
|
+
|
|
6
8
|
from pyglet.event import EventDispatcher
|
|
7
|
-
from pyglet.font.base import grapheme_break
|
|
9
|
+
from pyglet.font.base import grapheme_break, GlyphPosition
|
|
8
10
|
from pyglet.text import runlist
|
|
9
11
|
from pyglet.text.layout.base import (
|
|
10
12
|
TextLayout,
|
|
@@ -118,6 +120,9 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
118
120
|
#: :meta private:
|
|
119
121
|
self.glyphs = []
|
|
120
122
|
|
|
123
|
+
#: :meta private:
|
|
124
|
+
self.offsets = []
|
|
125
|
+
|
|
121
126
|
#: :meta private:
|
|
122
127
|
# All lines in the document, including those hidden from view.
|
|
123
128
|
self.lines = []
|
|
@@ -163,6 +168,7 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
163
168
|
def on_insert_text(self, start: int, text: str) -> None:
|
|
164
169
|
len_text = len(text)
|
|
165
170
|
self.glyphs[start:start] = [None] * len_text
|
|
171
|
+
self.offsets[start:start] = [GlyphPosition(0, 0, 0, 0)] * len_text
|
|
166
172
|
|
|
167
173
|
self._invalid_glyphs.insert(start, len_text)
|
|
168
174
|
|
|
@@ -186,6 +192,7 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
186
192
|
|
|
187
193
|
def on_delete_text(self, start: int, end: int) -> None:
|
|
188
194
|
self.glyphs[start:end] = []
|
|
195
|
+
self.offsets[start:end] = []
|
|
189
196
|
|
|
190
197
|
# Same requirement as on_insert_text
|
|
191
198
|
if self._multiline and self._content_valign != "top":
|
|
@@ -240,6 +247,7 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
240
247
|
self.lines[0].descent = font.descent
|
|
241
248
|
self.lines[0].paragraph_begin = self.lines[0].paragraph_end = True
|
|
242
249
|
self._invalid_lines.invalidate(0, 1)
|
|
250
|
+
self.offsets.clear()
|
|
243
251
|
|
|
244
252
|
self._update_glyphs()
|
|
245
253
|
self._update_flow_glyphs()
|
|
@@ -265,16 +273,25 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
265
273
|
if invalid_end - invalid_start <= 0:
|
|
266
274
|
return
|
|
267
275
|
|
|
276
|
+
|
|
268
277
|
# Find grapheme breaks and extend glyph range to encompass.
|
|
269
278
|
text = self.document.text
|
|
270
279
|
while invalid_start > 0:
|
|
271
|
-
|
|
280
|
+
left = text[invalid_start - 1]
|
|
281
|
+
right = text[invalid_start]
|
|
282
|
+
right_cc = unicodedata.category(right)
|
|
283
|
+
left_cc = unicodedata.category(left)
|
|
284
|
+
if grapheme_break(left, left_cc, right, right_cc):
|
|
272
285
|
break
|
|
273
286
|
invalid_start -= 1
|
|
274
287
|
|
|
275
288
|
len_text = len(text)
|
|
276
289
|
while invalid_end < len_text:
|
|
277
|
-
|
|
290
|
+
left = text[invalid_end - 1]
|
|
291
|
+
right = text[invalid_end]
|
|
292
|
+
right_cc = unicodedata.category(right)
|
|
293
|
+
left_cc = unicodedata.category(left)
|
|
294
|
+
if grapheme_break(left, left_cc, right, right_cc):
|
|
278
295
|
break
|
|
279
296
|
invalid_end += 1
|
|
280
297
|
|
|
@@ -285,13 +302,15 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
285
302
|
for start, end, (font, element) in runs.ranges(invalid_start, invalid_end):
|
|
286
303
|
if element:
|
|
287
304
|
self.glyphs[start] = _InlineElementBox(element)
|
|
305
|
+
self.offsets[start] = GlyphPosition(0, 0, 0, 0)
|
|
288
306
|
else:
|
|
289
307
|
text = self.document.text[start:end]
|
|
290
|
-
|
|
308
|
+
glyphs, offsets = font.get_glyphs(text)
|
|
309
|
+
self.glyphs[start:end] = glyphs
|
|
310
|
+
self.offsets[start:end] = offsets
|
|
291
311
|
|
|
292
312
|
# Update owner runs
|
|
293
|
-
|
|
294
|
-
self._owner_runs.set_runs(invalid_start, invalid_end, owner_runs)
|
|
313
|
+
self._get_owner_runs(self._owner_runs, self.glyphs, invalid_start, invalid_end)
|
|
295
314
|
|
|
296
315
|
# Updated glyphs need flowing
|
|
297
316
|
self._invalid_flow.invalidate(invalid_start, invalid_end)
|
|
@@ -334,7 +353,7 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
334
353
|
content_width_invalid = False
|
|
335
354
|
next_start = invalid_start
|
|
336
355
|
|
|
337
|
-
for line in self._flow_glyphs(self.glyphs, self._owner_runs, invalid_start, len(self._document.text)):
|
|
356
|
+
for line in self._flow_glyphs(self.glyphs, self.offsets, self._owner_runs, invalid_start, len(self._document.text)):
|
|
338
357
|
try:
|
|
339
358
|
old_line = self.lines[line_index]
|
|
340
359
|
old_line.delete(self)
|
|
@@ -444,6 +463,7 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
444
463
|
self._anchor_left = self._get_left_anchor()
|
|
445
464
|
self._anchor_bottom = self._get_bottom_anchor()
|
|
446
465
|
top_anchor = self._get_top_anchor()
|
|
466
|
+
group_ct = len(self.group_cache)
|
|
447
467
|
|
|
448
468
|
for line in lines:
|
|
449
469
|
line.delete(self)
|
|
@@ -462,6 +482,10 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
462
482
|
if update_view_translation:
|
|
463
483
|
self._update_view_translation()
|
|
464
484
|
|
|
485
|
+
# Update groups with scissor areas if any groups were changed.
|
|
486
|
+
if group_ct != len(self.group_cache):
|
|
487
|
+
self._update_scissor_area()
|
|
488
|
+
|
|
465
489
|
@property
|
|
466
490
|
def x(self) -> float:
|
|
467
491
|
return self._x
|
|
@@ -478,6 +502,8 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
478
502
|
@y.setter
|
|
479
503
|
def y(self, y: float) -> None:
|
|
480
504
|
super()._set_y(y)
|
|
505
|
+
# Invalidate vertices, as the vertices will have changed.
|
|
506
|
+
self._invalid_vertex_lines.invalidate(0, len(self.document.text))
|
|
481
507
|
self._update_scissor_area()
|
|
482
508
|
|
|
483
509
|
@property
|
|
@@ -495,7 +521,11 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
495
521
|
|
|
496
522
|
@position.setter
|
|
497
523
|
def position(self, position: tuple[float, float, float]) -> None:
|
|
524
|
+
old_y = self._y
|
|
498
525
|
super()._set_position(position)
|
|
526
|
+
# Invalidate the lines if the Y changed.
|
|
527
|
+
if self._y != old_y:
|
|
528
|
+
self._invalid_vertex_lines.invalidate(0, len(self.document.text))
|
|
499
529
|
self._update_view_translation()
|
|
500
530
|
self._update_scissor_area()
|
|
501
531
|
|
|
@@ -544,8 +574,8 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
|
|
544
574
|
if height == self._height:
|
|
545
575
|
return
|
|
546
576
|
self._height = height
|
|
577
|
+
self._update_visible_lines()
|
|
547
578
|
if self._update_enabled:
|
|
548
|
-
self._update_visible_lines()
|
|
549
579
|
self._update_vertex_lists()
|
|
550
580
|
|
|
551
581
|
@property
|
pyglet/text/layout/scrolling.py
CHANGED
|
@@ -123,7 +123,7 @@ class ScrollableTextLayout(TextLayout):
|
|
|
123
123
|
_translate_x: int = 0
|
|
124
124
|
_translate_y: int = 0
|
|
125
125
|
|
|
126
|
-
def __init__(self, document: AbstractDocument,
|
|
126
|
+
def __init__(self, document: AbstractDocument, # noqa: D107
|
|
127
127
|
x: float = 0, y: float = 0, z: float = 0,
|
|
128
128
|
width: int = None, height: int = None,
|
|
129
129
|
anchor_x: AnchorX = 'left', anchor_y: AnchorY = 'bottom', rotation: float = 0, multiline: bool = False,
|
pyglet/text/runlist.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any, Callable, Generator, Iterable, Iterator
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class _Run:
|
|
8
|
+
__slots__ = ('count', 'value')
|
|
8
9
|
def __init__(self, value: Any, count: int) -> None:
|
|
9
10
|
self.value = value
|
|
10
11
|
self.count = count
|
|
@@ -33,6 +34,7 @@ class RunList:
|
|
|
33
34
|
``set_run(2, 5, 'x')`` would change the sequence to ``aaxxxbccccc``.
|
|
34
35
|
"""
|
|
35
36
|
runs: list[_Run]
|
|
37
|
+
__slots__ = ['runs']
|
|
36
38
|
|
|
37
39
|
def __init__(self, size: int, initial: Any) -> None:
|
|
38
40
|
"""Create a run list of the given size and a default value.
|
|
@@ -64,7 +66,6 @@ class RunList:
|
|
|
64
66
|
for run in self.runs:
|
|
65
67
|
if i <= pos <= i + run.count:
|
|
66
68
|
run.count += length
|
|
67
|
-
break
|
|
68
69
|
i += run.count
|
|
69
70
|
|
|
70
71
|
def delete(self, start: int, end: int) -> None:
|
|
@@ -156,60 +157,6 @@ class RunList:
|
|
|
156
157
|
# Delete collapsed runs
|
|
157
158
|
self.runs = [r for r in self.runs if r.count > 0]
|
|
158
159
|
|
|
159
|
-
def append(self, length: int):
|
|
160
|
-
"""Append characters into end of the run list.
|
|
161
|
-
|
|
162
|
-
The appended characters will take on the value immediately preceding
|
|
163
|
-
the end of run list.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
length:
|
|
167
|
-
Number of characters to insert.
|
|
168
|
-
|
|
169
|
-
"""
|
|
170
|
-
self.runs[-1].count += length
|
|
171
|
-
|
|
172
|
-
def append_run(self, count: int, value: Any) -> None:
|
|
173
|
-
"""
|
|
174
|
-
Append a run to the end of the run list.
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
count:
|
|
178
|
-
Number of characters to append.
|
|
179
|
-
value:
|
|
180
|
-
Value to append.
|
|
181
|
-
"""
|
|
182
|
-
if self.runs[-1].value == value:
|
|
183
|
-
self.runs[-1].count += count
|
|
184
|
-
return
|
|
185
|
-
self.runs.append(_Run(value, count))
|
|
186
|
-
|
|
187
|
-
def insert_run(self, pos: int, length: int, value: Any) -> None:
|
|
188
|
-
"""
|
|
189
|
-
Insert a run into the run list.
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
pos:
|
|
193
|
-
Position to insert run.
|
|
194
|
-
length:
|
|
195
|
-
Number of characters to insert.
|
|
196
|
-
value:
|
|
197
|
-
Value to insert.
|
|
198
|
-
"""
|
|
199
|
-
i = 0
|
|
200
|
-
for run_i, run in enumerate(self.runs):
|
|
201
|
-
if i <= pos <= i + run.count:
|
|
202
|
-
if run.value == value:
|
|
203
|
-
run.count += length
|
|
204
|
-
else:
|
|
205
|
-
self.runs.insert(run_i + 1, _Run(value, length))
|
|
206
|
-
self.runs.insert(run_i + 2, _Run(run.value, run.count - (pos - i)))
|
|
207
|
-
run.count = pos - i
|
|
208
|
-
# Delete collapsed runs
|
|
209
|
-
self.runs[run_i: run_i+2] = [r for r in self.runs[run_i: run_i+2] if r.count > 0]
|
|
210
|
-
break
|
|
211
|
-
i += run.count
|
|
212
|
-
|
|
213
160
|
def __iter__(self) -> Generator[tuple[int, int, Any], Any, None]:
|
|
214
161
|
i = 0
|
|
215
162
|
for run in self.runs:
|
|
@@ -240,65 +187,6 @@ class RunList:
|
|
|
240
187
|
|
|
241
188
|
raise IndexError
|
|
242
189
|
|
|
243
|
-
def set_runs(self, start: int, end: int, runs: RunList) -> None:
|
|
244
|
-
"""Set a range of runs.
|
|
245
|
-
|
|
246
|
-
Args:
|
|
247
|
-
start:
|
|
248
|
-
Start index of range.
|
|
249
|
-
end:
|
|
250
|
-
End index of range, exclusive.
|
|
251
|
-
runs:
|
|
252
|
-
Runs to set.
|
|
253
|
-
"""
|
|
254
|
-
if end - start <= 0:
|
|
255
|
-
return
|
|
256
|
-
|
|
257
|
-
# Find runs that need to be split
|
|
258
|
-
i = 0
|
|
259
|
-
start_i = None
|
|
260
|
-
start_trim = 0
|
|
261
|
-
end_i = None
|
|
262
|
-
end_trim = 0
|
|
263
|
-
for run_i, run in enumerate(self.runs):
|
|
264
|
-
count = run.count
|
|
265
|
-
if i < start < i + count:
|
|
266
|
-
start_i = run_i
|
|
267
|
-
start_trim = start - i
|
|
268
|
-
if i < end < i + count:
|
|
269
|
-
end_i = run_i
|
|
270
|
-
end_trim = end - i
|
|
271
|
-
i += count
|
|
272
|
-
|
|
273
|
-
# Split runs
|
|
274
|
-
if start_i is not None:
|
|
275
|
-
run = self.runs[start_i]
|
|
276
|
-
self.runs.insert(start_i, _Run(run.value, start_trim))
|
|
277
|
-
run.count -= start_trim
|
|
278
|
-
if end_i is not None:
|
|
279
|
-
if end_i == start_i:
|
|
280
|
-
end_trim -= start_trim
|
|
281
|
-
end_i += 1
|
|
282
|
-
start_i += 1
|
|
283
|
-
if end_i is not None:
|
|
284
|
-
run = self.runs[end_i]
|
|
285
|
-
self.runs.insert(end_i, _Run(run.value, end_trim))
|
|
286
|
-
run.count -= end_trim
|
|
287
|
-
end_i += 1
|
|
288
|
-
|
|
289
|
-
self.runs[start_i: end_i] = runs.runs
|
|
290
|
-
|
|
291
|
-
# Merge adjacent runs
|
|
292
|
-
last_run = self.runs[0]
|
|
293
|
-
for run in self.runs[1:]:
|
|
294
|
-
if run.value == last_run.value:
|
|
295
|
-
run.count += last_run.count
|
|
296
|
-
last_run.count = 0
|
|
297
|
-
last_run = run
|
|
298
|
-
|
|
299
|
-
# Delete collapsed runs
|
|
300
|
-
self.runs = [r for r in self.runs if r.count > 0]
|
|
301
|
-
|
|
302
190
|
def __repr__(self) -> str:
|
|
303
191
|
return str(list(self))
|
|
304
192
|
|