mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-31 23:09:58 +08:00
add # 新增使用开源字体
This commit is contained in:
@@ -50,13 +50,18 @@ class Assets:
|
||||
else data.joinpath('assets.zip'))
|
||||
|
||||
def image(self, name, size=None):
|
||||
if name not in self.metadata["images"]:
|
||||
upgrade = name in ('soil/红土地.png', 'soil/黑土地.png', 'soil/金土地.png')
|
||||
if not upgrade and name not in self.metadata["images"]:
|
||||
raise KeyError("Unknown packaged image")
|
||||
size = tuple(size or self.metadata["images"][name]["size"])
|
||||
size = tuple(size or (self.metadata['soil_size'] if upgrade
|
||||
else self.metadata["images"][name]["size"]))
|
||||
key = (name, size)
|
||||
cached = self.cache.get(key)
|
||||
if cached is None:
|
||||
with self._bundle.open("rb") as stream, ZipFile(stream) as bundle:
|
||||
source = (resources.files(__package__).joinpath(
|
||||
'data', 'land-soils.zip',
|
||||
) if upgrade else self._bundle)
|
||||
with source.open("rb") as stream, ZipFile(stream) as bundle:
|
||||
content = bundle.read(name)
|
||||
with Image.open(BytesIO(content)) as original:
|
||||
original.thumbnail(size, Image.Resampling.LANCZOS)
|
||||
|
||||
@@ -5,4 +5,15 @@ assets.zip 原样复制自 Shu-Ying/zhenxun_plugin_farm 的本地保留基线,
|
||||
仅选取农场季节背景、草地/普通地、作物及两个状态图标;metadata.json
|
||||
来自旧布局和作物配置。沿用仓库 GPLv3 许可与原始素材归属,迁移不
|
||||
增加第三方素材权利。后续对外发布前仍须核实历史素材来源及分发权限。
|
||||
不包含字体;部署方配置可合法使用的中文字体路径。
|
||||
|
||||
## 小赖字体
|
||||
|
||||
默认字体由 farm_service 的 fonts/assets.zip 提供,插件不内置 TTF。
|
||||
Xiaolai-Regular.ttf 原样取自 lxgw/kose-font 的 v3.126 发布,
|
||||
项目地址:https://github.com/lxgw/kose-font 。
|
||||
下载地址:https://github.com/lxgw/kose-font/releases/download/v3.126/Xiaolai-Regular.ttf
|
||||
上游 SHA-256:e2f68daf0e72777a8cf58bc83de1b98634b251e537ddbfca24b0ae50d1802da2
|
||||
字体采用 SIL Open Font License 1.1,完整版权声明与许可见同目录
|
||||
OFL-Xiaolai.txt;字体不适用本项目代码的 GPLv3 许可。
|
||||
随软件分发时须保留字体的版权声明和完整许可,不得单独销售字体。
|
||||
插件缓存中同时保存原版字体和许可;下载摘要由 font-manifest.json 固定。
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
Copyright 2020-2024 LXGW (https://github.com/lxgw/kose-font)
|
||||
Copyright 2014 Nozomi Seto (https://ja.osdn.net/projects/setofont/)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"version": "xiaolai-v3.126",
|
||||
"sha256": "c2d1c1a5247e05b766d12a8d8410594d78b1a8b86031624a05b76ee0515c1335",
|
||||
"size_bytes": 14741741,
|
||||
"sources": {
|
||||
"github": "https://raw.githubusercontent.com/Shu-Ying/farm_service/main/fonts/assets.zip",
|
||||
"gitea": "https://gitea.diuse.work/Diuse/farm_service/raw/branch/main/fonts/assets.zip",
|
||||
"farm_service": "https://farm.example/v1/fonts/c2d1c1a5247e05b766d12a8d8410594d78b1a8b86031624a05b76ee0515c1335/assets.zip"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,106 @@
|
||||
"""从 service 仓库下载原版小赖字体,校验后原子写入本地缓存。"""
|
||||
|
||||
import hashlib
|
||||
from importlib import resources
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from zipfile import ZipFile
|
||||
|
||||
from .bundle import _LOCK, _copy_remote, validate_options
|
||||
|
||||
|
||||
FONT_NAME = "Xiaolai-Regular.ttf"
|
||||
FILES = {
|
||||
FONT_NAME: (
|
||||
22220806,
|
||||
"e2f68daf0e72777a8cf58bc83de1b98634b251e537ddbfca24b0ae50d1802da2",
|
||||
),
|
||||
"OFL-Xiaolai.txt": (
|
||||
4432,
|
||||
"0df7e09be4c2c850a48bd8beb9cd64b343aad49cd5d3f6cfb2ad2e3d28a56ca4",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def expected_manifest():
|
||||
return json.loads(resources.files(__package__).joinpath(
|
||||
"data", "font-manifest.json",
|
||||
).read_bytes())
|
||||
|
||||
|
||||
def _valid(path, size, digest):
|
||||
if not path.is_file() or path.stat().st_size != size:
|
||||
return False
|
||||
checksum = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(65536), b""):
|
||||
checksum.update(chunk)
|
||||
return checksum.hexdigest() == digest
|
||||
|
||||
|
||||
def _atomic_write(path, content):
|
||||
temporary = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
dir=path.parent, suffix=".download", delete=False,
|
||||
) as stream:
|
||||
temporary = Path(stream.name)
|
||||
stream.write(content)
|
||||
temporary.replace(path)
|
||||
finally:
|
||||
if temporary is not None:
|
||||
temporary.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def ensure_font(data_dir, source="Github", service_url=""):
|
||||
"""校验全部缓存文件;无效时下载,始终同时保留完整 OFL 许可。"""
|
||||
validate_options(source, service_url)
|
||||
manifest = expected_manifest()
|
||||
root = Path(data_dir) / "fonts" / manifest["sha256"]
|
||||
with _LOCK:
|
||||
if all(_valid(root / name, *checks)
|
||||
for name, checks in FILES.items()):
|
||||
return root / FONT_NAME
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
archive_path = root / "assets.zip"
|
||||
if not _valid(archive_path, manifest["size_bytes"],
|
||||
manifest["sha256"]):
|
||||
url = (
|
||||
service_url.rstrip("/") + "/v1/fonts/"
|
||||
+ manifest["sha256"] + "/assets.zip"
|
||||
if source == "farm_service"
|
||||
else manifest["sources"][source.lower()]
|
||||
)
|
||||
temporary = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
dir=root, suffix=".download", delete=False,
|
||||
) as stream:
|
||||
temporary = Path(stream.name)
|
||||
_copy_remote(url, stream, manifest["size_bytes"])
|
||||
if not _valid(temporary, manifest["size_bytes"],
|
||||
manifest["sha256"]):
|
||||
raise ValueError("Font archive size or SHA256 mismatch")
|
||||
temporary.replace(archive_path)
|
||||
finally:
|
||||
if temporary is not None:
|
||||
temporary.unlink(missing_ok=True)
|
||||
with ZipFile(archive_path) as archive:
|
||||
entries = archive.infolist()
|
||||
if (len(entries) != len(FILES)
|
||||
or {item.filename for item in entries} != set(FILES)):
|
||||
raise ValueError("Unexpected font archive contents")
|
||||
content = {}
|
||||
for item in entries:
|
||||
size, digest = FILES[item.filename]
|
||||
if item.file_size != size or item.flag_bits & 1:
|
||||
raise ValueError("Invalid font archive member")
|
||||
value = archive.read(item)
|
||||
if hashlib.sha256(value).hexdigest() != digest:
|
||||
raise ValueError("Font member SHA256 mismatch")
|
||||
content[item.filename] = value
|
||||
# 先写许可,再使字体可用;不使用 extractall 或远端提供的路径。
|
||||
for name in ("OFL-Xiaolai.txt", FONT_NAME):
|
||||
_atomic_write(root / name, content[name])
|
||||
return root / FONT_NAME
|
||||
@@ -1,9 +1,10 @@
|
||||
"""从一次快照与统一时间构造可缓存场景;不访问存储。"""
|
||||
|
||||
import hashlib
|
||||
from dataclasses import replace
|
||||
|
||||
from farm_core.domain import Identity, Snapshot, integer
|
||||
from farm_core.pests import advance_plot
|
||||
from farm_core.pests import advance_plot, season_duration
|
||||
|
||||
from .models import PlotView, Scene
|
||||
|
||||
@@ -53,8 +54,7 @@ def scene_from_snapshot(identity, snapshot, now, metadata):
|
||||
))
|
||||
continue
|
||||
phases = tuple(dict.fromkeys(int(p) for p in info["phase"].split(",")))
|
||||
duration = (plot.plant.growth_seconds if plot.harvest_count == 0
|
||||
else plot.plant.regrowth_seconds)
|
||||
duration = season_duration(plot)
|
||||
elapsed = max(0, now - (plot.mature_at - duration))
|
||||
work = (elapsed * 100 if plot.growth_remaining is None else
|
||||
duration * 100 - plot.growth_remaining)
|
||||
@@ -72,5 +72,10 @@ def scene_from_snapshot(identity, snapshot, now, metadata):
|
||||
pest_frames=frames,
|
||||
))
|
||||
return Scene(
|
||||
scope, snapshot.account, tuple(views),
|
||||
scope, snapshot.account, tuple(
|
||||
replace(view, land_level=plot.land_level)
|
||||
for view, plot in zip(views, sorted(
|
||||
snapshot.plots, key=lambda p: p.slot,
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
@@ -57,6 +57,7 @@ class PlotView:
|
||||
stage: int = 0 # 本季展示阶段。
|
||||
pest_count: int = 0 # 当前可见虫数;参与成图缓存键。
|
||||
pest_frames: tuple[str, ...] = () # 按虫序稳定选择的包内帧,参与缓存键。
|
||||
land_level: int = 0 # 土地等级;影响底图和成图缓存。
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -133,8 +133,9 @@ class FarmRenderer:
|
||||
)
|
||||
|
||||
def _base(self, scene, options):
|
||||
slots = frozenset(p.slot for p in scene.plots)
|
||||
key = ("base", options.theme, options.width, slots)
|
||||
slots = {p.slot: p.land_level for p in scene.plots}
|
||||
key = ("base", options.theme, options.width,
|
||||
tuple(sorted(slots.items())))
|
||||
cached = self.assets.cache.get(key)
|
||||
if cached is not None:
|
||||
return Canvas(cached.copy())
|
||||
@@ -149,14 +150,14 @@ class FarmRenderer:
|
||||
soil_size = tuple(max(1, s(n)) for n in (
|
||||
self.assets.metadata["soil_size"]
|
||||
))
|
||||
tiles = {
|
||||
True: self.assets.image("soil/普通土地.png", soil_size),
|
||||
False: self.assets.image("soil/草土地.png", soil_size),
|
||||
}
|
||||
names = ('普通土地', '红土地', '黑土地', '金土地')
|
||||
tiles = {level: self.assets.image(f'soil/{names[level]}.png', soil_size)
|
||||
for level in set(slots.values())}
|
||||
grass = self.assets.image('soil/草土地.png', soil_size)
|
||||
expansion = False
|
||||
for slot, pos in enumerate(self.assets.metadata["positions"], 1):
|
||||
xy = (s(pos["x"]), s(pos["y"]))
|
||||
canvas.paste(tiles[slot in slots], xy)
|
||||
canvas.paste(tiles[slots[slot]] if slot in slots else grass, xy)
|
||||
if slot not in slots and not expansion:
|
||||
icon = self.assets.image(
|
||||
"background/expansion.png", (max(1, s(90)),) * 2,
|
||||
|
||||
Reference in New Issue
Block a user