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.
Files changed (74) hide show
  1. pyglet/__init__.py +21 -9
  2. pyglet/__init__.pyi +3 -1
  3. pyglet/app/cocoa.py +6 -3
  4. pyglet/app/xlib.py +1 -1
  5. pyglet/display/cocoa.py +2 -2
  6. pyglet/display/win32.py +17 -18
  7. pyglet/display/xlib.py +2 -2
  8. pyglet/display/xlib_vidmoderestore.py +1 -1
  9. pyglet/extlibs/earcut.py +2 -2
  10. pyglet/font/__init__.py +3 -3
  11. pyglet/font/base.py +118 -51
  12. pyglet/font/dwrite/__init__.py +1381 -0
  13. pyglet/font/dwrite/d2d1_lib.py +637 -0
  14. pyglet/font/dwrite/d2d1_types_lib.py +60 -0
  15. pyglet/font/dwrite/dwrite_lib.py +1577 -0
  16. pyglet/font/fontconfig.py +79 -16
  17. pyglet/font/freetype.py +252 -77
  18. pyglet/font/freetype_lib.py +234 -125
  19. pyglet/font/harfbuzz/__init__.py +275 -0
  20. pyglet/font/harfbuzz/harfbuzz_lib.py +212 -0
  21. pyglet/font/quartz.py +432 -112
  22. pyglet/font/user.py +18 -11
  23. pyglet/font/win32.py +9 -1
  24. pyglet/gl/wgl.py +94 -87
  25. pyglet/gl/wglext_arb.py +472 -218
  26. pyglet/gl/wglext_nv.py +410 -188
  27. pyglet/gui/frame.py +4 -4
  28. pyglet/gui/widgets.py +6 -1
  29. pyglet/image/__init__.py +0 -2
  30. pyglet/image/codecs/bmp.py +3 -5
  31. pyglet/image/codecs/dds.py +1 -1
  32. pyglet/image/codecs/gdiplus.py +28 -9
  33. pyglet/image/codecs/wic.py +198 -489
  34. pyglet/image/codecs/wincodec_lib.py +413 -0
  35. pyglet/input/base.py +3 -2
  36. pyglet/input/linux/x11_xinput.py +3 -3
  37. pyglet/input/linux/x11_xinput_tablet.py +2 -2
  38. pyglet/input/macos/darwin_hid.py +28 -2
  39. pyglet/input/win32/directinput.py +3 -2
  40. pyglet/input/win32/wintab.py +1 -1
  41. pyglet/input/win32/xinput.py +10 -9
  42. pyglet/lib.py +14 -2
  43. pyglet/libs/darwin/cocoapy/cocoalibs.py +74 -3
  44. pyglet/libs/darwin/coreaudio.py +0 -2
  45. pyglet/libs/win32/__init__.py +4 -2
  46. pyglet/libs/win32/com.py +65 -12
  47. pyglet/libs/win32/constants.py +1 -0
  48. pyglet/libs/win32/dinput.py +1 -9
  49. pyglet/libs/win32/types.py +72 -8
  50. pyglet/math.py +5 -5
  51. pyglet/media/codecs/coreaudio.py +1 -0
  52. pyglet/media/codecs/wmf.py +93 -72
  53. pyglet/media/devices/win32.py +5 -4
  54. pyglet/media/drivers/directsound/lib_dsound.py +4 -4
  55. pyglet/media/drivers/xaudio2/interface.py +21 -17
  56. pyglet/media/drivers/xaudio2/lib_xaudio2.py +42 -25
  57. pyglet/model/__init__.py +78 -57
  58. pyglet/shapes.py +1 -1
  59. pyglet/text/document.py +7 -53
  60. pyglet/text/formats/attributed.py +3 -1
  61. pyglet/text/formats/plaintext.py +1 -1
  62. pyglet/text/formats/structured.py +1 -1
  63. pyglet/text/layout/base.py +76 -68
  64. pyglet/text/layout/incremental.py +38 -8
  65. pyglet/text/layout/scrolling.py +1 -1
  66. pyglet/text/runlist.py +2 -114
  67. pyglet/window/__init__.py +11 -8
  68. pyglet/window/win32/__init__.py +1 -3
  69. pyglet/window/xlib/__init__.py +2 -2
  70. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/METADATA +2 -3
  71. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/RECORD +73 -67
  72. pyglet/font/directwrite.py +0 -2798
  73. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/LICENSE +0 -0
  74. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/WHEEL +0 -0
@@ -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 = vec4(text_colors.rgb, texture(text, texture_coords).a * text_colors.a);
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
- :Parameters:
421
- `owner` : `pyglet.image.Texture`
421
+ Args:
422
+ owner:
422
423
  Texture of all glyphs in this run.
423
- `font` : `pyglet.font.base.Font`
424
+ font:
424
425
  Font of all glyphs in this run.
425
- `glyphs` : list of (int, `pyglet.font.base.Glyph`)
426
+ glyphs:
426
427
  Pairs of ``(kern, glyph)``, where ``kern`` gives horizontal
427
428
  displacement of the glyph in pixels (typically 0).
428
- `advance` : int
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 we move this layout, we bake those into the vertices. This way the translate can be moved directly.
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 = self._get_owner_runs(glyphs)
1443
- lines = [line for line in self._flow_glyphs(glyphs, owner_runs, 0, len_text)]
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
- glyphs.extend(font.get_glyphs(text[start:end]))
1634
- return glyphs
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]) -> runlist.RunList:
1637
- owner = glyphs[0].owner
1638
- run_start = 0
1639
- owner_runs = runlist.RunList(0, owner)
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
- for i, glyph in enumerate(glyphs):
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.append_run(i-run_start, owner)
1647
+ owner_runs.set_run(run_start, i + start, owner)
1644
1648
  owner = glyph.owner
1645
- run_start = i
1646
- owner_runs.append_run(len(glyphs)-run_start, owner)
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], owner_runs: runlist.RunList, start: int,
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], owner_runs: runlist.RunList,
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
- owner_glyphs.extend(zip([kern] * (kern_end - kern_start), gs))
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
- if grapheme_break(text[invalid_start - 1], text[invalid_start]):
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
- if grapheme_break(text[invalid_end - 1], text[invalid_end]):
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
- self.glyphs[start:end] = font.get_glyphs(text)
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
- owner_runs = self._get_owner_runs(self.glyphs[invalid_start:invalid_end])
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
@@ -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