You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

506 lines
17 KiB
Python

# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of pyglet nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
# $Id: $
'''Text formatting, layout and display.
This module provides classes for loading styled documents from text files,
HTML files and a pyglet-specific markup format. Documents can be styled with
multiple fonts, colours, styles, text sizes, margins, paragraph alignments,
and so on.
Using the layout classes, documents can be laid out on a single line or
word-wrapped to fit a rectangle. A layout can then be efficiently drawn in
a window or updated incrementally (for example, to support interactive text
editing).
The label classes provide a simple interface for the common case where an
application simply needs to display some text in a window.
A plain text label can be created with::
label = pyglet.text.Label('Hello, world',
font_name='Times New Roman',
font_size=36,
x=10, y=10)
Alternatively, a styled text label using HTML can be created with::
label = pyglet.text.HTMLLabel('<b>Hello</b>, <i>world</i>',
x=10, y=10)
Either label can then be drawn at any time with::
label.draw()
For details on the subset of HTML supported, see `pyglet.text.formats.html`.
Refer to the Programming Guide for advanced usage of the document and layout
classes, including interactive editing, embedding objects within documents and
creating scrollable layouts.
:since: pyglet 1.1
'''
__docformat__ = 'restructuredtext'
__version__ = '$Id: $'
import os.path
import pyglet
from pyglet.text import layout, document, caret
class DocumentDecodeException(Exception):
'''An error occured decoding document text.'''
pass
class DocumentDecoder(object):
'''Abstract document decoder.
'''
def decode(self, text, location=None):
'''Decode document text.
:Parameters:
`text` : str
Text to decode
`location` : `Location`
Location to use as base path for additional resources
referenced within the document (for example, HTML images).
:rtype: `AbstractDocument`
'''
raise NotImplementedError('abstract')
def get_decoder(filename, mimetype=None):
'''Get a document decoder for the given filename and MIME type.
If `mimetype` is omitted it is guessed from the filename extension.
The following MIME types are supported:
``text/plain``
Plain text
``text/html``
HTML 4 Transitional
``text/vnd.pyglet-attributed``
Attributed text; see `pyglet.text.formats.attributed`
`DocumentDecodeException` is raised if another MIME type is given.
:Parameters:
`filename` : str
Filename to guess the MIME type from. If a MIME type is given,
the filename is ignored.
`mimetype` : str
MIME type to lookup, or ``None`` to guess the type from the
filename.
:rtype: `DocumentDecoder`
'''
if mimetype is None:
_, ext = os.path.splitext(filename)
if ext.lower() in ('.htm', '.html', '.xhtml'):
mimetype = 'text/html'
else:
mimetype = 'text/plain'
if mimetype == 'text/plain':
from pyglet.text.formats import plaintext
return plaintext.PlainTextDecoder()
elif mimetype == 'text/html':
from pyglet.text.formats import html
return html.HTMLDecoder()
elif mimetype == 'text/vnd.pyglet-attributed':
from pyglet.text.formats import attributed
return attributed.AttributedTextDecoder()
else:
raise DocumentDecodeException('Unknown format "%s"' % mimetype)
def load(filename, file=None, mimetype=None):
'''Load a document from a file.
:Parameters:
`filename` : str
Filename of document to load.
`file` : file-like object
File object containing encoded data. If omitted, `filename` is
loaded from disk.
`mimetype` : str
MIME type of the document. If omitted, the filename extension is
used to guess a MIME type. See `get_decoder` for a list of
supported MIME types.
:rtype: `AbstractDocument`
'''
decoder = get_decoder(filename, mimetype)
if file is None:
file = open(filename)
location = pyglet.resource.FileLocation(os.path.dirname(filename))
return decoder.decode(file.read(), location)
def decode_html(text, location=None):
'''Create a document directly from some HTML formatted text.
:Parameters:
`text` : str
HTML data to decode.
`location` : str
Location giving the base path for additional resources
referenced from the document (e.g., images).
:rtype: `FormattedDocument`
'''
decoder = get_decoder(None, 'text/html')
return decoder.decode(text, location)
def decode_attributed(text):
'''Create a document directly from some attributed text.
See `pyglet.text.formats.attributed` for a description of attributed text.
:Parameters:
`text` : str
Attributed text to decode.
:rtype: `FormattedDocument`
'''
decoder = get_decoder(None, 'text/vnd.pyglet-attributed')
return decoder.decode(text)
def decode_text(text):
'''Create a document directly from some plain text.
:Parameters:
`text` : str
Plain text to initialise the document with.
:rtype: `UnformattedDocument`
'''
decoder = get_decoder(None, 'text/plain')
return decoder.decode(text)
class DocumentLabel(layout.TextLayout):
'''Base label class.
A label is a layout that exposes convenience methods for manipulating the
associated document.
'''
def __init__(self, document=None,
x=0, y=0, width=None, height=None,
anchor_x='left', anchor_y='baseline',
multiline=False, dpi=None, batch=None, group=None):
'''Create a label for a given document.
:Parameters:
`document` : `AbstractDocument`
Document to attach to the layout.
`x` : int
X coordinate of the label.
`y` : int
Y coordinate of the label.
`width` : int
Width of the label in pixels, or None
`height` : int
Height of the label in pixels, or None
`anchor_x` : str
Anchor point of the X coordinate: one of ``"left"``,
``"center"`` or ``"right"``.
`anchor_y` : str
Anchor point of the Y coordinate: one of ``"bottom"``,
``"baseline"``, ``"center"`` or ``"top"``.
`multiline` : bool
If True, the label will be word-wrapped and accept newline
characters. You must also set the width of the label.
`dpi` : float
Resolution of the fonts in this layout. Defaults to 96.
`batch` : `Batch`
Optional graphics batch to add the label to.
`group` : `Group`
Optional graphics group to use.
'''
super(DocumentLabel, self).__init__(document,
width=width, height=height,
multiline=multiline,
dpi=dpi, batch=batch, group=group)
self._x = x
self._y = y
self._anchor_x = anchor_x
self._anchor_y = anchor_y
self._update()
def _get_text(self):
return self.document.text
def _set_text(self, text):
self.document.text = text
text = property(_get_text, _set_text,
doc='''The text of the label.
:type: str
''')
def _get_color(self):
return self.document.get_style('color')
def _set_color(self, color):
self.document.set_style(0, len(self.document.text),
{'color': color})
color = property(_get_color, _set_color,
doc='''Text color.
Color is a 4-tuple of RGBA components, each in range [0, 255].
:type: (int, int, int, int)
''')
def _get_font_name(self):
return self.document.get_style('font_name')
def _set_font_name(self, font_name):
self.document.set_style(0, len(self.document.text),
{'font_name': font_name})
font_name = property(_get_font_name, _set_font_name,
doc='''Font family name.
The font name, as passed to `pyglet.font.load`. A list of names can
optionally be given: the first matching font will be used.
:type: str or list
''')
def _get_font_size(self):
return self.document.get_style('font_size')
def _set_font_size(self, font_size):
self.document.set_style(0, len(self.document.text),
{'font_size': font_size})
font_size = property(_get_font_size, _set_font_size,
doc='''Font size, in points.
:type: float
''')
def _get_bold(self):
return self.document.get_style('bold')
def _set_bold(self, bold):
self.document.set_style(0, len(self.document.text),
{'bold': bold})
bold = property(_get_bold, _set_bold,
doc='''Bold font style.
:type: bool
''')
def _get_italic(self):
return self.document.get_style('italic')
def _set_italic(self, italic):
self.document.set_style(0, len(self.document.text),
{'italic': italic})
italic = property(_get_italic, _set_italic,
doc='''Italic font style.
:type: bool
''')
def get_style(self, name):
'''Get a document style value by name.
If the document has more than one value of the named style,
`pyglet.text.document.STYLE_INDETERMINATE` is returned.
:Parameters:
`name` : str
Style name to query. See documentation for
`pyglet.text.layout` for known style names.
:rtype: object
'''
return self.document.get_style_range(name, 0, len(self.document.text))
def set_style(self, name, value):
'''Set a document style value by name over the whole document.
:Parameters:
`name` : str
Name of the style to set. See documentation for
`pyglet.text.layout` for known style names.
`value` : object
Value of the style.
'''
self.document.set_style(0, len(self.document.text), {name: value})
class Label(DocumentLabel):
'''Plain text label.
'''
def __init__(self, text='',
font_name=None, font_size=None, bold=False, italic=False,
color=(255, 255, 255, 255),
x=0, y=0, width=None, height=None,
anchor_x='left', anchor_y='baseline',
halign='left',
multiline=False, dpi=None, batch=None, group=None):
'''Create a plain text label.
:Parameters:
`text` : str
Text to display.
`font_name` : str or list
Font family name(s). If more than one name is given, the
first matching name is used.
`font_size` : float
Font size, in points.
`bold` : bool
Bold font style.
`italic` : bool
Italic font style.
`color` : (int, int, int, int)
Font colour, as RGBA components in range [0, 255].
`x` : int
X coordinate of the label.
`y` : int
Y coordinate of the label.
`width` : int
Width of the label in pixels, or None
`height` : int
Height of the label in pixels, or None
`anchor_x` : str
Anchor point of the X coordinate: one of ``"left"``,
``"center"`` or ``"right"``.
`anchor_y` : str
Anchor point of the Y coordinate: one of ``"bottom"``,
``"baseline"``, ``"center"`` or ``"top"``.
`halign` : str
Horizontal alignment of text on a line, only applies if
a width is supplied. One of ``"left"``, ``"center"``
or ``"right"``.
`multiline` : bool
If True, the label will be word-wrapped and accept newline
characters. You must also set the width of the label.
`dpi` : float
Resolution of the fonts in this layout. Defaults to 96.
`batch` : `Batch`
Optional graphics batch to add the label to.
`group` : `Group`
Optional graphics group to use.
'''
document = decode_text(text)
super(Label, self).__init__(document, x, y, width, height,
anchor_x, anchor_y,
multiline, dpi, batch, group)
self.document.set_style(0, len(self.document.text), {
'font_name': font_name,
'font_size': font_size,
'bold': bold,
'italic': italic,
'color': color,
'halign': halign,
})
class HTMLLabel(DocumentLabel):
'''HTML formatted text label.
A subset of HTML 4.01 is supported. See `pyglet.text.formats.html` for
details.
'''
def __init__(self, text='', location=None,
x=0, y=0, width=None, height=None,
anchor_x='left', anchor_y='baseline',
multiline=False, dpi=None, batch=None, group=None):
'''Create a label with an HTML string.
:Parameters:
`text` : str
HTML formatted text to display.
`location` : `Location`
Location object for loading images referred to in the
document. By default, the working directory is used.
`x` : int
X coordinate of the label.
`y` : int
Y coordinate of the label.
`width` : int
Width of the label in pixels, or None
`height` : int
Height of the label in pixels, or None
`anchor_x` : str
Anchor point of the X coordinate: one of ``"left"``,
``"center"`` or ``"right"``.
`anchor_y` : str
Anchor point of the Y coordinate: one of ``"bottom"``,
``"baseline"``, ``"center"`` or ``"top"``.
`multiline` : bool
If True, the label will be word-wrapped and render paragraph
and line breaks. You must also set the width of the label.
`dpi` : float
Resolution of the fonts in this layout. Defaults to 96.
`batch` : `Batch`
Optional graphics batch to add the label to.
`group` : `Group`
Optional graphics group to use.
'''
self._text = text
self._location = location
document = decode_html(text, location)
super(HTMLLabel, self).__init__(document, x, y, width, height,
anchor_x, anchor_y,
multiline, dpi, batch, group)
def _set_text(self, text):
self._text = text
self.document = decode_html(text, self._location)
def _get_text(self):
return self._text
text = property(_get_text, _set_text,
doc='''HTML formatted text of the label.
:type: str
''')