first version
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
/dist
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# TextFrame
|
||||||
|
一个通过canvas将长文本分成若干页渲染的js工具,处理排版禁则,支持辅助平面Unicode字符。目前仅工作于浏览器环境。
|
||||||
|
|
||||||
|
## 项目介绍
|
||||||
|
在浏览器中,尽管css有分栏布局,但却很难实现“分页”。浏览器没有提供计算一个盒子可以显示多少文本的API,只有Canvas的上下文提供了一个`measureText()`方法,但它只能简单的计算一段文本的渲染宽度,当文本不止一行时就无法计算了。由于不同字体不同字符有不同的宽度,再加上各浏览器对排版禁则的处理不一致,基本杜绝了计算浏览器原生DOM元素能够显示多少文本的可能。
|
||||||
|
|
||||||
|
于是我参考W3C对中文排版的[草案](https://www.w3.org/TR/2020/WD-clreq-20201001/#prohibition_rules_for_line_start_end)中的排版禁则,通过[CanvasRenderingContext2D.measureText()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/measureText)方法计算字符宽度,对文本进行分页。
|
||||||
|
|
||||||
|
### 行首禁则
|
||||||
|
| 名称 | 符号 |
|
||||||
|
| ---- | ------ |
|
||||||
|
| 点号 | 、,,..。::;;!!?? |
|
||||||
|
| 结束引号 | '"」』”’ |
|
||||||
|
| 结束括号 | )]})】〗〕]} |
|
||||||
|
| 结束乙式书名号 | 》〉 |
|
||||||
|
| 连接号 | –~~— |
|
||||||
|
| 间隔号 | ·.‧•・ |
|
||||||
|
| 分隔号 | // |
|
||||||
|
|
||||||
|
### 行尾禁则
|
||||||
|
| 名称 | 符号 |
|
||||||
|
| ---- | ------ |
|
||||||
|
| 开始引号 | 「『“‘ |
|
||||||
|
| 开始括号 | ([{(【〖〔[〔 |
|
||||||
|
| 开始单双书名号 | 《〈 |
|
||||||
|
| 分隔号 | // |
|
||||||
|
|
||||||
|
### 符号分离禁则
|
||||||
|
| 名称 | 符号 |
|
||||||
|
| ---- | ------ |
|
||||||
|
| 破折号 | ── |
|
||||||
|
| 省略号 | …… |
|
||||||
|
|
||||||
|
本项目默认按照上述禁则处理排版禁则,但可以通过[选项](https://mattuylee.github.io/text-frame/zh/options.md)控制排版禁则。
|
||||||
|
草案中规定,当碰到行首为(行首禁则中的)标点时,应遵守「先挤进,后推出」原则,即先尝试压缩当前行的标点,无法挤压再取前一行的最后一个字至下一行。考虑到标点符号压缩的复杂性,本项目暂未实现该特性,而是直接尝试取上一行的最后一字到下一行,如果直到上一行首都没找到允许出现在行首的字符,则采取不处理的方式。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
本项目定义了文本片段(text fragment)的概念。通过在选项中指定一个文本片段数组,可以对每一个片段设置不同的样式,如字体、颜色、对齐方式等,具体请参考[配置项](https://mattuylee.github.io/text-frame/zh/options.md)。一般来说,一个文本片段即一个文本段落,虽然文本片段内也支持换行,但不会有段间距和行缩进。在本项目文档中文本片段和文本段落等价,都是指`FragmentOptions`定义的文本片段。
|
||||||
|
|
||||||
|
本项目支持以下特性:
|
||||||
|
* 每个段落单独设置字体、缩进、边距、字体颜色等;
|
||||||
|
* 支持两端对齐;
|
||||||
|
* 支持自定义排版禁则;
|
||||||
|
* 可配置段落之间边距是否折叠;
|
||||||
|
* 支持rtl模式;
|
||||||
|
|
||||||
|
这是一个[在线例子](https://mattuylee.github.io/text-frame/zh/example.html)。
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
本项目仅支持浏览器环境,且依赖于Canvas 2D上下文,请参考[canvas兼容性](https://caniuse.com/?search=canvas)。
|
||||||
|
|
||||||
|
### API
|
||||||
|
```typescript
|
||||||
|
// 计算分页
|
||||||
|
function computeTextFrames(options: FrameOptions): TextFrame[];
|
||||||
|
// 渲染分页到canvas
|
||||||
|
function renderFrame(context: CanvasRenderingContext2D, frame: TextFrame, clear: boolean): void;
|
||||||
|
```
|
||||||
|
|
||||||
|
### npm
|
||||||
|
`npm install @mattuy/text-frame --save`
|
||||||
|
### ES2015 & CommonJS
|
||||||
|
```javascript
|
||||||
|
// cjs
|
||||||
|
// const { computeTextFrames, renderFrame } = require('@mattuy/text-frame');
|
||||||
|
// or esm
|
||||||
|
import { computeTextFrames, renderFrame } from '@mattuy/text-frame/esm';
|
||||||
|
|
||||||
|
const frames = computeTextFrames({
|
||||||
|
viewWidth: 320,
|
||||||
|
viewHeight: 640,
|
||||||
|
fontSize: 16,
|
||||||
|
margin: 8,
|
||||||
|
color: '#000',
|
||||||
|
fragments: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
fontSize: 20,
|
||||||
|
margin: 32,
|
||||||
|
textAlign: 'center',
|
||||||
|
text: "标题"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
textIndent: 32,
|
||||||
|
fontFamily: 'serif',
|
||||||
|
margin: { bottom: 32 },
|
||||||
|
marginCollapse: true,
|
||||||
|
textAlign: 'justify',
|
||||||
|
text: "这是一个多行文本。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.style.width = '320px';
|
||||||
|
canvas.style.height = '640px';
|
||||||
|
document.body.append(canvas);
|
||||||
|
renderFrame(canvas.getContext('2d'), frames[0]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 全局引入
|
||||||
|
```html
|
||||||
|
<script src="text-frame/dist/umd/text-frame-min.js"></script>
|
||||||
|
<script>
|
||||||
|
const frames = TextFrame.computeTextFrames({
|
||||||
|
// ...options
|
||||||
|
});
|
||||||
|
console.log(frames)
|
||||||
|
// ...render
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
配置说明请参考[配置项](https://mattuylee.github.io/text-frame/zh/options.md)。
|
||||||
|
|
||||||
|
|
||||||
|
## 源码编译
|
||||||
|
* 克隆仓库
|
||||||
|
`git clone https://github.com/mattuylee/text-frame.git`
|
||||||
|
|
||||||
|
* 安装依赖
|
||||||
|
`cd text-frame`
|
||||||
|
`npm install`
|
||||||
|
|
||||||
|
### 发布
|
||||||
|
`npm run dist`
|
||||||
|
|
||||||
|
### 启动demo
|
||||||
|
`npm run example`
|
||||||
|
|
||||||
|
### 调试代码
|
||||||
|
执行`npm run debug`,然后rollup会监听src下文件的变化自动重新编译。可浏览器直接打开`example/index.html`测试。
|
||||||
|
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# TextFrame
|
||||||
|
|
||||||
|
[中文版](https://github.com/mattuylee/text-frame) | [English](https://github.com/mattuylee/text-frame/docs/en/README-EN.md)
|
||||||
|
|
||||||
|
A javascript tool to split long text into frames, with typesetting prohibition processed and unicode full support.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
It's difficute to compute how many characters a DOM element can show, because diffrent User Agents have diffrent typesetting rules. So for that, we have to draw text on a canvas with our own rules to know how to split text into several text frames, since we can compute character width through [CanvasRenderingContext2D.measureText()](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/measureText).
|
||||||
|
|
||||||
|
refering to [W3C draft](https://www.w3.org/TR/2020/WD-clreq-20201001/#prohibition_rules_for_line_start_end) for Chinese typesetting, following are my rules:
|
||||||
|
|
||||||
|
### Prohibition Rules for Line Start
|
||||||
|
| Punctuation Name | The Punctuation Marks |
|
||||||
|
| ---- | ------ |
|
||||||
|
| Pause or Stop | 、,,..。::;;!!?? |
|
||||||
|
| Closing Quotation | '"」』”’ |
|
||||||
|
| Closing Parentheses | )]})】〗〕]} |
|
||||||
|
| Closing Angle Brackets | 》〉 |
|
||||||
|
| Connectors | –~~— |
|
||||||
|
| Interpuncts | ·.‧•・ |
|
||||||
|
| Solidi | // |
|
||||||
|
|
||||||
|
### Prohibition Rules for Line End
|
||||||
|
| Punctuation Name | The Punctuation Marks |
|
||||||
|
| ---- | ------ |
|
||||||
|
| Opening Quotation | 「『“‘ |
|
||||||
|
| opening Parentheses | ([{(【〖〔[〔 |
|
||||||
|
| Opening Angle Brackets | 《〈 |
|
||||||
|
| Solidi | // |
|
||||||
|
|
||||||
|
### Prohibition Rules for Unbreakable Marks
|
||||||
|
| Name | Mark |
|
||||||
|
| ---- | ------ |
|
||||||
|
| Em dash and long dash | ── |
|
||||||
|
| Ellipsis | …… |
|
||||||
|
|
||||||
|
This library take the above as default rules, but it can be configued with [options](https://mattuylee.github.io/text-frame/en/options.md).
|
||||||
|
|
||||||
|
Here is an online [demo](https://mattuylee.github.io/text-frame/en/example.html).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Browser environment is required, and [canvas support](https://caniuse.com/?search=canvas) is needed. This library CANNOT work under web worker.
|
||||||
|
|
||||||
|
### API
|
||||||
|
```typescript
|
||||||
|
// compute text frames
|
||||||
|
function computeTextFrames(options: FrameOptions): TextFrame[];
|
||||||
|
// render text frame
|
||||||
|
function renderFrame(context: CanvasRenderingContext2D, frame: TextFrame, clear: boolean): void;
|
||||||
|
```
|
||||||
|
|
||||||
|
### npm
|
||||||
|
`npm install @mattuy/text-frame --save`
|
||||||
|
### ES2015 & CommonJS
|
||||||
|
```javascript
|
||||||
|
// cjs
|
||||||
|
// const { computeTextFrames, renderFrame } = require('@mattuy/text-frame');
|
||||||
|
// or esm
|
||||||
|
import { computeTextFrames, renderFrame } from '@mattuy/text-frame/esm';
|
||||||
|
|
||||||
|
const frames = computeTextFrames({
|
||||||
|
viewWidth: 320,
|
||||||
|
viewHeight: 640,
|
||||||
|
fontSize: 16,
|
||||||
|
margin: 8,
|
||||||
|
color: '#000',
|
||||||
|
fragments: [
|
||||||
|
{
|
||||||
|
color: 'green',
|
||||||
|
fontSize: 20,
|
||||||
|
margin: 32,
|
||||||
|
textAlign: 'center',
|
||||||
|
text: "Caption"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
textIndent: 32,
|
||||||
|
fontFamily: 'serif',
|
||||||
|
margin: { bottom: 32 },
|
||||||
|
marginCollapse: true,
|
||||||
|
textAlign: 'justify',
|
||||||
|
text: "This is a multi-line text. Pass your text paragraph as this."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.style.width = '320px';
|
||||||
|
canvas.style.height = '640px';
|
||||||
|
document.body.append(canvas);
|
||||||
|
renderFrame(canvas.getContext('2d'), frames[0], true);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Globally Script
|
||||||
|
```html
|
||||||
|
<script src="text-frame/dist/umd/text-frame-min.js"></script>
|
||||||
|
<script>
|
||||||
|
const frames = TextFrame.computeTextFrames({
|
||||||
|
// ...options
|
||||||
|
});
|
||||||
|
console.log(frames)
|
||||||
|
// ...render
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
Option reference sits [here](https://mattuylee.github.io/text-frame/en/options.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Build from Source
|
||||||
|
* clone the repo
|
||||||
|
`git clone https://github.com/mattuylee/text-frame.git`
|
||||||
|
|
||||||
|
* install dependences
|
||||||
|
`cd text-frame`
|
||||||
|
`npm install`
|
||||||
|
|
||||||
|
### Distribution
|
||||||
|
`npm run dist`
|
||||||
|
|
||||||
|
### Start a demo serve
|
||||||
|
`npm run example`
|
||||||
|
|
||||||
|
### Debug
|
||||||
|
Run `npm run debug`, then open `example/index.html` in your browser, rollup will watch your code changes and automatically rebuild.
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Options
|
||||||
|
|
||||||
|
## API Signature
|
||||||
|
```typescript
|
||||||
|
export function computeTextFrames(options: FrameOptions): TextFrame[];
|
||||||
|
export function renderFrame(context: CanvasRenderingContext2D, frame: TextFrame, clear: boolean): void;
|
||||||
|
```
|
||||||
|
|
||||||
|
## FrameOptions
|
||||||
|
### viewWidth
|
||||||
|
* Type: `number`
|
||||||
|
* Default: `300`
|
||||||
|
* Description: view width, must equal to canvas.style.width, but be numeric
|
||||||
|
|
||||||
|
### viewHeight
|
||||||
|
* Type: `number`
|
||||||
|
* Default: `150`
|
||||||
|
* Description: view height
|
||||||
|
|
||||||
|
### canvasWidth
|
||||||
|
* Type: `number`
|
||||||
|
* Default: compute with `viewWidth`
|
||||||
|
* Description: canvas.width,default is `viewWidth * window.devicePixelRatio`
|
||||||
|
|
||||||
|
### canvasHeight
|
||||||
|
* Type: `number`
|
||||||
|
* Default: 根据`viewHeight`计算。
|
||||||
|
* Description: canvas.height is `viewHeight * window.devicePixelRatio`
|
||||||
|
|
||||||
|
### margin
|
||||||
|
* Type: `number | { left?: number, right?: number, top?: number, bottom?: number }`
|
||||||
|
* Default: `0`
|
||||||
|
* Description: margin for **frame**
|
||||||
|
|
||||||
|
### lineStartProhibitedMarks
|
||||||
|
* Type: `string`
|
||||||
|
* Default: `、,,..。::;;!!??'"」』”’)]})】〗〕]}》〉–~~—·.‧•・//`
|
||||||
|
* Description: prohibition characters for line start
|
||||||
|
|
||||||
|
### lineEndProhibitedMarks
|
||||||
|
* Type: `string`
|
||||||
|
* Default: `「『“‘([{(【〖〔[〔《〈//`
|
||||||
|
* Description: prohibition characters for line end
|
||||||
|
|
||||||
|
### unbreakableRule
|
||||||
|
* Type: `RegExp`
|
||||||
|
* Default: `/──|……|[\w\d]+/`
|
||||||
|
* Description: unbreakable marks. If not empty, must be a RegExp instance. Marks match the rule will not be split into diffrent lines
|
||||||
|
|
||||||
|
### fragments
|
||||||
|
* Type: `FragmentOptions[]`
|
||||||
|
* Default: `null`
|
||||||
|
* Description: text fragments, typically one paragraph one fragment
|
||||||
|
|
||||||
|
|
||||||
|
## FrameOptions & FragmentOptions
|
||||||
|
Options for both frame and fragment. Fragment option will inhirit from frame option if it's empty except `margin`.
|
||||||
|
|
||||||
|
### fontFamily
|
||||||
|
* Type: `string`
|
||||||
|
* Default: `serif`
|
||||||
|
* Description: font family
|
||||||
|
|
||||||
|
### fontSize
|
||||||
|
* Type: `number`
|
||||||
|
* Default: `16`
|
||||||
|
* Description: font size
|
||||||
|
|
||||||
|
### fontWeight
|
||||||
|
* Type: `string | number`
|
||||||
|
* Default: `16`
|
||||||
|
* Description: font weight, refer CSS `font-weight`
|
||||||
|
|
||||||
|
### color
|
||||||
|
* Type: `string`
|
||||||
|
* Default: `#000000`
|
||||||
|
* Description: font color
|
||||||
|
|
||||||
|
### lineHeight
|
||||||
|
* Type: `number`
|
||||||
|
* Default: 1.5 * `fontSize`
|
||||||
|
* Description: line height
|
||||||
|
|
||||||
|
### textIndent
|
||||||
|
* Type: `number`
|
||||||
|
* Default: `0`
|
||||||
|
* Description: text indentation for first line of fragment. interal new line of a fragment is not processed
|
||||||
|
|
||||||
|
### textAlign
|
||||||
|
* Type: `'center' | 'start' | 'end' | 'left' | 'right' | 'justify'`
|
||||||
|
* Default: `start`
|
||||||
|
* Description: text alignment, refer CSS `text-align`
|
||||||
|
|
||||||
|
### textAlignLast
|
||||||
|
* Type: `'center' | 'start' | 'end' | 'left' | 'right' | 'justify'`
|
||||||
|
* Default: `start`
|
||||||
|
* Description: when `textAlign` is `justify`, how the last line is aligned. internal new line of a fragment is also processed. refer CSS `text-align-last`
|
||||||
|
|
||||||
|
|
||||||
|
### rtl
|
||||||
|
* Type: `boolean`
|
||||||
|
* Default: `false`
|
||||||
|
* Description: if true, draw text from right to left
|
||||||
|
|
||||||
|
### trim
|
||||||
|
* Type: `boolean`
|
||||||
|
* Default: `false`
|
||||||
|
* Description: if true, trim white characters of text of a fragment
|
||||||
|
|
||||||
|
### marginCollapse
|
||||||
|
* Type: `boolean`
|
||||||
|
* Default: `true`
|
||||||
|
* Description: if true, the margin-top a fragment is the max of its own and the previous one. if `marginCollapse` of the previous fragment is `false`, margin of current fragment will not collapse
|
||||||
|
|
||||||
|
### noHeadMargin
|
||||||
|
* Type: `boolean`
|
||||||
|
* Default: `false`
|
||||||
|
* Description: ignore margin-top if a fragment is on the top of a frame
|
||||||
|
|
||||||
|
|
||||||
|
## FragmentOptions
|
||||||
|
### margin
|
||||||
|
* Type: `number | { left?: number, right?: number, top?: number, bottom?: number }`
|
||||||
|
* Default: `0`
|
||||||
|
* Description: margin of a **fragment**
|
||||||
|
|
||||||
|
### text
|
||||||
|
* Type: `string`
|
||||||
|
* Default: `''`
|
||||||
|
* Description: text content of a fragment
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
# 配置项
|
||||||
|
|
||||||
|
## 导出函数原型
|
||||||
|
```typescript
|
||||||
|
export function computeTextFrames(options: FrameOptions): TextFrame[];
|
||||||
|
export function renderFrame(context: CanvasRenderingContext2D, frame: TextFrame): void;
|
||||||
|
```
|
||||||
|
|
||||||
|
## FrameOptions
|
||||||
|
### viewWidth
|
||||||
|
* 类型: `number`
|
||||||
|
* 默认值: `300`
|
||||||
|
* 说明: 指定视图宽度,用于计算`canvasWidth`。除非显式提供`canvasWidth`参数,否则必须提供此参数。`viewWidth`应与要实际绘制文本的canvas的css宽度相等。
|
||||||
|
|
||||||
|
### viewHeight
|
||||||
|
* 类型: `number`
|
||||||
|
* 默认值: `150`
|
||||||
|
* 说明:指定视图高度。
|
||||||
|
|
||||||
|
### canvasWidth
|
||||||
|
* 类型: `number`
|
||||||
|
* 默认值: 根据`viewWidth`计算。
|
||||||
|
* 说明: canvas画布宽度,对应canvas.width,默认为`viewWidth * window.devicePixelRatio`。
|
||||||
|
|
||||||
|
### canvasHeight
|
||||||
|
* 类型: `number`
|
||||||
|
* 默认值: 根据`viewHeight`计算。
|
||||||
|
* 说明: canvas画布高度,对应canvas.height。
|
||||||
|
|
||||||
|
### margin
|
||||||
|
* 类型: `number | { left?: number, right?: number, top?: number, bottom?: number }`
|
||||||
|
* 默认值: `0`
|
||||||
|
* 说明: 每个分页(frame)的边距。注意,虽然`FragmentOptions`也有margin配置项,但二者并不是继承关系,而是作用于不同的对象:页面(frame)和文本段落(fragment)。
|
||||||
|
|
||||||
|
### lineStartProhibitedMarks
|
||||||
|
* 类型: `string`
|
||||||
|
* 默认值: `、,,..。::;;!!??'"」』”’)]})】〗〕]}》〉–~~—·.‧•・//`
|
||||||
|
* 说明: 禁止出现在行首的字符。
|
||||||
|
|
||||||
|
### lineEndProhibitedMarks
|
||||||
|
* 类型: `string`
|
||||||
|
* 默认值: `「『“‘([{(【〖〔[〔《〈//`
|
||||||
|
* 说明: 禁止出现在行尾的字符。
|
||||||
|
|
||||||
|
### unbreakableRule
|
||||||
|
* 类型: `RegExp`
|
||||||
|
* 默认值: `/──|……|[\w\d]+/`
|
||||||
|
* 说明: 符号分离禁则规则。如果提供,必须为正则表达式,正则表达式匹配则该认为该组合不能被拆分到新行,默认为破折号,省略号,和英文字母、数字组合。
|
||||||
|
|
||||||
|
### fragments
|
||||||
|
* 类型: `FragmentOptions[]`
|
||||||
|
* 默认值: `null`
|
||||||
|
* 说明: 文本段落数组。必须提供此参数。
|
||||||
|
|
||||||
|
|
||||||
|
## FrameOptions & FragmentOptions
|
||||||
|
以下配置项既可以在`FrameOptions`中指定,也可以在`FragmentOptions`中指定。当`FragmentOptions`未指定相关参数时,将继承`FrameOptions`的配置(再次强调,`margin`并不会继承)。
|
||||||
|
|
||||||
|
### fontFamily
|
||||||
|
* 类型: `string`
|
||||||
|
* 默认值: `serif`
|
||||||
|
* 说明: 字体名称。
|
||||||
|
|
||||||
|
### fontSize
|
||||||
|
* 类型: `number`
|
||||||
|
* 默认值: `16`
|
||||||
|
* 说明: 字体大小。
|
||||||
|
|
||||||
|
### fontWeight
|
||||||
|
* 类型: `string | number`
|
||||||
|
* 默认值: `16`
|
||||||
|
* 说明: 字体粗细。参考CSS `font-weight`。
|
||||||
|
|
||||||
|
### color
|
||||||
|
* 类型: `string`
|
||||||
|
* 默认值: `#000000`
|
||||||
|
* 说明: 字体颜色。
|
||||||
|
|
||||||
|
### lineHeight
|
||||||
|
* 类型: `number`
|
||||||
|
* 默认值: 1.5倍`fontSize`
|
||||||
|
* 说明: 行高。
|
||||||
|
|
||||||
|
### textIndent
|
||||||
|
* 类型: `number`
|
||||||
|
* 默认值: `0`
|
||||||
|
* 说明: 首行缩进。注意,对于每一个文本段落,仅首行缩进,即使文本段落中有换行符,新行也不会缩进。参考word中的软回车。若要分段落应提供多个文本段落。
|
||||||
|
|
||||||
|
### textAlign
|
||||||
|
* 类型: `'center' | 'start' | 'end' | 'left' | 'right' | 'justify'`
|
||||||
|
* 默认值: `start`
|
||||||
|
* 说明: 文本对齐方式。参考CSS `text-align`。
|
||||||
|
|
||||||
|
### textAlignLast
|
||||||
|
* 类型: `'center' | 'start' | 'end' | 'left' | 'right' | 'justify'`
|
||||||
|
* 默认值: `start`
|
||||||
|
* 说明: 当`textAlign`为`justify`时(两端对齐),段落最后一行的对齐方式。注意,这里的最后一行包括文本片段内部的换行符前的最后一行。参考CSS `text-align-last`。
|
||||||
|
|
||||||
|
### rtl
|
||||||
|
* 类型: `boolean`
|
||||||
|
* 默认值: `false`
|
||||||
|
* 说明: 是否从右到左渲染。
|
||||||
|
|
||||||
|
### trim
|
||||||
|
* 类型: `boolean`
|
||||||
|
* 默认值: `false`
|
||||||
|
* 说明: 是否自动删除文本片段首尾的空白符。注意,文本片段内部换行后行首尾空白符不会被清除。
|
||||||
|
|
||||||
|
### marginCollapse
|
||||||
|
* 类型: `boolean`
|
||||||
|
* 默认值: `true`
|
||||||
|
* 说明: 文本片段的上边距是否与前一文本片段的下边距折叠。注意,如果相邻的任一文本片段`marginCollapse`为false,则不会发生边距折叠。
|
||||||
|
|
||||||
|
### noHeadMargin
|
||||||
|
* 类型: `boolean`
|
||||||
|
* 默认值: `false`
|
||||||
|
* 说明: 当一个文本片段正好开始于一个空的页面时,是否忽略其上边距。
|
||||||
|
|
||||||
|
|
||||||
|
## FragmentOptions
|
||||||
|
### margin
|
||||||
|
* 类型: `number | { left?: number, right?: number, top?: number, bottom?: number }`
|
||||||
|
* 默认值: `0`
|
||||||
|
* 说明: 文本片段的边距。注意,此配置项不继承于`FrameOptions`的`margin`选项。
|
||||||
|
|
||||||
|
### text
|
||||||
|
* 类型: `string`
|
||||||
|
* 默认值: `''`
|
||||||
|
* 说明: 该文本段落要绘制的文本内容。
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { FrameOptions, TextFrame } from "./src/options";
|
||||||
|
|
||||||
|
export function computeTextFrames(options: FrameOptions): TextFrame[];
|
||||||
|
export function renderFrame(
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
frame: TextFrame,
|
||||||
|
clear?: boolean
|
||||||
|
): void;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { computeTextFrames, renderFrame } from './dist/esm/text-frame-min';
|
||||||
|
|
||||||
|
export {
|
||||||
|
computeTextFrames,
|
||||||
|
renderFrame
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Test</title>
|
||||||
|
<style>
|
||||||
|
.example-container {
|
||||||
|
display: flex;
|
||||||
|
min-width: 360px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 1024px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 300px;
|
||||||
|
width: 300px;
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
.example-item:first-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
#source {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 360px;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 360px;
|
||||||
|
border: 1px solid darkgray;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="../dist/umd/text-frame.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="example-container">
|
||||||
|
<div class="example-item">
|
||||||
|
<textarea id="source" oninput="refresh()" spellcheck="false">
|
||||||
|
options = {
|
||||||
|
viewWidth: 300,
|
||||||
|
viewHeight: 360,
|
||||||
|
fontSize: 16,
|
||||||
|
margin: 16,
|
||||||
|
fragments: [
|
||||||
|
{
|
||||||
|
margin: { left: 16, right: 16, top: 32 },
|
||||||
|
marginCollapse: false,
|
||||||
|
text: "在这里输入文本。"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</textarea>
|
||||||
|
<button>Options Refrence</button>
|
||||||
|
</div>
|
||||||
|
<div class="example-item">
|
||||||
|
<canvas id="canvas"></canvas>
|
||||||
|
<button onclick="setPagination(-1)">Previous</button>
|
||||||
|
<button onclick="setPagination(1)">Next</button>
|
||||||
|
<span id="pagination" style="float: right;">0 / 0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var options
|
||||||
|
, pagination = document.getElementById('pagination')
|
||||||
|
, canvas = document.getElementById('canvas')
|
||||||
|
, ctx = canvas.getContext('2d')
|
||||||
|
, pageIndex = 0
|
||||||
|
, frames = []
|
||||||
|
, defaultOptions = {
|
||||||
|
viewWidth: 300,
|
||||||
|
viewHeight: 360,
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: 20,
|
||||||
|
color: 'red',
|
||||||
|
fragments: [
|
||||||
|
{
|
||||||
|
textAlign: 'center',
|
||||||
|
margin: 16,
|
||||||
|
marginCollapse: false,
|
||||||
|
text: "bad input"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
canvas.width = 300 * devicePixelRatio;
|
||||||
|
canvas.height = 360 * devicePixelRatio;
|
||||||
|
window.onresize = refresh;
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
try {
|
||||||
|
var src = document.getElementById('source').value;
|
||||||
|
new Function(src)();
|
||||||
|
frames = TextFrame.computeTextFrames(options);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
frames = TextFrame.computeTextFrames(defaultOptions);
|
||||||
|
}
|
||||||
|
pageIndex = 0;
|
||||||
|
setPagination(0)
|
||||||
|
}
|
||||||
|
function setPagination(delta) {
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
if (!frames.length) {
|
||||||
|
pagination.textContent = '0 / 0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pageIndex += delta;
|
||||||
|
if (pageIndex < 0) { pageIndex = 0; }
|
||||||
|
else if (pageIndex >= frames.length) {
|
||||||
|
pageIndex = frames.length - 1;
|
||||||
|
}
|
||||||
|
TextFrame.renderFrame(ctx, frames[pageIndex]);
|
||||||
|
pagination.textContent = pageIndex + 1 + ' / ' + frames.length;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Vendored
+16
@@ -0,0 +1,16 @@
|
|||||||
|
import { FrameOptions, FragmentOptions, TextFrame } from "./src/options";
|
||||||
|
|
||||||
|
export as namespace TextFrame;
|
||||||
|
|
||||||
|
export function computeTextFrames(options: FrameOptions): TextFrame[];
|
||||||
|
export function renderFrame(
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
frame: TextFrame,
|
||||||
|
clear?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
|
export {
|
||||||
|
FrameOptions,
|
||||||
|
FragmentOptions,
|
||||||
|
TextFrame
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
const devMode = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
const TextFrame = require('./dist/umd/text-frame-min')
|
||||||
|
, TextFrameDev = require('./dist/umd/text-frame');
|
||||||
|
let _exports;
|
||||||
|
if (devMode) {
|
||||||
|
_exports = TextFrameDev;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_exports = TextFrame;
|
||||||
|
}
|
||||||
|
module.exports = _exports;
|
||||||
Generated
+484
@@ -0,0 +1,484 @@
|
|||||||
|
{
|
||||||
|
"name": "text-frame",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.10.4.tgz",
|
||||||
|
"integrity": "sha1-Fo2ho26Q2miujUnA8bSMfGJJITo=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/highlight": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-module-imports": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@babel/helper-module-imports/download/@babel/helper-module-imports-7.10.4.tgz?cache=0&sync_timestamp=1593522826853&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-module-imports%2Fdownload%2F%40babel%2Fhelper-module-imports-7.10.4.tgz",
|
||||||
|
"integrity": "sha1-TFxUvgS9MWcKc4J5fXW5+i5bViA=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@babel/helper-validator-identifier/download/@babel/helper-validator-identifier-7.10.4.tgz",
|
||||||
|
"integrity": "sha1-p4x6clHgH2FlEtMbEK3PUq2l4NI=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/highlight": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.10.4.tgz?cache=0&sync_timestamp=1593521095576&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhighlight%2Fdownload%2F%40babel%2Fhighlight-7.10.4.tgz",
|
||||||
|
"integrity": "sha1-fRvf1ldTU4+r5sOFls23bZrGAUM=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-validator-identifier": "^7.10.4",
|
||||||
|
"chalk": "^2.0.0",
|
||||||
|
"js-tokens": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/types": {
|
||||||
|
"version": "7.11.5",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.11.5.tgz?cache=0&sync_timestamp=1598904189191&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftypes%2Fdownload%2F%40babel%2Ftypes-7.11.5.tgz",
|
||||||
|
"integrity": "sha1-2d5XfQElLXfGgAzuA57mT691Zi0=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-validator-identifier": "^7.10.4",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"to-fast-properties": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@rollup/plugin-babel": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@rollup/plugin-babel/download/@rollup/plugin-babel-5.2.1.tgz",
|
||||||
|
"integrity": "sha1-IPyPiGTcDqocVXhAhFlgaAj3KSQ=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-module-imports": "^7.10.4",
|
||||||
|
"@rollup/pluginutils": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@rollup/pluginutils": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@rollup/pluginutils/download/@rollup/pluginutils-3.1.0.tgz",
|
||||||
|
"integrity": "sha1-cGtFJO5tyLEDs8mVUz5a1oDAK5s=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/estree": "0.0.39",
|
||||||
|
"estree-walker": "^1.0.1",
|
||||||
|
"picomatch": "^2.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/estree": {
|
||||||
|
"version": "0.0.39",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@types/estree/download/@types/estree-0.0.39.tgz",
|
||||||
|
"integrity": "sha1-4Xfmme4bjCLSMXTKqnQiZEOJUJ8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "14.11.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-14.11.1.tgz?cache=0&sync_timestamp=1600368053055&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.11.1.tgz",
|
||||||
|
"integrity": "sha1-Vq+QKtFX52P5umPWccOc2jGTyDU=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.1.tgz",
|
||||||
|
"integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buffer-from": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/chalk/download/chalk-2.4.2.tgz",
|
||||||
|
"integrity": "sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^3.2.1",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"supports-color": "^5.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz",
|
||||||
|
"integrity": "sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/commander/download/commander-2.20.3.tgz?cache=0&sync_timestamp=1598576050587&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-2.20.3.tgz",
|
||||||
|
"integrity": "sha1-/UhehMA+tIgcIHIrpIA16FMa6zM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"commondir": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/commondir/download/commondir-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"escape-string-regexp": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"estree-walker": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/estree-walker/download/estree-walker-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-MbxdYSyWtwQQa0d+bdXYqhOMtwA=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"find-cache-dir": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-3.3.1.tgz?cache=0&sync_timestamp=1583734806517&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-cache-dir%2Fdownload%2Ffind-cache-dir-3.3.1.tgz",
|
||||||
|
"integrity": "sha1-ibM/rUpGcNqpT4Vff74x1thP6IA=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"commondir": "^1.0.1",
|
||||||
|
"make-dir": "^3.0.2",
|
||||||
|
"pkg-dir": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"find-up": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/find-up/download/find-up-4.1.0.tgz",
|
||||||
|
"integrity": "sha1-l6/n1s3AvFkoWEt8jXsW6KmqXRk=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"locate-path": "^5.0.0",
|
||||||
|
"path-exists": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fs-extra": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/fs-extra/download/fs-extra-8.1.0.tgz?cache=0&sync_timestamp=1591229972229&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffs-extra%2Fdownload%2Ffs-extra-8.1.0.tgz",
|
||||||
|
"integrity": "sha1-SdQ8RaiM2Wd2aMt74bRu/bjS4cA=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^4.0.0",
|
||||||
|
"universalify": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fsevents": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.3.tgz",
|
||||||
|
"integrity": "sha1-+3OHA66NL5/pAMM4Nt3r7ouX8j4=",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"graceful-fs": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.4.tgz",
|
||||||
|
"integrity": "sha1-Ila94U02MpWMRl68ltxGfKB6Kfs=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"js-tokens": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/js-tokens/download/js-tokens-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"jsonfile": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/jsonfile/download/jsonfile-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locate-path": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-5.0.0.tgz",
|
||||||
|
"integrity": "sha1-Gvujlq/WdqbUJQTQpno6frn2KqA=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"p-locate": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.20",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/lodash/download/lodash-4.17.20.tgz?cache=0&sync_timestamp=1597336017469&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash%2Fdownload%2Flodash-4.17.20.tgz",
|
||||||
|
"integrity": "sha1-tEqbYpe8tpjxxRo1RaKzs2jVnFI=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"make-dir": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/make-dir/download/make-dir-3.1.0.tgz?cache=0&sync_timestamp=1587567875186&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmake-dir%2Fdownload%2Fmake-dir-3.1.0.tgz",
|
||||||
|
"integrity": "sha1-QV6WcEazp/HRhSd9hKpYIDcmoT8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"semver": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-6.3.0.tgz",
|
||||||
|
"integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"merge-stream": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/merge-stream/download/merge-stream-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-UoI2KaFN0AyXcPtq1H3GMQ8sH2A=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"mime": {
|
||||||
|
"version": "2.4.6",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/mime/download/mime-2.4.6.tgz?cache=0&sync_timestamp=1590596637243&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime%2Fdownload%2Fmime-2.4.6.tgz",
|
||||||
|
"integrity": "sha1-5bQHyQ20QvK+tbFiNz0Htpr/pNE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"opener": {
|
||||||
|
"version": "1.5.2",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/opener/download/opener-1.5.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fopener%2Fdownload%2Fopener-1.5.2.tgz",
|
||||||
|
"integrity": "sha1-XTfh81B3udysQwE3InGv3rKhNZg=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"p-limit": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/p-limit/download/p-limit-2.3.0.tgz?cache=0&sync_timestamp=1594559666231&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fp-limit%2Fdownload%2Fp-limit-2.3.0.tgz",
|
||||||
|
"integrity": "sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"p-try": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"p-locate": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/p-locate/download/p-locate-4.1.0.tgz",
|
||||||
|
"integrity": "sha1-o0KLtwiLOmApL2aRkni3wpetTwc=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"p-limit": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"p-try": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz",
|
||||||
|
"integrity": "sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"path-exists": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/path-exists/download/path-exists-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-UTvb4tO5XXdi6METfvoZXGxhtbM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"path-parse": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/path-parse/download/path-parse-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"picomatch": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.2.tgz",
|
||||||
|
"integrity": "sha1-IfMz6ba46v8CRo9RRupAbTRfTa0=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"pkg-dir": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-8JkTPfft5CLoHR2ESCcO6z5CYfM=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"find-up": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"randombytes": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/randombytes/download/randombytes-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "^5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resolve": {
|
||||||
|
"version": "1.17.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/resolve/download/resolve-1.17.0.tgz",
|
||||||
|
"integrity": "sha1-sllBtUloIxzC0bt2p5y38sC/hEQ=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"path-parse": "^1.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rollup": {
|
||||||
|
"version": "2.28.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/rollup/download/rollup-2.28.1.tgz?cache=0&sync_timestamp=1600668802635&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frollup%2Fdownload%2Frollup-2.28.1.tgz",
|
||||||
|
"integrity": "sha1-zu3KPNsBPC+o8i+ViinCAzaBWeo=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fsevents": "~2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rollup-plugin-serve": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/rollup-plugin-serve/download/rollup-plugin-serve-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-F2ZZdBhfkAfsrrCDXE6JYS4E0jQ=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"mime": ">=2.4.6",
|
||||||
|
"opener": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rollup-plugin-terser": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/rollup-plugin-terser/download/rollup-plugin-terser-7.0.2.tgz?cache=0&sync_timestamp=1599268471748&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frollup-plugin-terser%2Fdownload%2Frollup-plugin-terser-7.0.2.tgz",
|
||||||
|
"integrity": "sha1-6Pu6SGmYGy3DWufopQLVxsBNMk0=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"jest-worker": "^26.2.1",
|
||||||
|
"serialize-javascript": "^4.0.0",
|
||||||
|
"terser": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/has-flag/download/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"jest-worker": {
|
||||||
|
"version": "26.3.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/jest-worker/download/jest-worker-26.3.0.tgz?cache=0&sync_timestamp=1597057408381&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjest-worker%2Fdownload%2Fjest-worker-26.3.0.tgz",
|
||||||
|
"integrity": "sha1-fIqX5PQ2S08F7YvKjKDCTeCRhx8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"merge-stream": "^2.0.0",
|
||||||
|
"supports-color": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serialize-javascript": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-4.0.0.tgz?cache=0&sync_timestamp=1599740699862&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fserialize-javascript%2Fdownload%2Fserialize-javascript-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-tSXhI4SJpez8Qq+sw/6Z5mb0sao=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"randombytes": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-7.2.0.tgz?cache=0&sync_timestamp=1598611708628&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rollup-plugin-typescript2": {
|
||||||
|
"version": "0.27.3",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/rollup-plugin-typescript2/download/rollup-plugin-typescript2-0.27.3.tgz",
|
||||||
|
"integrity": "sha1-zZRVrAJtMlsgxXKNLMVKCKdxtos=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@rollup/pluginutils": "^3.1.0",
|
||||||
|
"find-cache-dir": "^3.3.1",
|
||||||
|
"fs-extra": "8.1.0",
|
||||||
|
"resolve": "1.17.0",
|
||||||
|
"tslib": "2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz?cache=0&sync_timestamp=1589129010497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsafe-buffer%2Fdownload%2Fsafe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"source-map-support": {
|
||||||
|
"version": "0.5.19",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/source-map-support/download/source-map-support-0.5.19.tgz",
|
||||||
|
"integrity": "sha1-qYti+G3K9PZzmWSMCFKRq56P7WE=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"buffer-from": "^1.0.0",
|
||||||
|
"source-map": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz?cache=0&sync_timestamp=1598611708628&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-5.5.0.tgz",
|
||||||
|
"integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"terser": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/terser/download/terser-5.3.2.tgz?cache=0&sync_timestamp=1600354856380&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser%2Fdownload%2Fterser-5.3.2.tgz",
|
||||||
|
"integrity": "sha1-9L6pDrkpRbKgKM7veRgbm7WG568=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"commander": "^2.20.0",
|
||||||
|
"source-map": "~0.6.1",
|
||||||
|
"source-map-support": "~0.5.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"to-fast-properties": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-2.0.0.tgz?cache=0&sync_timestamp=1580550347606&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fto-fast-properties%2Fdownload%2Fto-fast-properties-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/tslib/download/tslib-2.0.1.tgz?cache=0&sync_timestamp=1596754132993&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-QQ6w0RPltjVkkO7HSWA3JbAhtD4=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/typescript/download/typescript-4.0.3.tgz?cache=0&sync_timestamp=1601029293027&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-4.0.3.tgz",
|
||||||
|
"integrity": "sha1-FTu9Ro7wdyXB35x36LRT+NNqu6U=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"universalify": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npm.taobao.org/universalify/download/universalify-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "@mattuy/text-frame",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": false,
|
||||||
|
"description": "a javascript tool to split long text into pages, with typesetting prohibition processed and unicode full support.\n一个将长文本分成若干页的js工具,处理排版禁则,支持Unicode",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"example": "example"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c rollup.config.js --environment NODE_ENV:development",
|
||||||
|
"dist": "rollup -c rollup.config.js --environment NODE_ENV:production",
|
||||||
|
"debug": "rollup -c rollup.config.js --environment NODE_ENV:development --watch",
|
||||||
|
"install": "npm run build && npm run dist",
|
||||||
|
"example": "rollup -c rollup.config.js --environment NODE_ENV:development --environment SERVE"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/mattuylee/text-frame.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"text pagination",
|
||||||
|
"typesetting",
|
||||||
|
"canvas",
|
||||||
|
"Chinese",
|
||||||
|
"文本排版",
|
||||||
|
"排版禁则",
|
||||||
|
"分页",
|
||||||
|
"中文"
|
||||||
|
],
|
||||||
|
"author": "mattuy <mattuylee@outlook.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/mattuylee/text-frame/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/mattuylee/text-frame#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-babel": "^5.2.1",
|
||||||
|
"rollup": "^2.28.1",
|
||||||
|
"rollup-plugin-serve": "^1.0.4",
|
||||||
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"rollup-plugin-typescript2": "^0.27.3",
|
||||||
|
"typescript": "^4.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { terser } from "rollup-plugin-terser";
|
||||||
|
import typescript from 'rollup-plugin-typescript2';
|
||||||
|
import serve from 'rollup-plugin-serve';
|
||||||
|
|
||||||
|
const devMode = process.env.NODE_ENV === 'development';
|
||||||
|
const shouldServe = !!process.env.SERVE;
|
||||||
|
const filename = 'text-frame' + (devMode ? '' : '-min') + '.js';
|
||||||
|
const plugins = [
|
||||||
|
typescript({
|
||||||
|
clean: true,
|
||||||
|
tsconfig: './tsconfig.json'
|
||||||
|
}),
|
||||||
|
!devMode && terser(),
|
||||||
|
shouldServe && serve({
|
||||||
|
open: true,
|
||||||
|
contentBase: './',
|
||||||
|
openPage: '/example/index.html',
|
||||||
|
port: 5000
|
||||||
|
})
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/text-frame.ts',
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file: path.resolve('dist', 'esm', filename),
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap: devMode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: path.resolve('dist', 'umd', filename),
|
||||||
|
format: 'umd',
|
||||||
|
name: 'TextFrame',
|
||||||
|
sourcemap: devMode
|
||||||
|
}
|
||||||
|
],
|
||||||
|
plugins: plugins,
|
||||||
|
watch: {
|
||||||
|
include: ['src/**/*', 'example/*']
|
||||||
|
}
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
export enum TextAlign {
|
||||||
|
center = 'center',
|
||||||
|
start = 'start',
|
||||||
|
end = 'end',
|
||||||
|
left = 'left',
|
||||||
|
right = 'right',
|
||||||
|
justify = 'justify'
|
||||||
|
}
|
||||||
|
export interface BaseOptions {
|
||||||
|
color?: string
|
||||||
|
rtl?: boolean
|
||||||
|
trim?: boolean
|
||||||
|
fontFamily?: string
|
||||||
|
fontWeight?: string | number
|
||||||
|
fontSize?: number
|
||||||
|
lineHeight?: number
|
||||||
|
textIndent?: number
|
||||||
|
textAlign?: TextAlign | 'center' | 'start' | 'end' | 'left' | 'right' | 'justify'
|
||||||
|
textAlignLast?: TextAlign | 'center' | 'start' | 'end' | 'left' | 'right' | 'justify'
|
||||||
|
margin?: number | Margin
|
||||||
|
marginCollapse?: boolean
|
||||||
|
noHeadMargin?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FragmentOptions extends BaseOptions {
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Margin {
|
||||||
|
left?: number
|
||||||
|
right?: number
|
||||||
|
top?: number
|
||||||
|
bottom?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrameOptions extends BaseOptions {
|
||||||
|
viewWidth?: number
|
||||||
|
viewHeight?: number
|
||||||
|
canvasHeight?: number
|
||||||
|
canvasWidth?: number
|
||||||
|
lineStartProhibitedMarks?: string
|
||||||
|
lineEndProhibitedMarks?: string
|
||||||
|
unbreakableRule?: RegExp
|
||||||
|
fragments: FragmentOptions[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrameLine {
|
||||||
|
offset: { x: number, y: number }
|
||||||
|
fragmentOptions: FragmentOptions
|
||||||
|
isLastLine: boolean
|
||||||
|
chars: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextFrame {
|
||||||
|
options: FrameOptions
|
||||||
|
lines: FrameLine[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const
|
||||||
|
defaultOptionBase: BaseOptions = {
|
||||||
|
color: '#000000',
|
||||||
|
fontFamily: 'serif',
|
||||||
|
fontWeight: 'normal',
|
||||||
|
fontSize: 16,
|
||||||
|
rtl: false,
|
||||||
|
margin: 0,
|
||||||
|
marginCollapse: true,
|
||||||
|
noHeadMargin: false,
|
||||||
|
textIndent: 0,
|
||||||
|
textAlign: 'justify',
|
||||||
|
textAlignLast: 'start'
|
||||||
|
}
|
||||||
|
, defaultOptions: FrameOptions = {
|
||||||
|
...defaultOptionBase,
|
||||||
|
viewWidth: 300,
|
||||||
|
viewHeight: 150,
|
||||||
|
lineStartProhibitedMarks: `、,,..。::;;!!??'"」』”’)]})】〗〕]}》〉–~~—·.‧•・//`,
|
||||||
|
lineEndProhibitedMarks: `「『“‘([{(【〖〔[〔《〈//`,
|
||||||
|
unbreakableRule: /──|……|[\w\d]+/,
|
||||||
|
fragments: null
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveBaseOptions(options: BaseOptions) {
|
||||||
|
// we don't cache devicePixelRatio globally, since it may change
|
||||||
|
const dpi = devicePixelRatio || 1;
|
||||||
|
const resolvedOptions: BaseOptions = {
|
||||||
|
...defaultOptionBase,
|
||||||
|
...(options || Object())
|
||||||
|
};
|
||||||
|
resolvedOptions.textIndent = (resolvedOptions.textIndent | 0) * dpi;
|
||||||
|
if (resolvedOptions.fontSize > 0) {
|
||||||
|
resolvedOptions.fontSize *= dpi;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolvedOptions.fontSize = defaultOptions.fontSize * dpi;
|
||||||
|
}
|
||||||
|
if (resolvedOptions.lineHeight > 0) {
|
||||||
|
resolvedOptions.lineHeight *= dpi;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolvedOptions.lineHeight = resolvedOptions.fontSize * 1.5;
|
||||||
|
}
|
||||||
|
if (!(resolvedOptions.textAlign in TextAlign)) {
|
||||||
|
resolvedOptions.textAlign = TextAlign.start;
|
||||||
|
}
|
||||||
|
if (typeof resolvedOptions.margin === 'object' && resolvedOptions !== null) {
|
||||||
|
for (const s of ['left', 'right', 'top', 'bottom']) {
|
||||||
|
resolvedOptions.margin[s] = (resolvedOptions.margin[s] | 0) * dpi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const margin = (resolvedOptions.margin as number | 0) * dpi;
|
||||||
|
resolvedOptions.margin = {
|
||||||
|
left: margin,
|
||||||
|
right: margin,
|
||||||
|
top: margin,
|
||||||
|
bottom: margin
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return resolvedOptions;
|
||||||
|
}
|
||||||
|
export function resolveOptions(options: FrameOptions) {
|
||||||
|
const dpi = devicePixelRatio
|
||||||
|
, resolvedOptions: FrameOptions = {
|
||||||
|
...defaultOptions,
|
||||||
|
...resolveBaseOptions(options)
|
||||||
|
};
|
||||||
|
resolvedOptions.canvasWidth = options.canvasWidth || resolvedOptions.viewWidth * dpi;
|
||||||
|
resolvedOptions.canvasHeight = options.canvasHeight || resolvedOptions.viewHeight * dpi;
|
||||||
|
return resolvedOptions;
|
||||||
|
}
|
||||||
|
export function resolveFragmentOptions(slideOps: FrameOptions, fragOps: FragmentOptions) {
|
||||||
|
// margin is not inherited from frame options
|
||||||
|
const { margin: _, ...fallback } = slideOps;
|
||||||
|
// for ignored config, fallback to global option, then fallback to default option
|
||||||
|
let resolvedOptions: FragmentOptions = { ...fallback, ...fragOps };
|
||||||
|
resolvedOptions.text = resolvedOptions.text || '';
|
||||||
|
resolvedOptions.text =
|
||||||
|
resolvedOptions.text.replace('\r\n', '\n').replace('\r', '\n');
|
||||||
|
resolvedOptions = resolveBaseOptions(resolvedOptions) as FragmentOptions;
|
||||||
|
if (resolvedOptions.trim) {
|
||||||
|
resolvedOptions.text = resolvedOptions.text.trim();
|
||||||
|
}
|
||||||
|
return resolvedOptions;
|
||||||
|
}
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
import { TextFrame, FrameLine, FragmentOptions, Margin, FrameOptions, TextAlign, resolveOptions, resolveFragmentOptions } from "./options";
|
||||||
|
const ctx = document.createElement('canvas').getContext('2d');
|
||||||
|
|
||||||
|
export function computeTextFrames(options: FrameOptions) {
|
||||||
|
const ops = resolveOptions(options || {} as any)
|
||||||
|
, frames: TextFrame[] = []
|
||||||
|
, frameMargin = ops.margin as Margin
|
||||||
|
, maxFrameX = ops.canvasWidth - 1 - frameMargin.right
|
||||||
|
, maxFrameY = ops.canvasHeight - 1 - frameMargin.bottom;
|
||||||
|
let cursor = { x: 0, y: 0 }
|
||||||
|
, textIndex = 0
|
||||||
|
, maxLineX = 0
|
||||||
|
, remainingMargin = 0
|
||||||
|
, previousMarginCollapse = false
|
||||||
|
, currFragment: FragmentOptions
|
||||||
|
, currentText: string
|
||||||
|
, currentLine: FrameLine
|
||||||
|
, currentFrame: TextFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get next char.
|
||||||
|
* JavaScript encode string with UTF-16, where one unicode code point may be
|
||||||
|
* encoded with two chars(such as emoji), so we need to deal with that case.
|
||||||
|
* Refrence(English): @see https://en.wikipedia.org/wiki/UTF-16
|
||||||
|
* Refrence(Chinese): @see https://zh.wikipedia.org/wiki/UTF-16
|
||||||
|
*/
|
||||||
|
function nextChar() {
|
||||||
|
if (!currentText) { return ''; }
|
||||||
|
const codePoint = currentText.codePointAt(textIndex);
|
||||||
|
if (codePoint === undefined) { return ''; }
|
||||||
|
const charLen = String.fromCodePoint(codePoint).length;
|
||||||
|
return currentText.slice(textIndex, textIndex + charLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move cursor to a new frame
|
||||||
|
function newFrame() {
|
||||||
|
if (currentFrame) {
|
||||||
|
finishFrame();
|
||||||
|
}
|
||||||
|
currentFrame = {
|
||||||
|
options: ops,
|
||||||
|
lines: []
|
||||||
|
}
|
||||||
|
currentLine = null;
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
cursor = { x: frameMargin.left, y: frameMargin.top };
|
||||||
|
};
|
||||||
|
|
||||||
|
// move cursor to head of a new line
|
||||||
|
function newLine() {
|
||||||
|
const fragOps = currFragment;
|
||||||
|
if (!currentFrame) { newFrame(); }
|
||||||
|
finishLine();
|
||||||
|
if (currentFrame.lines.length === 0) {
|
||||||
|
// if there is no line in current frame, just start a new line without
|
||||||
|
// checking available height, since a frame must be high enouth for one
|
||||||
|
// line
|
||||||
|
cursor = {
|
||||||
|
x: frameMargin.left + fragOps.margin['left'],
|
||||||
|
y: frameMargin.top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// cursor.y are based on start of text box, but maxFrameY is max y which
|
||||||
|
// the end of text box can reach, so we need to minus an extra lineHeight
|
||||||
|
const availableHeight = maxFrameY - (cursor.y + fragOps.lineHeight);
|
||||||
|
if (availableHeight < fragOps.lineHeight) {
|
||||||
|
newFrame();
|
||||||
|
// we don't plus the top margin of current fragment, because fragment
|
||||||
|
// verticle margin should only be computed at the starting of fragment.
|
||||||
|
cursor = {
|
||||||
|
x: frameMargin.left + fragOps.margin['left'],
|
||||||
|
y: frameMargin.top
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cursor.x = frameMargin.left + fragOps.margin['left'];
|
||||||
|
cursor.y += fragOps.lineHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentLine = {
|
||||||
|
chars: [],
|
||||||
|
isLastLine: false,
|
||||||
|
fragmentOptions: fragOps,
|
||||||
|
offset: { ...cursor },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function finishLine() {
|
||||||
|
if (currentLine) {
|
||||||
|
// if a line exist currently, commit it
|
||||||
|
currentFrame.lines.push(currentLine);
|
||||||
|
currentLine = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function finishLastLine() {
|
||||||
|
if (currentLine) {
|
||||||
|
currentLine.isLastLine = true;
|
||||||
|
}
|
||||||
|
finishLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush work on current frame
|
||||||
|
function finishFrame() {
|
||||||
|
if (currentFrame) {
|
||||||
|
if (currentLine) { finishLine(); }
|
||||||
|
frames.push(currentFrame);
|
||||||
|
currentFrame = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (let fragOps of (ops.fragments || [])) {
|
||||||
|
// flush work on previous fragment
|
||||||
|
finishLastLine();
|
||||||
|
fragOps = currFragment = resolveFragmentOptions(options, fragOps);
|
||||||
|
ctx.font = fragOps.fontWeight + ' '
|
||||||
|
+ fragOps.fontSize + 'px '
|
||||||
|
+ fragOps.fontFamily;
|
||||||
|
newLine();
|
||||||
|
if (currentFrame.lines.length === 0) {
|
||||||
|
// don't let new frame affected by remaining margin from previous frame
|
||||||
|
remainingMargin = 0;
|
||||||
|
}
|
||||||
|
if (fragOps.marginCollapse && previousMarginCollapse) {
|
||||||
|
remainingMargin = Math.max(fragOps.margin['top'], remainingMargin);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
remainingMargin += fragOps.margin['top'];
|
||||||
|
}
|
||||||
|
if (currentFrame.lines.length > 0) {
|
||||||
|
if (cursor.y + remainingMargin + fragOps.lineHeight > maxFrameY + 1) {
|
||||||
|
// there is no enough space on current frame, get a new one
|
||||||
|
currentLine = null;
|
||||||
|
finishFrame();
|
||||||
|
newLine();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cursor.y += remainingMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// not "else if". we check, or maybe re-check whether current frame is
|
||||||
|
// empty, because current frame may be resetted above
|
||||||
|
if (currentFrame.lines.length === 0 && !fragOps.noHeadMargin) {
|
||||||
|
// current frame is empty, we don't create new frame anyway, so we can
|
||||||
|
// skip checking space
|
||||||
|
cursor.y = frameMargin.top + fragOps.margin['top'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if current frame is not empty, we have processed above; if
|
||||||
|
// fragOps.noHeadMargin is true, the cursor is set correctly when we
|
||||||
|
// create current frame. so there is nothing to do.
|
||||||
|
}
|
||||||
|
if (fragOps.textIndent > 0) {
|
||||||
|
cursor.x += fragOps.textIndent;
|
||||||
|
}
|
||||||
|
// we moved cursor, and the slot of line start may have changed, reset it
|
||||||
|
currentLine.offset.x = cursor.x;
|
||||||
|
currentLine.offset.y = cursor.y;
|
||||||
|
maxLineX = maxFrameX - fragOps.margin['right'];
|
||||||
|
currentText = fragOps.text;
|
||||||
|
textIndex = 0;
|
||||||
|
|
||||||
|
while (textIndex < currentText.length) {
|
||||||
|
let currentChar = nextChar()
|
||||||
|
, currentCharLength = currentChar.length
|
||||||
|
, currentCharWidth = ctx.measureText(currentChar).width
|
||||||
|
, availableWidth = maxLineX + 1 - cursor.x;
|
||||||
|
if (currentChar === '\n') {
|
||||||
|
// current char is a carrige return, just move cusor to next line.
|
||||||
|
// even if the last char of current line if a prohibition of line end,
|
||||||
|
// we don't process it, since the paragraph has ended.
|
||||||
|
finishLastLine();
|
||||||
|
newLine();
|
||||||
|
currentChar = null;
|
||||||
|
currentCharWidth = 0;
|
||||||
|
// we don't indent text since it's not the begining of a paragraph.
|
||||||
|
}
|
||||||
|
else if (availableWidth < currentCharWidth) {
|
||||||
|
// there is no enough space to place current char, we need to turn to a
|
||||||
|
// new line.
|
||||||
|
// but before that, we have to process the prohibition of line start
|
||||||
|
// and line end.
|
||||||
|
let lineStartChar = currentChar, lineEndChar, newIndex = textIndex;
|
||||||
|
for (let i = currentLine.chars.length - 1; i >= 0; --i) {
|
||||||
|
lineEndChar = currentLine.chars[i];
|
||||||
|
// we check whether there is a prohibition, if true, try to fix.
|
||||||
|
// if a prohibition found, we try to fix it through moving the
|
||||||
|
// line-end char of current line to the start of next line. then
|
||||||
|
// check, repeat.
|
||||||
|
// if we can't fix when current line has only one char left, just do
|
||||||
|
// nothing as if there was no prohibition found.
|
||||||
|
if (!ops.lineStartProhibitedMarks.includes(lineStartChar)
|
||||||
|
&& !ops.lineEndProhibitedMarks.includes(lineEndChar)
|
||||||
|
&& !ops.unbreakableRule.test(lineEndChar + lineStartChar)) {
|
||||||
|
// fix
|
||||||
|
currentChar = lineStartChar;
|
||||||
|
currentCharLength = currentChar.length;
|
||||||
|
currentCharWidth = ctx.measureText(currentChar).width;
|
||||||
|
textIndex = newIndex;
|
||||||
|
currentLine.chars = currentLine.chars.slice(0, i + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
newIndex -= lineStartChar.length;
|
||||||
|
lineStartChar = lineEndChar;
|
||||||
|
}
|
||||||
|
newLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentChar) {
|
||||||
|
currentLine.chars.push(currentChar);
|
||||||
|
}
|
||||||
|
cursor.x += currentCharWidth;
|
||||||
|
textIndex += currentCharLength;
|
||||||
|
}
|
||||||
|
remainingMargin = fragOps.margin['bottom'];
|
||||||
|
previousMarginCollapse = fragOps.marginCollapse;
|
||||||
|
}
|
||||||
|
finishLastLine();
|
||||||
|
finishFrame();
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// prepare canvas to render text.
|
||||||
|
function prepareCanvas(
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
fragOptions: FragmentOptions,
|
||||||
|
line: FrameLine) {
|
||||||
|
const mergedOptions = fragOptions;
|
||||||
|
if (mergedOptions) {
|
||||||
|
context.font = mergedOptions.fontWeight + ' '
|
||||||
|
+ mergedOptions.fontSize + 'px '
|
||||||
|
+ mergedOptions.fontFamily;
|
||||||
|
context.fillStyle = mergedOptions.color;
|
||||||
|
// since context.direction is an experimental technology, we cannot rely on it
|
||||||
|
context.direction = 'ltr';
|
||||||
|
let textAlign = mergedOptions.textAlign;
|
||||||
|
if (line.isLastLine && mergedOptions.textAlign === 'justify') {
|
||||||
|
textAlign = mergedOptions.textAlignLast;
|
||||||
|
}
|
||||||
|
if (textAlign === 'start') {
|
||||||
|
context.textAlign = mergedOptions.rtl ? 'right' : 'left';
|
||||||
|
}
|
||||||
|
else if (textAlign === 'end') {
|
||||||
|
context.textAlign = mergedOptions.rtl ? 'left' : 'right';
|
||||||
|
}
|
||||||
|
else if (['center', 'left', 'right'].includes(textAlign)) {
|
||||||
|
context.textAlign = textAlign as any;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.textAlign = 'left';
|
||||||
|
}
|
||||||
|
context.textBaseline = 'middle';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish work on current line
|
||||||
|
function renderLine(
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
options: FrameOptions,
|
||||||
|
line: FrameLine) {
|
||||||
|
if (line) {
|
||||||
|
const fragOps = line.fragmentOptions
|
||||||
|
, chars = fragOps.rtl
|
||||||
|
? line.chars.slice(0).reverse()
|
||||||
|
: line.chars
|
||||||
|
, frameMargin = options.margin as Margin
|
||||||
|
, fragMargin = fragOps.margin as Margin;
|
||||||
|
// draw text
|
||||||
|
prepareCanvas(context, fragOps, line);
|
||||||
|
let { x: offsetX, y: offsetY } = line.offset
|
||||||
|
, isJustifyMode = fragOps.textAlign === TextAlign.justify
|
||||||
|
, textIndent = offsetX - frameMargin.left - fragOps.margin['left'];
|
||||||
|
// offsetY is based on top of text box, and context.textBaseline is
|
||||||
|
// 'middle', so plus a half line height
|
||||||
|
offsetY += fragOps.lineHeight / 2;
|
||||||
|
if (isJustifyMode && line.isLastLine && fragOps.textAlignLast !== TextAlign.justify) {
|
||||||
|
isJustifyMode = false;
|
||||||
|
}
|
||||||
|
if (isJustifyMode) {
|
||||||
|
const contentWidth = context.measureText(chars.join('')).width
|
||||||
|
, remainWidth = (options.canvasWidth - contentWidth
|
||||||
|
- frameMargin.left - frameMargin.right - fragMargin.left
|
||||||
|
- fragMargin.right - textIndent)
|
||||||
|
, gap = remainWidth / (chars.length - 1);
|
||||||
|
if (fragOps.rtl) {
|
||||||
|
// initially, we assume using left-to-right mode, but it's right-to-left
|
||||||
|
// mode now, we'll reverse the text and recompute offsetX, and we have
|
||||||
|
// to "move" text indention to right
|
||||||
|
offsetX -= textIndent;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < chars.length; ++i) {
|
||||||
|
context.fillText(chars[i], Math.round(offsetX), offsetY);
|
||||||
|
offsetX += context.measureText(chars[i]).width + gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// we use context.textAlign not fragOps.textAlign, because the first one
|
||||||
|
// has been normalized when preparing canvas
|
||||||
|
if (context.textAlign === 'center') {
|
||||||
|
offsetX = context.canvas.width / 2;
|
||||||
|
}
|
||||||
|
else if (context.textAlign === 'right') {
|
||||||
|
offsetX = (options.canvasWidth - frameMargin.right
|
||||||
|
- fragOps.margin['right'] - textIndent);
|
||||||
|
}
|
||||||
|
context.fillText(chars.join(''), offsetX, offsetY);
|
||||||
|
}
|
||||||
|
line = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderFrame(context: CanvasRenderingContext2D, frame: TextFrame, clear?: boolean) {
|
||||||
|
if (!context) {
|
||||||
|
throw Error("error: text-frame - must provide drawing context!");
|
||||||
|
}
|
||||||
|
if (!frame) {
|
||||||
|
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const canvasWidth = Math.round(frame.options.canvasWidth)
|
||||||
|
, canvasHeight = Math.round(frame.options.canvasHeight);
|
||||||
|
if (clear
|
||||||
|
|| context.canvas.width !== canvasWidth
|
||||||
|
|| context.canvas.height !== canvasHeight) {
|
||||||
|
context.canvas.width = canvasWidth
|
||||||
|
context.canvas.height = canvasHeight
|
||||||
|
}
|
||||||
|
for (const line of frame.lines) {
|
||||||
|
renderLine(context, frame.options, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||||
|
"module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
"lib": ["ES5", "ES2015", "DOM"], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": false, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
// "skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user