Files
esl-reader/lib/words.py
2024-03-27 12:01:47 -04:00

933 lines
34 KiB
Python

import copy
import json
import re
from typing import Any, Optional, Self
import requests
from PyQt6.QtCore import QPoint, QRect, QSize, QUrl, Qt, pyqtSignal
from PyQt6.QtGui import (
QBrush,
QColor,
QFont,
QFontDatabase,
QFontMetrics,
QMouseEvent,
QPainter,
QPaintEvent,
QResizeEvent,
QTextOption,
QTransform,
)
from PyQt6.QtSql import QSqlQuery
from PyQt6.QtWidgets import QScrollArea, QScrollBar, QSizePolicy, QWidget
from lib import query_error
API = "https://api.dictionaryapi.dev/api/v2/entries/en/{word}"
MWAPI = "https://www.dictionaryapi.com/api/v3/references/collegiate/json/{word}?key=51d9df34-ee13-489e-8656-478c215e846c"
class Word:
_instance = None
_words: dict[str, Any] = {}
_current: dict[str, Any] = {}
_currentWord: str
class Fragment:
"""A structure to hold typed values of a fragment."""
_type: str # Function of this fragment. Think text, span, button
_text: str # The simple utf-8 text
_content: list['Word.Fragment']
_audio: QUrl # Optional audio URL
_font: QFont # The font, with all options, to use for this fragment
_align: QTextOption # Alignment information
_rect: QRect # The rect that contains _text
_padding: list[int] # space to add around the text
_border: list[int] # Size of the border (all the same)
_margin: list[int] # Space outside of the border
_color: QColor # the pen color
_wref: str # a word used as a 'href'
_position: QPoint # where to drawText
_borderRect: QRect # where to drawRect
_radius: int # Radius for rounded rects
_asis: bool = False
_left: int = 0
TYPES = [ 'text', 'span', 'button' ]
def __init__(self,
text:str,
font:QFont,
t:str = 'text',
audio:str = '',
color: Optional[QColor] = None,
asis: bool = False,
) -> None:
if t not in self.TYPES:
raise Exception(f"Unknown fragment type{t}")
self._type = t
self._text = text
self._font = font
self._audio = QUrl(audio)
self._align = QTextOption(
Qt.AlignmentFlag.AlignLeft
| Qt.AlignmentFlag.AlignBaseline
)
self._padding = [0, 0, 0, 0]
self._border = [0, 0, 0, 0]
self._margin = [0, 0, 0, 0]
self._wref = ''
self._position = QPoint()
self._borderRect = QRect()
if color:
self._color = color
else:
self._color = QColor()
self._asis = asis
return
def __str__(self) -> str:
return self._text
#
# Setters
#
def setType(self, t:str) -> None:
if t not in self.TYPES:
raise Exception(f"Unknown fragment type{t}")
self._type = t
return
def setText(self, text:str) -> None:
self._text = text
return
def setFont(self, font:QFont) -> None:
self._font = font
return
def setAudio(self, audio:str) -> None:
self._audio = QUrl(audio)
return
def setAlign(self, align:QTextOption) -> None:
self._align = align
return
def setRect(self,rect:QRect) -> None:
self._rect = rect
return
def setPadding(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
if top > -1 or right > -1 or bottom > -1 or left > -1:
if top >= 0:
self._padding[0] = top
if right >= 0:
self._padding[1] = right
if bottom >= 0:
self._padding[2] = bottom
if left >= 0:
self._padding[3] = left
return
if len(args) == 4:
self._padding = [args[0], args[1], args[2], args[3]]
elif len(args) == 3:
self._padding = [args[0], args[1], args[2], args[1]]
elif len(args) == 2:
self._padding = [args[0], args[1], args[0], args[1]]
elif len(args) == 1:
self._padding = [args[0], args[0], args[0], args[0]]
else:
raise Exception("argument error")
return
def setBorder(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
if top > -1 or right > -1 or bottom > -1 or left > -1:
if top >= 0:
self._border[0] = top
if right >= 0:
self._border[1] = right
if bottom >= 0:
self._border[2] = bottom
if left >= 0:
self._border[3] = left
return
if len(args) == 4:
self._border = [args[0], args[1], args[2], args[3]]
elif len(args) == 3:
self._border = [args[0], args[1], args[2], args[1]]
elif len(args) == 2:
self._border = [args[0], args[1], args[0], args[1]]
elif len(args) == 1:
self._border = [args[0], args[0], args[0], args[0]]
else:
raise Exception("argument error")
return
def setMargin(self, top:int = -1, right:int = -1, bottom:int = -1, left:int = -1, *args:int) -> None:
if top > -1 or right > -1 or bottom > -1 or left > -1:
if top >= 0:
self._margin[0] = top
if right >= 0:
self._margin[1] = right
if bottom >= 0:
self._margin[2] = bottom
if left >= 0:
self._margin[3] = left
return
if len(args) == 4:
self._margin = [args[0], args[1], args[2], args[3]]
elif len(args) == 3:
self._margin = [args[0], args[1], args[2], args[1]]
elif len(args) == 2:
self._margin = [args[0], args[1], args[0], args[1]]
elif len(args) == 1:
self._margin = [args[0], args[0], args[0], args[0]]
else:
raise Exception("argument error")
return
def setWRef(self, ref:str) -> None:
self._wref = ref
return
def setPosition(self, pnt:QPoint) -> None:
self._position = pnt
return
def setBorderRect(self, rect:QRect) -> None:
self._borderRect = rect
return
def setColor(self,color:QColor) -> None:
self._color = color
return
def setLeft(self, left:int) -> None:
self._left = left
return
#
# Getters
#
def wRef(self) -> str:
return self._wref
def type(self) -> str:
return self._type
def text(self) -> str:
return self._text
def font(self) -> QFont:
return self._font
def audio(self) -> str:
return self._audio.url()
def align(self) -> QTextOption:
return self._align
def rect(self) -> QRect:
return self._rect
def padding(self) -> list[int]:
return self._padding
def border(self) -> list[int]:
return self._border
def margin(self) -> list[int]:
return self._margin
def position(self) -> QPoint:
return self._position
def borderRect(self) -> QRect:
return self._borderRect
def color(self) -> QColor:
return self._color
def asis(self) -> bool:
return self._asis
def left(self) -> int:
return self._left
class Line:
_maxHeight: int
_leading: int
_baseLine: int
_fragments: list['Word.Fragment']
def __init__(self) -> None:
self._maxHeight = -1
self._baseLine = -1
self._leading = -1
self._fragments = []
return
def __repr__(self) -> str:
return '|'.join([x.text() for x in self._fragments])+f'|{self._maxHeight}'
def parseText(self, frag: 'Word.Fragment') -> list['Word.Fragment']:
org = frag.text()
if frag.asis():
return [frag]
#
# Needed Fonts
#
bold = QFont(frag.font())
bold.setWeight(QFont.Weight.Bold)
italic = QFont(frag.font())
italic.setItalic(True)
smallCaps = QFont(frag.font())
smallCaps.setCapitalization(QFont.Capitalization.SmallCaps)
script = QFont(frag.font())
script.setPixelSize(int(script.pixelSize()/4))
results: list['Word.Fragment'] = []
while True:
text = frag.text()
start = text.find('{')
if start < 0:
results.append(frag)
return results
if start > 0:
newFrag = copy.copy(frag)
newFrag.setText(text[:start])
results.append(newFrag)
frag.setText(text[start:])
continue
#
# Start == 0
#
#
# If the token is an end-token, return now.
#
if text.startswith('{/'):
results.append(frag)
return results
#
# extract this token
#
end = text.find('}')
token = text[1:end]
frag.setText(text[end+1:])
newFrag = copy.copy(frag)
oldFont = QFont(frag.font())
if token == 'bc':
results.append(Word.Fragment(': ', bold, color=QColor('#fff')))
continue
if token in ['b', 'inf', 'it', 'sc', 'sup', 'phrase', 'parahw', 'gloss',
'qword', 'wi', 'dx', 'dx_def', 'dx_ety', 'ma']:
if token == 'b':
frag.setFont(bold)
elif token in ['it', 'qword', 'wi']:
frag.setFont(italic)
elif token == 'sc':
frag.setFont(smallCaps)
elif token in ['inf', 'sup']:
frag.setFont(script)
elif token == 'phrase':
font = QFont(bold)
font.setItalic(True)
frag.setFont(font)
elif token == 'parahw':
font = QFont(smallCaps)
font.setWeight(QFont.Weight.Bold)
frag.setFont(font)
elif token == 'gloss':
frag.setText('[' + frag.text())
elif token in ['dx', 'dx_ety']:
frag.setText('\u2014'+frag.text())
elif token == 'ma':
frag.setText('\u2014 more at '+frag.text())
elif token == 'dx_def':
frag.setText('('+frag.text())
else:
raise Exception(f"Unknown block marker: {token}")
results += self.parseText(frag)
frag = results.pop()
frag.setFont(oldFont)
text = frag.text()
if not text.startswith('{/'+token+'}'):
raise Exception(f"No matching close for {token} in {org}")
if token == 'gloss':
results[-1].setText(results[-1].text() + ']')
elif token == 'dx_def':
results[-1].setText(results[-1].text() + ')')
end = text.find('}')
text = text[end+1:]
frag.setText(text)
continue
#
# These are codes that include all information within the token
#
fields = token.split('|')
token = fields[0]
if token in [ 'a_line', 'd_link', 'dxt', 'et_link', 'i_link',
'mat', 'sx']:
wref = ''
htext = fields[1]
oldFont = QFont(frag.font())
if token == 'a_link':
wref = fields[1]
elif token in ['d_link', 'et_link', 'mat', 'sx', 'i_link']:
if fields[2] == '':
wref = fields[1]
else:
wref = fields[2]
if token == 'i_link':
frag.setFont(italic)
elif token == 'i_link':
if fields[2] == '':
wref = fields[1]
else:
wref = fields[2]
else:
raise Exception(f"Unknown code: {token} in {org}")
newFrag = copy.copy(frag)
newFrag.setText(htext)
newFrag.setWRef(wref)
results.append(newFrag)
frag.setFont(oldFont)
text = frag.text()
continue
raise Exception(f"Unable to locate a known token {token} in {org}")
def addFragment(self, frag: 'Word.Fragment',) -> None:
SPEAKER = "\U0001F508"
if frag.audio():
frag.setText(frag.text() + ' ' + SPEAKER)
text = frag.text()
text = re.sub(r"\*", "\u2022", text)
text = re.sub(r"\{ldquo\}", "\u201c", text)
text = re.sub(r"\{rdquo\}", "\u201d", text)
frag.setText(text)
if frag.type() == 'btn':
frag.setPadding(3)
frag.setBorder(1)
frag.setMargin(2)
items = self.parseText(frag)
self._fragments += items
return
def finalizeLine(self, maxWidth:int) -> None:
"""Create all of the positions for all the fragments."""
#
# Find the maximum hight and max baseline
#
maxHeight = -1
baseLine = -1
leading = -1
for frag in self._fragments:
fm = QFontMetrics(frag.font())
rect = fm.boundingRect(frag.text(), frag.align())
height = rect.height()
bl = height - fm.descent()
#
# Add the padding, border and margin to adjust the baseline and height
#
b = frag.padding()
height += b[0] + b[2]
bl += b[2]
b = frag.border()
height += b[0] + b[2]
bl += b[2]
b = frag.margin()
height += b[0] + b[2]
bl += b[2]
if height > maxHeight:
maxHeight = height
if bl > baseLine:
baseLine = bl
self._baseLine = baseLine
self._maxHeight = maxHeight
self._leading = 0 # XXX - How should this be calculated?
x = 0
for frag in self._fragments:
#
# TODO - Wordwrap
#
if x < frag.left():
x = frag.left()
fm = QFontMetrics(frag.font())
width = fm.horizontalAdvance(frag.text())
padding = frag.padding()
offset = padding[3] # Left margin
width += padding[1] + padding[3]
border = frag.border()
offset += border[3]
width += border[1] + border[3]
margin = frag.margin()
offset += margin[3]
width += margin[1] + margin[3]
frag.setPosition(QPoint(x+offset, self._baseLine))
if frag.border()[0] != 0:
#
# self._baseLine is where the text will be drawn
# fm.descent is the distance from the baseline of the
# text to the bottom of the rect
# The top of the bounding rect is at self._baseLine
# + fm.descent - rect.height
# The border is drawn at top-padding-border-margin+marin
#
top = self._baseLine + fm.descent() - rect.height() -1
y = top - padding[0] - border[0]
frag.setBorderRect(QRect(x+margin[3], y, width, height))
if x + width > maxWidth:
print(f'Wrap text: {frag.text()}, {x}+{width} = {x + width}')
x += width
return
def getLine(self) -> list['Word.Fragment']:
return self._fragments
def getLeading(self) -> int:
return self._leading + self._maxHeight
_lines: list[Line] = []
def __new__(cls: type[Self], _: str) -> Self: # flycheck: ignore
if cls._instance:
return cls._instance
cls._instance = super(Word, cls).__new__(cls)
return cls._instance
def __init__(self, word: str) -> None:
#
# reset the current definition
#
try:
if word != self._current['word']:
self._lines = []
except KeyError:
pass
#
# Have we already retrieved this word?
#
try:
self._current = json.loads(self._words[word])
return
except KeyError:
pass
query = QSqlQuery()
query.prepare("SELECT * FROM words " "WHERE word = :word")
query.bindValue(":word", word)
if not query.exec():
query_error(query)
if query.next():
self._words[word] = {
'word': word,
'source': query.value('source'),
'definition': json.loads(query.value("definition")),
}
self._current = self._words[word]
return
source = 'mw'
response = requests.get(MWAPI.format(word=word))
if response.status_code != 200:
self._current = {}
return
data = json.loads(response.content.decode("utf-8"))
print(data)
self._words[word] = {
'word': word,
'source': source,
'definition': data,
}
self._current = self._words[word]
query.prepare(
"INSERT INTO words "
"(word, source, definition) "
"VALUES (:word, :source, :definition)"
)
query.bindValue(":word", self._current['word'])
query.bindValue(":source", self._current['source'])
query.bindValue(":definition", json.dumps(self._current['definition']))
if not query.exec():
query_error(query)
return
def getCurrent(self) -> str:
return self._current['word']
def get_html(self) -> str | None:
if self._current['source'] == 'mw':
return self.mw_html()
elif self._current['source'] == 'apidictionary':
return self.apidictionary_html()
else:
raise Exception(f"Unknown source: {self._current['source']}")
_resources:dict[str,Any] = {}
def get_def(self) -> list[Line] | None:
if len(self._lines) > 0:
return self._lines
if len(self._resources.keys()) < 1:
#
# Colors we used
#
headerFont = QFontDatabase.font("OpenDyslexic", None, 10)
headerFont.setPixelSize(48)
labelFont = QFont(headerFont)
labelFont.setPixelSize(30)
boldFont = QFont(headerFont)
boldFont.setPixelSize(20)
textFont = QFont(boldFont)
italicFont = QFont(boldFont)
headerFont.setWeight(QFont.Weight.Bold)
boldFont.setBold(True)
italicFont.setItalic(True)
phonicFont = QFontDatabase.font("Gentium", None, 10)
phonicFont.setPixelSize(20)
self._resources = {
'colors': {
'base':QColor(Qt.GlobalColor.white),
'blue': QColor("#4a7d95"),
},
'fonts': {
'header': headerFont,
'label': labelFont,
'phonic': phonicFont,
'bold': boldFont,
'italic': italicFont,
'text': textFont,
}
}
if self._current['source'] == 'mw':
return self.mw_def()
elif self._current['source'] == 'apidictionary':
return None
else:
raise Exception(f"Unknown source: {self._current['source']}")
def mw_def(self) -> list[Line]:
lines: list[Word.Line] = []
for entry in self._current['definition']:
line = Word.Line()
meta = json.dumps(entry['meta'])
line.addFragment(
Word.Fragment(
meta,
self._resources['fonts']['text'],asis=True
)
)
lines.append(line)
lines += self.mw_def_entry(entry)
self._lines = lines
return lines
def mw_seq(self, seq: list[Any]) -> list[Line]:
lines: list[Word.Line] = []
outer = ' '
inner = ' '
for value in seq:
sense = value[1]
#
# The optional 'sn' field tells us what sort of labeling to do
#
sn = sense.get('sn', '')
sns = sn.split(' ')
if len(sns) == 2:
outer = sns[0]
inner = sns[1]
elif len(sns) == 1:
if inner == ' ':
outer = sns[0]
else:
inner = sns[0]
for dt in sense['dt']:
if dt[0] == 'text':
line = Word.Line()
frag = Word.Fragment(
f"{outer} {inner} ",
self._resources['fonts']['bold'],
color = self._resources['colors']['base']
)
outer = ' '
frag.setLeft(10)
line.addFragment(frag)
frag = Word.Fragment(
dt[1],
self._resources['fonts']['text'],
color = self._resources['colors']['base']
)
frag.setLeft(30)
line.addFragment(frag)
lines.append(line)
elif dt[0] == 'vis':
for vis in dt[1]:
line = Word.Line()
frag =Word.Fragment(f" ",
self._resources['fonts']['bold'],
)
frag.setLeft(45)
line.addFragment(frag)
line.addFragment(
Word.Fragment(vis['t'],
self._resources['fonts']['text'],
color = QColor('#aaa')
)
)
lines.append(line)
return lines
def mw_def_entry(self, entry) -> list[Line]:
#
# Easy reference to colors
#
base = self._resources['colors']['base']
blue = self._resources['colors']['blue']
lines: list[Word.Line] = []
line = Word.Line()
hw = re.sub(r'\*', '', entry['hwi']['hw'])
frag = Word.Fragment(hw, self._resources['fonts']['header'], color=base)
line.addFragment(frag)
frag = Word.Fragment(' '+entry["fl"], self._resources['fonts']['label'], color=blue)
line.addFragment(frag)
lines.append(line)
if "vrs" in entry.keys():
line = self.Line()
space = ''
for vrs in entry["vrs"]:
frag = Word.Fragment(space + vrs["va"], self._resources['fonts']['label'], color=base)
space = ' '
line.addFragment(frag)
lines.append(line)
if "prs" in entry["hwi"].keys():
line = self.Line()
frag = Word.Fragment(entry['hwi']['hw'] + ' ', self._resources['fonts']['phonic'], color=base)
line.addFragment(frag)
for prs in entry["hwi"]["prs"]:
audio = self.sound_url(prs)
if audio is None:
audio = ""
frag = Word.Fragment(prs['mw'], self._resources['fonts']['phonic'], color=blue)
frag.setAudio(audio)
frag.setPadding(0,10,3,12)
frag.setBorder(1)
frag.setMargin(0,3,0,3)
line.addFragment(frag)
lines.append(line)
if "ins" in entry.keys():
line = self.Line()
space = ''
for ins in entry['ins']:
try:
frag = Word.Fragment(ins['il'], self._resources['fonts']['text'], color=base)
line.addFragment(frag)
space = ' '
except KeyError:
pass
frag = Word.Fragment(space + ins['if'], self._resources['fonts']['bold'], color=base)
line.addFragment(frag)
space = '; '
lines.append(line)
if 'lbs' in entry.keys():
line = self.Line()
frag = Word.Fragment('; '.join(entry['lbs']), self._resources['fonts']['bold'], color=base)
line.addFragment(frag)
lines.append(line)
for value in entry['def']: # has multiple 'sseg' or 'vd' init
for k,v in value.items():
if k == 'sseq': # has multiple 'senses'
for seq in v:
r = self.mw_seq(seq)
lines += r
elif k == 'vd':
line = self.Line()
line.addFragment(Word.Fragment(
v,
self._resources['fonts']['italic'],
color=blue
))
lines.append(line)
return lines
def sound_url(self, prs: dict[str, Any], fmt: str = "ogg") -> str | None:
"""Create a URL from a PRS structure."""
base = f"https://media.merriam-webster.com/audio/prons/en/us/{fmt}"
if "sound" not in prs.keys():
return None
audio = prs["sound"]["audio"]
m = re.match(r"(bix|gg|[a-zA-Z])", audio)
if m:
url = base + f"/{m.group(1)}/"
else:
url = base + "/number/"
url += audio + f".fmt"
return url
def mw_html(self) -> str:
def parse_sn(sn: str, old: str) -> str:
return sn
#
# Create the header, base word and its label
#
word = self._current["hwi"]["hw"]
label = self._current["fl"]
html = f'<h1 class="def-word">{word} <span class="def-label">{label}</span></h1>\n'
#
# If there are variants, then add them in an unordered list.
# CSS will make it pretty
#
if "vrs" in self._current.keys():
html += "<ul class=\"def-vrs'>\n"
html += "<li>"
html += "</li>\n<li>".join(
[vrs["va"] for vrs in self._current["vrs"]]
)
html += "</li>\n</ul>\n"
#
# If there is a pronunciation section, create it
#
if "prs" in self._current["hwi"].keys():
tmp = []
for prs in self._current["hwi"]["prs"]:
url = self.sound_url(prs)
how = prs["mw"]
if url:
tmp.append(f'<a href="{url}">\\{how}\\</a>')
else:
tmp.append(f"\\{how}\\")
html += '<span class="def-phonetic">'
html += '</span><span="def-phonetic">'.join(tmp)
html += "</span>\n"
#
# If there are inflections, create a header for that.
#
if "ins" in self._current.keys():
html += '<h2 class="def-word">'
html += ", ".join([ins["if"] for ins in self._current["ins"]])
html += "</h2>\n"
#
# Start creating the definition section
#
html += "<ul class='def-outer'>\n"
for meaning in self._current["def"]:
html += f"<li>{meaning['vd']}\n"
html += '<ul class="def-inner">\n'
label = ""
for sseq in meaning["sseq"]:
for sense in sseq:
label = parse_sn(sense[1]["sn"], label)
sls = ""
if "sls" in sense[1].keys():
sls = ", ".join(sense[1]["sls"])
sls = f'<span class="def-sls">{sls}</span> '
for dt in sense[1]["dt"]:
if dt[0] == "text":
html += f'<li class="def-text"><span class="def-sn">{label}</span>{sls}{dt[1]}</li>\n'
elif dt[0] == "vis":
for vis in dt[1]:
html += (
f"<li class=\"def-vis\">{vis['t']}</li>\n"
)
else:
print(f"Do something with {dt[0]}")
html += "</ul>\n"
html += "</ul>\n"
return html
def apidictionary_html(self) -> str:
html = ""
return html
class Definition(QWidget):
pronounce = pyqtSignal(str)
_word: str
_lines: list[Word.Line]
_buttons: list[QRect]
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
super(Definition, self).__init__(*args, **kwargs)
self._word = w.getCurrent()
lines = w.get_def()
assert lines is not None
self._lines = lines
self._buttons = []
assert self._lines is not None
#self.setFixedWidth(600)
base = 0
for line in self._lines:
line.finalizeLine(self.width())
base += line.getLeading()
self.setFixedHeight(base)
return
def resizeEvent(self, event: QResizeEvent) -> None:
base = 0
for line in self._lines:
line.finalizeLine(event.size().width())
base += line.getLeading()
self.setFixedHeight(base)
super(Definition,self).resizeEvent(event)
return
_downRect: QRect | None = None
def mousePressEvent(self, event: Optional[QMouseEvent]) -> None:
if not event:
return super().mousePressEvent(event)
for rect in self._buttons:
if rect.contains(event.pos()):
self._downRect = rect
return
return super().mousePressEvent(event)
def mouseReleaseEvent(self, event: Optional[QMouseEvent]) -> None:
if not event:
return super().mouseReleaseEvent(event)
if self._downRect is not None and self._downRect.contains(event.pos()):
self.pronounce.emit(
"https://media.merriam-webster.com/audio/prons/en/us/ogg/a/await001.ogg"
)
self._downRect = None
return
self._downRect = None
return super().mouseReleaseEvent(event)
def paintEvent(self, _: Optional[QPaintEvent]) -> None: # noqa
painter = QPainter(self)
painter.save()
painter.setBrush(QBrush())
painter.setPen(QColor("white"))
#
# Each line needs a base calculated. To do that, we need to find the
# bounding rectangle of the text. Once we have the bounding rectangle,
# we can use the descendant to calculate the baseline within that
# bounding box.
#
# All text on this line needs to be on the same baseline
#
assert self._lines is not None
base = 0
for line in self._lines:
transform = QTransform()
transform.translate(0, base)
painter.setTransform(transform)
for frag in line.getLine():
painter.save()
painter.setFont(frag.font())
painter.setPen(frag.color())
#
# Is this a button?
#
href = frag.audio()
if href:
radius = frag.borderRect().height()/2
painter.drawRoundedRect(frag.borderRect(), radius, radius)
#
# is it an anchor?
#
elif frag.wRef():
painter.drawLine(frag.borderRect().bottomLeft(), frag.borderRect().bottomRight())
painter.drawText(frag.position(), frag.text())
painter.restore()
painter.resetTransform()
base += line.getLeading()
painter.restore()
return
class DefinitionArea(QScrollArea):
def __init__(self, w: Word, *args: Any, **kwargs: Any) -> None:
super(DefinitionArea,self).__init__(*args,*kwargs)
d = Definition(w)
self.setWidget(d)
self.setWidgetResizable(True)
self.setVerticalScrollBarPolicy(
Qt.ScrollBarPolicy.ScrollBarAlwaysOn
)
return