Skip to content

Commit 98fae69

Browse files
authored
Bug fixes for Attributes + Setters for Rectangle + Acceptance tests (#42)
1 parent 2d6e561 commit 98fae69

16 files changed

+438
-162
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ __pycache__
88
# Build artifacts specific to pangocffi
99
_generated
1010

11+
# Test outputs
12+
/tests/acceptance/output
13+
1114
# Virtualenv
1215
venv
1316

pangocffi/attribute.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ class Attribute:
1515

1616
def __init__(self):
1717
self._pointer = None
18-
self._start_index = None
19-
self._end_index = None
2018

2119
@classmethod
2220
def _init_pointer(cls, pointer: ffi.CData) -> "Attribute":
2321
self = object.__new__(cls)
24-
self._pointer = ffi.gc(pointer, pango.pango_attribute_destroy)
22+
self._pointer = pointer
2523
return self
2624

2725
@classmethod
@@ -59,7 +57,7 @@ def __eq__(self, other: "Attribute") -> bool:
5957

6058
@property
6159
def start_index(self):
62-
return self._start_index
60+
return self._pointer.start_index
6361

6462
@start_index.setter
6563
def start_index(self, start_index: int):
@@ -76,13 +74,12 @@ def start_index(self, start_index: int):
7674
"start_index is too low"
7775
assert start_index < pango.PANGO_ATTR_INDEX_TO_TEXT_END, \
7876
"start_index is too high"
79-
self._start_index = start_index
8077
start = ffi.cast("guint", start_index)
8178
self._pointer.start_index = start
8279

8380
@property
8481
def end_index(self):
85-
return self._end_index
82+
return self._pointer.end_index
8683

8784
@end_index.setter
8885
def end_index(self, end_index: int):
@@ -98,9 +95,8 @@ def end_index(self, end_index: int):
9895
assert isinstance(end_index, int), "end_index isn't a int"
9996
assert end_index <= pango.PANGO_ATTR_INDEX_TO_TEXT_END, \
10097
"end_index is too high"
101-
self._end_index = end_index
10298
end = ffi.cast("guint", end_index)
103-
self._pointer.start_index = end
99+
self._pointer.end_index = end
104100

105101
# @classmethod
106102
# def from_language(

pangocffi/rectangle.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@ class Rectangle:
1010
"""
1111
def __init__(
1212
self,
13-
pointer: Optional[ffi.CData] = None
13+
pointer: Optional[ffi.CData] = None,
14+
width: Optional[int] = None,
15+
height: Optional[int] = None,
16+
x: Optional[int] = None,
17+
y: Optional[int] = None
1418
):
1519
if pointer is None:
1620
self.pointer = ffi.new("PangoRectangle *")
1721
else:
1822
self.pointer = pointer
1923

24+
if width is not None:
25+
self.pointer.width = width
26+
if height is not None:
27+
self.pointer.height = height
28+
if x is not None:
29+
self.pointer.x = x
30+
if y is not None:
31+
self.pointer.y = y
32+
2033
def get_pointer(self) -> ffi.CData:
2134
"""
2235
Returns a pointer to the :class:`Rectangle`.
@@ -47,6 +60,14 @@ def x(self) -> int:
4760
"""
4861
return self.pointer.x
4962

63+
@x.setter
64+
def x(self, value: int):
65+
"""
66+
:param value:
67+
Sets the X coordinate of the left side of the rectangle.
68+
"""
69+
self.pointer.x = value
70+
5071
@property
5172
def y(self) -> int:
5273
"""
@@ -56,6 +77,14 @@ def y(self) -> int:
5677
"""
5778
return self.pointer.y
5879

80+
@y.setter
81+
def y(self, value: int):
82+
"""
83+
:param value:
84+
Sets the Y coordinate of the the top side of the rectangle.
85+
"""
86+
self.pointer.y = value
87+
5988
@property
6089
def width(self) -> int:
6190
"""
@@ -65,6 +94,14 @@ def width(self) -> int:
6594
"""
6695
return self.pointer.width
6796

97+
@width.setter
98+
def width(self, value: int):
99+
"""
100+
:param value:
101+
Sets the width of the rectangle.
102+
"""
103+
self.pointer.width = value
104+
68105
@property
69106
def height(self) -> int:
70107
"""
@@ -73,3 +110,11 @@ def height(self) -> int:
73110
:type: int
74111
"""
75112
return self.pointer.height
113+
114+
@height.setter
115+
def height(self, value: int):
116+
"""
117+
:param value:
118+
Sets the height of the rectangle.
119+
"""
120+
self.pointer.height = value

tests/acceptance/__init__.py

Whitespace-only changes.

tests/acceptance/output/.gitkeep

Whitespace-only changes.
33.3 KB
Binary file not shown.

tests/acceptance/test_attributes.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import pangocffi
2+
from pangocffi import Layout, AttrList, Attribute, Underline, Rectangle, Style
3+
from tests.context_creator import ContextCreator
4+
5+
6+
def test_attributes():
7+
width = 150
8+
height = 50
9+
# CFFIs and Contexts
10+
context_creator = ContextCreator.create_pdf(
11+
'tests/acceptance/output/attributes.pdf', width, height
12+
)
13+
pangocairo = context_creator.pangocairo
14+
cairo_context = context_creator.cairo_context
15+
16+
text = 'Hi from Παν語! This test is to make sure that text is correctly ' \
17+
'annotated, and to also highlight functionality that is not ' \
18+
'available from methods like pango.set_markup(\'...\'). ' \
19+
'For instance, Attribute.from_shape() allows you to embed a ' \
20+
'picture or a widget in a layout. Example: Ø.'
21+
text_utf8 = text.encode('utf-8')
22+
23+
# Using pangocairocffi, this would be `pangocairocffi.create_layout()` with
24+
# a cairocffi context object.
25+
layout = Layout.from_pointer(
26+
pangocairo.pango_cairo_create_layout(cairo_context)
27+
)
28+
29+
layout.set_width(pangocffi.units_from_double(width * 72 / 25.4))
30+
layout.set_text(text)
31+
32+
attr_list = AttrList()
33+
34+
# Example: Handling byte indexes correctly.
35+
# When handling byte indexes, make sure to always use UTF-8 byte strings.
36+
# For example , while the string '語' has a length of 1 character in
37+
# python, in a UTF-8 byte string this is 3 bytes: `0xe8 0xaa 0x9e`. Pango
38+
# will not like byte indexes that occur in the middle of a character.
39+
text_to_match = 'Παν語!'.encode('utf-8')
40+
match_index = text_utf8.index(text_to_match)
41+
match_index_end = match_index + len(text_to_match)
42+
attr_list.insert(Attribute.from_style(
43+
Style.ITALIC,
44+
match_index,
45+
match_index_end
46+
))
47+
48+
# Example: Color alpha
49+
# `set_markup()` doesn't appear to have the ability to control color
50+
# alphas, but it is supported by Attributes! Note that the alpha color for
51+
# underline and foreground text cannot be controlled separately.
52+
text_to_match = 'correctly'.encode('utf-8')
53+
match_index = text_utf8.index(text_to_match)
54+
match_index_end = match_index + len(text_to_match)
55+
attr_list.insert(Attribute.from_underline(
56+
Underline.ERROR, match_index, match_index_end
57+
))
58+
attr_list.insert(Attribute.from_foreground_color(
59+
65535, 0, 0, match_index, match_index_end
60+
))
61+
attr_list.insert(Attribute.from_underline_color(
62+
0, 0, 65535, match_index, match_index_end
63+
))
64+
attr_list.insert(Attribute.from_background_color(
65+
0, 65535, 0, match_index, match_index_end
66+
))
67+
attr_list.insert(Attribute.from_foreground_alpha(
68+
32768, match_index, match_index_end
69+
))
70+
attr_list.insert(Attribute.from_background_alpha(
71+
8192, match_index, match_index_end
72+
))
73+
74+
# Example: Highlight multiple Monospace texts
75+
markup_texts = ('pango.set_markup(\'...\')', 'Attribute.from_shape()')
76+
for markup_text in markup_texts:
77+
text_to_match = markup_text.encode('utf-8')
78+
match_index = text_utf8.index(text_to_match)
79+
match_index_end = match_index + len(text_to_match)
80+
attr_list.insert(Attribute.from_family(
81+
'monospace',
82+
match_index,
83+
match_index_end
84+
))
85+
86+
# Example: Shape placeholders
87+
# `Attribute.from_shape()` can be used to insert a box within the layout.
88+
# In this case, we're inserting a 1cm square that sits on the baseline of
89+
# the final line. Note the negative y coordinate of the rectangle.
90+
# Visually, the background color will appear a bit larger, that's because
91+
# it will also apply a background below the baseline of the font.
92+
# Once rendered, it should be possible to retrieve the position of the
93+
# shape using using LayoutIter so that an actual image can be rendered in
94+
# place should you need to.
95+
text_to_match = 'Ø'.encode('utf-8')
96+
match_index = text_utf8.index(text_to_match)
97+
match_index_end = match_index + len(text_to_match)
98+
glyph_length = pangocffi.units_from_double(10 * 72 / 25.4) # 10 mm
99+
ink = Rectangle(
100+
width=glyph_length, height=glyph_length, x=0, y=-glyph_length
101+
)
102+
logical = Rectangle(
103+
width=glyph_length, height=glyph_length, x=0, y=-glyph_length
104+
)
105+
attr_list.insert(Attribute.from_background_color(
106+
65535, 0, 0, match_index, match_index_end
107+
))
108+
attr_list.insert(Attribute.from_background_alpha(
109+
8192, match_index, match_index_end
110+
))
111+
attr_list.insert(Attribute.from_shape(
112+
ink,
113+
logical,
114+
match_index,
115+
match_index_end
116+
))
117+
118+
# Apply all the attributes to the layout
119+
layout.set_attributes(attr_list)
120+
121+
# Using pangocairocffi, this would be `pangocairocffi.show_layout(layout)`
122+
pangocairo.pango_cairo_show_layout(cairo_context, layout.get_pointer())
123+
124+
context_creator.close()

0 commit comments

Comments
 (0)