@next2d/builder 0.1.8 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +59 -0
- package/dist/cli.js +141 -0
- package/dist/constants.d.ts +53 -0
- package/dist/constants.js +64 -0
- package/dist/context.d.ts +29 -0
- package/dist/context.js +19 -0
- package/dist/electron.d.ts +10 -0
- package/dist/electron.js +142 -0
- package/dist/index.js +18 -790
- package/dist/native.d.ts +27 -0
- package/dist/native.js +123 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +40 -0
- package/dist/web.d.ts +18 -0
- package/dist/web.js +70 -0
- package/dist/xbox.d.ts +62 -0
- package/dist/xbox.js +426 -0
- package/package.json +11 -9
- package/templates/xbox/CMakeLists.txt +47 -11
- package/templates/xbox/README.md +18 -0
- package/templates/xbox/js/bootstrap.js +120 -0
- package/templates/xbox/src/AssetLoader.cpp +115 -12
- package/templates/xbox/src/AssetLoader.h +4 -0
- package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
- package/templates/xbox/src/EmbeddedAssets.h +60 -0
- package/templates/xbox/src/EventLoop.cpp +0 -15
- package/templates/xbox/src/bindings/Audio.cpp +118 -22
- package/templates/xbox/src/bindings/Canvas.cpp +32 -2
- package/templates/xbox/src/bindings/Canvas2D.cpp +495 -39
- package/templates/xbox/src/bindings/Console.cpp +6 -1
- package/templates/xbox/src/bindings/DomShims.cpp +18 -2
- package/templates/xbox/src/bindings/EventTarget.cpp +9 -0
- package/templates/xbox/src/bindings/Image.cpp +83 -57
- package/templates/xbox/src/bindings/Polyfills.cpp +671 -236
- package/templates/xbox/src/bindings/RasterCore.h +105 -1
- package/templates/xbox/src/bindings/Video.cpp +57 -3
- package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +29 -3
- package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +4 -1
- package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
- package/templates/xbox/src/gpu/DawnContext.cpp +167 -16
- package/templates/xbox/src/main.cpp +385 -16
- package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
- package/templates/xbox/src/platform/AudioDecoder.h +18 -0
- package/templates/xbox/src/platform/AudioEngine.cpp +28 -3
- package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
- package/templates/xbox/src/platform/DecodeQueue.h +27 -0
- package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
- package/templates/xbox/src/platform/ImageDecoder.h +18 -0
- package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
- package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
- package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
- package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
- package/templates/xbox/src/v8/CodeCache.h +223 -0
- package/templates/xbox/src/v8/V8Runtime.cpp +34 -33
- package/templates/xbox/src/v8/V8Util.h +54 -4
- package/templates/xbox/src/worker/WorkerRuntime.cpp +165 -65
- package/templates/xbox/src/worker/WorkerRuntime.h +23 -3
- package/templates/xbox/tests/check_local.sh +5 -1
- package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
- package/templates/xbox/tests/raster_test.cpp +50 -0
- package/templates/xbox/tests/windows_platform_test.cpp +75 -3
- package/templates/xbox/third_party/LICENSES.md +14 -0
- package/templates/xbox/third_party/dr_mp3.h +5412 -0
- package/templates/xbox/third_party/dr_wav.h +9131 -0
- package/templates/xbox/third_party/stb_image.h +7988 -0
- package/templates/xbox/third_party/stb_truetype.h +5079 -0
- package/templates/xbox/third_party/stb_vorbis.c +5584 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
#include "AudioEngine.h"
|
|
2
|
+
#include "AudioDecoder.h"
|
|
2
3
|
|
|
3
4
|
#include <xaudio2.h>
|
|
5
|
+
#include <iostream>
|
|
6
|
+
|
|
7
|
+
// Media Foundation / shlwapi はコンソール (Game Core OS) に無いデスクトップ専用 API。
|
|
8
|
+
// 第一経路は AudioDecoder (dr_mp3/dr_wav/stb_vorbis) で、MF は AAC 等の
|
|
9
|
+
// デスクトップ限定フォールバックのみ。XAudio2 は両プラットフォームで使える。
|
|
10
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
4
11
|
#include <mfapi.h>
|
|
5
12
|
#include <mfidl.h>
|
|
6
13
|
#include <mfreadwrite.h>
|
|
7
14
|
#include <shlwapi.h>
|
|
8
15
|
#include <wrl/client.h>
|
|
9
|
-
#include <iostream>
|
|
10
16
|
|
|
11
17
|
#pragma comment(lib, "mfplat.lib")
|
|
12
18
|
#pragma comment(lib, "mfreadwrite.lib")
|
|
@@ -14,6 +20,7 @@
|
|
|
14
20
|
#pragma comment(lib, "shlwapi.lib")
|
|
15
21
|
|
|
16
22
|
using Microsoft::WRL::ComPtr;
|
|
23
|
+
#endif
|
|
17
24
|
|
|
18
25
|
namespace next2d {
|
|
19
26
|
|
|
@@ -97,15 +104,19 @@ AudioEngine::~AudioEngine()
|
|
|
97
104
|
if (xaudio_) {
|
|
98
105
|
xaudio_->Release();
|
|
99
106
|
}
|
|
107
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
100
108
|
MFShutdown();
|
|
109
|
+
#endif
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
bool AudioEngine::Initialize()
|
|
104
113
|
{
|
|
114
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
105
115
|
if (FAILED(MFStartup(MF_VERSION))) {
|
|
106
116
|
std::cerr << "[Audio] MFStartup failed" << std::endl;
|
|
107
117
|
return false;
|
|
108
118
|
}
|
|
119
|
+
#endif
|
|
109
120
|
if (FAILED(XAudio2Create(&xaudio_, 0, XAUDIO2_DEFAULT_PROCESSOR))) {
|
|
110
121
|
std::cerr << "[Audio] XAudio2Create failed" << std::endl;
|
|
111
122
|
return false;
|
|
@@ -119,8 +130,21 @@ bool AudioEngine::Initialize()
|
|
|
119
130
|
|
|
120
131
|
bool AudioEngine::Decode(const std::vector<uint8_t>& input, PcmBuffer& out)
|
|
121
132
|
{
|
|
122
|
-
//
|
|
123
|
-
//
|
|
133
|
+
// 第一候補: ポータブルデコーダ (dr_mp3/dr_wav/stb_vorbis)。
|
|
134
|
+
// コンソール (Game Core) には Media Foundation が無いため、共通経路を先に
|
|
135
|
+
// 通すことで PC 検証と実機の挙動を一致させる。
|
|
136
|
+
if (DecodeAudio(input, out)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#if NEXT2D_XBOX_CONSOLE
|
|
141
|
+
// コンソール: MF が無いため共通デコーダで扱えない形式は失敗とする
|
|
142
|
+
// (呼び出し側の graceful degradation = 無音バッファで進行)。
|
|
143
|
+
return false;
|
|
144
|
+
#else
|
|
145
|
+
// フォールバック (デスクトップのみ): Media Foundation の SourceReader。
|
|
146
|
+
// AAC/WMA 等、MP3/WAV/OGG 以外の形式への保険。
|
|
147
|
+
// メモリストリームから 32bit float PCM へ変換して読み出す。
|
|
124
148
|
ComPtr<IStream> stream =
|
|
125
149
|
SHCreateMemStream(input.data(), static_cast<UINT>(input.size()));
|
|
126
150
|
if (!stream) {
|
|
@@ -180,6 +204,7 @@ bool AudioEngine::Decode(const std::vector<uint8_t>& input, PcmBuffer& out)
|
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
return !out.samples.empty();
|
|
207
|
+
#endif // NEXT2D_XBOX_CONSOLE
|
|
183
208
|
}
|
|
184
209
|
|
|
185
210
|
std::shared_ptr<AudioVoice> AudioEngine::CreateVoice(std::shared_ptr<PcmBuffer> buffer)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#include "DecodeQueue.h"
|
|
2
|
+
|
|
3
|
+
#ifdef _WIN32
|
|
4
|
+
#include <objbase.h>
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <condition_variable>
|
|
9
|
+
#include <deque>
|
|
10
|
+
#include <mutex>
|
|
11
|
+
#include <thread>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
namespace next2d::decodequeue {
|
|
15
|
+
|
|
16
|
+
namespace {
|
|
17
|
+
|
|
18
|
+
struct Job {
|
|
19
|
+
std::function<void()> work;
|
|
20
|
+
std::function<void()> complete;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
struct Queue {
|
|
24
|
+
std::mutex mutex;
|
|
25
|
+
std::condition_variable cv;
|
|
26
|
+
std::deque<Job> pending; // 未処理 (プールスレッドが取る)
|
|
27
|
+
std::deque<std::function<void()>> done; // work 完了済み (Pump が実行)
|
|
28
|
+
std::vector<std::thread> threads;
|
|
29
|
+
bool stop = false;
|
|
30
|
+
|
|
31
|
+
void ThreadMain()
|
|
32
|
+
{
|
|
33
|
+
#ifdef _WIN32
|
|
34
|
+
// WIC / Media Foundation は COM を要する。プールスレッド毎に MTA で初期化する
|
|
35
|
+
// (per-call 実装なので MTA スレッド間で共有状態は無い)。
|
|
36
|
+
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
37
|
+
#endif
|
|
38
|
+
for (;;) {
|
|
39
|
+
Job job;
|
|
40
|
+
{
|
|
41
|
+
std::unique_lock<std::mutex> lock(mutex);
|
|
42
|
+
cv.wait(lock, [this] { return stop || !pending.empty(); });
|
|
43
|
+
if (stop) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
job = std::move(pending.front());
|
|
47
|
+
pending.pop_front();
|
|
48
|
+
}
|
|
49
|
+
if (job.work) {
|
|
50
|
+
job.work();
|
|
51
|
+
}
|
|
52
|
+
{
|
|
53
|
+
std::lock_guard<std::mutex> lock(mutex);
|
|
54
|
+
done.push_back(std::move(job.complete));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
#ifdef _WIN32
|
|
58
|
+
CoUninitialize();
|
|
59
|
+
#endif
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
void EnsureThreads()
|
|
63
|
+
{
|
|
64
|
+
if (!threads.empty()) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// デコードは CPU バウンド。コア数の半分 (2〜4) で遷移時のバースト
|
|
68
|
+
// (画像数十枚 + BGM) を並列処理しつつ、メイン/GC スレッドを圧迫しない。
|
|
69
|
+
const unsigned hw = std::thread::hardware_concurrency();
|
|
70
|
+
const unsigned count = std::clamp(hw / 2u, 2u, 4u);
|
|
71
|
+
threads.reserve(count);
|
|
72
|
+
for (unsigned i = 0; i < count; ++i) {
|
|
73
|
+
threads.emplace_back([this] { ThreadMain(); });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
Queue& Q()
|
|
79
|
+
{
|
|
80
|
+
static Queue q;
|
|
81
|
+
return q;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
} // namespace
|
|
85
|
+
|
|
86
|
+
void Submit(std::function<void()> work, std::function<void()> complete)
|
|
87
|
+
{
|
|
88
|
+
Queue& q = Q();
|
|
89
|
+
{
|
|
90
|
+
std::lock_guard<std::mutex> lock(q.mutex);
|
|
91
|
+
if (q.stop) {
|
|
92
|
+
return; // Shutdown 後の Submit は破棄 (終了処理中のロード要求)
|
|
93
|
+
}
|
|
94
|
+
q.EnsureThreads();
|
|
95
|
+
q.pending.push_back({std::move(work), std::move(complete)});
|
|
96
|
+
}
|
|
97
|
+
q.cv.notify_one();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
void Pump()
|
|
101
|
+
{
|
|
102
|
+
Queue& q = Q();
|
|
103
|
+
std::deque<std::function<void()>> ready;
|
|
104
|
+
{
|
|
105
|
+
std::lock_guard<std::mutex> lock(q.mutex);
|
|
106
|
+
ready.swap(q.done);
|
|
107
|
+
}
|
|
108
|
+
for (auto& complete : ready) {
|
|
109
|
+
if (complete) {
|
|
110
|
+
complete();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
void Shutdown()
|
|
116
|
+
{
|
|
117
|
+
Queue& q = Q();
|
|
118
|
+
{
|
|
119
|
+
std::lock_guard<std::mutex> lock(q.mutex);
|
|
120
|
+
q.stop = true;
|
|
121
|
+
}
|
|
122
|
+
q.cv.notify_all();
|
|
123
|
+
for (auto& t : q.threads) {
|
|
124
|
+
if (t.joinable()) {
|
|
125
|
+
t.join();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
q.threads.clear();
|
|
129
|
+
// 未実行の work / complete を破棄する。complete は v8::Global を捕捉して
|
|
130
|
+
// いるため、この関数は Isolate 破棄前に呼ばれる必要がある (main.cpp の後始末順)。
|
|
131
|
+
std::lock_guard<std::mutex> lock(q.mutex);
|
|
132
|
+
q.pending.clear();
|
|
133
|
+
q.done.clear();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
} // namespace next2d::decodequeue
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// DecodeQueue: リソースデコード用のバックグラウンドスレッドプール。
|
|
2
|
+
//
|
|
3
|
+
// 画像 (WIC) / 音声 (Media Foundation) のデコードはメインスレッドで同期実行すると
|
|
4
|
+
// 画面遷移時に 1 秒超のフレームヒッチになる (--perf 計測で特定)。work を
|
|
5
|
+
// プールスレッドへ逃がし、complete をメインループの Pump() で実行することで
|
|
6
|
+
// ヒッチを解消し、複数リソースの並列デコードでロード時間も短縮する。
|
|
7
|
+
// スレッドは Xbox で許可されている (禁止は動的コード生成のみ)。
|
|
8
|
+
//
|
|
9
|
+
// 契約:
|
|
10
|
+
// - work : プールスレッドで実行される。V8 / JS ヒープに触れてはならない。
|
|
11
|
+
// (WIC/MF は per-call 実装で COM はスレッド毎に MTA 初期化済み)
|
|
12
|
+
// - complete: work 完了後、メインスレッドの Pump() から実行される。V8 可。
|
|
13
|
+
// (v8::Global を捕捉してよい。Pump は isolate/コンテキストスコープ外で
|
|
14
|
+
// 呼ばれるため、complete 側で HandleScope/Context::Scope を張ること)
|
|
15
|
+
// - Shutdown: 全スレッドを join し、未実行の work / complete を破棄する。
|
|
16
|
+
// 捕捉された v8::Global の破棄があるため Isolate 破棄前に呼ぶこと。
|
|
17
|
+
#pragma once
|
|
18
|
+
|
|
19
|
+
#include <functional>
|
|
20
|
+
|
|
21
|
+
namespace next2d::decodequeue {
|
|
22
|
+
|
|
23
|
+
void Submit(std::function<void()> work, std::function<void()> complete);
|
|
24
|
+
void Pump();
|
|
25
|
+
void Shutdown();
|
|
26
|
+
|
|
27
|
+
} // namespace next2d::decodequeue
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#include "ImageDecoder.h"
|
|
2
|
+
|
|
3
|
+
// stb_image: public domain のヘッダオンリー画像デコーダ (third_party/)。
|
|
4
|
+
// メモリ入力のみ使用するため stdio を切る。
|
|
5
|
+
#define STB_IMAGE_IMPLEMENTATION
|
|
6
|
+
#define STBI_NO_STDIO
|
|
7
|
+
#include "third_party/stb_image.h"
|
|
8
|
+
|
|
9
|
+
#if defined(_WIN32) && !NEXT2D_XBOX_CONSOLE
|
|
10
|
+
#include "WicDecoder.h"
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
#include <cstring>
|
|
14
|
+
|
|
15
|
+
namespace next2d {
|
|
16
|
+
|
|
17
|
+
bool DecodeImage(const std::vector<uint8_t>& input, DecodedImage& out)
|
|
18
|
+
{
|
|
19
|
+
if (input.empty()) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 1) stb_image (PNG/JPEG/GIF/BMP/TGA/PSD)。コンソールでも動く共通経路。
|
|
24
|
+
int w = 0, h = 0, comp = 0;
|
|
25
|
+
stbi_uc* pixels = stbi_load_from_memory(
|
|
26
|
+
input.data(), static_cast<int>(input.size()), &w, &h, &comp, 4);
|
|
27
|
+
if (pixels) {
|
|
28
|
+
out.width = static_cast<uint32_t>(w);
|
|
29
|
+
out.height = static_cast<uint32_t>(h);
|
|
30
|
+
out.rgba.assign(pixels, pixels + static_cast<size_t>(w) * h * 4);
|
|
31
|
+
stbi_image_free(pixels);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#if defined(_WIN32) && !NEXT2D_XBOX_CONSOLE
|
|
36
|
+
// 2) デスクトップのみ: WIC フォールバック (TIFF/HEIF 等 stb 非対応形式の保険)。
|
|
37
|
+
// コンソールには WIC が無いためこの経路はコンパイルされない。
|
|
38
|
+
return DecodeImageWithWIC(input, out);
|
|
39
|
+
#else
|
|
40
|
+
return false;
|
|
41
|
+
#endif
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
} // namespace next2d
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 画像デコード (エンコード済みバイト列 -> RGBA8)。
|
|
2
|
+
//
|
|
3
|
+
// 第一候補は stb_image (PNG/JPEG/GIF/BMP、全プラットフォーム共通)。
|
|
4
|
+
// コンソール (Game Core OS) には WIC が存在しないため、WIC 依存を第一経路から
|
|
5
|
+
// 外すことで PC 検証と実機の挙動を一致させる。デスクトップのみ、stb が扱えない
|
|
6
|
+
// 形式 (TIFF/HEIF 等) への保険として WIC フォールバックを残す。
|
|
7
|
+
#pragma once
|
|
8
|
+
|
|
9
|
+
#include "ImageTypes.h"
|
|
10
|
+
|
|
11
|
+
#include <cstdint>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
namespace next2d {
|
|
15
|
+
|
|
16
|
+
bool DecodeImage(const std::vector<uint8_t>& input, DecodedImage& out);
|
|
17
|
+
|
|
18
|
+
} // namespace next2d
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#include "StbTextRasterizer.h"
|
|
2
|
+
|
|
3
|
+
#define STB_TRUETYPE_IMPLEMENTATION
|
|
4
|
+
#define STBTT_STATIC
|
|
5
|
+
#include "third_party/stb_truetype.h"
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cctype>
|
|
9
|
+
#include <cmath>
|
|
10
|
+
#include <cstring>
|
|
11
|
+
|
|
12
|
+
namespace next2d::stbtext {
|
|
13
|
+
|
|
14
|
+
namespace {
|
|
15
|
+
|
|
16
|
+
struct Font {
|
|
17
|
+
std::string name_lower; // 登録名 (小文字)
|
|
18
|
+
bool bold = false; // 名前に "bold" を含むか
|
|
19
|
+
std::vector<uint8_t> data; // TTF/OTF 本体 (fontinfo が参照し続けるため保持)
|
|
20
|
+
stbtt_fontinfo info = {};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 登録フォント。ゲーム起動時に main が登録し、以後は読み取りのみ
|
|
24
|
+
// (worker も協調シングルスレッドなので排他は不要)。
|
|
25
|
+
std::vector<Font>& Fonts()
|
|
26
|
+
{
|
|
27
|
+
static std::vector<Font> fonts;
|
|
28
|
+
return fonts;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
std::string ToLower(std::string s)
|
|
32
|
+
{
|
|
33
|
+
std::transform(s.begin(), s.end(), s.begin(),
|
|
34
|
+
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// CSS font 文字列から px サイズ / bold / ファミリ名リストを取り出す。
|
|
39
|
+
// 例: "bold 24px 'Noto Sans JP', sans-serif"
|
|
40
|
+
struct CssFont {
|
|
41
|
+
double px = 16.0;
|
|
42
|
+
bool bold = false;
|
|
43
|
+
std::vector<std::string> families; // 小文字・引用符除去済み
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
CssFont ParseCssFont(const std::string& css)
|
|
47
|
+
{
|
|
48
|
+
CssFont out;
|
|
49
|
+
const std::string lower = ToLower(css);
|
|
50
|
+
|
|
51
|
+
if (lower.find("bold") != std::string::npos) {
|
|
52
|
+
out.bold = true;
|
|
53
|
+
} else {
|
|
54
|
+
// 数値ウェイト (600 以上を bold 扱い)。"NNNpx" と混同しないよう
|
|
55
|
+
// 独立トークンのみ見る。
|
|
56
|
+
size_t pos = 0;
|
|
57
|
+
while (pos < lower.size()) {
|
|
58
|
+
const size_t start = lower.find_first_not_of(" \t", pos);
|
|
59
|
+
if (start == std::string::npos) break;
|
|
60
|
+
size_t end = lower.find_first_of(" \t", start);
|
|
61
|
+
if (end == std::string::npos) end = lower.size();
|
|
62
|
+
const std::string token = lower.substr(start, end - start);
|
|
63
|
+
char* endp = nullptr;
|
|
64
|
+
const long w = std::strtol(token.c_str(), &endp, 10);
|
|
65
|
+
if (endp && *endp == '\0' && w >= 600 && w <= 1000) {
|
|
66
|
+
out.bold = true;
|
|
67
|
+
}
|
|
68
|
+
pos = end;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// "NNpx" (小数許容)
|
|
73
|
+
const size_t px_pos = lower.find("px");
|
|
74
|
+
if (px_pos != std::string::npos) {
|
|
75
|
+
size_t num_start = px_pos;
|
|
76
|
+
while (num_start > 0 &&
|
|
77
|
+
(std::isdigit(static_cast<unsigned char>(lower[num_start - 1])) ||
|
|
78
|
+
lower[num_start - 1] == '.')) {
|
|
79
|
+
--num_start;
|
|
80
|
+
}
|
|
81
|
+
if (num_start < px_pos) {
|
|
82
|
+
out.px = std::strtod(lower.substr(num_start, px_pos - num_start).c_str(), nullptr);
|
|
83
|
+
}
|
|
84
|
+
if (out.px <= 0) {
|
|
85
|
+
out.px = 16.0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// px の後ろがファミリリスト (カンマ区切り、引用符除去)
|
|
89
|
+
size_t fam_start = lower.find_first_of(" \t", px_pos);
|
|
90
|
+
if (fam_start != std::string::npos) {
|
|
91
|
+
std::string families = lower.substr(fam_start + 1);
|
|
92
|
+
size_t pos = 0;
|
|
93
|
+
while (pos <= families.size()) {
|
|
94
|
+
size_t comma = families.find(',', pos);
|
|
95
|
+
if (comma == std::string::npos) comma = families.size();
|
|
96
|
+
std::string fam = families.substr(pos, comma - pos);
|
|
97
|
+
// trim + 引用符除去
|
|
98
|
+
const auto is_trim = [](char c) {
|
|
99
|
+
return c == ' ' || c == '\t' || c == '\'' || c == '"';
|
|
100
|
+
};
|
|
101
|
+
while (!fam.empty() && is_trim(fam.front())) fam.erase(fam.begin());
|
|
102
|
+
while (!fam.empty() && is_trim(fam.back())) fam.pop_back();
|
|
103
|
+
if (!fam.empty()) {
|
|
104
|
+
out.families.push_back(fam);
|
|
105
|
+
}
|
|
106
|
+
pos = comma + 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// wstring -> Unicode コードポイント列。
|
|
114
|
+
// Windows は wchar_t=UTF-16 (サロゲートペア復号)、他は UTF-32。
|
|
115
|
+
std::vector<uint32_t> ToCodepoints(const std::wstring& text)
|
|
116
|
+
{
|
|
117
|
+
std::vector<uint32_t> cps;
|
|
118
|
+
cps.reserve(text.size());
|
|
119
|
+
if (sizeof(wchar_t) == 2) {
|
|
120
|
+
for (size_t i = 0; i < text.size(); ++i) {
|
|
121
|
+
const uint32_t c = static_cast<uint16_t>(text[i]);
|
|
122
|
+
if (c >= 0xD800 && c <= 0xDBFF && i + 1 < text.size()) {
|
|
123
|
+
const uint32_t lo = static_cast<uint16_t>(text[i + 1]);
|
|
124
|
+
if (lo >= 0xDC00 && lo <= 0xDFFF) {
|
|
125
|
+
cps.push_back(0x10000 + ((c - 0xD800) << 10) + (lo - 0xDC00));
|
|
126
|
+
++i;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
cps.push_back(c);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
for (wchar_t c : text) {
|
|
134
|
+
cps.push_back(static_cast<uint32_t>(c));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return cps;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// css のファミリ指定と bold から最適な登録フォントを選ぶ。
|
|
141
|
+
// 優先: ファミリ一致 + bold 一致 > ファミリ一致 > bold 一致 > 先頭。
|
|
142
|
+
const Font* SelectFont(const CssFont& css)
|
|
143
|
+
{
|
|
144
|
+
const auto& fonts = Fonts();
|
|
145
|
+
if (fonts.empty()) {
|
|
146
|
+
return nullptr;
|
|
147
|
+
}
|
|
148
|
+
const Font* family_match = nullptr;
|
|
149
|
+
for (const auto& fam : css.families) {
|
|
150
|
+
for (const auto& f : fonts) {
|
|
151
|
+
if (f.name_lower.find(fam) == std::string::npos &&
|
|
152
|
+
fam.find(f.name_lower) == std::string::npos) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (f.bold == css.bold) {
|
|
156
|
+
return &f; // ファミリ+bold 完全一致
|
|
157
|
+
}
|
|
158
|
+
if (!family_match) {
|
|
159
|
+
family_match = &f;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (family_match) {
|
|
164
|
+
return family_match;
|
|
165
|
+
}
|
|
166
|
+
for (const auto& f : fonts) {
|
|
167
|
+
if (f.bold == css.bold) {
|
|
168
|
+
return &f;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return &fonts.front();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 計測とラスタライズで共有するレイアウト計算。
|
|
175
|
+
struct Layout {
|
|
176
|
+
const Font* font = nullptr;
|
|
177
|
+
float scale = 0;
|
|
178
|
+
int ascent = 0, descent = 0; // フォント単位
|
|
179
|
+
double width = 0; // px
|
|
180
|
+
std::vector<uint32_t> cps;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
bool ComputeLayout(const std::string& css_font, const std::wstring& text, Layout& lo)
|
|
184
|
+
{
|
|
185
|
+
const CssFont css = ParseCssFont(css_font);
|
|
186
|
+
lo.font = SelectFont(css);
|
|
187
|
+
if (!lo.font) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
lo.scale = stbtt_ScaleForPixelHeight(&lo.font->info, static_cast<float>(css.px));
|
|
191
|
+
int line_gap = 0;
|
|
192
|
+
stbtt_GetFontVMetrics(&lo.font->info, &lo.ascent, &lo.descent, &line_gap);
|
|
193
|
+
lo.cps = ToCodepoints(text);
|
|
194
|
+
|
|
195
|
+
double x = 0;
|
|
196
|
+
for (size_t i = 0; i < lo.cps.size(); ++i) {
|
|
197
|
+
int advance = 0, lsb = 0;
|
|
198
|
+
stbtt_GetCodepointHMetrics(&lo.font->info, static_cast<int>(lo.cps[i]), &advance, &lsb);
|
|
199
|
+
x += advance * lo.scale;
|
|
200
|
+
if (i + 1 < lo.cps.size()) {
|
|
201
|
+
x += stbtt_GetCodepointKernAdvance(
|
|
202
|
+
&lo.font->info,
|
|
203
|
+
static_cast<int>(lo.cps[i]),
|
|
204
|
+
static_cast<int>(lo.cps[i + 1])) * lo.scale;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
lo.width = x;
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
} // namespace
|
|
212
|
+
|
|
213
|
+
bool RegisterFont(const std::string& name, std::vector<uint8_t> data)
|
|
214
|
+
{
|
|
215
|
+
if (data.empty()) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
Font font;
|
|
219
|
+
font.name_lower = ToLower(name);
|
|
220
|
+
font.bold = font.name_lower.find("bold") != std::string::npos;
|
|
221
|
+
font.data = std::move(data);
|
|
222
|
+
const int offset = stbtt_GetFontOffsetForIndex(font.data.data(), 0);
|
|
223
|
+
if (offset < 0 || !stbtt_InitFont(&font.info, font.data.data(), offset)) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
Fonts().push_back(std::move(font));
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
bool HasFonts()
|
|
231
|
+
{
|
|
232
|
+
return !Fonts().empty();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
bool MeasureText(const std::string& css_font, const std::wstring& text,
|
|
236
|
+
TextMetricsInfo& out)
|
|
237
|
+
{
|
|
238
|
+
Layout lo;
|
|
239
|
+
if (!ComputeLayout(css_font, text, lo)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
out.width = lo.width;
|
|
243
|
+
out.ascent = lo.ascent * lo.scale;
|
|
244
|
+
out.descent = -lo.descent * lo.scale; // stbtt の descent は負値
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
bool RasterizeText(const std::string& css_font, const std::wstring& text,
|
|
249
|
+
uint8_t r, uint8_t g, uint8_t b, TextBitmap& out)
|
|
250
|
+
{
|
|
251
|
+
if (text.empty()) {
|
|
252
|
+
return false; // DirectWrite 実装と同じ契約 (空文字は false)
|
|
253
|
+
}
|
|
254
|
+
Layout lo;
|
|
255
|
+
if (!ComputeLayout(css_font, text, lo)) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const double ascent_px = lo.ascent * lo.scale;
|
|
260
|
+
const double descent_px = -lo.descent * lo.scale;
|
|
261
|
+
const int width = std::max(1, static_cast<int>(std::ceil(lo.width)) + 2);
|
|
262
|
+
const int height = std::max(1, static_cast<int>(std::ceil(ascent_px + descent_px)) + 2);
|
|
263
|
+
const int baseline = static_cast<int>(std::ceil(ascent_px)) + 1;
|
|
264
|
+
|
|
265
|
+
// 8bit カバレッジへ全グリフを合成してから RGBA 化する
|
|
266
|
+
std::vector<uint8_t> coverage(static_cast<size_t>(width) * height, 0);
|
|
267
|
+
double pen_x = 1.0;
|
|
268
|
+
for (size_t i = 0; i < lo.cps.size(); ++i) {
|
|
269
|
+
const int cp = static_cast<int>(lo.cps[i]);
|
|
270
|
+
int advance = 0, lsb = 0;
|
|
271
|
+
stbtt_GetCodepointHMetrics(&lo.font->info, cp, &advance, &lsb);
|
|
272
|
+
|
|
273
|
+
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
|
274
|
+
const float shift_x =
|
|
275
|
+
static_cast<float>(pen_x + lsb * lo.scale - std::floor(pen_x + lsb * lo.scale));
|
|
276
|
+
stbtt_GetCodepointBitmapBoxSubpixel(
|
|
277
|
+
&lo.font->info, cp, lo.scale, lo.scale, shift_x, 0, &x0, &y0, &x1, &y1);
|
|
278
|
+
|
|
279
|
+
const int gw = x1 - x0;
|
|
280
|
+
const int gh = y1 - y0;
|
|
281
|
+
if (gw > 0 && gh > 0) {
|
|
282
|
+
std::vector<uint8_t> glyph(static_cast<size_t>(gw) * gh);
|
|
283
|
+
stbtt_MakeCodepointBitmapSubpixel(
|
|
284
|
+
&lo.font->info, glyph.data(), gw, gh, gw,
|
|
285
|
+
lo.scale, lo.scale, shift_x, 0, cp);
|
|
286
|
+
|
|
287
|
+
const int dst_x = static_cast<int>(std::floor(pen_x + lsb * lo.scale));
|
|
288
|
+
const int dst_y = baseline + y0;
|
|
289
|
+
for (int yy = 0; yy < gh; ++yy) {
|
|
290
|
+
const int py = dst_y + yy;
|
|
291
|
+
if (py < 0 || py >= height) continue;
|
|
292
|
+
for (int xx = 0; xx < gw; ++xx) {
|
|
293
|
+
const int px = dst_x + xx;
|
|
294
|
+
if (px < 0 || px >= width) continue;
|
|
295
|
+
uint8_t& dst = coverage[static_cast<size_t>(py) * width + px];
|
|
296
|
+
dst = std::max(dst, glyph[static_cast<size_t>(yy) * gw + xx]);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
pen_x += advance * lo.scale;
|
|
302
|
+
if (i + 1 < lo.cps.size()) {
|
|
303
|
+
pen_x += stbtt_GetCodepointKernAdvance(
|
|
304
|
+
&lo.font->info, cp, static_cast<int>(lo.cps[i + 1])) * lo.scale;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
out.width = width;
|
|
309
|
+
out.height = height;
|
|
310
|
+
out.baseline = baseline;
|
|
311
|
+
out.rgba.assign(static_cast<size_t>(width) * height * 4, 0);
|
|
312
|
+
for (size_t i = 0; i < coverage.size(); ++i) {
|
|
313
|
+
const uint8_t a = coverage[i];
|
|
314
|
+
if (!a) continue;
|
|
315
|
+
uint8_t* dst = &out.rgba[i * 4];
|
|
316
|
+
dst[0] = r;
|
|
317
|
+
dst[1] = g;
|
|
318
|
+
dst[2] = b;
|
|
319
|
+
dst[3] = a; // straight alpha (TextBitmap の契約)
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
} // namespace next2d::stbtext
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// stb_truetype によるテキスト計測/ラスタライズ (全プラットフォーム共通)。
|
|
2
|
+
//
|
|
3
|
+
// コンソール (Game Core OS) には DirectWrite が無いため、TextRasterizer.cpp の
|
|
4
|
+
// コンソール分岐がこの実装へ委譲する (デスクトップは従来どおり DirectWrite)。
|
|
5
|
+
// システムフォントにも依存できないため、フォントは実行時に登録する:
|
|
6
|
+
// - 埋め込み pak 内の *.ttf/*.otf (ゲーム資材。main.cpp が起動時に登録)
|
|
7
|
+
// - exe 隣接の fonts/*.ttf (開発ビルド用)
|
|
8
|
+
// フォント未登録の場合は false を返し、呼び出し側 (Canvas2D) が近似値へ
|
|
9
|
+
// フォールバックする (TextRasterizer.h と同じ契約)。
|
|
10
|
+
//
|
|
11
|
+
// 制限 (DirectWrite との差): 合字/シェーピング無し (CJK は影響なし)、
|
|
12
|
+
// bold は登録フォントに bold バリアントがある場合のみ反映。
|
|
13
|
+
#pragma once
|
|
14
|
+
|
|
15
|
+
#include "TextRasterizer.h" // TextBitmap / TextMetricsInfo
|
|
16
|
+
|
|
17
|
+
#include <cstdint>
|
|
18
|
+
#include <string>
|
|
19
|
+
#include <vector>
|
|
20
|
+
|
|
21
|
+
namespace next2d::stbtext {
|
|
22
|
+
|
|
23
|
+
// TTF/OTF バイト列をフォントとして登録する (name はファイル名 stem 等の識別子。
|
|
24
|
+
// "bold" を含む名前は bold バリアントとして扱う)。不正なフォントは false。
|
|
25
|
+
bool RegisterFont(const std::string& name, std::vector<uint8_t> data);
|
|
26
|
+
|
|
27
|
+
// 登録済みフォントが 1 つ以上あるか。
|
|
28
|
+
bool HasFonts();
|
|
29
|
+
|
|
30
|
+
// css_font 例: "bold 24px Arial", "16px 'Noto Sans JP', sans-serif"
|
|
31
|
+
bool MeasureText(const std::string& css_font, const std::wstring& text,
|
|
32
|
+
TextMetricsInfo& out);
|
|
33
|
+
|
|
34
|
+
bool RasterizeText(const std::string& css_font, const std::wstring& text,
|
|
35
|
+
uint8_t r, uint8_t g, uint8_t b, TextBitmap& out);
|
|
36
|
+
|
|
37
|
+
} // namespace next2d::stbtext
|