- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
779 lines
27 KiB
Python
779 lines
27 KiB
Python
"""
|
|
Android Graphics Text 组件 — TextRunShaper, LineBreaker, MeasuredText, PositionedGlyphs, TextLayout 等
|
|
方法签名完全对齐 Android SDK (android.graphics.text 包)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
|
|
|
|
from .._core import Stub, AndroidOnlyError
|
|
from ..java.lang import RuntimeException
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# PositionedGlyphs
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class PositionedGlyphs(Stub):
|
|
"""PositionedGlyphs — 定位字形"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._glyph_ids: List[int] = []
|
|
self._positions_x: List[float] = []
|
|
self._positions_y: List[float] = []
|
|
self._advances_x: List[float] = []
|
|
self._advances_y: List[float] = []
|
|
self._width: float = 0.0
|
|
self._height: float = 0.0
|
|
self._ascent: float = 0.0
|
|
self._descent: float = 0.0
|
|
self._glyph_count: int = 0
|
|
|
|
def getGlyphCount(self) -> int:
|
|
return self._glyph_count
|
|
|
|
def getGlyphId(self, index: int) -> int:
|
|
return self._glyph_ids[index] if 0 <= index < len(self._glyph_ids) else 0
|
|
|
|
def getGlyphX(self, index: int) -> float:
|
|
return self._positions_x[index] if 0 <= index < len(self._positions_x) else 0.0
|
|
|
|
def getGlyphY(self, index: int) -> float:
|
|
return self._positions_y[index] if 0 <= index < len(self._positions_y) else 0.0
|
|
|
|
def getGlyphAdvanceX(self, index: int) -> float:
|
|
return self._advances_x[index] if 0 <= index < len(self._advances_x) else 0.0
|
|
|
|
def getGlyphAdvanceY(self, index: int) -> float:
|
|
return self._advances_y[index] if 0 <= index < len(self._advances_y) else 0.0
|
|
|
|
def getWidth(self) -> float:
|
|
return self._width
|
|
|
|
def getHeight(self) -> float:
|
|
return self._height
|
|
|
|
def getAscent(self) -> float:
|
|
return self._ascent
|
|
|
|
def getDescent(self) -> float:
|
|
return self._descent
|
|
|
|
def addGlyph(self, glyph_id: int, x: float, y: float,
|
|
advance_x: float, advance_y: float):
|
|
self._glyph_ids.append(glyph_id)
|
|
self._positions_x.append(x)
|
|
self._positions_y.append(y)
|
|
self._advances_x.append(advance_x)
|
|
self._advances_y.append(advance_y)
|
|
self._glyph_count += 1
|
|
|
|
def setBounds(self, width: float, height: float):
|
|
self._width = width
|
|
self._height = height
|
|
|
|
def setMetrics(self, ascent: float, descent: float):
|
|
self._ascent = ascent
|
|
self._descent = descent
|
|
|
|
def clear(self):
|
|
self._glyph_ids.clear()
|
|
self._positions_x.clear()
|
|
self._positions_y.clear()
|
|
self._advances_x.clear()
|
|
self._advances_y.clear()
|
|
self._glyph_count = 0
|
|
self._width = 0.0
|
|
self._height = 0.0
|
|
self._ascent = 0.0
|
|
self._descent = 0.0
|
|
|
|
def toString(self) -> str:
|
|
return f"PositionedGlyphs(count={self._glyph_count}, w={self._width:.1f}, h={self._height:.1f})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TextRunShaper
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TextRunShaper(Stub):
|
|
"""TextRunShaper — 文本运行重塑器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
@staticmethod
|
|
def shapeTextRun(text: str, start: int, count: int, context_start: int,
|
|
context_count: int, x_offset: float, y_offset: float,
|
|
is_rtl: bool, paint: Any) -> PositionedGlyphs:
|
|
"""重塑文本运行"""
|
|
glyphs = PositionedGlyphs()
|
|
for i in range(count):
|
|
if start + i < len(text):
|
|
char_code = ord(text[start + i])
|
|
glyphs.addGlyph(char_code, x_offset + i * 12.0, y_offset, 12.0, 0.0)
|
|
glyphs.setBounds(count * 12.0, 16.0)
|
|
glyphs.setMetrics(-12.0, 3.0)
|
|
return glyphs
|
|
|
|
@staticmethod
|
|
def shapeTextRunWithFont(text: str, start: int, count: int,
|
|
context_start: int, context_count: int,
|
|
x_offset: float, y_offset: float,
|
|
is_rtl: bool, paint: Any,
|
|
font: Any) -> PositionedGlyphs:
|
|
"""使用指定字体重塑文本运行"""
|
|
return TextRunShaper.shapeTextRun(text, start, count, context_start,
|
|
context_count, x_offset, y_offset,
|
|
is_rtl, paint)
|
|
|
|
@staticmethod
|
|
def shapeTextRunOnPath(text: str, start: int, count: int,
|
|
path: Any, paint: Any) -> PositionedGlyphs:
|
|
"""沿路径重塑文本运行"""
|
|
glyphs = PositionedGlyphs()
|
|
for i in range(count):
|
|
if start + i < len(text):
|
|
char_code = ord(text[start + i])
|
|
glyphs.addGlyph(char_code, i * 12.0, 0.0, 12.0, 0.0)
|
|
glyphs.setBounds(count * 12.0, 16.0)
|
|
glyphs.setMetrics(-12.0, 3.0)
|
|
return glyphs
|
|
|
|
@staticmethod
|
|
def getGlyphBounds(glyphs: PositionedGlyphs, index: int) -> Tuple[float, float, float, float]:
|
|
"""获取字形边界"""
|
|
x = glyphs.getGlyphX(index)
|
|
y = glyphs.getGlyphY(index)
|
|
return (x, y, x + 12.0, y + 16.0)
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# LineBreaker
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class LineBreaker(Stub):
|
|
"""LineBreaker — 换行器"""
|
|
|
|
BREAK_STRATEGY_SIMPLE = 0
|
|
BREAK_STRATEGY_HIGH_QUALITY = 1
|
|
BREAK_STRATEGY_BALANCED = 2
|
|
|
|
HYPHENATION_FREQUENCY_NONE = 0
|
|
HYPHENATION_FREQUENCY_NORMAL = 1
|
|
HYPHENATION_FREQUENCY_FULL = 2
|
|
|
|
WORD_BREAK_NORMAL = 0
|
|
WORD_BREAK_BREAK_ALL = 1
|
|
WORD_BREAK_KEEP_ALL = 2
|
|
|
|
TAB_STRATEGY_NONE = 0
|
|
TAB_STRATEGY_ORIGIN = 1
|
|
TAB_STRATEGY_DESTINATION = 2
|
|
|
|
JUSTIFICATION_MODE_NONE = 0
|
|
JUSTIFICATION_MODE_INTER_WORD = 1
|
|
JUSTIFICATION_MODE_CHARACTER = 2
|
|
|
|
LINE_LIMIT_UNLIMITED = 0
|
|
|
|
class Result(Stub):
|
|
"""LineBreaker.Result — 换行结果"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._line_counts: int = 0
|
|
self._line_breaks: List[Tuple[int, float]] = []
|
|
self._line_widths: List[float] = []
|
|
self._line_ascent: List[float] = []
|
|
self._line_descent: List[float] = []
|
|
|
|
def getLineCount(self) -> int:
|
|
return self._line_counts
|
|
|
|
def getLineBreakOffset(self, line_index: int) -> int:
|
|
if 0 <= line_index < len(self._line_breaks):
|
|
return self._line_breaks[line_index][0]
|
|
return 0
|
|
|
|
def getLineWidth(self, line_index: int) -> float:
|
|
if 0 <= line_index < len(self._line_widths):
|
|
return self._line_widths[line_index]
|
|
return 0.0
|
|
|
|
def getLineAscent(self, line_index: int) -> float:
|
|
if 0 <= line_index < len(self._line_ascent):
|
|
return self._line_ascent[line_index]
|
|
return 0.0
|
|
|
|
def getLineDescent(self, line_index: int) -> float:
|
|
if 0 <= line_index < len(self._line_descent):
|
|
return self._line_descent[line_index]
|
|
return 0.0
|
|
|
|
def addLineBreak(self, offset: int, width: float, ascent: float, descent: float):
|
|
self._line_breaks.append((offset, width))
|
|
self._line_widths.append(width)
|
|
self._line_ascent.append(ascent)
|
|
self._line_descent.append(descent)
|
|
self._line_counts += 1
|
|
|
|
def toString(self) -> str:
|
|
return f"LineBreaker.Result(lines={self._line_counts})"
|
|
|
|
def __init__(self, strategy: int = BREAK_STRATEGY_SIMPLE,
|
|
hyphenation: int = HYPHENATION_FREQUENCY_NONE):
|
|
super().__init__()
|
|
self._strategy = strategy
|
|
self._hyphenation_frequency = hyphenation
|
|
self._justification_mode: int = LineBreaker.JUSTIFICATION_MODE_NONE
|
|
self._word_break: int = LineBreaker.WORD_BREAK_NORMAL
|
|
self._tab_strategy: int = LineBreaker.TAB_STRATEGY_NONE
|
|
self._line_limit: int = LineBreaker.LINE_LIMIT_UNLIMITED
|
|
|
|
def getStrategy(self) -> int:
|
|
return self._strategy
|
|
|
|
def setStrategy(self, strategy: int):
|
|
self._strategy = strategy
|
|
|
|
def getHyphenationFrequency(self) -> int:
|
|
return self._hyphenation_frequency
|
|
|
|
def setHyphenationFrequency(self, freq: int):
|
|
self._hyphenation_frequency = freq
|
|
|
|
def setJustificationMode(self, mode: int):
|
|
self._justification_mode = mode
|
|
|
|
def getJustificationMode(self) -> int:
|
|
return self._justification_mode
|
|
|
|
def setWordBreak(self, word_break: int):
|
|
self._word_break = word_break
|
|
|
|
def getWordBreak(self) -> int:
|
|
return self._word_break
|
|
|
|
def setTabStrategy(self, strategy: int):
|
|
self._tab_strategy = strategy
|
|
|
|
def getTabStrategy(self) -> int:
|
|
return self._tab_strategy
|
|
|
|
def setLineLimit(self, limit: int):
|
|
self._line_limit = limit
|
|
|
|
def getLineLimit(self) -> int:
|
|
return self._line_limit
|
|
|
|
def computeLineBreaks(self, text: str, width: float,
|
|
paint: Any = None) -> Result:
|
|
"""计算换行"""
|
|
result = LineBreaker.Result()
|
|
if not text:
|
|
return result
|
|
words = text.split(" ")
|
|
line_start = 0
|
|
line_width = 0.0
|
|
char_width = 12.0 # 粗略估计
|
|
for word in words:
|
|
word_len = len(word) * char_width
|
|
if word_len + line_width > width and len(text[line_start:text.index(word)]) > 0:
|
|
break_offset = text.index(word, line_start) if line_start > 0 else len(word)
|
|
result.addLineBreak(break_offset, line_width, -12.0, 3.0)
|
|
line_start = break_offset
|
|
line_width = word_len
|
|
else:
|
|
line_width += word_len + char_width
|
|
if line_start < len(text):
|
|
result.addLineBreak(len(text), line_width, -12.0, 3.0)
|
|
return result
|
|
|
|
def computeLineBreaksForMeasuredText(self, measured_text: MeasuredText,
|
|
width: float) -> Result:
|
|
"""对度量文本计算换行"""
|
|
return self.computeLineBreaks(measured_text.getText(), width)
|
|
|
|
def toString(self) -> str:
|
|
return f"LineBreaker(strategy={self._strategy}, hyphen={self._hyphenation_frequency})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# MeasuredText
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class MeasuredText(Stub):
|
|
"""MeasuredText — 度量文本"""
|
|
|
|
def __init__(self, text: str = ""):
|
|
super().__init__()
|
|
self._text = text
|
|
self._widths: List[float] = []
|
|
self._ascent: float = 0.0
|
|
self._descent: float = 0.0
|
|
self._height: float = 0.0
|
|
self._glyph_count: int = 0
|
|
self._char_spacing: float = 0.0
|
|
self._word_spacing: float = 0.0
|
|
self._scale_x: float = 1.0
|
|
self._advances: List[float] = []
|
|
self._clusters: List[int] = []
|
|
for c in text:
|
|
self._widths.append(12.0)
|
|
self._advances.append(12.0)
|
|
self._clusters.append(1)
|
|
|
|
def getText(self) -> str:
|
|
return self._text
|
|
|
|
def getWidth(self, start: int = 0, end: int = -1) -> float:
|
|
if end < 0:
|
|
end = len(self._widths)
|
|
return sum(self._widths[start:end])
|
|
|
|
def getAscent(self) -> float:
|
|
return self._ascent
|
|
|
|
def getDescent(self) -> float:
|
|
return self._descent
|
|
|
|
def getHeight(self) -> float:
|
|
return self._height
|
|
|
|
def getCharWidthAt(self, index: int) -> float:
|
|
if 0 <= index < len(self._widths):
|
|
return self._widths[index]
|
|
return 0.0
|
|
|
|
def setWidths(self, widths: List[float]):
|
|
self._widths = list(widths)
|
|
|
|
def setMetrics(self, ascent: float, descent: float):
|
|
self._ascent = ascent
|
|
self._descent = descent
|
|
self._height = ascent - descent
|
|
|
|
def getAdvance(self, index: int) -> float:
|
|
if 0 <= index < len(self._advances):
|
|
return self._advances[index]
|
|
return 0.0
|
|
|
|
def getCluster(self, index: int) -> int:
|
|
if 0 <= index < len(self._clusters):
|
|
return self._clusters[index]
|
|
return 1
|
|
|
|
def getGlyphCount(self) -> int:
|
|
return self._glyph_count
|
|
|
|
def setGlyphCount(self, count: int):
|
|
self._glyph_count = count
|
|
|
|
def getCharSpacing(self) -> float:
|
|
return self._char_spacing
|
|
|
|
def getWordSpacing(self) -> float:
|
|
return self._word_spacing
|
|
|
|
def getScaleX(self) -> float:
|
|
return self._scale_x
|
|
|
|
def toString(self) -> str:
|
|
return f"MeasuredText(text={self._text[:20]}, w={self.getWidth():.1f})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TextLayout
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TextLayout(Stub):
|
|
"""TextLayout — 文本布局"""
|
|
|
|
DIRECTION_LEFT_TO_RIGHT = 0
|
|
DIRECTION_RIGHT_TO_LEFT = 1
|
|
|
|
ALIGN_NORMAL = 0
|
|
ALIGN_CENTER = 1
|
|
ALIGN_OPPOSITE = 2
|
|
|
|
TEXT_DIRECTION_FIRST_STRONG_LTR = 0
|
|
TEXT_DIRECTION_FIRST_STRONG_RTL = 1
|
|
TEXT_DIRECTION_ANY_LTR = 2
|
|
TEXT_DIRECTION_ANY_RTL = 3
|
|
TEXT_DIRECTION_LTR = 4
|
|
TEXT_DIRECTION_RTL = 5
|
|
TEXT_DIRECTION_LOCALE = 6
|
|
TEXT_DIRECTION_FIRST_STRONG = 7
|
|
|
|
def __init__(self, text: str = "", width: float = 0.0, paint: Any = None):
|
|
super().__init__()
|
|
self._text = text
|
|
self._width = width
|
|
self._paint = paint
|
|
self._lines: List[TextLayout.LineInfo] = []
|
|
self._alignment: int = TextLayout.ALIGN_NORMAL
|
|
self._direction: int = TextLayout.DIRECTION_LEFT_TO_RIGHT
|
|
self._text_direction: int = TextLayout.TEXT_DIRECTION_FIRST_STRONG_LTR
|
|
self._max_lines: int = 0
|
|
self._include_padding: bool = True
|
|
self._spacing_mult: float = 1.0
|
|
self._spacing_add: float = 0.0
|
|
self._ellipsized_width: float = 0.0
|
|
self._is_ellipsized: bool = False
|
|
|
|
def getText(self) -> str:
|
|
return self._text
|
|
|
|
def getWidth(self) -> float:
|
|
return self._width
|
|
|
|
def getHeight(self) -> float:
|
|
if not self._lines:
|
|
return 0.0
|
|
return sum(l.getHeight() for l in self._lines)
|
|
|
|
def getLineCount(self) -> int:
|
|
return len(self._lines)
|
|
|
|
def getLineInfo(self, line_index: int) -> Optional[TextLayout.LineInfo]:
|
|
if 0 <= line_index < len(self._lines):
|
|
return self._lines[line_index]
|
|
return None
|
|
|
|
def getLineStart(self, line_index: int) -> int:
|
|
if 0 <= line_index < len(self._lines):
|
|
return self._lines[line_index].getStart()
|
|
return 0
|
|
|
|
def getLineEnd(self, line_index: int) -> int:
|
|
if 0 <= line_index < len(self._lines):
|
|
return self._lines[line_index].getEnd()
|
|
return 0
|
|
|
|
def getLineMax(self, line_index: int) -> float:
|
|
if 0 <= line_index < len(self._lines):
|
|
return self._lines[line_index].getMax()
|
|
return 0.0
|
|
|
|
def getLineAscent(self, line_index: int) -> float:
|
|
if 0 <= line_index < len(self._lines):
|
|
return self._lines[line_index].getAscent()
|
|
return 0.0
|
|
|
|
def getLineDescent(self, line_index: int) -> float:
|
|
if 0 <= line_index < len(self._lines):
|
|
return self._lines[line_index].getDescent()
|
|
return 0.0
|
|
|
|
def getMaxLines(self) -> int:
|
|
return self._max_lines
|
|
|
|
def setMaxLines(self, max_lines: int):
|
|
self._max_lines = max_lines
|
|
|
|
def getAlignment(self) -> int:
|
|
return self._alignment
|
|
|
|
def setAlignment(self, alignment: int):
|
|
self._alignment = alignment
|
|
|
|
def getDirection(self) -> int:
|
|
return self._direction
|
|
|
|
def setDirection(self, direction: int):
|
|
self._direction = direction
|
|
|
|
def getTextDirection(self) -> int:
|
|
return self._text_direction
|
|
|
|
def setTextDirection(self, direction: int):
|
|
self._text_direction = direction
|
|
|
|
def setIncludePadding(self, include: bool):
|
|
self._include_padding = include
|
|
|
|
def isIncludePadding(self) -> bool:
|
|
return self._include_padding
|
|
|
|
def setSpacingMultiplier(self, mult: float):
|
|
self._spacing_mult = mult
|
|
|
|
def getSpacingMultiplier(self) -> float:
|
|
return self._spacing_mult
|
|
|
|
def setSpacingAdd(self, add: float):
|
|
self._spacing_add = add
|
|
|
|
def getSpacingAdd(self) -> float:
|
|
return self._spacing_add
|
|
|
|
def isEllipsized(self) -> bool:
|
|
return self._is_ellipsized
|
|
|
|
def setEllipsizedWidth(self, width: float):
|
|
self._ellipsized_width = width
|
|
|
|
def getEllipsizedWidth(self) -> float:
|
|
return self._ellipsized_width
|
|
|
|
def addLine(self, start: int, end: int, max_width: float,
|
|
ascent: float, descent: float):
|
|
line = TextLayout.LineInfo(start, end, max_width, ascent, descent)
|
|
self._lines.append(line)
|
|
|
|
def clearLines(self):
|
|
self._lines.clear()
|
|
|
|
def buildSimpleLayout(self, char_width: float = 12.0, line_height: float = 16.0):
|
|
"""构建简单布局"""
|
|
self._lines.clear()
|
|
if not self._text:
|
|
return
|
|
chars_per_line = int(self._width / char_width) if char_width > 0 else 10
|
|
if chars_per_line <= 0:
|
|
chars_per_line = 10
|
|
start = 0
|
|
while start < len(self._text):
|
|
end = min(start + chars_per_line, len(self._text))
|
|
line_text = self._text[start:end]
|
|
line_width = len(line_text) * char_width
|
|
self.addLine(start, end, line_width, -12.0, 3.0)
|
|
start = end
|
|
|
|
def toString(self) -> str:
|
|
return f"TextLayout(lines={len(self._lines)}, w={self._width:.1f})"
|
|
|
|
# ═══════════════════════════════════════════════════════
|
|
# TextLayout.LineInfo
|
|
# ═══════════════════════════════════════════════════════
|
|
|
|
class LineInfo(Stub):
|
|
"""TextLayout.LineInfo — 行信息"""
|
|
|
|
def __init__(self, start: int = 0, end: int = 0,
|
|
max_width: float = 0.0, ascent: float = 0.0,
|
|
descent: float = 0.0):
|
|
super().__init__()
|
|
self._start = start
|
|
self._end = end
|
|
self._max_width = max_width
|
|
self._ascent = ascent
|
|
self._descent = descent
|
|
self._height = ascent - descent if descent > 0 else 16.0
|
|
self._direction: int = TextLayout.DIRECTION_LEFT_TO_RIGHT
|
|
self._is_ellipsized: bool = False
|
|
|
|
def getStart(self) -> int:
|
|
return self._start
|
|
|
|
def getEnd(self) -> int:
|
|
return self._end
|
|
|
|
def getMax(self) -> float:
|
|
return self._max_width
|
|
|
|
def getAscent(self) -> float:
|
|
return self._ascent
|
|
|
|
def getDescent(self) -> float:
|
|
return self._descent
|
|
|
|
def getHeight(self) -> float:
|
|
return self._height
|
|
|
|
def getDirection(self) -> int:
|
|
return self._direction
|
|
|
|
def setDirection(self, direction: int):
|
|
self._direction = direction
|
|
|
|
def isEllipsized(self) -> bool:
|
|
return self._is_ellipsized
|
|
|
|
def setEllipsized(self, ellipsized: bool):
|
|
self._is_ellipsized = ellipsized
|
|
|
|
def toString(self) -> str:
|
|
return f"LineInfo({self._start}-{self._end}, w={self._max_width:.1f})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# FontShaper
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class FontShaper(Stub):
|
|
"""FontShaper — 字体重塑器"""
|
|
|
|
def __init__(self, font: Any = None):
|
|
super().__init__()
|
|
self._font = font
|
|
self._variations: Dict[str, float] = {}
|
|
self._features: Dict[str, int] = {}
|
|
self._is_ready: bool = True
|
|
|
|
def getFont(self) -> Any:
|
|
return self._font
|
|
|
|
def setFont(self, font: Any):
|
|
self._font = font
|
|
|
|
def setVariation(self, tag: str, value: float):
|
|
self._variations[tag] = value
|
|
|
|
def getVariation(self, tag: str) -> Optional[float]:
|
|
return self._variations.get(tag)
|
|
|
|
def getVariations(self) -> Dict[str, float]:
|
|
return dict(self._variations)
|
|
|
|
def setFeature(self, tag: str, value: int):
|
|
self._features[tag] = value
|
|
|
|
def getFeature(self, tag: str) -> int:
|
|
return self._features.get(tag, 0)
|
|
|
|
def getFeatures(self) -> Dict[str, int]:
|
|
return dict(self._features)
|
|
|
|
def shapeText(self, text: str, paint: Any = None) -> PositionedGlyphs:
|
|
"""重塑文本"""
|
|
glyphs = PositionedGlyphs()
|
|
for i, c in enumerate(text):
|
|
glyphs.addGlyph(ord(c), i * 12.0, 0.0, 12.0, 0.0)
|
|
glyphs.setBounds(len(text) * 12.0, 16.0)
|
|
glyphs.setMetrics(-12.0, 3.0)
|
|
return glyphs
|
|
|
|
def shapeTextRun(self, text: str, start: int, count: int,
|
|
context_start: int, context_count: int,
|
|
is_rtl: bool, paint: Any) -> PositionedGlyphs:
|
|
"""重塑文本运行"""
|
|
glyphs = PositionedGlyphs()
|
|
for i in range(count):
|
|
idx = start + i
|
|
if idx < len(text):
|
|
glyphs.addGlyph(ord(text[idx]), i * 12.0, 0.0, 12.0, 0.0)
|
|
glyphs.setBounds(count * 12.0, 16.0)
|
|
glyphs.setMetrics(-12.0, 3.0)
|
|
return glyphs
|
|
|
|
def getGlyphMetrics(self, glyph_id: int) -> Tuple[float, float, float, float]:
|
|
"""获取字形度量"""
|
|
return (0.0, 0.0, 12.0, 16.0)
|
|
|
|
def isReady(self) -> bool:
|
|
return self._is_ready
|
|
|
|
def toString(self) -> str:
|
|
return f"FontShaper(variations={len(self._variations)})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TextRunShaperResult (helper)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TextRunShaperResult(Stub):
|
|
"""TextRunShaperResult — 文本运行重塑结果"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._glyphs: PositionedGlyphs = PositionedGlyphs()
|
|
self._char_offset: int = 0
|
|
self._char_count: int = 0
|
|
self._is_rtl: bool = False
|
|
self._font_size: float = 12.0
|
|
|
|
def getGlyphs(self) -> PositionedGlyphs:
|
|
return self._glyphs
|
|
|
|
def setGlyphs(self, glyphs: PositionedGlyphs):
|
|
self._glyphs = glyphs
|
|
|
|
def getCharOffset(self) -> int:
|
|
return self._char_offset
|
|
|
|
def setCharOffset(self, offset: int):
|
|
self._char_offset = offset
|
|
|
|
def getCharCount(self) -> int:
|
|
return self._char_count
|
|
|
|
def setCharCount(self, count: int):
|
|
self._char_count = count
|
|
|
|
def isRtl(self) -> bool:
|
|
return self._is_rtl
|
|
|
|
def setRtl(self, rtl: bool):
|
|
self._is_rtl = rtl
|
|
|
|
def toString(self) -> str:
|
|
return f"TextRunShaperResult(glyphs={self._glyphs.getGlyphCount()})"
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# TextShaperUtils
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class TextShaperUtils(Stub):
|
|
"""TextShaperUtils — 文本重塑工具类"""
|
|
|
|
@staticmethod
|
|
def estimateCharWidth(paint: Any = None) -> float:
|
|
return 12.0
|
|
|
|
@staticmethod
|
|
def estimateLineHeight(paint: Any = None) -> float:
|
|
return 16.0
|
|
|
|
@staticmethod
|
|
def measureText(text: str, paint: Any = None) -> float:
|
|
return len(text) * 12.0
|
|
|
|
@staticmethod
|
|
def wordWrap(text: str, max_width: float, char_width: float = 12.0) -> List[str]:
|
|
lines = []
|
|
chars_per_line = int(max_width / char_width) if char_width > 0 else 10
|
|
if chars_per_line <= 0:
|
|
chars_per_line = 10
|
|
words = text.split(" ")
|
|
current_line = ""
|
|
for word in words:
|
|
if len(current_line) + len(word) + 1 <= chars_per_line:
|
|
if current_line:
|
|
current_line += " "
|
|
current_line += word
|
|
else:
|
|
if current_line:
|
|
lines.append(current_line)
|
|
current_line = word
|
|
if current_line:
|
|
lines.append(current_line)
|
|
return lines
|
|
|
|
@staticmethod
|
|
def truncateText(text: str, max_width: float, char_width: float = 12.0) -> str:
|
|
max_chars = int(max_width / char_width)
|
|
if max_chars <= 0:
|
|
return ""
|
|
if len(text) <= max_chars:
|
|
return text
|
|
return text[:max_chars - 1] + "..."
|
|
|
|
@staticmethod
|
|
def isRtlCharacter(c: str) -> bool:
|
|
cp = ord(c)
|
|
return (0x0590 <= cp <= 0x05FF or 0x0600 <= cp <= 0x06FF or
|
|
0x0700 <= cp <= 0x074F or 0x07C0 <= cp <= 0x07FF or
|
|
0xFB1D <= cp <= 0xFDFF or 0xFE70 <= cp <= 0xFEFF)
|
|
|
|
@staticmethod
|
|
def getTextDirection(text: str) -> int:
|
|
for c in text[:100]:
|
|
if TextShaperUtils.isRtlCharacter(c):
|
|
return TextLayout.TEXT_DIRECTION_FIRST_STRONG_RTL
|
|
return TextLayout.TEXT_DIRECTION_FIRST_STRONG_LTR |