Skip to content

Commit 9507f71

Browse files
committed
try fix css slow block render
1 parent 21ea91c commit 9507f71

File tree

5 files changed

+225
-19
lines changed

5 files changed

+225
-19
lines changed

docs/extra.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,19 @@ for (var i = 0; i < links.length; i++) {
1919
var to = lut[key];
2020
if (to !== undefined) {
2121
node.data = node.data.replace(key, to);
22-
} else {
23-
console.log({a: node.data});
2422
}
2523
}
2624
}
2725
}
26+
27+
var stylesheets = [
28+
'https://cdn.jsdelivr.net/npm/@fontsource/[email protected]/index.min.css',
29+
'https://cdn.jsdelivr.net/npm/[email protected]/css/jetbrains-mono.min.css',
30+
'https://cdn.jsdelivr.net/npm/@fontsource/[email protected]/chinese-simplified-500.min.css',
31+
];
32+
for (var i = 0; i < stylesheets.length; i++) {
33+
var link = document.createElement('link');
34+
link.setAttribute('rel', 'stylesheet');
35+
link.setAttribute('href', stylesheets[i]);
36+
document.head.appendChild(link);
37+
}

docs/unicode.md

+140-14
Original file line numberDiff line numberDiff line change
@@ -1541,11 +1541,18 @@ void u8print(std::string msg) {
15411541

15421542
> {{ icon.detail }} 此处 `static int dummy_init =` 是一种静态初始化钩子的小技巧,之后设计模式课程的单例模式中会详细讲解。
15431543
1544-
> {{ icon.detail }} 更多细节用法见官方文档:https://www.boost.org/doc/libs/1_81_0/libs/locale/doc/html/group__codepage.html
1545-
15461544
#### 更多功能?!
15471545

1548-
编码转换只是 `boost::locale::conv` 这个子模块下的一个小功能而已!`boost::locale` 还提供了更多功能,如按照地域语言规范格式化数字、货币、日期、时间等,下一小节中我们继续介绍。完全是 `std::locale` 的上位替代。
1546+
|函数|||
1547+
|----|--|--|
1548+
|utf_to_utf|UTF 系列|UTF 系列|
1549+
|from_utf|UTF 系列|杂牌字符编码|
1550+
|to_utf|杂牌字符编码|UTF 系列|
1551+
|between|杂牌字符编码|杂牌字符编码|
1552+
1553+
更多细节用法见官方文档:https://www.boost.org/doc/libs/1_81_0/libs/locale/doc/html/group__codepage.html
1554+
1555+
不可思议的是:编码转换只是 `boost::locale::conv` 这个子模块下的一个小功能而已!`boost::locale` 还提供了更多功能,如按照地域语言规范格式化数字、货币、日期、时间等,下一小节中我们继续介绍。完全是 `std::locale` 的上位替代。
15491556

15501557
> {{ icon.fun }} Boost 哪里都好,你想要的功能应有尽有。而且不需要 C++20,很低版本的 C++ 也能用。唯一缺点可能就是太肥了,编译慢。
15511558
@@ -1852,6 +1859,8 @@ zh_CN.UTF-8: 1
18521859
18531860
要注意的是,用户必须已经安装过该区域设置,程序才能使用 setlocale 设置,否则会出现找不到 locale 的错误。
18541861
1862+
> {{ icon.detail }} 这几乎导致你没法用除默认外的任何 locale,比如 `"zh_CN.UTF-8"`,因为你不能确定用户有没有安装他。但你可以用 `boost::locale::generator` 凭空生成一个系统里没有安装过的 locale,绕开标准库的限制,稍后介绍。
1863+
18551864
Linux 用户可以通过 修改 `/etc/locale.gen` 取消注释要启用的语言和编码格式,保存后,运行 `locale-gen` 即可安装所有没注释的语言。
18561865
18571866
```bash
@@ -2058,7 +2067,7 @@ int main() {
20582067
2024年 07月 19日 星期五 16时 01分
20592068
```
20602069

2061-
#### `std::locale` 对象
2070+
### `std::locale` 对象
20622071

20632072
C 语言的 `setlocale` 设置的是全局 locale,全局 locale 只有一个,一设就影响所有线程,非常沙雕。因此提倡“不要状态机要对象”的 C++,封装了 `std::locale` 对象。
20642073

@@ -2123,6 +2132,15 @@ Fri 19 Jul 2024 04:33:39 PM CST
21232132

21242133
> {{ icon.tip }} 关于 `"%c"``"%Y"` 这些格式化字符串的更多详细用法,参见 [`man strftime`](http://man7.org/linux/man-pages/man3/strftime.3.html)。我们作为字符编码的课程不再赘述,之后的时间与日期专题课也会稍微讲一下。
21252134
2135+
#### `boost::locale::generator` 凭空创建一个用户没安装过的 locale
2136+
2137+
```cpp
2138+
boost::locale::generator gen;
2139+
auto loc = gen("zh_CN.UTF-8");
2140+
boost::locale::date_time dt = boost::locale::date_time::now(loc);
2141+
std::cout << boost::locale::as::date(dt) << '\n';
2142+
```
2143+
21262144
## 宽字符流
21272145

21282146
之所以把宽字符流放到最后,是因为,首先 `iostream` 本来就是一个失败的设计。
@@ -2172,10 +2190,6 @@ std::string to_os_string(std::string const &u8s) {
21722190
21732191
这就是为什么宽字符流很糟糕,说是跨平台,跨了个寂寞。
21742192
2175-
### `wchar_t` 系列函数
2176-
2177-
TODO
2178-
21792193
### `std::wcout` 的使用坑点科普
21802194
21812195
#### `std::wcout` 必须设了 locale 才能用
@@ -2318,22 +2332,132 @@ C++ 真正的文本流实际上是宽字符流 `std::wifstream`,而指定编
23182332
23192333
> {{ icon.fun }} 理论上所有的程序都应该像这样,只不过是因为劳保教材从来不提,一口一个 `char []` 就是字符串,搞得 `wchar_t` 在除了 GNU 这种“体制内”环境之外,根本没人用了。现在为了处理中文字符,才闹出了 `char` 当 UTF-8 使这种招数,令人唏嘘。
23202334
2335+
总之,`.imbue(std::locale("zh_CN.GBK"))` 可以把 `GBK` 设为当前文本文件的编码格式,宽文件流将会按照这个编码和解码所有的字符串。
2336+
2337+
`std::locale` 的字符串构造函数,他的参数必须是用户系统里已经安装过的 locale(通过修改 `/etc/locale.gen``locale-gen` 命令安装)。但是,你无法确保用户的系统安装了 `"GBK"` locale。`std::locale("zh_CN.GBK")` 在没有安装 GBK 的用户电脑上运行就会抛出错误表示找不到该 locale。因此,如果要指定按 GBK 读取文件,不建议依赖系统中自带的 `std::locale("zh_CN.GBK"))`,而是调用 `boost::locale::generator` 就地生成一个 locale,这样程序无论系统有没有安装都能运行了:
2338+
2339+
```cpp
2340+
#include <boost/locale.hpp>
2341+
#include <fstream>
2342+
2343+
int main() {
2344+
std::wofstream fout;
2345+
boost::locale::generator gen;
2346+
std::locale loc = gen("zh_CN.GBK");
2347+
fout.imbue(loc);
2348+
fout << L"你好,世界\n"; // 以 GBK 编码写出文本文件
2349+
}
2350+
```
2351+
2352+
```
2353+
$ cat build/你好.txt
2354+
����
2355+
$ cat build/你好.txt | iconv -f GBK -t UTF-8
2356+
你好,世界
2357+
$
2358+
```
2359+
2360+
> {{ icon.detail }} 这是因为 `boost_locale` 链接了 `icu`,其内部包含了所有编码格式的字符映射表。`boost::locale::generator` 首先创建了一个 `std::locale`,然后通过虚函数重载的方式把 `std::locale` 对象中的 `std::codecvt` 替换成 `icu` 的映射表。从而让 `std::wofstream` 调用这个 `icu` 的映射函数,实现了 UTF-32 到 GBK 的转换。
2361+
2362+
此外,你还可以选择覆盖 locale 的部分方面 (facet),比如在文件编码时,我们只需要用 `"zh_CN.GBK"``LC_CTYPE` 方面就可以了,其他的例如时间格式、语言信息等,我们还是想保留默认的。为此,我们可以利用 locale 的“杂交”拷贝构造函数,保留老 locale 的绝大部分方面,只替换一个方面为新 locale 的:
2363+
2364+
```cpp
2365+
std::locale old_loc = std::locale(""); // 环境 locale
2366+
boost::locale::generator gen;
2367+
std::locale new_loc = gen("zh_CN.GBK"); // 全 GBK locale
2368+
std::locale loc = std::locale(old_loc, new_loc, std::locale::ctype); // 杂交:继承 old_loc 的其余全部,只替换掉 LC_CTYPE 部分为 new_loc 的
2369+
fout.imbue(loc);
2370+
```
2371+
23212372
### locale 用于字符编码转换
23222373

2323-
#### C 语言标准库的字符编码转换
2374+
```cpp
2375+
// 以 loc 规定的编码,把内码编码成外码
2376+
std::string narrow(std::locale const &loc, std::wstring const &wstr) {
2377+
// use_facet 函数获得 locale 在字符转换 方面的 facet
2378+
auto const &cvt = std::use_facet<Codecvt>(loc);
2379+
std::string str(wstr.size() * 4, '\0'); // 预留 4 倍空间
2380+
wchar_t const *from_next;
2381+
char *to_next;
2382+
std::mbstate_t state{};
2383+
auto res = cvt.in(state, wstr.data(), wstr.data() + wstr.size(), from_next, str.data(), str.data() + str.size(), to_next);
2384+
if (res == Codecvt::ok) {
2385+
// 转换成功
2386+
str.resize(to_next - str.data());
2387+
return str;
2388+
} else if (res == Codecvt::partial) {
2389+
// 转换部分成功
2390+
str.resize(to_next - str.data());
2391+
return str;
2392+
} else {
2393+
// 转换失败
2394+
return "";
2395+
}
2396+
}
23242397

2325-
TODO
2398+
// 以 loc 规定的编码,把外码解码成内码
2399+
std::wstring widen(std::locale const &loc, std::string const &str) {
2400+
// use_facet 函数获得 locale 在字符转换 方面的 facet
2401+
auto const &cvt = std::use_facet<Codecvt>(loc);
2402+
std::wstring wstr(str.size(), L'\0'); // 预留空间
2403+
char const *from_next;
2404+
wchar_t *to_next;
2405+
std::mbstate_t state{};
2406+
auto res = cvt.out(state, str.data(), str.data() + str.size(), from_next, wstr.data(), wstr.data() + wstr.size(), to_next);
2407+
if (res == Codecvt::ok) {
2408+
// 转换成功
2409+
wstr.resize(to_next - wstr.data());
2410+
return wstr;
2411+
} else if (res == Codecvt::partial) {
2412+
// 转换部分成功
2413+
wstr.resize(to_next - wstr.data());
2414+
return wstr;
2415+
} else {
2416+
// 转换失败
2417+
return L"";
2418+
}
2419+
}
2420+
```
23262421
2327-
#### C++ 标准库的字符编码转换
2422+
```cpp
2423+
std::wstring wstr = L"你好";
2424+
std::cout << narrow(std::locale("zh_CN.GBK"), wstr);
2425+
```
23282426

2329-
TODO
2427+
不过,我们都有更方便的 `boost::locale::conv` 了,还何必还用这么繁琐的 `std::locale` 呢?所以我是不推荐再用这破玩意,无论是易用性还是扩展性都是 Boost 完胜。
2428+
2429+
### C 语言中的 `wchar_t` 系列函数
2430+
2431+
对于所有的 `strcpy``strcmp``strlen` 这类 `str***` 系函数,都有一个相应的 `wcs***` 函数。
23302432

2331-
> `wchar_t``char16_t``char32_t` 之间的转换,可以用 `std::mbrtoc16``std::mbrtoc32``std::c16rtomb``std::c32rtomb` 函数
2433+
例如 `wcscpy``wcscmp``wcslen`
23322434

2333-
### C++ 字符串编码转换 `<codecvt>`
2435+
它们的原型如下:
2436+
2437+
```c
2438+
wchar_t *wcscpy(wchar_t *dest, const wchar_t *src);
2439+
int wcscmp(const wchar_t *s1, const wchar_t *s2);
2440+
size_t wcslen(const wchar_t *s);
2441+
```
2442+
2443+
它们的作用和 `str***` 系函数一样,但是它们操作的是 `wchar_t` 字符串。
2444+
2445+
对于所有的 `fputc`、`printf`,`fprintf`,`fgets` 这类操作文件的函数,也都有一个配套的 `fw***` 函数。
2446+
2447+
第一次使用过这些函数后,`FILE *` 将会被“宽化”(`fwiden`)。宽化的文件流今后将只能输入宽字符串。
2448+
2449+
> {{ icon.tip }} 但是,既然 C++ 已经有 `std::wstring`,就不建议再学 C 语言 `L'\0'` 结尾字符串了。
2450+
2451+
#### C 语言标准库的字符编码转换
23342452
23352453
TODO
23362454
2455+
#### C++ 标准库的字符编码转换 `<codecvt>`
2456+
2457+
`wchar_t`、`char16_t`、`char32_t` 与 `char` 之间的转换,可以用 `std::mbrtoc16`、`std::mbrtoc32`、`std::c16rtomb`、`std::c32rtomb` 函数。
2458+
2459+
然而,又臭又长,用封装好的 `boost::locale::utf_to_utf/from_utf/to_utf/between` 不香吗?
2460+
23372461
<!--
23382462
//=== 跨平台软件何去何从?
23392463
//
@@ -2935,6 +3059,8 @@ COW 字符串的缺点是:当你写多线程并发时,本来多线程只读
29353059
29363060
### 字符的显示宽度计算
29373061
3062+
TODO
3063+
29383064
### Grapheme
29393065
29403066
TODO

examples/locale_conv.cpp

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <boost/locale.hpp>
2+
#include <filesystem>
3+
#include <fstream>
4+
5+
// 外码类型:char
6+
// 内码类型:wchar_t
7+
// 状态类型:std::mbstate_t
8+
using Codecvt = std::codecvt<char, wchar_t, std::mbstate_t>;
9+
10+
// 以 loc 规定的编码,把内码编码成外码
11+
std::string narrow(std::locale const &loc, std::wstring const &wstr) {
12+
// use_facet 函数获得 locale 在字符转换 方面的 facet
13+
auto const &cvt = std::use_facet<Codecvt>(loc);
14+
std::string str(wstr.size() * 4, '\0');
15+
wchar_t const *from_next;
16+
char *to_next;
17+
std::mbstate_t state{};
18+
auto res = cvt.in(state, wstr.data(), wstr.data() + wstr.size(), from_next, str.data(), str.data() + str.size(), to_next);
19+
if (res == Codecvt::ok) {
20+
// 转换成功
21+
str.resize(to_next - str.data());
22+
return str;
23+
} else if (res == Codecvt::partial) {
24+
// 转换部分成功
25+
str.resize(to_next - str.data());
26+
return str;
27+
} else {
28+
// 转换失败
29+
return "";
30+
}
31+
}
32+
33+
// 以 loc 规定的编码,把外码编码成内码
34+
std::wstring widen(std::locale const &loc, std::string const &str) {
35+
// use_facet 函数获得 locale 在字符转换 方面的 facet
36+
auto const &cvt = std::use_facet<Codecvt>(loc);
37+
std::wstring wstr(str.size(), L'\0');
38+
char const *from_next;
39+
wchar_t *to_next;
40+
std::mbstate_t state{};
41+
auto res = cvt.out(state, str.data(), str.data() + str.size(), from_next, wstr.data(), wstr.data() + wstr.size(), to_next);
42+
if (res == Codecvt::ok) {
43+
// 转换成功
44+
wstr.resize(to_next - wstr.data());
45+
return wstr;
46+
} else if (res == Codecvt::partial) {
47+
// 转换部分成功
48+
wstr.resize(to_next - wstr.data());
49+
return wstr;
50+
} else {
51+
// 转换失败
52+
return L"";
53+
}
54+
}
55+
56+
int main() {
57+
std::wstring s = L"日本語";
58+
std::locale loc = std::locale("");
59+
// 用 facet 来转换字符串
60+
myfacet.i(s[0]); // 转换宽字符到内码
61+
myfacet.narrow(s[0], '?'); // 转换内码到宽字符
62+
}

examples/locale_gen.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <boost/locale.hpp>
2+
#include <filesystem>
3+
#include <fstream>
4+
5+
int main() {
6+
std::wofstream fout(std::filesystem::path(L"你好.txt"));
7+
boost::locale::generator gen;
8+
std::locale loc = gen("zh_CN.GBK");
9+
fout.imbue(loc);
10+
fout << L"你好,世界"; // 按 GBK 写出文本文件
11+
}

mkdocs.yml

-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ extra_javascript:
3737
- https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML
3838
extra_css:
3939
- extra.css
40-
- https://cdn.jsdelivr.net/npm/@fontsource/[email protected]/index.min.css
41-
- https://cdn.jsdelivr.net/npm/[email protected]/css/jetbrains-mono.min.css
42-
- https://cdn.jsdelivr.net/npm/@fontsource/[email protected]/chinese-simplified-500.min.css
4340
markdown_extensions:
4441
- extra
4542
- tables

0 commit comments

Comments
 (0)