Deploying to gh-pages from - 35dbe6f7e6a81742881494fad77c8f5c8e3b6e96 🚀

This commit is contained in:
sivan 2020-03-01 14:13:54 +00:00
parent 34c86b7204
commit 54031dbe90
3 changed files with 841 additions and 64 deletions

753
heti-addon.js Normal file
View file

@ -0,0 +1,753 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Heti = factory());
}(this, (function () { 'use strict';
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var findAndReplaceDOMText = createCommonjsModule(function (module) {
/**
* findAndReplaceDOMText v 0.4.6
* @author James Padolsey http://james.padolsey.com
* @license http://unlicense.org/UNLICENSE
*
* Matches the text of a DOM node against a regular expression
* and replaces each match (or node-separated portions of the match)
* in the specified element.
*/
(function (root, factory) {
if ( module.exports) {
// Node/CommonJS
module.exports = factory();
} else {
// Browser globals
root.findAndReplaceDOMText = factory();
}
}(commonjsGlobal, function factory() {
var PORTION_MODE_RETAIN = 'retain';
var PORTION_MODE_FIRST = 'first';
var doc = document;
var hasOwn = {}.hasOwnProperty;
function escapeRegExp(s) {
return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
}
function exposed() {
// Try deprecated arg signature first:
return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments);
}
function deprecated(regex, node, replacement, captureGroup, elFilter) {
if ((node && !node.nodeType) && arguments.length <= 2) {
return false;
}
var isReplacementFunction = typeof replacement == 'function';
if (isReplacementFunction) {
replacement = (function(original) {
return function(portion, match) {
return original(portion.text, match.startIndex);
};
}(replacement));
}
// Awkward support for deprecated argument signature (<0.4.0)
var instance = findAndReplaceDOMText(node, {
find: regex,
wrap: isReplacementFunction ? null : replacement,
replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'),
prepMatch: function(m, mi) {
// Support captureGroup (a deprecated feature)
if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches';
if (captureGroup > 0) {
var cg = m[captureGroup];
m.index += m[0].indexOf(cg);
m[0] = cg;
}
m.endIndex = m.index + m[0].length;
m.startIndex = m.index;
m.index = mi;
return m;
},
filterElements: elFilter
});
exposed.revert = function() {
return instance.revert();
};
return true;
}
/**
* findAndReplaceDOMText
*
* Locates matches and replaces with replacementNode
*
* @param {Node} node Element or Text node to search within
* @param {RegExp} options.find The regular expression to match
* @param {String|Element} [options.wrap] A NodeName, or a Node to clone
* @param {String} [options.wrapClass] A classname to append to the wrapping element
* @param {String|Function} [options.replace='$&'] What to replace each match with
* @param {Function} [options.filterElements] A Function to be called to check whether to
* process an element. (returning true = process element,
* returning false = avoid element)
*/
function findAndReplaceDOMText(node, options) {
return new Finder(node, options);
}
exposed.NON_PROSE_ELEMENTS = {
br:1, hr:1,
// Media / Source elements:
script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
// Input elements
input:1, textarea:1, select:1, option:1, optgroup: 1, button:1
};
exposed.NON_CONTIGUOUS_PROSE_ELEMENTS = {
// Elements that will not contain prose or block elements where we don't
// want prose to be matches across element borders:
// Block Elements
address:1, article:1, aside:1, blockquote:1, dd:1, div:1,
dl:1, fieldset:1, figcaption:1, figure:1, footer:1, form:1, h1:1, h2:1, h3:1,
h4:1, h5:1, h6:1, header:1, hgroup:1, hr:1, main:1, nav:1, noscript:1, ol:1,
output:1, p:1, pre:1, section:1, ul:1,
// Other misc. elements that are not part of continuous inline prose:
br:1, li: 1, summary: 1, dt:1, details:1, rp:1, rt:1, rtc:1,
// Media / Source elements:
script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
// Input elements
input:1, textarea:1, select:1, option:1, optgroup:1, button:1,
// Table related elements:
table:1, tbody:1, thead:1, th:1, tr:1, td:1, caption:1, col:1, tfoot:1, colgroup:1
};
exposed.NON_INLINE_PROSE = function(el) {
return hasOwn.call(exposed.NON_CONTIGUOUS_PROSE_ELEMENTS, el.nodeName.toLowerCase());
};
// Presets accessed via `options.preset` when calling findAndReplaceDOMText():
exposed.PRESETS = {
prose: {
forceContext: exposed.NON_INLINE_PROSE,
filterElements: function(el) {
return !hasOwn.call(exposed.NON_PROSE_ELEMENTS, el.nodeName.toLowerCase());
}
}
};
exposed.Finder = Finder;
/**
* Finder -- encapsulates logic to find and replace.
*/
function Finder(node, options) {
var preset = options.preset && exposed.PRESETS[options.preset];
options.portionMode = options.portionMode || PORTION_MODE_RETAIN;
if (preset) {
for (var i in preset) {
if (hasOwn.call(preset, i) && !hasOwn.call(options, i)) {
options[i] = preset[i];
}
}
}
this.node = node;
this.options = options;
// Enable match-preparation method to be passed as option:
this.prepMatch = options.prepMatch || this.prepMatch;
this.reverts = [];
this.matches = this.search();
if (this.matches.length) {
this.processMatches();
}
}
Finder.prototype = {
/**
* Searches for all matches that comply with the instance's 'match' option
*/
search: function() {
var match;
var matchIndex = 0;
var offset = 0;
var regex = this.options.find;
var textAggregation = this.getAggregateText();
var matches = [];
var self = this;
regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex;
matchAggregation(textAggregation);
function matchAggregation(textAggregation) {
for (var i = 0, l = textAggregation.length; i < l; ++i) {
var text = textAggregation[i];
if (typeof text !== 'string') {
// Deal with nested contexts: (recursive)
matchAggregation(text);
continue;
}
if (regex.global) {
while (match = regex.exec(text)) {
matches.push(self.prepMatch(match, matchIndex++, offset));
}
} else {
if (match = text.match(regex)) {
matches.push(self.prepMatch(match, 0, offset));
}
}
offset += text.length;
}
}
return matches;
},
/**
* Prepares a single match with useful meta info:
*/
prepMatch: function(match, matchIndex, characterOffset) {
if (!match[0]) {
throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
}
match.endIndex = characterOffset + match.index + match[0].length;
match.startIndex = characterOffset + match.index;
match.index = matchIndex;
return match;
},
/**
* Gets aggregate text within subject node
*/
getAggregateText: function() {
var elementFilter = this.options.filterElements;
var forceContext = this.options.forceContext;
return getText(this.node);
/**
* Gets aggregate text of a node without resorting
* to broken innerText/textContent
*/
function getText(node) {
if (node.nodeType === Node.TEXT_NODE) {
return [node.data];
}
if (elementFilter && !elementFilter(node)) {
return [];
}
var txt = [''];
var i = 0;
if (node = node.firstChild) do {
if (node.nodeType === Node.TEXT_NODE) {
txt[i] += node.data;
continue;
}
var innerText = getText(node);
if (
forceContext &&
node.nodeType === Node.ELEMENT_NODE &&
(forceContext === true || forceContext(node))
) {
txt[++i] = innerText;
txt[++i] = '';
} else {
if (typeof innerText[0] === 'string') {
// Bridge nested text-node data so that they're
// not considered their own contexts:
// I.e. ['some', ['thing']] -> ['something']
txt[i] += innerText.shift();
}
if (innerText.length) {
txt[++i] = innerText;
txt[++i] = '';
}
}
} while (node = node.nextSibling);
return txt;
}
},
/**
* Steps through the target node, looking for matches, and
* calling replaceFn when a match is found.
*/
processMatches: function() {
var matches = this.matches;
var node = this.node;
var elementFilter = this.options.filterElements;
var startPortion,
endPortion,
innerPortions = [],
curNode = node,
match = matches.shift(),
atIndex = 0, // i.e. nodeAtIndex
portionIndex = 0,
doAvoidNode,
nodeStack = [node];
out: while (true) {
if (curNode.nodeType === Node.TEXT_NODE) {
if (!endPortion && curNode.length + atIndex >= match.endIndex) {
// We've found the ending
// (Note that, in the case of a single portion, it'll be an
// endPortion, not a startPortion.)
endPortion = {
node: curNode,
index: portionIndex++,
text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex),
// If it's the first match (atIndex==0) we should just return 0
indexInMatch: atIndex === 0 ? 0 : atIndex - match.startIndex,
indexInNode: match.startIndex - atIndex,
endIndexInNode: match.endIndex - atIndex,
isEnd: true
};
} else if (startPortion) {
// Intersecting node
innerPortions.push({
node: curNode,
index: portionIndex++,
text: curNode.data,
indexInMatch: atIndex - match.startIndex,
indexInNode: 0 // always zero for inner-portions
});
}
if (!startPortion && curNode.length + atIndex > match.startIndex) {
// We've found the match start
startPortion = {
node: curNode,
index: portionIndex++,
indexInMatch: 0,
indexInNode: match.startIndex - atIndex,
endIndexInNode: match.endIndex - atIndex,
text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex)
};
}
atIndex += curNode.data.length;
}
doAvoidNode = curNode.nodeType === Node.ELEMENT_NODE && elementFilter && !elementFilter(curNode);
if (startPortion && endPortion) {
curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion);
// processMatches has to return the node that replaced the endNode
// and then we step back so we can continue from the end of the
// match:
atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode);
startPortion = null;
endPortion = null;
innerPortions = [];
match = matches.shift();
portionIndex = 0;
if (!match) {
break; // no more matches
}
} else if (
!doAvoidNode &&
(curNode.firstChild || curNode.nextSibling)
) {
// Move down or forward:
if (curNode.firstChild) {
nodeStack.push(curNode);
curNode = curNode.firstChild;
} else {
curNode = curNode.nextSibling;
}
continue;
}
// Move forward or up:
while (true) {
if (curNode.nextSibling) {
curNode = curNode.nextSibling;
break;
}
curNode = nodeStack.pop();
if (curNode === node) {
break out;
}
}
}
},
/**
* Reverts ... TODO
*/
revert: function() {
// Reversion occurs backwards so as to avoid nodes subsequently
// replaced during the matching phase (a forward process):
for (var l = this.reverts.length; l--;) {
this.reverts[l]();
}
this.reverts = [];
},
prepareReplacementString: function(string, portion, match) {
var portionMode = this.options.portionMode;
if (
portionMode === PORTION_MODE_FIRST &&
portion.indexInMatch > 0
) {
return '';
}
string = string.replace(/\$(\d+|&|`|')/g, function($0, t) {
var replacement;
switch(t) {
case '&':
replacement = match[0];
break;
case '`':
replacement = match.input.substring(0, match.startIndex);
break;
case '\'':
replacement = match.input.substring(match.endIndex);
break;
default:
replacement = match[+t] || '';
}
return replacement;
});
if (portionMode === PORTION_MODE_FIRST) {
return string;
}
if (portion.isEnd) {
return string.substring(portion.indexInMatch);
}
return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length);
},
getPortionReplacementNode: function(portion, match) {
var replacement = this.options.replace || '$&';
var wrapper = this.options.wrap;
var wrapperClass = this.options.wrapClass;
if (wrapper && wrapper.nodeType) {
// Wrapper has been provided as a stencil-node for us to clone:
var clone = doc.createElement('div');
clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper);
wrapper = clone.firstChild;
}
if (typeof replacement == 'function') {
replacement = replacement(portion, match);
if (replacement && replacement.nodeType) {
return replacement;
}
return doc.createTextNode(String(replacement));
}
var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper;
if (el && wrapperClass) {
el.className = wrapperClass;
}
replacement = doc.createTextNode(
this.prepareReplacementString(
replacement, portion, match
)
);
if (!replacement.data) {
return replacement;
}
if (!el) {
return replacement;
}
el.appendChild(replacement);
return el;
},
replaceMatch: function(match, startPortion, innerPortions, endPortion) {
var matchStartNode = startPortion.node;
var matchEndNode = endPortion.node;
var precedingTextNode;
var followingTextNode;
if (matchStartNode === matchEndNode) {
var node = matchStartNode;
if (startPortion.indexInNode > 0) {
// Add `before` text node (before the match)
precedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode));
node.parentNode.insertBefore(precedingTextNode, node);
}
// Create the replacement node:
var newNode = this.getPortionReplacementNode(
endPortion,
match
);
node.parentNode.insertBefore(newNode, node);
if (endPortion.endIndexInNode < node.length) { // ?????
// Add `after` text node (after the match)
followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode));
node.parentNode.insertBefore(followingTextNode, node);
}
node.parentNode.removeChild(node);
this.reverts.push(function() {
if (precedingTextNode === newNode.previousSibling) {
precedingTextNode.parentNode.removeChild(precedingTextNode);
}
if (followingTextNode === newNode.nextSibling) {
followingTextNode.parentNode.removeChild(followingTextNode);
}
newNode.parentNode.replaceChild(node, newNode);
});
return newNode;
} else {
// Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order)
precedingTextNode = doc.createTextNode(
matchStartNode.data.substring(0, startPortion.indexInNode)
);
followingTextNode = doc.createTextNode(
matchEndNode.data.substring(endPortion.endIndexInNode)
);
var firstNode = this.getPortionReplacementNode(
startPortion,
match
);
var innerNodes = [];
for (var i = 0, l = innerPortions.length; i < l; ++i) {
var portion = innerPortions[i];
var innerNode = this.getPortionReplacementNode(
portion,
match
);
portion.node.parentNode.replaceChild(innerNode, portion.node);
this.reverts.push((function(portion, innerNode) {
return function() {
innerNode.parentNode.replaceChild(portion.node, innerNode);
};
}(portion, innerNode)));
innerNodes.push(innerNode);
}
var lastNode = this.getPortionReplacementNode(
endPortion,
match
);
matchStartNode.parentNode.insertBefore(precedingTextNode, matchStartNode);
matchStartNode.parentNode.insertBefore(firstNode, matchStartNode);
matchStartNode.parentNode.removeChild(matchStartNode);
matchEndNode.parentNode.insertBefore(lastNode, matchEndNode);
matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode);
matchEndNode.parentNode.removeChild(matchEndNode);
this.reverts.push(function() {
precedingTextNode.parentNode.removeChild(precedingTextNode);
firstNode.parentNode.replaceChild(matchStartNode, firstNode);
followingTextNode.parentNode.removeChild(followingTextNode);
lastNode.parentNode.replaceChild(matchEndNode, lastNode);
});
return lastNode;
}
}
};
return exposed;
}));
});
/**
* Heti add-on v 0.1.0
* Add right spacing between CJK & ANS characters
*/
// 正则表达式来自 pangu.js https://github.com/vinta/pangu.js
const CJK = '\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30fa\u30fc-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff';
const A = 'A-Za-z\u0370-\u03ff';
const N = '0-9';
const S = '`~!@#\\$%\\^&\\*\\(\\)-_=\\+\\[\\]{}\\\\\\|;:\'",<.>\\/\\?';
const ANS = `${A}${N}${S}`;
const HETI_NON_CONTIGUOUS_ELEMENTS = {
// Block Elements
address: 1, article: 1, aside: 1, blockquote: 1, dd: 1, div: 1,
dl: 1, fieldset: 1, figcaption: 1, figure: 1, footer: 1, form: 1, h1: 1, h2: 1, h3: 1,
h4: 1, h5: 1, h6: 1, header: 1, hgroup: 1, hr: 1, main: 1, nav: 1, noscript: 1, ol: 1,
output: 1, p: 1, pre: 1, section: 1, ul: 1,
// Other misc. elements that are not part of continuous inline prose:
br: 1, li: 1, summary: 1, dt: 1, details: 1, rp: 1, rt: 1, rtc: 1,
// Media / Source elements:
script: 1, style: 1, img: 1, video: 1, audio: 1, canvas: 1, svg: 1, map: 1, object: 1,
// Input elements
input: 1, textarea: 1, select: 1, option: 1, optgroup: 1, button: 1,
// Table related elements:
table: 1, tbody: 1, thead: 1, th: 1, tr: 1, td: 1, caption: 1, col: 1, tfoot: 1, colgroup: 1,
// Inline elements
ins: 1, del: 1, s: 1,
};
const HETI_SKIPPED_ELEMENTS = {
br: 1, hr: 1,
// Media / Source elements:
script: 1, style: 1, img: 1, video: 1, audio: 1, canvas: 1, svg: 1, map: 1, object: 1,
// Input elements:
input: 1, textarea: 1, select: 1, option: 1, optgroup: 1, button: 1,
// Pre elements:
pre: 1, code: 1, sup: 1, sub: 1,
// Heti elements
'heti-spacing': 1,
};
const HETI_SKIPPED_CLASS = 'heti-skip';
const hasOwn = {}.hasOwnProperty;
class Heti {
constructor (rootSelector) {
this.rootSelector = rootSelector || '.heti';
this.REG_FULL = new RegExp(`(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`, 'g');
this.REG_START = new RegExp(`([${ANS}]+(?: +[${ANS}]+)* *)(?=[${CJK}])`, 'g');
this.REG_END = new RegExp(`(?<=[${CJK}])( *[${ANS}]+(?: +[${ANS}]+)*)`, 'g');
this.funcForceContext = function forceContext (el) {
return hasOwn.call(HETI_NON_CONTIGUOUS_ELEMENTS, el.nodeName.toLowerCase())
};
this.funcFilterElements = function filterElements (el) {
return (
!(el.classList && el.classList.contains(HETI_SKIPPED_CLASS)) &&
!hasOwn.call(HETI_SKIPPED_ELEMENTS, el.nodeName.toLowerCase())
)
};
}
spacingElements (elmList) {
for (let $$root of elmList) {
this.spacingElement($$root);
}
}
spacingElement ($$elm) {
const commonConfig = {
forceContext: this.funcForceContext,
filterElements: this.funcFilterElements,
};
const getWrapper = function (classList, text) {
const $$r = document.createElement('heti-spacing');
$$r.className = classList;
$$r.textContent = text.trim();
return $$r
};
findAndReplaceDOMText($$elm, Object.assign(commonConfig, {
find: this.REG_FULL,
replace: portion => getWrapper('heti-spacing-start heti-spacing-end', portion.text),
}));
findAndReplaceDOMText($$elm, Object.assign(commonConfig, {
find: this.REG_START,
replace: portion => getWrapper('heti-spacing-start', portion.text),
}));
findAndReplaceDOMText($$elm, Object.assign(commonConfig, {
find: this.REG_END,
replace: portion => getWrapper('heti-spacing-end', portion.text),
}));
}
autoSpacing () {
document.addEventListener('DOMContentLoaded', () => {
const $$rootList = document.querySelectorAll(this.rootSelector);
for (let $$root of $$rootList) {
this.spacingElement($$root);
}
});
}
}
return Heti;
})));

View file

@ -50,8 +50,9 @@ a {
.article[data-bg-grid="grid-24"] {
background-size: 100% 24px;
background-image: linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
linear-gradient(rgba(255, 255, 255, 0) 23px, #eee 1px);
background-image:
linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
linear-gradient(rgba(255, 255, 255, 0) 23px, #eee 1px);
outline-color: #eee;
}
@ -63,8 +64,9 @@ a {
.article[data-bg-grid="grid-12"] {
background-size: 100% 12px;
background-image: linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
linear-gradient(rgba(255, 255, 255, 0) 11px, #eee 12px);
background-image:
linear-gradient(to right, rgba(255, 255, 255, 0) 31px, #eee 1px, rgba(255, 255, 255, 0) 33px),
linear-gradient(rgba(255, 255, 255, 0) 11px, #eee 12px);
outline-color: #eee;
}
@ -313,4 +315,7 @@ a {
margin-block-start: 12px;
margin-block-end: 0;
}
.article .article__toc ol ol {
margin-block-start: 0;
}
}

View file

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./normalize.css">
<link rel="stylesheet" href="./index.css">
<link rel="stylesheet" href="./heti.min.css">
<link rel="stylesheet" href="./heti.css">
<link rel="icon" href="./favicon.svg">
</head>
<body>
@ -15,7 +15,7 @@
<h1>赫蹏</h1>
<blockquote>古代称用以书写的小幅绢帛。后亦以借指纸。《汉书·外戚传下·孝成赵皇后》:<q>武(籍武 )发篋中,有裹药二枚,赫蹏书。</q><u>颜师古</u>注:<q><u>邓展</u>曰:<q>赫音兄弟鬩墙之鬩。</q><u>应劭</u>曰:<q>赫蹏,薄小纸也。</q></q><u></u><u>赵彦卫</u> 《云麓漫钞》卷七:<q>《赵后传》所谓『赫蹏』者,注云<q>薄小纸</q>,然其寔亦縑帛。</q></blockquote>
<nav class="article__toc">
<nav class="article__toc heti-skip">
<details open>
<summary>目录</summary>
<ol>
@ -31,34 +31,33 @@
</ul>
</li>
<li><a href="#columns">多栏排版</a></li>
<li><a href="#vertical">垂直排版</a></li>
<li><a href="#guidelines">排版原则</a></li>
<li><a href="#vertical">竖排排版</a></li>
<li><a href="#guidelines">设计原则</a></li>
<li>
<a href="#appendix">附录</a>
<ol class="heti-list-latin">
<li><a href="#compatibility">兼容性</a></li>
<li><a href="#tags">标签示例表</a></li>
<li><a href="#javascript">增强脚本</a></li>
<li><a href="#license">开源协议</a></li>
</ol>
</li>
<li><a href="#wip">待开发功能</a></li>
</ol>
</details>
</nav>
<h2 id="intro">介绍<a class="anchor" href="#intro">#</a></h2>
<p><dfn>赫蹏</dfn><code>hètí</code>)是专为中文内容展示设计的排版样式增强。它基于通行的中文排版规范而来,可以为网站的读者带来更好的文章阅读体验。由于它不同于传统的网页样式重置,而是专注于<em>正文区域</em>的样式增强,因此可以很好的与常见的CSS样式重置方案共存。</p>
<p><ruby class="heti-ruby--inline"><rb></rb><rp>(</rp><rt></rt><rp>)</rp></ruby><ruby class="heti-ruby--inline"><rb></rb><rp>(</rp><rt></rt><rp>)</rp></ruby>是专为中文网页内容设计的排版样式增强。它基于通行的中文排版规范,可为网站的读者带来更好的内容阅读体验。它的主要特性有:</p>
<ul>
<li>全标签样式统一;</li>
<li>贴合网格的排版;</li>
<li>预置多种预设字体族(仅限桌面端);</li>
<li>预置横排、直排(竖排)样式;</li>
<li>全标签样式美化;</li>
<li>预置古文、诗词样式;</li>
<li>预置行间注排版样式;</li>
<li>预置多栏排版样式;</li>
<li>兼容<i>normalize.css</i><i>CSS Reset</i><sup><a href="#fn-01">[1]</a></sup></li>
<li>预置多种排版样式(行间注、多栏、竖排等);</li>
<li>多种预设字体族(仅限桌面端);</li>
<li>简/繁体中文支持;</li>
<li>中西文混排美化(基于JavaScript脚本实现);</li>
<li>兼容<i>normalize.css</i><i>CSS Reset<sup><a id="ref-01" href="#fn-01">[1]</a></sup></i></li>
<li>移动端支持;</li>
<li>基于BEM<sup><a href="#fn-02">[2]</a></sup>命名;</li>
<li>……</li>
</ul>
<p>总之,用上就会变好看。</p>
@ -66,27 +65,27 @@
<hr>
<h2 id="usage">使用方法<a class="anchor" href="#usage">#</a></h2>
<p>赫蹏的使用方法很简单,只需要引入样式文件并设定作用范围即可</p>
<p>赫蹏的源文件可在项目仓库(<a href="https://github.com/sivan/heti">https://github.com/sivan/heti</a>)下载,生产文件位于<code>dist</code>文件夹。使用方法如下</p>
<ol>
<li>
在页面的<code>&lt;head&gt;</code>标签中引入<code>heti.css</code>文件:
在页面的<code>&lt;/head&gt;</code>标签中引入<code>heti.css</code>样式文件:
<pre><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;./heti.css&quot;&gt;</code></pre>
</li>
<li>
在要作用的容器元素上增加<code>class="heti"</code>的类名即可:
<pre><code>&lt;article class=&quot;entry heti&quot;&gt;
<pre><code>&lt;article class=&quot;entry <ins>heti</ins>&quot;&gt;
&lt;h1&gt;我的世界观&lt;/h1&gt;
&lt;p&gt;有钱人的生活就是这么朴实无华,且枯燥&lt;/p&gt;
……
&lt;/article&gt;</code></pre>
</li>
<li>不建议将样式作用在根标签(比<code>&lt;body&gt;</code><code>&lt;div class=&quot;container&quot;&gt;</code>)上,除非你的页面<u title="就像本页面一样">通篇都是文章的一部分</u></li>
<li>注意:赫蹏是正文区域的样式增强,不是<i>normalize.css</i><i>CSS Reset</i>的替代。因此<b>不建议</b>将它作用在根标签(<code>&lt;body&gt;</code><code>&lt;div class=&quot;container&quot;&gt;</code>)上,除非像本页面一样通篇都是文章的一部分</li>
</ol>
<hr>
<h2 id="examples">效果示例<a class="anchor" href="#examples">#</a></h2>
<p>站已<em>全页</em>应用了赫蹏样式,下面是赫蹏在特殊排版下的效果演示。</p>
<p>页面<em>全页</em>应用了赫蹏样式,所见即所得。下面是内置的多种排版效果演示。</p>
<details open>
<summary id="example-1">示例1:古文</summary>
<section class="demo">
@ -120,12 +119,12 @@
</div>
<hr>
<div class="heti--poetry">
<h2>丑奴儿·书博山道中壁<span class="heti-meta heti-small">[宋]<abbr title="号稼轩">辛弃疾</abbr></span></h2>
<h2>赠汪伦<span class="heti-meta heti-small">[唐]<abbr title="号青莲居士">李白</abbr></span></h2>
<p class="heti-x-large">
少年不识愁滋味,爱上层楼<span class="heti-hang"></span><br>
爱上层楼,为赋新词强说愁<span class="heti-hang"></span><br>
而今识尽愁滋味,欲说还休<span class="heti-hang"></span><br>
欲说还休,却道天凉好个秋<span class="heti-hang"></span>
李白乘舟将欲行<span class="heti-hang"></span><br>
忽闻岸上踏歌声<span class="heti-hang"></span><br>
桃花潭水深千尺<span class="heti-hang"></span><br>
不及汪伦送我情<span class="heti-hang"></span>
</p>
</div>
</section>
@ -162,27 +161,27 @@
<hr>
<h2 id="columns">多栏排版<a class="anchor" href="#columns">#</a></h2>
<p>赫蹏预置了多种多栏布局类,可以按栏数或宽进行设置。</p>
<p>赫蹏预置了多种多栏布局类,可以按栏数或每栏行宽进行设置。</p>
<details>
<summary>查看使用说明</summary>
<p>为容器元素添加名为<code>heti--columns-2</code>class即可实现三栏排版。</p>
<pre><code>&lt;div class=&quot;heti heti--columns-2&quot;&gt;&lt;/div&gt;</code></pre>
<p>为容器元素添加名为<code>heti--columns-2</code>的class即可实现双栏排版。</p>
<pre><code>&lt;div class=&quot;heti heti--columns-2&quot;&gt;...&lt;/div&gt;</code></pre>
<table>
<thead>
<tr>
<th style="width: 60px;">方式</th>
<th style="width: 80px;">方式</th>
<th style="width: 160px;">对应类名</th>
<th style="width: 280px;">可选数值</th>
<th style="width: 300px;">可选数值</th>
</tr>
</thead>
<tbody>
<tr>
<td>按数量</td>
<td>栏目数量</td>
<td><code>heti--columns-3</code></td>
<td>2, 3, 4</td>
</tr>
<tr>
<td>按宽</td>
<td>每栏行</td>
<td><code>heti--columns-16em</code></td>
<td>16em, 20em, 24em, … +4em, … , 48em</td>
</tr>
@ -206,12 +205,12 @@
<hr>
<h2 id="vertical">垂直排版<a class="anchor" href="#vertical">#</a></h2>
<p>赫蹏预置了传统的垂直方向排版,同样贴合栅格。</p>
<h2 id="vertical">竖排排版<a class="anchor" href="#vertical">#</a></h2>
<p>赫蹏预置了传统的竖排(直排)方向排版,同样贴合栅格。</p>
<details>
<summary>查看使用说明</summary>
<p>为容器元素添加名为<code>heti--vertical</code>class即可实现垂直布局:</p>
<pre><code>&lt;div class=&quot;heti heti--vertical&quot;&gt;&lt;/div&gt;</code></pre>
<p>为容器元素添加名为<code>heti--vertical</code>的class即可实现竖排布局。</p>
<pre><code>&lt;div class=&quot;heti heti--vertical&quot;&gt;...&lt;/div&gt;</code></pre>
</details>
<figure class="card card--vertical">
<div class="card__vertical-container">
@ -227,21 +226,20 @@
<p>願陛下託臣以討賊興復之效;不效,則治臣之罪,以告先帝之靈。若無興德之言,則責攸之、禕、允等之慢,以彰其咎。陛下亦宜自謀,以諮諏善道,察納雅言,深追先帝遺詔。臣不勝受恩感激。今當遠離,臨表涕零,不知所言!</p>
</section>
</div>
<figcaption>垂直排版演示</figcaption>
<figcaption>竖排排版演示</figcaption>
</figure>
<hr>
<h2 id="guidelines">排版原则<a class="anchor" href="#guidelines">#</a></h2>
<h2 id="guidelines">设计原则<a class="anchor" href="#guidelines">#</a></h2>
<p>赫蹏项目的初衷很简单:它不作为一个CSS Reset出现,而是根据通行的中文排版规范,对网页正文区域进行排版样式增强。在部分CSS特性尚未有浏览器支持前,可通过JavaScript实现部分补充(非必要)。</p>
<h3>核心</h3>
<p>以美化中文方块字排版为目标,假定作用范围是网页中的文章区域(含标题、正文、元信息等),不包含网页导航、表单、侧边栏等区域(它们也不需要这套格式排版)。</p>
<p>使用<code>heti.css</code>时建议搭配良好的中文书写习惯,即:使用正确的HTML标签、正确的引号、中文全角标点等。使用Markdown书写文章将助你一臂之力。</p>
<h3>字号</h3>
<p><code>heti.css</code> 采用16px作为默认字号。在文字较大时(作用于标题等情况),会适当地增加字间距以便获得更好地可读性。</p>
<h3>字体</h3>
<p>参考《中文排版需求<sup><a href="#fn-03">[3]</a></sup>》中描述的四种常见书籍排版字体:宋体、楷体、黑体、仿宋。赫蹏提供黑体、宋体和<u title="以正文宋体、标题楷体构成的搭配">传统</u>三种字体风格,前两者分别对应无衬线、衬线字体族。</p>
<p>美化中文方块字排版,作用范围是网页中的文章区域(含标题、正文、元信息等),不包含网页导航、表单、侧边栏等区域(它们也不需要这套格式排版)。</p>
<p>使用<code>heti.css</code>时建议搭配良好的中文书写习惯,即:使用正确的HTML标签(可参考后面的<a href="#tags">标签示例表</a>)、正确的引号、中文全角标点等。使用Markdown书写文章将助你一臂之力。</p>
<h3>文字</h3>
<p>参考《中文排版需求<sup><a id="ref-02" href="#fn-02">[2]</a></sup>》中描述的常用书籍排版字体,赫蹏提供了黑体、宋体和<u title="以正文宋体、标题楷体构成的搭配">传统</u>三种字体风格,前两者分别对应无衬线、衬线字体族。文字默认采用16px作为标准字号。在标题等文字较大的情况下,会适当地增加字间距以便获得更好地可读性。</p>
<details>
<summary>查看对照表</summary>
<summary>查看字体风格对照表</summary>
<section class="section">
<table>
<caption>各字体族下不同标签对应的字体</caption>
@ -258,7 +256,7 @@
<tr>
<th>标题</th>
<td>黑体</td>
<td>宋体(粗)</td>
<td>宋体</td>
<td>楷体</td>
<td>
<section>
@ -363,7 +361,7 @@
<h3>标点</h3>
<p>参考《中文排版需求》制定符号样式。唯一差异在于简体中文一律采用直角引号(「」)替代圆角引号(“”),这样可以保持字符等宽。</p>
<h3>间距</h3>
<p>为保持页面元素总是贴合垂直栅格,大部分块级元素(段落、列表、表格等)采用一行行高作为底边距,半行行高作为顶边距。</p>
<p>为保持页面元素总是贴合垂直栅格,块级元素(段落、列表、表格等)采用一行行高作为底边距,半行行高作为顶边距。标题根据亲密性原则采用相反的边距设计。</p>
<hr>
@ -402,14 +400,14 @@
<table>
<caption>常用标签样式示例表</caption>
<tr>
<th style="width: 80px;">类型</th>
<th style="width: 72px;">类型</th>
<th style="width: 320px;">标签</th>
<th style="width: 240px;">效果</th>
</tr>
<tr>
<td>链接</td>
<td><code>&lt;a href=&quot;https://github.com/sivan/heti&quot; title=&quot;赫蹏&quot; target=&quot;_blank&quot;&gt;heti.css&lt;/a&gt;</code></td>
<td><a href="https://github.com/sivan/heti" title="赫蹏" target="_blank">heti.css</a></td>
<td><code>&lt;a href=&quot;https://github.com/sivan/heti&quot; title=&quot;赫蹏&quot;&gt;heti.css&lt;/a&gt;</code></td>
<td><a href="https://github.com/sivan/heti" title="赫蹏">heti.css</a></td>
</tr>
<tr>
<td>缩写</td>
@ -428,12 +426,12 @@
</tr>
<tr>
<td>文本变动</td>
<td><code>这次考试,我考了&lt;del datetime=&quot;17:00:00&quot;&gt;97&lt;/del&gt;&lt;ins datetime=&quot;18:15:00&quot;&gt;100&lt;/ins&gt;分呢!</code></td>
<td>这次考试,我考了<del datetime="17:00:00">97</del><ins datetime="18:15:00">100</ins>分呢!</td>
<td><code>这次考试,我考了&lt;del datetime=&quot;17:00:00&quot;&gt;58&lt;/del&gt;&lt;ins datetime=&quot;18:15:00&quot;&gt;98&lt;/ins&gt;分呢!</code></td>
<td>这次考试,我考了<del datetime="17:00:00">58</del><ins datetime="18:15:00">98</ins>分呢!</td>
</tr>
<tr>
<td>文本更新</td>
<td><code>因为谁也不认识,所以最后我们决定念&lt;s&gt;d&iacute;&lt;/s&gt; t&iacute;</code></td>
<td><code>因为谁也不认识,所以最后我们决定念&lt;s&gt;d&iacute;&lt;/s&gt;t&iacute;</code></td>
<td>因为谁也不认识,所以最后我们决定念<s></s>tí。</td>
</tr>
<tr>
@ -446,6 +444,11 @@
<td><code>&lt;dfn&gt;&middot;格瓦拉&lt;/dfn&gt;,中国大陆网络红人、罪犯。被奉为百度「戒赌吧」400万会员的「精神领袖」。</code></td>
<td><dfn>窃·格瓦拉</dfn>,中国大陆网络红人、罪犯。被奉为百度「戒赌吧」400万会员的「精神领袖」。</td>
</tr>
<tr>
<td>标记</td>
<td><code>这道题&lt;mark&gt;必考&lt;/mark&gt;,你们爱记不记。</code></td>
<td>这道题<mark>必考</mark>,你们爱记不记。</td>
</tr>
<tr>
<td>强调</td>
<td><code>稳住,&lt;em&gt;我们能赢&lt;/em&gt;</code></td>
@ -456,15 +459,22 @@
<td><code>我们&lt;span class=&quot;heti-em&quot;&gt;必将&lt;/span&gt;战胜这场疫情。</code></td>
<td>我们<span class="heti-em">必将</span>战胜这场疫情。</td>
</tr>
<tr>
<td>标记</td>
<td><code>这道题&lt;mark&gt;必考&lt;/mark&gt;,你们爱记不记。</code></td>
<td>这道题<mark>必考</mark>,你们爱记不记。</td>
</tr>
</table>
</section>
</details>
<h3 id="javascript">增强脚本<sup>beta</sup><a class="anchor" href="#javascript">#</a></h3>
<p>由于部分CSS特性尚未有浏览器,所以可选择使用增强脚本进行中西文混排优化。无论你的输入习惯是否在中西文之间加入「空格」,都会统一成一样的间距(1/4字宽的空白)。</p>
<details>
<summary>查看使用说明</summary>
<p>在页面的<code>&lt;/body&gt;</code>标签前引入JavaScript脚本:</p>
<pre><code>&lt;script src=&quot;./heti-addon.min.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;
const heti = new Heti();
heti.autoSpacing();
&lt;/script&gt;</code></pre>
</details>
<h3 id="license">开源协议<a class="anchor" href="#tags">#</a></h3>
<p>「赫蹏」遵循MIT协议开源。</p>
@ -472,18 +482,23 @@
<h2 id="wip">待开发功能<a class="anchor" href="#wip">#</a></h2>
<ul>
<li>️☑ 中、西文混排</li>
<li>☑ 标点挤压</li>
<li>☑ 标点悬挂</li>
<li>✅ 中、西文混排</li>
<li>✅ 诗词版式</li>
<li>✅ 行间注版式</li>
</ul>
<footer class="heti-fn">
<ol>
<li id="fn-01">CSS Reset:指代类似Eric Meyer's Reset CSS的样式重置方案</li>
<li id="fn-02">BEM:由Yandex公司创造的命名方式 https://en.bem.info/</li>
<li id="fn-03">《中文排版需求》:https://w3c.github.io/clreq/</li>
<li id="fn-01">
<a href="#ref-01" title="移至">^</a>
CSS Reset:指代类似Eric Meyer's Reset CSS的样式重置方案
</li>
<li id="fn-02">
<a href="#ref-02" title="移至">^</a>
《中文排版需求》:https://w3c.github.io/clreq/
</li>
</ol>
</footer>
</article>
@ -502,6 +517,7 @@
</ul>
</aside>
<script src="./heti-addon.js"></script>
<script>
const $$root = document.getElementsByTagName('article')[0]
@ -518,6 +534,9 @@
addEventListeners(document.getElementsByClassName('J_radioGrid'), 'change', function (e) {
$$root.setAttribute('data-bg-grid', e.target.value)
})
const heti = new Heti()
heti.autoSpacing()
</script>
</body>
</html>