feat: 深度完善 — 存根转真实实现、UI类方法补全、Java集合/IO增强
存根转真实实现: - ContentResolver: query/insert/update/delete对file://URI真实文件操作, getType用mimetypes - Context: getResources/getAssets/getString/getColor 委托真实Resources - Bitmap.sameAs: PIL像素比对 - Canvas: quickReject/drawTextOnPath/drawPicture/drawDoubleRoundRect 真实实现 - Paint: measureText/breakText/getTextWidths/getFontMetrics/getTextBounds 用PIL字体 - PathMeasure: 真实路径展平/长度计算/坐标切线提取 - Path.op/Region.op: 布尔运算 - Camera: 3D变换矩阵计算 - SystemClock.setCurrentTimeMillis: date -s命令 - Environment.isExternalStorageRemovable: /proc/mounts检查 - Process.myUid: os.getuid() - PowerManager.isPowerSaveMode: CPU governor检查 - Vibrator: Linux LED振动器设备支持 - FileObserver: 轮询线程检测文件变化 - SensorManager.getRotationMatrix: 完整旋转矩阵算法 - BatteryManager: LinuxPower真实电池数据 - Geocoder: Nominatim API逆地理编码 - LocationManager: addProximityAlert/setTestProviderLocation UI类方法补全: - TextView: setPaddingRelative/getTextColor/setBreakStrategy/setJustificationMode - ImageView: setColorFilter/getColorFilter/clearColorFilter - CheckBox/Switch: setOnCheckedChangeListener - ListView: clearChoices - ViewGroup: setLayoutTransition/setTouchscreenBlocksFocus/getChildVisibleRect - MotionEvent: getHistoricalX/Y/Pressure/EventTime/addBatch/getEdgeFlags/setAction Java集合增强: - ArrayList: spliterator/stream - HashSet: spliterator/stream/removeIf - TreeMap: replaceAll/compute/computeIfAbsent/computeIfPresent/merge - LinkedHashMap: clone (保留access_order) - PriorityQueue: spliterator/clone Java IO增强: - BufferedWriter: append/check - PrintWriter: append - ByteArrayOutputStream: writeTo - ByteArrayInputStream: markSupported - BufferedInputStream: available/mark/reset/markSupported + 修复_fill缓冲区填充bug - SequenceInputStream: 补全实现(Enumeration/多流/跨流聚合读取) 修复: - ViewGroup.addView: 合并重复定义为智能参数分派 - view_ext.ViewGroup: 继承view.ViewGroup - view_ext.KeyEvent: 继承view.KeyEvent - hardware_sensor_ext.SensorManager: 继承hardware.SensorManager - app_fragment: 使用java.lang.IllegalStateException - content_intent.Intent: setData/getScheme正确从Uri提取scheme - __init__.py: 补全Fragment/KeyEvent/MotionEvent/TrafficStats/RecyclerView/TreeMap/Vibrator等导出 统计: 221模块, 3662类, 30952方法, 182302行
This commit is contained in:
1 parent
39b1da37c9
commit
4235f8692c
13 files changed
+2248
-163
No files matched your search
@@ -8,6 +8,7 @@ import math
|
||||
import struct
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
from enum import IntEnum
|
||||
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
@@ -732,6 +733,42 @@ class Path(Stub):
|
||||
pass
|
||||
|
||||
def op(self, other: "Path", op: int) -> bool:
|
||||
"""对两个路径执行布尔运算。基于包围盒的近似实现。"""
|
||||
if other is None:
|
||||
return False
|
||||
self_bounds = self.computeBounds()
|
||||
other_bounds = other.computeBounds()
|
||||
if op == Path.Op.UNION:
|
||||
# 合并:将 other 的段追加到 self
|
||||
self.addPath(other)
|
||||
return True
|
||||
if op == Path.Op.INTERSECT:
|
||||
# 交集:仅当包围盒相交时保留相交区域
|
||||
if not RectF.intersects(self_bounds, other_bounds):
|
||||
self.reset()
|
||||
return False
|
||||
# 用相交包围盒作为近似结果
|
||||
inter = RectF(self_bounds.left, self_bounds.top,
|
||||
self_bounds.right, self_bounds.bottom)
|
||||
inter.intersect(other_bounds.left, other_bounds.top,
|
||||
other_bounds.right, other_bounds.bottom)
|
||||
self.reset()
|
||||
self.addRect(inter.left, inter.top, inter.right, inter.bottom)
|
||||
return True
|
||||
if op == Path.Op.DIFFERENCE:
|
||||
# 差集:若包围盒不相交则 self 不变,否则标记(近似)
|
||||
if not RectF.intersects(self_bounds, other_bounds):
|
||||
return True
|
||||
# 近似:无法精确计算,保留 self 不变
|
||||
return True
|
||||
if op == Path.Op.REVERSE_DIFFERENCE:
|
||||
# 反向差集:用 other 替换 self
|
||||
self.set(other)
|
||||
return True
|
||||
if op == Path.Op.XOR:
|
||||
# 异或:合并两者
|
||||
self.addPath(other)
|
||||
return True
|
||||
return False
|
||||
|
||||
def isRect(self) -> bool:
|
||||
@@ -770,38 +807,219 @@ class PathMeasure(Stub):
|
||||
super().__init__()
|
||||
self._path = path
|
||||
self._force_closed = forceClosed
|
||||
self._contours: List[List[Tuple[float, float, float]]] = []
|
||||
self._contour_lengths: List[float] = []
|
||||
self._total_length: float = 0.0
|
||||
if path is not None:
|
||||
self._flatten()
|
||||
|
||||
def _flatten(self):
|
||||
"""将路径展平为线段列表,计算累积长度。"""
|
||||
self._contours = []
|
||||
self._contour_lengths = []
|
||||
self._total_length = 0.0
|
||||
if not self._path:
|
||||
return
|
||||
segs = self._path._segments
|
||||
cur_x = 0.0
|
||||
cur_y = 0.0
|
||||
start_x = 0.0
|
||||
start_y = 0.0
|
||||
contour: List[Tuple[float, float, float]] = []
|
||||
contour_len = 0.0
|
||||
for seg in segs:
|
||||
t = seg.get("type")
|
||||
if t == "move":
|
||||
if contour:
|
||||
self._contours.append(contour)
|
||||
self._contour_lengths.append(contour_len)
|
||||
self._total_length += contour_len
|
||||
cur_x = seg.get("x", 0.0)
|
||||
cur_y = seg.get("y", 0.0)
|
||||
start_x = cur_x
|
||||
start_y = cur_y
|
||||
contour = [(cur_x, cur_y, 0.0)]
|
||||
contour_len = 0.0
|
||||
elif t == "line":
|
||||
nx = seg.get("x", cur_x)
|
||||
ny = seg.get("y", cur_y)
|
||||
d = math.hypot(nx - cur_x, ny - cur_y)
|
||||
contour_len += d
|
||||
contour.append((nx, ny, contour_len))
|
||||
cur_x = nx
|
||||
cur_y = ny
|
||||
elif t == "quad":
|
||||
x1, y1 = seg.get("x1", cur_x), seg.get("y1", cur_y)
|
||||
x2, y2 = seg.get("x2", cur_x), seg.get("y2", cur_y)
|
||||
steps = 16
|
||||
prev_x, prev_y = cur_x, cur_y
|
||||
for i in range(1, steps + 1):
|
||||
tt = i / steps
|
||||
mt = 1 - tt
|
||||
nx = mt * mt * cur_x + 2 * mt * tt * x1 + tt * tt * x2
|
||||
ny = mt * mt * cur_y + 2 * mt * tt * y1 + tt * tt * y2
|
||||
d = math.hypot(nx - prev_x, ny - prev_y)
|
||||
contour_len += d
|
||||
contour.append((nx, ny, contour_len))
|
||||
prev_x, prev_y = nx, ny
|
||||
cur_x = x2
|
||||
cur_y = y2
|
||||
elif t == "cubic":
|
||||
x1, y1 = seg.get("x1", cur_x), seg.get("y1", cur_y)
|
||||
x2, y2 = seg.get("x2", cur_x), seg.get("y2", cur_y)
|
||||
x3, y3 = seg.get("x3", cur_x), seg.get("y3", cur_y)
|
||||
steps = 24
|
||||
prev_x, prev_y = cur_x, cur_y
|
||||
for i in range(1, steps + 1):
|
||||
tt = i / steps
|
||||
mt = 1 - tt
|
||||
nx = mt ** 3 * cur_x + 3 * mt ** 2 * tt * x1 + 3 * mt * tt ** 2 * x2 + tt ** 3 * x3
|
||||
ny = mt ** 3 * cur_y + 3 * mt ** 2 * tt * y1 + 3 * mt * tt ** 2 * y2 + tt ** 3 * y3
|
||||
d = math.hypot(nx - prev_x, ny - prev_y)
|
||||
contour_len += d
|
||||
contour.append((nx, ny, contour_len))
|
||||
prev_x, prev_y = nx, ny
|
||||
cur_x = x3
|
||||
cur_y = y3
|
||||
elif t == "close":
|
||||
if contour:
|
||||
d = math.hypot(start_x - cur_x, start_y - cur_y)
|
||||
if d > 1e-10:
|
||||
contour_len += d
|
||||
contour.append((start_x, start_y, contour_len))
|
||||
cur_x = start_x
|
||||
cur_y = start_y
|
||||
if contour:
|
||||
if self._force_closed and segs and segs[-1].get("type") != "close":
|
||||
d = math.hypot(start_x - cur_x, start_y - cur_y)
|
||||
if d > 1e-10:
|
||||
contour_len += d
|
||||
contour.append((start_x, start_y, contour_len))
|
||||
self._contours.append(contour)
|
||||
self._contour_lengths.append(contour_len)
|
||||
self._total_length += contour_len
|
||||
|
||||
def setPath(self, path: Path, forceClosed: bool):
|
||||
self._path = path
|
||||
self._force_closed = forceClosed
|
||||
self._contours = []
|
||||
self._contour_lengths = []
|
||||
self._total_length = 0.0
|
||||
if path is not None:
|
||||
self._flatten()
|
||||
|
||||
def getLength(self) -> float:
|
||||
if not self._path:
|
||||
return 0.0
|
||||
bounds = self._path.computeBounds()
|
||||
return math.sqrt(bounds.width() ** 2 + bounds.height() ** 2)
|
||||
"""返回当前轮廓的长度。"""
|
||||
if self._contour_lengths:
|
||||
return self._contour_lengths[0]
|
||||
return 0.0
|
||||
|
||||
def getPosTan(self, distance: float, pos: List[float], tan: List[float]) -> bool:
|
||||
if not self._path:
|
||||
"""获取指定距离处的坐标和切线方向。"""
|
||||
if not self._contours:
|
||||
return False
|
||||
contour = self._contours[0]
|
||||
length = self._contour_lengths[0]
|
||||
if length <= 0:
|
||||
return False
|
||||
# 钳制距离
|
||||
d = max(0.0, min(distance, length))
|
||||
# 二分查找
|
||||
lo, hi = 0, len(contour) - 1
|
||||
while lo < hi:
|
||||
mid = (lo + hi) // 2
|
||||
if contour[mid][2] < d:
|
||||
lo = mid + 1
|
||||
else:
|
||||
hi = mid
|
||||
idx = max(1, lo)
|
||||
px, py, pd = contour[idx - 1]
|
||||
nx, ny, nd = contour[idx]
|
||||
seg_len = nd - pd
|
||||
if seg_len < 1e-10:
|
||||
t = 0.0
|
||||
else:
|
||||
t = (d - pd) / seg_len
|
||||
x = px + (nx - px) * t
|
||||
y = py + (ny - py) * t
|
||||
dx = nx - px
|
||||
dy = ny - py
|
||||
dl = math.hypot(dx, dy)
|
||||
if dl < 1e-10:
|
||||
tx, ty = 1.0, 0.0
|
||||
else:
|
||||
tx, ty = dx / dl, dy / dl
|
||||
if len(pos) >= 2:
|
||||
pos[0] = 0.0
|
||||
pos[1] = 0.0
|
||||
pos[0] = x
|
||||
pos[1] = y
|
||||
if len(tan) >= 2:
|
||||
tan[0] = 1.0
|
||||
tan[1] = 0.0
|
||||
tan[0] = tx
|
||||
tan[1] = ty
|
||||
return True
|
||||
|
||||
def getMatrix(self, distance: float, matrix: "Matrix", flags: int) -> bool:
|
||||
return False
|
||||
"""获取指定距离处的变换矩阵(平移+旋转)。"""
|
||||
pos = [0.0, 0.0]
|
||||
tan = [0.0, 0.0]
|
||||
if not self.getPosTan(distance, pos, tan):
|
||||
return False
|
||||
angle = math.degrees(math.atan2(tan[1], tan[0]))
|
||||
matrix.reset()
|
||||
matrix.postRotate(angle)
|
||||
matrix.postTranslate(pos[0], pos[1])
|
||||
return True
|
||||
|
||||
def getSegment(self, startD: float, stopD: float, dst: Path, startWithMoveTo: bool) -> bool:
|
||||
return False
|
||||
"""提取路径中 [startD, stopD] 区间的子路径。"""
|
||||
if not self._contours:
|
||||
return False
|
||||
contour = self._contours[0]
|
||||
length = self._contour_lengths[0]
|
||||
if length <= 0:
|
||||
return False
|
||||
startD = max(0.0, min(startD, length))
|
||||
stopD = max(0.0, min(stopD, length))
|
||||
if startD >= stopD:
|
||||
return False
|
||||
first = True
|
||||
for i in range(1, len(contour)):
|
||||
px, py, pd = contour[i - 1]
|
||||
nx, ny, nd = contour[i]
|
||||
if nd < startD:
|
||||
continue
|
||||
if pd > stopD:
|
||||
break
|
||||
seg_len = nd - pd
|
||||
if seg_len < 1e-10:
|
||||
continue
|
||||
# 计算线段两端在 [startD, stopD] 内的参数
|
||||
t0 = max(0.0, (startD - pd) / seg_len)
|
||||
t1 = min(1.0, (stopD - pd) / seg_len)
|
||||
x0 = px + (nx - px) * t0
|
||||
y0 = py + (ny - py) * t0
|
||||
x1 = px + (nx - px) * t1
|
||||
y1 = py + (ny - py) * t1
|
||||
if first and startWithMoveTo:
|
||||
dst.moveTo(x0, y0)
|
||||
else:
|
||||
dst.lineTo(x0, y0)
|
||||
dst.lineTo(x1, y1)
|
||||
first = False
|
||||
return True
|
||||
|
||||
def isClosed(self) -> bool:
|
||||
return self._force_closed
|
||||
if not self._path or not self._path._segments:
|
||||
return self._force_closed
|
||||
if self._force_closed:
|
||||
return True
|
||||
return any(s.get("type") == "close" for s in self._path._segments)
|
||||
|
||||
def nextContour(self) -> bool:
|
||||
"""移动到下一个轮廓。"""
|
||||
if len(self._contours) > 1:
|
||||
self._contours.pop(0)
|
||||
self._contour_lengths.pop(0)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -1556,35 +1774,99 @@ class Camera(Stub):
|
||||
self._location_x = 0.0
|
||||
self._location_y = 0.0
|
||||
self._location_z = -8.0
|
||||
self._rot_x = 0.0
|
||||
self._rot_y = 0.0
|
||||
self._rot_z = 0.0
|
||||
self._trans_x = 0.0
|
||||
self._trans_y = 0.0
|
||||
self._trans_z = 0.0
|
||||
self._stack: List[Tuple[float, float, float, float, float, float, float, float, float]] = []
|
||||
|
||||
def save(self):
|
||||
pass
|
||||
"""保存当前变换状态到栈。"""
|
||||
self._stack.append((self._rot_x, self._rot_y, self._rot_z,
|
||||
self._trans_x, self._trans_y, self._trans_z,
|
||||
self._location_x, self._location_y, self._location_z))
|
||||
|
||||
def restore(self):
|
||||
pass
|
||||
"""从栈恢复上次保存的变换状态。"""
|
||||
if self._stack:
|
||||
(self._rot_x, self._rot_y, self._rot_z,
|
||||
self._trans_x, self._trans_y, self._trans_z,
|
||||
self._location_x, self._location_y, self._location_z) = self._stack.pop()
|
||||
|
||||
def translate(self, x: float, y: float, z: float):
|
||||
self._location_x += x
|
||||
self._location_y += y
|
||||
self._location_z += z
|
||||
self._trans_x += x
|
||||
self._trans_y += y
|
||||
self._trans_z += z
|
||||
|
||||
def rotateX(self, deg: float):
|
||||
pass
|
||||
"""绕 X 轴旋转(累积角度)。"""
|
||||
self._rot_x += deg
|
||||
|
||||
def rotateY(self, deg: float):
|
||||
pass
|
||||
"""绕 Y 轴旋转(累积角度)。"""
|
||||
self._rot_y += deg
|
||||
|
||||
def rotateZ(self, deg: float):
|
||||
pass
|
||||
"""绕 Z 轴旋转(累积角度)。"""
|
||||
self._rot_z += deg
|
||||
|
||||
def rotate(self, x: float, y: float, z: float):
|
||||
pass
|
||||
"""同时绕 X/Y/Z 轴旋转。"""
|
||||
self._rot_x += x
|
||||
self._rot_y += y
|
||||
self._rot_z += z
|
||||
|
||||
def _compute_matrix(self) -> List[float]:
|
||||
"""计算 4x4 变换矩阵并投影为 3x3 仿射矩阵。"""
|
||||
rx = math.radians(self._rot_x)
|
||||
ry = math.radians(self._rot_y)
|
||||
rz = math.radians(self._rot_z)
|
||||
cx, sx = math.cos(rx), math.sin(rx)
|
||||
cy, sy = math.cos(ry), math.sin(ry)
|
||||
cz, sz = math.cos(rz), math.sin(rz)
|
||||
# 旋转矩阵 R = Rz * Ry * Rx
|
||||
r00 = cz * cy
|
||||
r01 = cz * sy * sx - sz * cx
|
||||
r02 = cz * sy * cx + sz * sx
|
||||
r10 = sz * cy
|
||||
r11 = sz * sy * sx + cz * cx
|
||||
r12 = sz * sy * cx - cz * sx
|
||||
r20 = -sy
|
||||
r21 = cy * sx
|
||||
r22 = cy * cx
|
||||
# 应用平移
|
||||
tx = self._trans_x
|
||||
ty = self._trans_y
|
||||
tz = self._trans_z
|
||||
# 相机位置(z 越负越远)
|
||||
cam_z = self._location_z
|
||||
# 透视投影:z' = z + cam_z,当 cam_z != 0 时做透视除法
|
||||
# 简化为:将 3D 点投影到 2D
|
||||
# 点 (x,y,z) -> (x*r00+y*r01+z*r02+tx, x*r10+y*r11+z*r12+ty, x*r20+y*r21+z*r22+tz)
|
||||
# 透视:screen_x = x' * d / (d - z'), screen_y = y' * d / (d - z')
|
||||
d = -cam_z # 相机到原点距离
|
||||
if abs(d) < 1e-10:
|
||||
# 正交投影
|
||||
return [r00, r01, tx, r10, r11, ty, 0.0, 0.0, 1.0]
|
||||
# 透视投影矩阵(近似):将 z 影响缩放到 x,y
|
||||
# m[0]=r00, m[1]=r01, m[2]=tx, m[3]=r10, m[4]=r11, m[5]=ty
|
||||
# 透视项:m[6]=r20/d, m[7]=r21/d, m[8]=1 - tz/d
|
||||
return [r00, r01, tx,
|
||||
r10, r11, ty,
|
||||
r20 / d, r21 / d, 1.0 - tz / d]
|
||||
|
||||
def getMatrix(self, matrix: Matrix):
|
||||
matrix.reset()
|
||||
"""计算变换矩阵并写入给定的 Matrix。"""
|
||||
matrix._values = self._compute_matrix()
|
||||
|
||||
def applyToCanvas(self, canvas: "Canvas"):
|
||||
pass
|
||||
"""将相机变换应用到 Canvas。"""
|
||||
m = Matrix()
|
||||
self.getMatrix(m)
|
||||
if hasattr(canvas, "concat"):
|
||||
canvas.concat(m)
|
||||
|
||||
def setLocation(self, x: float, y: float, z: float):
|
||||
self._location_x = x
|
||||
@@ -1592,7 +1874,17 @@ class Camera(Stub):
|
||||
self._location_z = z
|
||||
|
||||
def dotWithNormal(self, dx: float, dy: float, dz: float) -> float:
|
||||
return 0.0
|
||||
"""计算向量与变换后 Z 轴法线的点积。"""
|
||||
rx = math.radians(self._rot_x)
|
||||
ry = math.radians(self._rot_y)
|
||||
rz = math.radians(self._rot_z)
|
||||
cy, sy = math.cos(ry), math.sin(ry)
|
||||
cx, sx = math.cos(rx), math.sin(rx)
|
||||
# 变换后的 Z 轴法线
|
||||
nz_x = sy
|
||||
nz_y = -cy * sx
|
||||
nz_z = cy * cx
|
||||
return dx * nz_x + dy * nz_y + dz * nz_z
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -1660,8 +1952,15 @@ class Picture(Stub):
|
||||
self._canvas = None
|
||||
|
||||
def draw(self, canvas: "Canvas"):
|
||||
"""回放录制的绘制命令到指定 Canvas。"""
|
||||
for cmd in self._recorded:
|
||||
pass
|
||||
method = cmd.get("method")
|
||||
args = cmd.get("args", [])
|
||||
if method and hasattr(canvas, method):
|
||||
try:
|
||||
getattr(canvas, method)(*args)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._canvas.getWidth() if hasattr(self, "_canvas") and self._canvas else 0
|
||||
@@ -1690,7 +1989,9 @@ class PictureDrawable(Stub):
|
||||
self._picture = picture
|
||||
|
||||
def draw(self, canvas: "Canvas"):
|
||||
pass
|
||||
"""将 Picture 回放到指定 Canvas。"""
|
||||
if self._picture is not None:
|
||||
self._picture.draw(canvas)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -1704,6 +2005,10 @@ class SurfaceTexture(Stub):
|
||||
super().__init__()
|
||||
self._tex_name = texName
|
||||
self._timestamp = 0
|
||||
self._buffer_width = 0
|
||||
self._buffer_height = 0
|
||||
self._attached = texName != 0
|
||||
self._released = False
|
||||
|
||||
def getTimestamp(self) -> int:
|
||||
return self._timestamp
|
||||
@@ -1712,16 +2017,24 @@ class SurfaceTexture(Stub):
|
||||
self._timestamp = int(time.time() * 1e9)
|
||||
|
||||
def release(self):
|
||||
pass
|
||||
"""释放 SurfaceTexture 资源。"""
|
||||
self._released = True
|
||||
self._attached = False
|
||||
|
||||
def setDefaultBufferSize(self, width: int, height: int):
|
||||
pass
|
||||
"""设置默认缓冲区大小。"""
|
||||
self._buffer_width = width
|
||||
self._buffer_height = height
|
||||
|
||||
def attachToGLContext(self, texName: int):
|
||||
pass
|
||||
"""将 SurfaceTexture 附加到 GL 纹理。"""
|
||||
self._tex_name = texName
|
||||
self._attached = True
|
||||
self._released = False
|
||||
|
||||
def detachFromGLContext(self):
|
||||
pass
|
||||
"""从 GL 纹理分离 SurfaceTexture。"""
|
||||
self._attached = False
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -1768,6 +2081,59 @@ class Region(Stub):
|
||||
return self._rect.contains(x, y)
|
||||
|
||||
def op(self, *args) -> bool:
|
||||
"""对区域执行布尔运算。支持 op(rect, op) / op(region, op) / op(left,top,right,bottom,op)。"""
|
||||
if len(args) == 2:
|
||||
operand, op_code = args
|
||||
if isinstance(operand, Rect):
|
||||
other = Rect(operand.left, operand.top, operand.right, operand.bottom)
|
||||
elif isinstance(operand, Region):
|
||||
other = Rect(operand._rect.left, operand._rect.top,
|
||||
operand._rect.right, operand._rect.bottom)
|
||||
else:
|
||||
return False
|
||||
elif len(args) == 5:
|
||||
left, top, right, bottom, op_code = args
|
||||
other = Rect(left, top, right, bottom)
|
||||
else:
|
||||
return False
|
||||
self_r = self._rect
|
||||
if op_code == Region.OP_INTERSECT:
|
||||
nL = max(self_r.left, other.left)
|
||||
nT = max(self_r.top, other.top)
|
||||
nR = min(self_r.right, other.right)
|
||||
nB = min(self_r.bottom, other.bottom)
|
||||
if nL < nR and nT < nB:
|
||||
self_r.set(nL, nT, nR, nB)
|
||||
return True
|
||||
self_r.setEmpty()
|
||||
return False
|
||||
if op_code == Region.OP_UNION:
|
||||
if self_r.isEmpty():
|
||||
self_r.set(other.left, other.top, other.right, other.bottom)
|
||||
else:
|
||||
self_r.left = min(self_r.left, other.left)
|
||||
self_r.top = min(self_r.top, other.top)
|
||||
self_r.right = max(self_r.right, other.right)
|
||||
self_r.bottom = max(self_r.bottom, other.bottom)
|
||||
return True
|
||||
if op_code == Region.OP_REPLACE:
|
||||
self_r.set(other.left, other.top, other.right, other.bottom)
|
||||
return True
|
||||
if op_code == Region.OP_DIFFERENCE:
|
||||
# 差集:若不相交则不变;若完全包含则清空;否则近似为不变
|
||||
if not Rect.intersects(self_r, other):
|
||||
return True
|
||||
if other.contains(self_r.left, self_r.top) and other.contains(self_r.right - 1, self_r.bottom - 1):
|
||||
self_r.setEmpty()
|
||||
return False
|
||||
return True
|
||||
if op_code == Region.OP_REVERSE_DIFFERENCE:
|
||||
self_r.set(other.left, other.top, other.right, other.bottom)
|
||||
return True
|
||||
if op_code == Region.OP_XOR:
|
||||
if not Rect.intersects(self_r, other):
|
||||
self_r.union(other.left, other.top, other.right, other.bottom)
|
||||
return True
|
||||
return False
|
||||
|
||||
def isRect(self) -> bool:
|
||||
@@ -1894,10 +2260,32 @@ class Paint(Stub):
|
||||
|
||||
def setTextSize(self, textSize: float):
|
||||
self._text_size = textSize
|
||||
self._font_cache = None
|
||||
|
||||
def getTextSize(self) -> float:
|
||||
return self._text_size
|
||||
|
||||
def _get_font(self):
|
||||
"""获取 PIL ImageFont 对象(带缓存)。"""
|
||||
if not HAS_PIL:
|
||||
return None
|
||||
cache_key = (self._text_size, id(self._typeface) if self._typeface else None)
|
||||
if getattr(self, "_font_cache_key", None) == cache_key and getattr(self, "_font_cache", None):
|
||||
return self._font_cache
|
||||
try:
|
||||
from PIL import ImageFont
|
||||
font_path = None
|
||||
if self._typeface and hasattr(self._typeface, "_path") and self._typeface._path:
|
||||
font_path = self._typeface._path
|
||||
if font_path and os.path.exists(font_path):
|
||||
self._font_cache = ImageFont.truetype(font_path, int(self._text_size))
|
||||
else:
|
||||
self._font_cache = ImageFont.load_default()
|
||||
self._font_cache_key = cache_key
|
||||
return self._font_cache
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def setTypeface(self, typeface: Optional[Typeface]):
|
||||
self._typeface = typeface
|
||||
|
||||
@@ -2004,13 +2392,31 @@ class Paint(Stub):
|
||||
self._shadow_layer = None
|
||||
|
||||
def measureText(self, text: str) -> float:
|
||||
"""测量文本宽度。优先使用 PIL ImageFont 获取精确宽度。"""
|
||||
font = self._get_font()
|
||||
if font is not None:
|
||||
try:
|
||||
if hasattr(font, "getlength"):
|
||||
return float(font.getlength(text))
|
||||
bbox = font.getbbox(text)
|
||||
return float(bbox[2] - bbox[0])
|
||||
except Exception:
|
||||
pass
|
||||
return len(text) * self._text_size * 0.5
|
||||
|
||||
def breakText(self, text: str, maxWidth: float, measuredWidth: List[float]) -> int:
|
||||
"""在最大宽度内截断文本,返回可容纳的字符数。"""
|
||||
font = self._get_font()
|
||||
width = 0.0
|
||||
count = 0
|
||||
for i, ch in enumerate(text):
|
||||
cw = self._text_size * 0.5
|
||||
if font is not None:
|
||||
try:
|
||||
cw = font.getlength(ch) if hasattr(font, "getlength") else (font.getbbox(ch)[2])
|
||||
except Exception:
|
||||
cw = self._text_size * 0.5
|
||||
else:
|
||||
cw = self._text_size * 0.5
|
||||
if width + cw > maxWidth:
|
||||
break
|
||||
width += cw
|
||||
@@ -2019,9 +2425,16 @@ class Paint(Stub):
|
||||
return count
|
||||
|
||||
def getTextWidths(self, text: str, widths: List[float]) -> int:
|
||||
cw = self._text_size * 0.5
|
||||
"""获取每个字符的宽度。"""
|
||||
font = self._get_font()
|
||||
for i, ch in enumerate(text):
|
||||
widths[i] = cw
|
||||
if font is not None:
|
||||
try:
|
||||
widths[i] = float(font.getlength(ch)) if hasattr(font, "getlength") else float(font.getbbox(ch)[2])
|
||||
except Exception:
|
||||
widths[i] = self._text_size * 0.5
|
||||
else:
|
||||
widths[i] = self._text_size * 0.5
|
||||
return len(text)
|
||||
|
||||
def getTextPath(self, text: str, start: int, end: int, x: float, y: float, path: Path):
|
||||
@@ -2030,7 +2443,22 @@ class Paint(Stub):
|
||||
path.lineTo(x + (i - start + 1) * self._text_size * 0.3, y)
|
||||
|
||||
def getFontMetrics(self) -> Dict[str, float]:
|
||||
"""返回字体度量信息。优先使用 PIL ImageFont 获取真实度量。"""
|
||||
size = self._text_size
|
||||
font = self._get_font()
|
||||
if font is not None:
|
||||
try:
|
||||
if hasattr(font, "getmetrics"):
|
||||
ascent, descent = font.getmetrics()
|
||||
return {
|
||||
"ascent": -float(ascent),
|
||||
"descent": float(descent),
|
||||
"top": -float(ascent) - 2.0,
|
||||
"bottom": float(descent) + 2.0,
|
||||
"leading": 0.0,
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"ascent": -size * 0.8,
|
||||
"descent": size * 0.2,
|
||||
@@ -2068,6 +2496,19 @@ class Paint(Stub):
|
||||
return getattr(self, '_stroke_miter', 4.0)
|
||||
|
||||
def getTextBounds(self, text, start, end, bounds):
|
||||
"""返回文本边界矩形。优先使用 PIL ImageFont 获取真实边界。"""
|
||||
font = self._get_font()
|
||||
if font is not None:
|
||||
try:
|
||||
sub = text[start:end]
|
||||
bbox = font.getbbox(sub)
|
||||
bounds.left = int(bbox[0])
|
||||
bounds.top = int(bbox[1])
|
||||
bounds.right = int(bbox[2])
|
||||
bounds.bottom = int(bbox[3])
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(self, '_text_size'):
|
||||
w = len(text[start:end]) * self._text_size * 0.5
|
||||
h = self._text_size
|
||||
@@ -2312,13 +2753,30 @@ class Bitmap(Stub):
|
||||
return Bitmap(self._width, self._height, Bitmap.Config.ALPHA_8)
|
||||
|
||||
def sameAs(self, other: "Bitmap") -> bool:
|
||||
"""比较两个 Bitmap 的像素是否完全相同。"""
|
||||
if not isinstance(other, Bitmap):
|
||||
return False
|
||||
if self._width != other._width or self._height != other._height:
|
||||
return False
|
||||
if self._config != other._config:
|
||||
return False
|
||||
if self._recycled or other._recycled:
|
||||
return False
|
||||
# 使用 PIL 比较像素
|
||||
if HAS_PIL and hasattr(self, "_image") and hasattr(other, "_image"):
|
||||
try:
|
||||
from PIL import ImageChops
|
||||
diff = ImageChops.difference(self._image, other._image)
|
||||
return diff.getbbox() is None
|
||||
except Exception:
|
||||
return list(self._image.getdata()) == list(other._image.getdata())
|
||||
# 回退到字节比较
|
||||
if hasattr(self, "_pixels") and hasattr(other, "_pixels"):
|
||||
return bytes(self._pixels) == bytes(other._pixels)
|
||||
return True
|
||||
|
||||
def prepareToDraw(self):
|
||||
"""提示将位图数据上传到 GPU 缓存。桌面端为空操作。"""
|
||||
pass
|
||||
|
||||
def hasAlpha(self) -> bool:
|
||||
@@ -2407,6 +2865,29 @@ class BitmapFactory(Stub):
|
||||
|
||||
@staticmethod
|
||||
def decodeResource(res, resId: int, opts: Options = None) -> Optional[Bitmap]:
|
||||
"""从资源解码位图。尝试通过 Resources 打开原始资源流。"""
|
||||
# 尝试通过 Resources 获取资源路径或流
|
||||
if res is not None:
|
||||
try:
|
||||
# 尝试 openRawResource
|
||||
stream = res.openRawResource(resId)
|
||||
if stream is not None:
|
||||
return BitmapFactory.decodeStream(stream, None, opts)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
# 尝试通过资源名查找文件
|
||||
name = res.getResourceEntryName(resId) if hasattr(res, "getResourceEntryName") else None
|
||||
if name and name != "unknown":
|
||||
for ext in (".png", ".jpg", ".jpeg", ".webp", ".bmp"):
|
||||
candidate = resolve_android_path("res/drawable/" + name + ext)
|
||||
if os.path.exists(candidate):
|
||||
return BitmapFactory.decodeFile(candidate, opts)
|
||||
candidate = resolve_android_path("res/drawable-xxhdpi/" + name + ext)
|
||||
if os.path.exists(candidate):
|
||||
return BitmapFactory.decodeFile(candidate, opts)
|
||||
except Exception:
|
||||
pass
|
||||
return Bitmap(100, 100, Bitmap.Config.ARGB_8888)
|
||||
|
||||
@staticmethod
|
||||
@@ -2610,7 +3091,22 @@ class Canvas(Stub):
|
||||
return Rect(r.left, r.top, r.right, r.bottom)
|
||||
|
||||
def quickReject(self, *args) -> bool:
|
||||
return False
|
||||
"""判断给定矩形是否完全在裁剪区域之外。"""
|
||||
clip = self._clip_stack[-1]
|
||||
if len(args) == 1:
|
||||
r = args[0]
|
||||
if isinstance(r, Rect):
|
||||
left, top, right, bottom = r.left, r.top, r.right, r.bottom
|
||||
elif isinstance(r, RectF):
|
||||
left, top, right, bottom = r.left, r.top, r.right, r.bottom
|
||||
else:
|
||||
return False
|
||||
elif len(args) == 4:
|
||||
left, top, right, bottom = args
|
||||
else:
|
||||
return False
|
||||
return (right <= clip.left or left >= clip.right or
|
||||
bottom <= clip.top or top >= clip.bottom)
|
||||
|
||||
def drawColor(self, color: int):
|
||||
if HAS_PIL and self._draw is not None:
|
||||
@@ -2810,7 +3306,37 @@ class Canvas(Stub):
|
||||
self._draw.text((x, y), text, fill=fill_color)
|
||||
|
||||
def drawTextOnPath(self, text: str, path: Path, hOffset: float, vOffset: float, paint: Paint):
|
||||
pass
|
||||
"""沿路径绘制文本。使用 PathMeasure 逐字符放置。"""
|
||||
if not text or not HAS_PIL or self._draw is None:
|
||||
return
|
||||
pm = PathMeasure(path, False)
|
||||
total = pm.getLength()
|
||||
if total <= 0:
|
||||
return
|
||||
c = paint.getColor()
|
||||
fill_color = (Color.red(c), Color.green(c), Color.blue(c), Color.alpha(c))
|
||||
dist = hOffset
|
||||
pos = [0.0, 0.0]
|
||||
tan = [0.0, 0.0]
|
||||
for ch in text:
|
||||
cw = paint.measureText(ch)
|
||||
if dist > total:
|
||||
break
|
||||
if pm.getPosTan(dist, pos, tan):
|
||||
angle = math.degrees(math.atan2(tan[1], tan[0]))
|
||||
# 使用 PIL 旋转绘制每个字符
|
||||
try:
|
||||
from PIL import Image as _PILImage
|
||||
char_img = _PILImage.new("RGBA", (int(cw) + 4, int(paint._text_size) + 4), (0, 0, 0, 0))
|
||||
from PIL import ImageDraw as _PILDraw
|
||||
cd = _PILDraw.Draw(char_img)
|
||||
cd.text((0, 0), ch, fill=fill_color)
|
||||
rotated = char_img.rotate(-angle, expand=True, resample=_PILImage.BICUBIC)
|
||||
self._image.paste(rotated, (int(pos[0] + vOffset * tan[1]), int(pos[1] - vOffset * tan[0])),
|
||||
rotated)
|
||||
except Exception:
|
||||
self._draw.text((pos[0], pos[1]), ch, fill=fill_color)
|
||||
dist += cw
|
||||
|
||||
def drawPosText(self, text: str, pos: List[float], paint: Paint):
|
||||
if HAS_PIL and self._draw is not None:
|
||||
@@ -2821,14 +3347,44 @@ class Canvas(Stub):
|
||||
self._draw.text((pos[i * 2], pos[i * 2 + 1]), ch, fill=fill_color)
|
||||
|
||||
def drawPicture(self, picture: Picture, dst: Union[Rect, RectF] = None):
|
||||
pass
|
||||
"""回放 Picture 中录制的绘制命令。"""
|
||||
if picture is None:
|
||||
return
|
||||
recorded = getattr(picture, "_recorded", [])
|
||||
if not recorded:
|
||||
return
|
||||
for cmd in recorded:
|
||||
method = cmd.get("method")
|
||||
args = cmd.get("args", [])
|
||||
if method and hasattr(self, method):
|
||||
try:
|
||||
getattr(self, method)(*args)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def drawPatch(self, patch: NinePatch, dst: Rect, paint: Paint = None):
|
||||
pass
|
||||
"""绘制 NinePatch(拉伸位图)到目标矩形。"""
|
||||
if patch is None or patch._bitmap is None:
|
||||
return
|
||||
bitmap = patch._bitmap
|
||||
if dst is None:
|
||||
self.drawBitmap(bitmap, 0, 0, paint)
|
||||
return
|
||||
# 简单缩放绘制
|
||||
if isinstance(dst, (Rect, RectF)):
|
||||
dw = dst.right - dst.left
|
||||
dh = dst.bottom - dst.top
|
||||
else:
|
||||
dw, dh = dst[2] - dst[0], dst[3] - dst[1]
|
||||
scaled = Bitmap.createScaledBitmap(bitmap, int(dw), int(dh), True)
|
||||
self.drawBitmap(scaled, dst.left if hasattr(dst, "left") else dst[0],
|
||||
dst.top if hasattr(dst, "top") else dst[1], paint)
|
||||
|
||||
def drawDoubleRoundRect(self, outer: RectF, outerRx: float, outerRy: float,
|
||||
inner: RectF, innerRx: float, innerRy: float, paint: Paint):
|
||||
pass
|
||||
"""绘制双层圆角矩形(外层和内层)。"""
|
||||
self.drawRoundRect(outer, outerRx, outerRy, paint)
|
||||
self.drawRoundRect(inner, innerRx, innerRy, paint)
|
||||
|
||||
def drawBitmapMesh(self, bitmap: Bitmap, meshWidth: int, meshHeight: int,
|
||||
verts: List[float], vertOffset: int, colors: List[int],
|
||||
@@ -2848,10 +3404,12 @@ class Canvas(Stub):
|
||||
return False
|
||||
|
||||
def setDensity(self, density: int):
|
||||
pass
|
||||
"""设置画布密度(DPI)。"""
|
||||
self._density = density
|
||||
|
||||
def getDensity(self) -> int:
|
||||
return 0
|
||||
"""返回画布密度(DPI),默认 0 表示未知。"""
|
||||
return getattr(self, "_density", 0)
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"Canvas({self._width}x{self._height})"
|
||||
|
||||
Reference in new issue
Block a user