Skip to content

Commit

Permalink
Merge branch 'dev' into prerelease
Browse files Browse the repository at this point in the history
  • Loading branch information
be5invis committed Mar 23, 2024
2 parents 3e73cf3 + 3c81311 commit b4244b2
Show file tree
Hide file tree
Showing 180 changed files with 319 additions and 131 deletions.
84 changes: 34 additions & 50 deletions README.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,62 @@

# Sarasa Gothic (更纱黑体 / 更紗黑體)

Sarasa Gothic 是一款免费的开源字体,基于 Iosevka 和 Source Han Sans (思源黑体) 字型设计,适合在包含中日韩等多种亚洲语言之间混排的场景使用,主要用于操作系统页面和编程字体。
Sarasa Gothic 是一款免费的开源字体,基于 [Iosevka](https://github.com/be5invis/Iosevka)[Source Han Sans](https://github.com/adobe-fonts/source-han-sans) 字型设计,适合在包含中日韩等多种亚洲语言之间混排的场景使用,主要用于操作系统页面和编程字体。

## 安装说明

在更新字体之前请务必完全移除已安装的旧版字体。许多软件或操作系统的缓存系统对于大型字体并不友好
强烈建议在更新此字体前,完全卸载已安装的旧版字体。许多操作系统或软件的字体缓存系统在处理大型TTC字体时可能会遇到问题

## 国内镜像下载站点

- 清华大学镜像源: https://mirrors.tuna.tsinghua.edu.cn/github-release/be5invis/Sarasa-Gothic
- 南京大学镜像源: https://mirror.nju.edu.cn/github-release/be5invis/Sarasa-Gothic

## 下载说明

对于一般用户,可以下载 `sarasa-gothic-ttf` 的字体包,下载后解压,安装文件名中带有 `SC` 标注的字体文件,这表示中国大陆的字符集。如果希望用在代码编辑器中替换默认编程字体,可以选择 `sarasa-mono-sc-regular.ttf` 文件,并指定编辑器字体为 `等距更纱黑体 SC`

## 字体类型
## 如何下载

Sarasa Gothic 提供了多种字重和字型的组合,以满足不同的场景和需求
进入[最新发布版本](https://github.com/be5invis/Sarasa-Gothic/releases)页面,根据需要下载对应系列的字体包,下载后解压并安装

### 字重(Weight)
- Extralight:特细。
- Light:较细。
- Regular:标准字重。
- Semibold:半粗。
- Bold:粗体。

### 斜体(Italic)
- Extralightitalic 特细的斜体。
- Lightitalic 较细的斜体。
- Italic 标准斜体。
- Semibolditalic 半粗的斜体。
- Bold Italic 加粗的斜体。
## 下载说明

### 衬线(Serif)
Sarasa Gothic 提供了多种字形风格、字重的组合,以满足不同的场景和需求。对于仅需安装作为编程字体的用户,推荐选择 "Mono SC"。下载后,在 IDE 设置字体为 `等距更纱黑体 SC`

在原有的字形基础上增加了 Slab Serif 特征,使其更具有辨识度。
### 字型(Variant)

### 字型(Variant)
**更纱黑体**
西文字符基于 [Inter](https://github.com/rsms/inter) 字型设计。
- Gothic: 标准字型,全宽引号。
- UI: 专为UI界面设计的字型,半宽引号。

**等距更纱黑体**
西文字符基于 [Iosevka](https://github.com/be5invis/Iosevka) 字型设计。
- Mono: 等宽字型,全宽破折号。
- Term: 等宽字型,半宽破折号。
- Fixed: 等宽字型,半宽破折号,无连字。

对于字形,请看下方更详细的介绍
**Slab**: 粗衬线体。在原字形基础上增加了 Slab serif 的特征,使其更具有辨识度

- Gothic:标准字型。
- Mono:等宽字型。
- Term:更紧凑的等宽字型。
- Fixed:固定宽度字型。
- UI:专为UI界面设计的字型。
**连字** (Ligature) 遇到特定连续的字符时会进行组合,优化阅读体验。在编程语言中,连字特性也能让数学运算符号更容易的阅读,如输入 `!=` 时,会显示为 ``

## 字型讲解
### 地区语言(Variant)

### 风格样式
根据特定语言和地区主要使用的字形来选择字体。

针对拉丁文(Latin)、希腊文(Greek)和西里尔文(Cyrillic)的字符集:
- `SC`: 简体中文
- `TC`: 台湾繁体中文
- `HC`: 香港繁体中文
- `CL`: 传统旧字形
- `J`: 日文
- `K`: 韩文

基于 [Inter](https://github.com/rsms/inter) 字型设计:
- **Gothic** —— 全宽引号 (`“”`)
- **UI** —— 缩进引号 (`“”`)
### 其他说明

基于 [Iosevka](https://github.com/be5invis/Iosevka) 字型设计:
- **Mono** —— 全宽破折号 (`——`)
- **Term** —— 半宽破折号 (`——`)
- **Fixed** —— 半宽破折号,无连字 (`——`)
**Unhinted**: 没有进行微调字形的版本,也就是使用 Iosevka 和 Source Han Sans 原版的字形。 文件大小比其他版本更小,但可能在某些字的结构上,显示没那么清晰,特别是小字号效果更为不佳。仅需要在极端的环境中,需要更小的字体文件,且不在意字体的显示清晰效果时选择。一般用户建议不选。

### 书写字形
**TTF**: 如果不知道怎么选,选 TTF 肯定没错。但 TTF 通常体积较大。旧系统用户可选。

根据特定语言和地区主要使用的字形来选择字体。更多请参考[思源黑体](https://github.com/adobe-fonts/source-han-sans) 的说明
**TTC**: 相当于一个字体压缩包,在里面塞了很多个 TTF 的字体文件,可以包含多个 TrueType 字体的文件格式。好处就是,让文件更小

- `CL`:古典字形
- `SC`:中国大陆(简体中文)
- `TC`:台湾(繁体中文)
- `HC`:香港(繁体中文)
- `J`:日文
- `K`:韩文
**SuperTTC**: 是 TTC 的升级版,有更高效的打包方式,可以往里面塞更多的可变字体。进一步节省空间。


## 从源文件创建字体
Expand All @@ -84,7 +68,7 @@ Sarasa Gothic 提供了多种字重和字型的组合,以满足不同的场景

### 创建所有字体

将项目下载到本地,进入项目文件夹安装程序包
将项目下载到本地,从终端进入项目文件夹运行

```bash
npm install
Expand All @@ -102,4 +86,4 @@ npm run build ttf
npm run build ttc
```

请注意,打包 TTC 将需要占用*非常高*的内存,因为包含了大量的子家族字符集的组合。
请注意,打包 TTC 时将会占用 *非常高* 的内存,因为包含了大量的子家族字符集的组合。
9 changes: 1 addition & 8 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,7 @@
"latinGroups": {
"Inter": {
"isCff": false,
"bakeFeatures": [
{ "tag": "ss03" },
{ "tag": "tnum", "range": ["0", "9"] },
{ "tag": "cv03", "range": ["0", "9"] },
{ "tag": "cv04", "range": ["0", "9"] },
{ "tag": "cv02", "range": ["0", "9"] },
{ "tag": "cv10" }
],
"bakeFeatures": [{ "tag": "ss03" }, { "tag": "cv10" }],
"dropFeatures": [
"cv01",
"cv02",
Expand Down
4 changes: 2 additions & 2 deletions make/helpers/bake-feature.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { GlyphFinder } from "./glyph-finder.mjs";

export function bakeFeature(tag, font, filter) {
export function bakeFeature(tag, font, filter, scriptTag, languageTag) {
const find = new GlyphFinder(font);
for (const [c, g] of font.cmap.unicode.entries()) {
if (!filter(c)) continue;
font.cmap.unicode.set(c, find.subst(tag, g));
font.cmap.unicode.set(c, find.subst(tag, g, scriptTag, languageTag));
}
}
9 changes: 8 additions & 1 deletion make/helpers/geometry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export function alterContours(glyph, fn) {
if (!glyph.geometry) return;
const contours = Ot.GeometryUtil.apply(Ot.GeometryUtil.Flattener, glyph.geometry);
for (const c of contours) {
for (const z of c) [z.x, z.y] = fn(z.x, z.y);
for (let i = 0; i < c.length; i++) {
const [x, y] = fn(c[i].x, c[i].y);
c[i] = Ot.Glyph.Point.create(x, y, c[i].kind);
}
}
glyph.geometry = new Ot.Glyph.ContourSet(contours);
}
Expand All @@ -50,6 +53,10 @@ export function getAdvanceWidth(glyph) {
if (glyph.horizontal) return glyph.horizontal.end;
else return 0;
}
export function getAdvanceHeight(glyph) {
if (glyph.vertical) return glyph.vertical.start - glyph.vertical.end;
else return 0;
}

export function setAdvanceWidth(glyph, w) {
glyph.horizontal = { start: 0, end: w };
Expand Down
13 changes: 11 additions & 2 deletions make/helpers/glyph-finder.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,20 @@ export class GlyphFinder {
return this.font.cmap.unicode.get(u);
}
}
subst(tag, g) {
subst(tag, g, scriptTag, languageTag) {
if (!this.font.gsub) return g;

let features = this.font.gsub.features;
let candidateLookups = [];
for (const feature of this.font.gsub.features) {
if (scriptTag) {
const script = this.font.gsub.scripts.get(scriptTag);
const language = languageTag
? script.languages.get(languageTag)
: script.defaultLanguage;
features = language.features;
}

for (const feature of features) {
if (feature.tag === tag) {
for (const lookup of feature.lookups) candidateLookups.push(lookup);
}
Expand Down
2 changes: 1 addition & 1 deletion make/helpers/unicode-kind.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const isIdeograph = function (c) {
(c >= 0x20000 && c <= 0x3ffff) // SIP, TIP
);
};
export const isWestern = c => c < 0x2000 || (c >= 0x2070 && c <= 0x218f);
export const isWestern = c => (c < 0x2000 && c != 0xb7) || (c >= 0x2070 && c <= 0x218f);
export const isKorean = c =>
(c >= 0x1100 && c <= 0x11ff) ||
(c >= 0xac00 && c <= 0xd7af) ||
Expand Down
4 changes: 2 additions & 2 deletions make/non-kanji/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { dropCharacters, dropFeature, dropHints } from "../helpers/drop.mjs";
import { readFont, writeFont } from "../helpers/font-io.mjs";
import { isIdeograph, isKorean } from "../helpers/unicode-kind.mjs";

export default (async function pass(argv) {
export default async function pass(argv) {
const font = await readFont(argv.main);

dropHints(font);
Expand All @@ -16,4 +16,4 @@ export default (async function pass(argv) {
unifySameFeatures(font.gpos);
CliProc.gcFont(font, Ot.ListGlyphStoreFactory);
await writeFont(argv.o, font);
});
}
17 changes: 10 additions & 7 deletions make/punct/as.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { dropCharacters, dropFeature, dropHints } from "../helpers/drop.mjs";
import { readFont, writeFont } from "../helpers/font-io.mjs";
import { isFEMisc, isLongDash, isWS, isWestern } from "../helpers/unicode-kind.mjs";

import { bakeLocalization } from "./bake-locl.mjs";
import { buildContinuousEmDash } from "./build-continuous-em-dash.mjs";
import { transferMonoGeometry } from "./lgc-helpers.mjs";
import { sanitizeSymbols, toPWID } from "./sanitize-symbols.mjs";

Expand All @@ -17,15 +19,16 @@ async function pass(argv) {
main,
c => isWestern(c - 0) || isLongDash(c - 0, argv.term) || isWS(c - 0) || isFEMisc(c - 0)
);
if (argv.pwid) toPWID(main);
if (argv.pwid) toPWID(main, argv);
bakeLocalization(main, argv);
if (argv.mono) transferMonoGeometry(main, lgc);
if (!argv.pwid) sanitizeSymbols(main, argv.goth, !argv.pwid && !argv.term);
sanitizeSymbols(main, argv);

dropFeature(main.gsub, ["locl", "ccmp", "aalt", "pwid", "fwid", "hwid", "twid", "qwid"]);
if (argv.mono) dropFeature(main.gpos, ["kern", "vkrn", "halt", "palt", "vpal"]);

buildContinuousEmDash(main);

dropFeature(main.gsub, ["ccmp", "aalt", "pwid", "fwid", "hwid", "twid", "qwid"]);
if (argv.mono) {
dropFeature(main.gsub, ["locl"]);
dropFeature(main.gpos, ["kern", "vkrn", "halt", "palt", "vpal"]);
}
aliasFeatMap(main, "vert", 0x2014, 0x2015);
CliProc.gcFont(main, Ot.ListGlyphStoreFactory);
await writeFont(argv.o, main);
Expand Down
26 changes: 26 additions & 0 deletions make/punct/bake-locl.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { bakeFeature } from "../helpers/bake-feature.mjs";

export function bakeLocalization(font, flags) {
if (flags.mono || flags.term || flags.pwid) return;

let langTag = "JAN ";
switch (flags.region) {
case "J":
langTag = "JAN ";
break;
case "K":
langTag = "KOR ";
break;
case "SC":
langTag = "ZHS ";
break;
case "TC":
langTag = "ZHT ";
break;
case "HC":
langTag = "ZHH ";
break;
}

bakeFeature("locl", font, c => c != 0x2010, "hani", langTag);
}
93 changes: 93 additions & 0 deletions make/punct/build-continuous-em-dash.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Ot } from "ot-builder";

import {
alterContours,
copyGeometryData,
getAdvanceHeight,
getAdvanceWidth
} from "../helpers/geometry.mjs";
import { GlyphFinder } from "../helpers/glyph-finder.mjs";

export function buildContinuousEmDash(font) {
const finder = new GlyphFinder(font);
const emDash = finder.unicode(0x2014);
const bound = Ot.GeometryUtil.apply(Ot.GeometryUtil.GetBound, emDash.geometry);

// If the em-dash is already continuous, we are already done, and simply return
if (bound.xMin <= 0) return;

const emDashV = finder.subst("vert", emDash);
const boundV = Ot.GeometryUtil.apply(Ot.GeometryUtil.GetBound, emDashV.geometry);

// Create new glyphs, add them into font's glyph list
const emDashCont = buildHGlyph(emDash, bound);
const emDashVCont = buildVGlyph(emDashV, boundV);
font.glyphs = Ot.ListGlyphStoreFactory.createStoreFromList([
...font.glyphs.decideOrder(),
...[emDashCont, emDashVCont]
]);

// Build CALT and VERT
buildCalt(font, emDash, emDashCont);
buildVert(font, "vert", emDash, emDashCont, emDashVCont);
buildVert(font, "vrt2", emDash, emDashCont, emDashVCont);
}

function buildHGlyph(emDash, bound) {
const g1 = new Ot.Glyph();
const adw = getAdvanceWidth(emDash);
copyGeometryData(g1, emDash);
alterContours(g1, (x, y) => [
x <= (bound.xMin + bound.xMax) / 2 ? bound.xMax - 1.05 * adw : x,
y
]);
return g1;
}
function buildVGlyph(emDashV, boundV) {
const g1 = new Ot.Glyph();
const adh = getAdvanceHeight(emDashV);
copyGeometryData(g1, emDashV);
alterContours(g1, (x, y) => [
x,
y >= (boundV.yMin + boundV.yMax) / 2 ? boundV.yMin + 1.05 * adh : y
]);
return g1;
}

function buildCalt(font, emDash, emDashCont) {
const substSingle = new Ot.Gsub.Single();
substSingle.mapping.set(emDash, emDashCont);
font.gsub.lookups.push(substSingle);

const substCalt = new Ot.Gsub.Chaining();
substCalt.rules.push({
match: [new Set([emDash, emDashCont]), new Set([emDash])],
inputBegins: 1,
inputEnds: 2,
applications: [
{
at: 0,
apply: substSingle
}
]
});
font.gsub.lookups.unshift(substCalt);

const calt = { tag: "calt", lookups: [substCalt] };
font.gsub.features.unshift(calt);
for (const [scriptTag, script] of font.gsub.scripts) {
if (script.defaultLanguage) script.defaultLanguage.features.unshift(calt);
for (const [langTag, lang] of script.languages) lang.features.unshift(calt);
}
}

function buildVert(font, tag, emDash, emDashCont, emDashVCont) {
for (const feature of font.gsub.features) {
if (feature.tag !== tag) continue;
for (const lookup of feature.lookups) {
if (lookup.type === Ot.Gsub.LookupType.Single && lookup.mapping.has(emDash)) {
lookup.mapping.set(emDashCont, emDashVCont);
}
}
}
}
2 changes: 1 addition & 1 deletion make/punct/fe-misc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async function pass(argv) {
dropCharacters(font, c => !isFEMisc(c));
if (argv.mono) dropFeature(font.gpos, ["kern", "palt", "vkrn", "vpal"]);

if (!argv.pwid) sanitizeSymbols(font, argv.goth, !argv.pwid && !argv.term);
sanitizeSymbols(font, argv);
CliProc.gcFont(font, Ot.ListGlyphStoreFactory);
await writeFont(argv.o, font);
}
5 changes: 4 additions & 1 deletion make/punct/lgc-helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { GlyphFinder } from "../helpers/glyph-finder.mjs";

export function transferMonoGeometry(main, lgc) {
const find = new GlyphFinder(main);
for (let u = 0x2000; u < 0x20a0; u++) {
let uSet = new Set([0xb7]);
for (let u = 0x2000; u < 0x20a0; u++) uSet.add(u);

for (const u of uSet) {
const gSrc = lgc.cmap.unicode.get(u);
const gDst = main.cmap.unicode.get(u);
if (gSrc && gDst) copyGeometryData(gDst, gSrc);
Expand Down
Loading

0 comments on commit b4244b2

Please sign in to comment.