Skip to content

Commit 5e0ae59

Browse files
authored
Implementing Typography for Skia (#376)
* Add text variables to style data class for both vispy and skia as they are affected by push and pop operations. * Remove font size reset * Implement the render_text function in renderer * - Implemented preload - Implemented text_font and text * - Refactor text in skia renderer. - Update the text() to use current text_size if not specified - Remove unused variables * Reset stroke_set after each draw call * Added test for text APIs * - Added comment for typography variables in constants.py - Update typography test * Add a TODO in typography test * Update typography test to use path relative to sanity_testing.py * Fix text_font for vispy to set size as well * Additions - Added text_style for skia - Skia can also make fonts from FontFamily, addded support for that in p5py * Fix pylint
1 parent 4d41804 commit 5e0ae59

File tree

9 files changed

+184
-68
lines changed

9 files changed

+184
-68
lines changed

p5/core/attribs.py

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def stroke(*color_args, **color_kwargs):
8585
"""
8686
stroke_color = Color(*color_args, **color_kwargs)
8787
p5.renderer.style.stroke_enabled = True
88+
p5.renderer.style.stroke_set = True
8889
p5.renderer.style.stroke_color = stroke_color.normalized
8990

9091

p5/core/constants.py

+6
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,16 @@
4040
CORNER = "CORNER"
4141
CORNERS = "CORNERS"
4242

43+
# Typography Setting Options
4344
LEFT = "LEFT"
4445
RIGHT = "RIGHT"
4546
TOP = "TOP"
4647
BOTTOM = "BOTTOM"
48+
BASELINE = "BASELINE"
49+
NORMAL = "NORMAL"
50+
ITALIC = "ITALIC"
51+
BOLD = "BOLD"
52+
BOLDITALIC = "BOLDITALIC"
4753

4854
# Colors
4955
colour_codes = {

p5/core/font.py

+22-18
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,6 @@
1515
# You should have received a copy of the GNU General Public License
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
#
18-
import textwrap
19-
20-
from PIL import Image
21-
from PIL import ImageDraw
22-
from PIL import ImageFont
23-
from PIL import ImageFilter
24-
from PIL import ImageChops
25-
26-
from .image import image
27-
from .image import PImage
28-
from .structure import push_style
29-
3018
from . import p5
3119

3220
__all__ = [
@@ -40,6 +28,7 @@
4028
"text_width",
4129
"text_ascent",
4230
"text_descent",
31+
"text_style",
4332
]
4433

4534

@@ -102,14 +91,15 @@ def text(*args, wrap_at=None):
10291
return p5.renderer.text(text_string, position, wrap_at)
10392

10493

105-
def text_font(font, size=10):
94+
def text_font(font, size=None):
10695
"""Set current text font.
10796
108-
:param font:
97+
:param font: PIL.ImageFont.ImageFont for Vispy, Object|String: a font loaded via loadFont(), or a String
98+
representing a web safe font (a font that is generally available across all systems)
10999
:type font: PIL.ImageFont.ImageFont
110100
111101
"""
112-
p5.renderer.font_family = font
102+
p5.renderer.text_font(font, size)
113103

114104

115105
def text_align(align_x, align_y=None):
@@ -122,9 +112,9 @@ def text_align(align_x, align_y=None):
122112
:type align_y: string
123113
124114
"""
125-
p5.renderer.text_align_x = align_x
115+
p5.renderer.style.text_align_x = align_x
126116
if align_y:
127-
p5.renderer.text_align_y = align_y
117+
p5.renderer.style.text_align_y = align_y
128118

129119

130120
def text_leading(leading):
@@ -135,7 +125,7 @@ def text_leading(leading):
135125
136126
"""
137127

138-
p5.renderer.text_leading = leading
128+
p5.renderer.style.text_leading = leading
139129

140130

141131
def text_size(size):
@@ -182,3 +172,17 @@ def text_descent():
182172
183173
"""
184174
return p5.renderer.text_descent()
175+
176+
177+
def text_style(s):
178+
"""
179+
Sets/Gets the style of the text for system fonts to NORMAL, ITALIC, BOLD or BOLDITALIC
180+
For non-system fonts (opentype, truetype, etc.) please load styled fonts instead.
181+
182+
:param s: Style for the font
183+
:type s: NORMAL | ITALIC | BOLD | BOLDITALIC
184+
185+
:returns: Current text style
186+
:rtype: NORMAL | ITALIC | BOLD | BOLDITALIC
187+
"""
188+
return p5.renderer.text_style(s)

p5/sketch/Skia2DRenderer/renderer2d.py

+53-18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ class Style2D:
2929
stroke_cap = skia.Paint.kRound_Cap
3030
stroke_join = skia.Paint.kMiter_Join
3131

32+
# Flag that holds whether stroke() is called user
33+
stroke_set = False
34+
text_font = skia.Font(skia.Typeface())
35+
text_leading = 0
36+
text_size = 15
37+
text_align = (constants.LEFT, constants.TOP)
38+
text_wrap = None
39+
text_baseline = constants.BASELINE
40+
text_style = constants.NORMAL
41+
3242
def set_stroke_cap(self, c):
3343
if c == constants.ROUND:
3444
self.stroke_cap = skia.Paint.kRound_Cap
@@ -53,9 +63,6 @@ def __init__(self):
5363
self.style = Style2D()
5464
self.style_stack = []
5565
self.path = None
56-
self.font = skia.Font()
57-
self.typeface = skia.Typeface.MakeDefault()
58-
self.font.setTypeface(self.typeface)
5966
self.curve_tightness = 0
6067

6168
# Transforms functions
@@ -131,36 +138,63 @@ def render_text(self, text, x, y):
131138
# full path works relative does not
132139
# assert (typeface is not None), "should not be NULL"
133140
# handle alignment manually
141+
if self.style.stroke_enabled and self.style.stroke_set:
142+
self.paint.setStyle(skia.Paint.kStroke_Style)
143+
self.paint.setColor(skia.Color4f(*self.style.stroke_color))
144+
self.paint.setStrokeWidth(self.style.stroke_weight)
145+
self.canvas.drawSimpleText(text, x, y, self.style.text_font, self.paint)
146+
134147
if self.style.fill_enabled:
135148
self.paint.setStyle(skia.Paint.kFill_Style)
136-
self.paint.setColor(self.style.fill_color)
137-
self.canvas.drawPath(self.path, self.paint)
149+
self.paint.setColor(skia.Color4f(*self.style.fill_color))
150+
self.canvas.drawSimpleText(text, x, y, self.style.text_font, self.paint)
138151

139-
if self.style.stroke_enabled:
140-
self.paint.setStyle(skia.Paint.kStroke_Style)
141-
self.paint.setColor(self.style.stroke_color)
142-
self.paint.setStrokeWidth(self.style.stroke_weight)
143-
self.canvas.drawPath(self.path, self.paint)
144-
self.canvas.drawSimpleText(text, x, y, self.font, self.paint)
152+
def text(self, text, position, max_width=None, max_height=None):
153+
self.render_text(text, *position)
145154

146155
def load_font(self, path):
147156
"""
148157
path: string
149158
Absolute path of the font file
150159
returns: skia.Typeface
151160
"""
152-
typeface = skia.Typeface.MakeFromFile(path)
161+
typeface = skia.Typeface().MakeFromFile(path=path)
153162
return typeface
154163

155-
def set_font(self, font):
164+
def text_font(self, font, size=None):
156165
"""
157166
Sets the current font to be used for rendering text
158167
"""
159-
self.typeface = font
160-
self.font.setTypeface(self.typeface)
168+
if size:
169+
self.style.text_size = size
170+
if isinstance(font, str):
171+
self.style.text_font.setTypeface(skia.Typeface.MakeFromName(font))
172+
else:
173+
self.style.text_font.setTypeface(font)
174+
175+
def text_size(self, size):
176+
self.style.text_font.setSize(size)
177+
178+
def text_style(self, s):
179+
skia_font_style = None
180+
if s == constants.BOLD:
181+
skia_font_style = skia.FontStyle.Bold()
182+
elif s == constants.BOLDITALIC:
183+
skia_font_style = skia.FontStyle.BoldItalic()
184+
elif s == constants.ITALIC:
185+
skia_font_style = skia.FontStyle.BoldItalic()
186+
elif s == constants.NORMAL:
187+
skia_font_style = skia.FontStyle.Normal()
188+
else:
189+
return self.style.text_style
190+
191+
self.style.text_style = s
192+
# skia.Font.
193+
family_name = self.style.text_font.getTypeface().getFamilyName()
194+
typeface = skia.Typeface.MakeFromName(family_name, skia_font_style)
195+
self.style.text_font.setTypeface(typeface)
161196

162-
def set_font_size(self, size):
163-
self.font.setSize(size)
197+
return self.style.text_style
164198

165199
def render_image(self, img, *args):
166200
self.canvas.drawImage(img, *args)
@@ -213,7 +247,8 @@ def render(self, fill=True, stroke=True, rewind=True):
213247

214248
def reset(self):
215249
self.reset_matrix()
216-
self.font.setSize(15)
250+
# NOTE: We have to handle PGraphics stroke_set as well separately
251+
self.style.stroke_set = False
217252

218253
def line(self, path):
219254
x1, y1, x2, y2 = path[0].x, path[0].y, path[1].x, path[1].y

p5/sketch/Vispy2DRenderer/openglrenderer.py

+6-9
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ class Style2D:
238238
stroke_cap = ROUND
239239
stroke_join = MITER
240240

241+
# typography variables
242+
font_family = ImageFont.load_default()
243+
text_align_x = LEFT
244+
text_align_y = TOP
245+
text_leading = 0
246+
241247
def set_stroke_cap(self, c):
242248
self.stroke_cap = c
243249

@@ -287,15 +293,6 @@ def __init__(self, src_fbuffer, src_default):
287293
self.matrix_stack = []
288294

289295
self.curve_tightness = 0
290-
291-
# typography variables
292-
self.font_family = ImageFont.load_default()
293-
self.text_align_x = LEFT
294-
self.text_align_y = TOP
295-
self.text_leading = 0
296-
297-
298-
299296

300297
def render_default(self, draw_type, draw_queue):
301298
# 1. Get the maximum number of vertices persent in the shapes

p5/sketch/Vispy2DRenderer/renderer2d.py

+31-21
Original file line numberDiff line numberDiff line change
@@ -408,14 +408,14 @@ def text(self, text_string, position, wrap_at):
408408
multiline = False
409409
if not (wrap_at is None):
410410
text_string = textwrap.fill(text_string, wrap_at)
411-
size = self.font_family.getsize_multiline(text_string)
411+
size = self.style.font_family.getsize_multiline(text_string)
412412
multiline = True
413413
elif "\n" in text_string:
414414
multiline = True
415-
size = list(self.font_family.getsize_multiline(text_string))
416-
size[1] += self.text_leading * text_string.count("\n")
415+
size = list(self.style.font_family.getsize_multiline(text_string))
416+
size[1] += self.style.text_leading * text_string.count("\n")
417417
else:
418-
size = self.font_family.getsize(text_string)
418+
size = self.style.font_family.getsize(text_string)
419419

420420
is_stroke_valid = False # True when stroke_weight != 0
421421
is_min_filter = False # True when stroke_weight <0
@@ -445,11 +445,11 @@ def text(self, text_string, position, wrap_at):
445445
canvas_draw.multiline_text(
446446
text_xy,
447447
text_string,
448-
font=self.font_family,
449-
spacing=self.text_leading,
448+
font=self.style.style.font_family,
449+
spacing=self.style.text_leading,
450450
)
451451
else:
452-
canvas_draw.text(text_xy, text_string, font=self.font_family)
452+
canvas_draw.text(text_xy, text_string, font=self.style.font_family)
453453

454454
text_image = PImage(*new_size)
455455
text_image._img = canvas
@@ -465,18 +465,18 @@ def text(self, text_string, position, wrap_at):
465465

466466
width, height = new_size
467467
position = list(position)
468-
if self.text_align_x == LEFT:
468+
if self.style.text_align_x == LEFT:
469469
position[0] += 0
470-
elif self.text_align_x == RIGHT:
470+
elif self.style.text_align_x == RIGHT:
471471
position[0] -= width
472-
elif self.text_align_x == CENTER:
472+
elif self.style.text_align_x == CENTER:
473473
position[0] -= width / 2
474474

475-
if self.text_align_y == TOP:
475+
if self.style.text_align_y == TOP:
476476
position[1] += 0
477-
elif self.text_align_y == BOTTOM:
477+
elif self.style.text_align_y == BOTTOM:
478478
position[1] -= height
479-
elif self.text_align_y == CENTER:
479+
elif self.style.text_align_y == CENTER:
480480
position[1] -= height / 2
481481

482482
with push_style():
@@ -491,22 +491,32 @@ def text(self, text_string, position, wrap_at):
491491

492492
return text_string
493493

494+
def text_font(self, font, size):
495+
self.style.font_family = font
496+
if size:
497+
self.text_size(size)
498+
494499
def text_size(self, size):
495-
if hasattr(self.font_family, "path"):
496-
if self.font_family.path.endswith("ttf") or self.font_family.path.endswith(
497-
"otf"
498-
):
499-
self.font_family = ImageFont.truetype(self.font_family.path, size)
500+
if hasattr(self.style.font_family, "path"):
501+
if self.style.font_family.path.endswith(
502+
"ttf"
503+
) or self.style.font_family.path.endswith("otf"):
504+
self.style.font_family = ImageFont.truetype(
505+
self.style.font_family.path, size
506+
)
500507
else:
501508
raise ValueError("text_size is not supported for Bitmap Fonts")
502509

503510
def text_width(self, text):
504-
return self.font_family.getsize(text)[0]
511+
return self.style.font_family.getsize(text)[0]
505512

506513
def text_ascent(self):
507-
ascent, descent = self.font_family.getmetrics()
514+
ascent, descent = self.style.font_family.getmetrics()
508515
return ascent
509516

510517
def text_descent(self):
511-
ascent, descent = self.font_family.getmetrics()
518+
ascent, descent = self.style.font_family.getmetrics()
512519
return descent
520+
521+
def text_style(self):
522+
raise NotImplementedError("Not Implemented error in Vispy")

0 commit comments

Comments
 (0)