diff --git a/apps/api/src/share-routes.test.mjs b/apps/api/src/share-routes.test.mjs index 18d6d707..cbf9e5ac 100644 --- a/apps/api/src/share-routes.test.mjs +++ b/apps/api/src/share-routes.test.mjs @@ -1,3 +1,4 @@ +import { FONT_IDS } from "@edgeever/shared/font-catalog"; import { describe, expect, test } from "bun:test"; import { Database } from "bun:sqlite"; import { globSync, readFileSync } from "node:fs"; @@ -111,7 +112,7 @@ describe("public memo shares", () => { sqlite.query("UPDATE memo_shares SET created_by = ? WHERE id = ?").run("usr_font_catalog", "share_source"); const app = new Hono(); registerPublicShareRoutes(app); - for (const id of ["wenkai", "smiley", "puhui", "puhui-heavy", "youshe", "shuhei", "heavy", "zhuque", "source-han-serif"]) { + for (const id of ["wenkai-screen", ...FONT_IDS, "neo-zhi-song", "source-serif"]) { sqlite.query("UPDATE users SET note_body_font = ? WHERE id = ?").run(id, "usr_font_catalog"); const response = await app.request(`/api/public/shares/${sourceToken}`, {}, environment); expect(response.status).toBe(200); @@ -211,7 +212,7 @@ describe("public memo shares", () => { expect(response.status).toBe(200); expect(sqlite.query("SELECT note_body_font FROM users WHERE id = ?").get("usr_author").note_body_font).toBe("source-han-serif"); - for (const id of ["smiley", "wenkai", "zhuque", "heavy", "puhui", "puhui-heavy", "youshe", "shuhei"]) { + for (const id of FONT_IDS) { const response = await app.request("/api/v1/me/note-body-font", { method: "PUT", headers: { "content-type": "application/json" }, body: JSON.stringify({ bodyFont: id }), }, environment); diff --git a/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift b/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift index 2ce0be4a..7e9d6d26 100644 --- a/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift +++ b/apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift @@ -1,4 +1,34 @@ import Foundation + +// BEGIN GENERATED FONT CATALOG — bun scripts/sync-font-catalog.mjs +enum NoteImageFontCatalog { + static let choices: [(id: String, zh: String, en: String)] = [ + (id: "smiley", zh: "得意黑", en: "Smiley Sans"), + (id: "wenkai", zh: "霞鹜文楷", en: "LXGW WenKai"), + (id: "zhuque", zh: "朱雀仿宋", en: "Zhuque Fangsong"), + (id: "heavy", zh: "思源黑体 Heavy", en: "Noto Sans SC Heavy"), + (id: "puhui", zh: "普惠体", en: "PuHuiTi"), + (id: "puhui-heavy", zh: "普惠体 Heavy", en: "PuHuiTi Heavy"), + (id: "youshe", zh: "优设标题黑", en: "YouShe Title"), + (id: "shuhei", zh: "阿里妈妈数黑体", en: "Alimama ShuHei"), + (id: "source-han-serif", zh: "思源宋体", en: "Noto Serif SC"), + (id: "anton", zh: "Anton", en: "Anton"), + (id: "douyin", zh: "抖音美好体", en: "Douyin Sans"), + (id: "mashan", zh: "马善政楷书", en: "Ma Shan Zheng"), + (id: "jiangcheng", zh: "江城圆体", en: "Jiangcheng Rounded"), + (id: "huangyou", zh: "站酷庆科黄油体", en: "ZCOOL QingKe HuangYou"), + (id: "glow", zh: "未来荧黑", en: "Glow Sans"), + (id: "kuaile", zh: "站酷快乐体", en: "ZCOOL KuaiLe"), + (id: "source-han-sans", zh: "思源黑体", en: "Noto Sans SC"), + (id: "source-han-serif-heavy", zh: "思源宋体 Heavy", en: "Noto Serif SC Heavy"), + (id: "inter", zh: "Inter", en: "Inter"), + (id: "dmserif", zh: "DM Serif Display", en: "DM Serif Display"), + (id: "bebas", zh: "Bebas Neue", en: "Bebas Neue"), + (id: "spacegrotesk", zh: "Space Grotesk", en: "Space Grotesk"), + (id: "jetbrains", zh: "JetBrains Mono", en: "JetBrains Mono"), + ] +} +// END GENERATED FONT CATALOG import Observation import SwiftUI @@ -58,7 +88,7 @@ struct NoteImageSharePreferences: Codable, Equatable { theme = value } if let value = try? values.decode(String.self, forKey: .fontStyle), - ["auto", "serif", "sans", "mono", "kai", "smiley", "wenkai", "zhuque", "heavy", "puhui", "puhui-heavy", "youshe", "shuhei"].contains(value) { + (["auto", "serif", "sans", "mono", "kai"] + NoteImageFontCatalog.choices.map(\.id)).contains(value) { fontStyle = value } if let value = try? values.decode(String.self, forKey: .fontSize), diff --git a/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift b/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift index 4e956cec..23ca7366 100644 --- a/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift +++ b/apps/ios/EdgeEver/Features/Workspace/MemoDetailView.swift @@ -198,14 +198,9 @@ struct MemoDetailView: View { Text(env.preferences.t("文艺衬线", en: "Serif", pl: "Szeryfowa")).tag("serif") Text(env.preferences.t("现代无衬线", en: "Sans", pl: "Bezszeryfowa")).tag("sans") Text(env.preferences.t("极客等宽", en: "Mono", pl: "Monospace")).tag("mono") - Text(env.preferences.t("得意黑", en: "Smiley Sans", pl: "Smiley Sans")).tag("smiley") - Text(env.preferences.t("霞鹜文楷", en: "LXGW WenKai", pl: "LXGW WenKai")).tag("wenkai") - Text(env.preferences.t("朱雀仿宋", en: "Zhuque Fangsong", pl: "Zhuque Fangsong")).tag("zhuque") - Text(env.preferences.t("思源黑体 Heavy", en: "Noto Sans SC Heavy", pl: "Noto Sans SC Heavy")).tag("heavy") - Text(env.preferences.t("普惠体", en: "PuHuiTi", pl: "PuHuiTi")).tag("puhui") - Text(env.preferences.t("普惠体 Heavy", en: "PuHuiTi Heavy", pl: "PuHuiTi Heavy")).tag("puhui-heavy") - Text(env.preferences.t("优设标题黑", en: "YouShe Title", pl: "YouShe Title")).tag("youshe") - Text(env.preferences.t("阿里妈妈数黑体", en: "Alimama ShuHei", pl: "Alimama ShuHei")).tag("shuhei") + ForEach(NoteImageFontCatalog.choices, id: \.id) { font in + Text(env.preferences.t(font.zh, en: font.en, pl: font.en)).tag(font.id) + } } .pickerStyle(.menu) Text(env.preferences.t( diff --git a/apps/ios/EdgeEver/Resources/EditorBundle/index.html b/apps/ios/EdgeEver/Resources/EditorBundle/index.html index 2fe9ea75..3bc5f508 100644 --- a/apps/ios/EdgeEver/Resources/EditorBundle/index.html +++ b/apps/ios/EdgeEver/Resources/EditorBundle/index.html @@ -7,7 +7,7 @@ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> EdgeEver Editor - +`},"getStyles"),f8,vu=(f8=class{constructor(){this.d=""}moveTo(e,r){return this.d+=`M ${e} ${r} `,this}lineTo(e,r){return this.d+=`L ${e} ${r} `,this}horizontalTo(e){return this.d+=`H ${e} `,this}verticalTo(e){return this.d+=`V ${e} `,this}arcTo(e,r,n,i,a,s,o){return this.d+=`A ${e} ${r} ${n} ${i?1:0} ${a?1:0} ${s} ${o} `,this}build(){return this.d.trim()}},x(f8,"PathBuilder"),f8),p8,D3r=(p8=class{constructor(e,r=Ose()){this.textCache=new Map,this.svg=e,this.config=r}measureText(e){if(this.textCache.has(e))return this.textCache.get(e);const r=this.svg.append("text").attr("font-family",this.config.fontFamily).attr("font-size",this.config.fontSize).text(e),n=r.node().getBBox(),i={width:n.width,height:n.height};return r.remove(),this.textCache.set(e,i),i}renderTerminal(e,r){const n=this.measureText(r),i=n.width+this.config.padding*2,a=n.height+this.config.padding*2,s=e.append("g").attr("class","railroad-terminal");return s.append("rect").attr("x",0).attr("y",0).attr("width",i).attr("height",a).attr("rx",10).attr("ry",10),s.append("text").attr("x",i/2).attr("y",a/2).text(r),{element:s.node(),dimensions:{width:i,height:a,up:a/2,down:a/2}}}renderNonTerminal(e,r){const n=this.measureText(r),i=n.width+this.config.padding*2,a=n.height+this.config.padding*2,s=e.append("g").attr("class","railroad-nonterminal");return s.append("rect").attr("x",0).attr("y",0).attr("width",i).attr("height",a),s.append("text").attr("x",i/2).attr("y",a/2).text(r),{element:s.node(),dimensions:{width:i,height:a,up:a/2,down:a/2}}}renderSequence(e,r){const n=r.map(u=>this.renderExpression(e,u));let i=0,a=0,s=0;for(const u of n)i+=u.dimensions.width,a=Math.max(a,u.dimensions.up),s=Math.max(s,u.dimensions.down);i+=(n.length-1)*this.config.horizontalSeparation;const o=e.append("g").attr("class","railroad-sequence");let l=0;for(let u=0;uthis.renderExpression(e,f));let i=0,a=0;for(const f of n)i=Math.max(i,f.dimensions.width),a+=f.dimensions.height;a+=(n.length-1)*this.config.verticalSeparation;const s=this.config.arcRadius,o=s*4,l=i+o,u=e.append("g").attr("class","railroad-choice");let h=0;const d=a/2;for(const f of n){const p=h,g=p+f.dimensions.up,m=s*2+(i-f.dimensions.width)/2;u.node().appendChild(f.element).setAttribute("transform",`translate(${m}, ${p})`);const y=new vu,b=g>d;g===d?y.moveTo(0,d).lineTo(m,g):y.moveTo(0,d).arcTo(s,s,0,!1,b,s,d+(b?s:-s)).lineTo(s,g-(b?s:-s)).arcTo(s,s,0,!1,!b,s*2,g).lineTo(m,g),u.append("path").attr("class","railroad-line").attr("d",y.build());const v=new vu,w=m+f.dimensions.width,T=l-s*2;g===d?v.moveTo(w,g).lineTo(l,d):v.moveTo(w,g).lineTo(T,g).arcTo(s,s,0,!1,!b,l-s,g+(b?-s:s)).lineTo(l-s,d+(b?s:-s)).arcTo(s,s,0,!1,b,l,d),u.append("path").attr("class","railroad-line").attr("d",v.build()),h+=f.dimensions.height+this.config.verticalSeparation}return{element:u.node(),dimensions:{width:l,height:a,up:d,down:a-d}}}renderOptional(e,r){const n=this.renderExpression(e,r),i=this.config.arcRadius,a=i*2,s=n.dimensions.width+i*4,o=n.dimensions.height+a,l=e.append("g").attr("class","railroad-optional"),u=i*2,h=a;l.node().appendChild(n.element).setAttribute("transform",`translate(${u}, ${h})`);const f=h+n.dimensions.up,p=new vu().moveTo(0,f).lineTo(i*2,f);l.append("path").attr("class","railroad-line").attr("d",p.build());const g=new vu().moveTo(u+n.dimensions.width,f).lineTo(s,f);l.append("path").attr("class","railroad-line").attr("d",g.build());const m=new vu().moveTo(0,f).arcTo(i,i,0,!1,!1,i,f-i).lineTo(i,i).arcTo(i,i,0,!1,!0,i*2,0).lineTo(s-i*2,0).arcTo(i,i,0,!1,!0,s-i,i).lineTo(s-i,f-i).arcTo(i,i,0,!1,!1,s,f);return l.append("path").attr("class","railroad-line").attr("d",m.build()),{element:l.node(),dimensions:{width:s,height:o,up:f,down:o-f}}}renderRepetition(e,r,n){const i=this.renderExpression(e,r),a=this.config.arcRadius,s=a*2,o=i.dimensions.width+a*4,l=n===0,u=i.dimensions.height+s+(l?s:0),h=e.append("g").attr("class","railroad-repetition"),d=a*2,f=l?s:0;h.node().appendChild(i.element).setAttribute("transform",`translate(${d}, ${f})`);const g=f+i.dimensions.up;h.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(0,g).lineTo(a*2,g).build()),h.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(d+i.dimensions.width,g).lineTo(o,g).build());const m=f+i.dimensions.height+a,U=new vu().moveTo(d+i.dimensions.width,g).arcTo(a,a,0,!1,!0,d+i.dimensions.width+a,g+a).lineTo(d+i.dimensions.width+a,m).arcTo(a,a,0,!1,!0,d+i.dimensions.width,m+a).lineTo(a*2,m+a).arcTo(a,a,0,!1,!0,a,m).lineTo(a,g+a).arcTo(a,a,0,!1,!0,a*2,g);if(h.append("path").attr("class","railroad-line").attr("d",U.build()),l){const y=new vu().moveTo(0,g).arcTo(a,a,0,!1,!1,a,g-a).lineTo(a,a).arcTo(a,a,0,!1,!0,a*2,0).lineTo(o-a*2,0).arcTo(a,a,0,!1,!0,o-a,a).lineTo(o-a,g-a).arcTo(a,a,0,!1,!1,o,g);h.append("path").attr("class","railroad-line").attr("d",y.build())}return{element:h.node(),dimensions:{width:o,height:u,up:g,down:u-g}}}renderSpecial(e,r){const n=this.measureText("? "+r+" ?"),i=n.width+this.config.padding*2,a=n.height+this.config.padding*2,s=e.append("g").attr("class","railroad-special");return s.append("rect").attr("x",0).attr("y",0).attr("width",i).attr("height",a),s.append("text").attr("x",i/2).attr("y",a/2).text("? "+r+" ?"),{element:s.node(),dimensions:{width:i,height:a,up:a/2,down:a/2}}}renderExpression(e,r){switch(r.type){case"terminal":return this.renderTerminal(e,r.value);case"nonterminal":return this.renderNonTerminal(e,r.name);case"sequence":return this.renderSequence(e,r.elements);case"choice":return this.renderChoice(e,r.alternatives);case"optional":return this.renderOptional(e,r.element);case"repetition":return this.renderRepetition(e,r.element,r.min);case"special":return this.renderSpecial(e,r.text);default:throw new Error(`Unknown node type: ${r.type}`)}}renderRule(e,r){const n=this.svg.append("g").attr("class","railroad-rule").attr("transform",`translate(0, ${r})`),i=e.name+" =",a=this.measureText(i).width+20,s=a+20,o=n.append("g"),l=this.renderExpression(o,e.definition),u=Math.max(20,l.dimensions.up),h=u-l.dimensions.up;return o.attr("transform",`translate(${s}, ${h})`),n.append("g").attr("class","railroad-rule-name-group").append("text").attr("class","railroad-rule-name").attr("x",0).attr("y",u).text(i),n.append("g").attr("class","railroad-start").append("circle").attr("cx",a).attr("cy",u).attr("r",this.config.markerRadius),n.append("g").attr("class","railroad-end").append("circle").attr("cx",s+l.dimensions.width+10).attr("cy",u).attr("r",this.config.markerRadius),n.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(a+this.config.markerRadius,u).lineTo(s,u).build()),n.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(s+l.dimensions.width,u).lineTo(s+l.dimensions.width+10-this.config.markerRadius,u).build()),{height:Math.max(40,h+l.dimensions.height+this.config.padding*2),width:s+l.dimensions.width+10+this.config.markerRadius}}renderDiagram(e){let r=this.config.padding,n=0;for(const i of e){const a=this.renderRule(i,r);r+=a.height+this.config.verticalSeparation,n=Math.max(n,a.width)}return{width:n+this.config.padding*2,height:r+this.config.padding}}},x(p8,"RailroadRenderer"),p8),Z2e=x((t,e,r)=>{Ga(t,e.height,e.width,r),t.attr("viewBox",`0 0 ${e.width} ${e.height}`)},"configureRailroadSvgSize"),P3r=x((t,e,r)=>{de.debug(`[Railroad] Rendering diagram +`+t);try{const n=sl(e);n.attr("class","railroad-diagram");const i=xr().railroad,a=(i==null?void 0:i.useMaxWidth)??!0,s=ta.getRules();if(de.debug(`[Railroad] Rendering ${s.length} rules`),s.length===0){de.warn("[Railroad] No rules to render"),Z2e(n,{height:100,width:200},a);return}const l=new D3r(n,Ose()).renderDiagram(s);Z2e(n,l,a),de.debug("[Railroad] Render complete")}catch(n){throw de.error("[Railroad] Render error:",n),n}},"draw"),SB={draw:P3r},_3r=Iae().Railroad.parser.LangiumParser,E2=x(t=>{switch(t.$type){case"RailroadTerminalExpr":return{type:"terminal",value:t.value};case"RailroadNonTerminalExpr":return{type:"nonterminal",name:t.name};case"RailroadSpecialExpr":return{type:"special",text:t.text};case"RailroadSequenceExpr":{const e=t.elements.map(E2);return e.length===1?e[0]:{type:"sequence",elements:e}}case"RailroadChoiceExpr":{const e=t.alternatives.map(E2);return e.length===1?e[0]:{type:"choice",alternatives:e}}case"RailroadOptionalExpr":return{type:"optional",element:E2(t.element)};case"RailroadOneOrMoreExpr":return{type:"repetition",element:E2(t.element),min:1,max:1/0};case"RailroadZeroOrMoreExpr":return{type:"repetition",element:E2(t.element),min:0,max:1/0};default:throw new Error(`Unsupported railroad expression: ${t.$type}`)}},"transformExpression"),B3r=x(t=>({name:t.name,definition:E2(t.definition)}),"transformRule"),$3r=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(B3r(e)))},"populateDb"),F3r={parse:x(t=>{ta.clear(),de.debug("[Railroad Parser] Starting Langium parse");const e=_3r.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[Railroad Parser] Parsed rules:",r.rules.length),$3r(r),de.debug("[Railroad Parser] Parse complete")},"parse"),parser:{yy:ta}},z3r={parser:F3r,db:ta,renderer:SB,styles:CB};const H3r=Object.freeze(Object.defineProperty({__proto__:null,diagram:z3r},Symbol.toStringTag,{value:"Module"}));var V3r=Lae().RailroadEbnf.parser.LangiumParser,gR=x(t=>{const e=t.alternatives.map(G3r);return e.length===1?e[0]:{type:"choice",alternatives:e}},"transformChoice"),G3r=x(t=>{const e=t.elements.map(q3r);return e.length===1?e[0]:{type:"sequence",elements:e}},"transformSequence"),aJe=x(t=>{switch(t.$type){case"EbnfTerminal":return{type:"terminal",value:t.value};case"EbnfNonTerminal":return{type:"nonterminal",name:t.name};case"EbnfSpecial":return{type:"special",text:t.text};case"EbnfGroup":return gR(t.element);case"EbnfOptional":return{type:"optional",element:gR(t.element)};case"EbnfRepetition":return{type:"repetition",element:gR(t.element),min:0,max:1/0};default:throw new Error(`Unsupported EBNF primary node: ${t.$type}`)}},"transformPrimary"),W3r=x((t,e)=>{switch(e.$type){case"EbnfOptionalPostfix":return{type:"optional",element:t};case"EbnfZeroOrMorePostfix":return{type:"repetition",element:t,min:0,max:1/0};case"EbnfOneOrMorePostfix":return{type:"repetition",element:t,min:1,max:1/0};case"EbnfExceptionPostfix":return{type:"sequence",elements:[t,{type:"terminal",value:"-"},aJe(e.except)]};default:throw new Error(`Unsupported EBNF postfix node: ${e.$type}`)}},"transformPostfix"),q3r=x(t=>t.postfixes.reduce((e,r)=>W3r(e,r),aJe(t.base)),"transformTerm"),j3r=x(t=>({name:t.name,definition:gR(t.definition)}),"transformRule"),Y3r=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(j3r(e)))},"populateDb"),X3r={parse:x(t=>{ta.clear(),de.debug("[EBNF Parser] Starting Langium parse");const e=V3r.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[EBNF Parser] Parsed rules:",r.rules.length),Y3r(r),de.debug("[EBNF Parser] Parse complete")},"parse"),parser:{yy:ta}},K3r={parser:X3r,db:ta,renderer:SB,styles:CB};const Z3r=Object.freeze(Object.defineProperty({__proto__:null,diagram:K3r},Symbol.toStringTag,{value:"Module"}));var J3r=Nae().RailroadAbnf.parser.LangiumParser,pZ=x(t=>{const e=t.alternatives.map(Q3r);return e.length===1?e[0]:{type:"choice",alternatives:e}},"transformAlternation"),Q3r=x(t=>{const e=t.elements.map(txr);return e.length===1?e[0]:{type:"sequence",elements:e}},"transformConcatenation"),exr=x(t=>{if(t.includes("*")){const[r,n]=t.split("*"),i=r?parseInt(r,10):0,a=n?parseInt(n,10):1/0;return{min:i,max:a}}const e=parseInt(t,10);return{min:e,max:e}},"parseRepeat"),txr=x(t=>{const e=rxr(t.primary);if(!t.repeat)return e;const{min:r,max:n}=exr(t.repeat);return r===0&&n===1?{type:"optional",element:e}:{type:"repetition",element:e,min:r,max:n}},"transformElement"),rxr=x(t=>{switch(t.$type){case"AbnfStringLiteral":return{type:"terminal",value:t.value};case"AbnfNumVal":return{type:"terminal",value:t.value};case"AbnfRuleName":return{type:"nonterminal",name:t.name};case"AbnfGroup":return pZ(t.element);case"AbnfOptionalGroup":return{type:"optional",element:pZ(t.element)};default:throw new Error(`Unsupported ABNF primary node: ${t.$type}`)}},"transformPrimary"),nxr=x(t=>({name:t.name,definition:pZ(t.definition)}),"transformRule"),ixr=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(nxr(e)))},"populateDb"),axr={parse:x(t=>{ta.clear(),de.debug("[ABNF Parser] Starting Langium parse");const e=J3r.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[ABNF Parser] Parsed rules:",r.rules.length),ixr(r),de.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:ta}},sxr={parser:axr,db:ta,renderer:SB,styles:CB};const oxr=Object.freeze(Object.defineProperty({__proto__:null,diagram:sxr},Symbol.toStringTag,{value:"Module"}));var lxr=Oae().RailroadPeg.parser.LangiumParser,sJe=x(t=>{const e=t.alternatives.map(cxr);return e.length===1?e[0]:{type:"choice",alternatives:e}},"transformOrderedChoice"),cxr=x(t=>{const e=t.elements.map(uxr);return e.length===1?e[0]:{type:"sequence",elements:e}},"transformSequence"),uxr=x(t=>{const e=hxr(t.suffix);return t.operator?{type:"special",text:t.operator==="&"?`&${J2e(e)}`:`!${J2e(e)}`}:e},"transformPrefix"),J2e=x(t=>{switch(t.type){case"terminal":return`"${t.value}"`;case"nonterminal":return t.name;case"special":return t.text;default:return"(...)"}},"nodeToLabel"),hxr=x(t=>{const e=dxr(t.primary);if(!t.operator)return e;switch(t.operator){case"?":return{type:"optional",element:e};case"*":return{type:"repetition",element:e,min:0,max:1/0};case"+":return{type:"repetition",element:e,min:1,max:1/0};default:throw new Error(`Unsupported PEG suffix operator: ${t.operator}`)}},"transformSuffix"),dxr=x(t=>{switch(t.$type){case"PegLiteral":return{type:"terminal",value:t.value};case"PegIdentifier":return{type:"nonterminal",name:t.name};case"PegGroup":return sJe(t.element);case"PegAny":return{type:"special",text:t.dot};default:throw new Error(`Unsupported PEG primary node: ${t.$type}`)}},"transformPrimary"),fxr=x(t=>({name:t.name,definition:sJe(t.definition)}),"transformRule"),pxr=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(fxr(e)))},"populateDb"),gxr={parse:x(t=>{ta.clear(),de.debug("[PEG Parser] Starting Langium parse");const e=lxr.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[PEG Parser] Parsed rules:",r.rules.length),pxr(r),de.debug("[PEG Parser] Parse complete")},"parse"),parser:{yy:ta}},mxr={parser:gxr,db:ta,renderer:SB,styles:CB};const Uxr=Object.freeze(Object.defineProperty({__proto__:null,diagram:mxr},Symbol.toStringTag,{value:"Module"}));var yxr=1;function Dse(){if(!(typeof globalThis>"u"))return globalThis}x(Dse,"getCaptureGlobal");function oJe(){var t;return!!((t=Dse())!=null&&t.mermaidCaptureSizes)}x(oJe,"shouldCaptureSizes");function lJe(){return typeof location>"u"?"browser-dev":`${location.pathname}${location.search}`}x(lJe,"capturedFromLocation");function cJe(t,e){const r=Dse();if(!r)return;const n=e.node(),i=(n&&"ownerSVGElement"in n?n.ownerSVGElement:null)??n,a=(i==null?void 0:i.id)??"(unknown)";r.mermaidCapturedSizes??(r.mermaidCapturedSizes=[]);const s={svgId:a,sizes:t};r.mermaidCapturedSizes.push(s),r.mermaidLastCapturedSizes=s}x(cJe,"emitCapturedSizes");function uJe(t,e){const r=[];for(const n of e.nodes)n.isGroup||r.push({id:n.id,width:n.width??0,height:n.height??0});r.length!==0&&cJe({metadata:{captureVersion:yxr,capturedAt:new Date().toISOString(),capturedFrom:lJe()},nodes:r},t)}x(uJe,"captureNodeSizes");const bxr=Object.freeze(Object.defineProperty({__proto__:null,captureNodeSizes:uJe,shouldCaptureSizes:oJe},Symbol.toStringTag,{value:"Module"})),vxr=Object.freeze(Object.defineProperty({__proto__:null,InfoModule:Cje,createInfoServices:Sje},Symbol.toStringTag,{value:"Module"})),xxr=Object.freeze(Object.defineProperty({__proto__:null,PacketModule:kje,createPacketServices:Eje},Symbol.toStringTag,{value:"Module"})),wxr=Object.freeze(Object.defineProperty({__proto__:null,PieModule:Aje,createPieServices:Mje},Symbol.toStringTag,{value:"Module"})),Txr=Object.freeze(Object.defineProperty({__proto__:null,TreeViewModule:Rje,createTreeViewServices:Ije},Symbol.toStringTag,{value:"Module"})),Cxr=Object.freeze(Object.defineProperty({__proto__:null,ArchitectureModule:Lje,createArchitectureServices:Nje},Symbol.toStringTag,{value:"Module"})),Sxr=Object.freeze(Object.defineProperty({__proto__:null,GitGraphModule:wje,createGitGraphServices:Tje},Symbol.toStringTag,{value:"Module"})),kxr=Object.freeze(Object.defineProperty({__proto__:null,EventModelingModule:Dje,createEventModelingServices:Pje},Symbol.toStringTag,{value:"Module"})),Exr=Object.freeze(Object.defineProperty({__proto__:null,RadarModule:cje,createRadarServices:uje},Symbol.toStringTag,{value:"Module"})),Axr=Object.freeze(Object.defineProperty({__proto__:null,RailroadModule:hje,createRailroadServices:Iae},Symbol.toStringTag,{value:"Module"})),Mxr=Object.freeze(Object.defineProperty({__proto__:null,RailroadEbnfModule:dje,createRailroadEbnfServices:Lae},Symbol.toStringTag,{value:"Module"})),Rxr=Object.freeze(Object.defineProperty({__proto__:null,RailroadAbnfModule:fje,createRailroadAbnfServices:Nae},Symbol.toStringTag,{value:"Module"})),Ixr=Object.freeze(Object.defineProperty({__proto__:null,RailroadPegModule:pje,createRailroadPegServices:Oae},Symbol.toStringTag,{value:"Module"})),Lxr=Object.freeze(Object.defineProperty({__proto__:null,TreemapModule:mje,createTreemapServices:Uje},Symbol.toStringTag,{value:"Module"})),Nxr=Object.freeze(Object.defineProperty({__proto__:null,WardleyModule:yje,createWardleyServices:bje},Symbol.toStringTag,{value:"Module"})),Oxr=Object.freeze(Object.defineProperty({__proto__:null,CynefinModule:vje,createCynefinServices:xje},Symbol.toStringTag,{value:"Module"})); diff --git a/apps/ios/EditorBundle/index.html b/apps/ios/EditorBundle/index.html index 2fe9ea75..3bc5f508 100644 --- a/apps/ios/EditorBundle/index.html +++ b/apps/ios/EditorBundle/index.html @@ -7,7 +7,7 @@ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> EdgeEver Editor - +`},"getStyles"),f8,vu=(f8=class{constructor(){this.d=""}moveTo(e,r){return this.d+=`M ${e} ${r} `,this}lineTo(e,r){return this.d+=`L ${e} ${r} `,this}horizontalTo(e){return this.d+=`H ${e} `,this}verticalTo(e){return this.d+=`V ${e} `,this}arcTo(e,r,n,i,a,s,o){return this.d+=`A ${e} ${r} ${n} ${i?1:0} ${a?1:0} ${s} ${o} `,this}build(){return this.d.trim()}},x(f8,"PathBuilder"),f8),p8,D3r=(p8=class{constructor(e,r=Ose()){this.textCache=new Map,this.svg=e,this.config=r}measureText(e){if(this.textCache.has(e))return this.textCache.get(e);const r=this.svg.append("text").attr("font-family",this.config.fontFamily).attr("font-size",this.config.fontSize).text(e),n=r.node().getBBox(),i={width:n.width,height:n.height};return r.remove(),this.textCache.set(e,i),i}renderTerminal(e,r){const n=this.measureText(r),i=n.width+this.config.padding*2,a=n.height+this.config.padding*2,s=e.append("g").attr("class","railroad-terminal");return s.append("rect").attr("x",0).attr("y",0).attr("width",i).attr("height",a).attr("rx",10).attr("ry",10),s.append("text").attr("x",i/2).attr("y",a/2).text(r),{element:s.node(),dimensions:{width:i,height:a,up:a/2,down:a/2}}}renderNonTerminal(e,r){const n=this.measureText(r),i=n.width+this.config.padding*2,a=n.height+this.config.padding*2,s=e.append("g").attr("class","railroad-nonterminal");return s.append("rect").attr("x",0).attr("y",0).attr("width",i).attr("height",a),s.append("text").attr("x",i/2).attr("y",a/2).text(r),{element:s.node(),dimensions:{width:i,height:a,up:a/2,down:a/2}}}renderSequence(e,r){const n=r.map(u=>this.renderExpression(e,u));let i=0,a=0,s=0;for(const u of n)i+=u.dimensions.width,a=Math.max(a,u.dimensions.up),s=Math.max(s,u.dimensions.down);i+=(n.length-1)*this.config.horizontalSeparation;const o=e.append("g").attr("class","railroad-sequence");let l=0;for(let u=0;uthis.renderExpression(e,f));let i=0,a=0;for(const f of n)i=Math.max(i,f.dimensions.width),a+=f.dimensions.height;a+=(n.length-1)*this.config.verticalSeparation;const s=this.config.arcRadius,o=s*4,l=i+o,u=e.append("g").attr("class","railroad-choice");let h=0;const d=a/2;for(const f of n){const p=h,g=p+f.dimensions.up,m=s*2+(i-f.dimensions.width)/2;u.node().appendChild(f.element).setAttribute("transform",`translate(${m}, ${p})`);const y=new vu,b=g>d;g===d?y.moveTo(0,d).lineTo(m,g):y.moveTo(0,d).arcTo(s,s,0,!1,b,s,d+(b?s:-s)).lineTo(s,g-(b?s:-s)).arcTo(s,s,0,!1,!b,s*2,g).lineTo(m,g),u.append("path").attr("class","railroad-line").attr("d",y.build());const v=new vu,w=m+f.dimensions.width,T=l-s*2;g===d?v.moveTo(w,g).lineTo(l,d):v.moveTo(w,g).lineTo(T,g).arcTo(s,s,0,!1,!b,l-s,g+(b?-s:s)).lineTo(l-s,d+(b?s:-s)).arcTo(s,s,0,!1,b,l,d),u.append("path").attr("class","railroad-line").attr("d",v.build()),h+=f.dimensions.height+this.config.verticalSeparation}return{element:u.node(),dimensions:{width:l,height:a,up:d,down:a-d}}}renderOptional(e,r){const n=this.renderExpression(e,r),i=this.config.arcRadius,a=i*2,s=n.dimensions.width+i*4,o=n.dimensions.height+a,l=e.append("g").attr("class","railroad-optional"),u=i*2,h=a;l.node().appendChild(n.element).setAttribute("transform",`translate(${u}, ${h})`);const f=h+n.dimensions.up,p=new vu().moveTo(0,f).lineTo(i*2,f);l.append("path").attr("class","railroad-line").attr("d",p.build());const g=new vu().moveTo(u+n.dimensions.width,f).lineTo(s,f);l.append("path").attr("class","railroad-line").attr("d",g.build());const m=new vu().moveTo(0,f).arcTo(i,i,0,!1,!1,i,f-i).lineTo(i,i).arcTo(i,i,0,!1,!0,i*2,0).lineTo(s-i*2,0).arcTo(i,i,0,!1,!0,s-i,i).lineTo(s-i,f-i).arcTo(i,i,0,!1,!1,s,f);return l.append("path").attr("class","railroad-line").attr("d",m.build()),{element:l.node(),dimensions:{width:s,height:o,up:f,down:o-f}}}renderRepetition(e,r,n){const i=this.renderExpression(e,r),a=this.config.arcRadius,s=a*2,o=i.dimensions.width+a*4,l=n===0,u=i.dimensions.height+s+(l?s:0),h=e.append("g").attr("class","railroad-repetition"),d=a*2,f=l?s:0;h.node().appendChild(i.element).setAttribute("transform",`translate(${d}, ${f})`);const g=f+i.dimensions.up;h.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(0,g).lineTo(a*2,g).build()),h.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(d+i.dimensions.width,g).lineTo(o,g).build());const m=f+i.dimensions.height+a,U=new vu().moveTo(d+i.dimensions.width,g).arcTo(a,a,0,!1,!0,d+i.dimensions.width+a,g+a).lineTo(d+i.dimensions.width+a,m).arcTo(a,a,0,!1,!0,d+i.dimensions.width,m+a).lineTo(a*2,m+a).arcTo(a,a,0,!1,!0,a,m).lineTo(a,g+a).arcTo(a,a,0,!1,!0,a*2,g);if(h.append("path").attr("class","railroad-line").attr("d",U.build()),l){const y=new vu().moveTo(0,g).arcTo(a,a,0,!1,!1,a,g-a).lineTo(a,a).arcTo(a,a,0,!1,!0,a*2,0).lineTo(o-a*2,0).arcTo(a,a,0,!1,!0,o-a,a).lineTo(o-a,g-a).arcTo(a,a,0,!1,!1,o,g);h.append("path").attr("class","railroad-line").attr("d",y.build())}return{element:h.node(),dimensions:{width:o,height:u,up:g,down:u-g}}}renderSpecial(e,r){const n=this.measureText("? "+r+" ?"),i=n.width+this.config.padding*2,a=n.height+this.config.padding*2,s=e.append("g").attr("class","railroad-special");return s.append("rect").attr("x",0).attr("y",0).attr("width",i).attr("height",a),s.append("text").attr("x",i/2).attr("y",a/2).text("? "+r+" ?"),{element:s.node(),dimensions:{width:i,height:a,up:a/2,down:a/2}}}renderExpression(e,r){switch(r.type){case"terminal":return this.renderTerminal(e,r.value);case"nonterminal":return this.renderNonTerminal(e,r.name);case"sequence":return this.renderSequence(e,r.elements);case"choice":return this.renderChoice(e,r.alternatives);case"optional":return this.renderOptional(e,r.element);case"repetition":return this.renderRepetition(e,r.element,r.min);case"special":return this.renderSpecial(e,r.text);default:throw new Error(`Unknown node type: ${r.type}`)}}renderRule(e,r){const n=this.svg.append("g").attr("class","railroad-rule").attr("transform",`translate(0, ${r})`),i=e.name+" =",a=this.measureText(i).width+20,s=a+20,o=n.append("g"),l=this.renderExpression(o,e.definition),u=Math.max(20,l.dimensions.up),h=u-l.dimensions.up;return o.attr("transform",`translate(${s}, ${h})`),n.append("g").attr("class","railroad-rule-name-group").append("text").attr("class","railroad-rule-name").attr("x",0).attr("y",u).text(i),n.append("g").attr("class","railroad-start").append("circle").attr("cx",a).attr("cy",u).attr("r",this.config.markerRadius),n.append("g").attr("class","railroad-end").append("circle").attr("cx",s+l.dimensions.width+10).attr("cy",u).attr("r",this.config.markerRadius),n.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(a+this.config.markerRadius,u).lineTo(s,u).build()),n.append("path").attr("class","railroad-line").attr("d",new vu().moveTo(s+l.dimensions.width,u).lineTo(s+l.dimensions.width+10-this.config.markerRadius,u).build()),{height:Math.max(40,h+l.dimensions.height+this.config.padding*2),width:s+l.dimensions.width+10+this.config.markerRadius}}renderDiagram(e){let r=this.config.padding,n=0;for(const i of e){const a=this.renderRule(i,r);r+=a.height+this.config.verticalSeparation,n=Math.max(n,a.width)}return{width:n+this.config.padding*2,height:r+this.config.padding}}},x(p8,"RailroadRenderer"),p8),Z2e=x((t,e,r)=>{Ga(t,e.height,e.width,r),t.attr("viewBox",`0 0 ${e.width} ${e.height}`)},"configureRailroadSvgSize"),P3r=x((t,e,r)=>{de.debug(`[Railroad] Rendering diagram +`+t);try{const n=sl(e);n.attr("class","railroad-diagram");const i=xr().railroad,a=(i==null?void 0:i.useMaxWidth)??!0,s=ta.getRules();if(de.debug(`[Railroad] Rendering ${s.length} rules`),s.length===0){de.warn("[Railroad] No rules to render"),Z2e(n,{height:100,width:200},a);return}const l=new D3r(n,Ose()).renderDiagram(s);Z2e(n,l,a),de.debug("[Railroad] Render complete")}catch(n){throw de.error("[Railroad] Render error:",n),n}},"draw"),SB={draw:P3r},_3r=Iae().Railroad.parser.LangiumParser,E2=x(t=>{switch(t.$type){case"RailroadTerminalExpr":return{type:"terminal",value:t.value};case"RailroadNonTerminalExpr":return{type:"nonterminal",name:t.name};case"RailroadSpecialExpr":return{type:"special",text:t.text};case"RailroadSequenceExpr":{const e=t.elements.map(E2);return e.length===1?e[0]:{type:"sequence",elements:e}}case"RailroadChoiceExpr":{const e=t.alternatives.map(E2);return e.length===1?e[0]:{type:"choice",alternatives:e}}case"RailroadOptionalExpr":return{type:"optional",element:E2(t.element)};case"RailroadOneOrMoreExpr":return{type:"repetition",element:E2(t.element),min:1,max:1/0};case"RailroadZeroOrMoreExpr":return{type:"repetition",element:E2(t.element),min:0,max:1/0};default:throw new Error(`Unsupported railroad expression: ${t.$type}`)}},"transformExpression"),B3r=x(t=>({name:t.name,definition:E2(t.definition)}),"transformRule"),$3r=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(B3r(e)))},"populateDb"),F3r={parse:x(t=>{ta.clear(),de.debug("[Railroad Parser] Starting Langium parse");const e=_3r.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[Railroad Parser] Parsed rules:",r.rules.length),$3r(r),de.debug("[Railroad Parser] Parse complete")},"parse"),parser:{yy:ta}},z3r={parser:F3r,db:ta,renderer:SB,styles:CB};const H3r=Object.freeze(Object.defineProperty({__proto__:null,diagram:z3r},Symbol.toStringTag,{value:"Module"}));var V3r=Lae().RailroadEbnf.parser.LangiumParser,gR=x(t=>{const e=t.alternatives.map(G3r);return e.length===1?e[0]:{type:"choice",alternatives:e}},"transformChoice"),G3r=x(t=>{const e=t.elements.map(q3r);return e.length===1?e[0]:{type:"sequence",elements:e}},"transformSequence"),aJe=x(t=>{switch(t.$type){case"EbnfTerminal":return{type:"terminal",value:t.value};case"EbnfNonTerminal":return{type:"nonterminal",name:t.name};case"EbnfSpecial":return{type:"special",text:t.text};case"EbnfGroup":return gR(t.element);case"EbnfOptional":return{type:"optional",element:gR(t.element)};case"EbnfRepetition":return{type:"repetition",element:gR(t.element),min:0,max:1/0};default:throw new Error(`Unsupported EBNF primary node: ${t.$type}`)}},"transformPrimary"),W3r=x((t,e)=>{switch(e.$type){case"EbnfOptionalPostfix":return{type:"optional",element:t};case"EbnfZeroOrMorePostfix":return{type:"repetition",element:t,min:0,max:1/0};case"EbnfOneOrMorePostfix":return{type:"repetition",element:t,min:1,max:1/0};case"EbnfExceptionPostfix":return{type:"sequence",elements:[t,{type:"terminal",value:"-"},aJe(e.except)]};default:throw new Error(`Unsupported EBNF postfix node: ${e.$type}`)}},"transformPostfix"),q3r=x(t=>t.postfixes.reduce((e,r)=>W3r(e,r),aJe(t.base)),"transformTerm"),j3r=x(t=>({name:t.name,definition:gR(t.definition)}),"transformRule"),Y3r=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(j3r(e)))},"populateDb"),X3r={parse:x(t=>{ta.clear(),de.debug("[EBNF Parser] Starting Langium parse");const e=V3r.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[EBNF Parser] Parsed rules:",r.rules.length),Y3r(r),de.debug("[EBNF Parser] Parse complete")},"parse"),parser:{yy:ta}},K3r={parser:X3r,db:ta,renderer:SB,styles:CB};const Z3r=Object.freeze(Object.defineProperty({__proto__:null,diagram:K3r},Symbol.toStringTag,{value:"Module"}));var J3r=Nae().RailroadAbnf.parser.LangiumParser,pZ=x(t=>{const e=t.alternatives.map(Q3r);return e.length===1?e[0]:{type:"choice",alternatives:e}},"transformAlternation"),Q3r=x(t=>{const e=t.elements.map(txr);return e.length===1?e[0]:{type:"sequence",elements:e}},"transformConcatenation"),exr=x(t=>{if(t.includes("*")){const[r,n]=t.split("*"),i=r?parseInt(r,10):0,a=n?parseInt(n,10):1/0;return{min:i,max:a}}const e=parseInt(t,10);return{min:e,max:e}},"parseRepeat"),txr=x(t=>{const e=rxr(t.primary);if(!t.repeat)return e;const{min:r,max:n}=exr(t.repeat);return r===0&&n===1?{type:"optional",element:e}:{type:"repetition",element:e,min:r,max:n}},"transformElement"),rxr=x(t=>{switch(t.$type){case"AbnfStringLiteral":return{type:"terminal",value:t.value};case"AbnfNumVal":return{type:"terminal",value:t.value};case"AbnfRuleName":return{type:"nonterminal",name:t.name};case"AbnfGroup":return pZ(t.element);case"AbnfOptionalGroup":return{type:"optional",element:pZ(t.element)};default:throw new Error(`Unsupported ABNF primary node: ${t.$type}`)}},"transformPrimary"),nxr=x(t=>({name:t.name,definition:pZ(t.definition)}),"transformRule"),ixr=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(nxr(e)))},"populateDb"),axr={parse:x(t=>{ta.clear(),de.debug("[ABNF Parser] Starting Langium parse");const e=J3r.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[ABNF Parser] Parsed rules:",r.rules.length),ixr(r),de.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:ta}},sxr={parser:axr,db:ta,renderer:SB,styles:CB};const oxr=Object.freeze(Object.defineProperty({__proto__:null,diagram:sxr},Symbol.toStringTag,{value:"Module"}));var lxr=Oae().RailroadPeg.parser.LangiumParser,sJe=x(t=>{const e=t.alternatives.map(cxr);return e.length===1?e[0]:{type:"choice",alternatives:e}},"transformOrderedChoice"),cxr=x(t=>{const e=t.elements.map(uxr);return e.length===1?e[0]:{type:"sequence",elements:e}},"transformSequence"),uxr=x(t=>{const e=hxr(t.suffix);return t.operator?{type:"special",text:t.operator==="&"?`&${J2e(e)}`:`!${J2e(e)}`}:e},"transformPrefix"),J2e=x(t=>{switch(t.type){case"terminal":return`"${t.value}"`;case"nonterminal":return t.name;case"special":return t.text;default:return"(...)"}},"nodeToLabel"),hxr=x(t=>{const e=dxr(t.primary);if(!t.operator)return e;switch(t.operator){case"?":return{type:"optional",element:e};case"*":return{type:"repetition",element:e,min:0,max:1/0};case"+":return{type:"repetition",element:e,min:1,max:1/0};default:throw new Error(`Unsupported PEG suffix operator: ${t.operator}`)}},"transformSuffix"),dxr=x(t=>{switch(t.$type){case"PegLiteral":return{type:"terminal",value:t.value};case"PegIdentifier":return{type:"nonterminal",name:t.name};case"PegGroup":return sJe(t.element);case"PegAny":return{type:"special",text:t.dot};default:throw new Error(`Unsupported PEG primary node: ${t.$type}`)}},"transformPrimary"),fxr=x(t=>({name:t.name,definition:sJe(t.definition)}),"transformRule"),pxr=x(t=>{ec(t,ta),t.title&&ta.setTitle(t.title),t.rules.map(e=>ta.addRule(fxr(e)))},"populateDb"),gxr={parse:x(t=>{ta.clear(),de.debug("[PEG Parser] Starting Langium parse");const e=lxr.parse(t);if(e.lexerErrors.length>0||e.parserErrors.length>0)throw new nk(e);const r=e.value;de.debug("[PEG Parser] Parsed rules:",r.rules.length),pxr(r),de.debug("[PEG Parser] Parse complete")},"parse"),parser:{yy:ta}},mxr={parser:gxr,db:ta,renderer:SB,styles:CB};const Uxr=Object.freeze(Object.defineProperty({__proto__:null,diagram:mxr},Symbol.toStringTag,{value:"Module"}));var yxr=1;function Dse(){if(!(typeof globalThis>"u"))return globalThis}x(Dse,"getCaptureGlobal");function oJe(){var t;return!!((t=Dse())!=null&&t.mermaidCaptureSizes)}x(oJe,"shouldCaptureSizes");function lJe(){return typeof location>"u"?"browser-dev":`${location.pathname}${location.search}`}x(lJe,"capturedFromLocation");function cJe(t,e){const r=Dse();if(!r)return;const n=e.node(),i=(n&&"ownerSVGElement"in n?n.ownerSVGElement:null)??n,a=(i==null?void 0:i.id)??"(unknown)";r.mermaidCapturedSizes??(r.mermaidCapturedSizes=[]);const s={svgId:a,sizes:t};r.mermaidCapturedSizes.push(s),r.mermaidLastCapturedSizes=s}x(cJe,"emitCapturedSizes");function uJe(t,e){const r=[];for(const n of e.nodes)n.isGroup||r.push({id:n.id,width:n.width??0,height:n.height??0});r.length!==0&&cJe({metadata:{captureVersion:yxr,capturedAt:new Date().toISOString(),capturedFrom:lJe()},nodes:r},t)}x(uJe,"captureNodeSizes");const bxr=Object.freeze(Object.defineProperty({__proto__:null,captureNodeSizes:uJe,shouldCaptureSizes:oJe},Symbol.toStringTag,{value:"Module"})),vxr=Object.freeze(Object.defineProperty({__proto__:null,InfoModule:Cje,createInfoServices:Sje},Symbol.toStringTag,{value:"Module"})),xxr=Object.freeze(Object.defineProperty({__proto__:null,PacketModule:kje,createPacketServices:Eje},Symbol.toStringTag,{value:"Module"})),wxr=Object.freeze(Object.defineProperty({__proto__:null,PieModule:Aje,createPieServices:Mje},Symbol.toStringTag,{value:"Module"})),Txr=Object.freeze(Object.defineProperty({__proto__:null,TreeViewModule:Rje,createTreeViewServices:Ije},Symbol.toStringTag,{value:"Module"})),Cxr=Object.freeze(Object.defineProperty({__proto__:null,ArchitectureModule:Lje,createArchitectureServices:Nje},Symbol.toStringTag,{value:"Module"})),Sxr=Object.freeze(Object.defineProperty({__proto__:null,GitGraphModule:wje,createGitGraphServices:Tje},Symbol.toStringTag,{value:"Module"})),kxr=Object.freeze(Object.defineProperty({__proto__:null,EventModelingModule:Dje,createEventModelingServices:Pje},Symbol.toStringTag,{value:"Module"})),Exr=Object.freeze(Object.defineProperty({__proto__:null,RadarModule:cje,createRadarServices:uje},Symbol.toStringTag,{value:"Module"})),Axr=Object.freeze(Object.defineProperty({__proto__:null,RailroadModule:hje,createRailroadServices:Iae},Symbol.toStringTag,{value:"Module"})),Mxr=Object.freeze(Object.defineProperty({__proto__:null,RailroadEbnfModule:dje,createRailroadEbnfServices:Lae},Symbol.toStringTag,{value:"Module"})),Rxr=Object.freeze(Object.defineProperty({__proto__:null,RailroadAbnfModule:fje,createRailroadAbnfServices:Nae},Symbol.toStringTag,{value:"Module"})),Ixr=Object.freeze(Object.defineProperty({__proto__:null,RailroadPegModule:pje,createRailroadPegServices:Oae},Symbol.toStringTag,{value:"Module"})),Lxr=Object.freeze(Object.defineProperty({__proto__:null,TreemapModule:mje,createTreemapServices:Uje},Symbol.toStringTag,{value:"Module"})),Nxr=Object.freeze(Object.defineProperty({__proto__:null,WardleyModule:yje,createWardleyServices:bje},Symbol.toStringTag,{value:"Module"})),Oxr=Object.freeze(Object.defineProperty({__proto__:null,CynefinModule:vje,createCynefinServices:xje},Symbol.toStringTag,{value:"Module"})); diff --git a/apps/mobile/src/screens/WorkspaceMemoDetail.tsx b/apps/mobile/src/screens/WorkspaceMemoDetail.tsx index 603e593f..b611c803 100644 --- a/apps/mobile/src/screens/WorkspaceMemoDetail.tsx +++ b/apps/mobile/src/screens/WorkspaceMemoDetail.tsx @@ -1,3 +1,4 @@ +import { FONT_CATALOG, FONT_IDS } from "@edgeever/shared/font-catalog"; import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { DEFAULT_MEMO_TITLE, parseDiagramDocument, type MemoDetail, type Notebook, type TiptapDoc } from "@edgeever/shared"; import { @@ -239,14 +240,7 @@ const MOBILE_FONT_OPTIONS: Array<{ id: NoteImageFontStyle; labelZh: string; labe { id: "serif", labelZh: "文艺衬线", labelEn: "Serif" }, { id: "sans", labelZh: "现代无衬线", labelEn: "Sans" }, { id: "mono", labelZh: "极客等宽", labelEn: "Mono" }, - { id: "smiley", labelZh: "得意黑", labelEn: "Smiley Sans" }, - { id: "wenkai", labelZh: "霞鹜文楷", labelEn: "LXGW WenKai" }, - { id: "zhuque", labelZh: "朱雀仿宋", labelEn: "Zhuque Fangsong" }, - { id: "heavy", labelZh: "思源黑体 Heavy", labelEn: "Noto Sans SC Heavy" }, - { id: "puhui", labelZh: "普惠体", labelEn: "PuHuiTi" }, - { id: "puhui-heavy", labelZh: "普惠体 Heavy", labelEn: "PuHuiTi Heavy" }, - { id: "youshe", labelZh: "优设标题黑", labelEn: "YouShe Title" }, - { id: "shuhei", labelZh: "阿里妈妈数黑体", labelEn: "Alimama ShuHei" }, + ...FONT_IDS.map(id => ({ id, labelZh: FONT_CATALOG[id].zh, labelEn: FONT_CATALOG[id].en })), ]; const AuthenticatedResourceImage = ({ diff --git a/apps/web/src/components/FontLoadFeedback.tsx b/apps/web/src/components/FontLoadFeedback.tsx index f5bda0a4..b0d9097e 100644 --- a/apps/web/src/components/FontLoadFeedback.tsx +++ b/apps/web/src/components/FontLoadFeedback.tsx @@ -1,4 +1,4 @@ -import { Check, CircleAlert, LoaderCircle } from "lucide-react"; +import { CircleAlert, LoaderCircle } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import type { FontLoadStatus } from "@/lib/font-load-state"; @@ -12,10 +12,10 @@ export const FontLoadIcon = ({ status }: { status: FontLoadStatus }) => { /** Kept outside SelectTrigger/SelectItem so retry is a real keyboard-accessible button. */ export const FontLoadFeedback = ({ status, onRetry }: { status: FontLoadStatus; onRetry: () => void }) => { const { t } = useTranslation(); - if (status === "idle") return null; + if (status === "idle" || status === "ready") return null; return
- {status === "ready" ? {status === "error" ? : null} diff --git a/apps/web/src/components/settings/PreferenceCard.tsx b/apps/web/src/components/settings/PreferenceCard.tsx index a99b51ae..30d37ddc 100644 --- a/apps/web/src/components/settings/PreferenceCard.tsx +++ b/apps/web/src/components/settings/PreferenceCard.tsx @@ -110,6 +110,7 @@ const FontChoiceFields = ({ {t("settings.editorBodyFonts.system")} + {!includeCatalog ? <> + : null} + {includeCatalog && ["wenkai-screen", "neo-zhi-song", "source-serif"].includes(preference.choice) ? ( + + {t(`settings.editorBodyFonts.${preference.choice === "wenkai-screen" ? "wenkaiScreen" : preference.choice === "neo-zhi-song" ? "neoZhiSong" : "sourceSerif"}`)} + + ) : null} {(includeCatalog ? FONT_IDS : ["wenkai", "zhuque"] as const).map(id => ( diff --git a/apps/web/src/lib/editor-body-font.ts b/apps/web/src/lib/editor-body-font.ts index 6b5d961e..c10d93e6 100644 --- a/apps/web/src/lib/editor-body-font.ts +++ b/apps/web/src/lib/editor-body-font.ts @@ -4,18 +4,9 @@ import { watchEditorCatalogFont } from "./editor-catalog-fonts"; export const EDITOR_BODY_FONT_STORAGE_KEY = "edgeever.editorBodyFont"; export const EDITOR_BODY_FONT_CUSTOM_STORAGE_KEY = "edgeever.editorBodyFontCustom"; -export const EDITOR_BODY_FONT_CHOICES = [ - "system", - "wenkai", - "wenkai-screen", - "zhuque", - "source-han-serif", - "neo-zhi-song", - "source-han-sans", - "source-serif", - ...FONT_IDS.filter(id => id !== "wenkai" && id !== "zhuque"), - "custom", -] as const; +// Legacy choices remain readable, but new selections use the common 23-font directory. +export const LEGACY_EDITOR_BODY_FONT_CHOICES = ["wenkai-screen", "neo-zhi-song", "source-serif"] as const; +export const EDITOR_BODY_FONT_CHOICES = ["system", ...FONT_IDS, ...LEGACY_EDITOR_BODY_FONT_CHOICES, "custom"] as const; export type EditorBodyFontChoice = (typeof EDITOR_BODY_FONT_CHOICES)[number]; diff --git a/apps/web/src/lib/poster-design/fonts.ts b/apps/web/src/lib/poster-design/fonts.ts index 0449cc14..614233ab 100644 --- a/apps/web/src/lib/poster-design/fonts.ts +++ b/apps/web/src/lib/poster-design/fonts.ts @@ -3,34 +3,11 @@ import { loadCatalogFont } from "@edgeever/shared/font-loader"; import { cache } from "fabric"; import { POSTER_FONTS } from "@edgeever/shared"; export { POSTER_FONTS }; -const loads = new Map>(); export async function loadPosterFont(family: string, text = "") { const id = findCatalogFont(family); - if (id) { - await loadCatalogFont(id, text || `${FONT_CATALOG[id].zh} ${FONT_CATALOG[id].en}`, { family }); - cache.clearFontCache(family); - return; - } - const font = POSTER_FONTS.find((entry) => entry.family === family); - if (!font) return Promise.resolve(); - if (!loads.has(family)) { - const face = new FontFace( - family, - `url("/poster-fonts/${font.file}.${font.file === "douyin" ? "ttf" : "woff2"}")`, - ); - const promise = face - .load() - .then((loaded) => { - window.document.fonts.add(loaded); - cache.clearFontCache(family); - }) - .catch((error) => { - loads.delete(family); - throw error; - }); - loads.set(family, promise); - } - return loads.get(family)!; + if (!id) return; + await loadCatalogFont(id, text || `${FONT_CATALOG[id].zh} ${FONT_CATALOG[id].en}`, { family }); + cache.clearFontCache(family); } export const loadDesignFonts = (families: string[], text: string) => Promise.all([...new Set(families)].map(family => loadPosterFont(family, text))); diff --git a/docs/note-image-fonts.md b/docs/note-image-fonts.md index c7ed5040..a0a4806d 100644 --- a/docs/note-image-fonts.md +++ b/docs/note-image-fonts.md @@ -2,7 +2,7 @@ [简体中文](note-image-fonts.zh-CN.md) -The share-image typography picker includes all five fonts in Qiaomu’s designer starter pack, plus LXGW WenKai, Zhuque Fangsong and Noto Sans SC Heavy. They load remote, version-pinned resources on demand; no additional font binaries are included in Web, desktop, Android or iOS packages. The picker, loader and license notices add a small amount of application code. +Body settings, poster design and share images use the same directory of 23 named fonts, including all five fonts in Qiaomu’s designer starter pack. They load remote, version-pinned resources on demand; no additional font binaries are included in Web, desktop, Android or iOS packages. The picker, loader and license notices add a small amount of application code. | Choice | Pinned package and stylesheet | | --- | --- | @@ -21,7 +21,7 @@ Smiley Sans is also a member of the five-font recommended pack. The four additio For CSS-backed fonts, the selected font's manifest is fetched on demand. The renderer matches its Unicode ranges against the card's text and downloads only matching subsets, not the entire family. Note text is matched locally and is never sent in font requests. Manifests and font bytes are cached in the separate, disposable IndexedDB database `edgeever-share-font-cache`. Downloads are deduplicated, failed requests can be retried, and unavailable storage does not prevent online rendering. The application waits for font loading before rendering the image. -Offline reuse requires both the manifest and the relevant character subsets to remain cached. A new character may require another download. Browser storage eviction, restricted WebViews, or clearing site data can remove this ability; HTTP caching and in-session deduplication still apply where available. These fonts do not change editor typography or the existing fonts bundled with card themes. +Offline reuse requires both the manifest and the relevant character subsets to remain cached. A new character may require another download. Browser storage eviction, restricted WebViews, or clearing site data can remove this ability; HTTP caching and in-session deduplication still apply where available. Selecting a catalog font in body settings changes editor typography; the existing card-theme fonts remain available. The original four optional fonts use SIL OFL 1.1. The four newly added starter-pack fonts retain their original vendor terms and metadata, rather than being described as OFL. Copyright notices and the license are retained in `packages/shared/src/note-image-remote-font-notices.ts` and included with generated font CSS for OFL fonts. Original-file fonts instead include source attribution in the generated CSS. Font outlines and font-file names are not modified. The CSS family aliases isolate share-image fonts from editor fonts. @@ -31,10 +31,18 @@ The Web picker renders each visible name in its corresponding font, including th ## Shared body and poster typography -`font-loader.ts` owns optional-font downloading, integrity checks, registration and export CSS. All three entry points keep using the existing `edgeever-share-font-cache` database and URL keys, so previously cached fonts remain usable. The Web `FontLabel` component previews only visible rows; each picker reads the same eight catalog entries and names. +`font-loader.ts` owns optional-font downloading, integrity checks, registration and export CSS. All three entry points keep using the existing `edgeever-share-font-cache` database and URL keys, so previously cached fonts remain usable. The Web `FontLabel` component previews only visible rows; each picker reads the same 23 catalog entries, in the same order and with the same names. Body preferences keep their existing storage keys and legacy choices. Selecting a catalog face applies its shared family with a system or existing local fallback. A debounced observer loads new character subsets as note text changes or another note mounts. Publishing accepts the catalog IDs through the existing body-font setting; no database schema or migration is changed. -Posters preserve existing Chinese family names in saved source. These names resolve to catalog IDs at loading time. Template builds load their selected pairing and any explicit faces/fixed text before final layout; object creation and PNG export await the text's fonts. Reopening a serialized poster follows the same path. Existing non-catalog poster and body fonts remain available through their existing local resources. Interface typography retains its existing choices. +Posters preserve existing Chinese family names in saved source. These names resolve to catalog IDs at loading time. Template builds load their selected pairing and any explicit faces/fixed text before final layout; object creation and PNG export await the text's fonts. Reopening a serialized poster follows the same path. All 23 poster families now resolve through the common loader. Legacy body choices remain readable and are shown when already selected; new selections use the common directory. Interface typography retains its existing choices. No font binaries are added to client packages. The shared catalog, loader and preview UI add application code. Android and iOS device acceptance remains separate from Web and editor-bundle checks. + +## Existing poster assets + +The additional 15 fonts are Noto Serif SC, Anton, Douyin Sans, Ma Shan Zheng, Jiangcheng Rounded, ZCOOL QingKe HuangYou, Glow Sans, ZCOOL KuaiLe, Noto Sans SC, Noto Serif SC Heavy, Inter, DM Serif Display, Bebas Neue, Space Grotesk and JetBrains Mono. They reuse the exact existing poster files from repository commit `0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb`; byte lengths and SHA-256 digests are catalog metadata. Web and desktop first reuse their existing `/poster-fonts/` resources, then try the pinned jsDelivr GitHub URL and raw GitHub mirror. File-based native WebViews use the remote sources. All paths share the same persistent cache keys, integrity checks, loading status and retry behavior. + +These existing files are downloaded unchanged, including the already subsetted WOFF2 resources. Their retained copyright and license texts are included in exported CSS via `poster-font-notices.ts`. Latin fonts display Chinese through normal fallback; loading a Latin font does not add Chinese glyphs. No new font binaries are added or copied into client packages. Only catalog, license and application code changes are included. + +Android reads the shared catalog directly. The iOS choice projection is generated from it by `bun scripts/sync-font-catalog.mjs`; use `--check` to verify it is current. This keeps persisted share-image choices aligned with the editor renderer. Native device acceptance remains separate from browser verification. diff --git a/docs/note-image-fonts.zh-CN.md b/docs/note-image-fonts.zh-CN.md index 7210f047..527e0928 100644 --- a/docs/note-image-fonts.zh-CN.md +++ b/docs/note-image-fonts.zh-CN.md @@ -2,7 +2,7 @@ [English](note-image-fonts.md) -分享图片字体选择器包含乔木设计师基础包的全部五款字体,另保留霞鹜文楷、朱雀仿宋和思源黑体 Heavy。字体资源固定版本,远程按需下载。Web、桌面、Android 和 iOS 的安装资源均不增加这些字体的二进制文件;选择器、加载器和许可声明会增加少量应用代码。 +正文设置、海报设计和分享图片使用同一份 23 款具名字体目录,其中包含乔木设计师基础包的全部五款字体。字体资源固定版本,远程按需下载。Web、桌面、Android 和 iOS 的安装资源均不增加这些字体的二进制文件;选择器、加载器和许可声明会增加少量应用代码。 | 字体 | 固定的软件包和样式清单 | | --- | --- | @@ -21,7 +21,7 @@ 对于使用 CSS 分片的字体,选择后才请求其样式清单。渲染器根据卡片文字匹配 Unicode 范围,仅下载命中的分片,不下载整个字体家族。字符匹配在本地进行,字体请求不会发送笔记正文。清单和字体数据保存在独立、可丢弃的 IndexedDB 缓存 `edgeever-share-font-cache` 中;重复下载会合并,失败请求可以重试,存储不可用时仍可联网渲染。生成图片前会等待字体加载完成。 -离线复用需要清单和相关文字分片仍在缓存中,新增字符可能需要另行下载。浏览器回收存储、WebView 限制或清理站点数据可能使离线缓存失效;可用时仍会使用 HTTP 缓存和会话内请求复用。这些字体不改变编辑器排版,也不替换现有主题附带的字体。 +离线复用需要清单和相关文字分片仍在缓存中,新增字符可能需要另行下载。浏览器回收存储、WebView 限制或清理站点数据可能使离线缓存失效;可用时仍会使用 HTTP 缓存和会话内请求复用。在正文设置中选择目录字体会改变编辑器排版;现有卡片主题字体仍然保留。 原先四款可选字体采用 SIL OFL 1.1;本次新增的四款推荐字体保留原厂商授权及内嵌声明,不按 OFL 字体处理。版权声明和完整许可保留在 `packages/shared/src/note-image-remote-font-notices.ts`,并包含在 OFL 字体的生成 CSS 中,原始文件字体则在 CSS 中保留来源说明。字体轮廓和字体文件名称未经修改,CSS 家族别名用于隔离分享图片与编辑器字体。 @@ -31,10 +31,20 @@ Web 字体选择器中的可见名称以及当前选中值使用对应字体显 ## 正文与海报共用字体 -`font-loader.ts` 统一负责可选字体的下载、完整性检查、字体注册和导出 CSS。三个入口继续使用原有 `edgeever-share-font-cache` 数据库和 URL 缓存键,先前下载的字体仍可复用。Web `FontLabel` 组件只为可见行预览字体,各列表读取相同的八款目录条目及名称。 +`font-loader.ts` 统一负责可选字体的下载、完整性检查、字体注册和导出 CSS。三个入口继续使用原有 `edgeever-share-font-cache` 数据库和 URL 缓存键,先前下载的字体仍可复用。Web `FontLabel` 组件只为可见行预览字体,各列表读取相同的 23 款目录条目及名称。 正文字体沿用原有存储键和旧选项。选择目录字体后,使用共享字体家族,并保留系统或原有本地字体回退。防抖观察器会在正文变化或切换笔记时加载新增字符分片。公开分享通过既有正文字体设置接受目录 ID,不改变数据库结构、不新增迁移。 -海报源稿保留既有中文字体家族名称,加载时映射到目录 ID。模板先加载所选搭配,以及实际使用的明确字体和固定文字,再完成最终排版;创建文字对象和导出 PNG 时会等待对应文字的字体就绪。序列化源稿重开时走同一链路。目录之外的现有正文和海报字体继续使用原有本地资源,界面字体保留原有选项。 +海报源稿保留既有中文字体家族名称,加载时映射到目录 ID。模板先加载所选搭配,以及实际使用的明确字体和固定文字,再完成最终排版;创建文字对象和导出 PNG 时会等待对应文字的字体就绪。序列化源稿重开时走同一链路。全部 23 款海报字体统一使用共享加载器。旧正文选项保持可读,已选中的旧选项仍可显示;新选择使用共同目录。界面字体保留原有选项。 客户端不新增字体二进制文件,共享目录、加载器与预览控件会增加应用代码。Android 和 iOS 真机验收仍需单独完成,Web 与编辑器资源构建不能代替真机验收。 + +## 23 款统一字体目录 + +正文设置、海报设计和分享图片使用同一份 23 款字体目录,顺序和名称一致。各入口仍保留跟随系统、跟随主题等适用的特殊选项。旧正文设置保持可读,当前已选中的旧字体仍可显示,新选择使用共同目录。 + +新增接入的 15 款为:思源宋体、Anton、抖音美好体、马善政楷书、江城圆体、站酷庆科黄油体、未来荧黑、站酷快乐体、思源黑体、思源宋体 Heavy、Inter、DM Serif Display、Bebas Neue、Space Grotesk 和 JetBrains Mono。复用仓库提交 `0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb` 中已有的海报字体文件,目录记录文件长度和 SHA-256 摘要。Web 和桌面端优先复用已有 `/poster-fonts/` 资源,再尝试固定提交的 jsDelivr GitHub 地址和 raw GitHub 镜像;使用 file URL 的原生 WebView 使用远程来源。所有来源共用缓存键、完整性校验、加载状态和失败重试。 + +这些已有文件保持原样,包括此前已做过子集化的 WOFF2 文件。版权与许可全文通过 `poster-font-notices.ts` 保留在导出 CSS 中。西文字体的中文字符使用常规后备字体,下载西文字体不会增加其中文字形。没有新增或复制字体二进制到客户端安装包,仅增加目录、许可和应用代码。 + +Android 直接读取共享目录。iOS 选项由 `bun scripts/sync-font-catalog.mjs` 从同一目录生成,使用 `--check` 检查同步情况,确保已保存的分享字体与编辑器渲染一致。浏览器验证不能替代原生设备验收。 diff --git a/packages/shared/src/font-catalog.test.mjs b/packages/shared/src/font-catalog.test.mjs index c8a3c994..30e8de8e 100644 --- a/packages/shared/src/font-catalog.test.mjs +++ b/packages/shared/src/font-catalog.test.mjs @@ -7,8 +7,9 @@ import { parsePublishedNoteBodyFont } from "./sharing"; import { POSTER_FONTS } from "./poster-fonts"; import { createPosterDocument, parsePosterDocument, serializePosterDocument } from "./poster"; -test("all eight catalog fonts are available to sharing, body settings and posters without duplicate poster families", () => { - expect(FONT_IDS).toHaveLength(8); +test("all 23 catalog fonts are available to sharing, body settings and posters without duplicate poster families", () => { + expect(FONT_IDS).toHaveLength(23); + expect(POSTER_FONTS.map(font => font.family)).toEqual(FONT_IDS.map(id => FONT_CATALOG[id].posterFamily)); expect(NOTE_IMAGE_REMOTE_FONTS).toBe(FONT_CATALOG); expect(new Set(POSTER_FONTS.map(font => font.family)).size).toBe(POSTER_FONTS.length); for (const id of FONT_IDS) { @@ -26,3 +27,26 @@ test("all eight catalog fonts are available to sharing, body settings and poster } expect(NoteBodyFontUpdateSchema.safeParse({ bodyFont: "unknown-font" }).success).toBe(false); }); + +test("poster downloads reuse pinned assets and retain their integrity and license metadata", async () => { + const { readFile } = await import("node:fs/promises"); + const { createHash } = await import("node:crypto"); + const { remoteFontCssUrl, remoteFontUrls } = await import("./font-catalog"); + const { POSTER_FONT_NOTICES } = await import("./poster-font-notices"); + const { resolveNoteImagePreferences } = await import("./note-image-preferences"); + for (const id of FONT_IDS) { + expect(resolveNoteImagePreferences({ fontStyle: id }).fontStyle).toBe(id); + const font = FONT_CATALOG[id]; + if (!font.path.startsWith("gh/")) continue; + const name = font.path.split("/").at(-1); + const bytes = await readFile(new URL(`../../../apps/web/public/poster-fonts/${name}`, import.meta.url)); + expect(bytes.byteLength).toBe(font.bytes); + expect(createHash("sha256").update(bytes).digest("hex")).toBe(font.sha256); + const urls = remoteFontUrls(remoteFontCssUrl(id)); + expect(urls).toHaveLength(2); + expect(urls[1]).toStartWith("https://raw.githubusercontent.com/tianma-if/edgeever/"); + expect(urls[1]).toContain("/0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/"); + expect(POSTER_FONT_NOTICES[id]).toContain("Copyright"); + } + expect(() => remoteFontUrls("https://cdn.jsdelivr.net/gh/untrusted/repo@main/font.ttf")).toThrow(); +}); diff --git a/packages/shared/src/font-catalog.ts b/packages/shared/src/font-catalog.ts index db7cd9eb..39755dc2 100644 --- a/packages/shared/src/font-catalog.ts +++ b/packages/shared/src/font-catalog.ts @@ -9,6 +9,23 @@ export const FONT_CATALOG = { "puhui-heavy": { zh: "普惠体 Heavy", en: "PuHuiTi Heavy", posterFamily: "阿里巴巴普惠体 Heavy", labelKey: "editor.imageShare.fontStyles.puhuiHeavy", family: "EdgeEver Share Puhui Heavy", path: "@fontpkg/alibaba-pu-hui-ti-3-0@3.1.2/AlibabaPuHuiTi-3-105-Heavy.ttf", weight: "900", bytes: 2536300, sha256: "94cbb24e451a68b44d932d343579fb31571c743df2cfcd144ba60e0a9aa86e64" }, youshe: { zh: "优设标题黑", en: "YouShe Title", posterFamily: "优设标题黑", labelKey: "editor.imageShare.fontStyles.youshe", family: "EdgeEver Share Youshe", path: "@fontpkg/you-she-biao-ti-hei@1.0.0/%E4%BC%98%E8%AE%BE%E6%A0%87%E9%A2%98%E9%BB%91.ttf", weight: "400", bytes: 1414784, sha256: "dab883d69fb713233ea5d9aff8d1f27ccc2b71e249143edf6ea08367784c5dba" }, shuhei: { zh: "阿里妈妈数黑体", en: "Alimama ShuHei", posterFamily: "阿里妈妈数黑体", labelKey: "editor.imageShare.fontStyles.shuhei", family: "EdgeEver Share Shuhei", path: "@fontpkg/alimama-shu-hei-ti@1.0.5/AlimamaShuHeiTi-Bold.ttf", weight: "700", bytes: 1360708, sha256: "3ef4ec3f5bac4b71cdee9190aabd5b20995016a59ffee60de87c9a204ac2a684" }, + + // Reuse existing poster assets unchanged, pinned to a repository commit. + "source-han-serif": {"zh": "思源宋体", "en": "Noto Serif SC", "posterFamily": "思源宋体", "labelKey": "editor.imageShare.fontStyles.source-han-serif", "family": "EdgeEver Share Noto Serif SC", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/notoserif.woff2", "weight": "400", "bytes": 1662732, "sha256": "de134adb512429992d47096d5e5d94d0488d3890f4e9a592aaabcf4fc6fb3355"}, + "anton": {"zh": "Anton", "en": "Anton", "posterFamily": "Anton", "labelKey": "editor.imageShare.fontStyles.anton", "family": "EdgeEver Share Anton", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/anton.woff2", "weight": "400", "bytes": 19460, "sha256": "5aa51ca1a8d65846f52724d0e3c6f728347f19df1f4ee88acde613ef403f50c1"}, + "douyin": {"zh": "抖音美好体", "en": "Douyin Sans", "posterFamily": "抖音美好体", "labelKey": "editor.imageShare.fontStyles.douyin", "family": "EdgeEver Share Douyin Sans", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/douyin.ttf", "weight": "400", "bytes": 1919404, "sha256": "fbecfacdfac33982774e301073a8d357a09c698cd3134bb6ddec0f5bfc268fa1"}, + "mashan": {"zh": "马善政楷书", "en": "Ma Shan Zheng", "posterFamily": "马善政楷书", "labelKey": "editor.imageShare.fontStyles.mashan", "family": "EdgeEver Share Ma Shan Zheng", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/mashan.woff2", "weight": "400", "bytes": 3233640, "sha256": "9bb3130d1b7e752e1df902891a41c7aeb5b94697951acf54eec60b954d7a1cb2"}, + "jiangcheng": {"zh": "江城圆体", "en": "Jiangcheng Rounded", "posterFamily": "江城圆体", "labelKey": "editor.imageShare.fontStyles.jiangcheng", "family": "EdgeEver Share Jiangcheng Rounded", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/jiangcheng.woff2", "weight": "400", "bytes": 1565164, "sha256": "0be9728fe58f3c69ad93a2a952ab0e78089873f06ebe80110b67664ffa22cbdc"}, + "huangyou": {"zh": "站酷庆科黄油体", "en": "ZCOOL QingKe HuangYou", "posterFamily": "站酷庆科黄油体", "labelKey": "editor.imageShare.fontStyles.huangyou", "family": "EdgeEver Share ZCOOL QingKe HuangYou", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/huangyou.woff2", "weight": "400", "bytes": 1742972, "sha256": "a15d7e72cd7ad20b67ecf6adf46d08bab5764ea700ec8fae7cc29055e9cb9fbc"}, + "glow": {"zh": "未来荧黑", "en": "Glow Sans", "posterFamily": "未来荧黑", "labelKey": "editor.imageShare.fontStyles.glow", "family": "EdgeEver Share Glow Sans", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/glow.woff2", "weight": "900", "bytes": 1826500, "sha256": "c12d4668a33019c757a9ec2c6d7fc43629189a8d5e8636d9bb38c3936020684c"}, + "kuaile": {"zh": "站酷快乐体", "en": "ZCOOL KuaiLe", "posterFamily": "站酷快乐体", "labelKey": "editor.imageShare.fontStyles.kuaile", "family": "EdgeEver Share ZCOOL KuaiLe", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/kuaile.woff2", "weight": "400", "bytes": 855692, "sha256": "19d11b1154b2e5f37371d7be9640dbae97d06fbf2331cb5fc257ae5f813ea5e8"}, + "source-han-sans": {"zh": "思源黑体", "en": "Noto Sans SC", "posterFamily": "思源黑体", "labelKey": "editor.imageShare.fontStyles.source-han-sans", "family": "EdgeEver Share Noto Sans SC", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/notosans.woff2", "weight": "400", "bytes": 1263476, "sha256": "3ca125c45785eab24bdcd4d4039431479fef196c3aa9913fed4b0f255336101f"}, + "source-han-serif-heavy": {"zh": "思源宋体 Heavy", "en": "Noto Serif SC Heavy", "posterFamily": "思源宋体 Heavy", "labelKey": "editor.imageShare.fontStyles.source-han-serif-heavy", "family": "EdgeEver Share Noto Serif SC Heavy", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/notoserif-heavy.woff2", "weight": "900", "bytes": 1580648, "sha256": "729c42f795041ba47f794be1729e6d515b51f73d9baf2085c5b0941e30e6bfb3"}, + "inter": {"zh": "Inter", "en": "Inter", "posterFamily": "Inter", "labelKey": "editor.imageShare.fontStyles.inter", "family": "EdgeEver Share Inter", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/inter.woff2", "weight": "400", "bytes": 24980, "sha256": "47084ea0d235fbca0b0c5ec663969a906daaeb667fd5f191007ec5c80b62ed5c"}, + "dmserif": {"zh": "DM Serif Display", "en": "DM Serif Display", "posterFamily": "DM Serif Display", "labelKey": "editor.imageShare.fontStyles.dmserif", "family": "EdgeEver Share DM Serif Display", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/dmserif.woff2", "weight": "400", "bytes": 27692, "sha256": "c0652f22903b1a547ad50495aa3f5314e4f666e34921e9e953e3f545fdde14ba"}, + "bebas": {"zh": "Bebas Neue", "en": "Bebas Neue", "posterFamily": "Bebas Neue", "labelKey": "editor.imageShare.fontStyles.bebas", "family": "EdgeEver Share Bebas Neue", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/bebas.woff2", "weight": "400", "bytes": 15428, "sha256": "83baf2705cd75f7047e8d11dc9aceeb4ee50407a72f1981937b0b909227b1e68"}, + "spacegrotesk": {"zh": "Space Grotesk", "en": "Space Grotesk", "posterFamily": "Space Grotesk", "labelKey": "editor.imageShare.fontStyles.spacegrotesk", "family": "EdgeEver Share Space Grotesk", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/spacegrotesk.woff2", "weight": "400", "bytes": 15600, "sha256": "8c3d72df71f151bd549c89d78b146858c25e4d16d3a3bfe79bf93bf37ee2c9ae"}, + "jetbrains": {"zh": "JetBrains Mono", "en": "JetBrains Mono", "posterFamily": "JetBrains Mono", "labelKey": "editor.imageShare.fontStyles.jetbrains", "family": "EdgeEver Share JetBrains Mono", "path": "gh/tianma-if/edgeever@0da78f6ce8a4643b2dfb7afe9f7a5101472c4ffb/apps/web/public/poster-fonts/jetbrains.woff2", "weight": "400", "bytes": 22516, "sha256": "1d5b5738120473ecb0d758290426d6d4b59873b7f3d4608541989cd0ab5452e3"}, } as const; /** The five entries in Qiaomu's approximately 15 MB designer starter pack. */ @@ -44,11 +61,19 @@ export const parseRemoteFontCss = (css: string, font: RemoteNoteImageFont): Remo /** Mirror paths are equivalent, so both hosts share a single persistent cache key. */ export const remoteFontUrls = (url: string) => { + if (url.startsWith("https://cdn.jsdelivr.net/gh/")) { + // Only the pinned files listed in our catalog may use the repository mirror. + if (!FONT_IDS.some(id => remoteFontCssUrl(id) === url)) throw new Error("NOTE_IMAGE_FONT_UNAVAILABLE"); + return [url, url.replace("https://cdn.jsdelivr.net/gh/", "https://raw.githubusercontent.com/").replace("@", "/")]; + } if (!url.startsWith(CDN_ROOTS[0])) throw new Error("NOTE_IMAGE_FONT_UNAVAILABLE"); return CDN_ROOTS.map(root => root + url.slice(CDN_ROOTS[0].length)); }; -export const remoteFontCssUrl = (font: RemoteNoteImageFont) => CDN_ROOTS[0] + FONT_CATALOG[font].path; +export const remoteFontCssUrl = (font: RemoteNoteImageFont) => { + const path = FONT_CATALOG[font].path; + return path.startsWith("gh/") ? `https://cdn.jsdelivr.net/${path}` : CDN_ROOTS[0] + path; +}; export const remoteOriginalFont = (font: RemoteNoteImageFont): RemoteFontSubset | undefined => { const entry = FONT_CATALOG[font]; @@ -57,7 +82,7 @@ export const remoteOriginalFont = (font: RemoteNoteImageFont): RemoteFontSubset : undefined; }; -export const remoteFontIntegrity = (url: string) => Object.values(FONT_CATALOG).find(entry => "bytes" in entry && CDN_ROOTS[0] + entry.path === url); +export const remoteFontIntegrity = (url: string) => FONT_IDS.map(id => ({ ...FONT_CATALOG[id], url: remoteFontCssUrl(id) })).find(entry => "bytes" in entry && entry.url === url); export const FONT_IDS = Object.keys(FONT_CATALOG) as RemoteNoteImageFont[]; export const findCatalogFont = (idOrFamily: string) => FONT_IDS.find(id => id === idOrFamily || FONT_CATALOG[id].family === idOrFamily || FONT_CATALOG[id].posterFamily === idOrFamily); diff --git a/packages/shared/src/font-loader.ts b/packages/shared/src/font-loader.ts index d627e710..1e47a6ae 100644 --- a/packages/shared/src/font-loader.ts +++ b/packages/shared/src/font-loader.ts @@ -1,3 +1,4 @@ +import { POSTER_FONT_NOTICES } from "./poster-font-notices"; import { readFontCache, writeFontCache } from "./note-image-font-cache"; import { FONT_CATALOG, parseRemoteFontCss, remoteFontCssUrl, remoteFontUrls, remoteOriginalFont, remoteFontIntegrity, type RemoteNoteImageFont } from "./font-catalog"; import { NOTE_IMAGE_REMOTE_FONT_NOTICES } from "./note-image-remote-font-notices"; @@ -29,7 +30,14 @@ export const createRemoteFontResourceLoader = () => { pending = (async () => { const cached = await readFontCache(url); if (cached && await validRemoteResource(cached, css, url)) return cached; - for (const mirror of remoteFontUrls(url)) { + const mirrors = remoteFontUrls(url); + const entry = remoteFontIntegrity(url); + // Web/desktop already ship these poster assets. Reuse them without adding + // another copy, and cache under the same pinned key used by native WebViews. + if (entry?.path.startsWith("gh/") && typeof location !== "undefined" && /^https?:$/.test(location.protocol)) { + mirrors.unshift(new URL(`/poster-fonts/${entry.path.split("/").at(-1)}`, location.href).href); + } + for (const mirror of mirrors) { try { const data = await new Promise((resolve, reject) => { const request = new XMLHttpRequest(); @@ -44,7 +52,7 @@ export const createRemoteFontResourceLoader = () => { if (!await validRemoteResource(data, css, url)) continue; await writeFontCache(url, data); return data; - } catch { /* Try the second pinned mirror. Never send note text to either host. */ } + } catch { /* Try the next pinned source. Never send note text in requests. */ } } throw new Error("NOTE_IMAGE_FONT_UNAVAILABLE"); })(); @@ -110,8 +118,8 @@ export const loadCatalogFont = async (id: RemoteNoteImageFont, text: string, { e return embed ? `@font-face{font-family:"${family}";font-style:normal;font-weight:${block.weight};src:url("${await remoteDataUrl(block.asset)}");unicode-range:${block.range}}` : ""; })); if (!embed || !blocks.length) return ""; - const notices = original + const notices = POSTER_FONT_NOTICES[id] ?? (original ? `Original font loaded unchanged from ${original.asset}. Original copyright and licensing metadata are retained in the font container. Alibaba: https://fonts.alibabagroup.com/ ; YouShe: https://www.uisdc.com/ . No conversion or subsetting is performed.` - : NOTE_IMAGE_REMOTE_FONT_NOTICES; + : NOTE_IMAGE_REMOTE_FONT_NOTICES); return `/*! ${notices.replaceAll("*/", "* /")} */\n${css.join("\n")}`; }; diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts index 116fa058..66538136 100644 --- a/packages/shared/src/i18n/en-US.ts +++ b/packages/shared/src/i18n/en-US.ts @@ -2476,6 +2476,22 @@ export const enUS = { }, fontStyle: "Typography", fontStyles: { + "source-han-serif": "Noto Serif SC", + "anton": "Anton", + "douyin": "Douyin Sans", + "mashan": "Ma Shan Zheng", + "jiangcheng": "Jiangcheng Rounded", + "huangyou": "ZCOOL QingKe HuangYou", + "glow": "Glow Sans", + "kuaile": "ZCOOL KuaiLe", + "source-han-sans": "Noto Sans SC", + "source-han-serif-heavy": "Noto Serif SC Heavy", + "inter": "Inter", + "dmserif": "DM Serif Display", + "bebas": "Bebas Neue", + "spacegrotesk": "Space Grotesk", + "jetbrains": "JetBrains Mono", + auto: "Follow Theme", kai: "Handwritten Kai", serif: "Editorial Serif", diff --git a/packages/shared/src/i18n/ja.ts b/packages/shared/src/i18n/ja.ts index e2e53995..e9007571 100644 --- a/packages/shared/src/i18n/ja.ts +++ b/packages/shared/src/i18n/ja.ts @@ -2465,6 +2465,22 @@ export const ja = { }, fontStyle: "書体", fontStyles: { + "source-han-serif": "Noto Serif SC", + "anton": "Anton", + "douyin": "Douyin Sans", + "mashan": "Ma Shan Zheng", + "jiangcheng": "Jiangcheng Rounded", + "huangyou": "ZCOOL QingKe HuangYou", + "glow": "Glow Sans", + "kuaile": "ZCOOL KuaiLe", + "source-han-sans": "Noto Sans SC", + "source-han-serif-heavy": "Noto Serif SC Heavy", + "inter": "Inter", + "dmserif": "DM Serif Display", + "bebas": "Bebas Neue", + "spacegrotesk": "Space Grotesk", + "jetbrains": "JetBrains Mono", + auto: "テーマに合わせる", kai: "手書き・楷書", serif: "本文明朝", diff --git a/packages/shared/src/i18n/pl.ts b/packages/shared/src/i18n/pl.ts index 9d8543b1..466081a8 100644 --- a/packages/shared/src/i18n/pl.ts +++ b/packages/shared/src/i18n/pl.ts @@ -2465,6 +2465,22 @@ export const pl = { }, fontStyle: "Typografia", fontStyles: { + "source-han-serif": "Noto Serif SC", + "anton": "Anton", + "douyin": "Douyin Sans", + "mashan": "Ma Shan Zheng", + "jiangcheng": "Jiangcheng Rounded", + "huangyou": "ZCOOL QingKe HuangYou", + "glow": "Glow Sans", + "kuaile": "ZCOOL KuaiLe", + "source-han-sans": "Noto Sans SC", + "source-han-serif-heavy": "Noto Serif SC Heavy", + "inter": "Inter", + "dmserif": "DM Serif Display", + "bebas": "Bebas Neue", + "spacegrotesk": "Space Grotesk", + "jetbrains": "JetBrains Mono", + auto: "Zgodnie z motywem", kai: "Pismo odręczne / Kaligrafia", serif: "Redakcyjna szeryfowa", diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts index bafefa55..547568d1 100644 --- a/packages/shared/src/i18n/zh-CN.ts +++ b/packages/shared/src/i18n/zh-CN.ts @@ -2474,6 +2474,22 @@ export const zhCN = { }, fontStyle: "字体风格", fontStyles: { + "source-han-serif": "思源宋体", + "anton": "Anton", + "douyin": "抖音美好体", + "mashan": "马善政楷书", + "jiangcheng": "江城圆体", + "huangyou": "站酷庆科黄油体", + "glow": "未来荧黑", + "kuaile": "站酷快乐体", + "source-han-sans": "思源黑体", + "source-han-serif-heavy": "思源宋体 Heavy", + "inter": "Inter", + "dmserif": "DM Serif Display", + "bebas": "Bebas Neue", + "spacegrotesk": "Space Grotesk", + "jetbrains": "JetBrains Mono", + auto: "跟随主题", kai: "文楷手书", serif: "文艺衬线", diff --git a/packages/shared/src/note-image-card.ts b/packages/shared/src/note-image-card.ts index 274712a6..63a32154 100644 --- a/packages/shared/src/note-image-card.ts +++ b/packages/shared/src/note-image-card.ts @@ -1,4 +1,4 @@ -import { CATALOG_FONT_STACKS } from "./font-catalog"; +import { CATALOG_FONT_STACKS, type RemoteNoteImageFont } from "./font-catalog"; export type NoteImageFormat = "jpeg" | "png"; export const NOTE_IMAGE_THEME_OPTIONS = ["gradient", "glass", "quote", "book", "memo", "bento", "black-sun", "frame", "handwritten", "code"] as const; @@ -6,7 +6,7 @@ export type NoteImageActiveTheme = (typeof NOTE_IMAGE_THEME_OPTIONS)[number]; type LegacyNoteImageTheme = "paper" | "press" | "highlight" | "slate" | "aurora" | "sunset" | "midnight" | "editorial" | "terminal" | "film" | "mint" | "lavender"; export type NoteImageTheme = NoteImageActiveTheme | LegacyNoteImageTheme; export type NoteImageBackground = NoteImageTheme | "warm" | "notepad" | "xuan" | "polaroid"; -export type NoteImageFontStyle = "auto" | "sans" | "serif" | "mono" | "kai" | "smiley" | "wenkai" | "zhuque" | "heavy" | "puhui" | "puhui-heavy" | "youshe" | "shuhei"; +export type NoteImageFontStyle = "auto" | "sans" | "serif" | "mono" | "kai" | RemoteNoteImageFont; export type NoteImageFontSize = "sm" | "md" | "lg"; export type NoteImageCardWidth = "compact" | "standard" | "wide"; export const NOTE_IMAGE_EXPORT_WIDTH = 768; diff --git a/packages/shared/src/note-image-preferences.ts b/packages/shared/src/note-image-preferences.ts index 4ec42147..0f6912f4 100644 --- a/packages/shared/src/note-image-preferences.ts +++ b/packages/shared/src/note-image-preferences.ts @@ -1,3 +1,4 @@ +import { FONT_IDS } from "./font-catalog"; import { NOTE_IMAGE_THEME_OPTIONS, type NoteImageActiveTheme, @@ -30,7 +31,7 @@ export const DEFAULT_NOTE_IMAGE_PREFERENCES: NoteImagePreferences = { const choices = { theme: NOTE_IMAGE_THEME_OPTIONS, - fontStyle: ["auto", "kai", "serif", "sans", "mono", "smiley", "wenkai", "zhuque", "heavy", "puhui", "puhui-heavy", "youshe", "shuhei"], + fontStyle: ["auto", "kai", "serif", "sans", "mono", ...FONT_IDS], fontSize: ["sm", "md", "lg"], cardWidth: ["compact", "standard", "wide"], format: ["png", "jpeg"], diff --git a/packages/shared/src/note-image-remote-fonts.test.mjs b/packages/shared/src/note-image-remote-fonts.test.mjs index 0e30780d..9ae54413 100644 --- a/packages/shared/src/note-image-remote-fonts.test.mjs +++ b/packages/shared/src/note-image-remote-fonts.test.mjs @@ -13,7 +13,7 @@ test('all downloadable fonts retain their choice and use a private family withou for (const [font, metadata] of Object.entries(NOTE_IMAGE_REMOTE_FONTS)) { expect(resolveNoteImagePreferences({ fontStyle: font }).fontStyle).toBe(font); expect(resolveNoteImageTypography('book', font).bodyFamily).toContain(metadata.family); - expect(remoteFontCssUrl(font)).toMatch(/@\d+\.\d+\.\d+\//); + expect(remoteFontCssUrl(font)).toMatch(/@(?:\d+\.\d+\.\d+|[a-f0-9]{40})\//); } // Explicit system styles must not fetch any optional font manifest or file. expect((await prepareNoteImageAssets({ theme: 'book', fontStyle: 'sans', text: '中文', urls: {} })).fontCss).toBe(''); diff --git a/packages/shared/src/poster-font-notices.ts b/packages/shared/src/poster-font-notices.ts new file mode 100644 index 00000000..300cdf0d --- /dev/null +++ b/packages/shared/src/poster-font-notices.ts @@ -0,0 +1,18 @@ +// Existing poster font copyright and license notices, retained in exported font CSS. +export const POSTER_FONT_NOTICES: Readonly> = { + "source-han-serif": "思源宋体 (Noto Serif SC)\nSource: https://fonts.google.com/noto/specimen/Noto+Serif+SC\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\n(c) 2017-2024 Adobe (http://www.adobe.com/).\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "anton": "Anton (Anton)\nSource: https://fonts.google.com/specimen/Anton\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2020 The Anton Project Authors (https://github.com/googlefonts/AntonFont.git)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "douyin": "抖音美好体 (Douyin Sans)\nSource: https://github.com/bytedance/fonts\nThis copy: unmodified original\n\nCopyright (c) , 2023 Beijing Zitiao Network Technology Co. Ltd.\nwith Reserved Font Names \"Douyin\", \"抖音\", \"抖音美好\".\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "mashan": "马善政楷书 (Ma Shan Zheng)\nSource: https://fonts.google.com/specimen/Ma+Shan+Zheng\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2018 The MaShanZheng Project Authors (https://github.com/googlefonts/mashanzheng)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "jiangcheng": "江城圆体 (JiangCheng YuanTi)\nSource: https://www.npmjs.com/package/@fontpkg/jiang-cheng-yuan-ti\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nJiangCheng YuanTi\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "huangyou": "站酷庆科黄油体 (ZCOOL QingKe HuangYou)\nSource: https://fonts.google.com/specimen/ZCOOL+QingKe+HuangYou\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2018 The ZCOOL QingKe HuangYou Project Authors (https://www.github.com/googlefonts/zcool-qingke-huangyou)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "glow": "未来荧黑 (Glow Sans SC Heavy)\nSource: https://github.com/welai/glow-sans\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nGlow Sans (c) 2021 Project Welai\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "kuaile": "站酷快乐体 (ZCOOL KuaiLe)\nSource: https://fonts.google.com/specimen/ZCOOL+KuaiLe\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2018 The ZCOOL KuaiLe Project Authors (https://github.com/googlefonts/zcool-kuaile)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "source-han-sans": "思源黑体 (Noto Sans SC)\nSource: https://fonts.google.com/noto/specimen/Noto+Sans+SC\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\n(c) 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'.\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "source-han-serif-heavy": "思源宋体 Heavy (Noto Serif SC Black)\nSource: https://fonts.google.com/noto/specimen/Noto+Serif+SC\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\n(c) 2017-2024 Adobe (http://www.adobe.com/).\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "inter": "Inter (Inter)\nSource: https://fonts.google.com/specimen/Inter\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2016 The Inter Project Authors (https://github.com/rsms/inter)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "dmserif": "DM Serif Display (DM Serif Display)\nSource: https://fonts.google.com/specimen/DM+Serif+Display\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2014 - 2017 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. Copyright 2019 Google LLC.\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "bebas": "Bebas Neue (Bebas Neue)\nSource: https://fonts.google.com/specimen/Bebas+Neue\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2019 The Bebas Neue Project Authors (https://github.com/dharmatype/Bebas-Neue)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "spacegrotesk": "Space Grotesk (Space Grotesk)\nSource: https://fonts.google.com/specimen/Space+Grotesk\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n", + "jetbrains": "JetBrains Mono (JetBrains Mono)\nSource: https://fonts.google.com/specimen/JetBrains+Mono\nThis copy: subset and converted to WOFF2 by Qiaomu Cover Design\n\nCopyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at: https://openfontlicense.org\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe 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.\nThe 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.\nDEFINITIONS\n\"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.\n\"Reserved Font Name\" refers to any names specified as such after the copyright statement(s).\n\"Original Version\" refers to the collection of Font Software components as distributed by the Copyright Holder(s).\n\"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.\n\"Author\" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.\nPERMISSION & CONDITIONS\nPermission 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:\n- 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.\n- 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.\n- 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.\n- 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.\n- 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.\nTERMINATION\nThis license becomes null and void if any of the above conditions are not met.\nDISCLAIMER\nTHE 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.\n" +}; diff --git a/packages/shared/src/poster-fonts.ts b/packages/shared/src/poster-fonts.ts index 9f21458f..bdf68dda 100644 --- a/packages/shared/src/poster-fonts.ts +++ b/packages/shared/src/poster-fonts.ts @@ -1,95 +1,8 @@ import { FONT_CATALOG, FONT_IDS } from "./font-catalog"; -const LEGACY_POSTER_FONTS = [ - { - family: "思源宋体", - file: "notoserif", - zh: "思源宋体 · 正文", - en: "Source Han Serif", - }, - { family: "Anton", file: "anton", zh: "Anton · 冲击", en: "Anton" }, - { - family: "抖音美好体", - file: "douyin", - zh: "抖音美好体", - en: "Douyin Display", - }, - { - family: "马善政楷书", - file: "mashan", - zh: "马善政楷书", - en: "Ma Shan Zheng", - }, - { - family: "江城圆体", - file: "jiangcheng", - zh: "江城圆体", - en: "Jiangcheng Rounded", - }, - { - family: "站酷庆科黄油体", - file: "huangyou", - zh: "站酷庆科黄油体", - en: "ZCOOL QingKe HuangYou", - }, - { family: "未来荧黑", file: "glow", zh: "未来荧黑", en: "Glow Sans" }, - { family: "朱雀仿宋", file: "zhuque", zh: "朱雀仿宋", en: "Zhuque FangSong" }, - { - family: "站酷快乐体", - file: "kuaile", - zh: "站酷快乐体", - en: "ZCOOL KuaiLe", - }, - - { - family: "思源黑体", - file: "notosans", - zh: "思源黑体 · 正文", - en: "Source Han Sans", - }, - { - family: "思源黑体 Heavy", - file: "notosans-heavy", - zh: "思源黑体 · 特重", - en: "Source Han Sans Heavy", - }, - { - family: "思源宋体 Heavy", - file: "notoserif-heavy", - zh: "思源宋体 · 特重", - en: "Source Han Serif Heavy", - }, - { family: "得意黑", file: "smiley", zh: "得意黑 · 标题", en: "Smiley Sans" }, - { - family: "霞鹜文楷", - file: "wenkai", - zh: "霞鹜文楷 · 手写", - en: "LXGW WenKai", - }, - { family: "Inter", file: "inter", zh: "Inter · 无衬线", en: "Inter" }, - { - family: "DM Serif Display", - file: "dmserif", - zh: "DM Serif · 衬线", - en: "DM Serif Display", - }, - { family: "Bebas Neue", file: "bebas", zh: "Bebas · 窄体", en: "Bebas Neue" }, - { - family: "Space Grotesk", - file: "spacegrotesk", - zh: "Space Grotesk · 科技", - en: "Space Grotesk", - }, - { - family: "JetBrains Mono", - file: "jetbrains", - zh: "JetBrains · 等宽", - en: "JetBrains Mono", - }, -] as const; - -// Persist existing family names; source URLs and display names come from one catalog. -export const POSTER_FONTS = [ - ...LEGACY_POSTER_FONTS.filter(font => !FONT_IDS.some(id => FONT_CATALOG[id].posterFamily === font.family)), - ...FONT_IDS.map(id => ({ family: FONT_CATALOG[id].posterFamily, file: "", zh: FONT_CATALOG[id].zh, en: FONT_CATALOG[id].en })), -]; +/** All named choices come from the same catalog as body and share-image typography. */ +export const POSTER_FONTS = FONT_IDS.map(id => ({ + family: FONT_CATALOG[id].posterFamily, + zh: FONT_CATALOG[id].zh, + en: FONT_CATALOG[id].en, +})); diff --git a/packages/shared/src/sharing.ts b/packages/shared/src/sharing.ts index 45be5cf9..02cf0bd3 100644 --- a/packages/shared/src/sharing.ts +++ b/packages/shared/src/sharing.ts @@ -43,14 +43,7 @@ export type ShareBodyAccess = { }; export const PUBLISHED_NOTE_BODY_FONTS = [ - "wenkai", - "wenkai-screen", - "zhuque", - "source-han-serif", - "neo-zhi-song", - "source-han-sans", - "source-serif", - ...FONT_IDS.filter(id => id !== "wenkai" && id !== "zhuque"), + "wenkai-screen", "neo-zhi-song", "source-serif", ...FONT_IDS, ] as const; export type PublishedNoteBodyFont = (typeof PUBLISHED_NOTE_BODY_FONTS)[number]; diff --git a/scripts/sync-font-catalog.mjs b/scripts/sync-font-catalog.mjs new file mode 100644 index 00000000..d0014197 --- /dev/null +++ b/scripts/sync-font-catalog.mjs @@ -0,0 +1,18 @@ +// Run with Bun after changing the shared directory; Swift uses a generated projection. +import { readFile, writeFile } from 'node:fs/promises'; +import { FONT_CATALOG, FONT_IDS } from '../packages/shared/src/font-catalog.ts'; +const file = new URL('../apps/ios/EdgeEver/Data/Preferences/PreferencesStore.swift', import.meta.url); +const begin = '// BEGIN GENERATED FONT CATALOG — bun scripts/sync-font-catalog.mjs'; +const end = '// END GENERATED FONT CATALOG'; +const text = await readFile(file, 'utf8'); +const rows = FONT_IDS.map(id => { + const font = FONT_CATALOG[id]; + return ` (id: ${JSON.stringify(id)}, zh: ${JSON.stringify(font.zh)}, en: ${JSON.stringify(font.en)}),`; +}).join('\n'); +const generated = `${begin}\nenum NoteImageFontCatalog {\n static let choices: [(id: String, zh: String, en: String)] = [\n${rows}\n ]\n}\n${end}`; +const next = text.includes(begin) + ? text.slice(0, text.indexOf(begin)) + generated + text.slice(text.indexOf(end) + end.length) + : text.replace('import Foundation', `import Foundation\n\n${generated}`); +if (process.argv.includes('--check')) { + if (text !== next) throw new Error('Swift font directory is out of date. Run bun scripts/sync-font-catalog.mjs'); +} else await writeFile(file, next);