Add bundled bus fonts and shared font favorites
This commit is contained in:
@@ -16,6 +16,7 @@ from uuid import UUID, uuid4
|
||||
from app.animations.constants import DEFAULT_DURATION_MS, MAX_DURATION_MS, MIN_DURATION_MS
|
||||
from app.display.text_renderer import FontResolver
|
||||
from app.persistence import atomic_write_bytes
|
||||
from app.fonts.render_cache import cache_stale, stamp_cache, STAMP_SUFFIX, cached_versioned_url
|
||||
from app.templates.store import (
|
||||
TemplateConflictError, TemplateError, TemplateNotFoundError,
|
||||
TemplateStorageFullError, TemplateValidationError,
|
||||
@@ -166,11 +167,12 @@ class AnimationStore:
|
||||
def _write_thumbnail(self, animation_id: str, frame: dict[str, Any]) -> None:
|
||||
self._thumb_dir(animation_id).mkdir(parents=True, exist_ok=True)
|
||||
path = self._thumb_dir(animation_id) / frame["thumbnail"]
|
||||
if path.exists():
|
||||
return
|
||||
scene = frame.get("scene") or self._load_scene(animation_id, frame)
|
||||
if path.exists() and not cache_stale(path, scene, self._font_resolver):
|
||||
return
|
||||
try:
|
||||
atomic_write_bytes(path, render_scene_thumbnail(scene, font_resolver=self._font_resolver))
|
||||
stamp_cache(path, scene, self._font_resolver)
|
||||
except OSError as exc:
|
||||
self._translate(exc)
|
||||
|
||||
@@ -284,10 +286,10 @@ class AnimationStore:
|
||||
"updated_at": str(raw.get("updated_at") or ""),
|
||||
"digest": digest, "thumbnail": thumbnail, "scene_file": scene_file,
|
||||
}
|
||||
if self._load_scene(expected_id, frame)["version"] != 2:
|
||||
checked_scene = self._load_scene(expected_id, frame)
|
||||
if checked_scene["version"] != 2:
|
||||
raise TemplateValidationError("animation schema v3 requires scene v2")
|
||||
if not (self._thumb_dir(expected_id) / thumbnail).is_file():
|
||||
self._write_thumbnail(expected_id, frame)
|
||||
self._write_thumbnail(expected_id, {**frame, "scene": checked_scene})
|
||||
frames.append(frame)
|
||||
return {
|
||||
"id": expected_id, "name": _name(document.get("name")),
|
||||
@@ -315,6 +317,8 @@ class AnimationStore:
|
||||
for path in directory.iterdir():
|
||||
if path.is_file() and path.name not in expected and pattern.fullmatch(path.name):
|
||||
path.unlink(missing_ok=True)
|
||||
elif path.is_file() and path.name.endswith(STAMP_SUFFIX) and pattern.fullmatch(path.name[:-len(STAMP_SUFFIX)]) and path.name[:-len(STAMP_SUFFIX)] not in expected:
|
||||
path.unlink(missing_ok=True)
|
||||
|
||||
def _all(self) -> list[dict[str, Any]]:
|
||||
return [
|
||||
@@ -386,7 +390,8 @@ class AnimationStore:
|
||||
return all(r["name"].casefold() != checked.casefold() for r in self._all())
|
||||
|
||||
def thumbnail_url(self, animation_id: str, frame: dict[str, Any]) -> str:
|
||||
return f"/api/animations/{animation_id}/frames/{frame['id']}/thumbnail?v={frame['digest']}"
|
||||
return cached_versioned_url(f"/api/animations/{animation_id}/frames/{frame['id']}/thumbnail?v={frame['digest']}",
|
||||
self._thumb_dir(animation_id) / frame["thumbnail"])
|
||||
|
||||
def _metadata(self, record: dict[str, Any]) -> dict[str, Any]:
|
||||
revision = _revision(record)
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
[BoutiqueBitmap7x7]
|
||||
2020-2026《字言字型》Cen-cyun, Liu. Luke Liu.
|
||||
https://fontspeech.blogspot.com/
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
此字型是免費的。
|
||||
無論您是否進行對本字型進行商業或非商業性修改,均可無限制地使用,複製和分發它們。
|
||||
本字型的衍生品之授權必須與此字型相同,且不作任何擔保。
|
||||
[MisakiGothic]
|
||||
Copyright (C) 2002-2019 Num Kadoma
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
これらのフォントはフリー(自由な)ソフトウエアです。
|
||||
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
|
||||
[观致]
|
||||
这是日文字体Misaki基础上补充的
|
||||
|
||||
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:
|
||||
https://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,93 @@
|
||||
Copyright (c) 2019–2025 Lee Minseo (quiple@quiple.dev)
|
||||
|
||||
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:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
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,258 @@
|
||||
===============================================================================
|
||||
|
||||
8×8 ドット日本語フォント「美咲フォント」
|
||||
(2021-05-05 版)
|
||||
|
||||
Copyright(C) 2002-2021 Num Kadoma
|
||||
|
||||
===============================================================================
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
概要
|
||||
-------------------------------------------------------------------------------
|
||||
美咲フォントは 8×8 ドットの日本語ビットマップフォントです。
|
||||
JIS第一・第二水準をサポートしています。
|
||||
一部の記号を除いた全ての文字は 7×7 ドットの範囲に収まっているため、
|
||||
文字同士を隣接させても行間・字間が確保できます。
|
||||
|
||||
フォント名は制作のきっかけとなった美咲 礼威氏からいただきました。
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
バリエーション
|
||||
-------------------------------------------------------------------------------
|
||||
・「美咲明朝」は、美咲ゴシックの非漢字部を明朝風の字形に差し替えたものです。
|
||||
・「美咲ゴシック第2」は美咲ゴシックをベースに、半角文字は縦 7 px、
|
||||
全角仮名は横 7 px をいっぱいに使う字形に差し替えたものです。
|
||||
|
||||
各機種用のアーカイブに、差替え済みのフォントファイルを同梱してあります。
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
ライセンス
|
||||
-------------------------------------------------------------------------------
|
||||
These fonts are free softwares.
|
||||
Unlimited permission is granted to use, copy, and distribute it, with or without modification, either commercially and noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
|
||||
これらのフォントはフリー(自由な)ソフトウエアです。
|
||||
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
制作履歴
|
||||
-------------------------------------------------------------------------------
|
||||
■2021-05-05
|
||||
・文字を追加
|
||||
共通: ␣
|
||||
・字形を修正・変更
|
||||
共通: №
|
||||
ゴシック第2: プヴ
|
||||
・(TTF) ビットマップを埋込み
|
||||
|
||||
■2019-10-19
|
||||
・ドキュメントの文字コードを UTF-8N、改行コードを LF に統一
|
||||
・(TTF, BDF) 記号を追加:
|
||||
▁▂▃▄▅▆▇█▏▎▍▌▋▊▉▔▕╭╮╰╯♠♥♦♣╱╲╳♤♡♢♧
|
||||
|
||||
■2019-08-18
|
||||
・各ファイルのパーミッションから、不要な実行ビットを除去 (フォントの変更はなし)
|
||||
|
||||
■2019-06-03
|
||||
・新フォント「美咲ゴシック第2」を追加。英語名「MisakiGothic2nd」
|
||||
・「令和」の合字を追加
|
||||
・各種字形の修正
|
||||
・(E500) ファイル名のつけ方を変更して配布を再開
|
||||
・(BDF) 一部仕様を変更して配布を再開
|
||||
エンコーディングは ISO 10646-1 のみとし、全ての文字を 1 ファイルに収録
|
||||
XLFD 変更
|
||||
BITMAP を文字の上下左右端の空白を削った形とし、BBX で位置を指定する形に修正
|
||||
|
||||
■2019-02-03a
|
||||
・制作履歴における、変更した文字の抜け (ゴシックの「と」「ね」「ゐ」) を修正
|
||||
|
||||
■2019-02-03
|
||||
・ゴシック・明朝共通の変更: &,.‘’()〔〕[]{}〈〉【】°′″№0索二
|
||||
半角 (「&」字形変更)
|
||||
記号類 (主に括弧類の位置調整)
|
||||
数字 (ゼロにスラッシュを追加)
|
||||
漢字 (「索」「二」の字形変更)
|
||||
・ゴシックのみの変更: 69CGOQΟОСЭぐぜとどねばぶゐガグゾダバヴ
|
||||
数字 (「6」「9」の先端を伸ばす形に変更)
|
||||
アルファベット (オーなどの丸み変更)
|
||||
仮名 (主に濁点/半濁点つき文字の調整)
|
||||
・明朝のみの変更: ぐでとどなぶウグケサザソゾダドワヱヴЁ
|
||||
仮名 (各種字形変更)
|
||||
キリル (「Ё」の字形変更)
|
||||
|
||||
■2015-04-10
|
||||
・誤字になっていた「喪」を修正
|
||||
・(TTF) ウェイトを「Regular」に変更
|
||||
一部の記号を追加、変更
|
||||
バージョン番号を「yyyy.mmdd」の形に変更
|
||||
その他、パラメータを見直し
|
||||
埋込みビットマップ版の同梱を取り止め
|
||||
|
||||
■2012-06-03
|
||||
・正式公開初版
|
||||
|
||||
■2012-03-31
|
||||
・各種ファイル名、ドキュメントの見直し
|
||||
・BDF 版以外のアーカイブ形式を zip に変更
|
||||
・(TTF) アウトライン版の正式採用、Mac におそらく対応
|
||||
・(FONTX) 4X8.FNT の 00h~1Fh に DOS/V 罫線を追加
|
||||
|
||||
■2011-03-08
|
||||
・(TTF-Outline) 生成手順の改善に伴い periodβ11 作成・公開
|
||||
|
||||
■2010-03-07
|
||||
・(BDF) FOUNDRY, POINT_SIZE, AVERAGE_WIDTH, SWIDTH, XLFD, 各ファイル名の変更
|
||||
|
||||
■2006-10-01
|
||||
・(TTF-Outline) 試作版配布開始
|
||||
|
||||
■2006-04-01
|
||||
・(PNG) 配布開始
|
||||
|
||||
■2006-03-26
|
||||
・(E500) 13 区追加版・軽量版 (第一水準のみ) 同梱開始
|
||||
フォントファイルの命名規則変更
|
||||
・(BDF) 美咲明朝の配布形態をゴシックとの差分から実ファイル同梱へ
|
||||
BITMAP の記述方法を修正 (16 進 4 桁から 2 桁へ)
|
||||
・(FONTX) 配布再開
|
||||
|
||||
■2005-07-20
|
||||
・アーカイブの命名規則を変更
|
||||
・(TTF, BDF) 著作権表記を変更
|
||||
・(BDF) 1 バイト文字フォントのファイル名をそれぞれ 4x8.bdf, 4x8_8859.bdf に
|
||||
|
||||
■2004-09-12
|
||||
・(TTF, BDF) 13 区の文字追加
|
||||
|
||||
■2004-06-24
|
||||
・フォントの名称変更
|
||||
・(美咲フォントを美咲ゴシック、ファミリーの総称を美咲フォントに)
|
||||
|
||||
■2004-05-09
|
||||
・美咲明朝の同梱開始
|
||||
|
||||
■2004-04-19
|
||||
・半濁点の処理を変更
|
||||
|
||||
■2004-04-11
|
||||
・3 フォント同時編集ツール「"SCRNJPN.FNT" Editor」完成
|
||||
|
||||
■2004-02-09
|
||||
・濁点の処理を変更
|
||||
|
||||
■2003-09-21
|
||||
・制作履歴の日付ミスを修正 (美咲氏 Thanx!)
|
||||
|
||||
■2003-06-04
|
||||
・ライセンスに関する記述を変更
|
||||
|
||||
■2003-05-04
|
||||
・(BDF) 3x7_8859.bdf の CHARS の値を修正
|
||||
・(BDF) 3x7_8859.bdf のファイル名が 3X7_8859.bdf になっていたのを修正
|
||||
|
||||
■2003-03-26
|
||||
・マニュアルをフォントの説明と各形式の説明に分離
|
||||
|
||||
■2003-01-02
|
||||
・各部のフォント間での統一化作業を本格的に開始
|
||||
|
||||
■2002-06-10
|
||||
・JIS X 0208-1983 での文字の変更、JIS X 0208-1990 での文字の追加に対応
|
||||
|
||||
■2002-06-06
|
||||
・未定義文字のフォントを 3×3 の ■ に
|
||||
|
||||
■2002-06-01
|
||||
・第二水準完成
|
||||
|
||||
■2002-05-22
|
||||
・制作再開
|
||||
|
||||
■2002-05-21
|
||||
・フォント作成専用ツール「'SCRNJPN.FNT' Editor」完成
|
||||
|
||||
■2002-05-11
|
||||
・E650 暴走により第二水準中の 600 文字近くが消滅→制作中断
|
||||
|
||||
■2002-04-初
|
||||
・第一水準完成
|
||||
|
||||
■2002-03-中
|
||||
・制作開始
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
バージョン情報
|
||||
-------------------------------------------------------------------------------
|
||||
■2021-05-05
|
||||
・美咲フォント 2021-05-05 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-10-19
|
||||
・美咲フォント 2019-10-19 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-08-18
|
||||
・美咲フォント 2019-06-03a 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-06-03
|
||||
・美咲フォント 2019-06-03 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-02-03
|
||||
・美咲フォント 2019-02-03 版 (TTF/PNG)
|
||||
|
||||
■2015-04-10
|
||||
・美咲フォント 2015-04-10 版 (TTF/PNG)
|
||||
|
||||
■2012-06-03
|
||||
・美咲フォント 2012-06-03 版 (E500/BDF/TTF/Ruputer/FONTX/PNG)
|
||||
|
||||
■2012-03-31
|
||||
・美咲フォント periodβ12 (E500/BDF/TTF/Ruputer/FONTX/PNG)
|
||||
|
||||
■2010-03-07
|
||||
・美咲フォント periodβ11a (BDF)
|
||||
|
||||
■2008-06-03
|
||||
・美咲フォント periodβ11 (E500/BDF/TTF/Ruputer/FONTX/PNG/TTF-Outline)
|
||||
|
||||
■2006-03-26
|
||||
・美咲フォント periodβ10 (E500/BDF/TTF/Ruputer/FONTX/PNG/TTF-Outline)
|
||||
|
||||
■2005-07-20
|
||||
・美咲フォント periodβ9 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2004-05-09
|
||||
・美咲フォント periodβ8 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-09-21
|
||||
・美咲フォント periodβ7 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-06-04
|
||||
・美咲フォント periodβ6 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-05-04
|
||||
・美咲フォント periodβ5 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-03-26
|
||||
・美咲フォント periodβ4 (E500/BDF/TTF/Ruputer/FONTX)
|
||||
|
||||
■2002-12-14
|
||||
・美咲フォント periodβ3 (E500)
|
||||
|
||||
■2002-06-18
|
||||
・美咲フォント periodβ2 (E500)
|
||||
|
||||
■2002-06-03
|
||||
・美咲フォント periodβ1 (E500)
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
連絡先
|
||||
-------------------------------------------------------------------------------
|
||||
門真 なむ (Num Kadoma)
|
||||
・Twitter: @num_kadoma
|
||||
・Website: http://littlelimit.net/
|
||||
@@ -0,0 +1,92 @@
|
||||
Copyright (c) 2023-2024 Mark Li (itmarkibfb@gmail.com)
|
||||
|
||||
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 AND 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 @@
|
||||
COPYRIGHT "Copyright (C) 1998-2026 Roman Czyborra, Paul Hardy, Qianqian Fang, Andrew Miller, Johnnie Weaver, David Corbett, Ælla Chiana Moskopp, Rebecca Bettencourt, Minseo Lee, Ho-Seok Ee, et al. License: SIL Open Font License version 1.1 and GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html> with the GNU Font Embedding Exception."
|
||||
@@ -0,0 +1,476 @@
|
||||
LICENSE
|
||||
-------
|
||||
The source code for everything except the compiled fonts in this current
|
||||
release is licensed as follows:
|
||||
|
||||
License for this current distribution of program source
|
||||
files (i.e., everything except the fonts) is released under
|
||||
the terms of the GNU General Public License version 2,
|
||||
or (at your option) a later version.
|
||||
|
||||
See the section below for a copy of the GNU General Public License
|
||||
version 2.
|
||||
|
||||
The license for the compiled fonts is covered by the SIL Open Font License
|
||||
version 1.1 and by the above GPL terms with the GNU font embedding exception,
|
||||
as follows:
|
||||
|
||||
As a special exception, if you create a document which uses this font,
|
||||
and embed this font or unaltered portions of this font into the document,
|
||||
this font does not by itself cause the resulting document to be covered
|
||||
by the GNU General Public License. This exception does not however
|
||||
invalidate any other reasons why the document might be covered by the
|
||||
GNU General Public License. If you modify this font, you may extend
|
||||
this exception to your version of the font, but you are not obligated
|
||||
to do so. If you do not wish to do so, delete this exception statement
|
||||
from your version.
|
||||
|
||||
See "http://www.gnu.org/licenses/gpl-faq.html#FontException" and
|
||||
"https://scripts.sil.org/OFL" for more details.
|
||||
|
||||
|
||||
GPL VERSION 2
|
||||
-------------
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
|
||||
|
||||
|
||||
SIL Open Font License version 1.1
|
||||
---------------------------------
|
||||
Copyright (c) <dates>, <Copyright Holder> (<URL|email>),
|
||||
with Reserved Font Name <Reserved Font Name>.
|
||||
Copyright (c) <dates>, <additional Copyright Holder> (<URL|email>),
|
||||
with Reserved Font Name <additional Reserved Font Name>.
|
||||
Copyright (c) <dates>, <additional Copyright Holder> (<URL|email>).
|
||||
|
||||
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,90 @@
|
||||
The SIL Open Font License version 1.1 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,21 @@
|
||||
{
|
||||
"version": "18.0.01",
|
||||
"license_choice": "OFL-1.1",
|
||||
"files": [
|
||||
{
|
||||
"file": "unifont-18.0.01.bdf.gz",
|
||||
"url": "https://unifoundry.com/pub/unifont/unifont-18.0.01/font-builds/unifont-18.0.01.bdf.gz",
|
||||
"sha256": "2989e0211030d8219fd931aba599fc95c86bc327ce1423848ef949783b208257"
|
||||
},
|
||||
{
|
||||
"file": "OFL-1.1.txt",
|
||||
"url": "https://unifoundry.com/OFL-1.1.txt",
|
||||
"sha256": "869692af094c57fb7258c57fe26820c759319603321d0ffeb278de3651763ded"
|
||||
},
|
||||
{
|
||||
"file": "LICENSE.txt",
|
||||
"url": "https://unifoundry.com/LICENSE.txt",
|
||||
"sha256": "1e74cb82bf476843e97c2596297b04219b1a7e51f7238944a8c031cb9401fa87"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
Fusion Pixel Font
|
||||
https://github.com/TakWolf/fusion-pixel-font
|
||||
|
||||
Copyright (c) 2022, TakWolf (https://takwolf.com).
|
||||
|
||||
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:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
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.
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": 2,
|
||||
"fonts": [
|
||||
{
|
||||
"id": "builtin:walnut-bus-8x16",
|
||||
"file": "SimilarBus8x16.ttf",
|
||||
"sha256": "aba652e330975ff3db2f41a2795a2d0478806ed83c52197acdbee85291cf5111",
|
||||
"label": "类似公交窄体(推荐16px)",
|
||||
"family": "类似公交窄体(推荐16px)",
|
||||
"style": "Regular",
|
||||
"glyph_count": 6884,
|
||||
"recommended_size": 16,
|
||||
"cell": [
|
||||
8,
|
||||
16
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "builtin:walnut-bus-16x32",
|
||||
"file": "SimilarBus16x32.ttf",
|
||||
"sha256": "b1d4035f2fa9dfd839f1b573b994cbec245f041c4a0a62ee9ef1a7c7b1bfa126",
|
||||
"label": "类似公交窄体(推荐32px)",
|
||||
"family": "类似公交窄体(推荐32px)",
|
||||
"style": "Regular",
|
||||
"glyph_count": 6884,
|
||||
"recommended_size": 32,
|
||||
"cell": [
|
||||
16,
|
||||
32
|
||||
]
|
||||
}
|
||||
],
|
||||
"obsolete": [
|
||||
"d38bf29d6408a6f731865a1ca760a80b5156ebf6ab96d5ad8d649df28163564c"
|
||||
],
|
||||
"aliases": {
|
||||
"user:d38bf29d6408a6f731865a1ca760a80b5156ebf6ab96d5ad8d649df28163564c:0": "builtin:walnut-bus-8x16"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Version only thumbnails that use a bundled font; preserve scene revisions."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from app.persistence import atomic_write_bytes
|
||||
|
||||
STAMP_SUFFIX = ".font-version"
|
||||
|
||||
|
||||
def scene_font_version(scene: dict, resolver) -> str | None:
|
||||
owner = getattr(resolver, "__self__", None)
|
||||
version_for = getattr(owner, "render_version", None)
|
||||
if version_for is None:
|
||||
return None
|
||||
elements = scene.get("elements", []) if scene.get("version") == 1 else [
|
||||
element for layer in scene.get("layers", []) if layer.get("type") == "text"
|
||||
for element in layer.get("elements", [])
|
||||
]
|
||||
versions = sorted({version for element in elements
|
||||
if (version := version_for(element.get("font", "default")))})
|
||||
if not versions:
|
||||
return None
|
||||
return hashlib.sha256(json.dumps(versions, separators=(",", ":")).encode("ascii")).hexdigest()[:16]
|
||||
|
||||
|
||||
def stamp_path(path: Path) -> Path:
|
||||
return path.with_name(path.name + STAMP_SUFFIX)
|
||||
|
||||
|
||||
def cache_stale(path: Path, scene: dict, resolver) -> bool:
|
||||
version = scene_font_version(scene, resolver)
|
||||
if version is None:
|
||||
return False
|
||||
try:
|
||||
return stamp_path(path).read_text(encoding="ascii").strip() != version
|
||||
except (OSError, UnicodeError):
|
||||
return True
|
||||
|
||||
|
||||
def stamp_cache(path: Path, scene: dict, resolver) -> None:
|
||||
version = scene_font_version(scene, resolver)
|
||||
stamp = stamp_path(path)
|
||||
if version is not None:
|
||||
atomic_write_bytes(stamp, (version + "\n").encode("ascii"))
|
||||
else:
|
||||
stamp.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def versioned_url(url: str, scene: dict, resolver) -> str:
|
||||
version = scene_font_version(scene, resolver)
|
||||
return url + f"&font_v={version}" if version else url
|
||||
|
||||
|
||||
def cached_versioned_url(url: str, path: Path) -> str:
|
||||
try:
|
||||
version = stamp_path(path).read_text(encoding="ascii").strip()
|
||||
except (OSError, UnicodeError):
|
||||
return url
|
||||
if len(version) == 16 and all(ch in "0123456789abcdef" for ch in version):
|
||||
return url + f"&font_v={version}"
|
||||
return url
|
||||
+124
-1
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
@@ -15,6 +16,7 @@ from uuid import uuid4
|
||||
from fontTools.ttLib import TTCollection, TTFont, TTLibError
|
||||
|
||||
from app.display.text_renderer import DEFAULT_FONT_CANDIDATES, FontFace
|
||||
from app.persistence import atomic_write_bytes
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -186,10 +188,102 @@ class FontCatalog:
|
||||
self.root.mkdir(parents=True, exist_ok=True)
|
||||
if os.name != "nt":
|
||||
self.root.chmod(0o750)
|
||||
self._favorite_path = self.root / "favorites.json"
|
||||
self._favorite_ids = self._load_favorites()
|
||||
self._cleanup_upload_temporaries()
|
||||
self._builtin = self._install_builtins()
|
||||
self._system_catalog = self._system_faces()
|
||||
self.refresh()
|
||||
|
||||
def _load_favorites(self) -> list[str]:
|
||||
if not self._favorite_path.exists():
|
||||
atomic_write_bytes(self._favorite_path, b'{"schema_version":1,"favorite_ids":[]}\n')
|
||||
return []
|
||||
try:
|
||||
record = json.loads(self._favorite_path.read_text(encoding="utf-8"))
|
||||
if (not isinstance(record, dict) or set(record) != {"schema_version", "favorite_ids"}
|
||||
or type(record["schema_version"]) is not int or record["schema_version"] != 1):
|
||||
raise ValueError("invalid favorites schema")
|
||||
identifiers = record["favorite_ids"]
|
||||
if (not isinstance(identifiers, list)
|
||||
or any(not isinstance(value, str) or not value or len(value) > 512 for value in identifiers)
|
||||
or len(set(identifiers)) != len(identifiers)):
|
||||
raise ValueError("invalid favorites identifiers")
|
||||
return identifiers
|
||||
except (ValueError, UnicodeError) as exc:
|
||||
raise FontValidationError("invalid font favorites; original file preserved") from exc
|
||||
|
||||
def set_favorite(self, identifier: str, favorite: bool) -> list[str]:
|
||||
with self._lock:
|
||||
if favorite and not any(item["id"] == identifier for item in self._items):
|
||||
raise KeyError(identifier)
|
||||
updated = [value for value in self._favorite_ids if value != identifier]
|
||||
if favorite:
|
||||
updated.append(identifier)
|
||||
if set(updated) == set(self._favorite_ids):
|
||||
return list(self._favorite_ids)
|
||||
content = json.dumps({"schema_version": 1, "favorite_ids": updated}, ensure_ascii=False).encode("utf-8") + b"\n"
|
||||
atomic_write_bytes(self._favorite_path, content)
|
||||
self._favorite_ids = updated
|
||||
return list(updated)
|
||||
|
||||
def _install_builtins(self) -> dict[str, tuple[dict, FontFace]]:
|
||||
bundle = Path(__file__).resolve().parent / "bundled"
|
||||
manifest = json.loads((bundle / "manifest.json").read_text(encoding="utf-8"))
|
||||
expected = {"builtin:walnut-bus-8x16": "SimilarBus8x16.ttf", "builtin:walnut-bus-16x32": "SimilarBus16x32.ttf"}
|
||||
entries = manifest.get("fonts", [])
|
||||
if manifest.get("version") != 2 or len(entries) != 2 or {item["id"]: item["file"] for item in entries} != expected:
|
||||
raise FontValidationError("invalid bundled font registration")
|
||||
self._manifest = manifest
|
||||
# Validate the entire bundle before modifying persisted resources.
|
||||
for item in entries:
|
||||
path = bundle / item["file"]
|
||||
if hashlib.sha256(path.read_bytes()).hexdigest() != item["sha256"]:
|
||||
raise FontValidationError("bundled font checksum mismatch")
|
||||
_inspect_font_file(path, require_unicode=True)
|
||||
installed = {item["id"]: (item, self._install_face(bundle, item)) for item in entries}
|
||||
# Only retire the exact previous built-in asset; never glob user fonts.
|
||||
for digest in manifest.get("obsolete", []):
|
||||
if len(digest) != 64 or any(ch not in "0123456789abcdef" for ch in digest):
|
||||
raise FontValidationError("invalid obsolete font registration")
|
||||
old = self.root / _USER_FONT_NAME.format(digest=digest)
|
||||
if old.exists():
|
||||
if old.is_symlink() or hashlib.sha256(old.read_bytes()).hexdigest() != digest:
|
||||
raise FontValidationError("obsolete font checksum mismatch; file preserved")
|
||||
old.unlink()
|
||||
_sync_directory(self.root)
|
||||
return installed
|
||||
|
||||
def _install_face(self, bundle: Path, manifest: dict) -> FontFace:
|
||||
payload = (bundle / manifest["file"]).read_bytes()
|
||||
digest = hashlib.sha256(payload).hexdigest()
|
||||
if digest != manifest["sha256"]:
|
||||
raise FontValidationError("bundled font checksum mismatch")
|
||||
target = self.root / _USER_FONT_NAME.format(digest=digest)
|
||||
if target.exists():
|
||||
if hashlib.sha256(target.read_bytes()).hexdigest() != digest:
|
||||
raise FontValidationError("persisted bundled font checksum mismatch; file preserved")
|
||||
else:
|
||||
temporary = self.root / f".upload-{uuid4().hex}.tmp"
|
||||
try:
|
||||
with temporary.open("xb") as stream:
|
||||
if os.name != "nt":
|
||||
os.chmod(temporary, 0o640)
|
||||
stream.write(payload)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
_inspect_font_file(temporary, require_unicode=True)
|
||||
# Concurrent initialization must never replace an existing file.
|
||||
try:
|
||||
os.link(temporary, target)
|
||||
except FileExistsError:
|
||||
if hashlib.sha256(target.read_bytes()).hexdigest() != digest:
|
||||
raise FontValidationError("persisted bundled font checksum mismatch; file preserved")
|
||||
_sync_directory(self.root)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
return FontFace(str(target.resolve()), 0)
|
||||
|
||||
def _cleanup_upload_temporaries(self) -> None:
|
||||
for path in self.root.glob(".upload-*.tmp"):
|
||||
if path.is_file() and path.parent == self.root:
|
||||
@@ -222,6 +316,12 @@ class FontCatalog:
|
||||
system_items: list[dict[str, str]] = []
|
||||
imported_items: list[dict[str, str]] = []
|
||||
warnings: list[str] = []
|
||||
builtin_digests = {item["sha256"] for item, face in self._builtin.values()}
|
||||
for manifest, builtin_face in self._builtin.values():
|
||||
faces[manifest["id"]] = (builtin_face,)
|
||||
builtin_item = _entry(manifest["id"], manifest["family"], manifest["style"], "system")
|
||||
builtin_item["label"] = manifest["label"]
|
||||
system_items.append(builtin_item)
|
||||
|
||||
seen_system: set[tuple[str, int]] = set()
|
||||
for item in self._system_catalog:
|
||||
@@ -247,7 +347,11 @@ class FontCatalog:
|
||||
for item in inspected:
|
||||
identifier = _user_font_id(digest, item.face.index)
|
||||
faces[identifier] = (item.face,)
|
||||
imported_items.append(_entry(identifier, item.family, item.style, "imported"))
|
||||
if digest not in builtin_digests and digest not in self._manifest.get("obsolete", []):
|
||||
imported_items.append(_entry(identifier, item.family, item.style, "imported"))
|
||||
|
||||
for alias, identifier in self._manifest.get("aliases", {}).items():
|
||||
faces[alias] = faces[identifier]
|
||||
|
||||
sort_key = lambda value: (value["family"].casefold(), value["style"].casefold(), value["id"])
|
||||
automatic = {
|
||||
@@ -270,6 +374,7 @@ class FontCatalog:
|
||||
with self._lock:
|
||||
return {
|
||||
"items": [dict(item) for item in self._items],
|
||||
"favorite_ids": list(self._favorite_ids),
|
||||
"accepted_extensions": list(ACCEPTED_FONT_EXTENSIONS),
|
||||
"max_upload_bytes": MAX_FONT_UPLOAD_BYTES,
|
||||
"warnings": list(self._warnings),
|
||||
@@ -279,6 +384,16 @@ class FontCatalog:
|
||||
with self._lock:
|
||||
return self._faces.get(identifier, ())
|
||||
|
||||
def render_version(self, identifier: str) -> str | None:
|
||||
identifier = self._manifest.get("aliases", {}).get(identifier, identifier)
|
||||
item = self._builtin.get(identifier)
|
||||
if item:
|
||||
return item[0]["sha256"]
|
||||
for manifest, face in self._builtin.values():
|
||||
if identifier == _user_font_id(manifest["sha256"], 0):
|
||||
return manifest["sha256"]
|
||||
return None
|
||||
|
||||
async def import_font(
|
||||
self,
|
||||
filename: str,
|
||||
@@ -316,6 +431,11 @@ class FontCatalog:
|
||||
raise FontValidationError("font file is empty")
|
||||
inspected = _inspect_font_file(temporary, require_unicode=True)
|
||||
file_digest = digest.hexdigest()
|
||||
old_alias = _user_font_id(file_digest, 0)
|
||||
if file_digest in self._manifest.get("obsolete", []):
|
||||
temporary.unlink(missing_ok=True)
|
||||
destination = self._manifest["aliases"][old_alias]
|
||||
return False, [dict(item) for item in self._items if item["id"] == destination]
|
||||
target = self.root / _USER_FONT_NAME.format(digest=file_digest)
|
||||
with self._lock:
|
||||
created = not target.exists()
|
||||
@@ -326,6 +446,9 @@ class FontCatalog:
|
||||
temporary.unlink(missing_ok=True)
|
||||
self.refresh()
|
||||
identifiers = {_user_font_id(file_digest, item.face.index) for item in inspected}
|
||||
for manifest, face in self._builtin.values():
|
||||
if file_digest == manifest["sha256"]:
|
||||
identifiers.add(manifest["id"])
|
||||
imported = [dict(item) for item in self._items if item["id"] in identifiers]
|
||||
return created, imported
|
||||
except FontCatalogError:
|
||||
|
||||
@@ -134,6 +134,12 @@ def normalize_if_match(value: str | None) -> str:
|
||||
return candidate
|
||||
|
||||
|
||||
class FontFavoriteRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
id: StrictStr = Field(min_length=1, max_length=512)
|
||||
favorite: StrictBool
|
||||
|
||||
|
||||
class ConfigUpdate(BaseModel):
|
||||
orientation: int | None = None
|
||||
brightness: int | None = None
|
||||
@@ -991,6 +997,15 @@ def create_app(
|
||||
def get_fonts() -> dict:
|
||||
return fonts.document()
|
||||
|
||||
@app.put("/api/fonts/favorite")
|
||||
def update_font_favorite(payload: FontFavoriteRequest) -> dict:
|
||||
try:
|
||||
return {"favorite_ids": fonts.set_favorite(payload.id, payload.favorite)}
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="font is not available")
|
||||
except OSError:
|
||||
raise HTTPException(status_code=500, detail="font favorite could not be saved")
|
||||
|
||||
@app.post("/api/fonts/import")
|
||||
async def import_font(request: Request, response: Response, filename: str = "") -> dict:
|
||||
content_type = request.headers.get("content-type", "").split(";", 1)[0].strip().casefold()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { renderUiCopy, unregisterUiCopySubtree } from "./ui-copy-runtime.js";
|
||||
|
||||
const SOURCE_ORDER = Object.freeze(["automatic", "imported", "system"]);
|
||||
const SOURCE_LABELS = Object.freeze({
|
||||
automatic: "自动字体",
|
||||
@@ -33,23 +35,29 @@ export function filterFontItems(items, query = "") {
|
||||
));
|
||||
}
|
||||
|
||||
export function groupFontItems(items) {
|
||||
return SOURCE_ORDER
|
||||
export function groupFontItems(items, favoriteIds = []) {
|
||||
const favorites = new Set(favoriteIds);
|
||||
return [{ source: "favorites", label: "收藏字体", items: items.filter(item => favorites.has(item.id)) }, ...SOURCE_ORDER
|
||||
.map((source) => ({
|
||||
source,
|
||||
label: SOURCE_LABELS[source],
|
||||
items: items.filter((item) => item.source === source),
|
||||
}))
|
||||
.filter((group) => group.items.length);
|
||||
.filter((group) => group.items.length)];
|
||||
}
|
||||
|
||||
export function createFontPicker({ input, listbox, onCommit }) {
|
||||
export function createFontPicker({ input, listbox, onCommit, onFavorite, onOpen }) {
|
||||
let items = [];
|
||||
let selectedId = "default";
|
||||
let visibleItems = [];
|
||||
let activeIndex = -1;
|
||||
let enabled = false;
|
||||
let closeTimer = null;
|
||||
let favoriteIds = [];
|
||||
let queryText = "";
|
||||
let favoriteBusy = false;
|
||||
let dialogOpen = false;
|
||||
let restoringFocus = false;
|
||||
|
||||
function selectedItem() {
|
||||
return items.find((item) => item.id === selectedId) || null;
|
||||
@@ -101,17 +109,31 @@ export function createFontPicker({ input, listbox, onCommit }) {
|
||||
}
|
||||
|
||||
function render(query = "") {
|
||||
visibleItems = filterFontItems(items, query);
|
||||
const groups = groupFontItems(visibleItems);
|
||||
const bounds = input.getBoundingClientRect();
|
||||
listbox.style.width = `${Math.max(bounds.width, Math.min(440, window.innerWidth - bounds.left - 16))}px`;
|
||||
queryText = query;
|
||||
const filtered = filterFontItems(items, query);
|
||||
const groups = groupFontItems(filtered, favoriteIds);
|
||||
visibleItems = groups.flatMap(group => group.items);
|
||||
const nodes = [];
|
||||
let optionIndex = 0;
|
||||
for (const group of groups) {
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "font-option-group";
|
||||
heading.textContent = group.label;
|
||||
if (group.source === "favorites") renderUiCopy("copy.dynamic.font.favorites", heading, "收藏字体");
|
||||
else heading.textContent = group.label;
|
||||
heading.setAttribute("role", "presentation");
|
||||
nodes.push(heading);
|
||||
if (group.source === "favorites" && !group.items.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "font-options-empty";
|
||||
renderUiCopy(query.trim() ? "copy.dynamic.font.no_matches" : "copy.dynamic.font.empty", empty, query.trim() ? "没有匹配的收藏字体" : "暂无收藏字体");
|
||||
nodes.push(empty);
|
||||
}
|
||||
for (const item of group.items) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "font-option-row";
|
||||
row.setAttribute("role", "presentation");
|
||||
const option = document.createElement("div");
|
||||
option.id = optionId(optionIndex);
|
||||
option.className = "font-option";
|
||||
@@ -124,34 +146,96 @@ export function createFontPicker({ input, listbox, onCommit }) {
|
||||
const source = document.createElement("small");
|
||||
source.textContent = item.style;
|
||||
source.setAttribute("aria-hidden", "true");
|
||||
option.append(label, source);
|
||||
const star = document.createElement("button");
|
||||
star.type = "button";
|
||||
star.className = "font-favorite-button";
|
||||
const favorite = favoriteIds.includes(item.id);
|
||||
star.textContent = favorite ? "★" : "☆";
|
||||
star.setAttribute("aria-label", `${favorite ? "取消收藏" : "收藏"}字体“${item.label}”`);
|
||||
star.setAttribute("aria-pressed", String(favorite));
|
||||
star.disabled = favoriteBusy;
|
||||
star.addEventListener("pointerdown", event => event.stopPropagation());
|
||||
star.addEventListener("click", event => {
|
||||
event.stopPropagation();
|
||||
toggleFavorite(item, favorite);
|
||||
});
|
||||
option.append(label);
|
||||
row.append(option, star, source);
|
||||
option.addEventListener("pointerdown", (event) => event.preventDefault());
|
||||
option.addEventListener("click", () => commit(item));
|
||||
nodes.push(option);
|
||||
row.addEventListener("pointerdown", event => { if (event.target !== star) event.preventDefault(); });
|
||||
row.addEventListener("click", () => commit(item));
|
||||
nodes.push(row);
|
||||
optionIndex += 1;
|
||||
}
|
||||
}
|
||||
if (!visibleItems.length) {
|
||||
if (!filtered.length) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "font-options-empty";
|
||||
empty.textContent = "没有匹配的字体";
|
||||
nodes.push(empty);
|
||||
}
|
||||
unregisterUiCopySubtree(listbox);
|
||||
listbox.replaceChildren(...nodes);
|
||||
activeIndex = visibleItems.findIndex((item) => item.id === selectedId);
|
||||
if (activeIndex < 0 && visibleItems.length) activeIndex = 0;
|
||||
setActive(activeIndex);
|
||||
}
|
||||
|
||||
async function toggleFavorite(item, favorite) {
|
||||
if (favoriteBusy || dialogOpen) return;
|
||||
if (favorite) {
|
||||
dialogOpen = true;
|
||||
const dialog = document.createElement("dialog");
|
||||
dialog.className = "font-favorite-dialog";
|
||||
dialog.setAttribute("aria-label", "确认取消收藏");
|
||||
const message = document.createElement("p");
|
||||
renderUiCopy("copy.dynamic.font.confirm", message, "确认取消收藏字体“{name}/{style}”?", { name: item.family, style: item.style });
|
||||
dialog.append(message);
|
||||
for (const [value, title] of [["cancel", "取消"], ["confirm", "取消收藏"]]) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
renderUiCopy(value === "cancel" ? "copy.dynamic.font.cancel" : "copy.dynamic.font.remove", button, title);
|
||||
button.addEventListener("click", () => dialog.close(value));
|
||||
dialog.append(button);
|
||||
}
|
||||
document.body.append(dialog);
|
||||
const result = new Promise(resolve => dialog.addEventListener("close", () => resolve(dialog.returnValue === "confirm"), { once: true }));
|
||||
dialog.showModal();
|
||||
const confirmed = await result;
|
||||
unregisterUiCopySubtree(dialog);
|
||||
dialog.remove();
|
||||
dialogOpen = false;
|
||||
if (!confirmed) { restoreInputFocus(); return; }
|
||||
}
|
||||
favoriteBusy = true;
|
||||
render(queryText);
|
||||
try {
|
||||
const result = await onFavorite?.(item, !favorite);
|
||||
if (Array.isArray(result)) favoriteIds = [...result];
|
||||
} finally {
|
||||
favoriteBusy = false;
|
||||
render(queryText);
|
||||
restoreInputFocus();
|
||||
}
|
||||
}
|
||||
|
||||
function restoreInputFocus() {
|
||||
restoringFocus = true;
|
||||
input.focus({ preventScroll: true });
|
||||
restoringFocus = false;
|
||||
}
|
||||
|
||||
function open(query = "") {
|
||||
if (!enabled) return;
|
||||
const wasClosed = listbox.hidden;
|
||||
render(query);
|
||||
listbox.hidden = false;
|
||||
input.setAttribute("aria-expanded", "true");
|
||||
if (wasClosed) onOpen?.();
|
||||
}
|
||||
|
||||
input.addEventListener("focus", () => {
|
||||
if (!enabled) return;
|
||||
if (!enabled || restoringFocus) return;
|
||||
input.select();
|
||||
open();
|
||||
});
|
||||
@@ -179,7 +263,15 @@ export function createFontPicker({ input, listbox, onCommit }) {
|
||||
}
|
||||
});
|
||||
input.addEventListener("blur", () => {
|
||||
closeTimer = window.setTimeout(() => close(), 0);
|
||||
closeTimer = window.setTimeout(() => {
|
||||
if (!dialogOpen && !favoriteBusy && !listbox.contains(document.activeElement)) close();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
listbox.addEventListener("focusout", () => {
|
||||
closeTimer = window.setTimeout(() => {
|
||||
if (!dialogOpen && !favoriteBusy && document.activeElement !== input && !listbox.contains(document.activeElement)) close();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
restoreLabel();
|
||||
@@ -187,7 +279,11 @@ export function createFontPicker({ input, listbox, onCommit }) {
|
||||
setItems(nextItems) {
|
||||
items = normalizeFontItems(nextItems);
|
||||
restoreLabel();
|
||||
if (!listbox.hidden) render();
|
||||
if (!listbox.hidden) render(queryText);
|
||||
},
|
||||
setFavorites(ids) {
|
||||
favoriteIds = Array.isArray(ids) ? [...ids] : [];
|
||||
if (!listbox.hidden && !favoriteBusy && !dialogOpen) render(queryText);
|
||||
},
|
||||
setValue(identifier) {
|
||||
selectedId = typeof identifier === "string" && identifier ? identifier : "default";
|
||||
|
||||
@@ -3158,3 +3158,17 @@ noscript {
|
||||
#actual-screen-frame[hidden] { display:none; }
|
||||
#actual-screen-frame span { position:absolute; top:2px; left:3px; font-size:11px; color:#FFCC66; background:#000b; padding:2px; white-space:nowrap; }
|
||||
.board-expanded #actual-screen-frame { inset:33.333333%; }
|
||||
|
||||
.font-option-row { display:flex; align-items:center; gap:6px; border-radius:8px; padding-right:10px; }
|
||||
.font-option-row .font-option { flex:1; min-width:0; }
|
||||
.font-option-row small { flex:0 0 auto; max-width:30%; overflow:hidden; text-overflow:ellipsis; color:var(--muted); }
|
||||
.font-option-row:has(.font-option.active), .font-option-row:hover { background:rgb(53 194 160 / 16%); }
|
||||
.font-favorite-button { flex:0 0 44px; width:44px; min-height:44px; padding:0; background:transparent; border:0; font-size:24px; color:var(--muted); }
|
||||
.font-favorite-button[aria-pressed="true"] { color:#ffd166; }
|
||||
.font-favorite-button:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
|
||||
.font-favorite-dialog { background:var(--surface, #111923); color:var(--text); border:1px solid var(--muted); border-radius:12px; max-width:calc(100vw - 32px); }
|
||||
.font-favorite-dialog p { overflow-wrap:anywhere; }
|
||||
.font-favorite-dialog button { margin:4px; }
|
||||
.font-favorite-dialog::backdrop { background:rgb(0 0 0 / 60%); }
|
||||
|
||||
.font-options { right:auto; box-sizing:border-box; }
|
||||
|
||||
@@ -234,46 +234,264 @@
|
||||
"scope": "text",
|
||||
"source": "views/text.js::font_catalog_failed"
|
||||
},
|
||||
"copy.dynamic.media.state_uploading": {"text": "上传中", "scope": "media-import", "source": "views/media-import.js::state_uploading"},
|
||||
"copy.dynamic.media.state_analyzing": {"text": "分析中", "scope": "media-import", "source": "views/media-import.js::state_analyzing"},
|
||||
"copy.dynamic.media.state_awaiting": {"text": "等待设置", "scope": "media-import", "source": "views/media-import.js::state_awaiting"},
|
||||
"copy.dynamic.media.state_queued": {"text": "排队中", "scope": "media-import", "source": "views/media-import.js::state_queued"},
|
||||
"copy.dynamic.media.state_converting": {"text": "转换中", "scope": "media-import", "source": "views/media-import.js::state_converting"},
|
||||
"copy.dynamic.media.state_failed": {"text": "失败", "scope": "media-import", "source": "views/media-import.js::state_failed"},
|
||||
"copy.dynamic.media.queue_position": {"text": "{state} · 队列第 {position} 位", "scope": "media-import", "source": "views/media-import.js::queue_position"},
|
||||
"copy.dynamic.media.preview": {"text": "代表帧 {index}", "scope": "media-import", "source": "views/media-import.js::preview"},
|
||||
"copy.dynamic.media.output_name": {"text": "保存名称", "scope": "media-import", "source": "views/media-import.js::output_name"},
|
||||
"copy.dynamic.media.fit_mode": {"text": "1:1 适配", "scope": "media-import", "source": "views/media-import.js::fit_mode"},
|
||||
"copy.dynamic.media.mode_crop": {"text": "自由取景", "scope": "media-import", "source": "views/media-import.js::mode_crop"},
|
||||
"copy.dynamic.media.mode_contain": {"text": "完整包含", "scope": "media-import", "source": "views/media-import.js::mode_contain"},
|
||||
"copy.dynamic.media.mode_stretch": {"text": "拉伸", "scope": "media-import", "source": "views/media-import.js::mode_stretch"},
|
||||
"copy.dynamic.media.crop_zoom": {"text": "取景缩放", "scope": "media-import", "source": "views/media-import.js::crop_zoom"},
|
||||
"copy.dynamic.media.transparency": {"text": "透明像素填充色", "scope": "media-import", "source": "views/media-import.js::transparency"},
|
||||
"copy.dynamic.media.padding": {"text": "留白颜色", "scope": "media-import", "source": "views/media-import.js::padding"},
|
||||
"copy.dynamic.media.convert": {"text": "保存设置并开始转换", "scope": "media-import", "source": "views/media-import.js::convert"},
|
||||
"copy.dynamic.media.cancel_source": {"text": "取消并删除源文件", "scope": "media-import", "source": "views/media-import.js::cancel_source"},
|
||||
"copy.dynamic.media.retry": {"text": "重试", "scope": "media-import", "source": "views/media-import.js::retry"},
|
||||
"copy.dynamic.media.cancel": {"text": "取消并删除", "scope": "media-import", "source": "views/media-import.js::cancel"},
|
||||
"copy.dynamic.media.deleted": {"text": "任务和源文件已删除。", "scope": "media-import", "source": "views/media-import.js::deleted"},
|
||||
"copy.dynamic.media.delete_failed": {"text": "删除任务失败:{error}", "scope": "media-import", "source": "views/media-import.js::delete_failed"},
|
||||
"copy.dynamic.media.queued": {"text": "任务已加入转换队列。", "scope": "media-import", "source": "views/media-import.js::queued"},
|
||||
"copy.dynamic.media.name_conflict": {"text": "保存名称已存在,请更换名称。", "scope": "media-import", "source": "views/media-import.js::name_conflict"},
|
||||
"copy.dynamic.media.convert_failed": {"text": "无法开始转换:{error}", "scope": "media-import", "source": "views/media-import.js::convert_failed"},
|
||||
"copy.dynamic.media.retry_failed": {"text": "重试失败:{error}", "scope": "media-import", "source": "views/media-import.js::retry_failed"},
|
||||
"copy.dynamic.media.completed": {"text": "“{name}”转换完成,已加入内容库。", "scope": "media-import", "source": "views/media-import.js::completed"},
|
||||
"copy.dynamic.media.empty": {"text": "当前没有等待或转换中的任务。", "scope": "media-import", "source": "views/media-import.js::empty"},
|
||||
"copy.dynamic.media.list_failed": {"text": "任务列表读取失败:{error}", "scope": "media-import", "source": "views/media-import.js::list_failed"},
|
||||
"copy.dynamic.media.uploaded": {"text": "上传完成,设备正在分析媒体。", "scope": "media-import", "source": "views/media-import.js::uploaded"},
|
||||
"copy.dynamic.media.upload_failed": {"text": "上传失败:{error}", "scope": "media-import", "source": "views/media-import.js::upload_failed"},
|
||||
"copy.dynamic.current_display.loading": {"text": "正在读取当前画面…", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_loading"},
|
||||
"copy.dynamic.current_display.load_failed": {"text": "当前画面读取失败:{reason}", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_load_failed"},
|
||||
"copy.dynamic.current_display.pause": {"text": "暂停", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_pause"},
|
||||
"copy.dynamic.current_display.resume": {"text": "继续播放", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_resume"},
|
||||
"copy.dynamic.current_display.pausing": {"text": "正在暂停…", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_pausing"},
|
||||
"copy.dynamic.current_display.resuming": {"text": "正在继续播放…", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_resuming"},
|
||||
"copy.dynamic.current_display.paused": {"text": "动图已暂停", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_paused"},
|
||||
"copy.dynamic.current_display.resumed": {"text": "动图继续播放", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_resumed"},
|
||||
"copy.dynamic.current_display.speed_changing": {"text": "正在切换到 {speed}×…", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_speed_changing"},
|
||||
"copy.dynamic.current_display.speed_changed": {"text": "已切换到 {speed}×", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_speed_changed"},
|
||||
"copy.dynamic.current_display.control_failed": {"text": "动图播放控制失败:{reason}", "scope": "dialog:current-display-dialog", "source": "views/device.js::current_display_control_failed"}
|
||||
"copy.dynamic.media.state_uploading": {
|
||||
"text": "上传中",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::state_uploading"
|
||||
},
|
||||
"copy.dynamic.media.state_analyzing": {
|
||||
"text": "分析中",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::state_analyzing"
|
||||
},
|
||||
"copy.dynamic.media.state_awaiting": {
|
||||
"text": "等待设置",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::state_awaiting"
|
||||
},
|
||||
"copy.dynamic.media.state_queued": {
|
||||
"text": "排队中",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::state_queued"
|
||||
},
|
||||
"copy.dynamic.media.state_converting": {
|
||||
"text": "转换中",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::state_converting"
|
||||
},
|
||||
"copy.dynamic.media.state_failed": {
|
||||
"text": "失败",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::state_failed"
|
||||
},
|
||||
"copy.dynamic.media.queue_position": {
|
||||
"text": "{state} · 队列第 {position} 位",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::queue_position"
|
||||
},
|
||||
"copy.dynamic.media.preview": {
|
||||
"text": "代表帧 {index}",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::preview"
|
||||
},
|
||||
"copy.dynamic.media.output_name": {
|
||||
"text": "保存名称",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::output_name"
|
||||
},
|
||||
"copy.dynamic.media.fit_mode": {
|
||||
"text": "1:1 适配",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::fit_mode"
|
||||
},
|
||||
"copy.dynamic.media.mode_crop": {
|
||||
"text": "自由取景",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::mode_crop"
|
||||
},
|
||||
"copy.dynamic.media.mode_contain": {
|
||||
"text": "完整包含",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::mode_contain"
|
||||
},
|
||||
"copy.dynamic.media.mode_stretch": {
|
||||
"text": "拉伸",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::mode_stretch"
|
||||
},
|
||||
"copy.dynamic.media.crop_zoom": {
|
||||
"text": "取景缩放",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::crop_zoom"
|
||||
},
|
||||
"copy.dynamic.media.transparency": {
|
||||
"text": "透明像素填充色",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::transparency"
|
||||
},
|
||||
"copy.dynamic.media.padding": {
|
||||
"text": "留白颜色",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::padding"
|
||||
},
|
||||
"copy.dynamic.media.convert": {
|
||||
"text": "保存设置并开始转换",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::convert"
|
||||
},
|
||||
"copy.dynamic.media.cancel_source": {
|
||||
"text": "取消并删除源文件",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::cancel_source"
|
||||
},
|
||||
"copy.dynamic.media.retry": {
|
||||
"text": "重试",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::retry"
|
||||
},
|
||||
"copy.dynamic.media.cancel": {
|
||||
"text": "取消并删除",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::cancel"
|
||||
},
|
||||
"copy.dynamic.media.deleted": {
|
||||
"text": "任务和源文件已删除。",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::deleted"
|
||||
},
|
||||
"copy.dynamic.media.delete_failed": {
|
||||
"text": "删除任务失败:{error}",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::delete_failed"
|
||||
},
|
||||
"copy.dynamic.media.queued": {
|
||||
"text": "任务已加入转换队列。",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::queued"
|
||||
},
|
||||
"copy.dynamic.media.name_conflict": {
|
||||
"text": "保存名称已存在,请更换名称。",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::name_conflict"
|
||||
},
|
||||
"copy.dynamic.media.convert_failed": {
|
||||
"text": "无法开始转换:{error}",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::convert_failed"
|
||||
},
|
||||
"copy.dynamic.media.retry_failed": {
|
||||
"text": "重试失败:{error}",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::retry_failed"
|
||||
},
|
||||
"copy.dynamic.media.completed": {
|
||||
"text": "“{name}”转换完成,已加入内容库。",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::completed"
|
||||
},
|
||||
"copy.dynamic.media.empty": {
|
||||
"text": "当前没有等待或转换中的任务。",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::empty"
|
||||
},
|
||||
"copy.dynamic.media.list_failed": {
|
||||
"text": "任务列表读取失败:{error}",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::list_failed"
|
||||
},
|
||||
"copy.dynamic.media.uploaded": {
|
||||
"text": "上传完成,设备正在分析媒体。",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::uploaded"
|
||||
},
|
||||
"copy.dynamic.media.upload_failed": {
|
||||
"text": "上传失败:{error}",
|
||||
"scope": "media-import",
|
||||
"source": "views/media-import.js::upload_failed"
|
||||
},
|
||||
"copy.dynamic.current_display.loading": {
|
||||
"text": "正在读取当前画面…",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_loading"
|
||||
},
|
||||
"copy.dynamic.current_display.load_failed": {
|
||||
"text": "当前画面读取失败:{reason}",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_load_failed"
|
||||
},
|
||||
"copy.dynamic.current_display.pause": {
|
||||
"text": "暂停",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_pause"
|
||||
},
|
||||
"copy.dynamic.current_display.resume": {
|
||||
"text": "继续播放",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_resume"
|
||||
},
|
||||
"copy.dynamic.current_display.pausing": {
|
||||
"text": "正在暂停…",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_pausing"
|
||||
},
|
||||
"copy.dynamic.current_display.resuming": {
|
||||
"text": "正在继续播放…",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_resuming"
|
||||
},
|
||||
"copy.dynamic.current_display.paused": {
|
||||
"text": "动图已暂停",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_paused"
|
||||
},
|
||||
"copy.dynamic.current_display.resumed": {
|
||||
"text": "动图继续播放",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_resumed"
|
||||
},
|
||||
"copy.dynamic.current_display.speed_changing": {
|
||||
"text": "正在切换到 {speed}×…",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_speed_changing"
|
||||
},
|
||||
"copy.dynamic.current_display.speed_changed": {
|
||||
"text": "已切换到 {speed}×",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_speed_changed"
|
||||
},
|
||||
"copy.dynamic.current_display.control_failed": {
|
||||
"text": "动图播放控制失败:{reason}",
|
||||
"scope": "dialog:current-display-dialog",
|
||||
"source": "views/device.js::current_display_control_failed"
|
||||
},
|
||||
"copy.dynamic.font.favorites": {
|
||||
"text": "收藏字体",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.empty": {
|
||||
"text": "暂无收藏字体",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.no_matches": {
|
||||
"text": "没有匹配的收藏字体",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.confirm": {
|
||||
"text": "确认取消收藏字体“{name}/{style}”?",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.cancel": {
|
||||
"text": "取消",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.remove": {
|
||||
"text": "取消收藏",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.saved": {
|
||||
"text": "字体已收藏",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.removed": {
|
||||
"text": "已取消收藏字体",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.read_error": {
|
||||
"text": "收藏状态读取失败,请重新打开字体列表",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
},
|
||||
"copy.dynamic.font.save_error": {
|
||||
"text": "收藏保存失败:{error}",
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5206,6 +5206,111 @@
|
||||
"source": "views/device.js::voltage_ok",
|
||||
"text": "屏幕输入电压 {volts} V"
|
||||
},
|
||||
"copy.dynamic.font.cancel": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "取消"
|
||||
},
|
||||
"copy.dynamic.font.confirm": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [
|
||||
"name",
|
||||
"style"
|
||||
],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "确认取消收藏字体“{name}/{style}”?"
|
||||
},
|
||||
"copy.dynamic.font.empty": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "暂无收藏字体"
|
||||
},
|
||||
"copy.dynamic.font.favorites": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "收藏字体"
|
||||
},
|
||||
"copy.dynamic.font.no_matches": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "没有匹配的收藏字体"
|
||||
},
|
||||
"copy.dynamic.font.read_error": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "收藏状态读取失败,请重新打开字体列表"
|
||||
},
|
||||
"copy.dynamic.font.remove": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "取消收藏"
|
||||
},
|
||||
"copy.dynamic.font.removed": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "已取消收藏字体"
|
||||
},
|
||||
"copy.dynamic.font.save_error": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [
|
||||
"error"
|
||||
],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "收藏保存失败:{error}"
|
||||
},
|
||||
"copy.dynamic.font.saved": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
"render": {
|
||||
"type": "dynamic_template"
|
||||
},
|
||||
"scope": "text",
|
||||
"source": "font-picker.js / views/text.js::favorites",
|
||||
"text": "字体已收藏"
|
||||
},
|
||||
"copy.dynamic.media.cancel": {
|
||||
"kind": "dynamic_template",
|
||||
"placeholders": [],
|
||||
|
||||
@@ -32,6 +32,7 @@ const fontOptions = document.getElementById("text-font-options");
|
||||
const importFontButton = document.getElementById("import-font");
|
||||
const fontFileInput = document.getElementById("font-file");
|
||||
const fontFeedback = document.getElementById("font-feedback");
|
||||
let favoriteEpoch = 0;
|
||||
let initialized = false;
|
||||
let suppressForm = false;
|
||||
let renderedSelectionId = null;
|
||||
@@ -43,6 +44,25 @@ let acceptedFontExtensions = [".ttf", ".otf", ".ttc", ".otc"];
|
||||
const fontPicker = createFontPicker({
|
||||
input: fontInput,
|
||||
listbox: fontOptions,
|
||||
onOpen() {
|
||||
const epoch = ++favoriteEpoch;
|
||||
apiJson("/api/fonts").then(catalog => { if (epoch === favoriteEpoch) fontPicker.setFavorites(catalog.favorite_ids); }).catch(() => {
|
||||
setFontFeedbackTemplate("copy.dynamic.font.read_error", "收藏状态读取失败,请重新打开字体列表", {}, true);
|
||||
});
|
||||
},
|
||||
async onFavorite(item, favorite) {
|
||||
favoriteEpoch += 1;
|
||||
try {
|
||||
const result = await apiJson("/api/fonts/favorite", {
|
||||
method: "PUT", body: JSON.stringify({ id: item.id, favorite }),
|
||||
});
|
||||
setFontFeedbackTemplate(favorite ? "copy.dynamic.font.saved" : "copy.dynamic.font.removed", favorite ? "字体已收藏" : "已取消收藏字体");
|
||||
return result.favorite_ids;
|
||||
} catch (error) {
|
||||
setFontFeedbackTemplate("copy.dynamic.font.save_error", "收藏保存失败:{error}", { error: error.message }, true);
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
onCommit(item) {
|
||||
const selected = getSelectedTextElement();
|
||||
if (!selected) return;
|
||||
@@ -220,6 +240,7 @@ subscribeScene(renderSelection);
|
||||
async function loadFontCatalog() {
|
||||
const catalog = await apiJson("/api/fonts");
|
||||
fontPicker.setItems(catalog.items);
|
||||
fontPicker.setFavorites(catalog.favorite_ids);
|
||||
acceptedFontExtensions = Array.isArray(catalog.accepted_extensions)
|
||||
? catalog.accepted_extensions.map((value) => String(value).toLowerCase())
|
||||
: acceptedFontExtensions;
|
||||
|
||||
@@ -20,6 +20,7 @@ from PIL import Image
|
||||
from app.display.colors import parse_color
|
||||
from app.display.text_renderer import FontResolver, TextOptions, render_text_layer
|
||||
from app.persistence import atomic_write_bytes, atomic_write_many_bytes
|
||||
from app.fonts.render_cache import cache_stale, stamp_cache, STAMP_SUFFIX, versioned_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -341,7 +342,7 @@ class TemplateStore:
|
||||
|
||||
def _write_thumbnail(self, template_id: str, digest: str, scene: dict[str, Any]) -> Path:
|
||||
path = self._thumbnail_path(template_id, digest)
|
||||
if path.exists():
|
||||
if path.exists() and not cache_stale(path, scene, self._font_resolver):
|
||||
return path
|
||||
content = render_scene_thumbnail(scene, font_resolver=self._font_resolver)
|
||||
self._write_bytes_atomic(path, content)
|
||||
@@ -353,6 +354,7 @@ class TemplateStore:
|
||||
except Exception:
|
||||
path.unlink(missing_ok=True)
|
||||
raise
|
||||
stamp_cache(path, scene, self._font_resolver)
|
||||
return path
|
||||
|
||||
def _parse_record_document(
|
||||
@@ -488,7 +490,7 @@ class TemplateStore:
|
||||
for record in records:
|
||||
expected.add(record["thumbnail"])
|
||||
path = self.thumbnails_dir / record["thumbnail"]
|
||||
if not path.exists():
|
||||
if not path.exists() or cache_stale(path, record["scene"], self._font_resolver):
|
||||
try:
|
||||
self._write_thumbnail(record["id"], record["digest"], record["scene"])
|
||||
except Exception:
|
||||
@@ -500,6 +502,8 @@ class TemplateStore:
|
||||
path.unlink(missing_ok=True)
|
||||
elif THUMBNAIL_RE.fullmatch(path.name) and path.name not in expected:
|
||||
path.unlink(missing_ok=True)
|
||||
elif path.name.endswith(STAMP_SUFFIX) and THUMBNAIL_RE.fullmatch(path.name[:-len(STAMP_SUFFIX)]) and path.name[:-len(STAMP_SUFFIX)] not in expected:
|
||||
path.unlink(missing_ok=True)
|
||||
|
||||
def _ensure_unique_name(self, name: str, exclude_id: str | None = None) -> None:
|
||||
key = name.casefold()
|
||||
@@ -518,7 +522,7 @@ class TemplateStore:
|
||||
"updated_at": record["updated_at"],
|
||||
"digest": record["digest"],
|
||||
"revision": record_revision(record),
|
||||
"thumbnail_url": f"/api/templates/{record['id']}/thumbnail?v={record['digest']}",
|
||||
"thumbnail_url": versioned_url(f"/api/templates/{record['id']}/thumbnail?v={record['digest']}", record["scene"], self._font_resolver),
|
||||
"size_bytes": size_bytes,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user