0&&(p+=y,le)))for(r=0;r=a)continue;(o>0||e.hskipBeforeAndAfter)&&(i=l.deflt(c.pregap,u),0!==i&&(z=Ve.makeSpan(["arraycolsep"],[]),z.style.width=F(i),M.push(z)));let d=[];for(r=0;r0){const e=Ve.makeLineSpan("hline",t,m),r=Ve.makeLineSpan("hdashline",t,m),n=[{type:"elem",elem:h,shift:0}];for(;c.length>0;){const t=c.pop(),o=t.pos-k;t.isDashed?n.push({type:"elem",elem:r,shift:o}):n.push({type:"elem",elem:e,shift:o})}h=Ve.makeVList({positionType:"individualShift",children:n},t)}if(0===T.length)return Ve.makeSpan(["mord"],[h],t);{let e=Ve.makeVList({positionType:"individualShift",children:T},t);return e=Ve.makeSpan(["tag"],[e],t),Ve.makeFragment([h,e])}},Lr={c:"center ",l:"left ",r:"right "},Dr=function(e,t){const r=[],n=new gt.MathNode("mtd",[],["mtr-glue"]),o=new gt.MathNode("mtd",[],["mml-eqn-num"]);for(let s=0;s0){const t=e.cols;let r="",n=!1,o=0,i=t.length;"separator"===t[0].type&&(a+="top ",o=1),"separator"===t[t.length-1].type&&(a+="bottom ",i-=1);for(let e=o;e0?"left ":"",a+=c[c.length-1].length>0?"right ":"";for(let e=1;e-1?"alignat":"align",s="split"===e.envName,i=Hr(e.parser,{cols:r,addJot:!0,autoTag:s?void 0:Rr(e.envName),emptySingleRow:!0,colSeparationType:o,maxNumCols:s?2:void 0,leqno:e.parser.settings.leqno},"display");let a,l=0;const h={type:"ordgroup",mode:e.mode,body:[]};if(t[0]&&"ordgroup"===t[0].type){let e="";for(let r=0;r0&&c&&(n=1),r[e]={type:"align",align:t,pregap:n,postgap:0}}return i.colSeparationType=c?"align":"alignat",i};Ar({type:"array",names:["array","darray"],props:{numArgs:1},handler(e,t){const r=(Rt(t[0])?[t[0]]:qt(t[0],"ordgroup").body).map((function(e){const t=It(e).text;if(-1!=="lcr".indexOf(t))return{type:"align",align:t};if("|"===t)return{type:"separator",separator:"|"};if(":"===t)return{type:"separator",separator:":"};throw new n("Unknown column alignment: "+t,e)})),o={cols:r,hskipBeforeAndAfter:!0,maxNumCols:r.length};return Hr(e.parser,o,Or(e.envName))},htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix","matrix*","pmatrix*","bmatrix*","Bmatrix*","vmatrix*","Vmatrix*"],props:{numArgs:0},handler(e){const t={matrix:null,pmatrix:["(",")"],bmatrix:["[","]"],Bmatrix:["\\{","\\}"],vmatrix:["|","|"],Vmatrix:["\\Vert","\\Vert"]}[e.envName.replace("*","")];let r="c";const o={hskipBeforeAndAfter:!1,cols:[{type:"align",align:r}]};if("*"===e.envName.charAt(e.envName.length-1)){const t=e.parser;if(t.consumeSpaces(),"["===t.fetch().text){if(t.consume(),t.consumeSpaces(),r=t.fetch().text,-1==="lcr".indexOf(r))throw new n("Expected l or c or r",t.nextToken);t.consume(),t.consumeSpaces(),t.expect("]"),t.consume(),o.cols=[{type:"align",align:r}]}}const s=Hr(e.parser,o,Or(e.envName)),i=Math.max(0,...s.body.map((e=>e.length)));return s.cols=new Array(i).fill({type:"align",align:r}),t?{type:"leftright",mode:e.mode,body:[s],left:t[0],right:t[1],rightColor:void 0}:s},htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["smallmatrix"],props:{numArgs:0},handler(e){const t=Hr(e.parser,{arraystretch:.5},"script");return t.colSeparationType="small",t},htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["subarray"],props:{numArgs:1},handler(e,t){const r=(Rt(t[0])?[t[0]]:qt(t[0],"ordgroup").body).map((function(e){const t=It(e).text;if(-1!=="lc".indexOf(t))return{type:"align",align:t};throw new n("Unknown column alignment: "+t,e)}));if(r.length>1)throw new n("{subarray} can contain only one column");let o={cols:r,hskipBeforeAndAfter:!1,arraystretch:.5};if(o=Hr(e.parser,o,"script"),o.body.length>0&&o.body[0].length>1)throw new n("{subarray} can contain only one column");return o},htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["cases","dcases","rcases","drcases"],props:{numArgs:0},handler(e){const t=Hr(e.parser,{arraystretch:1.2,cols:[{type:"align",align:"l",pregap:0,postgap:1},{type:"align",align:"l",pregap:0,postgap:0}]},Or(e.envName));return{type:"leftright",mode:e.mode,body:[t],left:e.envName.indexOf("r")>-1?".":"\\{",right:e.envName.indexOf("r")>-1?"\\}":".",rightColor:void 0}},htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["align","align*","aligned","split"],props:{numArgs:0},handler:Vr,htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["gathered","gather","gather*"],props:{numArgs:0},handler(e){l.contains(["gather","gather*"],e.envName)&&Ir(e);const t={cols:[{type:"align",align:"c"}],addJot:!0,colSeparationType:"gather",autoTag:Rr(e.envName),emptySingleRow:!0,leqno:e.parser.settings.leqno};return Hr(e.parser,t,"display")},htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["alignat","alignat*","alignedat"],props:{numArgs:1},handler:Vr,htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["equation","equation*"],props:{numArgs:0},handler(e){Ir(e);const t={autoTag:Rr(e.envName),emptySingleRow:!0,singleRow:!0,maxNumCols:1,leqno:e.parser.settings.leqno};return Hr(e.parser,t,"display")},htmlBuilder:Er,mathmlBuilder:Dr}),Ar({type:"array",names:["CD"],props:{numArgs:0},handler(e){return Ir(e),function(e){const t=[];for(e.gullet.beginGroup(),e.gullet.macros.set("\\cr","\\\\\\relax"),e.gullet.beginGroup();;){t.push(e.parseExpression(!1,"\\\\")),e.gullet.endGroup(),e.gullet.beginGroup();const r=e.fetch().text;if("&"!==r&&"\\\\"!==r){if("\\end"===r){0===t[t.length-1].length&&t.pop();break}throw new n("Expected \\\\ or \\cr or \\end",e.nextToken)}e.consume()}let r=[];const o=[r];for(let a=0;a-1);else{if(!("<>AV".indexOf(o)>-1))throw new n('Expected one of "<>AV=|." after @',l[t]);for(let e=0;e<2;e++){let r=!0;for(let h=t+1;h{const r=e.font,n=t.withFont(r);return ht(e.body,n)},Gr=(e,t)=>{const r=e.font,n=t.withFont(r);return vt(e.body,n)},Ur={"\\Bbb":"\\mathbb","\\bold":"\\mathbf","\\frak":"\\mathfrak","\\bm":"\\boldsymbol"};je({type:"font",names:["\\mathrm","\\mathit","\\mathbf","\\mathnormal","\\mathbb","\\mathcal","\\mathfrak","\\mathscr","\\mathsf","\\mathtt","\\Bbb","\\bold","\\frak"],props:{numArgs:1,allowedInArgument:!0},handler:(e,t)=>{let{parser:r,funcName:n}=e;const o=Ze(t[0]);let s=n;return s in Ur&&(s=Ur[s]),{type:"font",mode:r.mode,font:s.slice(1),body:o}},htmlBuilder:Fr,mathmlBuilder:Gr}),je({type:"mclass",names:["\\boldsymbol","\\bm"],props:{numArgs:1},handler:(e,t)=>{let{parser:r}=e;const n=t[0],o=l.isCharacterBox(n);return{type:"mclass",mode:r.mode,mclass:Ft(n),body:[{type:"font",mode:r.mode,font:"boldsymbol",body:n}],isCharacterBox:o}}}),je({type:"font",names:["\\rm","\\sf","\\tt","\\bf","\\it","\\cal"],props:{numArgs:0,allowedInText:!0},handler:(e,t)=>{let{parser:r,funcName:n,breakOnTokenText:o}=e;const{mode:s}=r,i=r.parseExpression(!0,o);return{type:"font",mode:s,font:"math"+n.slice(1),body:{type:"ordgroup",mode:r.mode,body:i}}},htmlBuilder:Fr,mathmlBuilder:Gr});const Yr=(e,t)=>{let r=t;return"display"===e?r=r.id>=w.SCRIPT.id?r.text():w.DISPLAY:"text"===e&&r.size===w.DISPLAY.size?r=w.TEXT:"script"===e?r=w.SCRIPT:"scriptscript"===e&&(r=w.SCRIPTSCRIPT),r},Xr=(e,t)=>{const r=Yr(e.size,t.style),n=r.fracNum(),o=r.fracDen();let s;s=t.havingStyle(n);const i=ht(e.numer,s,t);if(e.continued){const e=8.5/t.fontMetrics().ptPerEm,r=3.5/t.fontMetrics().ptPerEm;i.height=i.height0?3*c:7*c,u=t.fontMetrics().denom1):(h>0?(m=t.fontMetrics().num2,p=c):(m=t.fontMetrics().num3,p=3*c),u=t.fontMetrics().denom2),l){const e=t.fontMetrics().axisHeight;m-i.depth-(e+.5*h){let r=new gt.MathNode("mfrac",[vt(e.numer,t),vt(e.denom,t)]);if(e.hasBarLine){if(e.barSize){const n=P(e.barSize,t);r.setAttribute("linethickness",F(n))}}else r.setAttribute("linethickness","0px");const n=Yr(e.size,t.style);if(n.size!==t.style.size){r=new gt.MathNode("mstyle",[r]);const e=n.size===w.DISPLAY.size?"true":"false";r.setAttribute("displaystyle",e),r.setAttribute("scriptlevel","0")}if(null!=e.leftDelim||null!=e.rightDelim){const t=[];if(null!=e.leftDelim){const r=new gt.MathNode("mo",[new gt.TextNode(e.leftDelim.replace("\\",""))]);r.setAttribute("fence","true"),t.push(r)}if(t.push(r),null!=e.rightDelim){const r=new gt.MathNode("mo",[new gt.TextNode(e.rightDelim.replace("\\",""))]);r.setAttribute("fence","true"),t.push(r)}return bt(t)}return r};je({type:"genfrac",names:["\\dfrac","\\frac","\\tfrac","\\dbinom","\\binom","\\tbinom","\\\\atopfrac","\\\\bracefrac","\\\\brackfrac"],props:{numArgs:2,allowedInArgument:!0},handler:(e,t)=>{let{parser:r,funcName:n}=e;const o=t[0],s=t[1];let i,a=null,l=null,h="auto";switch(n){case"\\dfrac":case"\\frac":case"\\tfrac":i=!0;break;case"\\\\atopfrac":i=!1;break;case"\\dbinom":case"\\binom":case"\\tbinom":i=!1,a="(",l=")";break;case"\\\\bracefrac":i=!1,a="\\{",l="\\}";break;case"\\\\brackfrac":i=!1,a="[",l="]";break;default:throw new Error("Unrecognized genfrac command")}switch(n){case"\\dfrac":case"\\dbinom":h="display";break;case"\\tfrac":case"\\tbinom":h="text"}return{type:"genfrac",mode:r.mode,continued:!1,numer:o,denom:s,hasBarLine:i,leftDelim:a,rightDelim:l,size:h,barSize:null}},htmlBuilder:Xr,mathmlBuilder:Wr}),je({type:"genfrac",names:["\\cfrac"],props:{numArgs:2},handler:(e,t)=>{let{parser:r,funcName:n}=e;const o=t[0],s=t[1];return{type:"genfrac",mode:r.mode,continued:!0,numer:o,denom:s,hasBarLine:!0,leftDelim:null,rightDelim:null,size:"display",barSize:null}}}),je({type:"infix",names:["\\over","\\choose","\\atop","\\brace","\\brack"],props:{numArgs:0,infix:!0},handler(e){let t,{parser:r,funcName:n,token:o}=e;switch(n){case"\\over":t="\\frac";break;case"\\choose":t="\\binom";break;case"\\atop":t="\\\\atopfrac";break;case"\\brace":t="\\\\bracefrac";break;case"\\brack":t="\\\\brackfrac";break;default:throw new Error("Unrecognized infix genfrac command")}return{type:"infix",mode:r.mode,replaceWith:t,token:o}}});const _r=["display","text","script","scriptscript"],jr=function(e){let t=null;return e.length>0&&(t=e,t="."===t?null:t),t};je({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler(e,t){let{parser:r}=e;const n=t[4],o=t[5],s=Ze(t[0]),i="atom"===s.type&&"open"===s.family?jr(s.text):null,a=Ze(t[1]),l="atom"===a.type&&"close"===a.family?jr(a.text):null,h=qt(t[2],"size");let c,m=null;h.isBlank?c=!0:(m=h.value,c=m.number>0);let p="auto",u=t[3];if("ordgroup"===u.type){if(u.body.length>0){const e=qt(u.body[0],"textord");p=_r[Number(e.text)]}}else u=qt(u,"textord"),p=_r[Number(u.text)];return{type:"genfrac",mode:r.mode,numer:n,denom:o,continued:!1,hasBarLine:c,barSize:m,leftDelim:i,rightDelim:l,size:p}},htmlBuilder:Xr,mathmlBuilder:Wr}),je({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler(e,t){let{parser:r,funcName:n,token:o}=e;return{type:"infix",mode:r.mode,replaceWith:"\\\\abovefrac",size:qt(t[0],"size").value,token:o}}}),je({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:(e,t)=>{let{parser:r,funcName:n}=e;const o=t[0],s=function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e}(qt(t[1],"infix").size),i=t[2],a=s.number>0;return{type:"genfrac",mode:r.mode,numer:o,denom:i,continued:!1,hasBarLine:a,barSize:s,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:Xr,mathmlBuilder:Wr});const $r=(e,t)=>{const r=t.style;let n,o;"supsub"===e.type?(n=e.sup?ht(e.sup,t.havingStyle(r.sup()),t):ht(e.sub,t.havingStyle(r.sub()),t),o=qt(e.base,"horizBrace")):o=qt(e,"horizBrace");const s=ht(o.base,t.havingBaseStyle(w.DISPLAY)),i=Nt(o,t);let a;if(o.isOver?(a=Ve.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:i}]},t),a.children[0].children[0].children[1].classes.push("svg-align")):(a=Ve.makeVList({positionType:"bottom",positionData:s.depth+.1+i.height,children:[{type:"elem",elem:i},{type:"kern",size:.1},{type:"elem",elem:s}]},t),a.children[0].children[0].children[0].classes.push("svg-align")),n){const e=Ve.makeSpan(["mord",o.isOver?"mover":"munder"],[a],t);a=o.isOver?Ve.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:e},{type:"kern",size:.2},{type:"elem",elem:n}]},t):Ve.makeVList({positionType:"bottom",positionData:e.depth+.2+n.height+n.depth,children:[{type:"elem",elem:n},{type:"kern",size:.2},{type:"elem",elem:e}]},t)}return Ve.makeSpan(["mord",o.isOver?"mover":"munder"],[a],t)};je({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler(e,t){let{parser:r,funcName:n}=e;return{type:"horizBrace",mode:r.mode,label:n,isOver:/^\\over/.test(n),base:t[0]}},htmlBuilder:$r,mathmlBuilder:(e,t)=>{const r=Ct(e.label);return new gt.MathNode(e.isOver?"mover":"munder",[vt(e.base,t),r])}}),je({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:(e,t)=>{let{parser:r}=e;const n=t[1],o=qt(t[0],"url").url;return r.settings.isTrusted({command:"\\href",url:o})?{type:"href",mode:r.mode,href:o,body:Ke(n)}:r.formatUnsupportedCmd("\\href")},htmlBuilder:(e,t)=>{const r=nt(e.body,t,!1);return Ve.makeAnchor(e.href,[],r,t)},mathmlBuilder:(e,t)=>{let r=wt(e.body,t);return r instanceof ut||(r=new ut("mrow",[r])),r.setAttribute("href",e.href),r}}),je({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:(e,t)=>{let{parser:r}=e;const n=qt(t[0],"url").url;if(!r.settings.isTrusted({command:"\\url",url:n}))return r.formatUnsupportedCmd("\\url");const o=[];for(let e=0;e{let{parser:r,funcName:o,token:s}=e;const i=qt(t[0],"raw").string,a=t[1];let l;r.settings.strict&&r.settings.reportNonstrict("htmlExtension","HTML extension is disabled on strict mode");const h={};switch(o){case"\\htmlClass":h.class=i,l={command:"\\htmlClass",class:i};break;case"\\htmlId":h.id=i,l={command:"\\htmlId",id:i};break;case"\\htmlStyle":h.style=i,l={command:"\\htmlStyle",style:i};break;case"\\htmlData":{const e=i.split(",");for(let t=0;t{const r=nt(e.body,t,!1),n=["enclosing"];e.attributes.class&&n.push(...e.attributes.class.trim().split(/\s+/));const o=Ve.makeSpan(n,r,t);for(const t in e.attributes)"class"!==t&&e.attributes.hasOwnProperty(t)&&o.setAttribute(t,e.attributes[t]);return o},mathmlBuilder:(e,t)=>wt(e.body,t)}),je({type:"htmlmathml",names:["\\html@mathml"],props:{numArgs:2,allowedInText:!0},handler:(e,t)=>{let{parser:r}=e;return{type:"htmlmathml",mode:r.mode,html:Ke(t[0]),mathml:Ke(t[1])}},htmlBuilder:(e,t)=>{const r=nt(e.html,t,!1);return Ve.makeFragment(r)},mathmlBuilder:(e,t)=>wt(e.mathml,t)});const Zr=function(e){if(/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(e))return{number:+e,unit:"bp"};{const t=/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(e);if(!t)throw new n("Invalid size: '"+e+"' in \\includegraphics");const r={number:+(t[1]+t[2]),unit:t[3]};if(!V(r))throw new n("Invalid unit: '"+r.unit+"' in \\includegraphics.");return r}};je({type:"includegraphics",names:["\\includegraphics"],props:{numArgs:1,numOptionalArgs:1,argTypes:["raw","url"],allowedInText:!1},handler:(e,t,r)=>{let{parser:o}=e,s={number:0,unit:"em"},i={number:.9,unit:"em"},a={number:0,unit:"em"},l="";if(r[0]){const e=qt(r[0],"raw").string.split(",");for(let t=0;t{const r=P(e.height,t);let n=0;e.totalheight.number>0&&(n=P(e.totalheight,t)-r);let o=0;e.width.number>0&&(o=P(e.width,t));const s={height:F(r+n)};o>0&&(s.width=F(o)),n>0&&(s.verticalAlign=F(-n));const i=new j(e.src,e.alt,s);return i.height=r,i.depth=n,i},mathmlBuilder:(e,t)=>{const r=new gt.MathNode("mglyph",[]);r.setAttribute("alt",e.alt);const n=P(e.height,t);let o=0;if(e.totalheight.number>0&&(o=P(e.totalheight,t)-n,r.setAttribute("valign",F(-o))),r.setAttribute("height",F(n+o)),e.width.number>0){const n=P(e.width,t);r.setAttribute("width",F(n))}return r.setAttribute("src",e.src),r}}),je({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler(e,t){let{parser:r,funcName:n}=e;const o=qt(t[0],"size");if(r.settings.strict){const e="m"===n[1],t="mu"===o.value.unit;e?(t||r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" supports only mu units, not "+o.value.unit+" units"),"math"!==r.mode&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" works only in math mode")):t&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" doesn't support mu units")}return{type:"kern",mode:r.mode,dimension:o.value}},htmlBuilder(e,t){return Ve.makeGlue(e.dimension,t)},mathmlBuilder(e,t){const r=P(e.dimension,t);return new gt.SpaceNode(r)}}),je({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:(e,t)=>{let{parser:r,funcName:n}=e;const o=t[0];return{type:"lap",mode:r.mode,alignment:n.slice(5),body:o}},htmlBuilder:(e,t)=>{let r;"clap"===e.alignment?(r=Ve.makeSpan([],[ht(e.body,t)]),r=Ve.makeSpan(["inner"],[r],t)):r=Ve.makeSpan(["inner"],[ht(e.body,t)]);const n=Ve.makeSpan(["fix"],[]);let o=Ve.makeSpan([e.alignment],[r,n],t);const s=Ve.makeSpan(["strut"]);return s.style.height=F(o.height+o.depth),o.depth&&(s.style.verticalAlign=F(-o.depth)),o.children.unshift(s),o=Ve.makeSpan(["thinbox"],[o],t),Ve.makeSpan(["mord","vbox"],[o],t)},mathmlBuilder:(e,t)=>{const r=new gt.MathNode("mpadded",[vt(e.body,t)]);if("rlap"!==e.alignment){const t="llap"===e.alignment?"-1":"-0.5";r.setAttribute("lspace",t+"width")}return r.setAttribute("width","0px"),r}}),je({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(e,t){let{funcName:r,parser:n}=e;const o=n.mode;n.switchMode("math");const s="\\("===r?"\\)":"$",i=n.parseExpression(!1,s);return n.expect(s),n.switchMode(o),{type:"styling",mode:n.mode,style:"text",body:i}}}),je({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(e,t){throw new n("Mismatched "+e.funcName)}});const Kr=(e,t)=>{switch(t.style.size){case w.DISPLAY.size:return e.display;case w.TEXT.size:return e.text;case w.SCRIPT.size:return e.script;case w.SCRIPTSCRIPT.size:return e.scriptscript;default:return e.text}};je({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4,primitive:!0},handler:(e,t)=>{let{parser:r}=e;return{type:"mathchoice",mode:r.mode,display:Ke(t[0]),text:Ke(t[1]),script:Ke(t[2]),scriptscript:Ke(t[3])}},htmlBuilder:(e,t)=>{const r=Kr(e,t),n=nt(r,t,!1);return Ve.makeFragment(n)},mathmlBuilder:(e,t)=>{const r=Kr(e,t);return wt(r,t)}});const Jr=(e,t,r,n,o,s,i)=>{e=Ve.makeSpan([],[e]);const a=r&&l.isCharacterBox(r);let h,c,m;if(t){const e=ht(t,n.havingStyle(o.sup()),n);c={elem:e,kern:Math.max(n.fontMetrics().bigOpSpacing1,n.fontMetrics().bigOpSpacing3-e.depth)}}if(r){const e=ht(r,n.havingStyle(o.sub()),n);h={elem:e,kern:Math.max(n.fontMetrics().bigOpSpacing2,n.fontMetrics().bigOpSpacing4-e.height)}}if(c&&h){const t=n.fontMetrics().bigOpSpacing5+h.elem.height+h.elem.depth+h.kern+e.depth+i;m=Ve.makeVList({positionType:"bottom",positionData:t,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:h.elem,marginLeft:F(-s)},{type:"kern",size:h.kern},{type:"elem",elem:e},{type:"kern",size:c.kern},{type:"elem",elem:c.elem,marginLeft:F(s)},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}else if(h){const t=e.height-i;m=Ve.makeVList({positionType:"top",positionData:t,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:h.elem,marginLeft:F(-s)},{type:"kern",size:h.kern},{type:"elem",elem:e}]},n)}else{if(!c)return e;{const t=e.depth+i;m=Ve.makeVList({positionType:"bottom",positionData:t,children:[{type:"elem",elem:e},{type:"kern",size:c.kern},{type:"elem",elem:c.elem,marginLeft:F(s)},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}}const p=[m];if(h&&0!==s&&!a){const e=Ve.makeSpan(["mspace"],[],n);e.style.marginRight=F(s),p.unshift(e)}return Ve.makeSpan(["mop","op-limits"],p,n)},Qr=["\\smallint"],en=(e,t)=>{let r,n,o,s=!1;"supsub"===e.type?(r=e.sup,n=e.sub,o=qt(e.base,"op"),s=!0):o=qt(e,"op");const i=t.style;let a,h=!1;if(i.size===w.DISPLAY.size&&o.symbol&&!l.contains(Qr,o.name)&&(h=!0),o.symbol){const e=h?"Size2-Regular":"Size1-Regular";let r="";if("\\oiint"!==o.name&&"\\oiiint"!==o.name||(r=o.name.slice(1),o.name="oiint"===r?"\\iint":"\\iiint"),a=Ve.makeSymbol(o.name,e,"math",t,["mop","op-symbol",h?"large-op":"small-op"]),r.length>0){const e=a.italic,n=Ve.staticSvg(r+"Size"+(h?"2":"1"),t);a=Ve.makeVList({positionType:"individualShift",children:[{type:"elem",elem:a,shift:0},{type:"elem",elem:n,shift:h?.08:0}]},t),o.name="\\"+r,a.classes.unshift("mop"),a.italic=e}}else if(o.body){const e=nt(o.body,t,!0);1===e.length&&e[0]instanceof Z?(a=e[0],a.classes[0]="mop"):a=Ve.makeSpan(["mop"],e,t)}else{const e=[];for(let r=1;r{let r;if(e.symbol)r=new ut("mo",[ft(e.name,e.mode)]),l.contains(Qr,e.name)&&r.setAttribute("largeop","false");else if(e.body)r=new ut("mo",xt(e.body,t));else{r=new ut("mi",[new dt(e.name.slice(1))]);const t=new ut("mo",[ft("\u2061","text")]);r=e.parentIsSupSub?new ut("mrow",[r,t]):pt([r,t])}return r},rn={"\u220f":"\\prod","\u2210":"\\coprod","\u2211":"\\sum","\u22c0":"\\bigwedge","\u22c1":"\\bigvee","\u22c2":"\\bigcap","\u22c3":"\\bigcup","\u2a00":"\\bigodot","\u2a01":"\\bigoplus","\u2a02":"\\bigotimes","\u2a04":"\\biguplus","\u2a06":"\\bigsqcup"};je({type:"op",names:["\\coprod","\\bigvee","\\bigwedge","\\biguplus","\\bigcap","\\bigcup","\\intop","\\prod","\\sum","\\bigotimes","\\bigoplus","\\bigodot","\\bigsqcup","\\smallint","\u220f","\u2210","\u2211","\u22c0","\u22c1","\u22c2","\u22c3","\u2a00","\u2a01","\u2a02","\u2a04","\u2a06"],props:{numArgs:0},handler:(e,t)=>{let{parser:r,funcName:n}=e,o=n;return 1===o.length&&(o=rn[o]),{type:"op",mode:r.mode,limits:!0,parentIsSupSub:!1,symbol:!0,name:o}},htmlBuilder:en,mathmlBuilder:tn}),je({type:"op",names:["\\mathop"],props:{numArgs:1,primitive:!0},handler:(e,t)=>{let{parser:r}=e;const n=t[0];return{type:"op",mode:r.mode,limits:!1,parentIsSupSub:!1,symbol:!1,body:Ke(n)}},htmlBuilder:en,mathmlBuilder:tn});const nn={"\u222b":"\\int","\u222c":"\\iint","\u222d":"\\iiint","\u222e":"\\oint","\u222f":"\\oiint","\u2230":"\\oiiint"};je({type:"op",names:["\\arcsin","\\arccos","\\arctan","\\arctg","\\arcctg","\\arg","\\ch","\\cos","\\cosec","\\cosh","\\cot","\\cotg","\\coth","\\csc","\\ctg","\\cth","\\deg","\\dim","\\exp","\\hom","\\ker","\\lg","\\ln","\\log","\\sec","\\sin","\\sinh","\\sh","\\tan","\\tanh","\\tg","\\th"],props:{numArgs:0},handler(e){let{parser:t,funcName:r}=e;return{type:"op",mode:t.mode,limits:!1,parentIsSupSub:!1,symbol:!1,name:r}},htmlBuilder:en,mathmlBuilder:tn}),je({type:"op",names:["\\det","\\gcd","\\inf","\\lim","\\max","\\min","\\Pr","\\sup"],props:{numArgs:0},handler(e){let{parser:t,funcName:r}=e;return{type:"op",mode:t.mode,limits:!0,parentIsSupSub:!1,symbol:!1,name:r}},htmlBuilder:en,mathmlBuilder:tn}),je({type:"op",names:["\\int","\\iint","\\iiint","\\oint","\\oiint","\\oiiint","\u222b","\u222c","\u222d","\u222e","\u222f","\u2230"],props:{numArgs:0},handler(e){let{parser:t,funcName:r}=e,n=r;return 1===n.length&&(n=nn[n]),{type:"op",mode:t.mode,limits:!1,parentIsSupSub:!1,symbol:!0,name:n}},htmlBuilder:en,mathmlBuilder:tn});const on=(e,t)=>{let r,n,o,s,i=!1;if("supsub"===e.type?(r=e.sup,n=e.sub,o=qt(e.base,"operatorname"),i=!0):o=qt(e,"operatorname"),o.body.length>0){const e=o.body.map((e=>{const t=e.text;return"string"==typeof t?{type:"textord",mode:e.mode,text:t}:e})),r=nt(e,t.withFont("mathrm"),!0);for(let e=0;e{let{parser:r,funcName:n}=e;const o=t[0];return{type:"operatorname",mode:r.mode,body:Ke(o),alwaysHandleSupSub:"\\operatornamewithlimits"===n,limits:!1,parentIsSupSub:!1}},htmlBuilder:on,mathmlBuilder:(e,t)=>{let r=xt(e.body,t.withFont("mathrm")),n=!0;for(let e=0;ee.toText())).join("");r=[new gt.TextNode(e)]}const o=new gt.MathNode("mi",r);o.setAttribute("mathvariant","normal");const s=new gt.MathNode("mo",[ft("\u2061","text")]);return e.parentIsSupSub?new gt.MathNode("mrow",[o,s]):gt.newDocumentFragment([o,s])}}),Br("\\operatorname","\\@ifstar\\operatornamewithlimits\\operatorname@"),$e({type:"ordgroup",htmlBuilder(e,t){return e.semisimple?Ve.makeFragment(nt(e.body,t,!1)):Ve.makeSpan(["mord"],nt(e.body,t,!0),t)},mathmlBuilder(e,t){return wt(e.body,t,!0)}}),je({type:"overline",names:["\\overline"],props:{numArgs:1},handler(e,t){let{parser:r}=e;const n=t[0];return{type:"overline",mode:r.mode,body:n}},htmlBuilder(e,t){const r=ht(e.body,t.havingCrampedStyle()),n=Ve.makeLineSpan("overline-line",t),o=t.fontMetrics().defaultRuleThickness,s=Ve.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:r},{type:"kern",size:3*o},{type:"elem",elem:n},{type:"kern",size:o}]},t);return Ve.makeSpan(["mord","overline"],[s],t)},mathmlBuilder(e,t){const r=new gt.MathNode("mo",[new gt.TextNode("\u203e")]);r.setAttribute("stretchy","true");const n=new gt.MathNode("mover",[vt(e.body,t),r]);return n.setAttribute("accent","true"),n}}),je({type:"phantom",names:["\\phantom"],props:{numArgs:1,allowedInText:!0},handler:(e,t)=>{let{parser:r}=e;const n=t[0];return{type:"phantom",mode:r.mode,body:Ke(n)}},htmlBuilder:(e,t)=>{const r=nt(e.body,t.withPhantom(),!1);return Ve.makeFragment(r)},mathmlBuilder:(e,t)=>{const r=xt(e.body,t);return new gt.MathNode("mphantom",r)}}),je({type:"hphantom",names:["\\hphantom"],props:{numArgs:1,allowedInText:!0},handler:(e,t)=>{let{parser:r}=e;const n=t[0];return{type:"hphantom",mode:r.mode,body:n}},htmlBuilder:(e,t)=>{let r=Ve.makeSpan([],[ht(e.body,t.withPhantom())]);if(r.height=0,r.depth=0,r.children)for(let e=0;e{const r=xt(Ke(e.body),t),n=new gt.MathNode("mphantom",r),o=new gt.MathNode("mpadded",[n]);return o.setAttribute("height","0px"),o.setAttribute("depth","0px"),o}}),je({type:"vphantom",names:["\\vphantom"],props:{numArgs:1,allowedInText:!0},handler:(e,t)=>{let{parser:r}=e;const n=t[0];return{type:"vphantom",mode:r.mode,body:n}},htmlBuilder:(e,t)=>{const r=Ve.makeSpan(["inner"],[ht(e.body,t.withPhantom())]),n=Ve.makeSpan(["fix"],[]);return Ve.makeSpan(["mord","rlap"],[r,n],t)},mathmlBuilder:(e,t)=>{const r=xt(Ke(e.body),t),n=new gt.MathNode("mphantom",r),o=new gt.MathNode("mpadded",[n]);return o.setAttribute("width","0px"),o}}),je({type:"raisebox",names:["\\raisebox"],props:{numArgs:2,argTypes:["size","hbox"],allowedInText:!0},handler(e,t){let{parser:r}=e;const n=qt(t[0],"size").value,o=t[1];return{type:"raisebox",mode:r.mode,dy:n,body:o}},htmlBuilder(e,t){const r=ht(e.body,t),n=P(e.dy,t);return Ve.makeVList({positionType:"shift",positionData:-n,children:[{type:"elem",elem:r}]},t)},mathmlBuilder(e,t){const r=new gt.MathNode("mpadded",[vt(e.body,t)]),n=e.dy.number+e.dy.unit;return r.setAttribute("voffset",n),r}}),je({type:"internal",names:["\\relax"],props:{numArgs:0,allowedInText:!0},handler(e){let{parser:t}=e;return{type:"internal",mode:t.mode}}}),je({type:"rule",names:["\\rule"],props:{numArgs:2,numOptionalArgs:1,argTypes:["size","size","size"]},handler(e,t,r){let{parser:n}=e;const o=r[0],s=qt(t[0],"size"),i=qt(t[1],"size");return{type:"rule",mode:n.mode,shift:o&&qt(o,"size").value,width:s.value,height:i.value}},htmlBuilder(e,t){const r=Ve.makeSpan(["mord","rule"],[],t),n=P(e.width,t),o=P(e.height,t),s=e.shift?P(e.shift,t):0;return r.style.borderRightWidth=F(n),r.style.borderTopWidth=F(o),r.style.bottom=F(s),r.width=n,r.height=o+s,r.depth=-s,r.maxFontSize=1.125*o*t.sizeMultiplier,r},mathmlBuilder(e,t){const r=P(e.width,t),n=P(e.height,t),o=e.shift?P(e.shift,t):0,s=t.color&&t.getColor()||"black",i=new gt.MathNode("mspace");i.setAttribute("mathbackground",s),i.setAttribute("width",F(r)),i.setAttribute("height",F(n));const a=new gt.MathNode("mpadded",[i]);return o>=0?a.setAttribute("height",F(o)):(a.setAttribute("height",F(o)),a.setAttribute("depth",F(-o))),a.setAttribute("voffset",F(o)),a}});const an=["\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge"];je({type:"sizing",names:an,props:{numArgs:0,allowedInText:!0},handler:(e,t)=>{let{breakOnTokenText:r,funcName:n,parser:o}=e;const s=o.parseExpression(!1,r);return{type:"sizing",mode:o.mode,size:an.indexOf(n)+1,body:s}},htmlBuilder:(e,t)=>{const r=t.havingSize(e.size);return sn(e.body,r,t)},mathmlBuilder:(e,t)=>{const r=t.havingSize(e.size),n=xt(e.body,r),o=new gt.MathNode("mstyle",n);return o.setAttribute("mathsize",F(r.sizeMultiplier)),o}}),je({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:(e,t,r)=>{let{parser:n}=e,o=!1,s=!1;const i=r[0]&&qt(r[0],"ordgroup");if(i){let e="";for(let t=0;t{const r=Ve.makeSpan([],[ht(e.body,t)]);if(!e.smashHeight&&!e.smashDepth)return r;if(e.smashHeight&&(r.height=0,r.children))for(let e=0;e{const r=new gt.MathNode("mpadded",[vt(e.body,t)]);return e.smashHeight&&r.setAttribute("height","0px"),e.smashDepth&&r.setAttribute("depth","0px"),r}}),je({type:"sqrt",names:["\\sqrt"],props:{numArgs:1,numOptionalArgs:1},handler(e,t,r){let{parser:n}=e;const o=r[0],s=t[0];return{type:"sqrt",mode:n.mode,body:s,index:o}},htmlBuilder(e,t){let r=ht(e.body,t.havingCrampedStyle());0===r.height&&(r.height=t.fontMetrics().xHeight),r=Ve.wrapFragment(r,t);const n=t.fontMetrics().defaultRuleThickness;let o=n;t.style.idr.height+r.depth+s&&(s=(s+c-r.height-r.depth)/2);const m=a.height-r.height-s-l;r.style.paddingLeft=F(h);const p=Ve.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:r,wrapperClasses:["svg-align"]},{type:"kern",size:-(r.height+m)},{type:"elem",elem:a},{type:"kern",size:l}]},t);if(e.index){const r=t.havingStyle(w.SCRIPTSCRIPT),n=ht(e.index,r,t),o=.6*(p.height-p.depth),s=Ve.makeVList({positionType:"shift",positionData:-o,children:[{type:"elem",elem:n}]},t),i=Ve.makeSpan(["root"],[s]);return Ve.makeSpan(["mord","sqrt"],[i,p],t)}return Ve.makeSpan(["mord","sqrt"],[p],t)},mathmlBuilder(e,t){const{body:r,index:n}=e;return n?new gt.MathNode("mroot",[vt(r,t),vt(n,t)]):new gt.MathNode("msqrt",[vt(r,t)])}});const ln={display:w.DISPLAY,text:w.TEXT,script:w.SCRIPT,scriptscript:w.SCRIPTSCRIPT};je({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(e,t){let{breakOnTokenText:r,funcName:n,parser:o}=e;const s=o.parseExpression(!0,r),i=n.slice(1,n.length-5);return{type:"styling",mode:o.mode,style:i,body:s}},htmlBuilder(e,t){const r=ln[e.style],n=t.havingStyle(r).withFont("");return sn(e.body,n,t)},mathmlBuilder(e,t){const r=ln[e.style],n=t.havingStyle(r),o=xt(e.body,n),s=new gt.MathNode("mstyle",o),i={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]}[e.style];return s.setAttribute("scriptlevel",i[0]),s.setAttribute("displaystyle",i[1]),s}});$e({type:"supsub",htmlBuilder(e,t){const r=function(e,t){const r=e.base;if(r)return"op"===r.type?r.limits&&(t.style.size===w.DISPLAY.size||r.alwaysHandleSupSub)?en:null:"operatorname"===r.type?r.alwaysHandleSupSub&&(t.style.size===w.DISPLAY.size||r.limits)?on:null:"accent"===r.type?l.isCharacterBox(r.base)?Ht:null:"horizBrace"===r.type&&!e.sub===r.isOver?$r:null;return null}(e,t);if(r)return r(e,t);const{base:n,sup:o,sub:s}=e,i=ht(n,t);let a,h;const c=t.fontMetrics();let m=0,p=0;const u=n&&l.isCharacterBox(n);if(o){const e=t.havingStyle(t.style.sup());a=ht(o,e,t),u||(m=i.height-e.fontMetrics().supDrop*e.sizeMultiplier/t.sizeMultiplier)}if(s){const e=t.havingStyle(t.style.sub());h=ht(s,e,t),u||(p=i.depth+e.fontMetrics().subDrop*e.sizeMultiplier/t.sizeMultiplier)}let d;d=t.style===w.DISPLAY?c.sup1:t.style.cramped?c.sup3:c.sup2;const g=t.sizeMultiplier,f=F(.5/c.ptPerEm/g);let b,y=null;if(h){const t=e.base&&"op"===e.base.type&&e.base.name&&("\\oiint"===e.base.name||"\\oiiint"===e.base.name);(i instanceof Z||t)&&(y=F(-i.italic))}if(a&&h){m=Math.max(m,d,a.depth+.25*c.xHeight),p=Math.max(p,c.sub2);const e=4*c.defaultRuleThickness;if(m-a.depth-(h.height-p)0&&(m+=t,p-=t)}const r=[{type:"elem",elem:h,shift:p,marginRight:f,marginLeft:y},{type:"elem",elem:a,shift:-m,marginRight:f}];b=Ve.makeVList({positionType:"individualShift",children:r},t)}else if(h){p=Math.max(p,c.sub1,h.height-.8*c.xHeight);const e=[{type:"elem",elem:h,marginLeft:y,marginRight:f}];b=Ve.makeVList({positionType:"shift",positionData:p,children:e},t)}else{if(!a)throw new Error("supsub must have either sup or sub.");m=Math.max(m,d,a.depth+.25*c.xHeight),b=Ve.makeVList({positionType:"shift",positionData:-m,children:[{type:"elem",elem:a,marginRight:f}]},t)}const x=at(i,"right")||"mord";return Ve.makeSpan([x],[i,Ve.makeSpan(["msupsub"],[b])],t)},mathmlBuilder(e,t){let r,n,o=!1;e.base&&"horizBrace"===e.base.type&&(n=!!e.sup,n===e.base.isOver&&(o=!0,r=e.base.isOver)),!e.base||"op"!==e.base.type&&"operatorname"!==e.base.type||(e.base.parentIsSupSub=!0);const s=[vt(e.base,t)];let i;if(e.sub&&s.push(vt(e.sub,t)),e.sup&&s.push(vt(e.sup,t)),o)i=r?"mover":"munder";else if(e.sub)if(e.sup){const r=e.base;i=r&&"op"===r.type&&r.limits&&t.style===w.DISPLAY||r&&"operatorname"===r.type&&r.alwaysHandleSupSub&&(t.style===w.DISPLAY||r.limits)?"munderover":"msubsup"}else{const r=e.base;i=r&&"op"===r.type&&r.limits&&(t.style===w.DISPLAY||r.alwaysHandleSupSub)||r&&"operatorname"===r.type&&r.alwaysHandleSupSub&&(r.limits||t.style===w.DISPLAY)?"munder":"msub"}else{const r=e.base;i=r&&"op"===r.type&&r.limits&&(t.style===w.DISPLAY||r.alwaysHandleSupSub)||r&&"operatorname"===r.type&&r.alwaysHandleSupSub&&(r.limits||t.style===w.DISPLAY)?"mover":"msup"}return new gt.MathNode(i,s)}}),$e({type:"atom",htmlBuilder(e,t){return Ve.mathsym(e.text,e.mode,t,["m"+e.family])},mathmlBuilder(e,t){const r=new gt.MathNode("mo",[ft(e.text,e.mode)]);if("bin"===e.family){const n=yt(e,t);"bold-italic"===n&&r.setAttribute("mathvariant",n)}else"punct"===e.family?r.setAttribute("separator","true"):"open"!==e.family&&"close"!==e.family||r.setAttribute("stretchy","false");return r}});const hn={mi:"italic",mn:"normal",mtext:"normal"};$e({type:"mathord",htmlBuilder(e,t){return Ve.makeOrd(e,t,"mathord")},mathmlBuilder(e,t){const r=new gt.MathNode("mi",[ft(e.text,e.mode,t)]),n=yt(e,t)||"italic";return n!==hn[r.type]&&r.setAttribute("mathvariant",n),r}}),$e({type:"textord",htmlBuilder(e,t){return Ve.makeOrd(e,t,"textord")},mathmlBuilder(e,t){const r=ft(e.text,e.mode,t),n=yt(e,t)||"normal";let o;return o="text"===e.mode?new gt.MathNode("mtext",[r]):/[0-9]/.test(e.text)?new gt.MathNode("mn",[r]):"\\prime"===e.text?new gt.MathNode("mo",[r]):new gt.MathNode("mi",[r]),n!==hn[o.type]&&o.setAttribute("mathvariant",n),o}});const cn={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},mn={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};$e({type:"spacing",htmlBuilder(e,t){if(mn.hasOwnProperty(e.text)){const r=mn[e.text].className||"";if("text"===e.mode){const n=Ve.makeOrd(e,t,"textord");return n.classes.push(r),n}return Ve.makeSpan(["mspace",r],[Ve.mathsym(e.text,e.mode,t)],t)}if(cn.hasOwnProperty(e.text))return Ve.makeSpan(["mspace",cn[e.text]],[],t);throw new n('Unknown type of space "'+e.text+'"')},mathmlBuilder(e,t){let r;if(!mn.hasOwnProperty(e.text)){if(cn.hasOwnProperty(e.text))return new gt.MathNode("mspace");throw new n('Unknown type of space "'+e.text+'"')}return r=new gt.MathNode("mtext",[new gt.TextNode("\xa0")]),r}});const pn=()=>{const e=new gt.MathNode("mtd",[]);return e.setAttribute("width","50%"),e};$e({type:"tag",mathmlBuilder(e,t){const r=new gt.MathNode("mtable",[new gt.MathNode("mtr",[pn(),new gt.MathNode("mtd",[wt(e.body,t)]),pn(),new gt.MathNode("mtd",[wt(e.tag,t)])])]);return r.setAttribute("width","100%"),r}});const un={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},dn={"\\textbf":"textbf","\\textmd":"textmd"},gn={"\\textit":"textit","\\textup":"textup"},fn=(e,t)=>{const r=e.font;return r?un[r]?t.withTextFontFamily(un[r]):dn[r]?t.withTextFontWeight(dn[r]):t.withTextFontShape(gn[r]):t};je({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler(e,t){let{parser:r,funcName:n}=e;const o=t[0];return{type:"text",mode:r.mode,body:Ke(o),font:n}},htmlBuilder(e,t){const r=fn(e,t),n=nt(e.body,r,!0);return Ve.makeSpan(["mord","text"],n,r)},mathmlBuilder(e,t){const r=fn(e,t);return wt(e.body,r)}}),je({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler(e,t){let{parser:r}=e;return{type:"underline",mode:r.mode,body:t[0]}},htmlBuilder(e,t){const r=ht(e.body,t),n=Ve.makeLineSpan("underline-line",t),o=t.fontMetrics().defaultRuleThickness,s=Ve.makeVList({positionType:"top",positionData:r.height,children:[{type:"kern",size:o},{type:"elem",elem:n},{type:"kern",size:3*o},{type:"elem",elem:r}]},t);return Ve.makeSpan(["mord","underline"],[s],t)},mathmlBuilder(e,t){const r=new gt.MathNode("mo",[new gt.TextNode("\u203e")]);r.setAttribute("stretchy","true");const n=new gt.MathNode("munder",[vt(e.body,t),r]);return n.setAttribute("accentunder","true"),n}}),je({type:"vcenter",names:["\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler(e,t){let{parser:r}=e;return{type:"vcenter",mode:r.mode,body:t[0]}},htmlBuilder(e,t){const r=ht(e.body,t),n=t.fontMetrics().axisHeight,o=.5*(r.height-n-(r.depth+n));return Ve.makeVList({positionType:"shift",positionData:o,children:[{type:"elem",elem:r}]},t)},mathmlBuilder(e,t){return new gt.MathNode("mpadded",[vt(e.body,t)],["vcenter"])}}),je({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler(e,t,r){throw new n("\\verb ended by end of line instead of matching delimiter")},htmlBuilder(e,t){const r=bn(e),n=[],o=t.havingStyle(t.style.text());for(let t=0;te.body.replace(/ /g,e.star?"\u2423":"\xa0");var yn=Xe;const xn="[ \r\n\t]",wn="(\\\\[a-zA-Z@]+)"+xn+"*",vn="[\u0300-\u036f]",kn=new RegExp(vn+"+$"),Sn="("+xn+"+)|\\\\(\n|[ \r\t]+\n?)[ \r\t]*|([!-\\[\\]-\u2027\u202a-\ud7ff\uf900-\uffff]"+vn+"*|[\ud800-\udbff][\udc00-\udfff]"+vn+"*|\\\\verb\\*([^]).*?\\4|\\\\verb([^*a-zA-Z]).*?\\5|"+wn+"|\\\\[^\ud800-\udfff])";class Mn{constructor(e,t){this.input=void 0,this.settings=void 0,this.tokenRegex=void 0,this.catcodes=void 0,this.input=e,this.settings=t,this.tokenRegex=new RegExp(Sn,"g"),this.catcodes={"%":14,"~":13}}setCatcode(e,t){this.catcodes[e]=t}lex(){const e=this.input,t=this.tokenRegex.lastIndex;if(t===e.length)return new Nr("EOF",new Cr(this,t,t));const r=this.tokenRegex.exec(e);if(null===r||r.index!==t)throw new n("Unexpected character: '"+e[t]+"'",new Nr(e[t],new Cr(this,t,t+1)));const o=r[6]||r[3]||(r[2]?"\\ ":" ");if(14===this.catcodes[o]){const t=e.indexOf("\n",this.tokenRegex.lastIndex);return-1===t?(this.tokenRegex.lastIndex=e.length,this.settings.reportNonstrict("commentAtEnd","% comment has no terminating newline; LaTeX would fail because of commenting the end of math mode (e.g. $)")):this.tokenRegex.lastIndex=t+1,this.lex()}return new Nr(o,new Cr(this,t,this.tokenRegex.lastIndex))}}class zn{constructor(e,t){void 0===e&&(e={}),void 0===t&&(t={}),this.current=void 0,this.builtins=void 0,this.undefStack=void 0,this.current=t,this.builtins=e,this.undefStack=[]}beginGroup(){this.undefStack.push({})}endGroup(){if(0===this.undefStack.length)throw new n("Unbalanced namespace destruction: attempt to pop global namespace; please report this as a bug");const e=this.undefStack.pop();for(const t in e)e.hasOwnProperty(t)&&(null==e[t]?delete this.current[t]:this.current[t]=e[t])}endGroups(){for(;this.undefStack.length>0;)this.endGroup()}has(e){return this.current.hasOwnProperty(e)||this.builtins.hasOwnProperty(e)}get(e){return this.current.hasOwnProperty(e)?this.current[e]:this.builtins[e]}set(e,t,r){if(void 0===r&&(r=!1),r){for(let t=0;t0&&(this.undefStack[this.undefStack.length-1][e]=t)}else{const t=this.undefStack[this.undefStack.length-1];t&&!t.hasOwnProperty(e)&&(t[e]=this.current[e])}null==t?delete this.current[e]:this.current[e]=t}}var An=Tr;Br("\\noexpand",(function(e){const t=e.popToken();return e.isExpandable(t.text)&&(t.noexpand=!0,t.treatAsRelax=!0),{tokens:[t],numArgs:0}})),Br("\\expandafter",(function(e){const t=e.popToken();return e.expandOnce(!0),{tokens:[t],numArgs:0}})),Br("\\@firstoftwo",(function(e){return{tokens:e.consumeArgs(2)[0],numArgs:0}})),Br("\\@secondoftwo",(function(e){return{tokens:e.consumeArgs(2)[1],numArgs:0}})),Br("\\@ifnextchar",(function(e){const t=e.consumeArgs(3);e.consumeSpaces();const r=e.future();return 1===t[0].length&&t[0][0].text===r.text?{tokens:t[1],numArgs:0}:{tokens:t[2],numArgs:0}})),Br("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}"),Br("\\TextOrMath",(function(e){const t=e.consumeArgs(2);return"text"===e.mode?{tokens:t[0],numArgs:0}:{tokens:t[1],numArgs:0}}));const Tn={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};Br("\\char",(function(e){let t,r=e.popToken(),o="";if("'"===r.text)t=8,r=e.popToken();else if('"'===r.text)t=16,r=e.popToken();else if("`"===r.text)if(r=e.popToken(),"\\"===r.text[0])o=r.text.charCodeAt(1);else{if("EOF"===r.text)throw new n("\\char` missing argument");o=r.text.charCodeAt(0)}else t=10;if(t){if(o=Tn[r.text],null==o||o>=t)throw new n("Invalid base-"+t+" digit "+r.text);let s;for(;null!=(s=Tn[e.future().text])&&s{let o=e.consumeArg().tokens;if(1!==o.length)throw new n("\\newcommand's first argument must be a macro name");const s=o[0].text,i=e.isDefined(s);if(i&&!t)throw new n("\\newcommand{"+s+"} attempting to redefine "+s+"; use \\renewcommand");if(!i&&!r)throw new n("\\renewcommand{"+s+"} when command "+s+" does not yet exist; use \\newcommand");let a=0;if(o=e.consumeArg().tokens,1===o.length&&"["===o[0].text){let t="",r=e.expandNextToken();for(;"]"!==r.text&&"EOF"!==r.text;)t+=r.text,r=e.expandNextToken();if(!t.match(/^\s*[0-9]+\s*$/))throw new n("Invalid number of arguments: "+t);a=parseInt(t),o=e.consumeArg().tokens}return e.macros.set(s,{tokens:o,numArgs:a}),""};Br("\\newcommand",(e=>Bn(e,!1,!0))),Br("\\renewcommand",(e=>Bn(e,!0,!1))),Br("\\providecommand",(e=>Bn(e,!0,!0))),Br("\\message",(e=>{const t=e.consumeArgs(1)[0];return console.log(t.reverse().map((e=>e.text)).join("")),""})),Br("\\errmessage",(e=>{const t=e.consumeArgs(1)[0];return console.error(t.reverse().map((e=>e.text)).join("")),""})),Br("\\show",(e=>{const t=e.popToken(),r=t.text;return console.log(t,e.macros.get(r),yn[r],oe.math[r],oe.text[r]),""})),Br("\\bgroup","{"),Br("\\egroup","}"),Br("~","\\nobreakspace"),Br("\\lq","`"),Br("\\rq","'"),Br("\\aa","\\r a"),Br("\\AA","\\r A"),Br("\\textcopyright","\\html@mathml{\\textcircled{c}}{\\char`\xa9}"),Br("\\copyright","\\TextOrMath{\\textcopyright}{\\text{\\textcopyright}}"),Br("\\textregistered","\\html@mathml{\\textcircled{\\scriptsize R}}{\\char`\xae}"),Br("\u212c","\\mathscr{B}"),Br("\u2130","\\mathscr{E}"),Br("\u2131","\\mathscr{F}"),Br("\u210b","\\mathscr{H}"),Br("\u2110","\\mathscr{I}"),Br("\u2112","\\mathscr{L}"),Br("\u2133","\\mathscr{M}"),Br("\u211b","\\mathscr{R}"),Br("\u212d","\\mathfrak{C}"),Br("\u210c","\\mathfrak{H}"),Br("\u2128","\\mathfrak{Z}"),Br("\\Bbbk","\\Bbb{k}"),Br("\xb7","\\cdotp"),Br("\\llap","\\mathllap{\\textrm{#1}}"),Br("\\rlap","\\mathrlap{\\textrm{#1}}"),Br("\\clap","\\mathclap{\\textrm{#1}}"),Br("\\mathstrut","\\vphantom{(}"),Br("\\underbar","\\underline{\\text{#1}}"),Br("\\not",'\\html@mathml{\\mathrel{\\mathrlap\\@not}}{\\char"338}'),Br("\\neq","\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`\u2260}}"),Br("\\ne","\\neq"),Br("\u2260","\\neq"),Br("\\notin","\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}{\\mathrel{\\char`\u2209}}"),Br("\u2209","\\notin"),Br("\u2258","\\html@mathml{\\mathrel{=\\kern{-1em}\\raisebox{0.4em}{$\\scriptsize\\frown$}}}{\\mathrel{\\char`\u2258}}"),Br("\u2259","\\html@mathml{\\stackrel{\\tiny\\wedge}{=}}{\\mathrel{\\char`\u2258}}"),Br("\u225a","\\html@mathml{\\stackrel{\\tiny\\vee}{=}}{\\mathrel{\\char`\u225a}}"),Br("\u225b","\\html@mathml{\\stackrel{\\scriptsize\\star}{=}}{\\mathrel{\\char`\u225b}}"),Br("\u225d","\\html@mathml{\\stackrel{\\tiny\\mathrm{def}}{=}}{\\mathrel{\\char`\u225d}}"),Br("\u225e","\\html@mathml{\\stackrel{\\tiny\\mathrm{m}}{=}}{\\mathrel{\\char`\u225e}}"),Br("\u225f","\\html@mathml{\\stackrel{\\tiny?}{=}}{\\mathrel{\\char`\u225f}}"),Br("\u27c2","\\perp"),Br("\u203c","\\mathclose{!\\mkern-0.8mu!}"),Br("\u220c","\\notni"),Br("\u231c","\\ulcorner"),Br("\u231d","\\urcorner"),Br("\u231e","\\llcorner"),Br("\u231f","\\lrcorner"),Br("\xa9","\\copyright"),Br("\xae","\\textregistered"),Br("\ufe0f","\\textregistered"),Br("\\ulcorner",'\\html@mathml{\\@ulcorner}{\\mathop{\\char"231c}}'),Br("\\urcorner",'\\html@mathml{\\@urcorner}{\\mathop{\\char"231d}}'),Br("\\llcorner",'\\html@mathml{\\@llcorner}{\\mathop{\\char"231e}}'),Br("\\lrcorner",'\\html@mathml{\\@lrcorner}{\\mathop{\\char"231f}}'),Br("\\vdots","\\mathord{\\varvdots\\rule{0pt}{15pt}}"),Br("\u22ee","\\vdots"),Br("\\varGamma","\\mathit{\\Gamma}"),Br("\\varDelta","\\mathit{\\Delta}"),Br("\\varTheta","\\mathit{\\Theta}"),Br("\\varLambda","\\mathit{\\Lambda}"),Br("\\varXi","\\mathit{\\Xi}"),Br("\\varPi","\\mathit{\\Pi}"),Br("\\varSigma","\\mathit{\\Sigma}"),Br("\\varUpsilon","\\mathit{\\Upsilon}"),Br("\\varPhi","\\mathit{\\Phi}"),Br("\\varPsi","\\mathit{\\Psi}"),Br("\\varOmega","\\mathit{\\Omega}"),Br("\\substack","\\begin{subarray}{c}#1\\end{subarray}"),Br("\\colon","\\nobreak\\mskip2mu\\mathpunct{}\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax"),Br("\\boxed","\\fbox{$\\displaystyle{#1}$}"),Br("\\iff","\\DOTSB\\;\\Longleftrightarrow\\;"),Br("\\implies","\\DOTSB\\;\\Longrightarrow\\;"),Br("\\impliedby","\\DOTSB\\;\\Longleftarrow\\;");const Cn={",":"\\dotsc","\\not":"\\dotsb","+":"\\dotsb","=":"\\dotsb","<":"\\dotsb",">":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};Br("\\dots",(function(e){let t="\\dotso";const r=e.expandAfterFuture().text;return r in Cn?t=Cn[r]:("\\not"===r.slice(0,4)||r in oe.math&&l.contains(["bin","rel"],oe.math[r].group))&&(t="\\dotsb"),t}));const Nn={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};Br("\\dotso",(function(e){return e.future().text in Nn?"\\ldots\\,":"\\ldots"})),Br("\\dotsc",(function(e){const t=e.future().text;return t in Nn&&","!==t?"\\ldots\\,":"\\ldots"})),Br("\\cdots",(function(e){return e.future().text in Nn?"\\@cdots\\,":"\\@cdots"})),Br("\\dotsb","\\cdots"),Br("\\dotsm","\\cdots"),Br("\\dotsi","\\!\\cdots"),Br("\\dotsx","\\ldots\\,"),Br("\\DOTSI","\\relax"),Br("\\DOTSB","\\relax"),Br("\\DOTSX","\\relax"),Br("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax"),Br("\\,","\\tmspace+{3mu}{.1667em}"),Br("\\thinspace","\\,"),Br("\\>","\\mskip{4mu}"),Br("\\:","\\tmspace+{4mu}{.2222em}"),Br("\\medspace","\\:"),Br("\\;","\\tmspace+{5mu}{.2777em}"),Br("\\thickspace","\\;"),Br("\\!","\\tmspace-{3mu}{.1667em}"),Br("\\negthinspace","\\!"),Br("\\negmedspace","\\tmspace-{4mu}{.2222em}"),Br("\\negthickspace","\\tmspace-{5mu}{.277em}"),Br("\\enspace","\\kern.5em "),Br("\\enskip","\\hskip.5em\\relax"),Br("\\quad","\\hskip1em\\relax"),Br("\\qquad","\\hskip2em\\relax"),Br("\\tag","\\@ifstar\\tag@literal\\tag@paren"),Br("\\tag@paren","\\tag@literal{({#1})}"),Br("\\tag@literal",(e=>{if(e.macros.get("\\df@tag"))throw new n("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"})),Br("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}"),Br("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)"),Br("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}"),Br("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1"),Br("\\newline","\\\\\\relax"),Br("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");const qn=F(T["Main-Regular"]["T".charCodeAt(0)][1]-.7*T["Main-Regular"]["A".charCodeAt(0)][1]);Br("\\LaTeX","\\textrm{\\html@mathml{L\\kern-.36em\\raisebox{"+qn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{LaTeX}}"),Br("\\KaTeX","\\textrm{\\html@mathml{K\\kern-.17em\\raisebox{"+qn+"}{\\scriptstyle A}\\kern-.15em\\TeX}{KaTeX}}"),Br("\\hspace","\\@ifstar\\@hspacer\\@hspace"),Br("\\@hspace","\\hskip #1\\relax"),Br("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax"),Br("\\ordinarycolon",":"),Br("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}"),Br("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}'),Br("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}'),Br("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}'),Br("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}'),Br("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}'),Br("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}'),Br("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}'),Br("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}'),Br("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}'),Br("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}'),Br("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}'),Br("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}'),Br("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}'),Br("\u2237","\\dblcolon"),Br("\u2239","\\eqcolon"),Br("\u2254","\\coloneqq"),Br("\u2255","\\eqqcolon"),Br("\u2a74","\\Coloneqq"),Br("\\ratio","\\vcentcolon"),Br("\\coloncolon","\\dblcolon"),Br("\\colonequals","\\coloneqq"),Br("\\coloncolonequals","\\Coloneqq"),Br("\\equalscolon","\\eqqcolon"),Br("\\equalscoloncolon","\\Eqqcolon"),Br("\\colonminus","\\coloneq"),Br("\\coloncolonminus","\\Coloneq"),Br("\\minuscolon","\\eqcolon"),Br("\\minuscoloncolon","\\Eqcolon"),Br("\\coloncolonapprox","\\Colonapprox"),Br("\\coloncolonsim","\\Colonsim"),Br("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),Br("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}"),Br("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),Br("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}"),Br("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220c}}"),Br("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}"),Br("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}"),Br("\\injlim","\\DOTSB\\operatorname*{inj\\,lim}"),Br("\\projlim","\\DOTSB\\operatorname*{proj\\,lim}"),Br("\\varlimsup","\\DOTSB\\operatorname*{\\overline{lim}}"),Br("\\varliminf","\\DOTSB\\operatorname*{\\underline{lim}}"),Br("\\varinjlim","\\DOTSB\\operatorname*{\\underrightarrow{lim}}"),Br("\\varprojlim","\\DOTSB\\operatorname*{\\underleftarrow{lim}}"),Br("\\gvertneqq","\\html@mathml{\\@gvertneqq}{\u2269}"),Br("\\lvertneqq","\\html@mathml{\\@lvertneqq}{\u2268}"),Br("\\ngeqq","\\html@mathml{\\@ngeqq}{\u2271}"),Br("\\ngeqslant","\\html@mathml{\\@ngeqslant}{\u2271}"),Br("\\nleqq","\\html@mathml{\\@nleqq}{\u2270}"),Br("\\nleqslant","\\html@mathml{\\@nleqslant}{\u2270}"),Br("\\nshortmid","\\html@mathml{\\@nshortmid}{\u2224}"),Br("\\nshortparallel","\\html@mathml{\\@nshortparallel}{\u2226}"),Br("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{\u2288}"),Br("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{\u2289}"),Br("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{\u228a}"),Br("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{\u2acb}"),Br("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{\u228b}"),Br("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{\u2acc}"),Br("\\imath","\\html@mathml{\\@imath}{\u0131}"),Br("\\jmath","\\html@mathml{\\@jmath}{\u0237}"),Br("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`\u27e6}}"),Br("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`\u27e7}}"),Br("\u27e6","\\llbracket"),Br("\u27e7","\\rrbracket"),Br("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`\u2983}}"),Br("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`\u2984}}"),Br("\u2983","\\lBrace"),Br("\u2984","\\rBrace"),Br("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u29b5}}"),Br("\u29b5","\\minuso"),Br("\\darr","\\downarrow"),Br("\\dArr","\\Downarrow"),Br("\\Darr","\\Downarrow"),Br("\\lang","\\langle"),Br("\\rang","\\rangle"),Br("\\uarr","\\uparrow"),Br("\\uArr","\\Uparrow"),Br("\\Uarr","\\Uparrow"),Br("\\N","\\mathbb{N}"),Br("\\R","\\mathbb{R}"),Br("\\Z","\\mathbb{Z}"),Br("\\alef","\\aleph"),Br("\\alefsym","\\aleph"),Br("\\Alpha","\\mathrm{A}"),Br("\\Beta","\\mathrm{B}"),Br("\\bull","\\bullet"),Br("\\Chi","\\mathrm{X}"),Br("\\clubs","\\clubsuit"),Br("\\cnums","\\mathbb{C}"),Br("\\Complex","\\mathbb{C}"),Br("\\Dagger","\\ddagger"),Br("\\diamonds","\\diamondsuit"),Br("\\empty","\\emptyset"),Br("\\Epsilon","\\mathrm{E}"),Br("\\Eta","\\mathrm{H}"),Br("\\exist","\\exists"),Br("\\harr","\\leftrightarrow"),Br("\\hArr","\\Leftrightarrow"),Br("\\Harr","\\Leftrightarrow"),Br("\\hearts","\\heartsuit"),Br("\\image","\\Im"),Br("\\infin","\\infty"),Br("\\Iota","\\mathrm{I}"),Br("\\isin","\\in"),Br("\\Kappa","\\mathrm{K}"),Br("\\larr","\\leftarrow"),Br("\\lArr","\\Leftarrow"),Br("\\Larr","\\Leftarrow"),Br("\\lrarr","\\leftrightarrow"),Br("\\lrArr","\\Leftrightarrow"),Br("\\Lrarr","\\Leftrightarrow"),Br("\\Mu","\\mathrm{M}"),Br("\\natnums","\\mathbb{N}"),Br("\\Nu","\\mathrm{N}"),Br("\\Omicron","\\mathrm{O}"),Br("\\plusmn","\\pm"),Br("\\rarr","\\rightarrow"),Br("\\rArr","\\Rightarrow"),Br("\\Rarr","\\Rightarrow"),Br("\\real","\\Re"),Br("\\reals","\\mathbb{R}"),Br("\\Reals","\\mathbb{R}"),Br("\\Rho","\\mathrm{P}"),Br("\\sdot","\\cdot"),Br("\\sect","\\S"),Br("\\spades","\\spadesuit"),Br("\\sub","\\subset"),Br("\\sube","\\subseteq"),Br("\\supe","\\supseteq"),Br("\\Tau","\\mathrm{T}"),Br("\\thetasym","\\vartheta"),Br("\\weierp","\\wp"),Br("\\Zeta","\\mathrm{Z}"),Br("\\argmin","\\DOTSB\\operatorname*{arg\\,min}"),Br("\\argmax","\\DOTSB\\operatorname*{arg\\,max}"),Br("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits"),Br("\\bra","\\mathinner{\\langle{#1}|}"),Br("\\ket","\\mathinner{|{#1}\\rangle}"),Br("\\braket","\\mathinner{\\langle{#1}\\rangle}"),Br("\\Bra","\\left\\langle#1\\right|"),Br("\\Ket","\\left|#1\\right\\rangle");const In=e=>t=>{const r=t.consumeArg().tokens,n=t.consumeArg().tokens,o=t.consumeArg().tokens,s=t.consumeArg().tokens,i=t.macros.get("|"),a=t.macros.get("\\|");t.macros.beginGroup();const l=t=>r=>{e&&(r.macros.set("|",i),o.length&&r.macros.set("\\|",a));let s=t;if(!t&&o.length){"|"===r.future().text&&(r.popToken(),s=!0)}return{tokens:s?o:n,numArgs:0}};t.macros.set("|",l(!1)),o.length&&t.macros.set("\\|",l(!0));const h=t.consumeArg().tokens,c=t.expandTokens([...s,...h,...r]);return t.macros.endGroup(),{tokens:c.reverse(),numArgs:0}};Br("\\bra@ket",In(!1)),Br("\\bra@set",In(!0)),Br("\\Braket","\\bra@ket{\\left\\langle}{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}"),Br("\\Set","\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}"),Br("\\set","\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}"),Br("\\angln","{\\angl n}"),Br("\\blue","\\textcolor{##6495ed}{#1}"),Br("\\orange","\\textcolor{##ffa500}{#1}"),Br("\\pink","\\textcolor{##ff00af}{#1}"),Br("\\red","\\textcolor{##df0030}{#1}"),Br("\\green","\\textcolor{##28ae7b}{#1}"),Br("\\gray","\\textcolor{gray}{#1}"),Br("\\purple","\\textcolor{##9d38bd}{#1}"),Br("\\blueA","\\textcolor{##ccfaff}{#1}"),Br("\\blueB","\\textcolor{##80f6ff}{#1}"),Br("\\blueC","\\textcolor{##63d9ea}{#1}"),Br("\\blueD","\\textcolor{##11accd}{#1}"),Br("\\blueE","\\textcolor{##0c7f99}{#1}"),Br("\\tealA","\\textcolor{##94fff5}{#1}"),Br("\\tealB","\\textcolor{##26edd5}{#1}"),Br("\\tealC","\\textcolor{##01d1c1}{#1}"),Br("\\tealD","\\textcolor{##01a995}{#1}"),Br("\\tealE","\\textcolor{##208170}{#1}"),Br("\\greenA","\\textcolor{##b6ffb0}{#1}"),Br("\\greenB","\\textcolor{##8af281}{#1}"),Br("\\greenC","\\textcolor{##74cf70}{#1}"),Br("\\greenD","\\textcolor{##1fab54}{#1}"),Br("\\greenE","\\textcolor{##0d923f}{#1}"),Br("\\goldA","\\textcolor{##ffd0a9}{#1}"),Br("\\goldB","\\textcolor{##ffbb71}{#1}"),Br("\\goldC","\\textcolor{##ff9c39}{#1}"),Br("\\goldD","\\textcolor{##e07d10}{#1}"),Br("\\goldE","\\textcolor{##a75a05}{#1}"),Br("\\redA","\\textcolor{##fca9a9}{#1}"),Br("\\redB","\\textcolor{##ff8482}{#1}"),Br("\\redC","\\textcolor{##f9685d}{#1}"),Br("\\redD","\\textcolor{##e84d39}{#1}"),Br("\\redE","\\textcolor{##bc2612}{#1}"),Br("\\maroonA","\\textcolor{##ffbde0}{#1}"),Br("\\maroonB","\\textcolor{##ff92c6}{#1}"),Br("\\maroonC","\\textcolor{##ed5fa6}{#1}"),Br("\\maroonD","\\textcolor{##ca337c}{#1}"),Br("\\maroonE","\\textcolor{##9e034e}{#1}"),Br("\\purpleA","\\textcolor{##ddd7ff}{#1}"),Br("\\purpleB","\\textcolor{##c6b9fc}{#1}"),Br("\\purpleC","\\textcolor{##aa87ff}{#1}"),Br("\\purpleD","\\textcolor{##7854ab}{#1}"),Br("\\purpleE","\\textcolor{##543b78}{#1}"),Br("\\mintA","\\textcolor{##f5f9e8}{#1}"),Br("\\mintB","\\textcolor{##edf2df}{#1}"),Br("\\mintC","\\textcolor{##e0e5cc}{#1}"),Br("\\grayA","\\textcolor{##f6f7f7}{#1}"),Br("\\grayB","\\textcolor{##f0f1f2}{#1}"),Br("\\grayC","\\textcolor{##e3e5e6}{#1}"),Br("\\grayD","\\textcolor{##d6d8da}{#1}"),Br("\\grayE","\\textcolor{##babec2}{#1}"),Br("\\grayF","\\textcolor{##888d93}{#1}"),Br("\\grayG","\\textcolor{##626569}{#1}"),Br("\\grayH","\\textcolor{##3b3e40}{#1}"),Br("\\grayI","\\textcolor{##21242c}{#1}"),Br("\\kaBlue","\\textcolor{##314453}{#1}"),Br("\\kaGreen","\\textcolor{##71B307}{#1}");const Rn={"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0};class Hn{constructor(e,t,r){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=t,this.expansionCount=0,this.feed(e),this.macros=new zn(An,t.macros),this.mode=r,this.stack=[]}feed(e){this.lexer=new Mn(e,this.settings)}switchMode(e){this.mode=e}beginGroup(){this.macros.beginGroup()}endGroup(){this.macros.endGroup()}endGroups(){this.macros.endGroups()}future(){return 0===this.stack.length&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]}popToken(){return this.future(),this.stack.pop()}pushToken(e){this.stack.push(e)}pushTokens(e){this.stack.push(...e)}scanArgument(e){let t,r,n;if(e){if(this.consumeSpaces(),"["!==this.future().text)return null;t=this.popToken(),({tokens:n,end:r}=this.consumeArg(["]"]))}else({tokens:n,start:t,end:r}=this.consumeArg());return this.pushToken(new Nr("EOF",r.loc)),this.pushTokens(n),t.range(r,"")}consumeSpaces(){for(;;){if(" "!==this.future().text)break;this.stack.pop()}}consumeArg(e){const t=[],r=e&&e.length>0;r||this.consumeSpaces();const o=this.future();let s,i=0,a=0;do{if(s=this.popToken(),t.push(s),"{"===s.text)++i;else if("}"===s.text){if(--i,-1===i)throw new n("Extra }",s)}else if("EOF"===s.text)throw new n("Unexpected end of input in a macro argument, expected '"+(e&&r?e[a]:"}")+"'",s);if(e&&r)if((0===i||1===i&&"{"===e[a])&&s.text===e[a]){if(++a,a===e.length){t.splice(-a,a);break}}else a=0}while(0!==i||r);return"{"===o.text&&"}"===t[t.length-1].text&&(t.pop(),t.shift()),t.reverse(),{tokens:t,start:o,end:s}}consumeArgs(e,t){if(t){if(t.length!==e+1)throw new n("The length of delimiters doesn't match the number of args!");const r=t[0];for(let e=0;ethis.settings.maxExpand)throw new n("Too many expansions: infinite loop or need to increase maxExpand setting")}expandOnce(e){const t=this.popToken(),r=t.text,o=t.noexpand?null:this._getExpansion(r);if(null==o||e&&o.unexpandable){if(e&&null==o&&"\\"===r[0]&&!this.isDefined(r))throw new n("Undefined control sequence: "+r);return this.pushToken(t),!1}this.countExpansion(1);let s=o.tokens;const i=this.consumeArgs(o.numArgs,o.delimiters);if(o.numArgs){s=s.slice();for(let e=s.length-1;e>=0;--e){let t=s[e];if("#"===t.text){if(0===e)throw new n("Incomplete placeholder at end of macro body",t);if(t=s[--e],"#"===t.text)s.splice(e+1,1);else{if(!/^[1-9]$/.test(t.text))throw new n("Not a valid argument number",t);s.splice(e,2,...i[+t.text-1])}}}}return this.pushTokens(s),s.length}expandAfterFuture(){return this.expandOnce(),this.future()}expandNextToken(){for(;;)if(!1===this.expandOnce()){const e=this.stack.pop();return e.treatAsRelax&&(e.text="\\relax"),e}throw new Error}expandMacro(e){return this.macros.has(e)?this.expandTokens([new Nr(e)]):void 0}expandTokens(e){const t=[],r=this.stack.length;for(this.pushTokens(e);this.stack.length>r;)if(!1===this.expandOnce(!0)){const e=this.stack.pop();e.treatAsRelax&&(e.noexpand=!1,e.treatAsRelax=!1),t.push(e)}return this.countExpansion(t.length),t}expandMacroAsText(e){const t=this.expandMacro(e);return t?t.map((e=>e.text)).join(""):t}_getExpansion(e){const t=this.macros.get(e);if(null==t)return t;if(1===e.length){const t=this.lexer.catcodes[e];if(null!=t&&13!==t)return}const r="function"==typeof t?t(this):t;if("string"==typeof r){let e=0;if(-1!==r.indexOf("#")){const t=r.replace(/##/g,"");for(;-1!==t.indexOf("#"+(e+1));)++e}const t=new Mn(r,this.settings),n=[];let o=t.lex();for(;"EOF"!==o.text;)n.push(o),o=t.lex();n.reverse();return{tokens:n,numArgs:e}}return r}isDefined(e){return this.macros.has(e)||yn.hasOwnProperty(e)||oe.math.hasOwnProperty(e)||oe.text.hasOwnProperty(e)||Rn.hasOwnProperty(e)}isExpandable(e){const t=this.macros.get(e);return null!=t?"string"==typeof t||"function"==typeof t||!t.unexpandable:yn.hasOwnProperty(e)&&!yn[e].primitive}}const On=/^[\u208a\u208b\u208c\u208d\u208e\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2090\u2091\u2095\u1d62\u2c7c\u2096\u2097\u2098\u2099\u2092\u209a\u1d63\u209b\u209c\u1d64\u1d65\u2093\u1d66\u1d67\u1d68\u1d69\u1d6a]/,En=Object.freeze({"\u208a":"+","\u208b":"-","\u208c":"=","\u208d":"(","\u208e":")","\u2080":"0","\u2081":"1","\u2082":"2","\u2083":"3","\u2084":"4","\u2085":"5","\u2086":"6","\u2087":"7","\u2088":"8","\u2089":"9","\u2090":"a","\u2091":"e","\u2095":"h","\u1d62":"i","\u2c7c":"j","\u2096":"k","\u2097":"l","\u2098":"m","\u2099":"n","\u2092":"o","\u209a":"p","\u1d63":"r","\u209b":"s","\u209c":"t","\u1d64":"u","\u1d65":"v","\u2093":"x","\u1d66":"\u03b2","\u1d67":"\u03b3","\u1d68":"\u03c1","\u1d69":"\u03d5","\u1d6a":"\u03c7","\u207a":"+","\u207b":"-","\u207c":"=","\u207d":"(","\u207e":")","\u2070":"0","\xb9":"1","\xb2":"2","\xb3":"3","\u2074":"4","\u2075":"5","\u2076":"6","\u2077":"7","\u2078":"8","\u2079":"9","\u1d2c":"A","\u1d2e":"B","\u1d30":"D","\u1d31":"E","\u1d33":"G","\u1d34":"H","\u1d35":"I","\u1d36":"J","\u1d37":"K","\u1d38":"L","\u1d39":"M","\u1d3a":"N","\u1d3c":"O","\u1d3e":"P","\u1d3f":"R","\u1d40":"T","\u1d41":"U","\u2c7d":"V","\u1d42":"W","\u1d43":"a","\u1d47":"b","\u1d9c":"c","\u1d48":"d","\u1d49":"e","\u1da0":"f","\u1d4d":"g","\u02b0":"h","\u2071":"i","\u02b2":"j","\u1d4f":"k","\u02e1":"l","\u1d50":"m","\u207f":"n","\u1d52":"o","\u1d56":"p","\u02b3":"r","\u02e2":"s","\u1d57":"t","\u1d58":"u","\u1d5b":"v","\u02b7":"w","\u02e3":"x","\u02b8":"y","\u1dbb":"z","\u1d5d":"\u03b2","\u1d5e":"\u03b3","\u1d5f":"\u03b4","\u1d60":"\u03d5","\u1d61":"\u03c7","\u1dbf":"\u03b8"}),Ln={"\u0301":{text:"\\'",math:"\\acute"},"\u0300":{text:"\\`",math:"\\grave"},"\u0308":{text:'\\"',math:"\\ddot"},"\u0303":{text:"\\~",math:"\\tilde"},"\u0304":{text:"\\=",math:"\\bar"},"\u0306":{text:"\\u",math:"\\breve"},"\u030c":{text:"\\v",math:"\\check"},"\u0302":{text:"\\^",math:"\\hat"},"\u0307":{text:"\\.",math:"\\dot"},"\u030a":{text:"\\r",math:"\\mathring"},"\u030b":{text:"\\H"},"\u0327":{text:"\\c"}},Dn={"\xe1":"a\u0301","\xe0":"a\u0300","\xe4":"a\u0308","\u01df":"a\u0308\u0304","\xe3":"a\u0303","\u0101":"a\u0304","\u0103":"a\u0306","\u1eaf":"a\u0306\u0301","\u1eb1":"a\u0306\u0300","\u1eb5":"a\u0306\u0303","\u01ce":"a\u030c","\xe2":"a\u0302","\u1ea5":"a\u0302\u0301","\u1ea7":"a\u0302\u0300","\u1eab":"a\u0302\u0303","\u0227":"a\u0307","\u01e1":"a\u0307\u0304","\xe5":"a\u030a","\u01fb":"a\u030a\u0301","\u1e03":"b\u0307","\u0107":"c\u0301","\u1e09":"c\u0327\u0301","\u010d":"c\u030c","\u0109":"c\u0302","\u010b":"c\u0307","\xe7":"c\u0327","\u010f":"d\u030c","\u1e0b":"d\u0307","\u1e11":"d\u0327","\xe9":"e\u0301","\xe8":"e\u0300","\xeb":"e\u0308","\u1ebd":"e\u0303","\u0113":"e\u0304","\u1e17":"e\u0304\u0301","\u1e15":"e\u0304\u0300","\u0115":"e\u0306","\u1e1d":"e\u0327\u0306","\u011b":"e\u030c","\xea":"e\u0302","\u1ebf":"e\u0302\u0301","\u1ec1":"e\u0302\u0300","\u1ec5":"e\u0302\u0303","\u0117":"e\u0307","\u0229":"e\u0327","\u1e1f":"f\u0307","\u01f5":"g\u0301","\u1e21":"g\u0304","\u011f":"g\u0306","\u01e7":"g\u030c","\u011d":"g\u0302","\u0121":"g\u0307","\u0123":"g\u0327","\u1e27":"h\u0308","\u021f":"h\u030c","\u0125":"h\u0302","\u1e23":"h\u0307","\u1e29":"h\u0327","\xed":"i\u0301","\xec":"i\u0300","\xef":"i\u0308","\u1e2f":"i\u0308\u0301","\u0129":"i\u0303","\u012b":"i\u0304","\u012d":"i\u0306","\u01d0":"i\u030c","\xee":"i\u0302","\u01f0":"j\u030c","\u0135":"j\u0302","\u1e31":"k\u0301","\u01e9":"k\u030c","\u0137":"k\u0327","\u013a":"l\u0301","\u013e":"l\u030c","\u013c":"l\u0327","\u1e3f":"m\u0301","\u1e41":"m\u0307","\u0144":"n\u0301","\u01f9":"n\u0300","\xf1":"n\u0303","\u0148":"n\u030c","\u1e45":"n\u0307","\u0146":"n\u0327","\xf3":"o\u0301","\xf2":"o\u0300","\xf6":"o\u0308","\u022b":"o\u0308\u0304","\xf5":"o\u0303","\u1e4d":"o\u0303\u0301","\u1e4f":"o\u0303\u0308","\u022d":"o\u0303\u0304","\u014d":"o\u0304","\u1e53":"o\u0304\u0301","\u1e51":"o\u0304\u0300","\u014f":"o\u0306","\u01d2":"o\u030c","\xf4":"o\u0302","\u1ed1":"o\u0302\u0301","\u1ed3":"o\u0302\u0300","\u1ed7":"o\u0302\u0303","\u022f":"o\u0307","\u0231":"o\u0307\u0304","\u0151":"o\u030b","\u1e55":"p\u0301","\u1e57":"p\u0307","\u0155":"r\u0301","\u0159":"r\u030c","\u1e59":"r\u0307","\u0157":"r\u0327","\u015b":"s\u0301","\u1e65":"s\u0301\u0307","\u0161":"s\u030c","\u1e67":"s\u030c\u0307","\u015d":"s\u0302","\u1e61":"s\u0307","\u015f":"s\u0327","\u1e97":"t\u0308","\u0165":"t\u030c","\u1e6b":"t\u0307","\u0163":"t\u0327","\xfa":"u\u0301","\xf9":"u\u0300","\xfc":"u\u0308","\u01d8":"u\u0308\u0301","\u01dc":"u\u0308\u0300","\u01d6":"u\u0308\u0304","\u01da":"u\u0308\u030c","\u0169":"u\u0303","\u1e79":"u\u0303\u0301","\u016b":"u\u0304","\u1e7b":"u\u0304\u0308","\u016d":"u\u0306","\u01d4":"u\u030c","\xfb":"u\u0302","\u016f":"u\u030a","\u0171":"u\u030b","\u1e7d":"v\u0303","\u1e83":"w\u0301","\u1e81":"w\u0300","\u1e85":"w\u0308","\u0175":"w\u0302","\u1e87":"w\u0307","\u1e98":"w\u030a","\u1e8d":"x\u0308","\u1e8b":"x\u0307","\xfd":"y\u0301","\u1ef3":"y\u0300","\xff":"y\u0308","\u1ef9":"y\u0303","\u0233":"y\u0304","\u0177":"y\u0302","\u1e8f":"y\u0307","\u1e99":"y\u030a","\u017a":"z\u0301","\u017e":"z\u030c","\u1e91":"z\u0302","\u017c":"z\u0307","\xc1":"A\u0301","\xc0":"A\u0300","\xc4":"A\u0308","\u01de":"A\u0308\u0304","\xc3":"A\u0303","\u0100":"A\u0304","\u0102":"A\u0306","\u1eae":"A\u0306\u0301","\u1eb0":"A\u0306\u0300","\u1eb4":"A\u0306\u0303","\u01cd":"A\u030c","\xc2":"A\u0302","\u1ea4":"A\u0302\u0301","\u1ea6":"A\u0302\u0300","\u1eaa":"A\u0302\u0303","\u0226":"A\u0307","\u01e0":"A\u0307\u0304","\xc5":"A\u030a","\u01fa":"A\u030a\u0301","\u1e02":"B\u0307","\u0106":"C\u0301","\u1e08":"C\u0327\u0301","\u010c":"C\u030c","\u0108":"C\u0302","\u010a":"C\u0307","\xc7":"C\u0327","\u010e":"D\u030c","\u1e0a":"D\u0307","\u1e10":"D\u0327","\xc9":"E\u0301","\xc8":"E\u0300","\xcb":"E\u0308","\u1ebc":"E\u0303","\u0112":"E\u0304","\u1e16":"E\u0304\u0301","\u1e14":"E\u0304\u0300","\u0114":"E\u0306","\u1e1c":"E\u0327\u0306","\u011a":"E\u030c","\xca":"E\u0302","\u1ebe":"E\u0302\u0301","\u1ec0":"E\u0302\u0300","\u1ec4":"E\u0302\u0303","\u0116":"E\u0307","\u0228":"E\u0327","\u1e1e":"F\u0307","\u01f4":"G\u0301","\u1e20":"G\u0304","\u011e":"G\u0306","\u01e6":"G\u030c","\u011c":"G\u0302","\u0120":"G\u0307","\u0122":"G\u0327","\u1e26":"H\u0308","\u021e":"H\u030c","\u0124":"H\u0302","\u1e22":"H\u0307","\u1e28":"H\u0327","\xcd":"I\u0301","\xcc":"I\u0300","\xcf":"I\u0308","\u1e2e":"I\u0308\u0301","\u0128":"I\u0303","\u012a":"I\u0304","\u012c":"I\u0306","\u01cf":"I\u030c","\xce":"I\u0302","\u0130":"I\u0307","\u0134":"J\u0302","\u1e30":"K\u0301","\u01e8":"K\u030c","\u0136":"K\u0327","\u0139":"L\u0301","\u013d":"L\u030c","\u013b":"L\u0327","\u1e3e":"M\u0301","\u1e40":"M\u0307","\u0143":"N\u0301","\u01f8":"N\u0300","\xd1":"N\u0303","\u0147":"N\u030c","\u1e44":"N\u0307","\u0145":"N\u0327","\xd3":"O\u0301","\xd2":"O\u0300","\xd6":"O\u0308","\u022a":"O\u0308\u0304","\xd5":"O\u0303","\u1e4c":"O\u0303\u0301","\u1e4e":"O\u0303\u0308","\u022c":"O\u0303\u0304","\u014c":"O\u0304","\u1e52":"O\u0304\u0301","\u1e50":"O\u0304\u0300","\u014e":"O\u0306","\u01d1":"O\u030c","\xd4":"O\u0302","\u1ed0":"O\u0302\u0301","\u1ed2":"O\u0302\u0300","\u1ed6":"O\u0302\u0303","\u022e":"O\u0307","\u0230":"O\u0307\u0304","\u0150":"O\u030b","\u1e54":"P\u0301","\u1e56":"P\u0307","\u0154":"R\u0301","\u0158":"R\u030c","\u1e58":"R\u0307","\u0156":"R\u0327","\u015a":"S\u0301","\u1e64":"S\u0301\u0307","\u0160":"S\u030c","\u1e66":"S\u030c\u0307","\u015c":"S\u0302","\u1e60":"S\u0307","\u015e":"S\u0327","\u0164":"T\u030c","\u1e6a":"T\u0307","\u0162":"T\u0327","\xda":"U\u0301","\xd9":"U\u0300","\xdc":"U\u0308","\u01d7":"U\u0308\u0301","\u01db":"U\u0308\u0300","\u01d5":"U\u0308\u0304","\u01d9":"U\u0308\u030c","\u0168":"U\u0303","\u1e78":"U\u0303\u0301","\u016a":"U\u0304","\u1e7a":"U\u0304\u0308","\u016c":"U\u0306","\u01d3":"U\u030c","\xdb":"U\u0302","\u016e":"U\u030a","\u0170":"U\u030b","\u1e7c":"V\u0303","\u1e82":"W\u0301","\u1e80":"W\u0300","\u1e84":"W\u0308","\u0174":"W\u0302","\u1e86":"W\u0307","\u1e8c":"X\u0308","\u1e8a":"X\u0307","\xdd":"Y\u0301","\u1ef2":"Y\u0300","\u0178":"Y\u0308","\u1ef8":"Y\u0303","\u0232":"Y\u0304","\u0176":"Y\u0302","\u1e8e":"Y\u0307","\u0179":"Z\u0301","\u017d":"Z\u030c","\u1e90":"Z\u0302","\u017b":"Z\u0307","\u03ac":"\u03b1\u0301","\u1f70":"\u03b1\u0300","\u1fb1":"\u03b1\u0304","\u1fb0":"\u03b1\u0306","\u03ad":"\u03b5\u0301","\u1f72":"\u03b5\u0300","\u03ae":"\u03b7\u0301","\u1f74":"\u03b7\u0300","\u03af":"\u03b9\u0301","\u1f76":"\u03b9\u0300","\u03ca":"\u03b9\u0308","\u0390":"\u03b9\u0308\u0301","\u1fd2":"\u03b9\u0308\u0300","\u1fd1":"\u03b9\u0304","\u1fd0":"\u03b9\u0306","\u03cc":"\u03bf\u0301","\u1f78":"\u03bf\u0300","\u03cd":"\u03c5\u0301","\u1f7a":"\u03c5\u0300","\u03cb":"\u03c5\u0308","\u03b0":"\u03c5\u0308\u0301","\u1fe2":"\u03c5\u0308\u0300","\u1fe1":"\u03c5\u0304","\u1fe0":"\u03c5\u0306","\u03ce":"\u03c9\u0301","\u1f7c":"\u03c9\u0300","\u038e":"\u03a5\u0301","\u1fea":"\u03a5\u0300","\u03ab":"\u03a5\u0308","\u1fe9":"\u03a5\u0304","\u1fe8":"\u03a5\u0306","\u038f":"\u03a9\u0301","\u1ffa":"\u03a9\u0300"};class Vn{constructor(e,t){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new Hn(e,t,this.mode),this.settings=t,this.leftrightDepth=0}expect(e,t){if(void 0===t&&(t=!0),this.fetch().text!==e)throw new n("Expected '"+e+"', got '"+this.fetch().text+"'",this.fetch());t&&this.consume()}consume(){this.nextToken=null}fetch(){return null==this.nextToken&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken}switchMode(e){this.mode=e,this.gullet.switchMode(e)}parse(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");try{const e=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),e}finally{this.gullet.endGroups()}}subparse(e){const t=this.nextToken;this.consume(),this.gullet.pushToken(new Nr("}")),this.gullet.pushTokens(e);const r=this.parseExpression(!1);return this.expect("}"),this.nextToken=t,r}parseExpression(e,t){const r=[];for(;;){"math"===this.mode&&this.consumeSpaces();const n=this.fetch();if(-1!==Vn.endOfExpression.indexOf(n.text))break;if(t&&n.text===t)break;if(e&&yn[n.text]&&yn[n.text].infix)break;const o=this.parseAtom(t);if(!o)break;"internal"!==o.type&&r.push(o)}return"text"===this.mode&&this.formLigatures(r),this.handleInfixNodes(r)}handleInfixNodes(e){let t,r=-1;for(let o=0;o=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+t[0]+'" used in math mode',e);const r=oe[this.mode][t].group,n=Cr.range(e);let s;if(te.hasOwnProperty(r)){const e=r;s={type:"atom",mode:this.mode,family:e,loc:n,text:t}}else s={type:r,mode:this.mode,loc:n,text:t};o=s}else{if(!(t.charCodeAt(0)>=128))return null;this.settings.strict&&(S(t.charCodeAt(0))?"math"===this.mode&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+t[0]+'" used in math mode',e):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+t[0]+'" ('+t.charCodeAt(0)+")",e)),o={type:"textord",mode:"text",loc:Cr.range(e),text:t}}if(this.consume(),r)for(let t=0;t=0;i--)t[i].loc.start>o&&(n+=" ",o=t[i].loc.start),n+=t[i].text,o+=t[i].text.length;return r.go(a.go(n,e))},a={go:function(t,e){if(!t)return[];void 0===e&&(e="ce");var n,o="0",r={};r.parenthesisLevel=0,t=(t=(t=t.replace(/\n/g," ")).replace(/[\u2212\u2013\u2014\u2010]/g,"-")).replace(/[\u2026]/g,"...");for(var i=10,c=[];;){n!==t?(i=10,n=t):i--;var u=a.stateMachines[e],p=u.transitions[o]||u.transitions["*"];t:for(var s=0;s0))return c;if(d.revisit||(t=_.remainder),!d.toContinue)break t}}if(i<=0)throw["MhchemBugU","mhchem bug U. Please report."]}},concatArray:function(t,e){if(e)if(Array.isArray(e))for(var n=0;n":/^[=<>]/,"#":/^[#\u2261]/,"+":/^\+/,"-$":/^-(?=[\s_},;\]/]|$|\([a-z]+\))/,"-9":/^-(?=[0-9])/,"- orbital overlap":/^-(?=(?:[spd]|sp)(?:$|[\s,;\)\]\}]))/,"-":/^-/,"pm-operator":/^(?:\\pm|\$\\pm\$|\+-|\+\/-)/,operator:/^(?:\+|(?:[\-=<>]|<<|>>|\\approx|\$\\approx\$)(?=\s|$|-?[0-9]))/,arrowUpDown:/^(?:v|\(v\)|\^|\(\^\))(?=$|[\s,;\)\]\}])/,"\\bond{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\bond{","","","}")},"->":/^(?:<->|<-->|->|<-|<=>>|<<=>|<=>|[\u2192\u27F6\u21CC])/,CMT:/^[CMT](?=\[)/,"[(...)]":function(t){return a.patterns.findObserveGroups(t,"[","","","]")},"1st-level escape":/^(&|\\\\|\\hline)\s*/,"\\,":/^(?:\\[,\ ;:])/,"\\x{}{}":function(t){return a.patterns.findObserveGroups(t,"",/^\\[a-zA-Z]+\{/,"}","","","{","}","",!0)},"\\x{}":function(t){return a.patterns.findObserveGroups(t,"",/^\\[a-zA-Z]+\{/,"}","")},"\\ca":/^\\ca(?:\s+|(?![a-zA-Z]))/,"\\x":/^(?:\\[a-zA-Z]+\s*|\\[_&{}%])/,orbital:/^(?:[0-9]{1,2}[spdfgh]|[0-9]{0,2}sp)(?=$|[^a-zA-Z])/,others:/^[\/~|]/,"\\frac{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\frac{","","","}","{","","","}")},"\\overset{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\overset{","","","}","{","","","}")},"\\underset{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\underset{","","","}","{","","","}")},"\\underbrace{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\underbrace{","","","}_","{","","","}")},"\\color{(...)}0":function(t){return a.patterns.findObserveGroups(t,"\\color{","","","}")},"\\color{(...)}{(...)}1":function(t){return a.patterns.findObserveGroups(t,"\\color{","","","}","{","","","}")},"\\color(...){(...)}2":function(t){return a.patterns.findObserveGroups(t,"\\color","\\","",/^(?=\{)/,"{","","","}")},"\\ce{(...)}":function(t){return a.patterns.findObserveGroups(t,"\\ce{","","","}")},oxidation$:/^(?:[+-][IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,"d-oxidation$":/^(?:[+-]?\s?[IVX]+|\\pm\s*0|\$\\pm\$\s*0)$/,"roman numeral":/^[IVX]+/,"1/2$":/^[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+(?:\$[a-z]\$|[a-z])?$/,amount:function(t){var e;if(e=t.match(/^(?:(?:(?:\([+\-]?[0-9]+\/[0-9]+\)|[+\-]?(?:[0-9]+|\$[a-z]\$|[a-z])\/[0-9]+|[+\-]?[0-9]+[.,][0-9]+|[+\-]?\.[0-9]+|[+\-]?[0-9]+)(?:[a-z](?=\s*[A-Z]))?)|[+\-]?[a-z](?=\s*[A-Z])|\+(?!\s))/))return{match_:e[0],remainder:t.substr(e[0].length)};var n=a.patterns.findObserveGroups(t,"","$","$","");return n&&(e=n.match_.match(/^\$(?:\(?[+\-]?(?:[0-9]*[a-z]?[+\-])?[0-9]*[a-z](?:[+\-][0-9]*[a-z]?)?\)?|\+|-)\$$/))?{match_:e[0],remainder:t.substr(e[0].length)}:null},amount2:function(t){return this.amount(t)},"(KV letters),":/^(?:[A-Z][a-z]{0,2}|i)(?=,)/,formula$:function(t){if(t.match(/^\([a-z]+\)$/))return null;var e=t.match(/^(?:[a-z]|(?:[0-9\ \+\-\,\.\(\)]+[a-z])+[0-9\ \+\-\,\.\(\)]*|(?:[a-z][0-9\ \+\-\,\.\(\)]+)+[a-z]?)$/);return e?{match_:e[0],remainder:t.substr(e[0].length)}:null},uprightEntities:/^(?:pH|pOH|pC|pK|iPr|iBu)(?=$|[^a-zA-Z])/,"/":/^\s*(\/)\s*/,"//":/^\s*(\/\/)\s*/,"*":/^\s*[*.]\s*/},findObserveGroups:function(t,e,n,o,a,r,i,c,u,p){var s=function(t,e){if("string"==typeof e)return 0!==t.indexOf(e)?null:e;var n=t.match(e);return n?n[0]:null},_=s(t,e);if(null===_)return null;if(t=t.substr(_.length),null===(_=s(t,n)))return null;var d=function(t,e,n){for(var o=0;e":{"0|1|2|3":{action_:"r=",nextState:"r"},"a|as":{action_:["output","r="],nextState:"r"},"*":{action_:["output","r="],nextState:"r"}},"+":{o:{action_:"d= kv",nextState:"d"},"d|D":{action_:"d=",nextState:"d"},q:{action_:"d=",nextState:"qd"},"qd|qD":{action_:"d=",nextState:"qd"},dq:{action_:["output","d="],nextState:"d"},3:{action_:["sb=false","output","operator"],nextState:"0"}},amount:{"0|2":{action_:"a=",nextState:"a"}},"pm-operator":{"0|1|2|a|as":{action_:["sb=false","output",{type_:"operator",option:"\\pm"}],nextState:"0"}},operator:{"0|1|2|a|as":{action_:["sb=false","output","operator"],nextState:"0"}},"-$":{"o|q":{action_:["charge or bond","output"],nextState:"qd"},d:{action_:"d=",nextState:"d"},D:{action_:["output",{type_:"bond",option:"-"}],nextState:"3"},q:{action_:"d=",nextState:"qd"},qd:{action_:"d=",nextState:"qd"},"qD|dq":{action_:["output",{type_:"bond",option:"-"}],nextState:"3"}},"-9":{"3|o":{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"3"}},"- orbital overlap":{o:{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"2"},d:{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"2"}},"-":{"0|1|2":{action_:[{type_:"output",option:1},"beginsWithBond=true",{type_:"bond",option:"-"}],nextState:"3"},3:{action_:{type_:"bond",option:"-"}},a:{action_:["output",{type_:"insert",option:"hyphen"}],nextState:"2"},as:{action_:[{type_:"output",option:2},{type_:"bond",option:"-"}],nextState:"3"},b:{action_:"b="},o:{action_:{type_:"- after o/d",option:!1},nextState:"2"},q:{action_:{type_:"- after o/d",option:!1},nextState:"2"},"d|qd|dq":{action_:{type_:"- after o/d",option:!0},nextState:"2"},"D|qD|p":{action_:["output",{type_:"bond",option:"-"}],nextState:"3"}},amount2:{"1|3":{action_:"a=",nextState:"a"}},letters:{"0|1|2|3|a|as|b|p|bp|o":{action_:"o=",nextState:"o"},"q|dq":{action_:["output","o="],nextState:"o"},"d|D|qd|qD":{action_:"o after d",nextState:"o"}},digits:{o:{action_:"q=",nextState:"q"},"d|D":{action_:"q=",nextState:"dq"},q:{action_:["output","o="],nextState:"o"},a:{action_:"o=",nextState:"o"}},"space A":{"b|p|bp":{}},space:{a:{nextState:"as"},0:{action_:"sb=false"},"1|2":{action_:"sb=true"},"r|rt|rd|rdt|rdq":{action_:"output",nextState:"0"},"*":{action_:["output","sb=true"],nextState:"1"}},"1st-level escape":{"1|2":{action_:["output",{type_:"insert+p1",option:"1st-level escape"}]},"*":{action_:["output",{type_:"insert+p1",option:"1st-level escape"}],nextState:"0"}},"[(...)]":{"r|rt":{action_:"rd=",nextState:"rd"},"rd|rdt":{action_:"rq=",nextState:"rdq"}},"...":{"o|d|D|dq|qd|qD":{action_:["output",{type_:"bond",option:"..."}],nextState:"3"},"*":{action_:[{type_:"output",option:1},{type_:"insert",option:"ellipsis"}],nextState:"1"}},". |* ":{"*":{action_:["output",{type_:"insert",option:"addition compound"}],nextState:"1"}},"state of aggregation $":{"*":{action_:["output","state of aggregation"],nextState:"1"}},"{[(":{"a|as|o":{action_:["o=","output","parenthesisLevel++"],nextState:"2"},"0|1|2|3":{action_:["o=","output","parenthesisLevel++"],nextState:"2"},"*":{action_:["output","o=","output","parenthesisLevel++"],nextState:"2"}},")]}":{"0|1|2|3|b|p|bp|o":{action_:["o=","parenthesisLevel--"],nextState:"o"},"a|as|d|D|q|qd|qD|dq":{action_:["output","o=","parenthesisLevel--"],nextState:"o"}},", ":{"*":{action_:["output","comma"],nextState:"0"}},"^_":{"*":{}},"^{(...)}|^($...$)":{"0|1|2|as":{action_:"b=",nextState:"b"},p:{action_:"b=",nextState:"bp"},"3|o":{action_:"d= kv",nextState:"D"},q:{action_:"d=",nextState:"qD"},"d|D|qd|qD|dq":{action_:["output","d="],nextState:"D"}},"^a|^\\x{}{}|^\\x{}|^\\x|'":{"0|1|2|as":{action_:"b=",nextState:"b"},p:{action_:"b=",nextState:"bp"},"3|o":{action_:"d= kv",nextState:"d"},q:{action_:"d=",nextState:"qd"},"d|qd|D|qD":{action_:"d="},dq:{action_:["output","d="],nextState:"d"}},"_{(state of aggregation)}$":{"d|D|q|qd|qD|dq":{action_:["output","q="],nextState:"q"}},"_{(...)}|_($...$)|_9|_\\x{}{}|_\\x{}|_\\x":{"0|1|2|as":{action_:"p=",nextState:"p"},b:{action_:"p=",nextState:"bp"},"3|o":{action_:"q=",nextState:"q"},"d|D":{action_:"q=",nextState:"dq"},"q|qd|qD|dq":{action_:["output","q="],nextState:"q"}},"=<>":{"0|1|2|3|a|as|o|q|d|D|qd|qD|dq":{action_:[{type_:"output",option:2},"bond"],nextState:"3"}},"#":{"0|1|2|3|a|as|o":{action_:[{type_:"output",option:2},{type_:"bond",option:"#"}],nextState:"3"}},"{}":{"*":{action_:{type_:"output",option:1},nextState:"1"}},"{...}":{"0|1|2|3|a|as|b|p|bp":{action_:"o=",nextState:"o"},"o|d|D|q|qd|qD|dq":{action_:["output","o="],nextState:"o"}},"$...$":{a:{action_:"a="},"0|1|2|3|as|b|p|bp|o":{action_:"o=",nextState:"o"},"as|o":{action_:"o="},"q|d|D|qd|qD|dq":{action_:["output","o="],nextState:"o"}},"\\bond{(...)}":{"*":{action_:[{type_:"output",option:2},"bond"],nextState:"3"}},"\\frac{(...)}":{"*":{action_:[{type_:"output",option:1},"frac-output"],nextState:"3"}},"\\overset{(...)}":{"*":{action_:[{type_:"output",option:2},"overset-output"],nextState:"3"}},"\\underset{(...)}":{"*":{action_:[{type_:"output",option:2},"underset-output"],nextState:"3"}},"\\underbrace{(...)}":{"*":{action_:[{type_:"output",option:2},"underbrace-output"],nextState:"3"}},"\\color{(...)}{(...)}1|\\color(...){(...)}2":{"*":{action_:[{type_:"output",option:2},"color-output"],nextState:"3"}},"\\color{(...)}0":{"*":{action_:[{type_:"output",option:2},"color0-output"]}},"\\ce{(...)}":{"*":{action_:[{type_:"output",option:2},"ce"],nextState:"3"}},"\\,":{"*":{action_:[{type_:"output",option:1},"copy"],nextState:"1"}},"\\x{}{}|\\x{}|\\x":{"0|1|2|3|a|as|b|p|bp|o|c0":{action_:["o=","output"],nextState:"3"},"*":{action_:["output","o=","output"],nextState:"3"}},others:{"*":{action_:[{type_:"output",option:1},"copy"],nextState:"3"}},else2:{a:{action_:"a to o",nextState:"o",revisit:!0},as:{action_:["output","sb=true"],nextState:"1",revisit:!0},"r|rt|rd|rdt|rdq":{action_:["output"],nextState:"0",revisit:!0},"*":{action_:["output","copy"],nextState:"3"}}}),actions:{"o after d":function(t,e){var n;if((t.d||"").match(/^[0-9]+$/)){var o=t.d;t.d=void 0,n=this.output(t),t.b=o}else n=this.output(t);return a.actions["o="](t,e),n},"d= kv":function(t,e){t.d=e,t.dType="kv"},"charge or bond":function(t,e){if(t.beginsWithBond){var n=[];return a.concatArray(n,this.output(t)),a.concatArray(n,a.actions.bond(t,e,"-")),n}t.d=e},"- after o/d":function(t,e,n){var o=a.patterns.match_("orbital",t.o||""),r=a.patterns.match_("one lowercase greek letter $",t.o||""),i=a.patterns.match_("one lowercase latin letter $",t.o||""),c=a.patterns.match_("$one lowercase latin letter$ $",t.o||""),u="-"===e&&(o&&""===o.remainder||r||i||c);!u||t.a||t.b||t.p||t.d||t.q||o||!i||(t.o="$"+t.o+"$");var p=[];return u?(a.concatArray(p,this.output(t)),p.push({type_:"hyphen"})):(o=a.patterns.match_("digits",t.d||""),n&&o&&""===o.remainder?(a.concatArray(p,a.actions["d="](t,e)),a.concatArray(p,this.output(t))):(a.concatArray(p,this.output(t)),a.concatArray(p,a.actions.bond(t,e,"-")))),p},"a to o":function(t){t.o=t.a,t.a=void 0},"sb=true":function(t){t.sb=!0},"sb=false":function(t){t.sb=!1},"beginsWithBond=true":function(t){t.beginsWithBond=!0},"beginsWithBond=false":function(t){t.beginsWithBond=!1},"parenthesisLevel++":function(t){t.parenthesisLevel++},"parenthesisLevel--":function(t){t.parenthesisLevel--},"state of aggregation":function(t,e){return{type_:"state of aggregation",p1:a.go(e,"o")}},comma:function(t,e){var n=e.replace(/\s*$/,"");return n!==e&&0===t.parenthesisLevel?{type_:"comma enumeration L",p1:n}:{type_:"comma enumeration M",p1:n}},output:function(t,e,n){var o,r,i;t.r?(r="M"===t.rdt?a.go(t.rd,"tex-math"):"T"===t.rdt?[{type_:"text",p1:t.rd||""}]:a.go(t.rd),i="M"===t.rqt?a.go(t.rq,"tex-math"):"T"===t.rqt?[{type_:"text",p1:t.rq||""}]:a.go(t.rq),o={type_:"arrow",r:t.r,rd:r,rq:i}):(o=[],(t.a||t.b||t.p||t.o||t.q||t.d||n)&&(t.sb&&o.push({type_:"entitySkip"}),t.o||t.q||t.d||t.b||t.p||2===n?t.o||t.q||t.d||!t.b&&!t.p?t.o&&"kv"===t.dType&&a.patterns.match_("d-oxidation$",t.d||"")?t.dType="oxidation":t.o&&"kv"===t.dType&&!t.q&&(t.dType=void 0):(t.o=t.a,t.d=t.b,t.q=t.p,t.a=t.b=t.p=void 0):(t.o=t.a,t.a=void 0),o.push({type_:"chemfive",a:a.go(t.a,"a"),b:a.go(t.b,"bd"),p:a.go(t.p,"pq"),o:a.go(t.o,"o"),q:a.go(t.q,"pq"),d:a.go(t.d,"oxidation"===t.dType?"oxidation":"bd"),dType:t.dType})));for(var c in t)"parenthesisLevel"!==c&&"beginsWithBond"!==c&&delete t[c];return o},"oxidation-output":function(t,e){var n=["{"];return a.concatArray(n,a.go(e,"oxidation")),n.push("}"),n},"frac-output":function(t,e){return{type_:"frac-ce",p1:a.go(e[0]),p2:a.go(e[1])}},"overset-output":function(t,e){return{type_:"overset",p1:a.go(e[0]),p2:a.go(e[1])}},"underset-output":function(t,e){return{type_:"underset",p1:a.go(e[0]),p2:a.go(e[1])}},"underbrace-output":function(t,e){return{type_:"underbrace",p1:a.go(e[0]),p2:a.go(e[1])}},"color-output":function(t,e){return{type_:"color",color1:e[0],color2:a.go(e[1])}},"r=":function(t,e){t.r=e},"rdt=":function(t,e){t.rdt=e},"rd=":function(t,e){t.rd=e},"rqt=":function(t,e){t.rqt=e},"rq=":function(t,e){t.rq=e},operator:function(t,e,n){return{type_:"operator",kind_:n||e}}}},a:{transitions:a.createTransitions({empty:{"*":{}},"1/2$":{0:{action_:"1/2"}},else:{0:{nextState:"1",revisit:!0}},"$(...)$":{"*":{action_:"tex-math tight",nextState:"1"}},",":{"*":{action_:{type_:"insert",option:"commaDecimal"}}},else2:{"*":{action_:"copy"}}}),actions:{}},o:{transitions:a.createTransitions({empty:{"*":{}},"1/2$":{0:{action_:"1/2"}},else:{0:{nextState:"1",revisit:!0}},letters:{"*":{action_:"rm"}},"\\ca":{"*":{action_:{type_:"insert",option:"circa"}}},"\\x{}{}|\\x{}|\\x":{"*":{action_:"copy"}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"{(...)}":{"*":{action_:"{text}"}},else2:{"*":{action_:"copy"}}}),actions:{}},text:{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"{...}":{"*":{action_:"text="}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"\\greek":{"*":{action_:["output","rm"]}},"\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:["output","copy"]}},else:{"*":{action_:"text="}}}),actions:{output:function(t){if(t.text_){var e={type_:"text",p1:t.text_};for(var n in t)delete t[n];return e}}}},pq:{transitions:a.createTransitions({empty:{"*":{}},"state of aggregation $":{"*":{action_:"state of aggregation"}},i$:{0:{nextState:"!f",revisit:!0}},"(KV letters),":{0:{action_:"rm",nextState:"0"}},formula$:{0:{nextState:"f",revisit:!0}},"1/2$":{0:{action_:"1/2"}},else:{0:{nextState:"!f",revisit:!0}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"{(...)}":{"*":{action_:"text"}},"a-z":{f:{action_:"tex-math"}},letters:{"*":{action_:"rm"}},"-9.,9":{"*":{action_:"9,9"}},",":{"*":{action_:{type_:"insert+p1",option:"comma enumeration S"}}},"\\color{(...)}{(...)}1|\\color(...){(...)}2":{"*":{action_:"color-output"}},"\\color{(...)}0":{"*":{action_:"color0-output"}},"\\ce{(...)}":{"*":{action_:"ce"}},"\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"copy"}},else2:{"*":{action_:"copy"}}}),actions:{"state of aggregation":function(t,e){return{type_:"state of aggregation subscript",p1:a.go(e,"o")}},"color-output":function(t,e){return{type_:"color",color1:e[0],color2:a.go(e[1],"pq")}}}},bd:{transitions:a.createTransitions({empty:{"*":{}},x$:{0:{nextState:"!f",revisit:!0}},formula$:{0:{nextState:"f",revisit:!0}},else:{0:{nextState:"!f",revisit:!0}},"-9.,9 no missing 0":{"*":{action_:"9,9"}},".":{"*":{action_:{type_:"insert",option:"electron dot"}}},"a-z":{f:{action_:"tex-math"}},x:{"*":{action_:{type_:"insert",option:"KV x"}}},letters:{"*":{action_:"rm"}},"'":{"*":{action_:{type_:"insert",option:"prime"}}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},"{(...)}":{"*":{action_:"text"}},"\\color{(...)}{(...)}1|\\color(...){(...)}2":{"*":{action_:"color-output"}},"\\color{(...)}0":{"*":{action_:"color0-output"}},"\\ce{(...)}":{"*":{action_:"ce"}},"\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"copy"}},else2:{"*":{action_:"copy"}}}),actions:{"color-output":function(t,e){return{type_:"color",color1:e[0],color2:a.go(e[1],"bd")}}}},oxidation:{transitions:a.createTransitions({empty:{"*":{}},"roman numeral":{"*":{action_:"roman-numeral"}},"${(...)}$|$(...)$":{"*":{action_:"tex-math"}},else:{"*":{action_:"copy"}}}),actions:{"roman-numeral":function(t,e){return{type_:"roman numeral",p1:e||""}}}},"tex-math":{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"\\ce{(...)}":{"*":{action_:["output","ce"]}},"{...}|\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"o="}},else:{"*":{action_:"o="}}}),actions:{output:function(t){if(t.o){var e={type_:"tex-math",p1:t.o};for(var n in t)delete t[n];return e}}}},"tex-math tight":{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"\\ce{(...)}":{"*":{action_:["output","ce"]}},"{...}|\\,|\\x{}{}|\\x{}|\\x":{"*":{action_:"o="}},"-|+":{"*":{action_:"tight operator"}},else:{"*":{action_:"o="}}}),actions:{"tight operator":function(t,e){t.o=(t.o||"")+"{"+e+"}"},output:function(t){if(t.o){var e={type_:"tex-math",p1:t.o};for(var n in t)delete t[n];return e}}}},"9,9":{transitions:a.createTransitions({empty:{"*":{}},",":{"*":{action_:"comma"}},else:{"*":{action_:"copy"}}}),actions:{comma:function(){return{type_:"commaDecimal"}}}},pu:{transitions:a.createTransitions({empty:{"*":{action_:"output"}},space$:{"*":{action_:["output","space"]}},"{[(|)]}":{"0|a":{action_:"copy"}},"(-)(9)^(-9)":{0:{action_:"number^",nextState:"a"}},"(-)(9.,9)(e)(99)":{0:{action_:"enumber",nextState:"a"}},space:{"0|a":{}},"pm-operator":{"0|a":{action_:{type_:"operator",option:"\\pm"},nextState:"0"}},operator:{"0|a":{action_:"copy",nextState:"0"}},"//":{d:{action_:"o=",nextState:"/"}},"/":{d:{action_:"o=",nextState:"/"}},"{...}|else":{"0|d":{action_:"d=",nextState:"d"},a:{action_:["space","d="],nextState:"d"},"/|q":{action_:"q=",nextState:"q"}}}),actions:{enumber:function(t,e){var n=[];return"+-"===e[0]||"+/-"===e[0]?n.push("\\pm "):e[0]&&n.push(e[0]),e[1]&&(a.concatArray(n,a.go(e[1],"pu-9,9")),e[2]&&(e[2].match(/[,.]/)?a.concatArray(n,a.go(e[2],"pu-9,9")):n.push(e[2])),e[3]=e[4]||e[3],e[3]&&(e[3]=e[3].trim(),"e"===e[3]||"*"===e[3].substr(0,1)?n.push({type_:"cdot"}):n.push({type_:"times"}))),e[3]&&n.push("10^{"+e[5]+"}"),n},"number^":function(t,e){var n=[];return"+-"===e[0]||"+/-"===e[0]?n.push("\\pm "):e[0]&&n.push(e[0]),a.concatArray(n,a.go(e[1],"pu-9,9")),n.push("^{"+e[2]+"}"),n},operator:function(t,e,n){return{type_:"operator",kind_:n||e}},space:function(){return{type_:"pu-space-1"}},output:function(t){var e,n=a.patterns.match_("{(...)}",t.d||"");n&&""===n.remainder&&(t.d=n.match_);var o=a.patterns.match_("{(...)}",t.q||"");if(o&&""===o.remainder&&(t.q=o.match_),t.d&&(t.d=t.d.replace(/\u00B0C|\^oC|\^{o}C/g,"{}^{\\circ}C"),t.d=t.d.replace(/\u00B0F|\^oF|\^{o}F/g,"{}^{\\circ}F")),t.q){t.q=t.q.replace(/\u00B0C|\^oC|\^{o}C/g,"{}^{\\circ}C"),t.q=t.q.replace(/\u00B0F|\^oF|\^{o}F/g,"{}^{\\circ}F");var r={d:a.go(t.d,"pu"),q:a.go(t.q,"pu")};"//"===t.o?e={type_:"pu-frac",p1:r.d,p2:r.q}:(e=r.d,r.d.length>1||r.q.length>1?e.push({type_:" / "}):e.push({type_:"/"}),a.concatArray(e,r.q))}else e=a.go(t.d,"pu-2");for(var i in t)delete t[i];return e}}},"pu-2":{transitions:a.createTransitions({empty:{"*":{action_:"output"}},"*":{"*":{action_:["output","cdot"],nextState:"0"}},"\\x":{"*":{action_:"rm="}},space:{"*":{action_:["output","space"],nextState:"0"}},"^{(...)}|^(-1)":{1:{action_:"^(-1)"}},"-9.,9":{0:{action_:"rm=",nextState:"0"},1:{action_:"^(-1)",nextState:"0"}},"{...}|else":{"*":{action_:"rm=",nextState:"1"}}}),actions:{cdot:function(){return{type_:"tight cdot"}},"^(-1)":function(t,e){t.rm+="^{"+e+"}"},space:function(){return{type_:"pu-space-2"}},output:function(t){var e=[];if(t.rm){var n=a.patterns.match_("{(...)}",t.rm||"");e=n&&""===n.remainder?a.go(n.match_,"pu"):{type_:"rm",p1:t.rm}}for(var o in t)delete t[o];return e}}},"pu-9,9":{transitions:a.createTransitions({empty:{0:{action_:"output-0"},o:{action_:"output-o"}},",":{0:{action_:["output-0","comma"],nextState:"o"}},".":{0:{action_:["output-0","copy"],nextState:"o"}},else:{"*":{action_:"text="}}}),actions:{comma:function(){return{type_:"commaDecimal"}},"output-0":function(t){var e=[];if(t.text_=t.text_||"",t.text_.length>4){var n=t.text_.length%3;0===n&&(n=3);for(var o=t.text_.length-3;o>0;o-=3)e.push(t.text_.substr(o,3)),e.push({type_:"1000 separator"});e.push(t.text_.substr(0,n)),e.reverse()}else e.push(t.text_);for(var a in t)delete t[a];return e},"output-o":function(t){var e=[];if(t.text_=t.text_||"",t.text_.length>4){for(var n=t.text_.length-3,o=0;o":case"\u2192":case"\u27f6":return"rightarrow";case"<-":return"leftarrow";case"<->":return"leftrightarrow";case"<--\x3e":return"rightleftarrows";case"<=>":case"\u21cc":return"rightleftharpoons";case"<=>>":return"rightequilibrium";case"<<=>":return"leftequilibrium";default:throw["MhchemBugT","mhchem bug T. Please report."]}},_getBond:function(t){switch(t){case"-":case"1":return"{-}";case"=":case"2":return"{=}";case"#":case"3":return"{\\equiv}";case"~":return"{\\tripledash}";case"~-":return"{\\mathrlap{\\raisebox{-.1em}{$-$}}\\raisebox{.1em}{$\\tripledash$}}";case"~=":case"~--":return"{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$\\tripledash$}}-}";case"-~-":return"{\\mathrlap{\\raisebox{-.2em}{$-$}}\\mathrlap{\\raisebox{.2em}{$-$}}\\tripledash}";case"...":return"{{\\cdot}{\\cdot}{\\cdot}}";case"....":return"{{\\cdot}{\\cdot}{\\cdot}{\\cdot}}";case"->":return"{\\rightarrow}";case"<-":return"{\\leftarrow}";case"<":return"{<}";case">":return"{>}";default:throw["MhchemBugT","mhchem bug T. Please report."]}},_getOperator:function(t){switch(t){case"+":return" {}+{} ";case"-":return" {}-{} ";case"=":return" {}={} ";case"<":return" {}<{} ";case">":return" {}>{} ";case"<<":return" {}\\ll{} ";case">>":return" {}\\gg{} ";case"\\pm":return" {}\\pm{} ";case"\\approx":case"$\\approx$":return" {}\\approx{} ";case"v":case"(v)":return" \\downarrow{} ";case"^":case"(^)":return" \\uparrow{} ";default:throw["MhchemBugT","mhchem bug T. Please report."]}}}}(),a=a.default}()}));
\ No newline at end of file
diff --git a/lib/lightgallery/fonts/lg.svg b/lib/lightgallery/fonts/lg.svg
new file mode 100644
index 00000000..fe8b0756
--- /dev/null
+++ b/lib/lightgallery/fonts/lg.svg
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/lightgallery/fonts/lg.ttf b/lib/lightgallery/fonts/lg.ttf
new file mode 100644
index 00000000..825f4832
Binary files /dev/null and b/lib/lightgallery/fonts/lg.ttf differ
diff --git a/lib/lightgallery/fonts/lg.woff b/lib/lightgallery/fonts/lg.woff
new file mode 100644
index 00000000..fd02a6f4
Binary files /dev/null and b/lib/lightgallery/fonts/lg.woff differ
diff --git a/lib/lightgallery/fonts/lg.woff2 b/lib/lightgallery/fonts/lg.woff2
new file mode 100644
index 00000000..2c2e2892
Binary files /dev/null and b/lib/lightgallery/fonts/lg.woff2 differ
diff --git a/lib/lightgallery/images/loading.gif b/lib/lightgallery/images/loading.gif
new file mode 100644
index 00000000..4744c455
Binary files /dev/null and b/lib/lightgallery/images/loading.gif differ
diff --git a/lib/sharer/sharer.min.js b/lib/sharer/sharer.min.js
new file mode 100644
index 00000000..953a4517
--- /dev/null
+++ b/lib/sharer/sharer.min.js
@@ -0,0 +1 @@
+(function(m,r){"use strict";var s=function(t){this.elem=t};s.init=function(){var t=r.querySelectorAll("[data-sharer]"),e,a=t.length;for(e=0;e0?"?":"";for(r=0;r Array.isArray(e),t=t=>e(t)?t:[t];const s="data-typeit-id",i="ti-cursor",r={started:!1,completed:!1,frozen:!1,destroyed:!1},n={breakLines:!0,cursor:{autoPause:!0,autoPauseDelay:500,animation:{frames:[0,0,1].map((e=>({opacity:e}))),options:{iterations:1/0,easing:"steps(2, start)",fill:"forwards"}}},cursorChar:"|",cursorSpeed:1e3,deleteSpeed:null,html:!0,lifeLike:!0,loop:!1,loopDelay:750,nextStringDelay:750,speed:100,startDelay:250,startDelete:!1,strings:[],waitUntilVisible:!1,beforeString:()=>{},afterString:()=>{},beforeStep:()=>{},afterStep:()=>{},afterComplete:()=>{}},o=`[${s}]:before {content: '.'; display: inline-block; width: 0; visibility: hidden;}`,a=e=>document.createElement(e),u=e=>document.createTextNode(e),l=(e,t="")=>{let s=a("style");s.id=t,s.appendChild(u(e)),document.head.appendChild(s)},h=t=>(e(t)||(t=[t/2,t/2]),t),d=(e,t)=>Math.abs(Math.random()*(e+t-(e-t))+(e-t));let p=e=>e/2;const c=e=>Array.from(e);let m=e=>([...e.childNodes].forEach((e=>{if(e.nodeValue)return[...e.nodeValue].forEach((t=>{e.parentNode.insertBefore(u(t),e)})),void e.remove();m(e)})),e);const f=e=>{let t=document.implementation.createHTMLDocument();return t.body.innerHTML=e,m(t.body)};function y(e,t=!1,s=!1){let r,n=e.querySelector(`.${i}`),o=document.createTreeWalker(e,NodeFilter.SHOW_ALL,{acceptNode:e=>{if(n&&s){if(e.classList?.contains(i))return NodeFilter.FILTER_ACCEPT;if(n.contains(e))return NodeFilter.FILTER_REJECT}return e.classList?.contains(i)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}),a=[];for(;r=o.nextNode();)r.originalParent||(r.originalParent=r.parentNode),a.push(r);return t?a.reverse():a}function g(e,t=!0){return t?y(f(e)):c(e).map(u)}const b=({index:e,newIndex:t,queueItems:s,cleanUp:i})=>{for(let r=e+1;rNumber.isInteger(e),C=({queueItems:e,selector:t,cursorPosition:s,to:i})=>{if(P(t))return-1*t;let r=new RegExp("END","i").test(i),n=t?[...e].reverse().findIndex((({char:e})=>{let s=e.parentElement,i=s.matches(t);return!(!r||!i)||i&&s.firstChild.isSameNode(e)})):-1;return n<0&&(n=r?0:e.length-1),n-s+(r?0:1)},I=(e,t)=>new Array(t).fill(e);let v=e=>new Promise((t=>{requestAnimationFrame((async()=>{t(await e())}))})),w=e=>e?.getAnimations().find((t=>t.id===e.dataset.tiAnimationId)),T=({cursor:e,frames:t,options:s})=>{let i=e.animate(t,s);return i.pause(),i.id=e.dataset.tiAnimationId,v((()=>{v((()=>{i.play()}))})),i},q=e=>e.func?.call(null),S=async({index:e,queueItems:t,wait:s,cursor:i,cursorOptions:r})=>{let n=t[e][1],o=[],a=e,u=n,l=()=>u&&!u.delay,h=n.shouldPauseCursor()&&r.autoPause;for(;l();)o.push(u),l()&&a++,u=t[a]?t[a][1]:null;if(o.length)return await v((async()=>{for(let e of o)await q(e)})),a-1;let d,p=w(i);return p&&(d={...p.effect.getComputedTiming(),delay:h?r.autoPauseDelay:0}),await s((async()=>{p&&h&&p.cancel(),await v((()=>{q(n)}))}),n.delay),await(({cursor:e,options:t,cursorOptions:s})=>{if(!e||!s)return;let i,r=w(e);r&&(t.delay=r.effect.getComputedTiming().delay,i=r.currentTime,r.cancel());let n=T({cursor:e,frames:s.animation.frames,options:t});return i&&(n.currentTime=i),n})({cursor:i,options:d,cursorOptions:r}),e};const N=e=>"value"in e;let A=e=>"function"==typeof e?e():e,E=(e,t=document,s=!1)=>t["querySelector"+(s?"All":"")](e);const L=(e,t)=>Object.assign({},e,t);let x={"font-family":"","font-weight":"","font-size":"","font-style":"","line-height":"",color:"",transform:"translateX(-.125em)"};return class{element;timeouts;cursorPosition;predictedCursorPosition;statuses={started:!1,completed:!1,frozen:!1,destroyed:!1};opts;id;queue;cursor;unfreeze=()=>{};constructor(e,s={}){var i;this.opts=L(n,s),this.element="string"==typeof(i=e)?E(i):i,this.timeouts=[],this.cursorPosition=0,this.unfreeze=()=>{},this.predictedCursorPosition=null,this.statuses=L({},r),this.id=Math.random().toString().substring(2,9),this.queue=function(e){let s=function(e){return t(e).forEach((e=>n.set(Symbol(e.char?.innerText),i({...e})))),this},i=e=>(e.shouldPauseCursor=function(){return Boolean(this.typeable||this.cursorable||this.deletable)},e),r=()=>Array.from(n.values()),n=new Map;return s(e),{add:s,set:function(e,t){let s=[...n.keys()];n.set(s[e],i(t))},wipe:function(){n=new Map,s(e)},done:(e,t=!1)=>t?n.delete(e):n.get(e).done=!0,reset:function(){n.forEach((e=>delete e.done))},destroy:e=>n.delete(e),getItems:(e=!1)=>e?r():r().filter((e=>!e.done)),getQueue:()=>n,getTypeable:()=>r().filter((e=>e.typeable))}}([{delay:this.opts.startDelay}]),this.#e(s),this.cursor=this.#t(),this.element.dataset.typeitId=this.id,l(o),this.opts.strings.length&&this.#s()}go(){return this.statuses.started?this:(this.#i(),this.opts.waitUntilVisible?(e=this.element,t=this.#r.bind(this),new IntersectionObserver(((s,i)=>{s.forEach((s=>{s.isIntersecting&&(t(),i.unobserve(e))}))}),{threshold:1}).observe(e),this):(this.#r(),this));var e,t}destroy(e=!0){this.timeouts=(this.timeouts.forEach(clearTimeout),[]),A(e)&&this.cursor&&this.#n(this.cursor),this.statuses.destroyed=!0}reset(e){!this.is("destroyed")&&this.destroy(),e?(this.queue.wipe(),e(this)):this.queue.reset(),this.cursorPosition=0;for(let t in this.statuses)this.statuses[t]=!1;return this.element[this.#o()?"value":"innerHTML"]="",this}is=function(e){return this.statuses[e]};type(e,t={}){e=A(e);let{instant:s}=t,i=this.#a(t),r=g(e,this.opts.html).map((e=>{return{func:()=>this.#u(e),char:e,delay:s||(t=e,/<(.+)>(.*?)<\/(.+)>/.test(t.outerHTML))?0:this.#l(),typeable:e.nodeType===Node.TEXT_NODE};var t})),n=[i[0],{func:async()=>await this.opts.beforeString(e,this)},...r,{func:async()=>await this.opts.afterString(e,this)},i[1]];return this.#h(n,t)}break(e={}){return this.#h({func:()=>this.#u(a("BR")),typeable:!0},e)}move(e,t={}){e=A(e);let s=this.#a(t),{instant:i,to:r}=t,n=C({queueItems:this.queue.getTypeable(),selector:null===e?"":e,to:r,cursorPosition:this.#d}),o=n<0?-1:1;return this.predictedCursorPosition=this.#d+n,this.#h([s[0],...I({func:()=>this.#p(o),delay:i?0:this.#l(),cursorable:!0},Math.abs(n)),s[1]],t)}exec(e,t={}){let s=this.#a(t);return this.#h([s[0],{func:()=>e(this)},s[1]],t)}options(e,t={}){return e=A(e),this.#c(e),this.#h({},t)}pause(e,t={}){return this.#h({delay:A(e)},t)}delete(e=null,t={}){e=A(e);let s=this.#a(t),i=e,{instant:r,to:n}=t,o=this.queue.getTypeable(),a=(()=>null===i?o.length:P(i)?i:C({queueItems:o,selector:i,cursorPosition:this.#d,to:n}))();return this.#h([s[0],...I({func:this.#m.bind(this),delay:r?0:this.#l(1),deletable:!0},a),s[1]],t)}freeze(){this.statuses.frozen=!0}flush(e=()=>{}){return this.#i(),this.#r(!1).then(e),this}getQueue(){return this.queue}getOptions(){return this.opts}updateOptions(e){return this.#c(e)}getElement(){return this.element}empty(e={}){return this.#h({func:this.#f.bind(this)},e)}async#f(){this.#o()?this.element.value="":this.#y.forEach(this.#n.bind(this))}async#r(e=!0){this.statuses.started=!0;let t=t=>{this.queue.done(t,!e)};try{let s=[...this.queue.getQueue()];for(let e=0;e{await this.#P(i[0]),this.#r()}),i[1])}catch(s){}return this}async#p(e){var t,s,r;this.cursorPosition=(t=e,s=this.cursorPosition,r=this.#y,Math.min(Math.max(s+t,0),r.length)),((e,t,s)=>{let r=t[s-1],n=E(`.${i}`,e);(e=r?.parentNode||e).insertBefore(n,r||null)})(this.element,this.#y,this.cursorPosition)}async#P(e){let t=this.#d;t&&await this.#p({value:t});let s=this.#y.map((e=>[Symbol(),{func:this.#m.bind(this),delay:this.#l(1),deletable:!0,shouldPauseCursor:()=>!0}]));for(let i=0;i{this.unfreeze=()=>{this.statuses.frozen=!1,e()}})),s||await this.opts.beforeStep(this),await((e,t,s)=>new Promise((i=>{s.push(setTimeout((async()=>{await e(),i()}),t||0))})))(e,t,this.timeouts),s||await this.opts.afterStep(this)}async#i(){if(!this.#o()&&this.cursor&&this.element.appendChild(this.cursor),this.#C){((e,t)=>{let r=`[${s}='${e}'] .${i}`,n=getComputedStyle(t),o=Object.entries(x).reduce(((e,[t,s])=>`${e} ${t}: var(--ti-cursor-${t}, ${s||n[t]});`),"");l(`${r} { display: inline-block; width: 0; ${o} }`,e)})(this.id,this.element),this.cursor.dataset.tiAnimationId=this.id;let{animation:e}=this.opts.cursor,{frames:t,options:r}=e;T({frames:t,cursor:this.cursor,options:{duration:this.opts.cursorSpeed,...r}})}}#o(){return N(this.element)}#h(e,t){return this.queue.add(e),this.#I(t),this}#I(e={}){let t=e.delay;t&&this.queue.add({delay:t})}#a(e={}){return[{func:()=>this.#c(e)},{func:()=>this.#c(this.opts)}]}async#c(e){this.opts=L(this.opts,e)}#s(){let e=this.opts.strings.filter((e=>!!e));e.forEach(((t,s)=>{if(this.type(t),s+1===e.length)return;let i=this.opts.breakLines?[{func:()=>this.#u(a("BR")),typeable:!0}]:I({func:this.#m.bind(this),delay:this.#l(1)},this.queue.getTypeable().length);this.#v(i)}))}#e=e=>{e.cursor=(e=>{if("object"==typeof e){let t={},{frames:s,options:i}=n.cursor.animation;return t.animation=e.animation||{},t.animation.frames=e.animation?.frames||s,t.animation.options=L(i,e.animation?.options||{}),t.autoPause=e.autoPause??n.cursor.autoPause,t.autoPauseDelay=e.autoPauseDelay||n.cursor.autoPauseDelay,t}return!0===e?n.cursor:e})(e.cursor??n.cursor),this.opts.strings=this.#w(t(this.opts.strings)),this.opts=L(this.opts,{html:!this.#T&&this.opts.html,nextStringDelay:h(this.opts.nextStringDelay),loopDelay:h(this.opts.loopDelay)})};#w(e){let t=this.element.innerHTML;return t?(this.element.innerHTML="",this.opts.startDelete?(this.element.innerHTML=t,m(this.element),this.#v(I({func:this.#m.bind(this),delay:this.#l(1),deletable:!0},this.#y.length)),e):(s=t,s.replace(//g,"").trim().split(/ /)).concat(e)):e;var s}#t(){if(this.#T)return null;let e=a("span");return e.className=i,this.#C?(e.innerHTML=f(this.opts.cursorChar).innerHTML,e):(e.style.visibility="hidden",e)}#v(e){let t=this.opts.nextStringDelay;this.queue.add([{delay:t[0]},...e,{delay:t[1]}])}#u(e){((e,t)=>{if(N(e))return void(e.value=`${e.value}${t.textContent}`);t.innerHTML="";let s=(r=t.originalParent,/body/i.test(r?.tagName)?e:t.originalParent||e);var r;s.insertBefore(t,E("."+i,s)||null)})(this.element,e)}#m(){this.#y.length&&(this.#T?this.element.value=this.element.value.slice(0,-1):this.#n(this.#y[this.cursorPosition]))}#n(e){((e,t)=>{if(!e)return;let s=e.parentNode;(s.childNodes.length>1||s.isSameNode(t)?e:s).remove()})(e,this.element)}#l(e=0){return function(e){let{speed:t,deleteSpeed:s,lifeLike:i}=e;return s=null!==s?s:t/3,i?[d(t,p(t)),d(s,p(s))]:[t,s]}(this.opts)[e]}get#d(){return this.predictedCursorPosition??this.cursorPosition}get#T(){return N(this.element)}get#C(){return!!this.opts.cursor&&!this.#T}get#y(){return e=this.element,N(e)?c(e.value):y(e,!0).filter((e=>!(e.childNodes.length>0)));var e}}}));
diff --git a/lib/webfonts/fa-brands-400.ttf b/lib/webfonts/fa-brands-400.ttf
new file mode 100644
index 00000000..30f55b74
Binary files /dev/null and b/lib/webfonts/fa-brands-400.ttf differ
diff --git a/lib/webfonts/fa-brands-400.woff2 b/lib/webfonts/fa-brands-400.woff2
new file mode 100644
index 00000000..8a480d9b
Binary files /dev/null and b/lib/webfonts/fa-brands-400.woff2 differ
diff --git a/lib/webfonts/fa-regular-400.ttf b/lib/webfonts/fa-regular-400.ttf
new file mode 100644
index 00000000..c79589d8
Binary files /dev/null and b/lib/webfonts/fa-regular-400.ttf differ
diff --git a/lib/webfonts/fa-regular-400.woff2 b/lib/webfonts/fa-regular-400.woff2
new file mode 100644
index 00000000..059a94e2
Binary files /dev/null and b/lib/webfonts/fa-regular-400.woff2 differ
diff --git a/lib/webfonts/fa-solid-900.ttf b/lib/webfonts/fa-solid-900.ttf
new file mode 100644
index 00000000..e479fb29
Binary files /dev/null and b/lib/webfonts/fa-solid-900.ttf differ
diff --git a/lib/webfonts/fa-solid-900.woff2 b/lib/webfonts/fa-solid-900.woff2
new file mode 100644
index 00000000..88b0367a
Binary files /dev/null and b/lib/webfonts/fa-solid-900.woff2 differ
diff --git a/lib/webfonts/fa-v4compatibility.ttf b/lib/webfonts/fa-v4compatibility.ttf
new file mode 100644
index 00000000..ba6cb258
Binary files /dev/null and b/lib/webfonts/fa-v4compatibility.ttf differ
diff --git a/lib/webfonts/fa-v4compatibility.woff2 b/lib/webfonts/fa-v4compatibility.woff2
new file mode 100644
index 00000000..23b1c47b
Binary files /dev/null and b/lib/webfonts/fa-v4compatibility.woff2 differ
diff --git a/page/1/index.html b/page/1/index.html
new file mode 100644
index 00000000..3ad781cc
--- /dev/null
+++ b/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/
+
\ No newline at end of file
diff --git a/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.html b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.html
new file mode 100644
index 00000000..fe5c7210
--- /dev/null
+++ b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.html
@@ -0,0 +1,1243 @@
+JDK8日期时间API导致Dubbo调用StackOverflow错误 - dingyufan's blog
+2022-04-30 2023-07-28 约 10200 字
0 前言
+博客搭建之后,不知道从何写起。思来想去,决定从记录一些工作中遇到的问题开始。一来对问题有个归档,算是有一些积累,方便日后回顾;二来促使自己遇到问题时能探本朔源,做到知其然知其所以然,不要草草百度了事。不积跬步,无以至千里;不积小流,无以成江海。
1 问题
+最近项目中使用dubbo(v2.5.3 )的时候,遇到一个问题:dubbo服务方在对response编码时失败,发生了栈溢出异常。可以通过一个demo(dubbo-stack-overflow-due-to-jkd8-instant )复现这个异常。
看到栈溢出,一般想到的情况要么是递归过深,或是循环调用方法次数过多。为什么会在dubbo序列化编码的时候发生这个异常呢?通过查看源码提交记录,结合异常信息Fail to encode repsonse,并且在异常栈中出现JavaSerializer类,判断问题出在Dubbo在对Response序列化时无法处理Instant类型对象 。
网上搜索到的博客基本上只是说了有这样的问题,但是没有说明具体的原因,都是草草改用Date了事。通过这篇博客,记录一下Instant类型对象引起Dubbo对Response序列化失败导致栈溢出的根本原因以及解决方案。
3 Dubbo序列化Response
+dubbo对不同协议、不同序列化方式处理方式是不一样的。此项目使用默认协议:dubbo协议 ,同时也采用dubbo协议默认序列化方式**:hessian2**。下文的序列化过程分析就是基于此的。
1
+ 2
+
<!-- dubbo协议hessian2序列化方式,是dubbo的默认值。所以在xml配置中写不写这行配置都可以 -->
+<dubbo:protocol name= "dubbo" serialization= "hessian2" />
**参考官网文档 2.4 服务提供方返回调用结果 **,我们从Dubbo处理返回结果入手。
3.1 ExchangeCodec / DubboCodec
+ExchangeCodec是Dubbo exchange信息交换层 的编码器。ExchangeCodec#encode(Channel, ChannelBuffer, Object)
方法是Dubbo对Request和Response进行编码的入口,逻辑很简单,主要是根据消息的类型,交由对应方法进行编码,对于不是自己负责的非Request、Response类型的消息对象,交给父类处理。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+
// ExchangeCodec.java
+ public void encode ( Channel channel , ChannelBuffer buffer , Object msg ) throws IOException {
+ if ( msg instanceof Request ) {
+ encodeRequest ( channel , buffer , ( Request ) msg );
+ } else if ( msg instanceof Response ) {
+ // 对response对象进行编码
+ encodeResponse ( channel , buffer , ( Response ) msg );
+ } else {
+ super . encode ( channel , buffer , msg );
+ }
+ }
ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)
方法主要做的就是组装消息写入ChannelBuffer,这样消息就可以发送给调用方。消息两部分组成:消息头 ,包含状态序列化等相关信息的字节数组;消息体 :Response返回的具体数据转换成的字节数组。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+
// ExchangeCodec.java
+ protected void encodeResponse ( Channel channel , ChannelBuffer buffer , Response res ) throws IOException {
+ try {
+ // 获取序列化方式*
+ Serialization serialization = getSerialization ( channel );
+ // 消息头字节数组。长度16
+ byte [] header = new byte [ HEADER_LENGTH ] ;
+ // 消息头 写入魔数
+ Bytes . short2bytes ( MAGIC , header );
+ // 消息头 写入序列化器ID信息
+ header [ 2 ] = serialization . getContentTypeId ();
+ if ( res . isHeartbeat ()) header [ 2 ] |= FLAG_EVENT ;
+ byte status = res . getStatus ();
+ // 消息头 写入状态
+ header [ 3 ] = status ;
+ // 消息头 写入
+ Bytes . long2bytes ( res . getId (), header , 4 );
+
+ int savedWriteIndex = buffer . writerIndex ();
+ buffer . writerIndex ( savedWriteIndex + HEADER_LENGTH );
+ ChannelBufferOutputStream bos = new ChannelBufferOutputStream ( buffer );
+ // 得到序列化器*
+ ObjectOutput out = serialization . serialize ( channel . getUrl (), bos );
+ if ( status == Response . OK ) {
+ if ( res . isHeartbeat ()) {
+ encodeHeartbeatData ( channel , out , res . getResult ());
+ } else {
+ // 对调用结果进行序列化*
+ // 这个方法会调用子类DubboCodec的override方法实现
+ encodeResponseData ( channel , out , res . getResult ());
+ }
+ }
+ else out . writeUTF ( res . getErrorMessage ());
+ out . flushBuffer ();
+ bos . flush ();
+ bos . close ();
+ // 消息体字节长度
+ int len = bos . writtenBytes ();
+ checkPayload ( channel , len );
+ Bytes . int2bytes ( len , header , 12 );
+ // 先写完消息体,再写消息头,再设置writeIndex位置
+ buffer . writerIndex ( savedWriteIndex );
+ buffer . writeBytes ( header );
+ buffer . writerIndex ( savedWriteIndex + HEADER_LENGTH + len );
+ } catch ( Throwable t ) {
+ // 略...
+ }
+ }
在这里先不关注消息头、ChannelBuffer等操作,这次主要看序列化相关的几个步骤:
3.1.1 获取序列化方式
+这里实际是父类中的方法AbstractCodec#getSerialization(Channel)
方法。这一步是利用Dubbo SPI机制,实例化一个序列化方式对象。具体过程不是本次的重点,只要理解这一步会根据<dubbo:protocol>
配置的序列化方式返回对应的序列化方式对象。比如这里得到的就是Hessian2Serialization对象。
3.1.2 得到序列化器
+这里调用Serialization#serialize(URL, OutputStream)
方法,得到一个序列化器。hessian2序列化提供的序列化器是Hessian2ObjectOutput对象,通过构造函数传入了ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)
方法中定义的包装了buffer的bos。这样Hessian2ObjectOutput对象可以将具体数据写入到buffer中。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+
public class Hessian2Serialization implements Serialization {
+
+ public static final byte ID = 2 ;
+
+ public byte getContentTypeId () {
+ return ID ;
+ }
+
+ public String getContentType () {
+ return "x-application/hessian2" ;
+ }
+
+ public ObjectOutput serialize ( URL url , OutputStream out ) throws IOException {
+ // 序列化器对象
+ return new Hessian2ObjectOutput ( out );
+ }
+
+ public ObjectInput deserialize ( URL url , InputStream is ) throws IOException {
+ return new Hessian2ObjectInput ( is );
+ }
+ }
3.1.3 对调用结果进行序列化
+配置中指定的是dubbo协议,这里实际会被调用的是DubboCodec#encodeResponseData(Channel, ObjectOutput, Object)
重写方法。DubboCodec继承自ExchangeCodec,是Dubbo protocol远程调用层 的编码器,主要负责实现协议层面的Response数据编码。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+
// DubboCodec.java
+ @Override
+ protected void encodeResponseData ( Channel channel , ObjectOutput out , Object data ) throws IOException {
+ Result result = ( Result ) data ;
+
+ Throwable th = result . getException ();
+ if ( th == null ) {
+ Object ret = result . getValue ();
+ if ( ret == null ) {
+ out . writeByte ( RESPONSE_NULL_VALUE );
+ } else {
+ // 序列化器中写入response数据标志位
+ out . writeByte ( RESPONSE_VALUE );
+ // 序列化器中写入正常返回的对象
+ out . writeObject ( ret );
+ }
+ } else {
+ out . writeByte ( RESPONSE_WITH_EXCEPTION );
+ out . writeObject ( th );
+ }
+ }
3.2 Hessian2ObjectOutput
+在3.1.2中提到过,得到的序列化器是Hessian2ObjectOutput,实现了ObjectOutput接口。对于常见的简单数据对象,ObjectOutput接口中都定义了相应处理方法,同时还定义writeObject(Object)方法处理复杂对象的序列化。
Hessian2ObjectOutput实现了接口定义的各个方法,通过一个Hessian2Output对象,完成各种数据类型hessian2序列化的具体操作。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+
public class Hessian2ObjectOutput implements ObjectOutput {
+ private final Hessian2Output mH2o ;
+
+ public Hessian2ObjectOutput ( OutputStream os ) {
+ mH2o = new Hessian2Output ( os );
+ mH2o . setSerializerFactory ( Hessian2SerializerFactory . SERIALIZER_FACTORY );
+ }
+
+ public void writeBool ( boolean v ) throws IOException {
+ mH2o . writeBoolean ( v );
+ }
+ // 略...
+
+ public void writeObject ( Object obj ) throws IOException {
+ mH2o . writeObject ( obj );
+ }
+
+ public void flushBuffer () throws IOException {
+ mH2o . flushBuffer ();
+ }
+ }
Hessian2ObjectOutput#writeObject(Object)
方法是response数据对象序列化的入口 ,数据对象序列化的操作从这一步正式开始。这个方法中调用的是Hessian2Output#writeObject(Object)
方法。
3.3 Hessian2Output
+注意Hessian2Output的方法,如writeBoolean(boolean)
,对值进行编码写入的时候,没有直接写入 _os
,而是维护一个字节数组 _buffer
和 _offset
值。回头看3.1中的ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)
方法,会调用out.flushBuffer()
,通过Hessian2ObjectOutput来调用Hessian2Output#flushBuffer()
方法,将 _buffer
内容写入 _os
,即写入 bos
,即ChannelBuffer对象buffer
。
Hessian2Output#writeObject(Object)
方法不像其他方法,没有直接向_buffer
写入具体的字节,而是尝试获取一个Serializer对象,完成对象的序列化。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+
public class Hessian2Output extends AbstractHessianOutput implements Hessian2Constants {
+ protected OutputStream _os ;
+ private final byte [] _buffer = new byte [ SIZE ] ;
+ // 略...
+
+ public Hessian2Output ( OutputStream os ) {
+ _os = os ;
+ }
+ // 略...
+
+ public void writeBoolean ( boolean value ) throws IOException {
+ if ( SIZE < _offset + 16 )
+ flush ();
+
+ if ( value )
+ // 不直接写入_os
+ _buffer [ _offset ++] = ( byte ) 'T' ;
+ else
+ _buffer [ _offset ++] = ( byte ) 'F' ;
+ }
+
+ public void writeObject ( Object object ) throws IOException {
+ if ( object == null ) {
+ writeNull ();
+ return ;
+ }
+
+ Serializer serializer ;
+ // 获得序列化工厂类,获得一个Serializer对象
+ serializer = findSerializerFactory (). getSerializer ( object . getClass ());
+ // 完成对象的序列化
+ serializer . writeObject ( object , this );
+ }
+
+ public final void flushBuffer () throws IOException {
+ int offset = _offset ;
+ if ( ! _isStreaming && offset > 0 ) {
+ _offset = 0 ;
+ _os . write ( _buffer , 0 , offset );
+ } else if ( _isStreaming && offset > 3 ) {
+ int len = offset - 3 ;
+ _buffer [ 0 ] = 'p' ;
+ _buffer [ 1 ] = ( byte ) ( len >> 8 );
+ _buffer [ 2 ] = ( byte ) len ;
+ _offset = 3 ;
+
+ _os . write ( _buffer , 0 , offset );
+ }
+ }
+ }
findSerializerFactory()会从父类获得一个SerializerFactory对象,回头从3.2看到,在Hessian2ObjectOutput中,为Hessian2Output指定SerializerFactory 具体对象为Hessian2SerializerFactory.SERIALIZER_FACTORY
。我们主要来看getSerializer(object.getClass())
3.4 SerializerFactory / Hessian2SerializerFactory
+Hessian2SerializerFactory相对比较简单,只是实现了getClassLoader() 方法,然后在公共静态常量量中构造了一个Hessian2SerializerFactory对象供使用。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+
public class Hessian2SerializerFactory extends SerializerFactory {
+
+ public static final SerializerFactory SERIALIZER_FACTORY = new Hessian2SerializerFactory ();
+
+ private Hessian2SerializerFactory () {
+ }
+
+ @Override
+ public ClassLoader getClassLoader () {
+ return Thread . currentThread (). getContextClassLoader ();
+ }
+
+ }
他父类SerializerFactory功能更加丰富。SerializerFactory#getSerializer(Class)
方法比较长,但是其实整体逻辑并不复杂:根据类型Class返回相应的Serializer实例,如果类型和方法中列举的都不匹配,则会返回一个默认的Serializer对象,即 JavaSerializer对象 。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+
public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ public Serializer getSerializer ( Class cl ) throws HessianProtocolException {
+ Serializer serializer ;
+ serializer = ( Serializer ) _staticSerializerMap . get ( cl );
+
+ if ( serializer != null )
+ return serializer ;
+
+ if ( _cachedSerializerMap != null ) {
+ synchronized ( _cachedSerializerMap ) {
+ serializer = ( Serializer ) _cachedSerializerMap . get ( cl );
+ }
+ if ( serializer != null )
+ return serializer ;
+ }
+
+ for ( int i = 0 ; serializer == null && _factories != null && i < _factories . size (); i ++ ) {
+ AbstractSerializerFactory factory ;
+ factory = ( AbstractSerializerFactory ) _factories . get ( i );
+ serializer = factory . getSerializer ( cl );
+ }
+
+ if ( serializer != null ) {
+
+ } else if ( JavaSerializer . getWriteReplace ( cl ) != null )
+ serializer = new JavaSerializer ( cl , _loader );
+ else if ( HessianRemoteObject . class . isAssignableFrom ( cl ))
+ serializer = new RemoteSerializer ();
+ // else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+ // serializer = new RemoteSerializer();
+ else if ( Map . class . isAssignableFrom ( cl )) {
+ if ( _mapSerializer == null )
+ _mapSerializer = new MapSerializer ();
+ serializer = _mapSerializer ;
+ }
+ else if ( Collection . class . isAssignableFrom ( cl )) {
+ if ( _collectionSerializer == null ) {
+ _collectionSerializer = new CollectionSerializer ();
+ }
+ serializer = _collectionSerializer ;
+ }
+ else if ( cl . isArray ())
+ serializer = new ArraySerializer ();
+ else if ( Throwable . class . isAssignableFrom ( cl ))
+ serializer = new ThrowableSerializer ( cl , getClassLoader ());
+ else if ( InputStream . class . isAssignableFrom ( cl ))
+ serializer = new InputStreamSerializer ();
+ else if ( Iterator . class . isAssignableFrom ( cl ))
+ serializer = IteratorSerializer . create ();
+ else if ( Enumeration . class . isAssignableFrom ( cl ))
+ serializer = EnumerationSerializer . create ();
+ else if ( Calendar . class . isAssignableFrom ( cl ))
+ serializer = CalendarSerializer . create ();
+ else if ( Locale . class . isAssignableFrom ( cl ))
+ serializer = LocaleSerializer . create ();
+ else if ( Enum . class . isAssignableFrom ( cl ))
+ serializer = new EnumSerializer ( cl );
+
+ if ( serializer == null )
+ // 都不符合以上类似,则返回默认的serializer
+ serializer = getDefaultSerializer ( cl );
+
+ if ( _cachedSerializerMap == null )
+ _cachedSerializerMap = new HashMap ( 8 );
+
+ synchronized ( _cachedSerializerMap ) {
+ // 缓存这个类型对应的serializer,下次直接从缓存获取
+ _cachedSerializerMap . put ( cl , serializer );
+ }
+
+ return serializer ;
+ }
+
+ protected Serializer getDefaultSerializer ( Class cl ) {
+ if ( _defaultSerializer != null )
+ return _defaultSerializer ;
+ if ( ! Serializable . class . isAssignableFrom ( cl ) && ! _isAllowNonSerializable ) {
+ throw new IllegalStateException ( "Serialized class " + cl . getName () + " must implement java.io.Serializable" );
+ }
+
+ return new JavaSerializer ( cl , _loader );
+ }
+ }
对于demo中的返回的数据对象DemoDTO,很显然就是会得到一个JavaSerializer,通过它的完成序列化。
3.5 JavaSerializer
+回顾上文3.3节中,serializer对象完成序列哈的入库也是writeObject()方法。来看JavaSerializer#writeObject(Object, AbstractHessianOutput)
方法。这里面序列化分两种情况:一是要序列化的对象有writeReplce()方法的,直接调用writeReplce()方法完成序列化;而是常规的使用静态类FieldSerializer序列化。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+
// JavaSerializer.java
+ public void writeObject ( Object obj , AbstractHessianOutput out ) throws IOException {
+ if ( out . addRef ( obj )) {
+ return ;
+ }
+
+ Class cl = obj . getClass ();
+ try {
+ // 调用序列化对象的writeReplace方法完成序列化
+ if ( _writeReplace != null ) {
+ Object repl ;
+ if ( _writeReplaceFactory != null )
+ repl = _writeReplace . invoke ( _writeReplaceFactory , obj );
+ else
+ repl = _writeReplace . invoke ( obj );
+
+ out . removeRef ( obj );
+ out . writeObject ( repl );
+ out . replaceRef ( repl , obj );
+ return ;
+ }
+ } catch ( RuntimeException e ) {
+ throw e ;
+ } catch ( Exception e ) {
+ // log.log(Level.FINE, e.toString(), e);
+ throw new RuntimeException ( e );
+ }
+
+ // hessian2会写入对象开始标记;hessian会写map开始标记并返回-2
+ int ref = out . writeObjectBegin ( cl . getName ());
+
+ if ( ref < - 1 ) {
+ // 这种情况看起来是兼容hessian序列化的,而不是hessian2序列化的
+ // hessian序列化是把对象当做map处理,所以writeObject10()方法里会循环写入字段名称然后马上字段序列化,最后还会写一个map结束标志
+ writeObject10 ( obj , out );
+ }
+ else {
+ if ( ref == - 1 ) {
+ // 写入对象包含字段长度,以及各个类型字段信息
+ writeDefinition20 ( out );
+ // 类型信息编码处理
+ out . writeObjectBegin ( cl . getName ());
+ }
+ // 这里会开始会遍历序列化对象包含的各个字段
+ writeInstance ( obj , out );
+ }
+ }
3.5.1 writeReplace方法
+JavaSerializer 使用一个Method变量 _writeReplace
保存writeReplace()方法,这个方法是从那里来的呢?在JavaSerializer构造方法中,会调用一个私有方法introspectWriteReplace(Class, ClassLoader)
。它会从两个地方找writeReplace()方法:一是序列化类相应的HessianSerializer类;二是序列化类本身 。但是要注意,HessianSerializer类的writeReplace方法签名 和 序列化类本身writeReplace方法签名 是有区别的。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+
// JavaSerializer.java
+ private void introspectWriteReplace ( Class cl , ClassLoader loader ) {
+ try {
+ String className = cl . getName () + "HessianSerializer" ;
+ // 加载 序列化类类名+HessianSerializer的类。例如demo中的DemoDTO类,则会
+ // 尝试加载cn.dingyufan.blog.demo.dubbostackoverflowduetojkd8instantprovider.api.dto.DemoDTOHessianSerializer类
+ Class serializerClass = Class . forName ( className , false , loader );
+ // 实例化相应的HessianSerializer类型
+ Object serializerObject = serializerClass . newInstance ();
+ // 反射获取WriteReplace方法
+ Method writeReplace = getWriteReplace ( serializerClass , cl );
+
+ if ( writeReplace != null ) {
+ _writeReplaceFactory = serializerObject ;
+ _writeReplace = writeReplace ;
+ return ;
+ }
+ } catch ( ClassNotFoundException e ) {
+ // 没有相应HessianSerializer类就会抛出ClassNotFoundException,这个异常不处理,后面会再次尝试从序列化类本身寻找
+ } catch ( Exception e ) {
+ log . log ( Level . FINER , e . toString (), e );
+ }
+ // 从序列化类本身找writeReplace方法
+ _writeReplace = getWriteReplace ( cl );
+ }
+
+ // 从要序列化的类型寻找 方法名为writeReplace、入参为空 的方法
+ protected static Method getWriteReplace ( Class cl ) {
+ for (; cl != null ; cl = cl . getSuperclass ()) {
+ Method [] methods = cl . getDeclaredMethods ();
+
+ for ( int i = 0 ; i < methods . length ; i ++ ) {
+ Method method = methods [ i ] ;
+
+ if ( method . getName (). equals ( "writeReplace" ) &&
+ method . getParameterTypes (). length == 0 )
+ return method ;
+ }
+ }
+
+ return null ;
+ }
+
+ // 从相应HessianSerializer类找到 方法名为writeReplace、 入参有且仅为要序列化类型 的方法
+ protected Method getWriteReplace ( Class cl , Class param )
+ {
+ for (; cl != null ; cl = cl . getSuperclass ()) {
+ for ( Method method : cl . getDeclaredMethods ()) {
+ if ( method . getName (). equals ( "writeReplace" )
+ && method . getParameterTypes (). length == 1
+ && param . equals ( method . getParameterTypes () [ 0 ] ))
+ return method ;
+ }
+ }
+
+ return null ;
+ }
writeReplace方法提供了一种扩展序列化的方式,对与Dubbo暂不支持的类型,可以通过这个扩展点,返回一个自定义的类型的对象,替代原本需要序列化的对象,然后对自定义的对象进行序列化。
但是有个问题,如果producer通过writeReplace方法自定义一个对象来序列化,consumer反序列化时,怎么把自定义的对象转回原本类型呢?这里不展开讲,但是办法肯定是有的,答案就是 readResolve方法 。: )
3.5.2 FieldSerializer
+对于没有writeReplace方法的类型,就继续走hessian2设定的序列化逻辑。从JavaSerializer#writeInstance(Object, AbstractHessianOutput)
继续。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+
// JavaSerializer.java
+ public void writeInstance ( Object obj , AbstractHessianOutput out ) throws IOException {
+ for ( int i = 0 ; i < _fields . length ; i ++ ) {
+ Field field = _fields [ i ] ;
+ // _fieldSerializers是数组 FieldSerializer[]
+ // 是在JavaSerializer构造方法中,根据序列化类中各个字段类型,依次创建FieldSerializer(或子类)的实例存入
+ _fieldSerializers [ i ] . serialize ( out , obj , field );
+ }
+ }
+
+ // JavaSerializer构造方法中调用
+ // 根据序列化类中各个字段类型,返回对应类型的FieldSerializer(或子类)实例
+ private static FieldSerializer getFieldSerializer ( Class type )
+ {
+ if ( int . class . equals ( type )
+ || byte . class . equals ( type )
+ || short . class . equals ( type )
+ || int . class . equals ( type )) {
+ return IntFieldSerializer . SER ;
+ }
+ else if ( long . class . equals ( type )) {
+ return LongFieldSerializer . SER ;
+ }
+ else if ( double . class . equals ( type ) ||
+ float . class . equals ( type )) {
+ return DoubleFieldSerializer . SER ;
+ }
+ else if ( boolean . class . equals ( type )) {
+ return BooleanFieldSerializer . SER ;
+ }
+ else if ( String . class . equals ( type )) {
+ return StringFieldSerializer . SER ;
+ }
+ else if ( java . util . Date . class . equals ( type )
+ || java . sql . Date . class . equals ( type )
+ || java . sql . Timestamp . class . equals ( type )
+ || java . sql . Time . class . equals ( type )) {
+ return DateFieldSerializer . SER ;
+ }
+ else
+ return FieldSerializer . SER ;
+ }
在JavaSerializer#writeObject(Object, AbstractHessianOutput)
方法中,写完类型信息、类型字段长度、各字段名称后,就会通过writeInstance方法依次序列化类型中的各字段。字段的序列化,就是由静态类FieldSerializer类完成。
比如DemoDTO中的String类型字段,就会有FieldSerializer的子类StringFieldSerializer,完成String consumer
字段序列化。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+
// JavaSerializer.java
+ static class StringFieldSerializer extends FieldSerializer {
+ static final FieldSerializer SER = new StringFieldSerializer ();
+
+ void serialize ( AbstractHessianOutput out , Object obj , Field field ) throws IOException {
+ String value = null ;
+ try {
+ value = ( String ) field . get ( obj );
+ } catch ( IllegalAccessException e ) {
+ log . log ( Level . FINE , e . toString (), e );
+ }
+ // 调用Hessian2Output类的writeString方法,值写入输出流
+ out . writeString ( value );
+ }
+ }
但是DemoDTO中的Instant类型字段,在JavaSerializer#getFieldSerializer(Class)
方法中没有对应类型的,得到的是默认的FieldSerializer实例。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+
static class FieldSerializer {
+ static final FieldSerializer SER = new FieldSerializer ();
+
+ void serialize ( AbstractHessianOutput out , Object obj , Field field ) throws IOException {
+ Object value = null ;
+
+ try {
+ value = field . get ( obj );
+ } catch ( IllegalAccessException e ) {
+ log . log ( Level . FINE , e . toString (), e );
+ }
+
+ try {
+ // 又回到 Hessian2Output#writeObject(Object)方法
+ // 再次获得序列化工厂类Hessian2SerializerFactory,获得一个Serializer对象,又通过Serializer对象序列化
+ // 这里有递归的感觉了
+ out . writeObject ( value );
+ } catch ( RuntimeException e ) {
+ throw new RuntimeException ( e . getMessage () + "\n Java field: " + field , e );
+ } catch ( IOException e ) {
+ throw new IOExceptionWrapper ( e . getMessage () + "\n Java field: " + field , e );
+ }
+ }
+ }
综上所述,Hessian2序列化的核心的几个类是:Hessian2Output、SerializerFactory(Hessian2SerializerFactory) 、JavaSerializer和FieldSerializer。
整体思路是:
Hessian2Output#writeObject(Object)
:对象 序列化的入口SerializerFactory#getSerializer(Class)
:根据对象类型,找到对应类型的Serializer完成对象序列化,无对应类型的交给JavaSerializerJavaSerializer#getFieldSerializer(Class)
:完成对象类型信息的序列化,然后依次序列化各个字段,为各字段找到对应类型的FieldSerializer子类完成字段序列化,无对应类型的字段交给FieldSerializer。FieldSerializer#serialize(AbstractHessianOutput, Object, Field)
:FieldSerializer会把字段值再当做一个对象,再走一遍整个对象序列化过程。这个设计想法是蛮好的,简单对象直接有对应类序列化,没有对应序列化方法的把对象的序列化拆解成各个字段的序列化。在理想的情况下,所有对象都可以通过不断的拆解,形成简单的对象,然后有相应的Serializer或FieldSerializer完成序列化 。就好像像复杂问题拆解成数个简单问题,也有点像庖丁解牛的感觉。
3.6 序列化调用栈
+了解dubbo在hessian2序列化方式时的逻辑之后,再来看本文开头的异常。上述大段文字内容繁多,很难串联起来,我们根据hessian2的序列化逻辑,整理整个序列化过程的调用栈,当然也可以直接打断点看
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+
ExchangeCodec # encode
+ -> ExchangeCodec # encodeResponseData
+ -> DubboCodec # encodeResponseData
+ -> Hessian2ObjectOutput # writeObject
+ // DemoDTO类型开始序列化
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ -> JavaSerializer # writeInstance
+ -> FieldSerializer # serialize
+ // Instant类型开始序列化
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ // Ser类型开始序列化(Instant的writeReplace方法产生的替代对象)
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ -> JavaSerializer # writeInstance
+ -> FieldSerializer # serialize
+ // Instant类型开始序列化(Ser类中的object字段)
+ -> Hessian2Output # writeObject
+ -> SerializerFactory # getSerializer
+ -> JavaSerializer # writeObject
+ // Ser(Instant的writeReplace方法产生的替代类)
+ -> Hessian2Output # writeObject
+ -> ......
通过调用栈,是不是一眼就发现问题所在了?这个调用栈倒序来看,可以发现正好开头是异常栈的顺序。
4 结论
+在Dubbo反序列化的过程中,产生的栈溢出的原因是:
Dubbo(v2.5.3)没有相应的Serializer或FieldSerializer能处理Instant数据类型,并且Instant正好有writeReplace方法,返回的替代类中的object字段又包含Instant本身。导致需要序列化的对象在Instant、Ser之间反复横跳,不断调用序列化方法逻辑,最终方法栈满,导致栈溢出错误。
结合网上信息,可以发现不只是Instant类,在JDK8新增的时间API,如LocalDateTime、Period等,都会和Instant有一样的问题,也是一样的原因。
5 解决方案
+知道了问题的原因之后,就可以对症下药了。本人水平有限,暂时想到以下几种方案:
5.1 使用Date类型
+首先最简单的方式,就是不使用JDK8中引入的时间API,改为使用Date。
这种方式没有直接解决问题,而是有些逃避问题了。
5.1 writeReplace / readResolve 方法
+修改Instant的writeReplace方法可能不是很方便,我们可以为DemoDTO添加writeReplace方法。总体思路的话就是:provider把Instant拆解成支持的数据类型,到了consumer再转化成Instant。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+
public class DemoDTO implements Serializable {
+
+ private static final long serialVersionUID = - 311647434535770294L ;
+
+ private String consumer ;
+ private Instant instant ;
+
+
+ // 序列化时JavaSerializer会调用此方法,然后序列化替代类对象repl
+ private DemoDTOHandle writeReplace () {
+ System . out . println ( "call writeReplace" );
+ DemoDTOHandle repl = new DemoDTOHandle ();
+ repl . setConsumer ( consumer );
+ if ( instant != null ) {
+ repl . setSeconds ( instant . getEpochSecond ());
+ repl . setNanos ( instant . getNano ());
+ }
+ return repl ;
+ }
+
+ // getter,setter略...
+ }
DemoDTOHandle中Instant拆成seconds、nanos字段存储,然后会代替DemoDTO进行序列化传输。代码如下。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+
public class DemoDTOHandle implements HessianHandle , Serializable {
+
+ private static final long serialVersionUID = - 8532997896892606136L ;
+
+ private String consumer ;
+
+ private long seconds ;
+ private int nanos ;
+
+ // 反序列化时,JavaDeserializer会调用此方法,重新组装实际的类
+ private DemoDTO readResolve () {
+ System . out . println ( "call readResolve" );
+ DemoDTO dto = new DemoDTO ();
+ dto . setConsumer ( consumer );
+ dto . setInstant ( Instant . ofEpochSecond ( seconds , nanos ));
+ return dto ;
+ }
+
+ // getter,setter略...
+ }
DemoDTOHandle为什么要实现HessianHandle接口呢?答案在SerializerFactory#getObjectDeserializer(String, Class)
中,如果不是HessianHandle实现类的话,是得不到DemoDTOHandle相关的Deserializer,只有DemoDTO相关的Deserializer,那么就无法使用DemoDTOHandle中的readResolve方法转换回对象了。
5.2 修改序列化方式
+之前提到,使用的是dubbo协议、hessian2序列化方式。事实上dubbo协议同样可以使用其他多种序列化方式。下面列举的序列化方式,都可以支持JDK8时间API的序列化处理。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+
<dubbo:protocol serialization= "java" />
+
+ <dubbo:protocol serialization= "compactedjava" />
+
+ <dubbo:protocol serialization= "nativejava" />
+
+ <dubbo:protocol serialization= "json" />
+
+ <dubbo:protocol serialization= "fastjson" />
当然如果升级了Dubbo版本的话,会支持更多的序列化方式。
5.3 升级Dubbo版本(√)
+当然,使用的Dubbo版本是2.5.3,实在是太老了。搜索maven仓库,看到2.5.3版本更新时间是2012年,十年前。
可以选择升级到2.6.x版本 。会发现2.6.x版本在序列化方面也做了一些优化。新版本不仅包含了新功能,扩展性也有所提升。但是需要注意,如果是升级2.6.6+版本,需要关注netty版本的变化。
2.6.x 支持JDK8时间API
+在2.6.x版本中,在SerializerFactory 中增加了对JDK8时间API的处理,加入了Java8TimeSerializer以及一系列的HessianHandle。意味着2.6.x版本Dubbo将不再有本文开头遇到的问题。
2.6.x 支持扩展SerializerFactory
+同时SerializerFactory 也提供了一定的扩展性,支持添加你想要的SerializerFactory。如果还有目标序列化方式无法处理的类型,可以自定义SerializerFactory、Serializer,只要将自定义SerializerFactory添加到当前序列化方式的SerializerFactory 中,就可以轻松扩展。在2.5.x上是不支持这种扩展的 。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+
// SerializerFactory.java 2.6.x
+ public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ protected ArrayList _factories = new ArrayList ();
+ // 略...
+
+ public void addFactory ( AbstractSerializerFactory factory ) {
+ _factories . add ( factory );
+ }
+
+ public Serializer getSerializer ( Class cl ) throws HessianProtocolException {
+ Serializer serializer ;
+ serializer = ( Serializer ) _staticSerializerMap . get ( cl );
+ if ( serializer != null )
+ return serializer ;
+
+ if ( _cachedSerializerMap != null ) {
+ synchronized ( _cachedSerializerMap ) {
+ serializer = ( Serializer ) _cachedSerializerMap . get ( cl );
+ }
+ if ( serializer != null )
+ return serializer ;
+ }
+
+ // 遍历扩展的SerializerFactory。尝试由扩展的SerializerFactory提供Serializer
+ // 2.5.3没有这个功能
+ for ( int i = 0 ; serializer == null && _factories != null && i < _factories . size (); i ++ ) {
+ AbstractSerializerFactory factory ;
+ factory = ( AbstractSerializerFactory ) _factories . get ( i );
+ serializer = factory . getSerializer ( cl );
+ }
+
+ if ( serializer != null ) {
+ }
+
+ else if ( JavaSerializer . getWriteReplace ( cl ) != null )
+ serializer = new JavaSerializer ( cl , _loader );
+ else if ( HessianRemoteObject . class . isAssignableFrom ( cl ))
+ serializer = new RemoteSerializer ();
+ // else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+ // serializer = new RemoteSerializer();
+ else if ( Map . class . isAssignableFrom ( cl )) {
+ if ( _mapSerializer == null )
+ _mapSerializer = new MapSerializer ();
+ serializer = _mapSerializer ;
+ }
+ else if ( Collection . class . isAssignableFrom ( cl )) {
+ if ( _collectionSerializer == null ) {
+ _collectionSerializer = new CollectionSerializer ();
+ }
+ serializer = _collectionSerializer ;
+ }
+ else if ( cl . isArray ())
+ serializer = new ArraySerializer ();
+ else if ( Throwable . class . isAssignableFrom ( cl ))
+ serializer = new ThrowableSerializer ( cl , getClassLoader ());
+ else if ( InputStream . class . isAssignableFrom ( cl ))
+ serializer = new InputStreamSerializer ();
+ else if ( Iterator . class . isAssignableFrom ( cl ))
+ serializer = IteratorSerializer . create ();
+
+ else if ( Enumeration . class . isAssignableFrom ( cl ))
+ serializer = EnumerationSerializer . create ();
+ else if ( Calendar . class . isAssignableFrom ( cl ))
+ serializer = CalendarSerializer . create ();
+ else if ( Locale . class . isAssignableFrom ( cl ))
+ serializer = LocaleSerializer . create ();
+ else if ( Enum . class . isAssignableFrom ( cl ))
+ serializer = new EnumSerializer ( cl );
+ if ( serializer == null )
+ serializer = getDefaultSerializer ( cl );
+ if ( _cachedSerializerMap == null )
+ _cachedSerializerMap = new HashMap ( 8 );
+
+ synchronized ( _cachedSerializerMap ) {
+ _cachedSerializerMap . put ( cl , serializer );
+ }
+ return serializer ;
+ }
+ }
5.4 SPI扩展Serialization
+这个方法理论上是完全可行的,但是有些过于大动干戈了。总体思路是:借助Dubbo的SPI机制,对Serialization进行扩展,基于hessian2的SerializerFactory ,对Serializer、Deserializer进行扩展。
6 写在最后
+最开始计划用一两天的时间写完这篇博客,但是过程中又不断的遇到问题、解决问题,最终花了整个五一假期才完成。通过这个问题,算是对Dubbo框架的序列化这块有了一些了解了。
\ No newline at end of file
diff --git a/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.md b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.md
new file mode 100644
index 00000000..c832f24f
--- /dev/null
+++ b/posts/dubbo-stack-overflow-due-to-jdk8-instant/index.md
@@ -0,0 +1,887 @@
+# JDK8日期时间API导致Dubbo调用StackOverflow错误
+
+
+<!--more-->
+
+
+
+## 0 前言
+
+博客搭建之后,不知道从何写起。思来想去,决定从记录一些工作中遇到的问题开始。一来对问题有个归档,算是有一些积累,方便日后回顾;二来促使自己遇到问题时能探本朔源,做到知其然知其所以然,不要草草百度了事。不积跬步,无以至千里;不积小流,无以成江海。
+
+
+
+## 1 问题
+
+最近项目中使用dubbo(**v2.5.3**)的时候,遇到一个问题:dubbo服务方在对response编码时失败,发生了栈溢出异常。可以通过一个demo([dubbo-stack-overflow-due-to-jkd8-instant](https://github.com/dingyufan/blog-demo-all/tree/main/dubbo-stack-overflow-due-to-jkd8-instant))复现这个异常。
+
+![image-20220421171300795](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220421171300795.png)
+
+看到栈溢出,一般想到的情况要么是递归过深,或是循环调用方法次数过多。为什么会在dubbo序列化编码的时候发生这个异常呢?通过查看源码提交记录,结合异常信息Fail to encode repsonse,并且在异常栈中出现JavaSerializer类,判断**问题出在Dubbo在对Response序列化时无法处理Instant类型对象**。
+
+网上搜索到的博客基本上只是说了有这样的问题,但是没有说明具体的原因,都是草草改用Date了事。通过这篇博客,记录一下Instant类型对象引起Dubbo对Response序列化失败导致栈溢出的根本原因以及解决方案。
+
+
+
+## 3 Dubbo序列化Response
+
+dubbo对不同协议、不同序列化方式处理方式是不一样的。此项目使用默认协议:**dubbo协议**,同时也采用dubbo协议默认序列化方式**:hessian2**。下文的序列化过程分析就是基于此的。
+
+```xml
+<!-- dubbo协议hessian2序列化方式,是dubbo的默认值。所以在xml配置中写不写这行配置都可以 -->
+<dubbo:protocol name="dubbo" serialization="hessian2"/>
+```
+
+**参考官网文档 [2.4 服务提供方返回调用结果](https://dubbo.apache.org/zh/docs/v2.7/dev/source/service-invoking-process/#24-%E6%9C%8D%E5%8A%A1%E6%8F%90%E4%BE%9B%E6%96%B9%E8%BF%94%E5%9B%9E%E8%B0%83%E7%94%A8%E7%BB%93%E6%9E%9C) **,我们从Dubbo处理返回结果入手。
+
+
+
+### 3.1 ExchangeCodec / DubboCodec
+
+ExchangeCodec是Dubbo **exchange信息交换层**的编码器。`ExchangeCodec#encode(Channel, ChannelBuffer, Object)`方法是Dubbo对Request和Response进行编码的入口,逻辑很简单,主要是根据消息的类型,交由对应方法进行编码,对于不是自己负责的非Request、Response类型的消息对象,交给父类处理。
+
+```java
+// ExchangeCodec.java
+public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
+ if (msg instanceof Request) {
+ encodeRequest(channel, buffer, (Request) msg);
+ } else if (msg instanceof Response) {
+ // 对response对象进行编码
+ encodeResponse(channel, buffer, (Response) msg);
+ } else {
+ super.encode(channel, buffer, msg);
+ }
+}
+```
+
+`ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)`方法主要做的就是组装消息写入ChannelBuffer,这样消息就可以发送给调用方。消息两部分组成:**消息头**,包含状态序列化等相关信息的字节数组;**消息体**:Response返回的具体数据转换成的字节数组。
+
+```java
+// ExchangeCodec.java
+protected void encodeResponse(Channel channel, ChannelBuffer buffer, Response res) throws IOException {
+ try {
+ // 获取序列化方式*
+ Serialization serialization = getSerialization(channel);
+ // 消息头字节数组。长度16
+ byte[] header = new byte[HEADER_LENGTH];
+ // 消息头 写入魔数
+ Bytes.short2bytes(MAGIC, header);
+ // 消息头 写入序列化器ID信息
+ header[2] = serialization.getContentTypeId();
+ if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
+ byte status = res.getStatus();
+ // 消息头 写入状态
+ header[3] = status;
+ // 消息头 写入
+ Bytes.long2bytes(res.getId(), header, 4);
+
+ int savedWriteIndex = buffer.writerIndex();
+ buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
+ ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
+ // 得到序列化器*
+ ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
+ if (status == Response.OK) {
+ if (res.isHeartbeat()) {
+ encodeHeartbeatData(channel, out, res.getResult());
+ } else {
+ // 对调用结果进行序列化*
+ // 这个方法会调用子类DubboCodec的override方法实现
+ encodeResponseData(channel, out, res.getResult());
+ }
+ }
+ else out.writeUTF(res.getErrorMessage());
+ out.flushBuffer();
+ bos.flush();
+ bos.close();
+ // 消息体字节长度
+ int len = bos.writtenBytes();
+ checkPayload(channel, len);
+ Bytes.int2bytes(len, header, 12);
+ // 先写完消息体,再写消息头,再设置writeIndex位置
+ buffer.writerIndex(savedWriteIndex);
+ buffer.writeBytes(header);
+ buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
+ } catch (Throwable t) {
+ // 略...
+ }
+}
+```
+
+在这里先不关注消息头、ChannelBuffer等操作,这次主要看序列化相关的几个步骤:
+
+#### 3.1.1 获取序列化方式
+
+这里实际是父类中的方法`AbstractCodec#getSerialization(Channel)`方法。这一步是利用Dubbo SPI机制,实例化一个序列化方式对象。具体过程不是本次的重点,只要理解这一步会根据`<dubbo:protocol>`配置的序列化方式返回对应的序列化方式对象。比如这里得到的就是Hessian2Serialization对象。
+
+#### 3.1.2 得到序列化器
+
+这里调用`Serialization#serialize(URL, OutputStream)`方法,得到一个序列化器。hessian2序列化提供的序列化器是Hessian2ObjectOutput对象,通过构造函数传入了`ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)`方法中定义的包装了buffer的bos。这样Hessian2ObjectOutput对象可以将具体数据写入到buffer中。
+
+```java
+public class Hessian2Serialization implements Serialization {
+
+ public static final byte ID = 2;
+
+ public byte getContentTypeId() {
+ return ID;
+ }
+
+ public String getContentType() {
+ return "x-application/hessian2";
+ }
+
+ public ObjectOutput serialize(URL url, OutputStream out) throws IOException {
+ // 序列化器对象
+ return new Hessian2ObjectOutput(out);
+ }
+
+ public ObjectInput deserialize(URL url, InputStream is) throws IOException {
+ return new Hessian2ObjectInput(is);
+ }
+}
+```
+
+#### 3.1.3 对调用结果进行序列化
+
+配置中指定的是dubbo协议,这里实际会被调用的是`DubboCodec#encodeResponseData(Channel, ObjectOutput, Object)`重写方法。DubboCodec继承自ExchangeCodec,是Dubbo **protocol远程调用层**的编码器,主要负责实现协议层面的Response数据编码。
+
+```java
+// DubboCodec.java
+@Override
+protected void encodeResponseData(Channel channel, ObjectOutput out, Object data) throws IOException {
+ Result result = (Result) data;
+
+ Throwable th = result.getException();
+ if (th == null) {
+ Object ret = result.getValue();
+ if (ret == null) {
+ out.writeByte(RESPONSE_NULL_VALUE);
+ } else {
+ // 序列化器中写入response数据标志位
+ out.writeByte(RESPONSE_VALUE);
+ // 序列化器中写入正常返回的对象
+ out.writeObject(ret);
+ }
+ } else {
+ out.writeByte(RESPONSE_WITH_EXCEPTION);
+ out.writeObject(th);
+ }
+}
+```
+
+
+
+### 3.2 Hessian2ObjectOutput
+
+在3.1.2中提到过,得到的序列化器是Hessian2ObjectOutput,实现了ObjectOutput接口。对于常见的简单数据对象,ObjectOutput接口中都定义了相应处理方法,同时还定义writeObject(Object)方法处理复杂对象的序列化。
+
+Hessian2ObjectOutput实现了接口定义的各个方法,通过一个Hessian2Output对象,完成各种数据类型hessian2序列化的具体操作。
+
+```java
+public class Hessian2ObjectOutput implements ObjectOutput {
+ private final Hessian2Output mH2o;
+
+ public Hessian2ObjectOutput(OutputStream os) {
+ mH2o = new Hessian2Output(os);
+ mH2o.setSerializerFactory(Hessian2SerializerFactory.SERIALIZER_FACTORY);
+ }
+
+ public void writeBool(boolean v) throws IOException {
+ mH2o.writeBoolean(v);
+ }
+ // 略...
+
+ public void writeObject(Object obj) throws IOException {
+ mH2o.writeObject(obj);
+ }
+
+ public void flushBuffer() throws IOException {
+ mH2o.flushBuffer();
+ }
+}
+```
+
+**`Hessian2ObjectOutput#writeObject(Object)`方法是response数据对象序列化的入口**,数据对象序列化的操作从这一步正式开始。这个方法中调用的是`Hessian2Output#writeObject(Object)`方法。
+
+
+
+### 3.3 Hessian2Output
+
+注意Hessian2Output的方法,如`writeBoolean(boolean)`,对值进行编码写入的时候,没有直接写入` _os`,而是维护一个字节数组 `_buffer`和 `_offset`值。回头看3.1中的`ExchangeCodec#encodeResponse(Channel, ChannelBuffer, Response)`方法,会调用`out.flushBuffer()`,通过Hessian2ObjectOutput来调用`Hessian2Output#flushBuffer()`方法,将 `_buffer`内容写入 `_os` ,即写入 `bos`,即ChannelBuffer对象`buffer`。
+
+`Hessian2Output#writeObject(Object)`方法不像其他方法,没有直接向`_buffer`写入具体的字节,而是尝试获取一个Serializer对象,完成对象的序列化。
+
+```java
+public class Hessian2Output extends AbstractHessianOutput implements Hessian2Constants {
+ protected OutputStream _os;
+ private final byte []_buffer = new byte[SIZE];
+ // 略...
+
+ public Hessian2Output(OutputStream os) {
+ _os = os;
+ }
+ // 略...
+
+ public void writeBoolean(boolean value) throws IOException {
+ if (SIZE < _offset + 16)
+ flush();
+
+ if (value)
+ // 不直接写入_os
+ _buffer[_offset++] = (byte) 'T';
+ else
+ _buffer[_offset++] = (byte) 'F';
+ }
+
+ public void writeObject(Object object) throws IOException {
+ if (object == null) {
+ writeNull();
+ return;
+ }
+
+ Serializer serializer;
+ // 获得序列化工厂类,获得一个Serializer对象
+ serializer = findSerializerFactory().getSerializer(object.getClass());
+ // 完成对象的序列化
+ serializer.writeObject(object, this);
+ }
+
+ public final void flushBuffer() throws IOException {
+ int offset = _offset;
+ if (! _isStreaming && offset > 0) {
+ _offset = 0;
+ _os.write(_buffer, 0, offset);
+ } else if (_isStreaming && offset > 3) {
+ int len = offset - 3;
+ _buffer[0] = 'p';
+ _buffer[1] = (byte) (len >> 8);
+ _buffer[2] = (byte) len;
+ _offset = 3;
+
+ _os.write(_buffer, 0, offset);
+ }
+ }
+}
+
+```
+
+findSerializerFactory()会从父类获得一个SerializerFactory对象,回头从3.2看到,在Hessian2ObjectOutput中,为Hessian2Output指定SerializerFactory 具体对象为`Hessian2SerializerFactory.SERIALIZER_FACTORY`。我们主要来看getSerializer(object.getClass())
+
+
+
+### 3.4 SerializerFactory / Hessian2SerializerFactory
+
+Hessian2SerializerFactory相对比较简单,只是实现了getClassLoader() 方法,然后在公共静态常量量中构造了一个Hessian2SerializerFactory对象供使用。
+
+```java
+public class Hessian2SerializerFactory extends SerializerFactory {
+
+ public static final SerializerFactory SERIALIZER_FACTORY = new Hessian2SerializerFactory();
+
+ private Hessian2SerializerFactory() {
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+}
+```
+
+他父类SerializerFactory功能更加丰富。`SerializerFactory#getSerializer(Class)`方法比较长,但是其实整体逻辑并不复杂:**根据类型Class返回相应的Serializer实例,如果类型和方法中列举的都不匹配,则会返回一个默认的Serializer对象,即 JavaSerializer对象**。
+
+```java
+public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ public Serializer getSerializer(Class cl) throws HessianProtocolException {
+ Serializer serializer;
+ serializer = (Serializer) _staticSerializerMap.get(cl);
+
+ if (serializer != null)
+ return serializer;
+
+ if (_cachedSerializerMap != null) {
+ synchronized (_cachedSerializerMap) {
+ serializer = (Serializer) _cachedSerializerMap.get(cl);
+ }
+ if (serializer != null)
+ return serializer;
+ }
+
+ for (int i = 0; serializer == null && _factories != null && i < _factories.size(); i++) {
+ AbstractSerializerFactory factory;
+ factory = (AbstractSerializerFactory) _factories.get(i);
+ serializer = factory.getSerializer(cl);
+ }
+
+ if (serializer != null) {
+
+ } else if (JavaSerializer.getWriteReplace(cl) != null)
+ serializer = new JavaSerializer(cl, _loader);
+ else if (HessianRemoteObject.class.isAssignableFrom(cl))
+ serializer = new RemoteSerializer();
+// else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+// serializer = new RemoteSerializer();
+ else if (Map.class.isAssignableFrom(cl)) {
+ if (_mapSerializer == null)
+ _mapSerializer = new MapSerializer();
+ serializer = _mapSerializer;
+ }
+ else if (Collection.class.isAssignableFrom(cl)) {
+ if (_collectionSerializer == null) {
+ _collectionSerializer = new CollectionSerializer();
+ }
+ serializer = _collectionSerializer;
+ }
+ else if (cl.isArray())
+ serializer = new ArraySerializer();
+ else if (Throwable.class.isAssignableFrom(cl))
+ serializer = new ThrowableSerializer(cl, getClassLoader());
+ else if (InputStream.class.isAssignableFrom(cl))
+ serializer = new InputStreamSerializer();
+ else if (Iterator.class.isAssignableFrom(cl))
+ serializer = IteratorSerializer.create();
+ else if (Enumeration.class.isAssignableFrom(cl))
+ serializer = EnumerationSerializer.create();
+ else if (Calendar.class.isAssignableFrom(cl))
+ serializer = CalendarSerializer.create();
+ else if (Locale.class.isAssignableFrom(cl))
+ serializer = LocaleSerializer.create();
+ else if (Enum.class.isAssignableFrom(cl))
+ serializer = new EnumSerializer(cl);
+
+ if (serializer == null)
+ // 都不符合以上类似,则返回默认的serializer
+ serializer = getDefaultSerializer(cl);
+
+ if (_cachedSerializerMap == null)
+ _cachedSerializerMap = new HashMap(8);
+
+ synchronized (_cachedSerializerMap) {
+ // 缓存这个类型对应的serializer,下次直接从缓存获取
+ _cachedSerializerMap.put(cl, serializer);
+ }
+
+ return serializer;
+ }
+
+ protected Serializer getDefaultSerializer(Class cl) {
+ if (_defaultSerializer != null)
+ return _defaultSerializer;
+ if (!Serializable.class.isAssignableFrom(cl) && ! _isAllowNonSerializable) {
+ throw new IllegalStateException("Serialized class " + cl.getName() + " must implement java.io.Serializable");
+ }
+
+ return new JavaSerializer(cl, _loader);
+ }
+}
+```
+
+对于demo中的返回的数据对象DemoDTO,很显然就是会得到一个JavaSerializer,通过它的完成序列化。
+
+
+
+### 3.5 JavaSerializer
+
+回顾上文3.3节中,serializer对象完成序列哈的入库也是writeObject()方法。来看`JavaSerializer#writeObject(Object, AbstractHessianOutput)`方法。这里面序列化分两种情况:一是要序列化的对象有writeReplce()方法的,直接调用writeReplce()方法完成序列化;而是常规的使用静态类FieldSerializer序列化。
+
+```java
+// JavaSerializer.java
+public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
+ if (out.addRef(obj)) {
+ return;
+ }
+
+ Class cl = obj.getClass();
+ try {
+ // 调用序列化对象的writeReplace方法完成序列化
+ if (_writeReplace != null) {
+ Object repl;
+ if (_writeReplaceFactory != null)
+ repl = _writeReplace.invoke(_writeReplaceFactory, obj);
+ else
+ repl = _writeReplace.invoke(obj);
+
+ out.removeRef(obj);
+ out.writeObject(repl);
+ out.replaceRef(repl, obj);
+ return;
+ }
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ // log.log(Level.FINE, e.toString(), e);
+ throw new RuntimeException(e);
+ }
+
+ // hessian2会写入对象开始标记;hessian会写map开始标记并返回-2
+ int ref = out.writeObjectBegin(cl.getName());
+
+ if (ref < -1) {
+ // 这种情况看起来是兼容hessian序列化的,而不是hessian2序列化的
+ // hessian序列化是把对象当做map处理,所以writeObject10()方法里会循环写入字段名称然后马上字段序列化,最后还会写一个map结束标志
+ writeObject10(obj, out);
+ }
+ else {
+ if (ref == -1) {
+ // 写入对象包含字段长度,以及各个类型字段信息
+ writeDefinition20(out);
+ // 类型信息编码处理
+ out.writeObjectBegin(cl.getName());
+ }
+ // 这里会开始会遍历序列化对象包含的各个字段
+ writeInstance(obj, out);
+ }
+}
+```
+
+
+
+#### 3.5.1 writeReplace方法
+
+JavaSerializer 使用一个Method变量 `_writeReplace`保存writeReplace()方法,这个方法是从那里来的呢?在JavaSerializer构造方法中,会调用一个私有方法`introspectWriteReplace(Class, ClassLoader)`。它会从两个地方找writeReplace()方法:**一是序列化类相应的HessianSerializer类;二是序列化类本身**。但是要注意,HessianSerializer类的writeReplace方法签名 和 序列化类本身writeReplace方法签名 是有区别的。
+
+```java
+// JavaSerializer.java
+private void introspectWriteReplace(Class cl, ClassLoader loader) {
+ try {
+ String className = cl.getName() + "HessianSerializer";
+ // 加载 序列化类类名+HessianSerializer的类。例如demo中的DemoDTO类,则会
+ // 尝试加载cn.dingyufan.blog.demo.dubbostackoverflowduetojkd8instantprovider.api.dto.DemoDTOHessianSerializer类
+ Class serializerClass = Class.forName(className, false, loader);
+ // 实例化相应的HessianSerializer类型
+ Object serializerObject = serializerClass.newInstance();
+ // 反射获取WriteReplace方法
+ Method writeReplace = getWriteReplace(serializerClass, cl);
+
+ if (writeReplace != null) {
+ _writeReplaceFactory = serializerObject;
+ _writeReplace = writeReplace;
+ return;
+ }
+ } catch (ClassNotFoundException e) {
+ // 没有相应HessianSerializer类就会抛出ClassNotFoundException,这个异常不处理,后面会再次尝试从序列化类本身寻找
+ } catch (Exception e) {
+ log.log(Level.FINER, e.toString(), e);
+ }
+ // 从序列化类本身找writeReplace方法
+ _writeReplace = getWriteReplace(cl);
+}
+
+// 从要序列化的类型寻找 方法名为writeReplace、入参为空 的方法
+protected static Method getWriteReplace(Class cl) {
+ for (; cl != null; cl = cl.getSuperclass()) {
+ Method []methods = cl.getDeclaredMethods();
+
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+
+ if (method.getName().equals("writeReplace") &&
+ method.getParameterTypes().length == 0)
+ return method;
+ }
+ }
+
+ return null;
+}
+
+// 从相应HessianSerializer类找到 方法名为writeReplace、 入参有且仅为要序列化类型 的方法
+protected Method getWriteReplace(Class cl, Class param)
+{
+ for (; cl != null; cl = cl.getSuperclass()) {
+ for (Method method : cl.getDeclaredMethods()) {
+ if (method.getName().equals("writeReplace")
+ && method.getParameterTypes().length == 1
+ && param.equals(method.getParameterTypes()[0]))
+ return method;
+ }
+ }
+
+ return null;
+}
+```
+
+writeReplace方法提供了一种扩展序列化的方式,对与Dubbo暂不支持的类型,可以通过这个扩展点,返回一个自定义的类型的对象,替代原本需要序列化的对象,然后对自定义的对象进行序列化。
+
+但是有个问题,**如果producer通过writeReplace方法自定义一个对象来序列化,consumer反序列化时,怎么把自定义的对象转回原本类型呢?**这里不展开讲,但是办法肯定是有的,答案就是**readResolve方法** 。: )
+
+
+
+#### 3.5.2 FieldSerializer
+
+对于没有writeReplace方法的类型,就继续走hessian2设定的序列化逻辑。从`JavaSerializer#writeInstance(Object, AbstractHessianOutput)`继续。
+
+```java
+// JavaSerializer.java
+public void writeInstance(Object obj, AbstractHessianOutput out) throws IOException {
+ for (int i = 0; i < _fields.length; i++) {
+ Field field = _fields[i];
+ // _fieldSerializers是数组 FieldSerializer[]
+ // 是在JavaSerializer构造方法中,根据序列化类中各个字段类型,依次创建FieldSerializer(或子类)的实例存入
+ _fieldSerializers[i].serialize(out, obj, field);
+ }
+}
+
+// JavaSerializer构造方法中调用
+// 根据序列化类中各个字段类型,返回对应类型的FieldSerializer(或子类)实例
+private static FieldSerializer getFieldSerializer(Class type)
+{
+ if (int.class.equals(type)
+ || byte.class.equals(type)
+ || short.class.equals(type)
+ || int.class.equals(type)) {
+ return IntFieldSerializer.SER;
+ }
+ else if (long.class.equals(type)) {
+ return LongFieldSerializer.SER;
+ }
+ else if (double.class.equals(type) ||
+ float.class.equals(type)) {
+ return DoubleFieldSerializer.SER;
+ }
+ else if (boolean.class.equals(type)) {
+ return BooleanFieldSerializer.SER;
+ }
+ else if (String.class.equals(type)) {
+ return StringFieldSerializer.SER;
+ }
+ else if (java.util.Date.class.equals(type)
+ || java.sql.Date.class.equals(type)
+ || java.sql.Timestamp.class.equals(type)
+ || java.sql.Time.class.equals(type)) {
+ return DateFieldSerializer.SER;
+ }
+ else
+ return FieldSerializer.SER;
+}
+```
+
+在`JavaSerializer#writeObject(Object, AbstractHessianOutput)`方法中,写完类型信息、类型字段长度、各字段名称后,就会通过writeInstance方法依次序列化类型中的各字段。字段的序列化,就是由静态类FieldSerializer类完成。
+
+比如DemoDTO中的String类型字段,就会有FieldSerializer的子类StringFieldSerializer,完成`String consumer`字段序列化。
+
+```java
+// JavaSerializer.java
+static class StringFieldSerializer extends FieldSerializer {
+ static final FieldSerializer SER = new StringFieldSerializer();
+
+ void serialize(AbstractHessianOutput out, Object obj, Field field) throws IOException {
+ String value = null;
+ try {
+ value = (String) field.get(obj);
+ } catch (IllegalAccessException e) {
+ log.log(Level.FINE, e.toString(), e);
+ }
+ // 调用Hessian2Output类的writeString方法,值写入输出流
+ out.writeString(value);
+ }
+}
+```
+
+但是DemoDTO中的Instant类型字段,在`JavaSerializer#getFieldSerializer(Class)`方法中没有对应类型的,得到的是默认的FieldSerializer实例。
+
+```java
+static class FieldSerializer {
+ static final FieldSerializer SER = new FieldSerializer();
+
+ void serialize(AbstractHessianOutput out, Object obj, Field field) throws IOException {
+ Object value = null;
+
+ try {
+ value = field.get(obj);
+ } catch (IllegalAccessException e) {
+ log.log(Level.FINE, e.toString(), e);
+ }
+
+ try {
+ // 又回到 Hessian2Output#writeObject(Object)方法
+ // 再次获得序列化工厂类Hessian2SerializerFactory,获得一个Serializer对象,又通过Serializer对象序列化
+ // 这里有递归的感觉了
+ out.writeObject(value);
+ } catch (RuntimeException e) {
+ throw new RuntimeException(e.getMessage() + "\n Java field: " + field, e);
+ } catch (IOException e) {
+ throw new IOExceptionWrapper(e.getMessage() + "\n Java field: " + field, e);
+ }
+ }
+}
+```
+
+
+
+综上所述,Hessian2序列化的核心的几个类是:Hessian2Output、SerializerFactory(Hessian2SerializerFactory) 、JavaSerializer和FieldSerializer。
+
+整体思路是:
+
+- `Hessian2Output#writeObject(Object)`:对象 序列化的入口
+- `SerializerFactory#getSerializer(Class)`:根据对象类型,找到对应类型的Serializer完成对象序列化,无对应类型的交给JavaSerializer
+- `JavaSerializer#getFieldSerializer(Class)`:完成对象类型信息的序列化,然后依次序列化各个字段,为各字段找到对应类型的FieldSerializer子类完成字段序列化,无对应类型的字段交给FieldSerializer。
+- `FieldSerializer#serialize(AbstractHessianOutput, Object, Field)`:FieldSerializer会把字段值再当做一个对象,再走一遍整个对象序列化过程。
+
+这个设计想法是蛮好的,**简单对象直接有对应类序列化,没有对应序列化方法的把对象的序列化拆解成各个字段的序列化。在理想的情况下,所有对象都可以通过不断的拆解,形成简单的对象,然后有相应的Serializer或FieldSerializer完成序列化**。就好像像复杂问题拆解成数个简单问题,也有点像庖丁解牛的感觉。
+
+
+
+### 3.6 序列化调用栈
+
+了解dubbo在hessian2序列化方式时的逻辑之后,再来看本文开头的异常。上述大段文字内容繁多,很难串联起来,我们根据hessian2的序列化逻辑,整理整个序列化过程的调用栈,当然也可以直接打断点看
+
+```java
+ExchangeCodec#encode
+ ->ExchangeCodec#encodeResponseData
+ ->DubboCodec#encodeResponseData
+ ->Hessian2ObjectOutput#writeObject
+ // DemoDTO类型开始序列化
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ ->JavaSerializer#writeInstance
+ ->FieldSerializer#serialize
+ // Instant类型开始序列化
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ // Ser类型开始序列化(Instant的writeReplace方法产生的替代对象)
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ ->JavaSerializer#writeInstance
+ ->FieldSerializer#serialize
+ // Instant类型开始序列化(Ser类中的object字段)
+ ->Hessian2Output#writeObject
+ ->SerializerFactory#getSerializer
+ ->JavaSerializer#writeObject
+ // Ser(Instant的writeReplace方法产生的替代类)
+ ->Hessian2Output#writeObject
+ -> ......
+```
+
+通过调用栈,是不是一眼就发现问题所在了?这个调用栈倒序来看,可以发现正好开头是异常栈的顺序。
+
+
+
+## 4 结论
+
+在Dubbo反序列化的过程中,产生的栈溢出的原因是:
+
+**Dubbo(v2.5.3)没有相应的Serializer或FieldSerializer能处理Instant数据类型,并且Instant正好有writeReplace方法,返回的替代类中的object字段又包含Instant本身。导致需要序列化的对象在Instant、Ser之间反复横跳,不断调用序列化方法逻辑,最终方法栈满,导致栈溢出错误。**
+
+结合网上信息,可以发现不只是Instant类,在JDK8新增的时间API,如LocalDateTime、Period等,都会和Instant有一样的问题,也是一样的原因。
+
+
+
+## 5 解决方案
+
+知道了问题的原因之后,就可以对症下药了。本人水平有限,暂时想到以下几种方案:
+
+### 5.1 使用Date类型
+
+首先最简单的方式,就是不使用JDK8中引入的时间API,改为使用Date。
+
+这种方式没有直接解决问题,而是有些逃避问题了。
+
+
+
+### 5.1 writeReplace / readResolve 方法
+
+修改Instant的writeReplace方法可能不是很方便,我们可以为DemoDTO添加writeReplace方法。总体思路的话就是:provider把Instant拆解成支持的数据类型,到了consumer再转化成Instant。
+
+```java
+public class DemoDTO implements Serializable {
+
+ private static final long serialVersionUID = -311647434535770294L;
+
+ private String consumer;
+ private Instant instant;
+
+
+ // 序列化时JavaSerializer会调用此方法,然后序列化替代类对象repl
+ private DemoDTOHandle writeReplace() {
+ System.out.println("call writeReplace");
+ DemoDTOHandle repl = new DemoDTOHandle();
+ repl.setConsumer(consumer);
+ if (instant != null) {
+ repl.setSeconds(instant.getEpochSecond());
+ repl.setNanos(instant.getNano());
+ }
+ return repl;
+ }
+
+ // getter,setter略...
+}
+```
+
+DemoDTOHandle中Instant拆成seconds、nanos字段存储,然后会代替DemoDTO进行序列化传输。代码如下。
+
+```java
+public class DemoDTOHandle implements HessianHandle, Serializable {
+
+ private static final long serialVersionUID = -8532997896892606136L;
+
+ private String consumer;
+
+ private long seconds;
+ private int nanos;
+
+ // 反序列化时,JavaDeserializer会调用此方法,重新组装实际的类
+ private DemoDTO readResolve() {
+ System.out.println("call readResolve");
+ DemoDTO dto = new DemoDTO();
+ dto.setConsumer(consumer);
+ dto.setInstant(Instant.ofEpochSecond(seconds, nanos));
+ return dto;
+ }
+
+ // getter,setter略...
+}
+```
+
+DemoDTOHandle为什么要实现HessianHandle接口呢?答案在`SerializerFactory#getObjectDeserializer(String, Class)`中,如果不是HessianHandle实现类的话,是得不到DemoDTOHandle相关的Deserializer,只有DemoDTO相关的Deserializer,那么就无法使用DemoDTOHandle中的readResolve方法转换回对象了。
+
+![image-20220504103337575](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220504103337575.png)
+
+
+
+### 5.2 修改序列化方式
+
+之前提到,使用的是dubbo协议、hessian2序列化方式。事实上dubbo协议同样可以使用其他多种序列化方式。下面列举的序列化方式,都可以支持JDK8时间API的序列化处理。
+
+```xml
+<dubbo:protocol serialization="java"/>
+
+<dubbo:protocol serialization="compactedjava"/>
+
+<dubbo:protocol serialization="nativejava"/>
+
+<dubbo:protocol serialization="json"/>
+
+<dubbo:protocol serialization="fastjson"/>
+```
+
+当然如果升级了Dubbo版本的话,会支持更多的序列化方式。
+
+
+
+### 5.3 升级Dubbo版本(√)
+
+当然,使用的Dubbo版本是2.5.3,实在是太老了。搜索maven仓库,看到2.5.3版本更新时间是2012年,十年前。
+
+![image-20220503122841533](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220503122841533.png)
+
+**可以选择升级到2.6.x版本**。会发现2.6.x版本在序列化方面也做了一些优化。新版本不仅包含了新功能,扩展性也有所提升。但是需要注意,如果是升级2.6.6+版本,需要关注netty版本的变化。
+
+#### 2.6.x 支持JDK8时间API
+
+在2.6.x版本中,在SerializerFactory 中增加了对JDK8时间API的处理,加入了Java8TimeSerializer以及一系列的HessianHandle。**意味着2.6.x版本Dubbo将不再有本文开头遇到的问题。**
+
+![image-20220503123444697](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220503123444697.png)
+
+#### 2.6.x 支持扩展SerializerFactory
+
+同时SerializerFactory 也提供了一定的扩展性,支持添加你想要的SerializerFactory。如果还有目标序列化方式无法处理的类型,可以自定义SerializerFactory、Serializer,只要将自定义SerializerFactory添加到当前序列化方式的SerializerFactory 中,就可以轻松扩展。**在2.5.x上是不支持这种扩展的**。
+
+```java
+// SerializerFactory.java 2.6.x
+public class SerializerFactory extends AbstractSerializerFactory {
+ // 略...
+
+ protected ArrayList _factories = new ArrayList();
+ // 略...
+
+ public void addFactory(AbstractSerializerFactory factory) {
+ _factories.add(factory);
+ }
+
+ public Serializer getSerializer(Class cl) throws HessianProtocolException{
+ Serializer serializer;
+ serializer = (Serializer) _staticSerializerMap.get(cl);
+ if (serializer != null)
+ return serializer;
+
+ if (_cachedSerializerMap != null) {
+ synchronized (_cachedSerializerMap) {
+ serializer = (Serializer) _cachedSerializerMap.get(cl);
+ }
+ if (serializer != null)
+ return serializer;
+ }
+
+ // 遍历扩展的SerializerFactory。尝试由扩展的SerializerFactory提供Serializer
+ // 2.5.3没有这个功能
+ for (int i = 0; serializer == null && _factories != null && i < _factories.size(); i++) {
+ AbstractSerializerFactory factory;
+ factory = (AbstractSerializerFactory) _factories.get(i);
+ serializer = factory.getSerializer(cl);
+ }
+
+ if (serializer != null) {
+ }
+
+ else if (JavaSerializer.getWriteReplace(cl) != null)
+ serializer = new JavaSerializer(cl, _loader);
+ else if (HessianRemoteObject.class.isAssignableFrom(cl))
+ serializer = new RemoteSerializer();
+ // else if (BurlapRemoteObject.class.isAssignableFrom(cl))
+ // serializer = new RemoteSerializer();
+ else if (Map.class.isAssignableFrom(cl)) {
+ if (_mapSerializer == null)
+ _mapSerializer = new MapSerializer();
+ serializer = _mapSerializer;
+ }
+ else if (Collection.class.isAssignableFrom(cl)) {
+ if (_collectionSerializer == null) {
+ _collectionSerializer = new CollectionSerializer();
+ }
+ serializer = _collectionSerializer;
+ }
+ else if (cl.isArray())
+ serializer = new ArraySerializer();
+ else if (Throwable.class.isAssignableFrom(cl))
+ serializer = new ThrowableSerializer(cl, getClassLoader());
+ else if (InputStream.class.isAssignableFrom(cl))
+ serializer = new InputStreamSerializer();
+ else if (Iterator.class.isAssignableFrom(cl))
+ serializer = IteratorSerializer.create();
+
+ else if (Enumeration.class.isAssignableFrom(cl))
+ serializer = EnumerationSerializer.create();
+ else if (Calendar.class.isAssignableFrom(cl))
+ serializer = CalendarSerializer.create();
+ else if (Locale.class.isAssignableFrom(cl))
+ serializer = LocaleSerializer.create();
+ else if (Enum.class.isAssignableFrom(cl))
+ serializer = new EnumSerializer(cl);
+ if (serializer == null)
+ serializer = getDefaultSerializer(cl);
+ if (_cachedSerializerMap == null)
+ _cachedSerializerMap = new HashMap(8);
+
+ synchronized (_cachedSerializerMap) {
+ _cachedSerializerMap.put(cl, serializer);
+ }
+ return serializer;
+ }
+}
+```
+
+
+
+### 5.4 SPI扩展Serialization
+
+这个方法理论上是完全可行的,但是有些过于大动干戈了。总体思路是:借助Dubbo的SPI机制,对Serialization进行扩展,基于hessian2的SerializerFactory ,对Serializer、Deserializer进行扩展。
+
+
+
+## 6 写在最后
+
+最开始计划用一两天的时间写完这篇博客,但是过程中又不断的遇到问题、解决问题,最终花了整个五一假期才完成。通过这个问题,算是对Dubbo框架的序列化这块有了一些了解了。
+
+
+
+---
+
+> 作者:
+> URL: https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/
+
diff --git "a/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html" "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html"
new file mode 100644
index 00000000..fe4ab0dd
--- /dev/null
+++ "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html"
@@ -0,0 +1,142 @@
+GitHub Pages + Hugo搭建个人博客 - dingyufan's blog
+2022-04-10 2024-10-19 约 2400 字
0 前言
+去年年底开始,就一直有写博客的想法,一方面来沉淀自己的技术,另一方面也希望能对他人有些小小的帮助。如今各种平台非常的多,但是不是广告铺天盖地,就是样式固定,不够个性化。最终决定借助Github Pages来搭建一个属于自己的个性化博客。
Github Pages 是由GitHub提供的一个免费构建网站的方式。通过创建名为github用户名.github.io
的仓库,上传文件后,就可以通过 http://github用户名.github.io
访问你的网站。
GIthub Pages默认使用Jekyll 构建静态网站,由于Jekyll依赖于Ruby,并且个人对Ruby完全不熟悉,所以最终选择了基于Golang的静态网站生成器Hugo 来生成静态网站,然后托管在Github上。
1 创建github仓库
+创建一个 名为github用户名.github.io
的仓库,github用户名
填写自己的GIthub用户名。比如我,仓库名就是dingyufan.github.io
。
创建之后,是一个空仓库,先放着暂时不管。
2 安装Golang
+Windows系统安装Golang非常简单,下载msi安装包 ,直接安装即可。默认会安装到 C:\Go
目录下,同时C:\Go\bin
目录也会被添加到PATH 环境变量中。可以通过go version
验证安装的版本。目前最新版本应该是go1.18,我是之前安装的老版本。
3 安装hugo
+创建文件夹C:\Hugo
、C:\Hugo\bin
、C:\Hugo\sites
。其中bin目录用来存放hugo可执行程序,sites目录则用来保存站点。
从Github下载Hugo发布的安装包 ,注意要使用extended版本 ,我下载的就是 hugo_extended_0.96.0_Windows-64bit.zip
将下载的hugo解压,并把解压得到的内容放到 C:\Hugo\bin
下。
为了方便使用hugo.exe,我们把C:\Hugo\bin
添加到环境变量PATH中。
4 创建网站
+准备完全后,就可以开始构建网站了。进入C:\Hugo\sites
目录,使用hugo new site 网站名
创建网站。
注意,为了方便关联Github仓库,建议网站名与Github仓库名一致,比如我站点名、Github仓库名都为 dingyufan.github.io。
5 修改主题
+Hugo拥有非常丰富的主题,可以实现各种个性化需求。我这次选择的主题是 FixIt 。
安装主题的方式有很多
注意:推荐使用第三种方式!推荐使用第三种方式!推荐使用第三种方式!
否则之后Github Pages在构建时会有异常!No url found for submodule path 'themes/FixIt' in .gitmodules
安装主题之后,如何选择使用该主题呢?修改站点下配置文件config.toml
修改其中的 theme 选项,同时可以根据FitIt主题说明 ,设置更多个性化配置。
我的config.toml文件基础配置如下:
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+
baseURL = "http://dingyufan.github.io/"
+# [en, zh-cn, fr, ...] 设置默认的语言
+defaultContentLanguage = "zh-cn"
+# 网站语言,仅在这里 CN 大写
+languageCode = "zh-CN"
+# 是否包括中日韩文字
+hasCJKLanguage = true
+# 网站标题
+title = "我的全新 Hugo 网站"
+# 文章发布位置
+publishDir = "docs"
+
+ # 更改使用 Hugo 构建网站时使用的默认主题
+theme = "FixIt"
+
+ [ params ]
+ # FixIt 主题版本
+ version = "0.2.X"
+
+ [ menu ]
+ [[ menu . main ]]
+ identifier = "posts"
+ # 你可以在名称(允许 HTML 格式)之前添加其他信息,例如图标
+ pre = ""
+ # 你可以在名称(允许 HTML 格式)之后添加其他信息,例如图标
+ post = ""
+ name = "文章"
+ url = "/posts/"
+ # 当你将鼠标悬停在此菜单链接上时,将显示的标题
+ title = ""
+ weight = 1
+ [[ menu . main ]]
+ identifier = "categories"
+ pre = ""
+ post = ""
+ name = "分类"
+ url = "/categories/"
+ title = ""
+ weight = 2
+ [[ menu . main ]]
+ identifier = "tags"
+ pre = ""
+ post = ""
+ name = "标签"
+ url = "/tags/"
+ title = ""
+ weight = 3
+
+ # Hugo 解析文档的配置
+[ markup ]
+ # 语法高亮设置 (https://gohugo.io/content-management/syntax-highlighting)
+ [ markup . highlight ]
+ # false 是必要的设置 (https://github.com/Lruihao/FixIt/issues/43)
+ noClasses = false
6 写文章
+创建文章。hugo new posts/文件名.md
, 会在站点的 content/post路径下创建一个md文件。这就是要写的博文。
注意,默认创建的md文件是草稿。草稿在构建网站时是会被忽略的。所以一定要在发布前,修改文件头中 draft为false 。
在里面写一行 hello github pages
可以通过hugo serve
命令启动本地预览,通过localhost:1313即可访问预览。
看到预览效果
7 发布网站
+Hugo默认会把构建后的网站放在 public 文件夹下,但是为了方用github统一管理 发布前、后的内容,我想要将构建后网站路径,修改为放在docs文件夹下。
可以通过-d docs
指定构建后放在docs文件夹下。也可以在config.toml中添加一行配置publishDir = "docs"
,这样就不需要在构建时使用 -d ,直接执行 hugo
就可以完成侯建并放入docs文件夹。
构建网站之后,需要将网站托管到Github上,即 上传到刚开始创建的空仓库里。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+
git init
+ git add -A
+ git commit -m 'init'
+ git branch -M main
+ git remote add origin git@githubcom:dingyufan/dingyufan.github.io.git
+ git push -u origin main
8 Github Pages配置
+提交代码之后,需要修改Github Pages配置。指定Github Pages发布docs文件夹下的内容。
这样,仓库里的内容就是hugo生成的site的全部内容,docs文件夹下则是构建后的静态页面。这样可以对这个site完整的进行版本控制。
9 绑定域名
+在完成以上步骤后,已经可以通过 http://dingyufan.github.io
访问到网站了。
Github Pages还支持自定义域名。我希望通能过自己的域名 http://blog.dingyufan.cn
访问
配置后,要在域名注册商那里修改 域名解析规则。添加CNAME记录类型。
待解析生效之后,即可通过自定义域名访问。
\ No newline at end of file
diff --git "a/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.md" "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.md"
new file mode 100644
index 00000000..6c939b01
--- /dev/null
+++ "b/posts/githubpages-hugo\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.md"
@@ -0,0 +1,246 @@
+# GitHub Pages + Hugo搭建个人博客
+
+
+<!--more-->
+
+## 0 前言
+
+去年年底开始,就一直有写博客的想法,一方面来沉淀自己的技术,另一方面也希望能对他人有些小小的帮助。如今各种平台非常的多,但是不是广告铺天盖地,就是样式固定,不够个性化。最终决定借助Github Pages来搭建一个属于自己的个性化博客。
+
+[Github Pages](https://pages.github.com/) 是由GitHub提供的一个免费构建网站的方式。通过创建名为`github用户名.github.io`的仓库,上传文件后,就可以通过 `http://github用户名.github.io`访问你的网站。
+
+GIthub Pages默认使用[Jekyll](https://jekyllrb.com/)构建静态网站,由于Jekyll依赖于Ruby,并且个人对Ruby完全不熟悉,所以最终选择了基于Golang的静态网站生成器[Hugo](https://gohugo.io/)来生成静态网站,然后托管在Github上。
+
+
+
+## 1 创建github仓库
+
+创建一个 名为`github用户名.github.io`的仓库,`github用户名`填写自己的GIthub用户名。比如我,仓库名就是`dingyufan.github.io`。
+
+![create-repository](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/create-repository.png)
+
+创建之后,是一个空仓库,先放着暂时不管。
+
+
+
+## 2 安装Golang
+
+Windows系统安装Golang非常简单,[下载msi安装包](https://studygolang.com/dl),直接安装即可。默认会安装到 `C:\Go` 目录下,同时`C:\Go\bin` 目录也会被添加到PATH 环境变量中。可以通过`go version`验证安装的版本。目前最新版本应该是go1.18,我是之前安装的老版本。
+
+![微信截图_20220410185748](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220410185748.png)
+
+
+
+## 3 安装hugo
+
+创建文件夹`C:\Hugo`、`C:\Hugo\bin`、`C:\Hugo\sites`。其中bin目录用来存放hugo可执行程序,sites目录则用来保存站点。
+
+![微信截图_20220409125654](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409125654.png)
+
+
+
+从Github下载[Hugo发布的安装包](https://github.com/gohugoio/hugo/releases),注意要使用**extended版本**,我下载的就是 hugo_extended_0.96.0_Windows-64bit.zip
+
+![微信截图_20220409130146](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409130146.png)
+
+
+
+将下载的hugo解压,并把解压得到的内容放到 `C:\Hugo\bin`下。
+
+![微信截图_20220409125710](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409125710.png)
+
+为了方便使用hugo.exe,我们把`C:\Hugo\bin`添加到环境变量PATH中。
+
+![image-20220410191852967](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410191852967.png)
+
+
+
+
+
+## 4 创建网站
+
+准备完全后,就可以开始构建网站了。进入`C:\Hugo\sites`目录,使用`hugo new site 网站名`创建网站。
+
+注意,为了方便关联Github仓库,建议网站名与Github仓库名一致,比如我站点名、Github仓库名都为 dingyufan.github.io。
+
+![微信截图_20220409130406](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409130406.png)
+
+
+
+## 5 修改主题
+
+Hugo拥有非常丰富的主题,可以实现各种个性化需求。我这次选择的主题是 [FixIt](https://github.com/Lruihao/FixIt)。
+
+安装主题的方式有很多
+
+- 一:可以直接下载主题,放置在站点的 themes文件夹下。需要注意的是,下载的主题要放在themes下和主题同名的文件夹内。比如FixIt,放置在`C:\Hugo\sites\dingyufan.github.io\themes\FixIt`中,文件夹不存在则自行创建;
+
+- 二:可以把主题克隆到 `themes` 目录。
+
+ ```bash
+ cd C:/Hugo/sites/dingyufan.github.io
+ git clone https://github.com/Lruihao/FixIt.git themes/FixIt
+ ```
+
+- **三:初始化仓库,并将主题仓库作为你的网站目录的子模块**。
+
+ ```bash
+ cd C:/Hugo/sites/dingyufan.github.io
+ git init
+ git submodule add https://github.com/Lruihao/FixIt.git themes/FixIt
+ ```
+
+
+
+注意:**推荐使用第三种方式!推荐使用第三种方式!推荐使用第三种方式!**
+
+否则之后Github Pages在构建时会有异常!<font color="red">`No url found for submodule path 'themes/FixIt' in .gitmodules`</font>
+
+
+
+安装主题之后,如何选择使用该主题呢?修改站点下配置文件config.toml
+
+![image-20220410195927557](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410195927557.png)
+
+修改其中的 theme 选项,同时可以根据[FitIt主题说明](https://fixit.lruihao.cn/zh-cn/theme-documentation-basics/),设置更多个性化配置。
+
+我的config.toml文件基础配置如下:
+
+```toml
+baseURL = "http://dingyufan.github.io/"
+# [en, zh-cn, fr, ...] 设置默认的语言
+defaultContentLanguage = "zh-cn"
+# 网站语言,仅在这里 CN 大写
+languageCode = "zh-CN"
+# 是否包括中日韩文字
+hasCJKLanguage = true
+# 网站标题
+title = "我的全新 Hugo 网站"
+# 文章发布位置
+publishDir = "docs"
+
+# 更改使用 Hugo 构建网站时使用的默认主题
+theme = "FixIt"
+
+[params]
+ # FixIt 主题版本
+ version = "0.2.X"
+
+[menu]
+ [[menu.main]]
+ identifier = "posts"
+ # 你可以在名称(允许 HTML 格式)之前添加其他信息,例如图标
+ pre = ""
+ # 你可以在名称(允许 HTML 格式)之后添加其他信息,例如图标
+ post = ""
+ name = "文章"
+ url = "/posts/"
+ # 当你将鼠标悬停在此菜单链接上时,将显示的标题
+ title = ""
+ weight = 1
+ [[menu.main]]
+ identifier = "categories"
+ pre = ""
+ post = ""
+ name = "分类"
+ url = "/categories/"
+ title = ""
+ weight = 2
+ [[menu.main]]
+ identifier = "tags"
+ pre = ""
+ post = ""
+ name = "标签"
+ url = "/tags/"
+ title = ""
+ weight = 3
+
+# Hugo 解析文档的配置
+[markup]
+ # 语法高亮设置 (https://gohugo.io/content-management/syntax-highlighting)
+ [markup.highlight]
+ # false 是必要的设置 (https://github.com/Lruihao/FixIt/issues/43)
+ noClasses = false
+```
+
+
+
+## 6 写文章
+
+创建文章。`hugo new posts/文件名.md`, 会在站点的 content/post路径下创建一个md文件。这就是要写的博文。
+
+![微信截图_20220409191339](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191339.png)
+
+注意,默认创建的md文件是草稿。草稿在构建网站时是会被忽略的。所以**一定要在发布前,修改文件头中 draft为false**。
+
+在里面写一行 hello github pages
+
+![微信截图_20220409191751](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191751.png)
+
+
+
+可以通过`hugo serve`命令启动本地预览,通过localhost:1313即可访问预览。
+
+![微信截图_20220409191826](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191826.png)
+
+
+
+看到预览效果
+
+![微信截图_20220409191900](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191900.png)
+
+
+
+## 7 发布网站
+
+Hugo默认会把构建后的网站放在 public 文件夹下,但是为了方用github统一管理 发布前、后的内容,我想要将构建后网站路径,修改为放在docs文件夹下。
+
+可以通过`-d docs`指定构建后放在docs文件夹下。**也可以在config.toml中添加一行配置`publishDir = "docs"`,这样就不需要在构建时使用 -d ,直接执行 `hugo`就可以完成侯建并放入docs文件夹。**
+
+![微信截图_20220409191950](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220409191950.png)
+
+
+
+构建网站之后,需要将网站托管到Github上,即 上传到刚开始创建的空仓库里。
+
+```bash
+git init
+git add -A
+git commit -m 'init'
+git branch -M main
+git remote add origin git@githubcom:dingyufan/dingyufan.github.io.git
+git push -u origin main
+```
+
+
+
+## 8 Github Pages配置
+
+提交代码之后,需要修改Github Pages配置。指定Github Pages发布docs文件夹下的内容。
+
+![image-20220410202202488](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410202202488.png)
+
+这样,仓库里的内容就是hugo生成的site的全部内容,docs文件夹下则是构建后的静态页面。这样可以对这个site完整的进行版本控制。
+
+
+
+## 9 绑定域名
+
+在完成以上步骤后,已经可以通过 `http://dingyufan.github.io`访问到网站了。
+
+Github Pages还支持自定义域名。我希望通能过自己的域名 `http://blog.dingyufan.cn` 访问
+
+![image-20220410202804130](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410202804130.png)
+
+配置后,要在域名注册商那里修改 域名解析规则。添加CNAME记录类型。
+
+![image-20220410203010269](https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/image-20220410203010269.png)
+
+待解析生效之后,即可通过自定义域名访问。
+
+
+---
+
+> 作者:
+> URL: https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/
+
diff --git a/posts/index.html b/posts/index.html
new file mode 100644
index 00000000..f8de5b1a
--- /dev/null
+++ b/posts/index.html
@@ -0,0 +1,9 @@
+所有文章 - dingyufan's blog
+
\ No newline at end of file
diff --git a/posts/index.xml b/posts/index.xml
new file mode 100644
index 00000000..626eda25
--- /dev/null
+++ b/posts/index.xml
@@ -0,0 +1 @@
+所有文章 - dingyufan's blog https://dingyufan.github.io/posts/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Sat, 19 Oct 2024 01:35:51 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/ Tech 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 JDK8日期时间API导致Dubbo调用StackOverflow错误 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/Sat, 30 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/ Coding JDK8日期时间API导致Dubbo调用StackOverflow错误 GitHub Pages + Hugo搭建个人博客 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/Sun, 10 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/ Tech GitHub Pages + Hugo搭建个人博客
\ No newline at end of file
diff --git a/posts/page/1/index.html b/posts/page/1/index.html
new file mode 100644
index 00000000..521f0e7d
--- /dev/null
+++ b/posts/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/posts/
+
\ No newline at end of file
diff --git "a/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html" "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html"
new file mode 100644
index 00000000..30bb7060
--- /dev/null
+++ "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html"
@@ -0,0 +1,151 @@
+浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 - dingyufan's blog
+2023-09-09 2024-10-19 约 3100 字
0 前言
+树莓派(Raspberry Pi )是一种基于ARM架构的单板计算机,仅有银行卡大小,本意是提供一种低成本的计算机学习硬件。但前几年由于各种原因,价格一路高歌猛进,让人望而却步。最近逛淘宝发现,树莓派价格差不多回归了正常架构,于是果断下手买一个尝尝。
1 安装 Ubuntu22.04
+到手的是 树莓派4B 4G内存版,因为内存足够,准备安装带GUI界面的 Ubuntu Desktop 22.04 。
1.1 烧录操作系统
+树莓派的存储是在一张SD卡上,所以需要通过 Raspberry Pi Imager 将操作系统烧录到SD卡中。
不需要提前下载镜像,直接可以在工具中选择系统,然后选中目标SD卡,点击烧录。
1
+ 2
+ 3
+
> Other general-purpose OS
+ > Ubuntu
+ > Ubuntu Desktop 22.04.3 LTS (64-bit)
烧录完成后,将SD卡插入树莓派,通电启动即可。系统安装后,即可通过 micro HMDI 连接到显示设备,并使用键盘、鼠标进行操作。首次启动会引导设置语言时区、用户名主机名等信息,不在此赘述。
1.2 Ubuntu软件源修改
+树莓派切换镜像源的方式和一般Ubuntu设备并无不同,唯一需要注意的是 要使用 ubuntu-ports 镜像 ,这里面才有arm64的源。
我们使用清华镜像站 ,页面比较友好,可以直接在页面切换选项并产生配置内容。
备份原 sources.list
,将清华源信息覆写到 sources.list
,执行 sudo apt update
更新软件包信息。
1.3 固定网络IP
+为了方便ssh登录,以及作为网络代理服务器,需要设置一个固定的IP地址。
编辑netplan配置文件 /etc/netplan/01-network-manager-all.yaml
。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+
network :
+ version : 2
+ renderer : NetworkManager
+ # 无线网络配置
+ wifis :
+ # 无线网卡 wlan0
+ wlan0 :
+ # 关闭dhcp
+ dhcp4 : false
+ # 固定IP, /24 用来表示掩码信息
+ addresses : [ 192.168.31.250 /24]
+ optional : true
+ # 配置项 gateway4 在新版本中已弃用,通过routes配置网关
+ routes :
+ - to : default
+ via : 192.168.31.1
+ # DNS服务
+ nameservers :
+ addresses : [ 114.114.114.114 ]
+ # 配置WiFi信息
+ access-points :
+ "WiFi名称" :
+ password : 'WiFi密码'
配置修改完成后,使用 sudo netplan try
测试网络配置,无异常即可使用 sudo netplan apply
应用配置。
1.4 安装 openssh-server
+配置完成后,发现无法ssh访问,发现默认没有安装 openssh-server,通过apt安装。安装后即可远程ssh访问。
1
+
sudo apt install openssh-server
2 Clash
+Clash 是一个基于规则的代理工具,主要的用途不多说了,如果你知道这个软件,就说明你大概率有这个需求。这个安装方式也不限于树莓派设备,只要是运行Linux系统的设备,都可以
这次之所以选择带GUI的Ubuntu,很大一部分原因就是想要通过图形化界面,比较方便的配置Clash。
2.1 Clash for Windows
+Clash for Windows 是比较好用的一个 Clash GUI 客户端。
但是!它这个名字太具有迷惑性了!
它其实 支持 Windows系统!也支持 Linux!还支持 macOS !
从GitHub release页面下载,Clash.for.Windows-0.20.34-arm64-linux.tar.gz
,从它提供的各种安装包也能看出支持多平台
下载后解压,运行 cfw
,即可出现GUI界面,然后开始进行配置。
在左侧 General 菜单 中,打开 Allow LAN开关,允许局域网连接 。这样不止为本机提供网络代理,也能为局域网其他设备提供代理。 在左侧 Profiles 菜单 中,将机场提供的订阅地址填入输入框,下载得到代理规则 。config.yaml为默认配置,sub为订阅到的规则。 到这为止,Clash for Windows就已经准备完毕了,随时可以使用了。
2.2 Clash(无图形界面)
+如是是无图形界面的情况,可以通过命令安装Clash。
下载Clash,注意版本,这里以树莓派使用的ARM版本为例
1
+ 2
+
mkdir ~/clash && cd ~/clash
+ wget https://github.com/Dreamacro/clash/releases/download/v1.18.0/clash-linux-armv7-v1.18.0.gz
解压后重命名
1
+ 2
+
gunzip clash-linux-armv7-v1.18.0.gz
+ mv clash-linux-armv7-v1.18.0 clash
移动clash到指定位置,并赋予执行权限
1
+ 2
+
sudo cp clash /usr/local/bin
+ sudo chmod +x /usr/local/bin/clash
初始化执行,自动生成config.yaml 和 Country.mmdb
1
+ 2
+ 3
+ 4
+ 5
+
sudo /usr/local/bin/clash -d /etc/clash/
+ # 输出
+# INFO[0000] Can't find config, create a initial config file
+# INFO[0000] Can't find MMDB, start download
+# INFO[0003] inbound mixed://127.0.0.1:7890 create success.
ctrl+c中断执行,修改config.yaml 。这个就替换成你机场Clash订阅地址返回的yaml代理站点内容。
需要注意的是,在config.yaml中有几个配置,可能需要自己修改一下,因为机场提供的值可能不是你想要的
关于端口
代理端口分为 http代理端口(port)
、socks代理端口(socks-port)
和 混合代理端口 (mixed-port)
。这里建议直接将混合代理端口 (mixed-port)
设置为7890,其他两项设为0。这样不管是http、socks都是用同一个7890端口,更加方便
关于外部控制
Clash是支持通过 RESTful 接口来对Clash进行功能配置的,目前市面上的GUI客户端配置管理也是基于这个方式。
config.yaml中有一项配置 external-controller
,修改为 0.0.0.0:9090
这样方便我从局域网其他设备对其进行控制。
还有一项配置 secret
主要用于 RESTful 控制接口的身份认证密码。如果是局域网不对外的话,可暂时不配。
clash.razord.top
对于没有GUI的Clash,可以在其他设备上通过这个网站,获得一个控制的Dashboard。
网站打开时会让填写 Host、端口、秘钥,填写后就可以借助这个网页,对无图形界面设备上安装的Clash进行配置。具体原理的话就是页面发起配置对应功能的http请求。
代理配置完后,创建systemd 配置文件 /etc/systemd/system/clash.service
,让我们的Clash能够开机自启。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+
[Unit]
+Description = Clash 守护进程, Go 语言实现的基于规则的代理.
+After = network-online.target
+
+ [Service]
+Type = simple
+Restart = always
+ExecStart = /usr/local/bin/clash -d /etc/clash
+
+ [Install]
+WantedBy = multi-user.target
重新加载 systemd,设置开机启动 和 立即启动
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+
sudo systemctl daemon-reload
+ # 设置系统启动时启动clash
+sudo systemctl enable clash
+ # 立即启动clash
+sudo systemctl start clash
+ # 查看clash运行状态
+sudo systemctl status clash
到这为止,Clash就已经准备完毕了,随时可以使用了。
2.3 使用方式
+如果你使用的设备安装了SSR、V2ray或者Clash,那你可以直接在软件界面上操作,通过界面提供的功能来开启系统代理。
这里主要是介绍一下没有安装代理软件的设备,如何借助局域网内其他已安装的代理设备(即代理服务器),实现网络代理功能。
2.3.1 Windows 10 设备
+在 Window 10 设置 > 网络和Internet > 代理 中,打开【使用代理服务器】开关,地址填树莓派IP地址,端口为Clash使用的7890端口。
也可以设置对 本地地址(localhost)、常见的局域网网段(192.168.x.x) 不使用代理服务器。
2.3.2 手机、iPad等无线设备
+进入 无线局域网设置(WLAN设置),点击已连接的无线网络,将网络详情页面拉到底,有HTTP代理设置,填入树莓派IP地址,Clash的7890端口保存即可。
2.3.3 Ubuntu/Linux
+如果是带有GUI界面的操作系统,直接在界面上配置,以Ubuntu为例,在 Settings > Network > Network Proxy 中,将代理配置中填入树莓派IP地址和Clash的7890端口保存即可
如果是没有界面的Linux系统,可以通过设置环境变量的方式设置
1
+ 2
+ 3
+
export https_proxy = http://192.168.31.250:7890
+export http_proxy = http://192.168.31.250:7890
+export all_proxy = socks5://192.168.31.250:7890
2.3.4 按需配置
+总的来说,我个人并不喜欢为系统配置全局网络代理,我更喜欢给有需要的程序配置网络代理 。举个几个例子:
容器运行时containerd
使用containerd时,有些镜像国内无法拉取,而且国内没有厂商同步这些镜像,这就可以为containerd配置代理服务器,来帮助拉取这些镜像。
1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+
sudo mkdir -p /etc/systemd/system/containerd.service.d/
+
+ cat << EOF | sudo tee -a /etc/systemd/system/containerd.service.d/http-proxy.conf
+ [Service]
+ Environment="HTTP_PROXY=192.168.31.250:7890"
+ Environment="HTTPS_PROXY=192.168.31.250:7890"
+ EOF
+
+ sudo systemctl restart containerd
proxychains4
这是一个很简单、好用的代理工工具。你只需要通过apt安装
1
+
sudo apt install proxychains4
安装后修改配置文件 /etc/proxychains.config
,将文件最后的代理地址改为代理服务器的地址,比如 socks5 192.168.31.250 7890
即可。
使用方法的话,只需要在原本命令前添加 proxychains4 即可
1
+ 2
+ 3
+
proxychains4 wget xxx
+ proxychains4 curl xxx
+ proxychains4 git clone xxx
\ No newline at end of file
diff --git "a/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.md" "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.md"
new file mode 100644
index 00000000..6fc1fae5
--- /dev/null
+++ "b/posts/\346\265\205\345\260\235\346\240\221\350\216\223\346\264\2764bubuntu22.04\345\256\211\350\243\205clash\344\275\234\344\270\272\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.md"
@@ -0,0 +1,316 @@
+# 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器
+
+
+<!--more-->
+
+
+
+## 0 前言
+
+树莓派([Raspberry Pi](https://www.raspberrypi.com/))是一种基于ARM架构的单板计算机,仅有银行卡大小,本意是提供一种低成本的计算机学习硬件。但前几年由于各种原因,价格一路高歌猛进,让人望而却步。最近逛淘宝发现,树莓派价格差不多回归了正常架构,于是果断下手买一个尝尝。
+
+<img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309091517163.jpg" alt="1694243814807的副本" style="zoom:50%;" />
+
+
+
+## 1 安装 Ubuntu22.04
+
+到手的是 树莓派4B 4G内存版,因为内存足够,准备安装带GUI界面的 Ubuntu Desktop 22.04 。
+
+
+
+### 1.1 烧录操作系统
+
+树莓派的存储是在一张SD卡上,所以需要通过 [Raspberry Pi Imager](https://www.raspberrypi.com/software/) 将操作系统烧录到SD卡中。
+
+不需要提前下载镜像,直接可以在工具中选择系统,然后选中目标SD卡,点击烧录。
+
+```txt
+> Other general-purpose OS
+ > Ubuntu
+ > Ubuntu Desktop 22.04.3 LTS (64-bit)
+```
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309091548861.png" alt="截屏2023-09-09 15.48.11" style="zoom:50%;" /></div>
+
+
+
+烧录完成后,将SD卡插入树莓派,通电启动即可。系统安装后,即可通过 micro HMDI 连接到显示设备,并使用键盘、鼠标进行操作。首次启动会引导设置语言时区、用户名主机名等信息,不在此赘述。
+
+
+
+### 1.2 Ubuntu软件源修改
+
+树莓派切换镜像源的方式和一般Ubuntu设备并无不同,唯一需要注意的是 **要使用 ubuntu-ports 镜像**,这里面才有arm64的源。
+
+我们使用[清华镜像站](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu-ports/),页面比较友好,可以直接在页面切换选项并产生配置内容。
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309091610222.png" style="zoom:50%;" /></div>
+
+备份原 `sources.list`,将清华源信息覆写到 `sources.list`,执行 `sudo apt update ` 更新软件包信息。
+
+
+
+### 1.3 固定网络IP
+
+为了方便ssh登录,以及作为网络代理服务器,需要设置一个固定的IP地址。
+
+编辑netplan配置文件 `/etc/netplan/01-network-manager-all.yaml`。
+
+```yaml
+network:
+ version: 2
+ renderer: NetworkManager
+ # 无线网络配置
+ wifis:
+ # 无线网卡 wlan0
+ wlan0:
+ # 关闭dhcp
+ dhcp4: false
+ # 固定IP, /24 用来表示掩码信息
+ addresses: [192.168.31.250/24]
+ optional: true
+ # 配置项 gateway4 在新版本中已弃用,通过routes配置网关
+ routes:
+ - to: default
+ via: 192.168.31.1
+ # DNS服务
+ nameservers:
+ addresses: [114.114.114.114]
+ # 配置WiFi信息
+ access-points:
+ "WiFi名称":
+ password: 'WiFi密码'
+```
+
+配置修改完成后,使用 `sudo netplan try` 测试网络配置,无异常即可使用 `sudo netplan apply` 应用配置。
+
+
+
+### 1.4 安装 openssh-server
+
+配置完成后,发现无法ssh访问,发现默认没有安装 openssh-server,通过apt安装。安装后即可远程ssh访问。
+
+```shell
+sudo apt install openssh-server
+```
+
+
+
+## 2 Clash
+
+[Clash](https://github.com/Dreamacro/clash) 是一个基于规则的代理工具,主要的用途不多说了,如果你知道这个软件,就说明你大概率有这个需求。这个安装方式也不限于树莓派设备,只要是运行Linux系统的设备,都可以
+
+
+
+这次之所以选择带GUI的Ubuntu,很大一部分原因就是想要通过图形化界面,比较方便的配置Clash。
+
+
+
+### 2.1 Clash for Windows
+
+[Clash for Windows](https://github.com/Fndroid/clash_for_windows_pkg/releases) 是比较好用的一个 Clash GUI 客户端。
+
+***<font color='red'>但是!它这个名字太具有迷惑性了!</font>***
+
+***<font color='red'>它其实 支持 Windows系统!也支持 Linux!还支持 macOS !</font>***
+
+从GitHub release页面下载,`Clash.for.Windows-0.20.34-arm64-linux.tar.gz`,从它提供的各种安装包也能看出支持多平台
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309091901250.png" alt="截屏2023-09-09 18.58.02" style="zoom:55%;" /></div>
+
+下载后解压,运行 `cfw`,即可出现GUI界面,然后开始进行配置。
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309091910073.png" alt="clashforwin-0" style="zoom:100%;" /></div>
+
+1. 在左侧 **General 菜单**中,**打开 Allow LAN开关,允许局域网连接**。这样不止为本机提供网络代理,也能为局域网其他设备提供代理。
+2. 在左侧 **Profiles 菜单**中,**将机场提供的订阅地址填入输入框,下载得到代理规则**。config.yaml为默认配置,sub为订阅到的规则。
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309091928725.png" alt="202309091916624" style="zoom:100%;" /></div>
+
+到这为止,Clash for Windows就已经准备完毕了,随时可以使用了。
+
+
+
+### 2.2 Clash(无图形界面)
+
+如是是无图形界面的情况,可以通过命令安装Clash。
+
+下载Clash,注意版本,这里以树莓派使用的ARM版本为例
+
+```shell
+mkdir ~/clash && cd ~/clash
+wget https://github.com/Dreamacro/clash/releases/download/v1.18.0/clash-linux-armv7-v1.18.0.gz
+```
+
+解压后重命名
+
+```shell
+gunzip clash-linux-armv7-v1.18.0.gz
+mv clash-linux-armv7-v1.18.0 clash
+```
+
+移动clash到指定位置,并赋予执行权限
+
+```shell
+sudo cp clash /usr/local/bin
+sudo chmod +x /usr/local/bin/clash
+```
+
+初始化执行,自动生成config.yaml 和 Country.mmdb
+
+```shell
+sudo /usr/local/bin/clash -d /etc/clash/
+# 输出
+# INFO[0000] Can't find config, create a initial config file
+# INFO[0000] Can't find MMDB, start download
+# INFO[0003] inbound mixed://127.0.0.1:7890 create success.
+```
+
+ctrl+c中断执行,**修改config.yaml**。这个就替换成你机场Clash订阅地址返回的yaml代理站点内容。
+
+**<font color='red'>需要注意的是,在config.yaml中有几个配置,可能需要自己修改一下,因为机场提供的值可能不是你想要的</font>**
+
+1. **关于端口**
+
+ 代理端口分为 `http代理端口(port)`、`socks代理端口(socks-port)` 和 `混合代理端口 (mixed-port)`。这里建议直接将`混合代理端口 (mixed-port)`设置为7890,其他两项设为0。这样不管是http、socks都是用同一个7890端口,更加方便
+
+2. **关于外部控制**
+
+ Clash是支持通过 RESTful 接口来对Clash进行功能配置的,目前市面上的GUI客户端配置管理也是基于这个方式。
+
+ config.yaml中有一项配置 `external-controller`,修改为 `0.0.0.0:9090` 这样方便我从局域网其他设备对其进行控制。
+
+ 还有一项配置 `secret` 主要用于 RESTful 控制接口的身份认证密码。如果是局域网不对外的话,可暂时不配。
+
+3. **clash.razord.top**
+
+ 对于没有GUI的Clash,可以在其他设备上通过这个网站,获得一个控制的Dashboard。
+
+ 网站打开时会让填写 Host、端口、秘钥,填写后就可以借助这个网页,对无图形界面设备上安装的Clash进行配置。具体原理的话就是页面发起配置对应功能的http请求。
+
+
+
+代理配置完后,创建systemd 配置文件 `/etc/systemd/system/clash.service`,让我们的Clash能够开机自启。
+
+```ini
+[Unit]
+Description=Clash 守护进程, Go 语言实现的基于规则的代理.
+After=network-online.target
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/local/bin/clash -d /etc/clash
+
+[Install]
+WantedBy=multi-user.target
+```
+
+重新加载 systemd,设置开机启动 和 立即启动
+
+```shell
+sudo systemctl daemon-reload
+# 设置系统启动时启动clash
+sudo systemctl enable clash
+# 立即启动clash
+sudo systemctl start clash
+# 查看clash运行状态
+sudo systemctl status clash
+```
+
+到这为止,Clash就已经准备完毕了,随时可以使用了。
+
+
+
+### 2.3 使用方式
+
+> 如果你使用的设备安装了SSR、V2ray或者Clash,那你可以直接在软件界面上操作,通过界面提供的功能来开启系统代理。
+
+这里主要是介绍一下没有安装代理软件的设备,如何借助局域网内其他已安装的代理设备(即代理服务器),实现网络代理功能。
+
+
+
+#### 2.3.1 Windows 10 设备
+
+在 Window 10 设置 > 网络和Internet > 代理 中,打开【使用代理服务器】开关,地址填树莓派IP地址,端口为Clash使用的7890端口。
+
+也可以设置对 本地地址(localhost)、常见的局域网网段(192.168.x.x) 不使用代理服务器。
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309101055135.png" alt="VD7~4$~CB7W10@9NIW6HVFH" style="zoom: 70%;" /></div>
+
+
+
+#### 2.3.2 手机、iPad等无线设备
+
+进入 无线局域网设置(WLAN设置),点击已连接的无线网络,将网络详情页面拉到底,有HTTP代理设置,填入树莓派IP地址,Clash的7890端口保存即可。
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309101116742.jpg" alt="MTXX_PT20230910_111545372" style="zoom:15%;" /></div>
+
+
+
+#### 2.3.3 Ubuntu/Linux
+
+如果是带有GUI界面的操作系统,直接在界面上配置,以Ubuntu为例,在 Settings > Network > Network Proxy 中,将代理配置中填入树莓派IP地址和Clash的7890端口保存即可
+
+<div align=center><img src="https://dingyufan-github-io.oss-cn-hangzhou.aliyuncs.com/blog/202309101147300.png" alt="截屏2023-09-10 11.47.19" style="zoom:60%;" /></div>
+
+如果是没有界面的Linux系统,可以通过设置环境变量的方式设置
+
+```shell
+export https_proxy=http://192.168.31.250:7890
+export http_proxy=http://192.168.31.250:7890
+export all_proxy=socks5://192.168.31.250:7890
+```
+
+
+
+#### 2.3.4 按需配置
+
+总的来说,我个人并不喜欢为系统配置全局网络代理,我更喜欢**给有需要的程序配置网络代理**。举个几个例子:
+
+
+
+**容器运行时containerd**
+
+使用containerd时,有些镜像国内无法拉取,而且国内没有厂商同步这些镜像,这就可以为containerd配置代理服务器,来帮助拉取这些镜像。
+
+```shell
+sudo mkdir -p /etc/systemd/system/containerd.service.d/
+
+cat << EOF | sudo tee -a /etc/systemd/system/containerd.service.d/http-proxy.conf
+[Service]
+Environment="HTTP_PROXY=192.168.31.250:7890"
+Environment="HTTPS_PROXY=192.168.31.250:7890"
+EOF
+
+sudo systemctl restart containerd
+```
+
+
+
+**proxychains4**
+
+这是一个很简单、好用的代理工工具。你只需要通过apt安装
+
+```shell
+sudo apt install proxychains4
+```
+
+安装后修改配置文件 `/etc/proxychains.config`,将文件最后的代理地址改为代理服务器的地址,比如 `socks5 192.168.31.250 7890`即可。
+
+使用方法的话,只需要在原本命令前添加 proxychains4 即可
+
+```shell
+proxychains4 wget xxx
+proxychains4 curl xxx
+proxychains4 git clone xxx
+```
+
+
+
+---
+
+> 作者:
+> URL: https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/
+
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 00000000..311f91cd
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,100 @@
+User-agent: *
+
+Disallow: /images/
+Disallow: /js/
+Disallow: /css/
+
+Disallow: /*offline/
+Disallow: /*404.html$
+Disallow: /*.md$
+
+User-agent: MJ12bot
+Disallow: /
+
+User-agent: AhrefsBot
+Disallow: /
+
+User-agent: BLEXBot
+Disallow: /
+
+# Block SISTRIX
+User-agent: SISTRIX Crawler
+Disallow: /
+User-agent: sistrix
+Disallow: /
+User-agent: 007ac9
+Disallow: /
+User-agent: 007ac9 Crawler
+Disallow: /
+
+# Block Uptime robot
+User-agent: UptimeRobot/2.0
+Disallow: /
+
+# Block Ezooms Robot
+User-agent: Ezooms Robot
+Disallow: /
+
+# Block Perl LWP
+User-agent: Perl LWP
+Disallow: /
+
+# Block netEstate NE Crawler (+http://www.website-datenbank.de/)
+User-agent: netEstate NE Crawler (+http://www.website-datenbank.de/)
+Disallow: /
+
+# Block WiseGuys Robot
+User-agent: WiseGuys Robot
+Disallow: /
+
+# Block Turnitin Robot
+User-agent: Turnitin Robot
+Disallow: /
+
+# Block Heritrix
+User-agent: Heritrix
+Disallow: /
+
+# Block pricepi
+User-agent: pimonster
+Disallow: /
+
+User-agent: SurdotlyBot
+Disallow: /
+
+User-agent: ZoominfoBot
+Disallow: /
+
+# OpenAI, ChatGPT
+User-agent: GPTBot
+Disallow: /
+
+# Google AI (Bard, etc)
+User-agent: Google-Extended
+Disallow: /
+
+# Block common crawl
+User-agent: CCBot
+Disallow: /
+
+# Facebook
+User-agent: FacebookBot
+Disallow: /
+
+# Cohere.ai
+User-agent: cohere-ai
+Disallow: /
+
+# Perplexity
+User-agent: PerplexityBot
+Disallow: /
+
+# Anthropic
+User-agent: anthropic-ai
+Disallow: /
+
+# Claudebot by Anthropic
+User-agent: ClaudeBot
+Disallow: /
+
+Sitemap: https://dingyufan.github.io/sitemap.xml
diff --git a/sitemap.xml b/sitemap.xml
new file mode 100644
index 00000000..6a60beae
--- /dev/null
+++ b/sitemap.xml
@@ -0,0 +1 @@
+https://dingyufan.github.io/categories/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/tags/clash/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/posts/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/tags/raspberry-pi/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/tags/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/categories/tech/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/tags/ubuntu/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/categories/coding/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/tags/dubbo/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/tags/java/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/ 2023-07-28T16:32:25+08:00 weekly 0.5 https://dingyufan.github.io/tags/github-pages/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/ 2024-10-19T01:35:51+00:00 weekly 0.5 https://dingyufan.github.io/tags/hugo/ 2024-10-19T01:35:51+00:00 weekly 0.5
\ No newline at end of file
diff --git a/tags/clash/index.html b/tags/clash/index.html
new file mode 100644
index 00000000..e219c03c
--- /dev/null
+++ b/tags/clash/index.html
@@ -0,0 +1,6 @@
+Clash - 标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/clash/index.xml b/tags/clash/index.xml
new file mode 100644
index 00000000..193c563e
--- /dev/null
+++ b/tags/clash/index.xml
@@ -0,0 +1 @@
+Clash - 标签 - dingyufan's blog https://dingyufan.github.io/tags/clash/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Sat, 19 Oct 2024 01:35:51 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/ Tech 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器
\ No newline at end of file
diff --git a/tags/clash/page/1/index.html b/tags/clash/page/1/index.html
new file mode 100644
index 00000000..2991b9e7
--- /dev/null
+++ b/tags/clash/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/tags/clash/
+
\ No newline at end of file
diff --git a/tags/dubbo/index.html b/tags/dubbo/index.html
new file mode 100644
index 00000000..b3358f29
--- /dev/null
+++ b/tags/dubbo/index.html
@@ -0,0 +1,5 @@
+Dubbo - 标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/dubbo/index.xml b/tags/dubbo/index.xml
new file mode 100644
index 00000000..812bb62f
--- /dev/null
+++ b/tags/dubbo/index.xml
@@ -0,0 +1 @@
+Dubbo - 标签 - dingyufan's blog https://dingyufan.github.io/tags/dubbo/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Fri, 28 Jul 2023 16:32:25 +0800 JDK8日期时间API导致Dubbo调用StackOverflow错误 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/Sat, 30 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/ Coding JDK8日期时间API导致Dubbo调用StackOverflow错误
\ No newline at end of file
diff --git a/tags/dubbo/page/1/index.html b/tags/dubbo/page/1/index.html
new file mode 100644
index 00000000..9f3bdb3e
--- /dev/null
+++ b/tags/dubbo/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/tags/dubbo/
+
\ No newline at end of file
diff --git a/tags/github-pages/index.html b/tags/github-pages/index.html
new file mode 100644
index 00000000..636e4c4a
--- /dev/null
+++ b/tags/github-pages/index.html
@@ -0,0 +1,6 @@
+Github Pages - 标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/github-pages/index.xml b/tags/github-pages/index.xml
new file mode 100644
index 00000000..8fba825f
--- /dev/null
+++ b/tags/github-pages/index.xml
@@ -0,0 +1 @@
+Github Pages - 标签 - dingyufan's blog https://dingyufan.github.io/tags/github-pages/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Sat, 19 Oct 2024 01:35:51 +0000 GitHub Pages + Hugo搭建个人博客 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/Sun, 10 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/ Tech GitHub Pages + Hugo搭建个人博客
\ No newline at end of file
diff --git a/tags/github-pages/page/1/index.html b/tags/github-pages/page/1/index.html
new file mode 100644
index 00000000..e6c2375d
--- /dev/null
+++ b/tags/github-pages/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/tags/github-pages/
+
\ No newline at end of file
diff --git a/tags/hugo/index.html b/tags/hugo/index.html
new file mode 100644
index 00000000..1842f21c
--- /dev/null
+++ b/tags/hugo/index.html
@@ -0,0 +1,6 @@
+Hugo - 标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/hugo/index.xml b/tags/hugo/index.xml
new file mode 100644
index 00000000..921d851b
--- /dev/null
+++ b/tags/hugo/index.xml
@@ -0,0 +1 @@
+Hugo - 标签 - dingyufan's blog https://dingyufan.github.io/tags/hugo/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Sat, 19 Oct 2024 01:35:51 +0000 GitHub Pages + Hugo搭建个人博客 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/Sun, 10 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/posts/githubpages-hugo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/ Tech GitHub Pages + Hugo搭建个人博客
\ No newline at end of file
diff --git a/tags/hugo/page/1/index.html b/tags/hugo/page/1/index.html
new file mode 100644
index 00000000..34029925
--- /dev/null
+++ b/tags/hugo/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/tags/hugo/
+
\ No newline at end of file
diff --git a/tags/index.html b/tags/index.html
new file mode 100644
index 00000000..bbd423d1
--- /dev/null
+++ b/tags/index.html
@@ -0,0 +1,4 @@
+所有标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/index.xml b/tags/index.xml
new file mode 100644
index 00000000..c38fc8ae
--- /dev/null
+++ b/tags/index.xml
@@ -0,0 +1 @@
+Tags - 标签 - dingyufan's blog https://dingyufan.github.io/tags/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Sat, 19 Oct 2024 01:35:51 +0000 Clash https://dingyufan.github.io/tags/clash/Sat, 09 Sep 2023 00:00:00 +0000 https://dingyufan.github.io/tags/clash/ Clash Raspberry Pi https://dingyufan.github.io/tags/raspberry-pi/Sat, 09 Sep 2023 00:00:00 +0000 https://dingyufan.github.io/tags/raspberry-pi/ Raspberry Pi Ubuntu https://dingyufan.github.io/tags/ubuntu/Sat, 09 Sep 2023 00:00:00 +0000 https://dingyufan.github.io/tags/ubuntu/ Ubuntu Dubbo https://dingyufan.github.io/tags/dubbo/Sat, 30 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/tags/dubbo/ Dubbo Java https://dingyufan.github.io/tags/java/Sat, 30 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/tags/java/ Java Github Pages https://dingyufan.github.io/tags/github-pages/Sun, 10 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/tags/github-pages/ Github Pages Hugo https://dingyufan.github.io/tags/hugo/Sun, 10 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/tags/hugo/ Hugo
\ No newline at end of file
diff --git a/tags/java/index.html b/tags/java/index.html
new file mode 100644
index 00000000..c47c587e
--- /dev/null
+++ b/tags/java/index.html
@@ -0,0 +1,5 @@
+Java - 标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/java/index.xml b/tags/java/index.xml
new file mode 100644
index 00000000..e8138aa1
--- /dev/null
+++ b/tags/java/index.xml
@@ -0,0 +1 @@
+Java - 标签 - dingyufan's blog https://dingyufan.github.io/tags/java/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Fri, 28 Jul 2023 16:32:25 +0800 JDK8日期时间API导致Dubbo调用StackOverflow错误 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/Sat, 30 Apr 2022 11:34:02 +0800 https://dingyufan.github.io/posts/dubbo-stack-overflow-due-to-jdk8-instant/ Coding JDK8日期时间API导致Dubbo调用StackOverflow错误
\ No newline at end of file
diff --git a/tags/java/page/1/index.html b/tags/java/page/1/index.html
new file mode 100644
index 00000000..7ee29248
--- /dev/null
+++ b/tags/java/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/tags/java/
+
\ No newline at end of file
diff --git a/tags/raspberry-pi/index.html b/tags/raspberry-pi/index.html
new file mode 100644
index 00000000..82908fad
--- /dev/null
+++ b/tags/raspberry-pi/index.html
@@ -0,0 +1,6 @@
+Raspberry Pi - 标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/raspberry-pi/index.xml b/tags/raspberry-pi/index.xml
new file mode 100644
index 00000000..2748037d
--- /dev/null
+++ b/tags/raspberry-pi/index.xml
@@ -0,0 +1 @@
+Raspberry Pi - 标签 - dingyufan's blog https://dingyufan.github.io/tags/raspberry-pi/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Sat, 19 Oct 2024 01:35:51 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/ Tech 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器
\ No newline at end of file
diff --git a/tags/raspberry-pi/page/1/index.html b/tags/raspberry-pi/page/1/index.html
new file mode 100644
index 00000000..09a52dd1
--- /dev/null
+++ b/tags/raspberry-pi/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/tags/raspberry-pi/
+
\ No newline at end of file
diff --git a/tags/ubuntu/index.html b/tags/ubuntu/index.html
new file mode 100644
index 00000000..badc8eb2
--- /dev/null
+++ b/tags/ubuntu/index.html
@@ -0,0 +1,6 @@
+Ubuntu - 标签 - dingyufan's blog
+
\ No newline at end of file
diff --git a/tags/ubuntu/index.xml b/tags/ubuntu/index.xml
new file mode 100644
index 00000000..a411619f
--- /dev/null
+++ b/tags/ubuntu/index.xml
@@ -0,0 +1 @@
+Ubuntu - 标签 - dingyufan's blog https://dingyufan.github.io/tags/ubuntu/this is dingyufan's blog Hugo 0.136.2 & FixIt v0.3.13 zh-CN Sat, 19 Oct 2024 01:35:51 +0000 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/Sat, 09 Sep 2023 00:00:00 +0000 https://dingyufan.github.io/posts/%E6%B5%85%E5%B0%9D%E6%A0%91%E8%8E%93%E6%B4%BE4bubuntu22.04%E5%AE%89%E8%A3%85clash%E4%BD%9C%E4%B8%BA%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/ Tech 浅尝树莓派4B(Ubuntu22.04)安装Clash作为代理服务器
\ No newline at end of file
diff --git a/tags/ubuntu/page/1/index.html b/tags/ubuntu/page/1/index.html
new file mode 100644
index 00000000..0fccb308
--- /dev/null
+++ b/tags/ubuntu/page/1/index.html
@@ -0,0 +1,2 @@
+https://dingyufan.github.io/tags/ubuntu/
+
\ No newline at end of file