\n GT')
+
+ for i, filename in enumerate(sorted(images_dir)):
+ if filename.endswith("txt"): continue
+ print(filename)
+
+ base = "{}".format(filename)
+ if True:
+ html.write("
\n")
+ html.write(f'
{filename}\n GT')
+ html.write('
GT 310\n
' % (base))
+ html.write("
\n")
+
+ html.write('\n')
+ html.write('
\n')
+ html.write('\n\n')
+ print("ok")
+
+
+def crop_seal_from_img(label_file, data_dir, save_dir, save_gt_path):
+
+ if not os.path.exists(save_dir):
+ os.makedirs(save_dir)
+
+ datas = open(label_file, 'r').readlines()
+ all_gts = []
+ count = 0
+ for idx, line in enumerate(datas):
+ img_path, label = line.strip().split('\t')
+ img_path = os.path.join(data_dir, img_path)
+
+ label = json.loads(label)
+ src_im = cv2.imread(img_path)
+ if src_im is None:
+ continue
+
+ for c, anno in enumerate(label):
+ seal_poly = anno['seal_box']
+ txt_boxes = anno['polys']
+ txts = anno['texts']
+ ignore_tags = anno['ignore_tags']
+
+ box = poly2box(seal_poly)
+ img_crop = src_im[box[0][1]:box[2][1], box[0][0]:box[2][0], :]
+
+ save_path = os.path.join(save_dir, f"{idx}_{c}.jpg")
+ cv2.imwrite(save_path, np.array(img_crop))
+
+ img_gt = []
+ for i in range(len(txts)):
+ txt_boxes_crop = np.array(txt_boxes[i])
+ txt_boxes_crop[:, 1] -= box[0, 1]
+ txt_boxes_crop[:, 0] -= box[0, 0]
+ img_gt.append({'transcription': txts[i], "points": txt_boxes_crop.tolist(), "ignore_tag": ignore_tags[i]})
+
+ if len(img_gt) >= 1:
+ count += 1
+ save_gt = f"{os.path.basename(save_path)}\t{json.dumps(img_gt)}\n"
+
+ all_gts.append(save_gt)
+
+ print(f"The num of all image: {len(all_gts)}, and the number of useful image: {count}")
+ if not os.path.exists(os.path.dirname(save_gt_path)):
+ os.makedirs(os.path.dirname(save_gt_path))
+
+ with open(save_gt_path, "w") as f:
+ f.writelines(all_gts)
+ f.close()
+ print("Done")
+
+
+if __name__ == "__main__":
+ # 数据处理
+ gen_extract_label("./seal_labeled_datas", "./seal_labeled_datas/Label.txt", "./seal_ppocr_gt/seal_det_img.txt", "./seal_ppocr_gt/seal_ppocr_img.txt")
+ vis_seal_ppocr("./seal_labeled_datas", "./seal_ppocr_gt/seal_ppocr_img.txt", "./seal_ppocr_gt/seal_ppocr_vis/")
+ draw_html("./seal_ppocr_gt/seal_ppocr_vis/", "./vis_seal_ppocr.html")
+ seal_ppocr_img_label = "./seal_ppocr_gt/seal_ppocr_img.txt"
+ crop_seal_from_img(seal_ppocr_img_label, "./seal_labeled_datas/", "./seal_img_crop", "./seal_img_crop/label.txt")
+```
+
+
+
+处理完成后,生成的文件如下:
+
+```text linenums="1"
+├── seal_img_crop/
+│ ├── 0_0.jpg
+│ ├── ...
+│ └── label.txt
+├── seal_ppocr_gt/
+│ ├── seal_det_img.txt
+│ ├── seal_ppocr_img.txt
+│ └── seal_ppocr_vis/
+│ ├── test1.png
+│ ├── ...
+└── vis_seal_ppocr.html
+
+```
+
+其中`seal_img_crop/label.txt`文件为印章识别标签文件,其内容格式为:
+
+```text linenums="1"
+0_0.jpg [{"transcription": "\u7535\u5b50\u56de\u5355", "points": [[29, 73], [96, 73], [96, 90], [29, 90]], "ignore_tag": false}, {"transcription": "\u4e91\u5357\u7701\u519c\u6751\u4fe1\u7528\u793e", "points": [[9, 58], [26, 63], [30, 49], [38, 35], [47, 29], [64, 26], [81, 32], [90, 45], [94, 63], [118, 57], [110, 35], [95, 17], [67, 0], [38, 7], [21, 23], [10, 43]], "ignore_tag": false}, {"transcription": "\u4e13\u7528\u7ae0", "points": [[29, 87], [95, 87], [95, 106], [29, 106]], "ignore_tag": false}]
+```
+
+可以直接用于PaddleOCR的PGNet算法的训练。
+
+`seal_ppocr_gt/seal_det_img.txt`为印章检测标签文件,其内容格式为:
+
+```text linenums="1"
+img/test1.png [{"polys": [[408, 232], [537, 232], [537, 352], [408, 352]], "cls": 1}]
+```
+
+为了使用PaddleDetection工具完成印章检测模型的训练,需要将`seal_det_img.txt`转换为COCO或者VOC的数据标注格式。
+
+可以直接使用下述代码将印章检测标注转换成VOC格式。
+
+
+
+```python linenums="1"
+import numpy as np
+import json
+import cv2
+import os
+from shapely.geometry import Polygon
+
+seal_train_gt = "./seal_ppocr_gt/seal_det_img.txt"
+# 注:仅用于示例,实际使用中需要分别转换训练集和测试集的标签
+seal_valid_gt = "./seal_ppocr_gt/seal_det_img.txt"
+
+def gen_main_train_txt(mode='train'):
+ if mode == "train":
+ file_path = seal_train_gt
+ if mode in ['valid', 'test']:
+ file_path = seal_valid_gt
+
+ save_path = f"./seal_VOC/ImageSets/Main/{mode}.txt"
+ save_train_path = f"./seal_VOC/{mode}.txt"
+ if not os.path.exists(os.path.dirname(save_path)):
+ os.makedirs(os.path.dirname(save_path))
+
+ datas = open(file_path, 'r').readlines()
+ img_names = []
+ train_names = []
+ for line in datas:
+ img_name = line.strip().split('\t')[0]
+ img_name = os.path.basename(img_name)
+ (i_name, extension) = os.path.splitext(img_name)
+ t_name = 'JPEGImages/'+str(img_name)+' '+'Annotations/'+str(i_name)+'.xml\n'
+ train_names.append(t_name)
+ img_names.append(i_name + "\n")
+
+ with open(save_train_path, "w") as f:
+ f.writelines(train_names)
+ f.close()
+
+ with open(save_path, "w") as f:
+ f.writelines(img_names)
+ f.close()
+
+ print(f"{mode} save done")
+
+
+def gen_xml_label(mode='train'):
+ if mode == "train":
+ file_path = seal_train_gt
+ if mode in ['valid', 'test']:
+ file_path = seal_valid_gt
+
+ datas = open(file_path, 'r').readlines()
+ img_names = []
+ train_names = []
+ anno_path = "./seal_VOC/Annotations"
+ img_path = "./seal_VOC/JPEGImages"
+
+ if not os.path.exists(anno_path):
+ os.makedirs(anno_path)
+ if not os.path.exists(img_path):
+ os.makedirs(img_path)
+
+ for idx, line in enumerate(datas):
+ img_name, label = line.strip().split('\t')
+ img = cv2.imread(os.path.join("./seal_labeled_datas", img_name))
+ cv2.imwrite(os.path.join(img_path, os.path.basename(img_name)), img)
+ height, width, c = img.shape
+ img_name = os.path.basename(img_name)
+ (i_name, extension) = os.path.splitext(img_name)
+ label = json.loads(label)
+
+ xml_file = open(("./seal_VOC/Annotations" + '/' + i_name + '.xml'), 'w')
+ xml_file.write('\n')
+ xml_file.write(' seal_VOC\n')
+ xml_file.write(' ' + str(img_name) + '\n')
+ xml_file.write(' ' + 'Annotations/' + str(img_name) + '\n')
+ xml_file.write(' \n')
+ xml_file.write(' ' + str(width) + '\n')
+ xml_file.write(' ' + str(height) + '\n')
+ xml_file.write(' 3\n')
+ xml_file.write(' \n')
+ xml_file.write(' 0\n')
+
+ for anno in label:
+ poly = anno['polys']
+ if anno['cls'] == 1:
+ gt_cls = 'redseal'
+ xmin = np.min(np.array(poly)[:, 0])
+ ymin = np.min(np.array(poly)[:, 1])
+ xmax = np.max(np.array(poly)[:, 0])
+ ymax = np.max(np.array(poly)[:, 1])
+ xmin,ymin,xmax,ymax= int(xmin),int(ymin),int(xmax),int(ymax)
+ xml_file.write(' \n')
+ xml_file.write('')
+ xml_file.close()
+ print(f'{mode} xml save done!')
+
+
+gen_main_train_txt()
+gen_main_train_txt('valid')
+gen_xml_label('train')
+gen_xml_label('valid')
+
+```
+
+
+
+数据处理完成后,转换为VOC格式的印章检测数据存储在~/data/seal_VOC目录下,目录组织结构为:
+
+```text linenums="1"
+├── Annotations/
+├── ImageSets/
+│ └── Main/
+│ ├── train.txt
+│ └── valid.txt
+├── JPEGImages/
+├── train.txt
+└── valid.txt
+└── label_list.txt
+```
+
+Annotations下为数据的标签,JPEGImages目录下为图像文件,label_list.txt为标注检测框类别标签文件。
+
+在接下来一节中,将介绍如何使用PaddleDetection工具库完成印章检测模型的训练。
+
+## 4. 印章检测实践
+
+在实际应用中,印章多是出现在合同,发票,公告等场景中,印章文字识别的任务需要排除图像中背景文字的影响,因此需要先检测出图像中的印章区域。
+
+借助PaddleDetection目标检测库可以很容易的实现印章检测任务,使用PaddleDetection训练印章检测任务流程如下:
+
+- 选择算法
+- 修改数据集配置路径
+- 启动训练
+
+**算法选择**
+
+PaddleDetection中有许多检测算法可以选择,考虑到每条数据中印章区域较为清晰,且考虑到性能需求。在本项目中,我们采用mobilenetv3为backbone的ppyolo算法完成印章检测任务,对应的配置文件是:configs/ppyolo/ppyolo_mbv3_large.yml
+
+**修改配置文件**
+
+配置文件中的默认数据路径是COCO,
+需要修改为印章检测的数据路径,主要修改如下:
+在配置文件'configs/ppyolo/ppyolo_mbv3_large.yml'末尾增加如下内容:
+
+```yaml linenums="1"
+metric: VOC
+map_type: 11point
+num_classes: 2
+
+TrainDataset:
+ !VOCDataSet
+ dataset_dir: dataset/seal_VOC
+ anno_path: train.txt
+ label_list: label_list.txt
+ data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']
+
+EvalDataset:
+ !VOCDataSet
+ dataset_dir: dataset/seal_VOC
+ anno_path: test.txt
+ label_list: label_list.txt
+ data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']
+
+TestDataset:
+ !ImageFolder
+ anno_path: dataset/seal_VOC/label_list.txt
+```
+
+配置文件中设置的数据路径在PaddleDetection/dataset目录下,我们可以将处理后的印章检测训练数据移动到PaddleDetection/dataset目录下或者创建一个软连接。
+
+```bash linenums="1"
+!ln -s seal_VOC ./PaddleDetection/dataset/
+```
+
+另外图象中印章数量比较少,可以调整NMS后处理的检测框数量,即keep_top_k,nms_top_k 从100,1000,调整为10,100。在配置文件'configs/ppyolo/ppyolo_mbv3_large.yml'末尾增加如下内容完成后处理参数的调整
+
+```yaml linenums="1"
+BBoxPostProcess:
+ decode:
+ name: YOLOBox
+ conf_thresh: 0.005
+ downsample_ratio: 32
+ clip_bbox: true
+ scale_x_y: 1.05
+ nms:
+ name: MultiClassNMS
+ keep_top_k: 10 # 修改前100
+ nms_threshold: 0.45
+ nms_top_k: 100 # 修改前1000
+ score_threshold: 0.005
+```
+
+修改完成后,需要在PaddleDetection中增加印章数据的处理代码,即在PaddleDetection/ppdet/data/source/目录下创建seal.py文件,文件中填充如下代码:
+
+
+
+```python linenums="1"
+import os
+import numpy as np
+from ppdet.core.workspace import register, serializable
+from .dataset import DetDataset
+import cv2
+import json
+
+from ppdet.utils.logger import setup_logger
+logger = setup_logger(__name__)
+
+
+@register
+@serializable
+class SealDataSet(DetDataset):
+ """
+ Load dataset with COCO format.
+
+ Args:
+ dataset_dir (str): root directory for dataset.
+ image_dir (str): directory for images.
+ anno_path (str): coco annotation file path.
+ data_fields (list): key name of data dictionary, at least have 'image'.
+ sample_num (int): number of samples to load, -1 means all.
+ load_crowd (bool): whether to load crowded ground-truth.
+ False as default
+ allow_empty (bool): whether to load empty entry. False as default
+ empty_ratio (float): the ratio of empty record number to total
+ record's, if empty_ratio is out of [0. ,1.), do not sample the
+ records and use all the empty entries. 1. as default
+ """
+
+ def __init__(self,
+ dataset_dir=None,
+ image_dir=None,
+ anno_path=None,
+ data_fields=['image'],
+ sample_num=-1,
+ load_crowd=False,
+ allow_empty=False,
+ empty_ratio=1.):
+ super(SealDataSet, self).__init__(dataset_dir, image_dir, anno_path,
+ data_fields, sample_num)
+ self.load_image_only = False
+ self.load_semantic = False
+ self.load_crowd = load_crowd
+ self.allow_empty = allow_empty
+ self.empty_ratio = empty_ratio
+
+ def _sample_empty(self, records, num):
+ # if empty_ratio is out of [0. ,1.), do not sample the records
+ if self.empty_ratio < 0. or self.empty_ratio >= 1.:
+ return records
+ import random
+ sample_num = min(
+ int(num * self.empty_ratio / (1 - self.empty_ratio)), len(records))
+ records = random.sample(records, sample_num)
+ return records
+
+ def parse_dataset(self):
+ anno_path = os.path.join(self.dataset_dir, self.anno_path)
+ image_dir = os.path.join(self.dataset_dir, self.image_dir)
+
+ records = []
+ empty_records = []
+ ct = 0
+
+ assert anno_path.endswith('.txt'), \
+ 'invalid seal_gt file: ' + anno_path
+
+ all_datas = open(anno_path, 'r').readlines()
+
+ for idx, line in enumerate(all_datas):
+ im_path, label = line.strip().split('\t')
+ img_path = os.path.join(image_dir, im_path)
+ label = json.loads(label)
+ im_h, im_w, im_c = cv2.imread(img_path).shape
+
+ coco_rec = {
+ 'im_file': img_path,
+ 'im_id': np.array([idx]),
+ 'h': im_h,
+ 'w': im_w,
+ } if 'image' in self.data_fields else {}
+
+ if not self.load_image_only:
+ bboxes = []
+ for anno in label:
+ poly = anno['polys']
+ # poly to box
+ x1 = np.min(np.array(poly)[:, 0])
+ y1 = np.min(np.array(poly)[:, 1])
+ x2 = np.max(np.array(poly)[:, 0])
+ y2 = np.max(np.array(poly)[:, 1])
+ eps = 1e-5
+ if x2 - x1 > eps and y2 - y1 > eps:
+ clean_box = [
+ round(float(x), 3) for x in [x1, y1, x2, y2]
+ ]
+ anno = {'clean_box': clean_box, 'gt_cls':int(anno['cls'])}
+ bboxes.append(anno)
+ else:
+ logger.info("invalid box")
+
+ num_bbox = len(bboxes)
+ if num_bbox <= 0:
+ continue
+
+ gt_bbox = np.zeros((num_bbox, 4), dtype=np.float32)
+ gt_class = np.zeros((num_bbox, 1), dtype=np.int32)
+ is_crowd = np.zeros((num_bbox, 1), dtype=np.int32)
+ # gt_poly = [None] * num_bbox
+
+ for i, box in enumerate(bboxes):
+ gt_class[i][0] = box['gt_cls']
+ gt_bbox[i, :] = box['clean_box']
+ is_crowd[i][0] = 0
+
+ gt_rec = {
+ 'is_crowd': is_crowd,
+ 'gt_class': gt_class,
+ 'gt_bbox': gt_bbox,
+ # 'gt_poly': gt_poly,
+ }
+
+ for k, v in gt_rec.items():
+ if k in self.data_fields:
+ coco_rec[k] = v
+
+ records.append(coco_rec)
+ ct += 1
+ if self.sample_num > 0 and ct >= self.sample_num:
+ break
+ self.roidbs = records
+```
+
+
+
+**启动训练**
+
+启动单卡训练的命令为:
+
+```bash linenums="1"
+!python3 tools/train.py -c configs/ppyolo/ppyolo_mbv3_large.yml --eval
+
+# 分布式训练命令为:
+!python3 -m paddle.distributed.launch --gpus 0,1,2,3,4,5,6,7 tools/train.py -c configs/ppyolo/ppyolo_mbv3_large.yml --eval
+```
+
+训练完成后,日志中会打印模型的精度:
+
+```bash linenums="1"
+[07/05 11:42:09] ppdet.engine INFO: Eval iter: 0
+[07/05 11:42:14] ppdet.metrics.metrics INFO: Accumulating evaluatation results...
+[07/05 11:42:14] ppdet.metrics.metrics INFO: mAP(0.50, 11point) = 99.31%
+[07/05 11:42:14] ppdet.engine INFO: Total sample number: 112, averge FPS: 26.45840794253432
+[07/05 11:42:14] ppdet.engine INFO: Best test bbox ap is 0.996.
+```
+
+我们可以使用训练好的模型观察预测结果:
+
+```bash linenums="1"
+!python3 tools/infer.py -c configs/ppyolo/ppyolo_mbv3_large.yml -o weights=./output/ppyolo_mbv3_large/model_final.pdparams --img_dir=./test.jpg
+```
+
+预测结果如下:
+
+![](./images/0f650c032b0f4d56bd639713924768cc820635e9977845008d233f465291a29e.jpeg)
+
+## 5. 印章文字识别实践
+
+在使用ppyolo检测到印章区域后,接下来借助PaddleOCR里的文字识别能力,完成印章中文字的识别。
+
+PaddleOCR中的OCR算法包含文字检测算法,文字识别算法以及OCR端对端算法。
+
+文字检测算法负责检测到图像中的文字,再由文字识别模型识别出检测到的文字,进而实现OCR的任务。文字检测+文字识别串联完成OCR任务的架构称为两阶段的OCR算法。相对应的端对端的OCR方法可以用一个算法同时完成文字检测和识别的任务。
+
+| 文字检测 | 文字识别 | 端对端算法 |
+| -------- | -------- | -------- |
+| DB\DB++\EAST\SAST\PSENet | SVTR\CRNN\NRTN\Abinet\SAR\... | PGNet |
+
+本节中将分别介绍端对端的文字检测识别算法以及两阶段的文字检测识别算法在印章检测识别任务上的实践。
+
+### 5.1 端对端印章文字识别实践
+
+本节介绍使用PaddleOCR里的PGNet算法完成印章文字识别。
+
+PGNet属于端对端的文字检测识别算法,在PaddleOCR中的配置文件为:
+[PaddleOCR/configs/e2e/e2e_r50_vd_pg.yml](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.6/configs/e2e/e2e_r50_vd_pg.yml)
+
+使用PGNet完成文字检测识别任务的步骤为:
+
+- 修改配置文件
+- 启动训练
+
+PGNet默认配置文件的数据路径为totaltext数据集路径,本次训练中,需要修改为上一节数据处理后得到的标签文件和数据目录:
+
+训练数据配置修改后如下:
+
+```yaml linenums="1"
+Train:
+ dataset:
+ name: PGDataSet
+ data_dir: ./train_data/seal_ppocr
+ label_file_list: [./train_data/seal_ppocr/seal_ppocr_img.txt]
+ ratio_list: [1.0]
+```
+
+测试数据集配置修改后如下:
+
+```yaml linenums="1"
+Eval:
+ dataset:
+ name: PGDataSet
+ data_dir: ./train_data/seal_ppocr_test
+ label_file_list: [./train_data/seal_ppocr_test/seal_ppocr_img.txt]
+```
+
+启动训练的命令为:
+
+```bash linenums="1"
+!python3 tools/train.py -c configs/e2e/e2e_r50_vd_pg.yml
+```
+
+模型训练完成后,可以得到最终的精度为47.4%。数据量较少,以及数据质量较差会影响模型的训练精度,如果有更多的数据参与训练,精度将进一步提升。
+
+如需获取已训练模型,请点击文末的链接,加入官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁
+
+### 5.2 两阶段印章文字识别实践
+
+上一节介绍了使用PGNet实现印章识别任务的训练流程。本小节将介绍使用PaddleOCR里的文字检测和文字识别算法分别完成印章文字的检测和识别。
+
+#### 5.2.1 印章文字检测
+
+PaddleOCR中包含丰富的文字检测算法,包含DB,DB++,EAST,SAST,PSENet等等。其中DB,DB++,PSENet均支持弯曲文字检测,本项目中,使用DB++作为印章弯曲文字检测算法。
+
+PaddleOCR中发布的db++文字检测算法模型是英文文本检测模型,因此需要重新训练模型。
+
+修改[DB++配置文件](DB++的默认配置文件位于[configs/det/det_r50_db++_icdar15.yml](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.6/configs/det/det_r50_db%2B%2B_icdar15.yml)
+中的数据路径:
+
+```yaml linenums="1"
+Train:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data/seal_ppocr
+ label_file_list: [./train_data/seal_ppocr/seal_ppocr_img.txt]
+ ratio_list: [1.0]
+```
+
+测试数据集配置修改后如下:
+
+```yaml linenums="1"
+Eval:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data/seal_ppocr_test
+ label_file_list: [./train_data/seal_ppocr_test/seal_ppocr_img.txt]
+```
+
+启动训练:
+
+```bash linenums="1"
+!python3 tools/train.py -c configs/det/det_r50_db++_icdar15.yml -o Global.epoch_num=100
+```
+
+考虑到数据较少,通过Global.epoch_num设置仅训练100个epoch。
+模型训练完成后,在测试集上预测的可视化效果如下:
+
+![](./images/498119182f0a414ab86ae2de752fa31c9ddc3a74a76847049cc57884602cb269-20240704185744623.png)
+
+如需获取已训练模型,请点击文末的链接,加入官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁
+
+#### 5.2.2 印章文字识别
+
+上一节中完成了印章文字的检测模型训练,本节介绍印章文字识别模型的训练。识别模型采用SVTR算法,SVTR算法是IJCAI收录的文字识别算法,SVTR模型具备超轻量高精度的特点。
+
+在启动训练之前,需要准备印章文字识别需要的数据集,需要使用如下代码,将印章中的文字区域剪切出来构建训练集。
+
+```python linenums="1"
+import cv2
+import numpy as np
+
+def get_rotate_crop_image(img, points):
+ '''
+ img_height, img_width = img.shape[0:2]
+ left = int(np.min(points[:, 0]))
+ right = int(np.max(points[:, 0]))
+ top = int(np.min(points[:, 1]))
+ bottom = int(np.max(points[:, 1]))
+ img_crop = img[top:bottom, left:right, :].copy()
+ points[:, 0] = points[:, 0] - left
+ points[:, 1] = points[:, 1] - top
+ '''
+ assert len(points) == 4, "shape of points must be 4*2"
+ img_crop_width = int(
+ max(
+ np.linalg.norm(points[0] - points[1]),
+ np.linalg.norm(points[2] - points[3])))
+ img_crop_height = int(
+ max(
+ np.linalg.norm(points[0] - points[3]),
+ np.linalg.norm(points[1] - points[2])))
+ pts_std = np.float32([[0, 0], [img_crop_width, 0],
+ [img_crop_width, img_crop_height],
+ [0, img_crop_height]])
+ M = cv2.getPerspectiveTransform(points, pts_std)
+ dst_img = cv2.warpPerspective(
+ img,
+ M, (img_crop_width, img_crop_height),
+ borderMode=cv2.BORDER_REPLICATE,
+ flags=cv2.INTER_CUBIC)
+ dst_img_height, dst_img_width = dst_img.shape[0:2]
+ if dst_img_height * 1.0 / dst_img_width >= 1.5:
+ dst_img = np.rot90(dst_img)
+ return dst_img
+
+
+def run(data_dir, label_file, save_dir):
+ datas = open(label_file, 'r').readlines()
+ for idx, line in enumerate(datas):
+ img_path, label = line.strip().split('\t')
+ img_path = os.path.join(data_dir, img_path)
+
+ label = json.loads(label)
+ src_im = cv2.imread(img_path)
+ if src_im is None:
+ continue
+
+ for anno in label:
+ seal_box = anno['seal_box']
+ txt_boxes = anno['polys']
+ crop_im = get_rotate_crop_image(src_im, text_boxes)
+
+ save_path = os.path.join(save_dir, f'{idx}.png')
+ if not os.path.exists(save_dir):
+ os.makedirs(save_dir)
+ # print(src_im.shape)
+ cv2.imwrite(save_path, crop_im)
+
+```
+
+数据处理完成后,即可配置训练的配置文件。SVTR配置文件选择[configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.6/configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml)
+修改SVTR配置文件中的训练数据部分如下:
+
+```yaml linenums="1"
+Train:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data/seal_ppocr_crop/
+ label_file_list:
+ - ./train_data/seal_ppocr_crop/train_list.txt
+```
+
+修改预测部分配置文件:
+
+```yaml linenums="1"
+Train:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data/seal_ppocr_crop/
+ label_file_list:
+ - ./train_data/seal_ppocr_crop_test/train_list.txt
+```
+
+启动训练:
+
+```bash linenums="1"
+!python3 tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml
+
+```
+
+训练完成后可以发现测试集指标达到了61%。
+由于数据较少,训练时会发现在训练集上的acc指标远大于测试集上的acc指标,即出现过拟合现象。通过补充数据和一些数据增强可以缓解这个问题。
+
+如需获取已训练模型,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+- PaddleX官方交流频道:
diff --git "a/docs/applications/\345\217\221\347\245\250\345\205\263\351\224\256\344\277\241\346\201\257\346\212\275\345\217\226.md" "b/docs/applications/\345\217\221\347\245\250\345\205\263\351\224\256\344\277\241\346\201\257\346\212\275\345\217\226.md"
new file mode 100644
index 0000000000..2c7508f7a3
--- /dev/null
+++ "b/docs/applications/\345\217\221\347\245\250\345\205\263\351\224\256\344\277\241\346\201\257\346\212\275\345\217\226.md"
@@ -0,0 +1,315 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# 基于VI-LayoutXLM的发票关键信息抽取
+
+## 1. 项目背景及意义
+
+关键信息抽取在文档场景中被广泛使用,如身份证中的姓名、住址信息抽取,快递单中的姓名、联系方式等关键字段内容的抽取。传统基于模板匹配的方案需要针对不同的场景制定模板并进行适配,较为繁琐,不够鲁棒。基于该问题,我们借助飞桨提供的PaddleOCR套件中的关键信息抽取方案,实现对增值税发票场景的关键信息抽取。
+
+## 2. 项目内容
+
+本项目基于PaddleOCR开源套件,以VI-LayoutXLM多模态关键信息抽取模型为基础,针对增值税发票场景进行适配,提取该场景的关键信息。
+
+## 3. 安装环境
+
+```bash linenums="1"
+# 首先git官方的PaddleOCR项目,安装需要的依赖
+# 第一次运行打开该注释
+git clone https://gitee.com/PaddlePaddle/PaddleOCR.git
+cd PaddleOCR
+# 安装PaddleOCR的依赖
+pip install -r requirements.txt
+# 安装关键信息抽取任务的依赖
+pip install -r ./ppstructure/kie/requirements.txt
+```
+
+## 4. 关键信息抽取
+
+基于文档图像的关键信息抽取包含3个部分:(1)文本检测(2)文本识别(3)关键信息抽取方法,包括语义实体识别或者关系抽取,下面分别进行介绍。
+
+### 4.1 文本检测
+
+本文重点关注发票的关键信息抽取模型训练与预测过程,因此在关键信息抽取过程中,直接使用标注的文本检测与识别标注信息进行测试,如果你希望自定义该场景的文本检测模型,完成端到端的关键信息抽取部分,请参考[文本检测模型训练教程](../ppocr/model_train/detection.md),按照训练数据格式准备数据,并完成该场景下垂类文本检测模型的微调过程。
+
+### 4.2 文本识别
+
+本文重点关注发票的关键信息抽取模型训练与预测过程,因此在关键信息抽取过程中,直接使用提供的文本检测与识别标注信息进行测试,如果你希望自定义该场景的文本检测模型,完成端到端的关键信息抽取部分,请参考[文本识别模型训练教程](../ppocr/model_train/recognition.md),按照训练数据格式准备数据,并完成该场景下垂类文本识别模型的微调过程。
+
+### 4.3 语义实体识别 (Semantic Entity Recognition)
+
+语义实体识别指的是给定一段文本行,确定其类别(如`姓名`、`住址`等类别)。PaddleOCR中提供了基于VI-LayoutXLM的多模态语义实体识别方法,融合文本、位置与版面信息,相比LayoutXLM多模态模型,去除了其中的视觉骨干网络特征提取部分,引入符合阅读顺序的文本行排序方法,同时使用UDML联合互蒸馏方法进行训练,最终在精度与速度方面均超越LayoutXLM。更多关于VI-LayoutXLM的算法介绍与精度指标,请参考:[VI-LayoutXLM算法介绍](../algorithm/kie/algorithm_kie_layoutxlm.md)。
+
+#### 4.3.1 准备数据
+
+发票场景为例,我们首先需要标注出其中的关键字段,我们将其标注为`问题-答案`的key-value pair,如下,编号No为12270830,则`No`字段标注为question,`12270830`字段标注为answer。如下图所示。
+
+![](./images/185381131-76b6e260-04fe-46d9-baca-6bdd7fe0d0ce.jpg)
+
+**注意:**
+
+* 如果文本检测模型数据标注过程中,没有标注 **非关键信息内容** 的检测框,那么在标注关键信息抽取任务的时候,也不需要标注该部分,如上图所示;如果标注的过程,如果同时标注了**非关键信息内容** 的检测框,那么我们需要将该部分的label记为other。
+* 标注过程中,需要以文本行为单位进行标注,无需标注单个字符的位置信息。
+
+已经处理好的增值税发票数据集从这里下载:[增值税发票数据集下载链接](https://aistudio.baidu.com/aistudio/datasetdetail/165561)。
+
+下载好发票数据集,并解压在train_data目录下,目录结构如下所示。
+
+```text linenums="1"
+train_data
+ |--zzsfp
+ |---class_list.txt
+ |---imgs/
+ |---train.json
+ |---val.json
+```
+
+其中`class_list.txt`是包含`other`, `question`, `answer`,3个种类的的类别列表(不区分大小写),`imgs`目录底下,`train.json`与`val.json`分别表示训练与评估集合的标注文件。训练集中包含30张图片,验证集中包含8张图片。部分标注如下所示。
+
+```python linenums="1"
+b33.jpg [{"transcription": "No", "label": "question", "points": [[2882, 472], [3026, 472], [3026, 588], [2882, 588]], }, {"transcription": "12269563", "label": "answer", "points": [[3066, 448], [3598, 448], [3598, 576], [3066, 576]], ]}]
+```
+
+相比于OCR检测的标注,仅多了`label`字段。
+
+#### 4.3.2 开始训练
+
+VI-LayoutXLM的配置为[ser_vi_layoutxlm_xfund_zh_udml.yml](../configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml),需要修改数据、类别数目以及配置文件。
+
+```yaml linenums="1" linenums="1"
+Architecture:
+ model_type: &model_type "kie"
+ name: DistillationModel
+ algorithm: Distillation
+ Models:
+ Teacher:
+ pretrained:
+ freeze_params: false
+ return_all_feats: true
+ model_type: *model_type
+ algorithm: &algorithm "LayoutXLM"
+ Transform:
+ Backbone:
+ name: LayoutXLMForSer
+ pretrained: True
+ # one of base or vi
+ mode: vi
+ checkpoints:
+ # 定义类别数目
+ num_classes: &num_classes 5
+ ...
+
+PostProcess:
+ name: DistillationSerPostProcess
+ model_name: ["Student", "Teacher"]
+ key: backbone_out
+ # 定义类别文件
+ class_path: &class_path train_data/zzsfp/class_list.txt
+
+Train:
+ dataset:
+ name: SimpleDataSet
+ # 定义训练数据目录与标注文件
+ data_dir: train_data/zzsfp/imgs
+ label_file_list:
+ - train_data/zzsfp/train.json
+ ...
+
+Eval:
+ dataset:
+ # 定义评估数据目录与标注文件
+ name: SimpleDataSet
+ data_dir: train_data/zzsfp/imgs
+ label_file_list:
+ - train_data/zzsfp/val.json
+ ...
+```
+
+LayoutXLM与VI-LayoutXLM针对该场景的训练结果如下所示。
+
+| 模型 | 迭代轮数 | Hmean |
+| :---: | :---: | :---: |
+| LayoutXLM | 50 | 100.00% |
+| VI-LayoutXLM | 50 | 100.00% |
+
+可以看出,由于当前数据量较少,场景比较简单,因此2个模型的Hmean均达到了100%。
+
+#### 4.3.3 模型评估
+
+模型训练过程中,使用的是知识蒸馏的策略,最终保留了学生模型的参数,在评估时,我们需要针对学生模型的配置文件进行修改: [ser_vi_layoutxlm_xfund_zh.yml](../configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml),修改内容与训练配置相同,包括**类别数、类别映射文件、数据目录**。
+
+修改完成后,执行下面的命令完成评估过程。
+
+```bash linenums="1"
+# 注意:需要根据你的配置文件地址与保存的模型地址,对评估命令进行修改
+python3 tools/eval.py -c ./fapiao/ser_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/ser_vi_layoutxlm_fapiao_udml/best_accuracy
+```
+
+输出结果如下所示:
+
+```bash linenums="1"
+[2022/08/18 08:49:58] ppocr INFO: metric eval ***************
+[2022/08/18 08:49:58] ppocr INFO: precision:1.0
+[2022/08/18 08:49:58] ppocr INFO: recall:1.0
+[2022/08/18 08:49:58] ppocr INFO: hmean:1.0
+[2022/08/18 08:49:58] ppocr INFO: fps:1.9740402401574881
+```
+
+#### 4.3.4 模型预测
+
+使用下面的命令进行预测:
+
+```bash linenums="1"
+python3 tools/infer_kie_token_ser.py -c fapiao/ser_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/ser_vi_layoutxlm_fapiao_udml/best_accuracy Global.infer_img=./train_data/XFUND/zh_val/val.json Global.infer_mode=False
+```
+
+预测结果会保存在配置文件中的`Global.save_res_path`目录中。
+
+部分预测结果如下所示。
+
+![](./images/185310636-6ce02f7c-790d-479f-b163-ea97a5a04808-20240704190212828.jpg)
+
+* 注意:在预测时,使用的文本检测与识别结果为标注的结果,直接从json文件里面进行读取。
+
+如果希望使用OCR引擎结果得到的结果进行推理,则可以使用下面的命令进行推理。
+
+```bash linenums="1"
+python3 tools/infer_kie_token_ser.py -c fapiao/ser_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/ser_vi_layoutxlm_fapiao_udml/best_accuracy Global.infer_img=./train_data/zzsfp/imgs/b25.jpg Global.infer_mode=True
+```
+
+结果如下所示:
+
+![](./images/185384321-61153faa-e407-45c4-8e7c-a39540248189.jpg)
+
+它会使用PP-OCRv3的文本检测与识别模型进行获取文本位置与内容信息。
+
+可以看出,由于训练的过程中,没有标注额外的字段为other类别,所以大多数检测出来的字段被预测为question或者answer。
+
+如果希望构建基于你在垂类场景训练得到的OCR检测与识别模型,可以使用下面的方法传入检测与识别的inference 模型路径,即可完成OCR文本检测与识别以及SER的串联过程。
+
+```bash linenums="1"
+python3 tools/infer_kie_token_ser.py -c fapiao/ser_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/ser_vi_layoutxlm_fapiao_udml/best_accuracy Global.infer_img=./train_data/zzsfp/imgs/b25.jpg Global.infer_mode=True Global.kie_rec_model_dir="your_rec_model" Global.kie_det_model_dir="your_det_model"
+```
+
+### 4.4 关系抽取(Relation Extraction)
+
+使用SER模型,可以获取图像中所有的question与answer的字段,继续这些字段的类别,我们需要进一步获取question与answer之间的连接,因此需要进一步训练关系抽取模型,解决该问题。本文也基于VI-LayoutXLM多模态预训练模型,进行下游RE任务的模型训练。
+
+#### 4.4.1 准备数据
+
+以发票场景为例,相比于SER任务,RE中还需要标记每个文本行的id信息以及链接关系linking,如下所示。
+
+![](./images/185387870-dc9125a0-9ceb-4036-abf3-184b6e65dc7d.jpg)
+
+![](./images/185387870-dc9125a0-9ceb-4036-abf3-184b6e65dc7d-20240704190305748.jpg)
+
+标注文件的部分内容如下所示。
+
+```python linenums="1"
+b33.jpg [{"transcription": "No", "label": "question", "points": [[2882, 472], [3026, 472], [3026, 588], [2882, 588]], "id": 0, "linking": [[0, 1]]}, {"transcription": "12269563", "label": "answer", "points": [[3066, 448], [3598, 448], [3598, 576], [3066, 576]], "id": 1, "linking": [[0, 1]]}]
+```
+
+相比与SER的标注,多了`id`与`linking`的信息,分别表示唯一标识以及连接关系。
+
+已经处理好的增值税发票数据集从这里下载:[增值税发票数据集下载链接](https://aistudio.baidu.com/aistudio/datasetdetail/165561)。
+
+#### 4.4.2 开始训练
+
+基于VI-LayoutXLM的RE任务配置为[re_vi_layoutxlm_xfund_zh_udml.yml](../configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml),需要修改**数据路径、类别列表文件**。
+
+```yaml linenums="1" linenums="1"
+Train:
+ dataset:
+ name: SimpleDataSet
+ # 定义训练数据目录与标注文件
+ data_dir: train_data/zzsfp/imgs
+ label_file_list:
+ - train_data/zzsfp/train.json
+ transforms:
+ - DecodeImage: # load image
+ img_mode: RGB
+ channel_first: False
+ - VQATokenLabelEncode: # Class handling label
+ contains_re: True
+ algorithm: *algorithm
+ class_path: &class_path train_data/zzsfp/class_list.txt
+ ...
+
+Eval:
+ dataset:
+ # 定义评估数据目录与标注文件
+ name: SimpleDataSet
+ data_dir: train_data/zzsfp/imgs
+ label_file_list:
+ - train_data/zzsfp/val.json
+ ...
+
+```
+
+LayoutXLM与VI-LayoutXLM针对该场景的训练结果如下所示。
+
+| 模型 | 迭代轮数 | Hmean |
+| :---: | :---: | :---: |
+| LayoutXLM | 50 | 98.00% |
+| VI-LayoutXLM | 50 | 99.30% |
+
+可以看出,对于VI-LayoutXLM相比LayoutXLM的Hmean高了1.3%。
+
+如需获取已训练模型,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+* PaddleX官方交流频道:
+
+#### 4.4.3 模型评估
+
+模型训练过程中,使用的是知识蒸馏的策略,最终保留了学生模型的参数,在评估时,我们需要针对学生模型的配置文件进行修改: [re_vi_layoutxlm_xfund_zh.yml](../configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml),修改内容与训练配置相同,包括**类别映射文件、数据目录**。
+
+修改完成后,执行下面的命令完成评估过程。
+
+```bash linenums="1"
+# 注意:需要根据你的配置文件地址与保存的模型地址,对评估命令进行修改
+python3 tools/eval.py -c ./fapiao/re_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/re_vi_layoutxlm_fapiao_udml/best_accuracy
+```
+
+输出结果如下所示:
+
+```python linenums="1"
+[2022/08/18 12:17:14] ppocr INFO: metric eval ***************
+[2022/08/18 12:17:14] ppocr INFO: precision:1.0
+[2022/08/18 12:17:14] ppocr INFO: recall:0.9873417721518988
+[2022/08/18 12:17:14] ppocr INFO: hmean:0.9936305732484078
+[2022/08/18 12:17:14] ppocr INFO: fps:2.765963539771157
+```
+
+#### 4.4.4 模型预测
+
+使用下面的命令进行预测:
+
+```bash linenums="1"
+# -c 后面的是RE任务的配置文件
+# -o 后面的字段是RE任务的配置
+# -c_ser 后面的是SER任务的配置文件
+# -c_ser 后面的字段是SER任务的配置
+python3 tools/infer_kie_token_ser_re.py -c fapiao/re_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/re_vi_layoutxlm_fapiao_trained/best_accuracy Global.infer_img=./train_data/zzsfp/val.json Global.infer_mode=False -c_ser fapiao/ser_vi_layoutxlm.yml -o_ser Architecture.Backbone.checkpoints=fapiao/models/ser_vi_layoutxlm_fapiao_trained/best_accuracy
+```
+
+预测结果会保存在配置文件中的`Global.save_res_path`目录中。
+
+部分预测结果如下所示。
+
+![](./images/185393805-c67ff571-cf7e-4217-a4b0-8b396c4f22bb-20240704190316813.jpg)
+
+* 注意:在预测时,使用的文本检测与识别结果为标注的结果,直接从json文件里面进行读取。
+
+如果希望使用OCR引擎结果得到的结果进行推理,则可以使用下面的命令进行推理。
+
+```bash linenums="1"
+python3 tools/infer_kie_token_ser_re.py -c fapiao/re_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/re_vi_layoutxlm_fapiao_udml/best_accuracy Global.infer_img=./train_data/zzsfp/val.json Global.infer_mode=True -c_ser fapiao/ser_vi_layoutxlm.yml -o_ser Architecture.Backbone.checkpoints=fapiao/models/ser_vi_layoutxlm_fapiao_udml/best_accuracy
+```
+
+如果希望构建基于你在垂类场景训练得到的OCR检测与识别模型,可以使用下面的方法传入,即可完成SER + RE的串联过程。
+
+```bash linenums="1"
+python3 tools/infer_kie_token_ser_re.py -c fapiao/re_vi_layoutxlm.yml -o Architecture.Backbone.checkpoints=fapiao/models/re_vi_layoutxlm_fapiao_udml/best_accuracy Global.infer_img=./train_data/zzsfp/val.json Global.infer_mode=True -c_ser fapiao/ser_vi_layoutxlm.yml -o_ser Architecture.Backbone.checkpoints=fapiao/models/ser_vi_layoutxlm_fapiao_udml/best_accuracy Global.kie_rec_model_dir="your_rec_model" Global.kie_det_model_dir="your_det_model"
+```
diff --git "a/docs/applications/\345\244\232\346\250\241\346\200\201\350\241\250\345\215\225\350\257\206\345\210\253.md" "b/docs/applications/\345\244\232\346\250\241\346\200\201\350\241\250\345\215\225\350\257\206\345\210\253.md"
new file mode 100644
index 0000000000..15b899e730
--- /dev/null
+++ "b/docs/applications/\345\244\232\346\250\241\346\200\201\350\241\250\345\215\225\350\257\206\345\210\253.md"
@@ -0,0 +1,797 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# 多模态表单识别
+
+## 1 项目说明
+
+计算机视觉在金融领域的应用覆盖文字识别、图像识别、视频识别等,其中文字识别(OCR)是金融领域中的核心AI能力,其应用覆盖客户服务、风险防控、运营管理等各项业务,针对的对象包括通用卡证票据识别(银行卡、身份证、营业执照等)、通用文本表格识别(印刷体、多语言、手写体等)以及一些金融特色票据凭证。通过因此如果能够在结构化信息提取时同时利用文字、页面布局等信息,便可增强不同版式下的泛化性。
+
+表单识别旨在识别各种具有表格性质的证件、房产证、营业执照、个人信息表、发票等关键键值对(如姓名-张三),其广泛应用于银行、证券、公司财务等领域,具有很高的商业价值。本次范例项目开源了全流程表单识别方案,能够在多个场景快速实现迁移能力。表单识别通常存在以下难点:
+
+- 人工摘录工作效率低;
+- 国内常见表单版式多;
+- 传统技术方案泛化效果不满足。
+
+表单识别包含两大阶段:OCR阶段和文档视觉问答阶段。
+
+其中,OCR阶段选取了PaddleOCR的PP-OCRv2模型,主要由文本检测和文本识别两个模块组成。DOC-VQA文档视觉问答阶段基于PaddleNLP自然语言处理算法库实现的LayoutXLM模型,支持基于多模态方法的语义实体识别(Semantic Entity Recognition, SER)以及关系抽取(Relation Extraction, RE)任务。本案例流程如 **图1** 所示:
+
+![](./images/9bd844b970f94e5ba0bc0c5799bd819ea9b1861bb306471fabc2d628864d418e.jpeg)
+
+注:欢迎再AIStudio领取免费算力体验线上实训,项目链接: [多模态表单识别](https://aistudio.baidu.com/aistudio/projectdetail/3884375?contributionType=1)
+
+## 2 安装说明
+
+下载PaddleOCR源码,上述AIStudio项目中已经帮大家打包好的PaddleOCR(已经修改好配置文件),无需下载解压即可,只需安装依赖环境~
+
+```bash linenums="1"
+unzip -q PaddleOCR.zip
+```
+
+```bash linenums="1"
+# 如仍需安装or安装更新,可以执行以下步骤
+# git clone https://github.com/PaddlePaddle/PaddleOCR.git -b dygraph
+# git clone https://gitee.com/PaddlePaddle/PaddleOCR
+```
+
+```bash linenums="1"
+# 安装依赖包
+pip install -U pip
+pip install -r /home/aistudio/PaddleOCR/requirements.txt
+pip install paddleocr
+
+pip install yacs gnureadline paddlenlp==2.2.1
+pip install xlsxwriter
+```
+
+## 3 数据准备
+
+这里使用[XFUN数据集](https://github.com/doc-analysis/XFUND)做为实验数据集。 XFUN数据集是微软提出的一个用于KIE任务的多语言数据集,共包含七个数据集,每个数据集包含149张训练集和50张验证集
+
+分别为:ZH(中文)、JA(日语)、ES(西班牙)、FR(法语)、IT(意大利)、DE(德语)、PT(葡萄牙)
+
+本次实验选取中文数据集作为我们的演示数据集。法语数据集作为实践课程的数据集,数据集样例图如 **图2** 所示。
+
+![](./images/0f84137778cd4ab6899c64109d452290e9c678ccf01744978bc9c0647adbba45.jpg)
+
+### 3.1 下载处理好的数据集
+
+处理好的XFUND中文数据集下载地址:[https://paddleocr.bj.bcebos.com/dataset/XFUND.tar](https://paddleocr.bj.bcebos.com/dataset/XFUND.tar) ,可以运行如下指令完成中文数据集下载和解压。
+
+![](./images/31e3dbee31d441d2a36d45b5af660e832dfa2f437f4d49a1914312a15b6a29a7.jpeg)
+
+```bash linenums="1"
+wget https://paddleocr.bj.bcebos.com/dataset/XFUND.tar
+tar -xf XFUND.tar
+
+# XFUN其他数据集使用下面的代码进行转换
+# 代码链接:https://github.com/PaddlePaddle/PaddleOCR/blob/release%2F2.4/ppstructure/vqa/helper/trans_xfun_data.py
+# %cd PaddleOCR
+# python3 ppstructure/vqa/tools/trans_xfun_data.py --ori_gt_path=path/to/json_path --output_path=path/to/save_path
+# %cd ../
+```
+
+运行上述指令后在 /home/aistudio/PaddleOCR/ppstructure/vqa/XFUND 目录下有2个文件夹,目录结构如下所示:
+
+```bash linenums="1"
+/home/aistudio/PaddleOCR/ppstructure/vqa/XFUND
+ └─ zh_train/ 训练集
+ ├── image/ 图片存放文件夹
+ ├── xfun_normalize_train.json 标注信息
+ └─ zh_val/ 验证集
+ ├── image/ 图片存放文件夹
+ ├── xfun_normalize_val.json 标注信息
+
+```
+
+该数据集的标注格式为
+
+```bash linenums="1"
+{
+ "height": 3508, # 图像高度
+ "width": 2480, # 图像宽度
+ "ocr_info": [
+ {
+ "text": "邮政地址:", # 单个文本内容
+ "label": "question", # 文本所属类别
+ "bbox": [261, 802, 483, 859], # 单个文本框
+ "id": 54, # 文本索引
+ "linking": [[54, 60]], # 当前文本和其他文本的关系 [question, answer]
+ "words": []
+ },
+ {
+ "text": "湖南省怀化市市辖区",
+ "label": "answer",
+ "bbox": [487, 810, 862, 859],
+ "id": 60,
+ "linking": [[54, 60]],
+ "words": []
+ }
+ ]
+}
+```
+
+### 3.2 转换为PaddleOCR检测和识别格式
+
+使用XFUND训练PaddleOCR检测和识别模型,需要将数据集格式改为训练需求的格式。
+
+![](./images/9a709f19e7174725a8cfb09fd922ade74f8e9eb73ae1438596cbb2facef9c24a.jpeg)
+
+**文本检测** 标注文件格式如下,中间用'\t'分隔:
+
+" 图像文件名 json.dumps编码的图像标注信息"
+ch4_test_images/img_61.jpg [{"transcription": "MASA", "points": [[310, 104], [416, 141], [418, 216], [312, 179]]}, {...}]
+
+json.dumps编码前的图像标注信息是包含多个字典的list,字典中的 `points` 表示文本框的四个点的坐标(x, y),从左上角的点开始顺时针排列。 `transcription` 表示当前文本框的文字,***当其内容为“###”时,表示该文本框无效,在训练时会跳过。***
+
+**文本识别** 标注文件的格式如下, txt文件中默认请将图片路径和图片标签用'\t'分割,如用其他方式分割将造成训练报错。
+
+```text linenums="1"
+" 图像文件名 图像标注信息 "
+
+train_data/rec/train/word_001.jpg 简单可依赖
+train_data/rec/train/word_002.jpg 用科技让复杂的世界更简单
+...
+```
+
+```bash linenums="1"
+unzip -q /home/aistudio/data/data140302/XFUND_ori.zip -d /home/aistudio/data/data140302/
+```
+
+已经提供转换脚本,执行如下代码即可转换成功:
+
+```bash linenums="1"
+%cd /home/aistudio/
+python trans_xfund_data.py
+```
+
+## 4 OCR
+
+选用飞桨OCR开发套件[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)中的PP-OCRv2模型进行文本检测和识别。PP-OCRv2在PP-OCR的基础上,进一步在5个方面重点优化,检测模型采用CML协同互学习知识蒸馏策略和CopyPaste数据增广策略;识别模型采用LCNet轻量级骨干网络、UDML 改进知识蒸馏策略和[Enhanced CTC loss](../ppocr/blog/enhanced_ctc_loss.md)损失函数改进,进一步在推理速度和预测效果上取得明显提升。更多细节请参考PP-OCRv2[技术报告](https://arxiv.org/abs/2109.03144)。
+
+### 4.1 文本检测
+
+我们使用2种方案进行训练、评估:
+
+- **PP-OCRv2中英文超轻量检测预训练模型**
+- **XFUND数据集+fine-tune**
+
+#### 4.1.1 方案1:预训练模型
+
+##### 1)下载预训练模型
+
+![](./images/2aff41ee8fce4e9bac8295cc00720217bde2aeee7ee7473689848bed0b6fde05.jpeg)
+
+PaddleOCR已经提供了PP-OCR系列模型,部分模型展示如下表所示:
+
+| 模型简介 | 模型名称 | 推荐场景 | 检测模型 | 方向分类器 | 识别模型 |
+| ------------------------------------- | ----------------------- | --------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 中英文超轻量PP-OCRv2模型(13.0M) | ch_PP-OCRv2_xx | 移动端&服务器端 | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) / [预训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_train.tar) |
+| 中英文超轻量PP-OCR mobile模型(9.4M) | ch_ppocr_mobile_v2.0_xx | 移动端&服务器端 | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar) / [预训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) / [预训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar) / [预训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_pre.tar) |
+| 中英文通用PP-OCR server模型(143.4M) | ch_ppocr_server_v2.0_xx | 服务器端 | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_infer.tar) / [预训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) / [预训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_infer.tar) / [预训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_pre.tar) |
+
+更多模型下载(包括多语言),可以参考[PP-OCR 系列模型下载](./doc/doc_ch/models_list.md)
+
+这里我们使用PP-OCRv2中英文超轻量检测模型,下载并解压预训练模型:
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/pretrain/
+wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar
+tar -xf ch_PP-OCRv2_det_distill_train.tar && rm -rf ch_PP-OCRv2_det_distill_train.tar
+% cd ..
+```
+
+##### 2)模型评估
+
+![](./images/75b0e977dfb74a83851f8828460759f337b1b7a0c33c47a08a30f3570e1e2e74.jpeg)
+
+接着使用下载的超轻量检测模型在XFUND验证集上进行评估,由于蒸馏需要包含多个网络,甚至多个Student网络,在计算指标的时候只需要计算一个Student网络的指标即可,key字段设置为Student则表示只计算Student网络的精度。
+
+```yaml linenums="1"
+Metric:
+ name: DistillationMetric
+ base_metric_name: DetMetric
+ main_indicator: hmean
+ key: "Student"
+```
+
+首先修改配置文件`configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml`中的以下字段:
+
+```yaml linenums="1"
+Eval.dataset.data_dir:指向验证集图片存放目录
+Eval.dataset.label_file_list:指向验证集标注文件
+```
+
+然后在XFUND验证集上进行评估,具体代码如下:
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR
+python tools/eval.py \
+ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml \
+ -o Global.checkpoints="./pretrain_models/ch_PP-OCRv2_det_distill_train/best_accuracy"
+```
+
+使用预训练模型进行评估,指标如下所示:
+
+| 方案 | hmeans |
+| -------- | -------- |
+| PP-OCRv2中英文超轻量检测预训练模型 | 77.26% |
+
+使用文本检测预训练模型在XFUND验证集上评估,达到77%左右,充分说明ppocr提供的预训练模型具有泛化能力。
+
+#### 4.1.2 方案2:XFUND数据集+fine-tune
+
+PaddleOCR提供的蒸馏预训练模型包含了多个模型的参数,我们提取Student模型的参数,在XFUND数据集上进行finetune,可以参考如下代码:
+
+```python linenums="1"
+import paddle
+# 加载预训练模型
+all_params = paddle.load("pretrain/ch_PP-OCRv2_det_distill_train/best_accuracy.pdparams")
+# 查看权重参数的keys
+# print(all_params.keys())
+# 学生模型的权重提取
+s_params = {key[len("student_model."):]: all_params[key] for key in all_params if "student_model." in key}
+# 查看学生模型权重参数的keys
+print(s_params.keys())
+# 保存
+paddle.save(s_params, "pretrain/ch_PP-OCRv2_det_distill_train/student.pdparams")
+```
+
+##### 1)模型训练
+
+![](./images/560c44b8dd604da7987bd25da0a882156ffcfb7f6bcb44108fe9bde77512e572.jpeg)
+
+修改配置文件`configs/det/ch_PP-OCRv2_det_student.yml`中的以下字段:
+
+```yaml linenums="1"
+Global.pretrained_model:指向预训练模型路径
+Train.dataset.data_dir:指向训练集图片存放目录
+Train.dataset.label_file_list:指向训练集标注文件
+Eval.dataset.data_dir:指向验证集图片存放目录
+Eval.dataset.label_file_list:指向验证集标注文件
+Optimizer.lr.learning_rate:调整学习率,本实验设置为0.005
+Train.dataset.transforms.EastRandomCropData.size:训练尺寸改为[1600, 1600]
+Eval.dataset.transforms.DetResizeForTest:评估尺寸,添加如下参数
+ limit_side_len: 1600
+ limit_type: 'min'
+
+```
+
+执行下面命令启动训练:
+
+```bash linenums="1"
+CUDA_VISIBLE_DEVICES=0 python tools/train.py \
+ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml
+```
+
+##### 2)模型评估
+
+![](./images/5a75137c5f924dfeb6956b5818812298cc3dc7992ac84954b4175be9adf83c77.jpeg)
+
+使用训练好的模型进行评估,更新模型路径`Global.checkpoints`。
+
+如需获取已训练模型,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+- PaddleX官方交流频道:
+
+将下载或训练完成的模型放置在对应目录下即可完成模型评估
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/
+python tools/eval.py \
+ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml \
+ -o Global.checkpoints="pretrain/ch_db_mv3-student1600-finetune/best_accuracy"
+```
+
+同时我们提供了未finetuen的模型,配置文件参数(`pretrained_model`设置为空,`learning_rate` 设置为0.001)
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/
+python tools/eval.py \
+ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml \
+ -o Global.checkpoints="pretrain/ch_db_mv3-student1600/best_accuracy"
+```
+
+使用训练好的模型进行评估,指标如下所示:
+
+| 方案 | hmeans |
+| -------- | -------- |
+| XFUND数据集 | 79.27% |
+| XFUND数据集+fine-tune | 85.24% |
+
+对比仅使用XFUND数据集训练的模型,使用XFUND数据集+finetune训练,在验证集上评估达到85%左右,说明 finetune会提升垂类场景效果。
+
+##### 3)导出模型
+
+![](./images/07c3b060c54e4b00be7de8d41a8a4696ff53835343cc4981aab0555183306e79.jpeg)
+
+在模型训练过程中保存的模型文件是包含前向预测和反向传播的过程,在实际的工业部署则不需要反向传播,因此需要将模型进行导成部署需要的模型格式。 执行下面命令,即可导出模型。
+
+```bash linenums="1"
+# 加载配置文件`ch_PP-OCRv2_det_student.yml`,从`pretrain/ch_db_mv3-student1600-finetune`目录下加载`best_accuracy`模型
+# inference模型保存在`./output/det_db_inference`目录下
+%cd /home/aistudio/PaddleOCR/
+python tools/export_model.py \
+ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml \
+ -o Global.pretrained_model="pretrain/ch_db_mv3-student1600-finetune/best_accuracy" \
+ Global.save_inference_dir="./output/det_db_inference/"
+```
+
+转换成功后,在目录下有三个文件:
+
+```text linenums="1"
+/inference/rec_crnn/
+ ├── inference.pdiparams # 识别inference模型的参数文件
+ ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略
+ └── inference.pdmodel # 识别inference模型的program文件
+```
+
+##### 4)模型预测
+
+![](./images/0d582de9aa46474791e08654f84a614a6510e98bfe5f4ad3a26501cbf49ec151.jpeg)
+
+加载上面导出的模型,执行如下命令对验证集或测试集图片进行预测:
+
+```yaml linenums="1"
+det_model_dir:预测模型
+image_dir:测试图片路径
+use_gpu:是否使用GPU
+```
+
+检测可视化结果保存在`/home/aistudio/inference_results/`目录下,查看检测效果。
+
+```bash linenums="1"
+%pwd
+!python tools/infer/predict_det.py \
+ --det_algorithm="DB" \
+ --det_model_dir="./output/det_db_inference/" \
+ --image_dir="./doc/vqa/input/zh_val_21.jpg" \
+ --use_gpu=True
+```
+
+总结,我们分别使用PP-OCRv2中英文超轻量检测预训练模型、XFUND数据集+finetune2种方案进行评估、训练等,指标对比如下:
+
+| 方案 | hmeans | 结果分析 |
+| -------- | -------- | -------- |
+| PP-OCRv2中英文超轻量检测预训练模型 | 77.26% | ppocr提供的预训练模型有泛化能力 |
+| XFUND数据集 | 79.27% | |
+| XFUND数据集+finetune | 85.24% | finetune会提升垂类场景效果 |
+
+### 4.2 文本识别
+
+我们分别使用如下3种方案进行训练、评估:
+
+- PP-OCRv2中英文超轻量识别预训练模型
+- XFUND数据集+fine-tune
+- XFUND数据集+fine-tune+真实通用识别数据
+
+#### 4.2.1 方案1:预训练模型
+
+##### 1)下载预训练模型
+
+![](./images/b7230e9964074181837e1132029f9da8178bf564ac5c43a9a93a30e975c0d8b4.jpeg)
+
+我们使用PP-OCRv2中英文超轻量文本识别模型,下载并解压预训练模型:
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/pretrain/
+wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_train.tar
+tar -xf ch_PP-OCRv2_rec_train.tar && rm -rf ch_PP-OCRv2_rec_train.tar
+% cd ..
+```
+
+##### 2)模型评估
+
+![](./images/166ce56d634c4c7589fe68fbc6e7ae663305dcc82ba144c781507341ffae7fe8.jpeg)
+
+首先修改配置文件`configs/det/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml`中的以下字段:
+
+```bash linenums="1"
+Eval.dataset.data_dir:指向验证集图片存放目录
+Eval.dataset.label_file_list:指向验证集标注文件
+```
+
+我们使用下载的预训练模型进行评估:
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR
+CUDA_VISIBLE_DEVICES=0 python tools/eval.py \
+ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml \
+ -o Global.checkpoints=./pretrain/ch_PP-OCRv2_rec_train/best_accuracy
+```
+
+使用预训练模型进行评估,指标如下所示:
+
+| 方案 | acc |
+| -------- | -------- |
+| PP-OCRv2中英文超轻量识别预训练模型 | 67.48% |
+
+使用文本预训练模型在XFUND验证集上评估,acc达到67%左右,充分说明ppocr提供的预训练模型具有泛化能力。
+
+#### 4.2.2 方案2:XFUND数据集+finetune
+
+同检测模型,我们提取Student模型的参数,在XFUND数据集上进行finetune,可以参考如下代码:
+
+```python linenums="1"
+import paddle
+# 加载预训练模型
+all_params = paddle.load("pretrain/ch_PP-OCRv2_rec_train/best_accuracy.pdparams")
+# 查看权重参数的keys
+print(all_params.keys())
+# 学生模型的权重提取
+s_params = {key[len("Student."):]: all_params[key] for key in all_params if "Student." in key}
+# 查看学生模型权重参数的keys
+print(s_params.keys())
+# 保存
+paddle.save(s_params, "pretrain/ch_PP-OCRv2_rec_train/student.pdparams")
+```
+
+##### 1)模型训练
+
+![](./images/166ce56d634c4c7589fe68fbc6e7ae663305dcc82ba144c781507341ffae7fe8.jpeg)
+
+修改配置文件`configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml`中的以下字段:
+
+```yaml linenums="1"
+Global.pretrained_model:指向预训练模型路径
+Global.character_dict_path: 字典路径
+Optimizer.lr.values:学习率
+Train.dataset.data_dir:指向训练集图片存放目录
+Train.dataset.label_file_list:指向训练集标注文件
+Eval.dataset.data_dir:指向验证集图片存放目录
+Eval.dataset.label_file_list:指向验证集标注文件
+```
+
+执行如下命令启动训练:
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/
+CUDA_VISIBLE_DEVICES=0 python tools/train.py \
+ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml
+```
+
+##### 2)模型评估
+
+![](./images/c07c88f708ad43cc8cd615861626d0e8333c0e3d4dda49ac8cba1f8939fa8a94.jpeg)
+
+使用训练好的模型进行评估,更新模型路径`Global.checkpoints`,这里为大家提供训练好的模型`./pretrain/rec_mobile_pp-OCRv2-student-finetune/best_accuracy`
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/
+CUDA_VISIBLE_DEVICES=0 python tools/eval.py \
+ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml \
+ -o Global.checkpoints=./pretrain/rec_mobile_pp-OCRv2-student-finetune/best_accuracy
+```
+
+使用预训练模型进行评估,指标如下所示:
+
+| 方案 | acc |
+| -------- | -------- |
+| XFUND数据集+finetune | 72.33% |
+
+使用XFUND数据集+finetune训练,在验证集上评估达到72%左右,说明 finetune会提升垂类场景效果。
+
+#### 4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据
+
+接着我们在上述`XFUND数据集+finetune`实验的基础上,添加真实通用识别数据,进一步提升识别效果。首先准备真实通用识别数据,并上传到AIStudio:
+
+##### 1)模型训练
+
+![](./images/45f288ce8b2c45d8aa5407785b4b40f4876fc3da23744bd7a78060797fba0190.jpeg)
+
+在上述`XFUND数据集+finetune`实验中修改配置文件`configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml`的基础上,继续修改以下字段:
+
+```yaml linenums="1"
+Train.dataset.label_file_list:指向真实识别训练集图片存放目录
+Train.dataset.ratio_list:动态采样
+```
+
+执行如下命令启动训练:
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/
+CUDA_VISIBLE_DEVICES=0 python tools/train.py \
+ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml
+```
+
+##### 2)模型评估
+
+![](./images/965db9f758614c6f9be301286cd5918f21110603c8aa4a1dbf5371e3afeec782.jpeg)
+
+使用训练好的模型进行评估,更新模型路径`Global.checkpoints`。
+
+```bash linenums="1"
+CUDA_VISIBLE_DEVICES=0 python tools/eval.py \
+ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml \
+ -o Global.checkpoints=./pretrain/rec_mobile_pp-OCRv2-student-realdata/best_accuracy
+```
+
+使用预训练模型进行评估,指标如下所示:
+
+| 方案 | acc |
+| -------- | -------- |
+| XFUND数据集+fine-tune+真实通用识别数据 | 85.29% |
+
+使用XFUND数据集+finetune训练,在验证集上评估达到85%左右,说明真实通用识别数据对于性能提升很有帮助。
+
+##### 3)导出模型
+
+![](./images/3dc7f69fac174cde96b9d08b5e2353a1d88dc63e7be9410894c0783660b35b76.jpeg)
+
+导出模型只保留前向预测的过程:
+
+```bash linenums="1"
+!python tools/export_model.py \
+ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml \
+ -o Global.pretrained_model=pretrain/rec_mobile_pp-OCRv2-student-realdata/best_accuracy \
+ Global.save_inference_dir=./output/rec_crnn_inference/
+```
+
+##### 4)模型预测
+
+![](./images/60b95b4945954f81a080a8f308cee66f83146479cd1142b9b6b1290938fd1df8.jpeg)
+
+加载上面导出的模型,执行如下命令对验证集或测试集图片进行预测,检测可视化结果保存在`/home/aistudio/inference_results/`目录下,查看检测、识别效果。需要通过`--rec_char_dict_path`指定使用的字典路径
+
+```bash linenums="1"
+python tools/infer/predict_system.py \
+ --image_dir="./doc/vqa/input/zh_val_21.jpg" \
+ --det_model_dir="./output/det_db_inference/" \
+ --rec_model_dir="./output/rec_crnn_inference/" \
+ --rec_image_shape="3, 32, 320" \
+ --rec_char_dict_path="/home/aistudio/XFUND/word_dict.txt"
+```
+
+总结,我们分别使用PP-OCRv2中英文超轻量检测预训练模型、XFUND数据集+finetune2种方案进行评估、训练等,指标对比如下:
+
+| 方案 | acc | 结果分析 |
+| -------- | -------- | -------- |
+| PP-OCRv2中英文超轻量识别预训练模型 | 67.48% | ppocr提供的预训练模型具有泛化能力 |
+| XFUND数据集+fine-tune |72.33% | finetune会提升垂类场景效果 |
+| XFUND数据集+fine-tune+真实通用识别数据 | 85.29% | 真实通用识别数据对于性能提升很有帮助 |
+
+## 5 文档视觉问答(DOC-VQA)
+
+VQA指视觉问答,主要针对图像内容进行提问和回答,DOC-VQA是VQA任务中的一种,DOC-VQA主要针对文本图像的文字内容提出问题。
+
+PaddleOCR中DOC-VQA系列算法基于PaddleNLP自然语言处理算法库实现LayoutXLM论文,支持基于多模态方法的 **语义实体识别 (Semantic Entity Recognition, SER)** 以及 **关系抽取 (Relation Extraction, RE)** 任务。
+
+如果希望直接体验预测过程,可以下载我们提供的预训练模型,跳过训练过程,直接预测即可。
+
+```bash linenums="1"
+%cd pretrain
+#下载SER模型
+wget https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar && tar -xvf ser_LayoutXLM_xfun_zh.tar
+#下载RE模型
+wget https://paddleocr.bj.bcebos.com/pplayout/re_LayoutXLM_xfun_zh.tar && tar -xvf re_LayoutXLM_xfun_zh.tar
+%cd ../
+```
+
+### 5.1 SER
+
+SER: 语义实体识别 (Semantic Entity Recognition), 可以完成对图像中的文本识别与分类。
+
+![](./images/a3b25766f3074d2facdf88d4a60fc76612f51992fd124cf5bd846b213130665b-0097611.jpeg)
+
+**图19** 中不同颜色的框表示不同的类别,对于XFUND数据集,有QUESTION, ANSWER, HEADER 3种类别
+
+- 深紫色:HEADER
+- 浅紫色:QUESTION
+- 军绿色:ANSWER
+
+在OCR检测框的左上方也标出了对应的类别和OCR识别结果。
+
+#### 5.1.1 模型训练
+
+![](./images/2e45f297c9d44ca5b8718ae100a365f7348eaeed4cb8495b904f28a9c8075d8a.jpeg)
+
+启动训练之前,需要修改配置文件 `configs/vqa/ser/layoutxlm.yml` 以下四个字段:
+
+```yaml linenums="1"
+Train.dataset.data_dir:指向训练集图片存放目录
+Train.dataset.label_file_list:指向训练集标注文件
+Eval.dataset.data_dir:指指向验证集图片存放目录
+Eval.dataset.label_file_list:指向验证集标注文件
+```
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR/
+CUDA_VISIBLE_DEVICES=0 python tools/train.py -c configs/vqa/ser/layoutxlm.yml
+```
+
+最终会打印出`precision`, `recall`, `hmean`等指标。 在`./output/ser_layoutxlm/`文件夹中会保存训练日志,最优的模型和最新epoch的模型。
+
+#### 5.1.2 模型评估
+
+![](./images/5df160ac39ee4d9e92a937094bc53a737272f9f2abeb4ddfaebb48e8eccf1be2.jpeg)
+
+我们使用下载的预训练模型进行评估,如果使用自己训练好的模型进行评估,将待评估的模型所在文件夹路径赋值给 `Architecture.Backbone.checkpoints` 字段即可。
+
+```bash linenums="1"
+CUDA_VISIBLE_DEVICES=0 python tools/eval.py \
+ -c configs/vqa/ser/layoutxlm.yml \
+ -o Architecture.Backbone.checkpoints=pretrain/ser_LayoutXLM_xfun_zh/
+```
+
+最终会打印出`precision`, `recall`, `hmean`等指标,预训练模型评估指标如下:
+
+![](./images/2854aee557a74079a82dd5cd57e48bc2ce97974d5637477fb4deea137d0e312c.png)
+
+#### 5.1.3 模型预测
+
+![](./images/0f7d50a0fb924b408b93e1fbd6ca64148eed34a2e6724280acd3e113fef7dc48.jpeg)
+
+使用如下命令即可完成`OCR引擎 + SER`的串联预测, 以SER预训练模型为例:
+
+```bash linenums="1"
+CUDA_VISIBLE_DEVICES=0 python tools/infer_vqa_token_ser.py \
+ -c configs/vqa/ser/layoutxlm.yml \
+ -o Architecture.Backbone.checkpoints=pretrain/ser_LayoutXLM_xfun_zh/ \
+ Global.infer_img=doc/vqa/input/zh_val_42.jpg
+```
+
+最终会在`config.Global.save_res_path`字段所配置的目录下保存预测结果可视化图像以及预测结果文本文件,预测结果文本文件名为`infer_results.txt`。通过如下命令查看预测图片:
+
+```python linenums="1"
+import cv2
+from matplotlib import pyplot as plt
+# 在notebook中使用matplotlib.pyplot绘图时,需要添加该命令进行显示
+%matplotlib inline
+
+img = cv2.imread('output/ser/zh_val_42_ser.jpg')
+plt.figure(figsize=(48,24))
+plt.imshow(img)
+```
+
+### 5.2 RE
+
+基于 RE 任务,可以完成对图象中的文本内容的关系提取,如判断问题对(pair)。
+
+![](./images/4de19ca3e54343e88961e816cad28bbacdc807f40b9440be914d871b0a914570.jpeg)
+
+图中红色框表示问题,蓝色框表示答案,问题和答案之间使用绿色线连接。在OCR检测框的左上方也标出了对应的类别和OCR识别结果。
+
+#### 5.2.1 模型训练
+
+![](./images/268c707a62c54e93958d2b2ab29e0932953aad41819e44aaaaa05c8ad85c6491.jpeg)
+
+启动训练之前,需要修改配置文件`configs/vqa/re/layoutxlm.yml`中的以下四个字段
+
+```yaml linenums="1"
+Train.dataset.data_dir:指向训练集图片存放目录
+Train.dataset.label_file_list:指向训练集标注文件
+Eval.dataset.data_dir:指指向验证集图片存放目录
+Eval.dataset.label_file_list:指向验证集标注文件
+```
+
+```bash linenums="1"
+CUDA_VISIBLE_DEVICES=0 python3 tools/train.py -c configs/vqa/re/layoutxlm.yml
+```
+
+最终会打印出`precision`, `recall`, `hmean`等指标。 在`./output/re_layoutxlm/`文件夹中会保存训练日志,最优的模型和最新epoch的模型
+
+#### 5.2.2 模型评估
+
+![](./images/93c66a43a69e472899c1c6732408b7a42e99a43721e94e9ca3c0a64e080306e4.jpeg)
+
+我们使用下载的预训练模型进行评估,如果使用自己训练好的模型进行评估,将待评估的模型所在文件夹路径赋值给 `Architecture.Backbone.checkpoints` 字段即可。
+
+```bash linenums="1"
+CUDA_VISIBLE_DEVICES=0 python3 tools/eval.py \
+ -c configs/vqa/re/layoutxlm.yml \
+ -o Architecture.Backbone.checkpoints=pretrain/re_LayoutXLM_xfun_zh/
+```
+
+最终会打印出`precision`, `recall`, `hmean`等指标,预训练模型评估指标如下:
+
+![](./images/f99af54fb2d14691a73b1a748e0ca22618aeddfded0c4da58bbbb03edb8c2340.png)
+
+#### 5.2.3 模型预测
+
+![](./images/bab32d32bdec4339b9a3e5f911e4b41f77996f3faabc40bd8309b5b20cad31e4.jpeg)
+
+使用如下命令即可完成OCR引擎 + SER + RE的串联预测, 以预训练SER和RE模型为例,
+
+最终会在config.Global.save_res_path字段所配置的目录下保存预测结果可视化图像以及预测结果文本文件,预测结果文本文件名为infer_results.txt。
+
+```bash linenums="1"
+cd /home/aistudio/PaddleOCR
+CUDA_VISIBLE_DEVICES=0 python3 tools/infer_vqa_token_ser_re.py \
+ -c configs/vqa/re/layoutxlm.yml \
+ -o Architecture.Backbone.checkpoints=pretrain/re_LayoutXLM_xfun_zh/ \
+ Global.infer_img=test_imgs/ \
+ -c_ser configs/vqa/ser/layoutxlm.yml \
+ -o_ser Architecture.Backbone.checkpoints=pretrain/ser_LayoutXLM_xfun_zh/
+```
+
+最终会在config.Global.save_res_path字段所配置的目录下保存预测结果可视化图像以及预测结果文本文件,预测结果文本文件名为infer_results.txt, 每一行表示一张图片的结果,每张图片的结果如下所示,前面表示测试图片路径,后面为测试结果:key字段及对应的value字段。
+
+```bash linenums="1"
+test_imgs/t131.jpg {"政治面税": "群众", "性别": "男", "籍贯": "河北省邯郸市", "婚姻状况": "亏末婚口已婚口已娇", "通讯地址": "邯郸市阳光苑7号楼003", "民族": "汉族", "毕业院校": "河南工业大学", "户口性质": "口农村城镇", "户口地址": "河北省邯郸市", "联系电话": "13288888888", "健康状况": "健康", "姓名": "小六", "好高cm": "180", "出生年月": "1996年8月9日", "文化程度": "本科", "身份证号码": "458933777777777777"}
+```
+
+展示预测结果
+
+```python linenums="1"
+import cv2
+from matplotlib import pyplot as plt
+%matplotlib inline
+
+img = cv2.imread('./output/re/t131_ser.jpg')
+plt.figure(figsize=(48,24))
+plt.imshow(img)
+```
+
+## 6 导出Excel
+
+![](./images/ab93d3d90d77437a81c9534b2dd1d3e39ef81e8473054fd3aeff6e837ebfb827.jpeg)
+
+为了输出信息匹配对,我们修改`tools/infer_vqa_token_ser_re.py`文件中的`line 194-197`。
+
+```python linenums="1"
+ fout.write(img_path + "\t" + json.dumps(
+ {
+ "ser_result": result,
+ }, ensure_ascii=False) + "\n")
+
+```
+
+更改为
+
+```python linenums="1"
+result_key = {}
+for ocr_info_head, ocr_info_tail in result:
+ result_key[ocr_info_head['text']] = ocr_info_tail['text']
+
+fout.write(img_path + "\t" + json.dumps(
+ result_key, ensure_ascii=False) + "\n")
+```
+
+同时将输出结果导出到Excel中,效果如 图28 所示:
+![](./images/9f45d3eef75e4842a0828bb9e518c2438300264aec0646cc9addfce860a04196.png)
+
+```python linenums="1"
+import json
+import xlsxwriter as xw
+
+workbook = xw.Workbook('output/re/infer_results.xlsx')
+format1 = workbook.add_format({
+ 'align': 'center',
+ 'valign': 'vcenter',
+ 'text_wrap': True,
+})
+worksheet1 = workbook.add_worksheet('sheet1')
+worksheet1.activate()
+title = ['姓名', '性别', '民族', '文化程度', '身份证号码', '联系电话', '通讯地址']
+worksheet1.write_row('A1', title)
+i = 2
+
+with open('output/re/infer_results.txt', 'r', encoding='utf-8') as fin:
+ lines = fin.readlines()
+ for line in lines:
+ img_path, result = line.strip().split('\t')
+ result_key = json.loads(result)
+ # 写入Excel
+ row_data = [result_key['姓名'], result_key['性别'], result_key['民族'], result_key['文化程度'], result_key['身份证号码'],
+ result_key['联系电话'], result_key['通讯地址']]
+ row = 'A' + str(i)
+ worksheet1.write_row(row, row_data, format1)
+ i+=1
+workbook.close()
+```
+
+## 更多资源
+
+- 更多深度学习知识、产业案例、面试宝典等,请参考:[awesome-DeepLearning](https://github.com/paddlepaddle/awesome-DeepLearning)
+- 更多PaddleOCR使用教程,请参考:[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR/tree/dygraph)
+- 更多PaddleNLP使用教程,请参考:[PaddleNLP](https://github.com/PaddlePaddle/PaddleNLP)
+- 飞桨框架相关资料,请参考:[飞桨深度学习平台](https://www.paddlepaddle.org.cn/?fr=paddleEdu_aistudio)
+
+## 参考链接
+
+- LayoutXLM: Multimodal Pre-training for Multilingual Visually-rich Document Understanding,
+- microsoft/unilm/layoutxlm,
+- XFUND dataset,
diff --git "a/docs/applications/\345\277\253\351\200\237\346\236\204\345\273\272\345\215\241\350\257\201\347\261\273OCR.md" "b/docs/applications/\345\277\253\351\200\237\346\236\204\345\273\272\345\215\241\350\257\201\347\261\273OCR.md"
new file mode 100644
index 0000000000..df2890c977
--- /dev/null
+++ "b/docs/applications/\345\277\253\351\200\237\346\236\204\345\273\272\345\215\241\350\257\201\347\261\273OCR.md"
@@ -0,0 +1,696 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+
+# 快速构建卡证类OCR
+
+## 1. 金融行业卡证识别应用
+
+### 1.1 金融行业中的OCR相关技术
+
+《“十四五”数字经济发展规划》指出,2020年我国数字经济核心产业增加值占GDP比重达7.8%,随着数字经济迈向全面扩展,到2025年该比例将提升至10%。
+
+在过去数年的跨越发展与积累沉淀中,数字金融、金融科技已在对金融业的重塑与再造中充分印证了其自身价值。
+
+以智能为目标,提升金融数字化水平,实现业务流程自动化,降低人力成本。
+
+![](./images/8bb381f164c54ea9b4043cf66fc92ffdea8aaf851bab484fa6e19bd2f93f154f.jpeg)
+
+### 1.2 金融行业中的卡证识别场景介绍
+
+应用场景:身份证、银行卡、营业执照、驾驶证等。
+
+应用难点:由于数据的采集来源多样,以及实际采集数据各种噪声:反光、褶皱、模糊、倾斜等各种问题干扰。
+
+![](./images/981640e17d05487e961162f8576c9e11634ca157f79048d4bd9d3bc21722afe8-20240704185952731.jpeg)
+
+### 1.3 OCR落地挑战
+
+![](./images/a5973a8ddeff4bd7ac082f02dc4d0c79de21e721b41641cbb831f23c2cb8fce2.jpeg)
+
+## 2. 卡证识别技术解析
+
+![](./images/d7f96effc2434a3ca2d4144ff33c50282b830670c892487d8d7dec151921cce7.jpeg)
+
+### 2.1 卡证分类模型
+
+卡证分类:基于PPLCNet
+
+与其他轻量级模型相比在CPU环境下ImageNet数据集上的表现
+
+![](./images/cbda3390cb994f98a3c8a9ba88c90c348497763f6c9f4b4797f7d63d84da5f63.jpeg)
+
+![](./images/dedab7b7fd6543aa9e7f625132b24e3ba3f200e361fa468dac615f7814dfb98d.jpeg)
+
+模型来自模型库PaddleClas,它是一个图像识别和图像分类任务的工具集,助力使用者训练出更好的视觉模型和应用落地。
+
+### 2.2 卡证识别模型
+
+检测:DBNet 识别:SVRT
+
+![](./images/9a7a4e19edc24310b46620f2ee7430f918223b93d4f14a15a52973c096926bad.jpeg)
+
+PPOCRv3在文本检测、识别进行了一系列改进优化,在保证精度的同时提升预测效率
+
+![](./images/6afdbb77e8db4aef9b169e4e94c5d90a9764cfab4f2c4c04aa9afdf4f54d7680.jpeg)
+
+![](./images/c1a7d197847a4f168848c59b8e625d1d5e8066b778144395a8b9382bb85dc364.jpeg)
+
+## 3. OCR技术拆解
+
+### 3.1技术流程
+
+![](./images/89ba046177864d8783ced6cb31ba92a66ca2169856a44ee59ac2bb18e44a6c4b.jpeg)
+
+### 3.2 OCR技术拆解---卡证分类
+
+#### 卡证分类:数据、模型准备
+
+A 使用爬虫获取无标注数据,将相同类别的放在同一文件夹下,文件名从0开始命名。具体格式如下图所示。
+
+注:卡证类数据,建议每个类别数据量在500张以上
+
+![](./images/6f875b6e695e4fe5aedf427beb0d4ce8064ad7cc33c44faaad59d3eb9732639d.jpeg)
+
+B 一行命令生成标签文件
+
+```bash linenums="1"
+tree -r -i -f | grep -E "jpg|JPG|jpeg|JPEG|png|PNG|webp" | awk -F "/" '{print $0" "$2}' > train_list.txt
+```
+
+C [下载预训练模型](https://github.com/PaddlePaddle/PaddleClas/blob/release/2.4/docs/zh_CN/models/PP-LCNet.md)
+
+#### 卡证分类---修改配置文件
+
+配置文件主要修改三个部分:
+
+- 全局参数:预训练模型路径/训练轮次/图像尺寸
+- 模型结构:分类数
+- 数据处理:训练/评估数据路径
+
+![](./images/e0dc05039c7444c5ab1260ff550a408748df8d4cfe864223adf390e51058dbd5.jpeg)
+
+#### 卡证分类---训练
+
+指定配置文件启动训练:
+
+```bash linenums="1"
+!python /home/aistudio/work/PaddleClas/tools/train.py -c /home/aistudio/work/PaddleClas/ppcls/configs/PULC/text_image_orientation/PPLCNet_x1_0.yaml
+```
+
+![](./images/06af09bde845449ba0a676410f4daa1cdc3983ac95034bdbbafac3b7fd94042f.jpeg)
+
+注:日志中显示了训练结果和评估结果(训练时可以设置固定轮数评估一次)
+
+### 3.2 OCR技术拆解---卡证识别
+
+卡证识别(以身份证检测为例)
+存在的困难及问题:
+
+- 在自然场景下,由于各种拍摄设备以及光线、角度不同等影响导致实际得到的证件影像千差万别。
+
+- 如何快速提取需要的关键信息
+
+- 多行的文本信息,检测结果如何正确拼接
+
+ ![](./images/4f8f5533a2914e0a821f4a639677843c32ec1f08a1b1488d94c0b8bfb6e72d2d.jpeg)
+
+- OCR技术拆解---OCR工具库
+
+ PaddleOCR是一个丰富、领先且实用的OCR工具库,助力开发者训练出更好的模型并应用落地
+
+身份证识别:用现有的方法识别
+
+![](./images/12d402e6a06d482a88f979e0ebdfb39f4d3fc8b80517499689ec607ddb04fbf3.jpeg)
+
+#### 身份证识别:检测+分类
+>
+> 方法:基于现有的dbnet检测模型,加入分类方法。检测同时进行分类,从一定程度上优化识别流程
+
+![](./images/e1e798c87472477fa0bfca0da12bb0c180845a3e167a4761b0d26ff4330a5ccb.jpeg)
+
+![](./images/23a5a19c746441309864586e467f995ec8a551a3661640e493fc4d77520309cd.jpeg)
+
+#### 数据标注
+
+使用PaddleOCRLable进行快速标注
+
+![](./images/a73180425fa14f919ce52d9bf70246c3995acea1831843cca6c17d871b8f5d95.jpeg)
+
+- 修改PPOCRLabel.py,将下图中的kie参数设置为True
+
+![](./images/d445cf4d850e4063b9a7fc6a075c12204cf912ff23ec471fa2e268b661b3d693.jpeg)
+
+- 数据标注踩坑分享
+
+![](./images/89f42eccd600439fa9e28c97ccb663726e4e54ce3a854825b4c3b7d554ea21df.jpeg)
+
+ 注:两者只有标注有差别,训练参数数据集都相同
+
+## 4 . 项目实践
+
+AIStudio项目链接:[快速构建卡证类OCR](https://aistudio.baidu.com/aistudio/projectdetail/4459116)
+
+### 4.1 环境准备
+
+1)拉取[paddleocr](https://github.com/PaddlePaddle/PaddleOCR)项目,如果从github上拉取速度慢可以选择从gitee上获取。
+
+```bash linenums="1"
+!git clone https://github.com/PaddlePaddle/PaddleOCR.git -b release/2.6 /home/aistudio/work/
+```
+
+2)获取并解压预训练模型,如果要使用其他模型可以从模型库里自主选择合适模型。
+
+```bash linenums="1"
+!wget -P work/pre_trained/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_distill_train.tar
+!tar -vxf /home/aistudio/work/pre_trained/ch_PP-OCRv3_det_distill_train.tar -C /home/aistudio/work/pre_trained
+```
+
+3)安装必要依赖
+
+```bash linenums="1"
+!pip install -r /home/aistudio/work/requirements.txt
+```
+
+### 4.2 配置文件修改
+
+修改配置文件 `work/configs/det/detmv3db.yml`
+
+具体修改说明如下:
+
+![](./images/fcdf517af5a6466294d72db7450209378d8efd9b77764e329d3f2aff3579a20c.jpeg)
+
+注:在上述的配置文件的Global变量中需要添加以下两个参数:
+
+ - label_list 为标签表
+ - num_classes 为分类数
+上述两个参数根据实际的情况配置即可
+
+![](./images/0b056be24f374812b61abf43305774767ae122c8479242f98aa0799b7bfc81d4.jpeg)
+
+其中lable_list内容如下例所示,***建议第一个参数设置为 background,不要设置为实际要提取的关键信息种类***:
+
+![](./images/9fc78bbcdf754898b9b2c7f000ddf562afac786482ab4f2ab063e2242faa542a.jpeg)
+
+配置文件中的其他设置说明
+
+![](./images/c7fc5e631dd44bc8b714630f4e49d9155a831d9e56c64e2482ded87081d0db22.jpeg)
+
+![](./images/8d1022ac25d9474daa4fb236235bd58760039d58ad46414f841559d68e0d057f.jpeg)
+
+![](./images/ee927ad9ebd442bb96f163a7ebbf4bc95e6bedee97324a51887cf82de0851fd3.jpeg)
+
+### 4.3 代码修改
+
+#### 4.3.1 数据读取
+
+修改 PaddleOCR/ppocr/data/imaug/label_ops.py中的DetLabelEncode
+
+```python linenums="1"
+class DetLabelEncode(object):
+
+ # 修改检测标签的编码处,新增了参数分类数:num_classes,重写初始化方法,以及分类标签的读取
+
+ def __init__(self, label_list, num_classes=8, **kwargs):
+ self.num_classes = num_classes
+ self.label_list = []
+ if label_list:
+ if isinstance(label_list, str):
+ with open(label_list, 'r+', encoding='utf-8') as f:
+ for line in f.readlines():
+ self.label_list.append(line.replace("\n", ""))
+ else:
+ self.label_list = label_list
+ else:
+ assert ' please check label_list whether it is none or config is right'
+
+ if num_classes != len(self.label_list): # 校验分类数和标签的一致性
+ assert 'label_list length is not equal to the num_classes'
+
+ def __call__(self, data):
+ label = data['label']
+ label = json.loads(label)
+ nBox = len(label)
+ boxes, txts, txt_tags, classes = [], [], [], []
+ for bno in range(0, nBox):
+ box = label[bno]['points']
+ txt = label[bno]['key_cls'] # 此处将kie中的参数作为分类读取
+ boxes.append(box)
+ txts.append(txt)
+
+ if txt in ['*', '###']:
+ txt_tags.append(True)
+ if self.num_classes > 1:
+ classes.append(-2)
+ else:
+ txt_tags.append(False)
+ if self.num_classes > 1: # 将KIE内容的key标签作为分类标签使用
+ classes.append(int(self.label_list.index(txt)))
+
+ if len(boxes) == 0:
+
+ return None
+ boxes = self.expand_points_num(boxes)
+ boxes = np.array(boxes, dtype=np.float32)
+ txt_tags = np.array(txt_tags, dtype=np.bool_)
+ classes = classes
+ data['polys'] = boxes
+ data['texts'] = txts
+ data['ignore_tags'] = txt_tags
+ if self.num_classes > 1:
+ data['classes'] = classes
+ return data
+```
+
+修改P`addleOCR/ppocr/data/imaug/make_shrink_map.py`中的MakeShrinkMap类。这里需要注意的是,如果我们设置的label_list中的第一个参数为要检测的信息那么会得到如下的mask,
+
+举例说明:
+这是检测的mask图,图中有四个mask那么实际对应的分类应该是4类
+
+![](./images/42d2188d3d6b498880952e12c3ceae1efabf135f8d9f4c31823f09ebe02ba9d2.jpeg)
+
+label_list中第一个为关键分类,则得到的分类Mask实际如下,与上图相比,少了一个box:
+
+![](./images/864604967256461aa7c5d32cd240645e9f4c70af773341d5911f22d5a3e87b5f.jpeg)
+
+```python linenums="1"
+class MakeShrinkMap(object):
+ r'''
+ Making binary mask from detection data with ICDAR format.
+ Typically following the process of class `MakeICDARData`.
+ '''
+
+ def __init__(self, min_text_size=8, shrink_ratio=0.4, num_classes=8, **kwargs):
+ self.min_text_size = min_text_size
+ self.shrink_ratio = shrink_ratio
+ self.num_classes = num_classes # 添加了分类
+
+ def __call__(self, data):
+ image = data['image']
+ text_polys = data['polys']
+ ignore_tags = data['ignore_tags']
+ if self.num_classes > 1:
+ classes = data['classes']
+
+ h, w = image.shape[:2]
+ text_polys, ignore_tags = self.validate_polygons(text_polys,
+ ignore_tags, h, w)
+ gt = np.zeros((h, w), dtype=np.float32)
+ mask = np.ones((h, w), dtype=np.float32)
+ gt_class = np.zeros((h, w), dtype=np.float32) # 新增分类
+ for i in range(len(text_polys)):
+ polygon = text_polys[i]
+ height = max(polygon[:, 1]) - min(polygon[:, 1])
+ width = max(polygon[:, 0]) - min(polygon[:, 0])
+ if ignore_tags[i] or min(height, width) < self.min_text_size:
+ cv2.fillPoly(mask,
+ polygon.astype(np.int32)[np.newaxis, :, :], 0)
+ ignore_tags[i] = True
+ else:
+ polygon_shape = Polygon(polygon)
+ subject = [tuple(l) for l in polygon]
+ padding = pyclipper.PyclipperOffset()
+ padding.AddPath(subject, pyclipper.JT_ROUND,
+ pyclipper.ET_CLOSEDPOLYGON)
+ shrinked = []
+
+ # Increase the shrink ratio every time we get multiple polygon returned back
+ possible_ratios = np.arange(self.shrink_ratio, 1,
+ self.shrink_ratio)
+ np.append(possible_ratios, 1)
+ for ratio in possible_ratios:
+ distance = polygon_shape.area * (
+ 1 - np.power(ratio, 2)) / polygon_shape.length
+ shrinked = padding.Execute(-distance)
+ if len(shrinked) == 1:
+ break
+
+ if shrinked == []:
+ cv2.fillPoly(mask,
+ polygon.astype(np.int32)[np.newaxis, :, :], 0)
+ ignore_tags[i] = True
+ continue
+
+ for each_shirnk in shrinked:
+ shirnk = np.array(each_shirnk).reshape(-1, 2)
+ cv2.fillPoly(gt, [shirnk.astype(np.int32)], 1)
+ if self.num_classes > 1: # 绘制分类的mask
+ cv2.fillPoly(gt_class, polygon.astype(np.int32)[np.newaxis, :, :], classes[i])
+
+
+ data['shrink_map'] = gt
+
+ if self.num_classes > 1:
+ data['class_mask'] = gt_class
+
+ data['shrink_mask'] = mask
+ return data
+```
+
+由于在训练数据中会对数据进行resize设置,yml中的操作为:`EastRandomCropData`,所以需要修改`PaddleOCR/ppocr/data/imaug/random_crop_data.py`中的`EastRandomCropData`
+
+```python linenums="1"
+class EastRandomCropData(object):
+ def __init__(self,
+ size=(640, 640),
+ max_tries=10,
+ min_crop_side_ratio=0.1,
+ keep_ratio=True,
+ num_classes=8,
+ **kwargs):
+ self.size = size
+ self.max_tries = max_tries
+ self.min_crop_side_ratio = min_crop_side_ratio
+ self.keep_ratio = keep_ratio
+ self.num_classes = num_classes
+
+ def __call__(self, data):
+ img = data['image']
+ text_polys = data['polys']
+ ignore_tags = data['ignore_tags']
+ texts = data['texts']
+ if self.num_classes > 1:
+ classes = data['classes']
+ all_care_polys = [
+ text_polys[i] for i, tag in enumerate(ignore_tags) if not tag
+ ]
+ # 计算crop区域
+ crop_x, crop_y, crop_w, crop_h = crop_area(
+ img, all_care_polys, self.min_crop_side_ratio, self.max_tries)
+ # crop 图片 保持比例填充
+ scale_w = self.size[0] / crop_w
+ scale_h = self.size[1] / crop_h
+ scale = min(scale_w, scale_h)
+ h = int(crop_h * scale)
+ w = int(crop_w * scale)
+ if self.keep_ratio:
+ padimg = np.zeros((self.size[1], self.size[0], img.shape[2]),
+ img.dtype)
+ padimg[:h, :w] = cv2.resize(
+ img[crop_y:crop_y + crop_h, crop_x:crop_x + crop_w], (w, h))
+ img = padimg
+ else:
+ img = cv2.resize(
+ img[crop_y:crop_y + crop_h, crop_x:crop_x + crop_w],
+ tuple(self.size))
+ # crop 文本框
+ text_polys_crop = []
+ ignore_tags_crop = []
+ texts_crop = []
+ classes_crop = []
+ for poly, text, tag,class_index in zip(text_polys, texts, ignore_tags,classes):
+ poly = ((poly - (crop_x, crop_y)) * scale).tolist()
+ if not is_poly_outside_rect(poly, 0, 0, w, h):
+ text_polys_crop.append(poly)
+ ignore_tags_crop.append(tag)
+ texts_crop.append(text)
+ if self.num_classes > 1:
+ classes_crop.append(class_index)
+ data['image'] = img
+ data['polys'] = np.array(text_polys_crop)
+ data['ignore_tags'] = ignore_tags_crop
+ data['texts'] = texts_crop
+ if self.num_classes > 1:
+ data['classes'] = classes_crop
+ return data
+```
+
+#### 4.3.2 head修改
+
+主要修改`ppocr/modeling/heads/det_db_head.py`,将Head类中的最后一层的输出修改为实际的分类数,同时在DBHead中新增分类的head。
+
+![](./images/0e25da2ccded4af19e95c85c3d3287ab4d53e31a4eed4607b6a4cb637c43f6d3.jpeg)
+
+#### 4.3.3 修改loss
+
+修改`PaddleOCR/ppocr/losses/det_db_loss.py`中的DBLoss类,分类采用交叉熵损失函数进行计算。
+
+![](./images/dc10a070018d4d27946c26ec24a2a85bc3f16422f4964f72a9b63c6170d954e1.jpeg)
+
+#### 4.3.4 后处理
+
+由于涉及到eval以及后续推理能否正常使用,我们需要修改后处理的相关代码,修改位置`PaddleOCR/ppocr/postprocess/db_postprocess.py`中的DBPostProcess类
+
+```python linenums="1"
+class DBPostProcess(object):
+ """
+ The post process for Differentiable Binarization (DB).
+ """
+
+ def __init__(self,
+ thresh=0.3,
+ box_thresh=0.7,
+ max_candidates=1000,
+ unclip_ratio=2.0,
+ use_dilation=False,
+ score_mode="fast",
+ **kwargs):
+ self.thresh = thresh
+ self.box_thresh = box_thresh
+ self.max_candidates = max_candidates
+ self.unclip_ratio = unclip_ratio
+ self.min_size = 3
+ self.score_mode = score_mode
+ assert score_mode in [
+ "slow", "fast"
+ ], "Score mode must be in [slow, fast] but got: {}".format(score_mode)
+
+ self.dilation_kernel = None if not use_dilation else np.array(
+ [[1, 1], [1, 1]])
+
+ def boxes_from_bitmap(self, pred, _bitmap, classes, dest_width, dest_height):
+ """
+ _bitmap: single map with shape (1, H, W),
+ whose values are binarized as {0, 1}
+ """
+
+ bitmap = _bitmap
+ height, width = bitmap.shape
+
+ outs = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST,
+ cv2.CHAIN_APPROX_SIMPLE)
+ if len(outs) == 3:
+ img, contours, _ = outs[0], outs[1], outs[2]
+ elif len(outs) == 2:
+ contours, _ = outs[0], outs[1]
+
+ num_contours = min(len(contours), self.max_candidates)
+
+ boxes = []
+ scores = []
+ class_indexes = []
+ class_scores = []
+ for index in range(num_contours):
+ contour = contours[index]
+ points, sside = self.get_mini_boxes(contour)
+ if sside < self.min_size:
+ continue
+ points = np.array(points)
+ if self.score_mode == "fast":
+ score, class_index, class_score = self.box_score_fast(pred, points.reshape(-1, 2), classes)
+ else:
+ score, class_index, class_score = self.box_score_slow(pred, contour, classes)
+ if self.box_thresh > score:
+ continue
+
+ box = self.unclip(points).reshape(-1, 1, 2)
+ box, sside = self.get_mini_boxes(box)
+ if sside < self.min_size + 2:
+ continue
+ box = np.array(box)
+
+ box[:, 0] = np.clip(
+ np.round(box[:, 0] / width * dest_width), 0, dest_width)
+ box[:, 1] = np.clip(
+ np.round(box[:, 1] / height * dest_height), 0, dest_height)
+
+ boxes.append(box.astype(np.int16))
+ scores.append(score)
+
+ class_indexes.append(class_index)
+ class_scores.append(class_score)
+
+ if classes is None:
+ return np.array(boxes, dtype=np.int16), scores
+ else:
+ return np.array(boxes, dtype=np.int16), scores, class_indexes, class_scores
+
+ def unclip(self, box):
+ unclip_ratio = self.unclip_ratio
+ poly = Polygon(box)
+ distance = poly.area * unclip_ratio / poly.length
+ offset = pyclipper.PyclipperOffset()
+ offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
+ expanded = np.array(offset.Execute(distance))
+ return expanded
+
+ def get_mini_boxes(self, contour):
+ bounding_box = cv2.minAreaRect(contour)
+ points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])
+
+ index_1, index_2, index_3, index_4 = 0, 1, 2, 3
+ if points[1][1] > points[0][1]:
+ index_1 = 0
+ index_4 = 1
+ else:
+ index_1 = 1
+ index_4 = 0
+ if points[3][1] > points[2][1]:
+ index_2 = 2
+ index_3 = 3
+ else:
+ index_2 = 3
+ index_3 = 2
+
+ box = [
+ points[index_1], points[index_2], points[index_3], points[index_4]
+ ]
+ return box, min(bounding_box[1])
+
+ def box_score_fast(self, bitmap, _box, classes):
+ '''
+ box_score_fast: use bbox mean score as the mean score
+ '''
+ h, w = bitmap.shape[:2]
+ box = _box.copy()
+ xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int32), 0, w - 1)
+ xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int32), 0, w - 1)
+ ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int32), 0, h - 1)
+ ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int32), 0, h - 1)
+
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
+ box[:, 0] = box[:, 0] - xmin
+ box[:, 1] = box[:, 1] - ymin
+ cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1)
+
+ if classes is None:
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0], None, None
+ else:
+ k = 999
+ class_mask = np.full((ymax - ymin + 1, xmax - xmin + 1), k, dtype=np.int32)
+
+ cv2.fillPoly(class_mask, box.reshape(1, -1, 2).astype(np.int32), 0)
+ classes = classes[ymin:ymax + 1, xmin:xmax + 1]
+
+ new_classes = classes + class_mask
+ a = new_classes.reshape(-1)
+ b = np.where(a >= k)
+ classes = np.delete(a, b[0].tolist())
+
+ class_index = np.argmax(np.bincount(classes))
+ class_score = np.sum(classes == class_index) / len(classes)
+
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0], class_index, class_score
+
+ def box_score_slow(self, bitmap, contour, classes):
+ """
+ box_score_slow: use polyon mean score as the mean score
+ """
+ h, w = bitmap.shape[:2]
+ contour = contour.copy()
+ contour = np.reshape(contour, (-1, 2))
+
+ xmin = np.clip(np.min(contour[:, 0]), 0, w - 1)
+ xmax = np.clip(np.max(contour[:, 0]), 0, w - 1)
+ ymin = np.clip(np.min(contour[:, 1]), 0, h - 1)
+ ymax = np.clip(np.max(contour[:, 1]), 0, h - 1)
+
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
+
+ contour[:, 0] = contour[:, 0] - xmin
+ contour[:, 1] = contour[:, 1] - ymin
+
+ cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype(np.int32), 1)
+
+ if classes is None:
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0], None, None
+ else:
+ k = 999
+ class_mask = np.full((ymax - ymin + 1, xmax - xmin + 1), k, dtype=np.int32)
+
+ cv2.fillPoly(class_mask, contour.reshape(1, -1, 2).astype(np.int32), 0)
+ classes = classes[ymin:ymax + 1, xmin:xmax + 1]
+
+ new_classes = classes + class_mask
+ a = new_classes.reshape(-1)
+ b = np.where(a >= k)
+ classes = np.delete(a, b[0].tolist())
+
+ class_index = np.argmax(np.bincount(classes))
+ class_score = np.sum(classes == class_index) / len(classes)
+
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0], class_index, class_score
+
+ def __call__(self, outs_dict, shape_list):
+ pred = outs_dict['maps']
+ if isinstance(pred, paddle.Tensor):
+ pred = pred.numpy()
+ pred = pred[:, 0, :, :]
+ segmentation = pred > self.thresh
+
+ if "classes" in outs_dict:
+ classes = outs_dict['classes']
+ if isinstance(classes, paddle.Tensor):
+ classes = classes.numpy()
+ classes = classes[:, 0, :, :]
+
+ else:
+ classes = None
+
+ boxes_batch = []
+ for batch_index in range(pred.shape[0]):
+ src_h, src_w, ratio_h, ratio_w = shape_list[batch_index]
+ if self.dilation_kernel is not None:
+ mask = cv2.dilate(
+ np.array(segmentation[batch_index]).astype(np.uint8),
+ self.dilation_kernel)
+ else:
+ mask = segmentation[batch_index]
+
+ if classes is None:
+ boxes, scores = self.boxes_from_bitmap(pred[batch_index], mask, None,
+ src_w, src_h)
+ boxes_batch.append({'points': boxes})
+ else:
+ boxes, scores, class_indexes, class_scores = self.boxes_from_bitmap(pred[batch_index], mask,
+ classes[batch_index],
+ src_w, src_h)
+ boxes_batch.append({'points': boxes, "classes": class_indexes, "class_scores": class_scores})
+
+ return boxes_batch
+```
+
+### 4.4. 模型启动
+
+在完成上述步骤后我们就可以正常启动训练
+
+```bash linenums="1"
+!python /home/aistudio/work/PaddleOCR/tools/train.py -c /home/aistudio/work/PaddleOCR/configs/det/det_mv3_db.yml
+```
+
+其他命令:
+
+```bash linenums="1"
+!python /home/aistudio/work/PaddleOCR/tools/eval.py -c /home/aistudio/work/PaddleOCR/configs/det/det_mv3_db.yml
+!python /home/aistudio/work/PaddleOCR/tools/infer_det.py -c /home/aistudio/work/PaddleOCR/configs/det/det_mv3_db.yml
+```
+
+模型推理
+
+```bash linenums="1"
+!python /home/aistudio/work/PaddleOCR/tools/infer/predict_det.py --image_dir="/home/aistudio/work/test_img/" --det_model_dir="/home/aistudio/work/PaddleOCR/output/infer"
+```
+
+## 5 总结
+
+1. 分类+检测在一定程度上能够缩短用时,具体的模型选取要根据业务场景恰当选择。
+2. 数据标注需要多次进行测试调整标注方法,一般进行检测模型微调,需要标注至少上百张。
+3. 设置合理的batch_size以及resize大小,同时注意lr设置。
+
+## References
+
+1.
+2.
+3.
diff --git "a/docs/applications/\346\211\213\345\206\231\346\226\207\345\255\227\350\257\206\345\210\253.md" "b/docs/applications/\346\211\213\345\206\231\346\226\207\345\255\227\350\257\206\345\210\253.md"
new file mode 100644
index 0000000000..f68f4634d9
--- /dev/null
+++ "b/docs/applications/\346\211\213\345\206\231\346\226\207\345\255\227\350\257\206\345\210\253.md"
@@ -0,0 +1,242 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+
+# 基于PP-OCRv3的手写文字识别
+
+## 1. 项目背景及意义
+
+目前光学字符识别(OCR)技术在我们的生活当中被广泛使用,但是大多数模型在通用场景下的准确性还有待提高。针对于此我们借助飞桨提供的PaddleOCR套件较容易的实现了在垂类场景下的应用。手写体在日常生活中较为常见,然而手写体的识别却存在着很大的挑战,因为每个人的手写字体风格不一样,这对于视觉模型来说还是相当有挑战的。因此训练一个手写体识别模型具有很好的现实意义。下面给出一些手写体的示例图:
+
+![example](./images/7a8865b2836f42d382e7c3fdaedc4d307d797fa2bcd0466e9f8b7705efff5a7b.png)
+
+## 2. 项目内容
+
+本项目基于PaddleOCR套件,以PP-OCRv3识别模型为基础,针对手写文字识别场景进行优化。
+
+Aistudio项目链接:[OCR手写文字识别](https://aistudio.baidu.com/aistudio/projectdetail/4330587)
+
+## 3. PP-OCRv3识别算法介绍
+
+PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2205.00159)优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。如下图所示,PP-OCRv3采用了6个优化策略。
+
+![v3_rec](./images/d4f5344b5b854d50be738671598a89a45689c6704c4d481fb904dd7cf72f2a1a-20240704185905678.jpg)
+
+优化策略汇总如下:
+
+* SVTR_LCNet:轻量级文本识别网络
+* GTC:Attention指导CTC训练策略
+* TextConAug:挖掘文字上下文信息的数据增广策略
+* TextRotNet:自监督的预训练模型
+* UDML:联合互学习策略
+* UIM:无标注数据挖掘方案
+
+详细优化策略描述请参考[PP-OCRv3优化策略](../ppocr/blog/PP-OCRv3_introduction.md#3-识别优化)
+
+## 4. 安装环境
+
+```bash linenums="1"
+# 首先git官方的PaddleOCR项目,安装需要的依赖
+git clone https://github.com/PaddlePaddle/PaddleOCR.git
+cd PaddleOCR
+pip install -r requirements.txt
+```
+
+## 5. 数据准备
+
+本项目使用公开的手写文本识别数据集,包含Chinese OCR, 中科院自动化研究所-手写中文数据集[CASIA-HWDB2.x](http://www.nlpr.ia.ac.cn/databases/handwriting/Download.html),以及由中科院手写数据和网上开源数据合并组合的[数据集](https://aistudio.baidu.com/aistudio/datasetdetail/102884/0)等,该项目已经挂载处理好的数据集,可直接下载使用进行训练。
+
+```bash linenums="1"
+下载并解压数据
+tar -xf hw_data.tar
+```
+
+## 6. 模型训练
+
+### 6.1 下载预训练模型
+
+首先需要下载我们需要的PP-OCRv3识别预训练模型,更多选择请自行选择其他的[文字识别模型](../ppocr/model_list.md)
+
+```bash linenums="1"
+# 使用该指令下载需要的预训练模型
+wget -P ./pretrained_models/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar
+# 解压预训练模型文件
+tar -xf ./pretrained_models/ch_PP-OCRv3_rec_train.tar -C pretrained_models
+```
+
+### 6.2 修改配置文件
+
+我们使用`configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml`,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方:
+
+```yaml linenums="1"
+ epoch_num: 100 # 训练epoch数
+ save_model_dir: ./output/ch_PP-OCR_v3_rec
+ save_epoch_step: 10
+ eval_batch_step: [0, 100] # 评估间隔,每隔100step评估一次
+ pretrained_model: ./pretrained_models/ch_PP-OCRv3_rec_train/best_accuracy # 预训练模型路径
+
+
+ lr:
+ name: Cosine # 修改学习率衰减策略为Cosine
+ learning_rate: 0.0001 # 修改fine-tune的学习率
+ warmup_epoch: 2 # 修改warmup轮数
+
+Train:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data # 训练集图片路径
+ ext_op_transform_idx: 1
+ label_file_list:
+ - ./train_data/chineseocr-data/rec_hand_line_all_label_train.txt # 训练集标签
+ - ./train_data/handwrite/HWDB2.0Train_label.txt
+ - ./train_data/handwrite/HWDB2.1Train_label.txt
+ - ./train_data/handwrite/HWDB2.2Train_label.txt
+ - ./train_data/handwrite/hwdb_ic13/handwriting_hwdb_train_labels.txt
+ - ./train_data/handwrite/HW_Chinese/train_hw.txt
+ ratio_list:
+ - 0.1
+ - 1.0
+ - 1.0
+ - 1.0
+ - 0.02
+ - 1.0
+ loader:
+ shuffle: true
+ batch_size_per_card: 64
+ drop_last: true
+ num_workers: 4
+Eval:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data # 测试集图片路径
+ label_file_list:
+ - ./train_data/chineseocr-data/rec_hand_line_all_label_val.txt # 测试集标签
+ - ./train_data/handwrite/HWDB2.0Test_label.txt
+ - ./train_data/handwrite/HWDB2.1Test_label.txt
+ - ./train_data/handwrite/HWDB2.2Test_label.txt
+ - ./train_data/handwrite/hwdb_ic13/handwriting_hwdb_val_labels.txt
+ - ./train_data/handwrite/HW_Chinese/test_hw.txt
+ loader:
+ shuffle: false
+ drop_last: false
+ batch_size_per_card: 64
+ num_workers: 4
+```
+
+由于数据集大多是长文本,因此需要**注释**掉下面的数据增广策略,以便训练出更好的模型。
+
+```yaml linenums="1"
+- RecConAug:
+ prob: 0.5
+ ext_data_num: 2
+ image_shape: [48, 320, 3]
+```
+
+### 6.3 开始训练
+
+我们使用上面修改好的配置文件`configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml`,预训练模型,数据集路径,学习率,训练轮数等都已经设置完毕后,可以使用下面命令开始训练。
+
+```bash linenums="1"
+# 开始训练识别模型
+python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml
+
+```
+
+## 7. 模型评估
+
+在训练之前,我们可以直接使用下面命令来评估预训练模型的效果:
+
+```bash linenums="1"
+# 评估预训练模型
+python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./pretrained_models/ch_PP-OCRv3_rec_train/best_accuracy"
+```
+
+```
+[2022/07/14 10:46:22] ppocr INFO: load pretrain successful from ./pretrained_models/ch_PP-OCRv3_rec_train/best_accuracy
+eval model:: 100%|████████████████████████████| 687/687 [03:29<00:00, 3.27it/s]
+[2022/07/14 10:49:52] ppocr INFO: metric eval ***************
+[2022/07/14 10:49:52] ppocr INFO: acc:0.03724954461811258
+[2022/07/14 10:49:52] ppocr INFO: norm_edit_dis:0.4859541065843199
+[2022/07/14 10:49:52] ppocr INFO: Teacher_acc:0.0371584699368947
+[2022/07/14 10:49:52] ppocr INFO: Teacher_norm_edit_dis:0.48718814890536477
+[2022/07/14 10:49:52] ppocr INFO: fps:947.8562684823883
+```
+
+可以看出,直接加载预训练模型进行评估,效果较差,因为预训练模型并不是基于手写文字进行单独训练的,所以我们需要基于预训练模型进行finetune。
+训练完成后,可以进行测试评估,评估命令如下:
+
+```bash linenums="1"
+# 评估finetune效果
+python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_rec/best_accuracy"
+
+```
+
+评估结果如下,可以看出识别准确率为54.3%。
+
+```
+[2022/07/14 10:54:06] ppocr INFO: metric eval ***************
+[2022/07/14 10:54:06] ppocr INFO: acc:0.5430100180913
+[2022/07/14 10:54:06] ppocr INFO: norm_edit_dis:0.9203322593158589
+[2022/07/14 10:54:06] ppocr INFO: Teacher_acc:0.5401183969626324
+[2022/07/14 10:54:06] ppocr INFO: Teacher_norm_edit_dis:0.919827504507755
+[2022/07/14 10:54:06] ppocr INFO: fps:928.948733797251
+```
+
+如需获取已训练模型,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+* PaddleX官方交流频道:
+
+将下载或训练完成的模型放置在对应目录下即可完成模型推理
+
+## 8. 模型导出推理
+
+训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。
+
+### 8.1 模型导出
+
+导出命令如下:
+
+```bash linenums="1"
+# 转化为推理模型
+python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_rec/best_accuracy" Global.save_inference_dir="./inference/rec_ppocrv3/"
+
+```
+
+### 8.2 模型推理
+
+导出模型后,可以使用如下命令进行推理预测:
+
+```bash linenums="1"
+# 推理预测
+python tools/infer/predict_rec.py --image_dir="train_data/handwrite/HWDB2.0Test_images/104-P16_4.jpg" --rec_model_dir="./inference/rec_ppocrv3/Student"
+```
+
+```bash linenums="1"
+[2022/07/14 10:55:56] ppocr INFO: In PP-OCRv3, rec_image_shape parameter defaults to '3, 48, 320', if you are using recognition model with PP-OCRv2 or an older version, please set --rec_image_shape='3,32,320
+[2022/07/14 10:55:58] ppocr INFO: Predicts of train_data/handwrite/HWDB2.0Test_images/104-P16_4.jpg:('品结构,差异化的多品牌渗透使欧莱雅确立了其在中国化妆', 0.9904912114143372)
+```
+
+```python linenums="1"
+# 可视化文字识别图片
+from PIL import Image
+import matplotlib.pyplot as plt
+import numpy as np
+import os
+
+
+img_path = 'train_data/handwrite/HWDB2.0Test_images/104-P16_4.jpg'
+
+def vis(img_path):
+ plt.figure()
+ image = Image.open(img_path)
+ plt.imshow(image)
+ plt.show()
+ # image = image.resize([208, 208])
+
+
+vis(img_path)
+```
+
+![res](./images/ad7c02745491498d82e0ce95f4a274f9b3920b2f467646858709359b7af9d869.png)
diff --git "a/docs/applications/\346\211\253\346\217\217\345\220\210\345\220\214\345\205\263\351\224\256\344\277\241\346\201\257\346\217\220\345\217\226.md" "b/docs/applications/\346\211\253\346\217\217\345\220\210\345\220\214\345\205\263\351\224\256\344\277\241\346\201\257\346\217\220\345\217\226.md"
new file mode 100644
index 0000000000..d70679c39f
--- /dev/null
+++ "b/docs/applications/\346\211\253\346\217\217\345\220\210\345\220\214\345\205\263\351\224\256\344\277\241\346\201\257\346\217\220\345\217\226.md"
@@ -0,0 +1,280 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+
+# 金融智能核验:扫描合同关键信息抽取
+
+本案例将使用OCR技术和通用信息抽取技术,实现合同关键信息审核和比对。通过本章的学习,你可以快速掌握:
+
+1. 使用PaddleOCR提取扫描文本内容
+2. 使用PaddleNLP抽取自定义信息
+
+点击进入 [AI Studio 项目](https://aistudio.baidu.com/aistudio/projectdetail/4545772)
+
+## 1. 项目背景
+
+合同审核广泛应用于大中型企业、上市公司、证券、基金公司中,是规避风险的重要任务。
+
+- 合同内容对比:合同审核场景中,快速找出不同版本合同修改区域、版本差异;如合同盖章归档场景中有效识别实际签署的纸质合同、电子版合同差异。
+- 合规性检查:法务人员进行合同审核,如合同完备性检查、大小写金额检查、签约主体一致性检查、双方权利和义务对等性分析等。
+- 风险点识别:通过合同审核可识别事实倾向型风险点和数值计算型风险点等,例如交付地点约定不明、合同总价款不一致、重要条款缺失等风险点。
+
+![](./images/d5143df967fa4364a38868793fe7c57b0c0b1213930243babd6ae01423dcbc4d.png)
+
+传统业务中大多使用人工进行纸质版合同审核,存在成本高,工作量大,效率低的问题,且一旦出错将造成巨额损失。
+
+本项目针对以上场景,使用PaddleOCR+PaddleNLP快速提取文本内容,经过少量数据微调即可准确抽取关键信息,**高效完成合同内容对比、合规性检查、风险点识别等任务,提高效率,降低风险**。
+
+![](./images/54f3053e6e1b47a39b26e757006fe2c44910d60a3809422ab76c25396b92e69b-0096905.png)
+
+## 2. 解决方案
+
+### 2.1 扫描合同文本内容提取
+
+使用PaddleOCR开源的模型可以快速完成扫描文档的文本内容提取,在清晰文档上识别准确率可达到95%+。下面来快速体验一下:
+
+#### 2.1.1 环境准备
+
+[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)提供了适用于通用场景的高精轻量模型,提供数据预处理-模型推理-后处理全流程,支持pip安装:
+
+```bash linenums="1"
+python -m pip install paddleocr
+```
+
+#### 2.1.2 效果测试
+
+使用一张合同图片作为测试样本,感受ppocrv3模型效果:
+
+![](./images/46258d0dc9dc40bab3ea0e70434e4a905646df8a647f4c49921e217de5142def.jpeg)
+
+使用中文检测+识别模型提取文本,实例化PaddleOCR类:
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+# paddleocr目前支持中英文、英文、法语、德语、韩语、日语等80个语种,可以通过修改lang参数进行切换
+ocr = PaddleOCR(use_angle_cls=False, lang="ch") # need to run only once to download and load model into memory
+```
+
+一行命令启动预测,预测结果包括`检测框`和`文本识别内容`:
+
+```python linenums="1"
+img_path = "./test_img/hetong2.jpg"
+result = ocr.ocr(img_path, cls=False)
+for line in result:
+ print(line)
+
+# 可视化结果
+from PIL import Image
+
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='./simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.show()
+```
+
+#### 2.1.3 图片预处理
+
+通过上图可视化结果可以看到,印章部分造成的文本遮盖,影响了文本识别结果,因此可以考虑通道提取,去除图片中的红色印章:
+
+```python linenums="1"
+import cv2
+import numpy as np
+import matplotlib.pyplot as plt
+
+#读入图像,三通道
+image=cv2.imread("./test_img/hetong2.jpg",cv2.IMREAD_COLOR) #timg.jpeg
+
+#获得三个通道
+Bch,Gch,Rch=cv2.split(image)
+
+#保存三通道图片
+cv2.imwrite('blue_channel.jpg',Bch)
+cv2.imwrite('green_channel.jpg',Gch)
+cv2.imwrite('red_channel.jpg',Rch)
+```
+
+#### 2.1.4 合同文本信息提取
+
+经过2.1.3的预处理后,合同照片的红色通道被分离,获得了一张相对更干净的图片,此时可以再次使用ppocr模型提取文本内容:
+
+```python linenums="1"
+import numpy as np
+import cv2
+
+
+img_path = './red_channel.jpg'
+result = ocr.ocr(img_path, cls=False)
+
+# 可视化结果
+from PIL import Image
+
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='./simfang.ttf')
+im_show = Image.fromarray(im_show)
+vis = np.array(im_show)
+im_show.show()
+```
+
+忽略检测框内容,提取完整的合同文本:
+
+```python linenums="1"
+txts = [line[1][0] for line in result]
+all_context = "\n".join(txts)
+print(all_context)
+```
+
+通过以上环节就完成了扫描合同关键信息抽取的第一步:文本内容提取,接下来可以基于识别出的文本内容抽取关键信息
+
+### 2.2 合同关键信息抽取
+
+#### 2.2.1 环境准备
+
+安装PaddleNLP
+
+```bash linenums="1"
+pip install --upgrade pip
+pip install --upgrade paddlenlp
+```
+
+#### 2.2.2 合同关键信息抽取
+
+PaddleNLP 使用 Taskflow 统一管理多场景任务的预测功能,其中`information_extraction` 通过大量的有标签样本进行训练,在通用的场景中一般可以直接使用,只需更换关键字即可。例如在合同信息抽取中,我们重新定义抽取关键字:
+
+甲方、乙方、币种、金额、付款方式
+
+将使用OCR提取好的文本作为输入,使用三行命令可以对上文中提取到的合同文本进行关键信息抽取:
+
+```python linenums="1"
+from paddlenlp import Taskflow
+schema = ["甲方","乙方","总价"]
+ie = Taskflow('information_extraction', schema=schema)
+ie.set_schema(schema)
+ie(all_context)
+```
+
+可以看到UIE模型可以准确的提取出关键信息,用于后续的信息比对或审核。
+
+## 3.效果优化
+
+### 3.1 文本识别后处理调优
+
+实际图片采集过程中,可能出现部分图片弯曲等问题,导致使用默认参数识别文本时存在漏检,影响关键信息获取。
+
+例如下图:
+
+![](./images/fe350481be0241c58736d487d1bf06c2e65911bf01254a79944be629c4c10091.jpeg)
+
+直接进行预测:
+
+```python linenums="1"
+img_path = "./test_img/hetong3.jpg"
+# 预测结果
+result = ocr.ocr(img_path, cls=False)
+# 可视化结果
+from PIL import Image
+
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='./simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.show()
+```
+
+可视化结果可以看到,弯曲图片存在漏检,一般来说可以通过调整后处理参数解决,无需重新训练模型。漏检问题往往是因为检测模型获得的分割图太小,生成框的得分过低被过滤掉了,通常有两种方式调整参数:
+
+- 开启`use_dilatiion=True` 膨胀分割区域
+- 调小`det_db_box_thresh`阈值
+
+```python linenums="1"
+# 重新实例化 PaddleOCR
+ocr = PaddleOCR(use_angle_cls=False, lang="ch", det_db_box_thresh=0.3, use_dilation=True)
+
+# 预测并可视化
+img_path = "./test_img/hetong3.jpg"
+# 预测结果
+result = ocr.ocr(img_path, cls=False)
+# 可视化结果
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='./simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.show()
+```
+
+可以看到漏检问题被很好的解决,提取完整的文本内容:
+
+```python linenums="1"
+txts = [line[1][0] for line in result]
+context = "\n".join(txts)
+print(context)
+```
+
+### 3.2 关键信息提取调优
+
+UIE通过大量有标签样本进行训练,得到了一个开箱即用的高精模型。 然而针对不同场景,可能会出现部分实体无法被抽取的情况。通常来说有以下几个方法进行效果调优:
+
+- 修改 schema
+- 添加正则方法
+- 标注小样本微调模型
+
+**修改schema**
+
+Prompt和原文描述越像,抽取效果越好,例如
+
+```text linenums="1"
+三:合同价格:总价为人民币大写:参拾玖万捌仟伍佰
+元,小写:398500.00元。总价中包括站房工程建设、安装
+及相关避雷、消防、接地、电力、材料费、检验费、安全、
+验收等所需费用及其他相关费用和税金。
+```
+
+schema = ["总金额"] 时无法准确抽取,与原文描述差异较大。 修改 schema = ["总价"] 再次尝试:
+
+```python linenums="1"
+from paddlenlp import Taskflow
+# schema = ["总金额"]
+schema = ["总价"]
+ie = Taskflow('information_extraction', schema=schema)
+ie.set_schema(schema)
+ie(all_context)
+```
+
+**模型微调**
+UIE的建模方式主要是通过 `Prompt` 方式来建模, `Prompt` 在小样本上进行微调效果非常有效。详细的数据标注+模型微调步骤可以参考项目:
+
+[PaddleNLP信息抽取技术重磅升级!](https://aistudio.baidu.com/aistudio/projectdetail/3914778?channelType=0&channel=0)
+
+[工单信息抽取](https://aistudio.baidu.com/aistudio/projectdetail/3914778?contributionType=1)
+
+[快递单信息抽取](https://aistudio.baidu.com/aistudio/projectdetail/4038499?contributionType=1)
+
+## 总结
+
+扫描合同的关键信息提取可以使用 PaddleOCR + PaddleNLP 组合实现,两个工具均有以下优势:
+
+- 使用简单:whl包一键安装,3行命令调用
+- 效果领先:优秀的模型效果可覆盖几乎全部的应用场景
+- 调优成本低:OCR模型可通过后处理参数的调整适配略有偏差的扫描文本, UIE模型可以通过极少的标注样本微调,成本很低。
+
+## 作业
+
+尝试自己解析出 `test_img/homework.png` 扫描合同中的 [甲方、乙方] 关键词:
+
+![](./images/50a49a3c9f8348bfa04e8c8b97d3cce0d0dd6b14040f43939268d120688ef7ca.jpg)
+
+更多场景下的垂类模型获取,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+- PaddleX官方交流频道:
diff --git "a/docs/applications/\346\266\262\346\231\266\345\261\217\350\257\273\346\225\260\350\257\206\345\210\253.md" "b/docs/applications/\346\266\262\346\231\266\345\261\217\350\257\273\346\225\260\350\257\206\345\210\253.md"
new file mode 100644
index 0000000000..9922a055f4
--- /dev/null
+++ "b/docs/applications/\346\266\262\346\231\266\345\261\217\350\257\273\346\225\260\350\257\206\345\210\253.md"
@@ -0,0 +1,642 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+
+# 基于PP-OCRv3的液晶屏读数识别
+
+## 1. 项目背景及意义
+
+目前光学字符识别(OCR)技术在我们的生活当中被广泛使用,但是大多数模型在通用场景下的准确性还有待提高,针对于此我们借助飞桨提供的PaddleOCR套件较容易的实现了在垂类场景下的应用。
+
+该项目以国家质量基础(NQI)为准绳,充分利用大数据、云计算、物联网等高新技术,构建覆盖计量端、实验室端、数据端和硬件端的完整计量解决方案,解决传统计量校准中存在的难题,拓宽计量检测服务体系和服务领域;解决无数传接口或数传接口不统一、不公开的计量设备,以及计量设备所处的环境比较恶劣,不适合人工读取数据。通过OCR技术实现远程计量,引领计量行业向智慧计量转型和发展。
+
+## 2. 项目内容
+
+本项目基于PaddleOCR开源套件,以PP-OCRv3检测和识别模型为基础,针对液晶屏读数识别场景进行优化。
+
+Aistudio项目链接:[OCR液晶屏读数识别](https://aistudio.baidu.com/aistudio/projectdetail/4080130)
+
+## 3. 安装环境
+
+```bash linenums="1"
+# 首先git官方的PaddleOCR项目,安装需要的依赖
+# 第一次运行打开该注释
+# git clone https://gitee.com/PaddlePaddle/PaddleOCR.git
+cd PaddleOCR
+pip install -r requirements.txt
+```
+
+## 4. 文字检测
+
+文本检测的任务是定位出输入图像中的文字区域。近年来学术界关于文本检测的研究非常丰富,一类方法将文本检测视为目标检测中的一个特定场景,基于通用目标检测算法进行改进适配,如TextBoxes[1]基于一阶段目标检测器SSD[2]算法,调整目标框使之适合极端长宽比的文本行,CTPN[3]则是基于Faster RCNN[4]架构改进而来。但是文本检测与目标检测在目标信息以及任务本身上仍存在一些区别,如文本一般长宽比较大,往往呈“条状”,文本行之间可能比较密集,弯曲文本等,因此又衍生了很多专用于文本检测的算法。本项目基于PP-OCRv3算法进行优化。
+
+### 4.1 PP-OCRv3检测算法介绍
+
+PP-OCRv3检测模型是对PP-OCRv2中的CML(Collaborative Mutual Learning) 协同互学习文本检测蒸馏策略进行了升级。如下图所示,CML的核心思想结合了①传统的Teacher指导Student的标准蒸馏与 ②Students网络之间的DML互学习,可以让Students网络互学习的同时,Teacher网络予以指导。PP-OCRv3分别针对教师模型和学生模型进行进一步效果优化。其中,在对教师模型优化时,提出了大感受野的PAN结构LK-PAN和引入了DML(Deep Mutual Learning)蒸馏策略;在对学生模型优化时,提出了残差注意力机制的FPN结构RSE-FPN。
+![](./images/c306b2f028364805a55494d435ab553a76cf5ae5dd3f4649a948ea9aeaeb28b8.png)
+
+详细优化策略描述请参考[PP-OCRv3优化策略](../ppocr/blog/PP-OCRv3_introduction.md#2-检测优化)
+
+### 4.2 数据准备
+
+[计量设备屏幕字符检测数据集](https://aistudio.baidu.com/aistudio/datasetdetail/127845)数据来源于实际项目中各种计量设备的数显屏,以及在网上搜集的一些其他数显屏,包含训练集755张,测试集355张。
+
+```bash linenums="1"
+# 在PaddleOCR下创建新的文件夹train_data
+mkdir train_data
+# 下载数据集并解压到指定路径下
+unzip icdar2015.zip -d train_data
+```
+
+```python linenums="1"
+# 随机查看文字检测数据集图片
+from PIL import Image
+import matplotlib.pyplot as plt
+import numpy as np
+import os
+
+
+train = './train_data/icdar2015/text_localization/test'
+# 从指定目录中选取一张图片
+def get_one_image(train):
+ plt.figure()
+ files = os.listdir(train)
+ n = len(files)
+ ind = np.random.randint(0,n)
+ img_dir = os.path.join(train,files[ind])
+ image = Image.open(img_dir)
+ plt.imshow(image)
+ plt.show()
+ image = image.resize([208, 208])
+
+get_one_image(train)
+```
+
+![det_png](./images/0639da09b774458096ae577e82b2c59e89ced6a00f55458f946997ab7472a4f8.jpeg)
+
+### 4.3 模型训练
+
+#### 4.3.1 预训练模型直接评估
+
+下载我们需要的PP-OCRv3检测预训练模型,更多选择请自行选择其他的[文字检测模型](../ppocr/model_list.md#1-文本检测模型)
+
+```bash linenums="1"
+#使用该指令下载需要的预训练模型
+wget -P ./pretrained_models/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_distill_train.tar
+# 解压预训练模型文件
+tar -xf ./pretrained_models/ch_PP-OCRv3_det_distill_train.tar -C pretrained_models
+```
+
+在训练之前,我们可以直接使用下面命令来评估预训练模型的效果:
+
+```bash linenums="1"
+# 评估预训练模型
+python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Global.pretrained_model="./pretrained_models/ch_PP-OCRv3_det_distill_train/best_accuracy"
+```
+
+结果如下:
+
+| | 方案 |hmeans|
+|---|---------------------------|---|
+| 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.50%|
+
+#### 4.3.2 预训练模型直接finetune
+
+##### 修改配置文件
+
+我们使用configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方:
+
+```yaml linenums="1"
+epoch:100
+save_epoch_step:10
+eval_batch_step:[0, 50]
+save_model_dir: ./output/ch_PP-OCR_v3_det/
+pretrained_model: ./pretrained_models/ch_PP-OCRv3_det_distill_train/best_accuracy
+learning_rate: 0.00025
+num_workers: 0 # 如果单卡训练,建议将Train和Eval的loader部分的num_workers设置为0,否则会出现`/dev/shm insufficient`的报错
+```
+
+##### 开始训练
+
+使用我们上面修改的配置文件configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml,训练命令如下:
+
+```bash linenums="1"
+# 开始训练模型
+python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Global.pretrained_model=./pretrained_models/ch_PP-OCRv3_det_distill_train/best_accuracy
+```
+
+评估训练好的模型:
+
+```bash linenums="1"
+# 评估训练好的模型
+python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_det/best_accuracy"
+```
+
+结果如下:
+
+| | 方案 |hmeans|
+|---|---------------------------|---|
+| 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.50%|
+| 1 | PP-OCRv3中英文超轻量检测预训练模型fintune |65.20%|
+
+#### 4.3.3 基于预训练模型Finetune_student模型
+
+我们使用configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方:
+
+```yaml linenums="1"
+epoch:100
+save_epoch_step:10
+eval_batch_step:[0, 50]
+save_model_dir: ./output/ch_PP-OCR_v3_det_student/
+pretrained_model: ./pretrained_models/ch_PP-OCRv3_det_distill_train/student
+learning_rate: 0.00025
+num_workers: 0 # 如果单卡训练,建议将Train和Eval的loader部分的num_workers设置为0,否则会出现`/dev/shm insufficient`的报错
+```
+
+训练命令如下:
+
+```bash linenums="1"
+python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o Global.pretrained_model=./pretrained_models/ch_PP-OCRv3_det_distill_train/student
+```
+
+评估训练好的模型:
+
+```bash linenums="1"
+# 评估训练好的模型
+python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_det_student/best_accuracy"
+```
+
+结果如下:
+
+| | 方案 |hmeans|
+|---|---------------------------|---|
+| 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.50%|
+| 1 | PP-OCRv3中英文超轻量检测预训练模型fintune |65.20%|
+| 2 | PP-OCRv3中英文超轻量检测预训练模型fintune学生模型 |80.00%|
+
+#### 4.3.4 基于预训练模型Finetune_teacher模型
+
+首先需要从提供的预训练模型best_accuracy.pdparams中提取teacher参数,组合成适合dml训练的初始化模型,提取代码如下:
+
+```python linenums="1"
+cd ./pretrained_models/
+# transform teacher params in best_accuracy.pdparams into teacher_dml.paramers
+import paddle
+
+# load pretrained model
+all_params = paddle.load("ch_PP-OCRv3_det_distill_train/best_accuracy.pdparams")
+# print(all_params.keys())
+
+# keep teacher params
+t_params = {key[len("Teacher."):]: all_params[key] for key in all_params if "Teacher." in key}
+
+# print(t_params.keys())
+
+s_params = {"Student." + key: t_params[key] for key in t_params}
+s2_params = {"Student2." + key: t_params[key] for key in t_params}
+s_params = {**s_params, **s2_params}
+# print(s_params.keys())
+
+paddle.save(s_params, "ch_PP-OCRv3_det_distill_train/teacher_dml.pdparams")
+
+```
+
+我们使用configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_dml.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方:
+
+```yaml linenums="1"
+epoch:100
+save_epoch_step:10
+eval_batch_step:[0, 50]
+save_model_dir: ./output/ch_PP-OCR_v3_det_teacher/
+pretrained_model: ./pretrained_models/ch_PP-OCRv3_det_distill_train/teacher_dml
+learning_rate: 0.00025
+num_workers: 0 # 如果单卡训练,建议将Train和Eval的loader部分的num_workers设置为0,否则会出现`/dev/shm insufficient`的报错
+```
+
+训练命令如下:
+
+```bash linenums="1"
+python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_dml.yml -o Global.pretrained_model=./pretrained_models/ch_PP-OCRv3_det_distill_train/teacher_dml
+```
+
+评估训练好的模型:
+
+```bash linenums="1"
+# 评估训练好的模型
+python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_dml.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_det_teacher/best_accuracy"
+```
+
+结果如下:
+
+| | 方案 |hmeans|
+|---|---------------------------|---|
+| 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.50%|
+| 1 | PP-OCRv3中英文超轻量检测预训练模型fintune |65.20%|
+| 2 | PP-OCRv3中英文超轻量检测预训练模型fintune学生模型 |80.00%|
+| 3 | PP-OCRv3中英文超轻量检测预训练模型fintune教师模型 |84.80%|
+
+#### 4.3.5 采用CML蒸馏进一步提升student模型精度
+
+需要从4.3.3和4.3.4训练得到的best_accuracy.pdparams中提取各自代表student和teacher的参数,组合成适合cml训练的初始化模型,提取代码如下:
+
+```python linenums="1"
+# transform teacher params and student parameters into cml model
+import paddle
+
+all_params = paddle.load("./pretrained_models/ch_PP-OCRv3_det_distill_train/best_accuracy.pdparams")
+# print(all_params.keys())
+
+t_params = paddle.load("./output/ch_PP-OCR_v3_det_teacher/best_accuracy.pdparams")
+# print(t_params.keys())
+
+s_params = paddle.load("./output/ch_PP-OCR_v3_det_student/best_accuracy.pdparams")
+# print(s_params.keys())
+
+for key in all_params:
+ # teacher is OK
+ if "Teacher." in key:
+ new_key = key.replace("Teacher", "Student")
+ #print("{} >> {}\n".format(key, new_key))
+ assert all_params[key].shape == t_params[new_key].shape
+ all_params[key] = t_params[new_key]
+
+ if "Student." in key:
+ new_key = key.replace("Student.", "")
+ #print("{} >> {}\n".format(key, new_key))
+ assert all_params[key].shape == s_params[new_key].shape
+ all_params[key] = s_params[new_key]
+
+ if "Student2." in key:
+ new_key = key.replace("Student2.", "")
+ print("{} >> {}\n".format(key, new_key))
+ assert all_params[key].shape == s_params[new_key].shape
+ all_params[key] = s_params[new_key]
+
+paddle.save(all_params, "./pretrained_models/ch_PP-OCRv3_det_distill_train/teacher_cml_student.pdparams")
+```
+
+训练命令如下:
+
+```bash linenums="1"
+python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Global.pretrained_model=./pretrained_models/ch_PP-OCRv3_det_distill_train/teacher_cml_student Global.save_model_dir=./output/ch_PP-OCR_v3_det_finetune/
+```
+
+评估训练好的模型:
+
+```bash linenums="1"
+# 评估训练好的模型
+python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_det_finetune/best_accuracy"
+```
+
+结果如下:
+
+| | 方案 |hmeans|
+|---|---------------------------|---|
+| 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.50%|
+| 1 | PP-OCRv3中英文超轻量检测预训练模型fintune |65.20%|
+| 2 | PP-OCRv3中英文超轻量检测预训练模型fintune学生模型 |80.00%|
+| 3 | PP-OCRv3中英文超轻量检测预训练模型fintune教师模型 |84.80%|
+| 4 | 基于2和3训练好的模型fintune |82.70%|
+
+如需获取已训练模型,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+- PaddleX官方交流频道:
+
+将下载或训练完成的模型放置在对应目录下即可完成模型推理
+
+#### 4.3.6 模型导出推理
+
+训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。
+
+##### 4.3.6.1 模型导出
+
+导出命令如下:
+
+```bash linenums="1"
+# 转化为推理模型
+python tools/export_model.py \
+-c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml \
+-o Global.pretrained_model=./output/ch_PP-OCR_v3_det_finetune/best_accuracy \
+-o Global.save_inference_dir="./inference/det_ppocrv3"
+
+```
+
+##### 4.3.6.2 模型推理
+
+导出模型后,可以使用如下命令进行推理预测:
+
+```bash linenums="1"
+# 推理预测
+python tools/infer/predict_det.py --image_dir="train_data/icdar2015/text_localization/test/1.jpg" --det_model_dir="./inference/det_ppocrv3/Student"
+```
+
+## 5. 文字识别
+
+文本识别的任务是识别出图像中的文字内容,一般输入来自于文本检测得到的文本框截取出的图像文字区域。文本识别一般可以根据待识别文本形状分为规则文本识别和不规则文本识别两大类。规则文本主要指印刷字体、扫描文本等,文本大致处在水平线位置;不规则文本往往不在水平位置,存在弯曲、遮挡、模糊等问题。不规则文本场景具有很大的挑战性,也是目前文本识别领域的主要研究方向。本项目基于PP-OCRv3算法进行优化。
+
+### 5.1 PP-OCRv3识别算法介绍
+
+PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2205.00159)优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。如下图所示,PP-OCRv3采用了6个优化策略。
+![](./images/d4f5344b5b854d50be738671598a89a45689c6704c4d481fb904dd7cf72f2a1a.png)
+
+优化策略汇总如下:
+
+- SVTR_LCNet:轻量级文本识别网络
+- GTC:Attention指导CTC训练策略
+- TextConAug:挖掘文字上下文信息的数据增广策略
+- TextRotNet:自监督的预训练模型
+- UDML:联合互学习策略
+- UIM:无标注数据挖掘方案
+
+详细优化策略描述请参考[PP-OCRv3优化策略](../ppocr/blog/PP-OCRv3_introduction.md#3-识别优化)
+
+### 5.2 数据准备
+
+[计量设备屏幕字符识别数据集](https://aistudio.baidu.com/aistudio/datasetdetail/128714)数据来源于实际项目中各种计量设备的数显屏,以及在网上搜集的一些其他数显屏,包含训练集19912张,测试集4099张。
+
+```bash linenums="1"
+# 解压下载的数据集到指定路径下
+unzip ic15_data.zip -d train_data
+```
+
+```python linenums="1"
+# 随机查看文字检测数据集图片
+from PIL import Image
+import matplotlib.pyplot as plt
+import numpy as np
+import os
+
+train = './train_data/ic15_data/train'
+# 从指定目录中选取一张图片
+def get_one_image(train):
+ plt.figure()
+ files = os.listdir(train)
+ n = len(files)
+ ind = np.random.randint(0,n)
+ img_dir = os.path.join(train,files[ind])
+ image = Image.open(img_dir)
+ plt.imshow(image)
+ plt.show()
+ image = image.resize([208, 208])
+
+get_one_image(train)
+```
+
+![rec_png](./images/3de0d475c69746d0a184029001ef07c85fd68816d66d4beaa10e6ef60030f9b4.jpeg)
+
+### 5.3 模型训练
+
+#### 下载预训练模型
+
+下载我们需要的PP-OCRv3识别预训练模型,更多选择请自行选择其他的[文字识别模型](../ppocr/model_list.md#2-文本识别模型)
+
+```bash linenums="1"
+# 使用该指令下载需要的预训练模型
+wget -P ./pretrained_models/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar
+# 解压预训练模型文件
+tar -xf ./pretrained_models/ch_PP-OCRv3_rec_train.tar -C pretrained_models
+```
+
+#### 修改配置文件
+
+我们使用configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方:
+
+```yaml linenums="1"
+ epoch_num: 100 # 训练epoch数
+ save_model_dir: ./output/ch_PP-OCR_v3_rec
+ save_epoch_step: 10
+ eval_batch_step: [0, 100] # 评估间隔,每隔100step评估一次
+ cal_metric_during_train: true
+ pretrained_model: ./pretrained_models/ch_PP-OCRv3_rec_train/best_accuracy # 预训练模型路径
+ character_dict_path: ppocr/utils/ppocr_keys_v1.txt
+ use_space_char: true # 使用空格
+
+ lr:
+ name: Cosine # 修改学习率衰减策略为Cosine
+ learning_rate: 0.0002 # 修改fine-tune的学习率
+ warmup_epoch: 2 # 修改warmup轮数
+
+Train:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data/ic15_data/ # 训练集图片路径
+ ext_op_transform_idx: 1
+ label_file_list:
+ - ./train_data/ic15_data/rec_gt_train.txt # 训练集标签
+ ratio_list:
+ - 1.0
+ loader:
+ shuffle: true
+ batch_size_per_card: 64
+ drop_last: true
+ num_workers: 4
+Eval:
+ dataset:
+ name: SimpleDataSet
+ data_dir: ./train_data/ic15_data/ # 测试集图片路径
+ label_file_list:
+ - ./train_data/ic15_data/rec_gt_test.txt # 测试集标签
+ ratio_list:
+ - 1.0
+ loader:
+ shuffle: false
+ drop_last: false
+ batch_size_per_card: 64
+ num_workers: 4
+```
+
+在训练之前,我们可以直接使用下面命令来评估预训练模型的效果:
+
+```bash linenums="1"
+# 评估预训练模型
+python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./pretrained_models/ch_PP-OCRv3_rec_train/best_accuracy"
+```
+
+结果如下:
+
+| | 方案 |accuracy|
+|---|---------------------------|---|
+| 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.40%|
+
+#### 开始训练
+
+我们使用上面修改好的配置文件configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,预训练模型,数据集路径,学习率,训练轮数等都已经设置完毕后,可以使用下面命令开始训练。
+
+```bash linenums="1"
+# 开始训练识别模型
+python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml
+```
+
+训练完成后,可以对训练模型中最好的进行测试,评估命令如下:
+
+```bash linenums="1"
+# 评估finetune效果
+python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.checkpoints="./output/ch_PP-OCR_v3_rec/best_accuracy"
+```
+
+结果如下:
+
+| | 方案 |accuracy|
+|---|---------------------------|---|
+| 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.40%|
+| 1 | PP-OCRv3中英文超轻量识别预训练模型finetune |82.20%|
+
+如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁
+
+
+
+将下载或训练完成的模型放置在对应目录下即可完成模型推理。
+
+### 5.4 模型导出推理
+
+训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。
+
+#### 模型导出
+
+导出命令如下:
+
+```bash linenums="1"
+# 转化为推理模型
+python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_rec/best_accuracy" Global.save_inference_dir="./inference/rec_ppocrv3/"
+```
+
+#### 模型推理
+
+导出模型后,可以使用如下命令进行推理预测
+
+```bash linenums="1"
+# 推理预测
+python tools/infer/predict_rec.py --image_dir="train_data/ic15_data/test/1_crop_0.jpg" --rec_model_dir="./inference/rec_ppocrv3/Student"
+```
+
+## 6. 系统串联
+
+我们将上面训练好的检测和识别模型进行系统串联测试,命令如下:
+
+```bash linenums="1"
+#串联测试
+python3 tools/infer/predict_system.py --image_dir="./train_data/icdar2015/text_localization/test/142.jpg" --det_model_dir="./inference/det_ppocrv3/Student" --rec_model_dir="./inference/rec_ppocrv3/Student"
+```
+
+测试结果保存在`./inference_results/`目录下,可以用下面代码进行可视化
+
+```bash linenums="1"
+%cd /home/aistudio/PaddleOCR
+# 显示结果
+import matplotlib.pyplot as plt
+from PIL import Image
+img_path= "./inference_results/142.jpg"
+img = Image.open(img_path)
+plt.figure("test_img", figsize=(30,30))
+plt.imshow(img)
+plt.show()
+```
+
+![sys_res_png](./images/901ab741cb46441ebec510b37e63b9d8d1b7c95f63cc4e5e8757f35179ae6373-20240704185855034.png)
+
+### 6.1 后处理
+
+如果需要获取key-value信息,可以基于启发式的规则,将识别结果与关键字库进行匹配;如果匹配上了,则取该字段为key, 后面一个字段为value。
+
+```python linenums="1"
+def postprocess(rec_res):
+ keys = ["型号", "厂家", "版本号", "检定校准分类", "计量器具编号", "烟尘流量",
+ "累积体积", "烟气温度", "动压", "静压", "时间", "试验台编号", "预测流速",
+ "全压", "烟温", "流速", "工况流量", "标杆流量", "烟尘直读嘴", "烟尘采样嘴",
+ "大气压", "计前温度", "计前压力", "干球温度", "湿球温度", "流量", "含湿量"]
+ key_value = []
+ if len(rec_res) > 1:
+ for i in range(len(rec_res) - 1):
+ rec_str, _ = rec_res[i]
+ for key in keys:
+ if rec_str in key:
+ key_value.append([rec_str, rec_res[i + 1][0]])
+ break
+ return key_value
+key_value = postprocess(filter_rec_res)
+```
+
+## 7. PaddleServing部署
+
+首先需要安装PaddleServing部署相关的环境
+
+```bash linenums="1"
+python -m pip install paddle-serving-server-gpu
+python -m pip install paddle_serving_client
+python -m pip install paddle-serving-app
+```
+
+### 7.1 转化检测模型
+
+```bash linenums="1"
+cd deploy/pdserving/
+python -m paddle_serving_client.convert --dirname ../../inference/det_ppocrv3/Student/ \
+ --model_filename inference.pdmodel \
+ --params_filename inference.pdiparams \
+ --serving_server ./ppocr_det_v3_serving/ \
+ --serving_client ./ppocr_det_v3_client/
+```
+
+### 7.2 转化识别模型
+
+```bash linenums="1"
+python -m paddle_serving_client.convert --dirname ../../inference/rec_ppocrv3/Student \
+ --model_filename inference.pdmodel \
+ --params_filename inference.pdiparams \
+ --serving_server ./ppocr_rec_v3_serving/ \
+ --serving_client ./ppocr_rec_v3_client/
+```
+
+### 7.3 启动服务
+
+首先可以将后处理代码加入到web_service.py中,具体修改如下:
+
+```python linenums="1"
+# 代码153行后面增加下面代码
+def _postprocess(rec_res):
+ keys = ["型号", "厂家", "版本号", "检定校准分类", "计量器具编号", "烟尘流量",
+ "累积体积", "烟气温度", "动压", "静压", "时间", "试验台编号", "预测流速",
+ "全压", "烟温", "流速", "工况流量", "标杆流量", "烟尘直读嘴", "烟尘采样嘴",
+ "大气压", "计前温度", "计前压力", "干球温度", "湿球温度", "流量", "含湿量"]
+ key_value = []
+ if len(rec_res) > 1:
+ for i in range(len(rec_res) - 1):
+ rec_str, _ = rec_res[i]
+ for key in keys:
+ if rec_str in key:
+ key_value.append([rec_str, rec_res[i + 1][0]])
+ break
+ return key_value
+key_value = _postprocess(rec_list)
+res = {"result": str(key_value)}
+# res = {"result": str(result_list)}
+```
+
+启动服务端
+
+```bash linenums="1"
+python web_service.py 2>&1 >log.txt
+```
+
+### 7.4 发送请求
+
+然后再开启一个新的终端,运行下面的客户端代码
+
+```bash linenums="1"
+python pipeline_http_client.py --image_dir ../../train_data/icdar2015/text_localization/test/142.jpg
+```
+
+可以获取到最终的key-value结果:
+
+```text linenums="1"
+大气压, 100.07kPa
+干球温度, 0000℃
+计前温度, 0000℃
+湿球温度, 0000℃
+计前压力, -0000kPa
+流量, 00.0L/min
+静压, 00000kPa
+含湿量, 00.0 %
+```
diff --git "a/docs/applications/\350\275\273\351\207\217\347\272\247\350\275\246\347\211\214\350\257\206\345\210\253.md" "b/docs/applications/\350\275\273\351\207\217\347\272\247\350\275\246\347\211\214\350\257\206\345\210\253.md"
new file mode 100644
index 0000000000..de15aee6ff
--- /dev/null
+++ "b/docs/applications/\350\275\273\351\207\217\347\272\247\350\275\246\347\211\214\350\257\206\345\210\253.md"
@@ -0,0 +1,815 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+
+# 一种基于PaddleOCR的轻量级车牌识别模型
+
+## 1. 项目介绍
+
+车牌识别(Vehicle License Plate Recognition,VLPR) 是计算机视频图像识别技术在车辆牌照识别中的一种应用。车牌识别技术要求能够将运动中的汽车牌照从复杂背景中提取并识别出来,在高速公路车辆管理,停车场管理和城市交通中得到广泛应用。
+
+本项目难点如下:
+
+1. 车牌在图像中的尺度差异大、在车辆上的悬挂位置不固定
+2. 车牌图像质量层次不齐: 角度倾斜、图片模糊、光照不足、过曝等问题严重
+3. 边缘和端测场景应用对模型大小有限制,推理速度有要求
+
+针对以上问题, 本例选用 PP-OCRv3 这一开源超轻量OCR系统进行车牌识别系统的开发。基于PP-OCRv3模型,在CCPD数据集达到99%的检测和94%的识别精度,模型大小12.8M(2.5M+10.3M)。基于量化对模型体积进行进一步压缩到5.8M(1M+4.8M), 同时推理速度提升25%。
+
+aistudio项目链接: [基于PaddleOCR的轻量级车牌识别范例](https://aistudio.baidu.com/aistudio/projectdetail/3919091?contributionType=1)
+
+## 2. 环境搭建
+
+本任务基于Aistudio完成, 具体环境如下:
+
+- 操作系统: Linux
+- PaddlePaddle: 2.3
+- paddleslim: 2.2.2
+- PaddleOCR: Release/2.5
+
+下载 PaddleOCR代码
+
+```bash linenums="1"
+git clone -b dygraph https://github.com/PaddlePaddle/PaddleOCR
+```
+
+安装依赖库
+
+```bash linenums="1"
+pip install -r PaddleOCR/requirements.txt
+```
+
+## 3. 数据集准备
+
+所使用的数据集为 CCPD2020 新能源车牌数据集,该数据集为
+
+该数据集分布如下:
+
+|数据集类型|数量|
+|---|---|
+|训练集| 5769|
+|验证集| 1001|
+|测试集| 5006|
+
+数据集图片示例如下:
+
+![](./images/3bce057a8e0c40a0acbd26b2e29e4e2590a31bc412764be7b9e49799c69cb91c.jpg)
+
+数据集可以从这里下载
+
+下载好数据集后对数据集进行解压
+
+```bash linenums="1"
+unzip -d /home/aistudio/data /home/aistudio/data/data101595/CCPD2020.zip
+```
+
+### 3.1 数据集标注规则
+
+CPPD数据集的图片文件名具有特殊规则,详细可查看:
+
+具体规则如下:
+
+例如: 025-95_113-154&383_386&473-386&473_177&454_154&383_363&402-0_0_22_27_27_33_16-37-15.jpg
+
+每个名称可以分为七个字段,以-符号作为分割。这些字段解释如下:
+
+- 025:车牌面积与整个图片区域的面积比。025 (25%)
+- 95_113:水平倾斜程度和垂直倾斜度。水平 95度 垂直 113度
+- 154&383_386&473:左上和右下顶点的坐标。左上(154,383) 右下(386,473)
+- 386&473_177&454_154&383_363&402:整个图像中车牌的四个顶点的精确(x,y)坐标。这些坐标从右下角顶点开始。(386,473) (177,454) (154,383) (363,402)
+- 0_0_22_27_27_33_16:CCPD中的每个图像只有一个车牌。每个车牌号码由一个汉字,一个字母和五个字母或数字组成。有效的中文车牌由七个字符组成:省(1个字符),字母(1个字符),字母+数字(5个字符)。“ 0_0_22_27_27_33_16”是每个字符的索引。这三个数组定义如下:每个数组的最后一个字符是字母O,而不是数字0。我们将O用作“无字符”的符号,因为中文车牌字符中没有O。因此以上车牌拼起来即为 皖AY339S
+- 37:牌照区域的亮度。 37 (37%)
+- 15:车牌区域的模糊度。15 (15%)
+
+```python linenums="1"
+provinces = ["皖", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "京", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "警", "学", "O"]
+alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W','X', 'Y', 'Z', 'O']
+ads = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X','Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'O']
+```
+
+### 3.2 制作符合PP-OCR训练格式的标注文件
+
+在开始训练之前,可使用如下代码制作符合PP-OCR训练格式的标注文件。
+
+```python linenums="1"
+import cv2
+import os
+import json
+from tqdm import tqdm
+import numpy as np
+
+provinces = ["皖", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "京", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "警", "学", "O"]
+alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'O']
+ads = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'O']
+
+def make_label(img_dir, save_gt_folder, phase):
+ crop_img_save_dir = os.path.join(save_gt_folder, phase, 'crop_imgs')
+ os.makedirs(crop_img_save_dir, exist_ok=True)
+
+ f_det = open(os.path.join(save_gt_folder, phase, 'det.txt'), 'w', encoding='utf-8')
+ f_rec = open(os.path.join(save_gt_folder, phase, 'rec.txt'), 'w', encoding='utf-8')
+
+ i = 0
+ for filename in tqdm(os.listdir(os.path.join(img_dir, phase))):
+ str_list = filename.split('-')
+ if len(str_list) < 5:
+ continue
+ coord_list = str_list[3].split('_')
+ txt_list = str_list[4].split('_')
+ boxes = []
+ for coord in coord_list:
+ boxes.append([int(x) for x in coord.split("&")])
+ boxes = [boxes[2], boxes[3], boxes[0], boxes[1]]
+ lp_number = provinces[int(txt_list[0])] + alphabets[int(txt_list[1])] + ''.join([ads[int(x)] for x in txt_list[2:]])
+
+ # det
+ det_info = [{'points':boxes, 'transcription':lp_number}]
+ f_det.write('{}\t{}\n'.format(os.path.join(phase, filename), json.dumps(det_info, ensure_ascii=False)))
+
+ # rec
+ boxes = np.float32(boxes)
+ img = cv2.imread(os.path.join(img_dir, phase, filename))
+ # crop_img = img[int(boxes[:,1].min()):int(boxes[:,1].max()),int(boxes[:,0].min()):int(boxes[:,0].max())]
+ crop_img = get_rotate_crop_image(img, boxes)
+ crop_img_save_filename = '{}_{}.jpg'.format(i,'_'.join(txt_list))
+ crop_img_save_path = os.path.join(crop_img_save_dir, crop_img_save_filename)
+ cv2.imwrite(crop_img_save_path, crop_img)
+ f_rec.write('{}/crop_imgs/{}\t{}\n'.format(phase, crop_img_save_filename, lp_number))
+ i+=1
+ f_det.close()
+ f_rec.close()
+
+def get_rotate_crop_image(img, points):
+ '''
+ img_height, img_width = img.shape[0:2]
+ left = int(np.min(points[:, 0]))
+ right = int(np.max(points[:, 0]))
+ top = int(np.min(points[:, 1]))
+ bottom = int(np.max(points[:, 1]))
+ img_crop = img[top:bottom, left:right, :].copy()
+ points[:, 0] = points[:, 0] - left
+ points[:, 1] = points[:, 1] - top
+ '''
+ assert len(points) == 4, "shape of points must be 4*2"
+ img_crop_width = int(
+ max(
+ np.linalg.norm(points[0] - points[1]),
+ np.linalg.norm(points[2] - points[3])))
+ img_crop_height = int(
+ max(
+ np.linalg.norm(points[0] - points[3]),
+ np.linalg.norm(points[1] - points[2])))
+ pts_std = np.float32([[0, 0], [img_crop_width, 0],
+ [img_crop_width, img_crop_height],
+ [0, img_crop_height]])
+ M = cv2.getPerspectiveTransform(points, pts_std)
+ dst_img = cv2.warpPerspective(
+ img,
+ M, (img_crop_width, img_crop_height),
+ borderMode=cv2.BORDER_REPLICATE,
+ flags=cv2.INTER_CUBIC)
+ dst_img_height, dst_img_width = dst_img.shape[0:2]
+ if dst_img_height * 1.0 / dst_img_width >= 1.5:
+ dst_img = np.rot90(dst_img)
+ return dst_img
+
+img_dir = '/home/aistudio/data/CCPD2020/ccpd_green'
+save_gt_folder = '/home/aistudio/data/CCPD2020/PPOCR'
+# phase = 'train' # change to val and test to make val dataset and test dataset
+for phase in ['train','val','test']:
+ make_label(img_dir, save_gt_folder, phase)
+```
+
+通过上述命令可以完成了`训练集`,`验证集`和`测试集`的制作,制作完成的数据集信息如下:
+
+| 类型 | 数据集 | 图片地址 | 标签地址 | 图片数量 |
+| --- | --- | --- | --- | --- |
+| 检测 | 训练集 | /home/aistudio/data/CCPD2020/ccpd_green/train | /home/aistudio/data/CCPD2020/PPOCR/train/det.txt | 5769 |
+| 检测 | 验证集 | /home/aistudio/data/CCPD2020/ccpd_green/val | /home/aistudio/data/CCPD2020/PPOCR/val/det.txt | 1001 |
+| 检测 | 测试集 | /home/aistudio/data/CCPD2020/ccpd_green/test | /home/aistudio/data/CCPD2020/PPOCR/test/det.txt | 5006 |
+| 识别 | 训练集 | /home/aistudio/data/CCPD2020/PPOCR/train/crop_imgs | /home/aistudio/data/CCPD2020/PPOCR/train/rec.txt | 5769 |
+| 识别 | 验证集 | /home/aistudio/data/CCPD2020/PPOCR/val/crop_imgs | /home/aistudio/data/CCPD2020/PPOCR/val/rec.txt | 1001 |
+| 识别 | 测试集 | /home/aistudio/data/CCPD2020/PPOCR/test/crop_imgs | /home/aistudio/data/CCPD2020/PPOCR/test/rec.txt | 5006 |
+
+在普遍的深度学习流程中,都是在训练集训练,在验证集选择最优模型后在测试集上进行测试。在本例中,我们省略中间步骤,直接在训练集训练,在测试集选择最优模型,因此我们只使用训练集和测试集。
+
+## 4. 实验
+
+由于数据集比较少,为了模型更好和更快的收敛,这里选用 PaddleOCR 中的 PP-OCRv3 模型进行文本检测和识别,并且使用 PP-OCRv3 模型参数作为预训练模型。PP-OCRv3在PP-OCRv2的基础上,中文场景端到端Hmean指标相比于PP-OCRv2提升5%, 英文数字模型端到端效果提升11%。详细优化细节请参考[PP-OCRv3](../ppocr/blog/PP-OCRv3_introduction.md)技术报告。
+
+由于车牌场景均为端侧设备部署,因此对速度和模型大小有比较高的要求,因此还需要采用量化训练的方式进行模型大小的压缩和模型推理速度的加速。模型量化可以在基本不损失模型的精度的情况下,将FP32精度的模型参数转换为Int8精度,减小模型参数大小并加速计算,使用量化后的模型在移动端等部署时更具备速度优势。
+
+因此,本实验中对于车牌检测和识别有如下3种方案:
+
+1. PP-OCRv3中英文超轻量预训练模型直接预测
+2. CCPD车牌数据集在PP-OCRv3模型上fine-tune
+3. CCPD车牌数据集在PP-OCRv3模型上fine-tune后量化
+
+### 4.1 检测
+
+#### 4.1.1 预训练模型直接预测
+
+从下表中下载PP-OCRv3文本检测预训练模型
+
+|模型名称|模型简介|配置文件|推理模型大小|下载地址|
+| --- | --- | --- | --- | --- |
+|ch_PP-OCRv3_det| 【最新】原始超轻量模型,支持中英文、多语种文本检测 |[ch_PP-OCRv3_det_cml.yml](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml)| 3.8M |[推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_distill_train.tar)|
+
+使用如下命令下载预训练模型
+
+```bash linenums="1"
+mkdir models
+cd models
+wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_distill_train.tar
+tar -xf ch_PP-OCRv3_det_distill_train.tar
+cd /home/aistudio/PaddleOCR
+```
+
+预训练模型下载完成后,我们使用[ch_PP-OCRv3_det_student.yml](../configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml) 配置文件进行后续实验,在开始评估之前需要对配置文件中部分字段进行设置,具体如下:
+
+1. 模型存储和训练相关:
+ - Global.pretrained_model: 指向PP-OCRv3文本检测预训练模型地址
+2. 数据集相关
+ - Eval.dataset.data_dir:指向测试集图片存放目录
+ - Eval.dataset.label_file_list:指向测试集标注文件
+
+上述字段均为必须修改的字段,可以通过修改配置文件的方式改动,也可在不需要修改配置文件的情况下,改变训练的参数。这里使用不改变配置文件的方式 。使用如下命令进行PP-OCRv3文本检测预训练模型的评估
+
+```bash linenums="1"
+python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=models/ch_PP-OCRv3_det_distill_train/student.pdparams \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/ccpd_green \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/det.txt]
+```
+
+上述指令中,通过-c 选择训练使用配置文件,通过-o参数在不需要修改配置文件的情况下,改变训练的参数。
+
+使用预训练模型进行评估,指标如下所示:
+
+| 方案 |hmeans|
+|---------------------------|---|
+| PP-OCRv3中英文超轻量检测预训练模型直接预测 |76.12%|
+
+#### 4.1.2 CCPD车牌数据集fine-tune
+
+##### 训练
+
+为了进行fine-tune训练,我们需要在配置文件中设置需要使用的预训练模型地址,学习率和数据集等参数。 具体如下:
+
+1. 模型存储和训练相关:
+ 1. Global.pretrained_model: 指向PP-OCRv3文本检测预训练模型地址
+ 2. Global.eval_batch_step: 模型多少step评估一次,这里设为从第0个step开始没隔772个step评估一次,772为一个epoch总的step数。
+2. 优化器相关:
+ 1. Optimizer.lr.name: 学习率衰减器设为常量 Const
+ 2. Optimizer.lr.learning_rate: 做 fine-tune 实验,学习率需要设置的比较小,此处学习率设为配置文件中的0.05倍
+ 3. Optimizer.lr.warmup_epoch: warmup_epoch设为0
+3. 数据集相关:
+ 1. Train.dataset.data_dir:指向训练集图片存放目录
+ 2. Train.dataset.label_file_list:指向训练集标注文件
+ 3. Eval.dataset.data_dir:指向测试集图片存放目录
+ 4. Eval.dataset.label_file_list:指向测试集标注文件
+
+使用如下代码即可启动在CCPD车牌数据集上的fine-tune。
+
+```bash linenums="1"
+python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=models/ch_PP-OCRv3_det_distill_train/student.pdparams \
+ Global.save_model_dir=output/CCPD/det \
+ Global.eval_batch_step="[0, 772]" \
+ Optimizer.lr.name=Const \
+ Optimizer.lr.learning_rate=0.0005 \
+ Optimizer.lr.warmup_epoch=0 \
+ Train.dataset.data_dir=/home/aistudio/data/CCPD2020/ccpd_green \
+ Train.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/train/det.txt] \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/ccpd_green \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/det.txt]
+```
+
+在上述命令中,通过`-o`的方式修改了配置文件中的参数。
+
+##### 评估
+
+训练完成后使用如下命令进行评估
+
+```bash linenums="1"
+python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=output/CCPD/det/best_accuracy.pdparams \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/ccpd_green \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/det.txt]
+```
+
+使用预训练模型和CCPD车牌数据集fine-tune,指标分别如下:
+
+|方案|hmeans|
+|---|---|
+|PP-OCRv3中英文超轻量检测预训练模型直接预测|76.12%|
+|PP-OCRv3中英文超轻量检测预训练模型 fine-tune|99.00%|
+
+可以看到进行fine-tune能显著提升车牌检测的效果。
+
+#### 4.1.3 CCPD车牌数据集fine-tune+量化训练
+
+此处采用 PaddleOCR 中提供好的[量化教程](../ppocr/model_compress/quantization.md)对模型进行量化训练。
+
+量化训练可通过如下命令启动:
+
+```bash linenums="1"
+python3.7 deploy/slim/quantization/quant.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=output/CCPD/det/best_accuracy.pdparams \
+ Global.save_model_dir=output/CCPD/det_quant \
+ Global.eval_batch_step="[0, 772]" \
+ Optimizer.lr.name=Const \
+ Optimizer.lr.learning_rate=0.0005 \
+ Optimizer.lr.warmup_epoch=0 \
+ Train.dataset.data_dir=/home/aistudio/data/CCPD2020/ccpd_green \
+ Train.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/train/det.txt] \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/ccpd_green \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/det.txt]
+```
+
+量化后指标对比如下
+
+|方案|hmeans| 模型大小 | 预测速度(lite) |
+|---|---|------|------------|
+|PP-OCRv3中英文超轻量检测预训练模型 fine-tune|99.00%| 2.5M | 223ms |
+|PP-OCRv3中英文超轻量检测预训练模型 fine-tune+量化|98.91%| 1.0M | 189ms |
+
+可以看到通过量化训练在精度几乎无损的情况下,降低模型体积60%并且推理速度提升15%。
+
+速度测试基于[PaddleOCR lite教程](../ppocr/infer_deploy/lite.md)完成。
+
+#### 4.1.4 模型导出
+
+使用如下命令可以将训练好的模型进行导出
+
+非量化模型
+
+```bash linenums="1"
+python tools/export_model.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=output/CCPD/det/best_accuracy.pdparams \
+ Global.save_inference_dir=output/det/infer
+```
+
+量化模型
+
+```bash linenums="1"
+python deploy/slim/quantization/export_model.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=output/CCPD/det_quant/best_accuracy.pdparams \
+ Global.save_inference_dir=output/det/infer
+```
+
+### 4.2 识别
+
+#### 4.2.1 预训练模型直接预测
+
+从下表中下载PP-OCRv3文本识别预训练模型
+
+|模型名称|模型简介|配置文件|推理模型大小|下载地址|
+| --- | --- | --- | --- | --- |
+|ch_PP-OCRv3_rec|【最新】原始超轻量模型,支持中英文、数字识别|[ch_PP-OCRv3_rec_distillation.yml](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml)| 12.4M |[推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar) |
+
+使用如下命令下载预训练模型
+
+```bash linenums="1"
+mkdir models
+cd models
+wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar
+tar -xf ch_PP-OCRv3_rec_train.tar
+cd /home/aistudio/PaddleOCR
+```
+
+PaddleOCR提供的PP-OCRv3识别模型采用蒸馏训练策略,因此提供的预训练模型中会包含`Teacher`和`Student`模型的参数,详细信息可参考[knowledge_distillation.md](../ppocr/model_compress/knowledge_distillation.md)。 因此,模型下载完成后需要使用如下代码提取`Student`模型的参数:
+
+```python linenums="1"
+import paddle
+# 加载预训练模型
+all_params = paddle.load("models/ch_PP-OCRv3_rec_train/best_accuracy.pdparams")
+# 查看权重参数的keys
+print(all_params.keys())
+# 学生模型的权重提取
+s_params = {key[len("Student."):]: all_params[key] for key in all_params if "Student." in key}
+# 查看学生模型权重参数的keys
+print(s_params.keys())
+# 保存
+paddle.save(s_params, "models/ch_PP-OCRv3_rec_train/student.pdparams")
+```
+
+预训练模型下载完成后,我们使用[ch_PP-OCRv3_rec.yml](../configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml) 配置文件进行后续实验,在开始评估之前需要对配置文件中部分字段进行设置,具体如下:
+
+1. 模型存储和训练相关:
+ 1. Global.pretrained_model: 指向PP-OCRv3文本识别预训练模型地址
+2. 数据集相关
+ 1. Eval.dataset.data_dir:指向测试集图片存放目录
+ 2. Eval.dataset.label_file_list:指向测试集标注文件
+
+使用如下命令进行PP-OCRv3文本识别预训练模型的评估
+
+```bash linenums="1"
+python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=models/ch_PP-OCRv3_rec_train/student.pdparams \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/PPOCR \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/rec.txt]
+```
+
+如需获取已训练模型,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+- PaddleX官方交流频道:
+
+评估部分日志如下:
+
+```bash linenums="1"
+[2022/05/12 19:52:02] ppocr INFO: load pretrain successful from models/ch_PP-OCRv3_rec_train/best_accuracy
+eval model:: 100%|██████████████████████████████| 40/40 [00:15<00:00, 2.57it/s]
+[2022/05/12 19:52:17] ppocr INFO: metric eval ***************
+[2022/05/12 19:52:17] ppocr INFO: acc:0.0
+[2022/05/12 19:52:17] ppocr INFO: norm_edit_dis:0.8656084923002452
+[2022/05/12 19:52:17] ppocr INFO: Teacher_acc:0.000399520574511545
+[2022/05/12 19:52:17] ppocr INFO: Teacher_norm_edit_dis:0.8657902943394548
+[2022/05/12 19:52:17] ppocr INFO: fps:1443.1801978719905
+
+```
+
+使用预训练模型进行评估,指标如下所示:
+
+|方案|acc|
+|---|---|
+|PP-OCRv3中英文超轻量识别预训练模型直接预测|0%|
+
+从评估日志中可以看到,直接使用PP-OCRv3预训练模型进行评估,acc非常低,但是norm_edit_dis很高。因此,我们猜测是模型大部分文字识别是对的,只有少部分文字识别错误。使用如下命令进行infer查看模型的推理结果进行验证:
+
+```bash linenums="1"
+python tools/infer_rec.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=models/ch_PP-OCRv3_rec_train/student.pdparams \
+ Global.infer_img=/home/aistudio/data/CCPD2020/PPOCR/test/crop_imgs/0_0_0_3_32_30_31_30_30.jpg
+```
+
+输出部分日志如下:
+
+```bash linenums="1"
+[2022/05/01 08:51:57] ppocr INFO: train with paddle 2.2.2 and device CUDAPlace(0)
+W0501 08:51:57.127391 11326 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.0, Runtime API Version: 10.1
+W0501 08:51:57.132315 11326 device_context.cc:465] device: 0, cuDNN Version: 7.6.
+[2022/05/01 08:52:00] ppocr INFO: load pretrain successful from models/ch_PP-OCRv3_rec_train/student
+[2022/05/01 08:52:00] ppocr INFO: infer_img: /home/aistudio/data/CCPD2020/PPOCR/test/crop_imgs/0_0_3_32_30_31_30_30.jpg
+[2022/05/01 08:52:00] ppocr INFO: result: {"Student": {"label": "皖A·D86766", "score": 0.9552637934684753}, "Teacher": {"label": "皖A·D86766", "score": 0.9917094707489014}}
+[2022/05/01 08:52:00] ppocr INFO: success!
+```
+
+从infer结果可以看到,车牌中的文字大部分都识别正确,只是多识别出了一个`·`。针对这种情况,有如下两种方案:
+
+1. 直接通过后处理去掉多识别的`·`。
+2. 进行 fine-tune。
+
+#### 4.2.2 预训练模型直接预测+改动后处理
+
+直接通过后处理去掉多识别的`·`,在后处理的改动比较简单,只需在 [ppocr/postprocess/rec_postprocess.py](../ppocr/postprocess/rec_postprocess.py) 文件的76行添加如下代码:
+
+```python linenums="1"
+text = text.replace('·','')
+```
+
+改动前后指标对比:
+
+|方案|acc|
+|---|---|
+|PP-OCRv3中英文超轻量识别预训练模型直接预测|0.20%|
+|PP-OCRv3中英文超轻量识别预训练模型直接预测+后处理去掉多识别的`·`|90.97%|
+
+可以看到,去掉多余的`·`能大幅提高精度。
+
+#### 4.2.3 CCPD车牌数据集fine-tune
+
+##### 训练
+
+为了进行fine-tune训练,我们需要在配置文件中设置需要使用的预训练模型地址,学习率和数据集等参数。 具体如下:
+
+1. 模型存储和训练相关:
+ 1. Global.pretrained_model: 指向PP-OCRv3文本识别预训练模型地址
+ 2. Global.eval_batch_step: 模型多少step评估一次,这里设为从第0个step开始没隔45个step评估一次,45为一个epoch总的step数。
+2. 优化器相关
+ 1. Optimizer.lr.name: 学习率衰减器设为常量 Const
+ 2. Optimizer.lr.learning_rate: 做 fine-tune 实验,学习率需要设置的比较小,此处学习率设为配置文件中的0.05倍
+ 3. Optimizer.lr.warmup_epoch: warmup_epoch设为0
+3. 数据集相关
+ 1. Train.dataset.data_dir:指向训练集图片存放目录
+ 2. Train.dataset.label_file_list:指向训练集标注文件
+ 3. Eval.dataset.data_dir:指向测试集图片存放目录
+ 4. Eval.dataset.label_file_list:指向测试集标注文件
+
+使用如下命令启动 fine-tune
+
+```bash linenums="1"
+python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=models/ch_PP-OCRv3_rec_train/student.pdparams \
+ Global.save_model_dir=output/CCPD/rec/ \
+ Global.eval_batch_step="[0, 90]" \
+ Optimizer.lr.name=Const \
+ Optimizer.lr.learning_rate=0.0005 \
+ Optimizer.lr.warmup_epoch=0 \
+ Train.dataset.data_dir=/home/aistudio/data/CCPD2020/PPOCR \
+ Train.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/train/rec.txt] \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/PPOCR \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/rec.txt]
+```
+
+##### 评估
+
+训练完成后使用如下命令进行评估
+
+```bash linenums="1"
+python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=output/CCPD/rec/best_accuracy.pdparams \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/PPOCR \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/rec.txt]
+```
+
+使用预训练模型和CCPD车牌数据集fine-tune,指标分别如下:
+
+|方案| acc |
+|---|--------|
+|PP-OCRv3中英文超轻量识别预训练模型直接预测| 0.00% |
+|PP-OCRv3中英文超轻量识别预训练模型直接预测+后处理去掉多识别的`·`| 90.97% |
+|PP-OCRv3中英文超轻量识别预训练模型 fine-tune| 94.54% |
+
+可以看到进行fine-tune能显著提升车牌识别的效果。
+
+#### 4.2.4 CCPD车牌数据集fine-tune+量化训练
+
+此处采用 PaddleOCR 中提供好的[量化教程](../ppocr/model_compress/quantization.md)对模型进行量化训练。
+
+量化训练可通过如下命令启动:
+
+```bash linenums="1"
+python3.7 deploy/slim/quantization/quant.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=output/CCPD/rec/best_accuracy.pdparams \
+ Global.save_model_dir=output/CCPD/rec_quant/ \
+ Global.eval_batch_step="[0, 90]" \
+ Optimizer.lr.name=Const \
+ Optimizer.lr.learning_rate=0.0005 \
+ Optimizer.lr.warmup_epoch=0 \
+ Train.dataset.data_dir=/home/aistudio/data/CCPD2020/PPOCR \
+ Train.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/train/rec.txt] \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/PPOCR \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/rec.txt]
+```
+
+量化后指标对比如下
+
+|方案| acc | 模型大小 | 预测速度(lite) |
+|---|--------|-------|------------|
+|PP-OCRv3中英文超轻量识别预训练模型 fine-tune| 94.54% | 10.3M | 4.2ms |
+|PP-OCRv3中英文超轻量识别预训练模型 fine-tune + 量化| 93.40% | 4.8M | 1.8ms |
+
+可以看到量化后能降低模型体积53%并且推理速度提升57%,但是由于识别数据过少,量化带来了1%的精度下降。
+
+速度测试基于[PaddleOCR lite教程](../ppocr/infer_deploy/lite.md)完成。
+
+#### 4.2.5 模型导出
+
+使用如下命令可以将训练好的模型进行导出。
+
+非量化模型
+
+```bash linenums="1"
+python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=output/CCPD/rec/best_accuracy.pdparams \
+ Global.save_inference_dir=output/CCPD/rec/infer
+```
+
+量化模型
+
+```bash linenums="1"
+python deploy/slim/quantization/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=output/CCPD/rec_quant/best_accuracy.pdparams \
+ Global.save_inference_dir=output/CCPD/rec_quant/infer
+```
+
+### 4.3 计算End2End指标
+
+端到端指标可通过 [PaddleOCR内置脚本](../tools/end2end/readme.md) 进行计算,具体步骤如下:
+
+#### 1. 导出模型
+
+通过如下命令进行模型的导出。注意,量化模型导出时,需要配置eval数据集
+
+```bash linenums="1"
+# 检测模型
+
+# 预训练模型
+python tools/export_model.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=models/ch_PP-OCRv3_det_distill_train/student.pdparams \
+ Global.save_inference_dir=output/ch_PP-OCRv3_det_distill_train/infer
+
+# 非量化模型
+python tools/export_model.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=output/CCPD/det/best_accuracy.pdparams \
+ Global.save_inference_dir=output/CCPD/det/infer
+
+# 量化模型
+python deploy/slim/quantization/export_model.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o \
+ Global.pretrained_model=output/CCPD/det_quant/best_accuracy.pdparams \
+ Global.save_inference_dir=output/CCPD/det_quant/infer \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/ccpd_green \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/det.txt] \
+ Eval.loader.num_workers=0
+
+# 识别模型
+
+# 预训练模型
+python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=models/ch_PP-OCRv3_rec_train/student.pdparams \
+ Global.save_inference_dir=output/ch_PP-OCRv3_rec_train/infer
+
+# 非量化模型
+python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=output/CCPD/rec/best_accuracy.pdparams \
+ Global.save_inference_dir=output/CCPD/rec/infer
+
+# 量化模型
+python deploy/slim/quantization/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec.yml -o \
+ Global.pretrained_model=output/CCPD/rec_quant/best_accuracy.pdparams \
+ Global.save_inference_dir=output/CCPD/rec_quant/infer \
+ Eval.dataset.data_dir=/home/aistudio/data/CCPD2020/PPOCR \
+ Eval.dataset.label_file_list=[/home/aistudio/data/CCPD2020/PPOCR/test/rec.txt]
+```
+
+#### 2. 用导出的模型对测试集进行预测
+
+此处,分别使用PP-OCRv3预训练模型,fintune模型和量化模型对测试集的所有图像进行预测,命令如下:
+
+```bash linenums="1"
+# PP-OCRv3中英文超轻量检测预训练模型,PP-OCRv3中英文超轻量识别预训练模型
+python3 tools/infer/predict_system.py --det_model_dir=models/ch_PP-OCRv3_det_distill_train/infer --rec_model_dir=models/ch_PP-OCRv3_rec_train/infer --det_limit_side_len=736 --det_limit_type=min --image_dir=/home/aistudio/data/CCPD2020/ccpd_green/test/ --draw_img_save_dir=infer/pretrain --use_dilation=true
+
+# PP-OCRv3中英文超轻量检测预训练模型+fine-tune,PP-OCRv3中英文超轻量识别预训练模型+fine-tune
+python3 tools/infer/predict_system.py --det_model_dir=output/CCPD/det/infer --rec_model_dir=output/CCPD/rec/infer --det_limit_side_len=736 --det_limit_type=min --image_dir=/home/aistudio/data/CCPD2020/ccpd_green/test/ --draw_img_save_dir=infer/fine-tune --use_dilation=true
+
+# PP-OCRv3中英文超轻量检测预训练模型 fine-tune +量化,PP-OCRv3中英文超轻量识别预训练模型 fine-tune +量化 结果转换和评估
+python3 tools/infer/predict_system.py --det_model_dir=output/CCPD/det_quant/infer --rec_model_dir=output/CCPD/rec_quant/infer --det_limit_side_len=736 --det_limit_type=min --image_dir=/home/aistudio/data/CCPD2020/ccpd_green/test/ --draw_img_save_dir=infer/quant --use_dilation=true
+```
+
+#### 3. 转换label并计算指标
+
+将gt和上一步保存的预测结果转换为端对端评测需要的数据格式,并根据转换后的数据进行端到端指标计算
+
+```bash linenums="1"
+python3 tools/end2end/convert_ppocr_label.py --mode=gt --label_path=/home/aistudio/data/CCPD2020/PPOCR/test/det.txt --save_folder=end2end/gt
+
+# PP-OCRv3中英文超轻量检测预训练模型,PP-OCRv3中英文超轻量识别预训练模型 结果转换和评估
+python3 tools/end2end/convert_ppocr_label.py --mode=pred --label_path=infer/pretrain/system_results.txt --save_folder=end2end/pretrain
+python3 tools/end2end/eval_end2end.py end2end/gt end2end/pretrain
+
+# PP-OCRv3中英文超轻量检测预训练模型,PP-OCRv3中英文超轻量识别预训练模型+后处理去掉多识别的`·` 结果转换和评估
+# 需手动修改后处理函数
+python3 tools/end2end/convert_ppocr_label.py --mode=pred --label_path=infer/post/system_results.txt --save_folder=end2end/post
+python3 tools/end2end/eval_end2end.py end2end/gt end2end/post
+
+# PP-OCRv3中英文超轻量检测预训练模型 fine-tune,PP-OCRv3中英文超轻量识别预训练模型 fine-tune 结果转换和评估
+python3 tools/end2end/convert_ppocr_label.py --mode=pred --label_path=infer/fine-tune/system_results.txt --save_folder=end2end/fine-tune
+python3 tools/end2end/eval_end2end.py end2end/gt end2end/fine-tune
+
+# PP-OCRv3中英文超轻量检测预训练模型 fine-tune +量化,PP-OCRv3中英文超轻量识别预训练模型 fine-tune +量化 结果转换和评估
+python3 tools/end2end/convert_ppocr_label.py --mode=pred --label_path=infer/quant/system_results.txt --save_folder=end2end/quant
+python3 tools/end2end/eval_end2end.py end2end/gt end2end/quant
+```
+
+日志如下:
+
+```bash linenums="1"
+The convert label saved in end2end/gt
+The convert label saved in end2end/pretrain
+start testing...
+hit, dt_count, gt_count 2 5988 5006
+character_acc: 70.42%
+avg_edit_dist_field: 2.37
+avg_edit_dist_img: 2.37
+precision: 0.03%
+recall: 0.04%
+fmeasure: 0.04%
+The convert label saved in end2end/post
+start testing...
+hit, dt_count, gt_count 4224 5988 5006
+character_acc: 81.59%
+avg_edit_dist_field: 1.47
+avg_edit_dist_img: 1.47
+precision: 70.54%
+recall: 84.38%
+fmeasure: 76.84%
+The convert label saved in end2end/fine-tune
+start testing...
+hit, dt_count, gt_count 4286 4898 5006
+character_acc: 94.16%
+avg_edit_dist_field: 0.47
+avg_edit_dist_img: 0.47
+precision: 87.51%
+recall: 85.62%
+fmeasure: 86.55%
+The convert label saved in end2end/quant
+start testing...
+hit, dt_count, gt_count 4349 4951 5006
+character_acc: 94.13%
+avg_edit_dist_field: 0.47
+avg_edit_dist_img: 0.47
+precision: 87.84%
+recall: 86.88%
+fmeasure: 87.36%
+```
+
+各个方案端到端指标如下:
+
+|模型| 指标 |
+|---|--------|
+|PP-OCRv3中英文超轻量检测预训练模型 PP-OCRv3中英文超轻量识别预训练模型| 0.04% |
+|PP-OCRv3中英文超轻量检测预训练模型 PP-OCRv3中英文超轻量识别预训练模型 + 后处理去掉多识别的`·`| 78.27% |
+|PP-OCRv3中英文超轻量检测预训练模型+fine-tune PP-OCRv3中英文超轻量识别预训练模型+fine-tune| 87.14% |
+|PP-OCRv3中英文超轻量检测预训练模型+fine-tune+量化 PP-OCRv3中英文超轻量识别预训练模型+fine-tune+量化| 88.00% |
+
+从结果中可以看到对预训练模型不做修改,只根据场景下的具体情况进行后处理的修改就能大幅提升端到端指标到78.27%,在CCPD数据集上进行 fine-tune 后指标进一步提升到87.14%, 在经过量化训练之后,由于检测模型的recall变高,指标进一步提升到88%。但是这个结果仍旧不符合检测模型+识别模型的真实性能(99%*94%=93%),因此我们需要对 base case 进行具体分析。
+
+在之前的端到端预测结果中,可以看到很多不符合车牌标注的文字被识别出来, 因此可以进行简单的过滤来提升precision
+
+为了快速评估,我们在 `tools/end2end/convert_ppocr_label.py` 脚本的 58 行加入如下代码,对非8个字符的结果进行过滤
+
+```python linenums="1"
+if len(txt) != 8: # 车牌字符串长度为8
+ continue
+```
+
+此外,通过可视化box可以发现有很多框都是竖直翻转之后的框,并且没有完全框住车牌边界,因此需要进行框的竖直翻转以及轻微扩大,示意图如下:
+
+![](./images/59ab0411c8eb4dfd917fb2b6e5b69a17ee7ca48351444aec9ac6104b79ff1028.jpg)
+
+修改前后个方案指标对比如下:
+
+各个方案端到端指标如下:
+
+|模型|base|A:识别结果过滤|B:use_dilation|C:flip_box|best|
+|---|---|---|---|---|---|
+|PP-OCRv3中英文超轻量检测预训练模型 PP-OCRv3中英文超轻量识别预训练模型|0.04%|0.08%|0.02%|0.05%|0.00%(A)|
+|PP-OCRv3中英文超轻量检测预训练模型 PP-OCRv3中英文超轻量识别预训练模型 + 后处理去掉多识别的`·`|78.27%|90.84%|78.61%|79.43%|91.66%(A+B+C)|
+|PP-OCRv3中英文超轻量检测预训练模型+fine-tune PP-OCRv3中英文超轻量识别预训练模型+fine-tune|87.14%|90.40%|87.66%|89.98%|92.50%(A+B+C)|
+|PP-OCRv3中英文超轻量检测预训练模型+fine-tune+量化 PP-OCRv3中英文超轻量识别预训练模型+fine-tune+量化|88.00%|90.54%|88.50%|89.46%|92.02%(A+B+C)|
+
+从结果中可以看到对预训练模型不做修改,只根据场景下的具体情况进行后处理的修改就能大幅提升端到端指标到91.66%,在CCPD数据集上进行 fine-tune 后指标进一步提升到92.5%, 在经过量化训练之后,指标变为92.02%。
+
+### 4.4 部署
+
+#### 基于 Paddle Inference 的python推理
+
+检测模型和识别模型分别 fine-tune 并导出为inference模型之后,可以使用如下命令基于 Paddle Inference 进行端到端推理并对结果进行可视化。
+
+```bash linenums="1"
+python tools/infer/predict_system.py \
+ --det_model_dir=output/CCPD/det/infer/ \
+ --rec_model_dir=output/CCPD/rec/infer/ \
+ --image_dir="/home/aistudio/data/CCPD2020/ccpd_green/test/04131106321839081-92_258-159&509_530&611-527&611_172&599_159&509_530&525-0_0_3_32_30_31_30_30-109-106.jpg" \
+ --rec_image_shape=3,48,320
+```
+
+推理结果如下
+
+![](./images/76b6a0939c2c4cf49039b6563c4b28e241e11285d7464e799e81c58c0f7707a7-20240704185943337.png)
+
+#### 端侧部署
+
+端侧部署我们采用基于 PaddleLite 的 cpp 推理。Paddle Lite是飞桨轻量化推理引擎,为手机、IOT端提供高效推理能力,并广泛整合跨平台硬件,为端侧部署及应用落地问题提供轻量化的部署方案。具体可参考 [PaddleOCR lite教程](../ppocr/infer_deploy/lite.md)
+
+### 4.5 实验总结
+
+我们分别使用PP-OCRv3中英文超轻量预训练模型在车牌数据集上进行了直接评估和 fine-tune 和 fine-tune +量化3种方案的实验,并基于[PaddleOCR lite教程](../ppocr/infer_deploy/lite.md)进行了速度测试,指标对比如下:
+
+- 检测
+
+|方案|hmeans| 模型大小 | 预测速度(lite) |
+|---|---|------|------------|
+|PP-OCRv3中英文超轻量检测预训练模型直接预测|76.12%|2.5M| 233ms |
+|PP-OCRv3中英文超轻量检测预训练模型 fine-tune|99.00%| 2.5M | 233ms |
+|PP-OCRv3中英文超轻量检测预训练模型 fine-tune + 量化|98.91%| 1.0M | 189ms |
+
+- 识别
+
+|方案| acc | 模型大小 | 预测速度(lite) |
+|---|--------|-------|------------|
+|PP-OCRv3中英文超轻量识别预训练模型直接预测| 0.00% |10.3M| 4.2ms |
+|PP-OCRv3中英文超轻量识别预训练模型直接预测+后处理去掉多识别的`·`| 90.97% |10.3M| 4.2ms |
+|PP-OCRv3中英文超轻量识别预训练模型 fine-tune| 94.54% | 10.3M | 4.2ms |
+|PP-OCRv3中英文超轻量识别预训练模型 fine-tune + 量化| 93.40% | 4.8M | 1.8ms |
+
+- 端到端指标如下:
+
+|方案|fmeasure|模型大小|预测速度(lite) |
+|---|---|---|---|
+|PP-OCRv3中英文超轻量检测预训练模型 PP-OCRv3中英文超轻量识别预训练模型|0.08%|12.8M|298ms|
+|PP-OCRv3中英文超轻量检测预训练模型 PP-OCRv3中英文超轻量识别预训练模型 + 后处理去掉多识别的`·`|91.66%|12.8M|298ms|
+|PP-OCRv3中英文超轻量检测预训练模型+fine-tune PP-OCRv3中英文超轻量识别预训练模型+fine-tune|92.50%|12.8M|298ms|
+|PP-OCRv3中英文超轻量检测预训练模型+fine-tune+量化 PP-OCRv3中英文超轻量识别预训练模型+fine-tune+量化|92.02%|5.80M|224ms|
+
+## **结论**
+
+PP-OCRv3的检测模型在未经过fine-tune的情况下,在车牌数据集上也有一定的精度,经过 fine-tune 后能够极大的提升检测效果,精度达到99%。在使用量化训练后检测模型的精度几乎无损,并且模型大小压缩60%。
+
+PP-OCRv3的识别模型在未经过fine-tune的情况下,在车牌数据集上精度为0,但是经过分析可以知道,模型大部分字符都预测正确,但是会多预测一个特殊字符,去掉这个特殊字符后,精度达到90%。PP-OCRv3识别模型在经过 fine-tune 后识别精度进一步提升,达到94.4%。在使用量化训练后识别模型大小压缩53%,但是由于数据量多少,带来了1%的精度损失。
+
+从端到端结果中可以看到对预训练模型不做修改,只根据场景下的具体情况进行后处理的修改就能大幅提升端到端指标到91.66%,在CCPD数据集上进行 fine-tune 后指标进一步提升到92.5%, 在经过量化训练之后,指标轻微下降到92.02%但模型大小降低54%。
diff --git "a/docs/applications/\351\253\230\347\262\276\345\272\246\344\270\255\346\226\207\350\257\206\345\210\253\346\250\241\345\236\213.md" "b/docs/applications/\351\253\230\347\262\276\345\272\246\344\270\255\346\226\207\350\257\206\345\210\253\346\250\241\345\236\213.md"
new file mode 100644
index 0000000000..11d059190a
--- /dev/null
+++ "b/docs/applications/\351\253\230\347\262\276\345\272\246\344\270\255\346\226\207\350\257\206\345\210\253\346\250\241\345\236\213.md"
@@ -0,0 +1,113 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# 高精度中文场景文本识别模型SVTR
+
+## 1. 简介
+
+PP-OCRv3是百度开源的超轻量级场景文本检测识别模型库,其中超轻量的场景中文识别模型SVTR_LCNet使用了SVTR算法结构。为了保证速度,SVTR_LCNet将SVTR模型的Local Blocks替换为LCNet,使用两层Global Blocks。在中文场景中,PP-OCRv3识别主要使用如下优化策略([详细技术报告](../ppocr/blog/PP-OCRv3_introduction.md)):
+
+- GTC:Attention指导CTC训练策略;
+- TextConAug:挖掘文字上下文信息的数据增广策略;
+- TextRotNet:自监督的预训练模型;
+- UDML:联合互学习策略;
+- UIM:无标注数据挖掘方案。
+
+其中 *UIM:无标注数据挖掘方案* 使用了高精度的SVTR中文模型进行无标注文件的刷库,该模型在PP-OCRv3识别的数据集上训练,精度对比如下表。
+
+|中文识别算法|模型|UIM|精度|
+| --- | --- | --- |--- |
+|PP-OCRv3|SVTR_LCNet| w/o |78.40%|
+|PP-OCRv3|SVTR_LCNet| w |79.40%|
+|SVTR|SVTR-Tiny|-|82.50%|
+
+aistudio项目链接: [高精度中文场景文本识别模型SVTR](https://aistudio.baidu.com/aistudio/projectdetail/4263032)
+
+## 2. SVTR中文模型使用
+
+### 环境准备
+
+本任务基于Aistudio完成, 具体环境如下:
+
+- 操作系统: Linux
+- PaddlePaddle: 2.3
+- PaddleOCR: dygraph
+
+下载PaddleOCR代码
+
+```bash linenums="1"
+git clone -b dygraph https://github.com/PaddlePaddle/PaddleOCR
+```
+
+安装依赖库
+
+```bash linenums="1"
+pip install -r PaddleOCR/requirements.txt -i https://mirror.baidu.com/pypi/simple
+```
+
+### 快速使用
+
+获取SVTR中文模型文件,请加入PaddleX官方交流频道,获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料)
+
+- PaddleX官方交流频道:
+
+```bash linenums="1"
+# 解压模型文件
+tar xf svtr_ch_high_accuracy.tar
+```
+
+预测中文文本,以下图为例:
+![](../doc/imgs_words/ch/word_1.jpg)
+
+预测命令:
+
+```bash linenums="1"
+# CPU预测
+python tools/infer_rec.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.infer_img=./doc/imgs_words/ch/word_1.jpg Global.use_gpu=False
+
+# GPU预测
+#python tools/infer_rec.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.infer_img=./doc/imgs_words/ch/word_1.jpg Global.use_gpu=True
+```
+
+可以看到最后打印结果为
+
+- result: 韩国小馆 0.9853458404541016
+
+0.9853458404541016为预测置信度。
+
+### 推理模型导出与预测
+
+inference 模型(paddle.jit.save保存的模型) 一般是模型训练,把模型结构和模型参数保存在文件中的固化模型,多用于预测部署场景。 训练过程中保存的模型是checkpoints模型,保存的只有模型的参数,多用于恢复训练等。 与checkpoints模型相比,inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。
+
+运行识别模型转inference模型命令,如下:
+
+```bash linenums="1"
+python tools/export_model.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.save_inference_dir=./inference/svtr_ch
+```
+
+转换成功后,在目录下有三个文件:
+
+```bash linenums="1"
+inference/svtr_ch/
+ ├── inference.pdiparams # 识别inference模型的参数文件
+ ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略
+ └── inference.pdmodel # 识别inference模型的program文件
+```
+
+inference模型预测,命令如下:
+
+```bash linenums="1"
+# CPU预测
+python3 tools/infer/predict_rec.py --image_dir="./doc/imgs_words/ch/word_1.jpg" --rec_algorithm='SVTR' --rec_model_dir=./inference/svtr_ch/ --rec_image_shape='3, 32, 320' --rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt --use_gpu=False
+
+# GPU预测
+#python3 tools/infer/predict_rec.py --image_dir="./doc/imgs_words/ch/word_1.jpg" --rec_algorithm='SVTR' --rec_model_dir=./inference/svtr_ch/ --rec_image_shape='3, 32, 320' --rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt --use_gpu=True
+```
+
+**注意**
+
+- 使用SVTR算法时,需要指定--rec_algorithm='SVTR'
+- 如果使用自定义字典训练的模型,需要将--rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt修改为自定义的字典
+- --rec_image_shape='3, 32, 320' 该参数不能去掉
diff --git a/docs/community/code_and_doc.en.md b/docs/community/code_and_doc.en.md
new file mode 100644
index 0000000000..ad846752d2
--- /dev/null
+++ b/docs/community/code_and_doc.en.md
@@ -0,0 +1,336 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# Appendix
+
+ This appendix contains python, document specifications and Pull Request process.
+
+## Appendix 1:Python Code Specification
+
+The Python code of PaddleOCR follows [PEP8 Specification]( https://www.python.org/dev/peps/pep-0008/ ), some of the key concerns include the following
+
+- Space
+
+ - Spaces should be added after commas, semicolons, colons, not before them
+
+ ```python linenums="1"
+ # true:
+ print(x, y)
+
+ # false:
+ print(x , y)
+ ```
+
+ - When specifying a keyword parameter or default parameter value in a function, do not use spaces on both sides of it
+
+ ```python linenums="1"
+ # true:
+ def complex(real, imag=0.0)
+ # false:
+ def complex(real, imag = 0.0)
+ ```
+
+- comment
+
+ - Inline comments: inline comments are indicated by the` # `sign. Two spaces should be left between code and` # `, and one space should be left between` # `and comments, for example
+
+ ```python linenums="1"
+ x = x + 1 # Compensate for border
+ ```
+
+ - Functions and methods: The definition of each function should include the following:
+
+ - Function description: Utility, input and output of function
+ - Args: Name and description of each parameter
+ - Returns: The meaning and type of the return value
+
+ ```python linenums="1"
+ def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
+ """Fetches rows from a Bigtable.
+
+ Retrieves rows pertaining to the given keys from the Table instance
+ represented by big_table. Silly things may happen if
+ other_silly_variable is not None.
+
+ Args:
+ big_table: An open Bigtable Table instance.
+ keys: A sequence of strings representing the key of each table row
+ to fetch.
+ other_silly_variable: Another optional variable, that has a much
+ longer name than the other args, and which does nothing.
+
+ Returns:
+ A dict mapping keys to the corresponding table row data
+ fetched. Each row is represented as a tuple of strings. For
+ example:
+
+ {'Serak': ('Rigel VII', 'Preparer'),
+ 'Zim': ('Irk', 'Invader'),
+ 'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+ If a key from the keys argument is missing from the dictionary,
+ then that row was not found in the table.
+ """
+ pass
+ ```
+
+## Appendix 2: Document Specification
+
+### 2.1 Overall Description
+
+- Document Location: If you add new features to your original Markdown file, please **Do not re-create** a new file. If you don't know where to add it, you can first PR the code and then ask the official in commit.
+
+- New Markdown Document Name: Describe the content of the document in English, typically a combination of lowercase letters and underscores, such as `add_New_Algorithm.md`
+
+- New Markdown Document Format: Catalog - Body - FAQ
+
+ > The directory generation method can use [this site](https://ecotrust-canada.github.io/markdown-toc/ ) Automatically extract directories after copying MD contents, and then add `
+
+- English and Chinese: Any changes or additions to the document need to be made in both Chinese and English documents.
+
+### 2.2 Format Specification
+
+- Title format: The document title format follows the format of: Arabic decimal point combination-space-title (for example, `2.1 XXXX`, `2.XXXX`)
+
+- Code block: Displays code in code block format that needs to be run, describing the meaning of command parameters before the code block. for example:
+
+ > Pipeline of detection + direction Classify + recognition: Vertical text can be recognized after set direction classifier parameters`--use_angle_cls true`.
+ >
+ > ```bash linenums="1"
+ > paddleocr --image_dir ./imgs/11.jpg --use_angle_cls true
+ > ```
+
+- Variable Rrferences: If code variables or command parameters are referenced in line, they need to be represented in line code, for example, above `--use_angle_cls true` with one space in front and one space in back
+
+- Uniform naming: e.g. PP-OCRv2, PP-OCR mobile, `paddleocr` whl package, PPOCRLabel, Paddle Lite, etc.
+
+- Supplementary notes: Supplementary notes by reference format `>`.
+
+- Picture: If a picture is added to the description document, specify the naming of the picture (describing its content) and add the picture under `doc/`.
+
+- Title: Capitalize the first letter of each word in the title.
+
+## Appendix 3: Pull Request Description
+
+### 3.1 PaddleOCR Branch Description
+
+PaddleOCR will maintain two branches in the future, one for each:
+
+- release/x.x family branch: stable release version branch, also the default branch. PaddleOCR releases a new release branch based on feature updates and adapts to the release version of Paddle. As versions iterate, more and more release/x.x family branches are maintained by default with the latest version of the release branch.
+- dygraph branch: For the development branch, adapts the dygraph version of the Paddle dynamic graph to primarily develop new functionality. If you need to redevelop, choose the dygraph branch. To ensure that the dygraph branch pulls out the release/x.x branch when needed, the code for the dygraph branch can only use the valid API in the latest release branch of Paddle. That is, if a new API has been developed in the Paddle dygraph branch but has not yet appeared in the release branch code, do not use it in Paddle OCR. In addition, performance optimization, parameter tuning, policy updates that do not involve API can be developed normally.
+
+The historical branch of PaddleOCR will no longer be maintained in the future. These branches will continue to be maintained, considering that some of you may still be using them:
+
+Develop branch: This branch was used for the development and testing of static diagrams and is currently compatible with version >=1.7. If you have special needs, you can also use this branch to accommodate older versions of Paddle, but you won't update your code until you fix the bug.
+
+PaddleOCR welcomes you to actively contribute code to repo. Here are some basic processes for contributing code.
+
+### 3.2 PaddleOCR Code Submission Process And Specification
+
+If you are familiar with Git use, you can jump directly to [Some Conventions For Submitting Code in 3.2.10](#Some_conventions_for_submitting_code)
+
+#### 3.2.1 Create Your `Remote Repo`
+
+In PaddleOCR [GitHub Home]( https://github.com/PaddlePaddle/PaddleOCR ) Click the `Fork` button in the upper left corner to create a `remote repo`in your personal directory, such as `https://github.com/ {your_name}/PaddleOCR`.
+
+ ![banner](./images/banner.png)
+
+Clone `Remote repo`
+
+ ```bash linenums="1"
+ # pull code of develop branch
+ git clone https://github.com/{your_name}/PaddleOCR.git -b dygraph
+ cd PaddleOCR
+ ```
+
+Clone failures are mostly due to network reasons, try again later or configure the proxy
+
+#### 3.2.2 Login And Connect Using Token
+
+Start by viewing the information for the current `remote repo`.
+
+```bash linenums="1"
+git remote -v
+# origin https://github.com/{your_name}/PaddleOCR.git (fetch)
+# origin https://github.com/{your_name}/PaddleOCR.git (push)
+```
+
+Only the information of the clone `remote repo`, i.e. the PaddleOCR under your username, is available. Due to the change in Github's login method, you need to reconfigure the `remote repo` address by means of a Token. The token is generated as follows:
+
+1. Find Personal Access Tokens: Click on your avatar in the upper right corner of the Github page and choose Settings --> Developer settings --> Personal access tokens,
+
+2. Click Generate new token: Fill in the token name in Note, such as 'paddle'. In Select scopes, select repo (required), admin:repo_hook, delete_repo, etc. You can check them according to your needs. Then click Generate token to generate the token, and finally copy the generated token.
+
+Delete the original origin configuration
+
+```bash linenums="1"
+git remote rm origin
+```
+
+Change the remote branch to `https://oauth2:{token}@github.com/{your_name}/PaddleOCR.git`. For example, if the token value is 12345 and your user name is PPOCR, run the following command
+
+```bash linenums="1"
+git remote add origin https://oauth2:12345@github.com/PPOCR/PaddleOCR.git
+```
+
+This establishes a connection to our own `remote repo`. Next we create a remote host of the original PaddleOCR repo, named upstream.
+
+```bash linenums="1"
+git remote add upstream https://github.com/PaddlePaddle/PaddleOCR.git
+```
+
+Use `git remote -v` to view current `remote warehouse` information, output as follows, found to include two origin and two upstream of `remote repo` .
+
+```bash linenums="1"
+origin https://github.com/{your_name}/PaddleOCR.git (fetch)
+origin https://github.com/{your_name}/PaddleOCR.git (push)
+upstream https://github.com/PaddlePaddle/PaddleOCR.git (fetch)
+upstream https://github.com/PaddlePaddle/PaddleOCR.git (push)
+```
+
+This is mainly to keep the local repository up to date when subsequent pull request (PR) submissions are made.
+
+#### 3.2.3 Create Local Branch
+
+First get the latest code of upstream, then create a new_branch branch based on the dygraph of the upstream repo (upstream).
+
+```bash linenums="1"
+git fetch upstream
+git checkout -b new_branch upstream/dygraph
+```
+
+> If for a newly forked PaddleOCR project, the user's remote repo (origin) has the same branch updates as the upstream repository (upstream), you can also create a new local branch based on the default branch of the origin repo or a specified branch with the following command
+>
+> ```bash linenums="1"
+> # Create new_branch branch on user remote repo (origin) based on develop branch
+> git checkout -b new_branch origin/develop
+> # Create new_branch branch based on upstream remote repo develop branch
+> # If you need to create a new branch from upstream,
+> # you need to first use git fetch upstream to get upstream code
+> git checkout -b new_branch upstream/develop
+> ```
+
+The final switch to the new branch is displayed with the following output information.
+
+Branch new_branch set up to track remote branch develop from upstream.
+Switched to a new branch 'new_branch'
+
+After switching branches, file changes can be made on this branch
+
+#### 3.2.4 Use Pre-Commit Hook
+
+Paddle developers use the pre-commit tool to manage Git pre-submit hooks. It helps us format the source code (C++, Python) and automatically check for basic things (such as having only one EOL per file, not adding large files to Git) before committing it.
+
+The pre-commit test is part of the unit test in Travis-CI. PR that does not satisfy the hook cannot be submitted to PaddleOCR. Install it first and run it in the current directory:
+
+```bash linenums="1"
+pip install pre-commit
+pre-commit install
+```
+
+> 1. Paddle uses clang-format to adjust the C/C++ source code format. Make sure the `clang-format` version is above 3.8.
+>
+> 2. Yapf installed through pip install pre-commit is slightly different from conda install-c conda-forge pre-commit, and PaddleOCR developers use `pip install pre-commit`.
+
+#### 3.2.5 Modify And Submit Code
+
+If you make some changes on `README.Md` on PaddleOCR, you can view the changed file through `git status`, and then add the changed file using `git add`。
+
+```bash linenums="1"
+git status # View change files
+git add README.md
+pre-commit
+```
+
+Repeat these steps until the pre-comit format check does not error. As shown below.
+
+![img](./images/precommit_pass.png)
+
+Use the following command to complete the submission.
+
+```bash linenums="1"
+git commit -m "your commit info"
+```
+
+#### 3.2.6 Keep Local Repo Up To Date
+
+Get the latest code for upstream and update the current branch. Here the upstream comes from section 2.2, `Connecting to a remote repo`.
+
+```bash linenums="1"
+git fetch upstream
+# If you want to commit to another branch, you need to pull code from another branch of upstream, here is develop
+git pull upstream develop
+```
+
+#### 3.2.7 Push To Remote Repo
+
+```bash linenums="1"
+git push origin new_branch
+```
+
+#### 3.2.7 Submit Pull Request
+
+Click the new pull request to select the local branch and the target branch, as shown in the following figure. In the description of PR, fill in the functions completed by the PR. Next, wait for review, and if you need to modify something, update the corresponding branch in origin with the steps above.
+
+![banner](./images/pr.png)
+
+#### 3.2.8 Sign CLA Agreement And Pass Unit Tests
+
+Signing the CLA When submitting a Pull Request to PaddlePaddle for the first time, you need to sign a CLA (Contributor License Agreement) agreement to ensure that your code can be incorporated as follows:
+
+1. Please check the Check section in PR, find the license/cla, and click on the right detail to enter the CLA website
+
+2. Click Sign in with GitHub to agree on the CLA website and when clicked, it will jump back to your Pull Request page
+
+#### 3.2.9 Delete Branch
+
+- Remove remote branch
+
+ After PR is merged into the main repo, we can delete the branch of the remote repofrom the PR page.
+ You can also use `git push origin:branch name` to delete remote branches, such as:
+
+ ```bash linenums="1"
+ git push origin :new_branch
+ ```
+
+- Delete local branch
+
+ ```bash linenums="1"
+ # Switch to the development branch, otherwise the current branch cannot be deleted
+ git checkout develop
+
+ # Delete new_ Branch Branch
+ git branch -D new_branch
+ ```
+
+#### 3.2.10 Some Conventions For Submitting Code
+
+In order for official maintainers to better focus on the code itself when reviewing it, please follow the following conventions each time you submit your code:
+
+1)Please ensure that the unit tests in Travis-CI pass smoothly. If not, indicate that there is a problem with the submitted code, and the official maintainer generally does not review it.
+
+2)Before submitting a Pull Request.
+
+- Note the number of commits.
+
+ Reason: If you only modify one file and submit more than a dozen commits, each commit will only make a few modifications, which can be very confusing to the reviewer. The reviewer needs to look at each commit individually to see what changes have been made, and does not exclude the fact that changes between commits overlap each other.
+
+ Suggestion: Keep as few commits as possible each time you submit, and supplement your last commit with git commit --amend. For multiple commits that have been Push to a remote warehouse, you can refer to [squash commits after push](https://stackoverflow.com/questions/5667884/how-to-squash-commits-in-git-after-they-have-been-pushed ).
+
+- Note the name of each commit: it should reflect the content of the current commit, not be too arbitrary.
+
+3) If you have solved a problem, add in the first comment box of the Pull Request:fix #issue_number,This will automatically close the corresponding Issue when the Pull Request is merged. Key words include:close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved,please choose the right vocabulary. Detailed reference [Closing issues via commit messages](https://help.github.com/articles/closing-issues-via-commit-messages).
+
+In addition, in response to the reviewer's comments, you are requested to abide by the following conventions:
+
+1) Each review comment from an official maintainer would like a response, which would better enhance the contribution of the open source community.
+
+- If you agree to the review opinion and modify it accordingly, give a simple Done.
+- If you disagree with the review, please give your own reasons for refuting.
+
+2)If there are many reviews:
+
+- Please give an overview of the changes.
+- Please reply with `start a review', not directly. The reason is that each reply sends an e-mail message, which can cause a mail disaster.
diff --git a/docs/community/code_and_doc.md b/docs/community/code_and_doc.md
new file mode 100644
index 0000000000..c81a4cd322
--- /dev/null
+++ b/docs/community/code_and_doc.md
@@ -0,0 +1,327 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# 附录
+
+本附录包含了Python、文档规范以及Pull Request流程,请各位开发者遵循相关内容
+
+## 附录1:Python代码规范
+
+PaddleOCR的Python代码遵循 [PEP8规范](https://www.python.org/dev/peps/pep-0008/),其中一些关注的重点包括如下内容
+
+- 空格
+
+ - 空格应该加在逗号、分号、冒号后,而非他们的前面
+
+ ```python linenums="1"
+ # 正确:
+ print(x, y)
+
+ # 错误:
+ print(x , y)
+ ```
+
+ - 在函数中指定关键字参数或默认参数值时, 不要在其两侧使用空格
+
+ ```python linenums="1"
+ # 正确:
+ def complex(real, imag=0.0)
+ # 错误:
+ def complex(real, imag = 0.0)
+ ```
+
+- 注释
+
+ - 行内注释:行内注释使用 `#` 号表示,在代码与 `#` 之间需要空两个空格, `#` 与注释之间应当空一个空格,例如
+
+ ```python linenums="1"
+ x = x + 1 # Compensate for border
+ ```
+
+ - 函数和方法:每个函数的定义后的描述应该包括以下内容:
+
+ - 函数描述:函数的作用,输入输出的
+ - Args:每个参数的名字以及对该参数的描述
+ - Returns:返回值的含义和类型
+
+ ```python linenums="1"
+ def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
+ """Fetches rows from a Bigtable.
+
+ Retrieves rows pertaining to the given keys from the Table instance
+ represented by big_table. Silly things may happen if
+ other_silly_variable is not None.
+
+ Args:
+ big_table: An open Bigtable Table instance.
+ keys: A sequence of strings representing the key of each table row
+ to fetch.
+ other_silly_variable: Another optional variable, that has a much
+ longer name than the other args, and which does nothing.
+
+ Returns:
+ A dict mapping keys to the corresponding table row data
+ fetched. Each row is represented as a tuple of strings. For
+ example:
+
+ {'Serak': ('Rigel VII', 'Preparer'),
+ 'Zim': ('Irk', 'Invader'),
+ 'Lrrr': ('Omicron Persei 8', 'Emperor')}
+
+ If a key from the keys argument is missing from the dictionary,
+ then that row was not found in the table.
+ """
+ pass
+ ```
+
+## 附录2:文档规范
+
+### 2.1 总体说明
+
+- 文档位置:如果您增加的新功能可以补充在原有的Markdown文件中,请**不要重新新建**一个文件。如果您对添加的位置不清楚,可以先PR代码,然后在commit中询问官方人员。
+
+- 新增Markdown文档名称:使用英文描述文档内容,一般由小写字母与下划线组合而成,例如 `add_new_algorithm.md`
+
+- 新增Markdown文档格式:目录 - 正文 - FAQ
+
+ > 目录生成方法可以使用 [此网站](https://ecotrust-canada.github.io/markdown-toc/) 将md内容复制之后自动提取目录,然后在md文件的每个标题前添加
+
+- 中英双语:任何对文档的改动或新增都需要分别在中文和英文文档上进行。
+
+### 2.2 格式规范
+
+- 标题格式:文档标题格式按照:阿拉伯数字小数点组合 - 空格 - 标题的格式(例如 `2.1 XXXX` , `2. XXXX`)
+
+- 代码块:通过代码块格式展示需要运行的代码,在代码块前描述命令参数的含义。例如:
+
+ > 检测+方向分类器+识别全流程:设置方向分类器参数 `--use_angle_cls true` 后可对竖排文本进行识别。
+ >
+ > ```bash linenums="1"
+ > paddleocr --image_dir ./imgs/11.jpg --use_angle_cls true
+ > ```
+
+- 变量引用:如果在行内引用到代码变量或命令参数,需要用行内代码表示,例如上方 `--use_angle_cls true` ,并在前后各空一格
+
+- 统一命名:如PP-OCRv2、PP-OCR mobile、`paddleocr` whl包、PPOCRLabel、Paddle Lite等
+
+- 补充说明:通过引用格式 `>` 补充说明,或对注意事项进行说明
+
+- 图片:如果在说明文档中增加了图片,请规范图片的命名形式(描述图片内容),并将图片添加在 `doc/` 下
+
+## 附录3:Pull Request说明
+
+### 3.1 PaddleOCR分支说明
+
+PaddleOCR未来将维护2种分支,分别为:
+
+- release/x.x系列分支:为稳定的发行版本分支,也是默认分支。PaddleOCR会根据功能更新情况发布新的release分支,同时适配Paddle的release版本。随着版本迭代,release/x.x系列分支会越来越多,默认维护最新版本的release分支。
+- dygraph分支:为开发分支,适配Paddle动态图的dygraph版本,主要用于开发新功能。如果有同学需要进行二次开发,请选择dygraph分支。为了保证dygraph分支能在需要的时候拉出release/x.x分支,dygraph分支的代码只能使用Paddle最新release分支中有效的api。也就是说,如果Paddle dygraph分支中开发了新的api,但尚未出现在release分支代码中,那么请不要在PaddleOCR中使用。除此之外,对于不涉及api的性能优化、参数调整、策略更新等,都可以正常进行开发。
+
+PaddleOCR的历史分支,未来将不再维护。考虑到一些同学可能仍在使用,这些分支还会继续保留:
+
+- develop分支:这个分支曾用于静态图的开发与测试,目前兼容>=1.7版本的Paddle。如果有特殊需求,要适配旧版本的Paddle,那还可以使用这个分支,但除了修复bug外不再更新代码。
+
+PaddleOCR欢迎大家向repo中积极贡献代码,下面给出一些贡献代码的基本流程。
+
+### 3.2 PaddleOCR代码提交流程与规范
+
+> 如果你熟悉Git使用,可以直接跳转到 [3.2.10 提交代码的一些约定](#提交代码的一些约定)
+
+#### 3.2.1 创建你的 `远程仓库`
+
+- 在PaddleOCR的 [GitHub首页](https://github.com/PaddlePaddle/PaddleOCR),点击左上角 `Fork` 按钮,在你的个人目录下创建 `远程仓库`,比如`https://github.com/{your_name}/PaddleOCR`。
+
+ ![banner](./images/banner.png)
+
+- 将 `远程仓库` Clone到本地
+
+ ```bash linenums="1"
+ # 拉取dygraph分支的代码
+ git clone https://github.com/{your_name}/PaddleOCR.git -b dygraph
+ cd PaddleOCR
+ ```
+
+> 多数情况下clone失败是由于网络原因,请稍后重试或配置代理
+
+#### 3.2.2 通过Token方式登录与建立连接
+
+首先查看当前 `远程仓库` 的信息。
+
+```bash linenums="1"
+git remote -v
+# origin https://github.com/{your_name}/PaddleOCR.git (fetch)
+# origin https://github.com/{your_name}/PaddleOCR.git (push)
+```
+
+只有clone的 `远程仓库` 的信息,也就是自己用户名下的 PaddleOCR。由于Github的登录方式变化,需要通过Token的方式重新配置 `远程仓库` 的地址。生成Token的方式如下:
+
+1. 找到个人访问令牌(token):在Github页面右上角点击自己的头像,然后依次选择 Settings --> Developer settings --> Personal access tokens
+2. 点击 Generate new token:在Note中填入token名称,例如’paddle‘。在Select scopes选择repo(必选)、admin:repo_hook、delete_repo等,可根据自身需要勾选。然后点击Generate token生成token。最后复制生成的token。
+
+删除原始的origin配置
+
+```bash linenums="1"
+git remote rm origin
+```
+
+将remote分支改成 `https://oauth2:{token}@github.com/{your_name}/PaddleOCR.git`。例如:如果token值为12345,你的用户名为PPOCR,则运行下方命令
+
+```bash linenums="1"
+git remote add origin https://oauth2:12345@github.com/PPOCR/PaddleOCR.git
+```
+
+这样我们就与自己的 `远程仓库` 建立了连接。接下来我们创建一个原始 PaddleOCR 仓库的远程主机,命名为 upstream。
+
+```bash linenums="1"
+git remote add upstream https://github.com/PaddlePaddle/PaddleOCR.git
+```
+
+使用 `git remote -v` 查看当前 `远程仓库` 的信息,输出如下,发现包括了origin和upstream 2个 `远程仓库` 。
+
+```bash linenums="1"
+origin https://oauth2:{token}@github.com/{your_name}/PaddleOCR.git (fetch)
+origin https://oauth2:{token}@github.com/{your_name}/PaddleOCR.git (push)
+upstream https://github.com/PaddlePaddle/PaddleOCR.git (fetch)
+upstream https://github.com/PaddlePaddle/PaddleOCR.git (push)
+```
+
+这主要是为了后续在提交pull request(PR)时,始终保持本地仓库最新。
+
+#### 3.2.3 创建本地分支
+
+首先获取 upstream 的最新代码,然后基于上游仓库 (upstream)的dygraph创建new_branch分支。
+
+```bash linenums="1"
+git fetch upstream
+git checkout -b new_branch upstream/dygraph
+```
+
+> 如果对于新Fork的PaddleOCR项目,用户远程仓库(origin)与上游(upstream)仓库的分支更新情况相同,也可以基于origin仓库的默认分支或指定分支创建新的本地分支,命令如下。
+>
+> ```bash linenums="1"
+> # 基于用户远程仓库(origin)的dygraph创建new_branch分支
+> git checkout -b new_branch origin/dygraph
+>
+> # 基于用户远程仓库(origin)的默认分支创建new_branch分支
+> git checkout -b new_branch
+> ```
+
+最终会显示切换到新的分支,输出信息如下
+
+```bash linenums="1"
+Branch new_branch set up to track remote branch develop from upstream.
+Switched to a new branch 'new_branch'
+```
+
+切换分支之后即可在此分支上进行文件改动
+
+#### 3.2.4 使用pre-commit勾子
+
+Paddle 开发人员使用 pre-commit 工具来管理 Git 预提交钩子。 它可以帮助我们格式化源代码(C++,Python),在提交(commit)前自动检查一些基本事宜(如每个文件只有一个 EOL,Git 中不要添加大文件等)。
+
+pre-commit测试是 Travis-CI 中单元测试的一部分,不满足钩子的 PR 不能被提交到 PaddleOCR,首先安装并在当前目录运行它:
+
+```bash linenums="1"
+pip install pre-commit
+pre-commit install
+```
+
+ > 1. Paddle 使用 clang-format 来调整 C/C++ 源代码格式,请确保 `clang-format` 版本在 3.8 以上。
+ >
+ > 2. 通过pip install pre-commit和conda install -c conda-forge pre-commit安装的yapf稍有不同的,PaddleOCR 开发人员使用的是 `pip install pre-commit`。
+
+#### 3.2.5 修改与提交代码
+
+ 假设对PaddleOCR的 `README.md` 做了一些修改,可以通过 `git status` 查看改动的文件,然后使用 `git add` 添加改动文件。
+
+```bash linenums="1"
+git status # 查看改动文件
+git add README.md
+pre-commit
+```
+
+重复上述步骤,直到pre-comit格式检查不报错。如下所示。
+
+![img](./images/precommit_pass.png)
+
+提交修改,并写明修改内容("your commit info")
+
+```bash linenums="1"
+git commit -m "your commit info"
+```
+
+#### 3.2.6 Push到远程仓库
+
+使用push命令将修改的commit提交到 `远程仓库`
+
+```bash linenums="1"
+git push origin new_branch
+```
+
+#### 3.2.7 提交Pull Request
+
+打开自己的远程仓库界面,选择提交的分支。点击new pull request或contribute进入PR界面。选择本地分支和目标分支,如下图所示。在PR的描述说明中,填写该PR所完成的功能。接下来等待review,如果有需要修改的地方,参照上述步骤更新 origin 中的对应分支即可。
+
+![img](./images/pr.png)
+
+#### 3.2.8 签署CLA协议和通过单元测试
+
+- 签署CLA 在首次向PaddlePaddle提交Pull Request时,您需要您签署一次CLA(Contributor License Agreement)协议,以保证您的代码可以被合入,具体签署方式如下:
+
+ 1. 请您查看PR中的Check部分,找到license/cla,并点击右侧detail,进入CLA网站
+
+ 2. 点击CLA网站中的“Sign in with GitHub to agree”,点击完成后将会跳转回您的Pull Request页面
+
+#### 3.2.9 删除分支
+
+- 删除远程分支
+
+ 在 PR 被 merge 进主仓库后,我们可以在 PR 的页面删除远程仓库的分支。
+
+ 也可以使用 `git push origin :分支名` 删除远程分支,如:
+
+ ```bash linenums="1"
+ git push origin :new_branch
+ ```
+
+- 删除本地分支
+
+ ```bash linenums="1"
+ # 切换到dygraph分支,否则无法删除当前分支
+ git checkout dygraph
+
+ # 删除new_branch分支
+ git branch -D new_branch
+ ```
+
+#### 3.2.10 提交代码的一些约定
+
+为了使官方维护人员在评审代码时更好地专注于代码本身,请您每次提交代码时,遵守以下约定:
+
+1)请保证Travis-CI 中单元测试能顺利通过。如果没过,说明提交的代码存在问题,官方维护人员一般不做评审。
+
+2)提交Pull Request前:
+
+- 请注意commit的数量。
+
+ 原因:如果仅仅修改一个文件但提交了十几个commit,每个commit只做了少量的修改,这会给评审人带来很大困扰。评审人需要逐一查看每个commit才能知道做了哪些修改,且不排除commit之间的修改存在相互覆盖的情况。
+
+ 建议:每次提交时,保持尽量少的commit,可以通过git commit --amend补充上次的commit。对已经Push到远程仓库的多个commit,可以参考[squash commits after push](https://stackoverflow.com/questions/5667884/how-to-squash-commits-in-git-after-they-have-been-pushed)。
+
+- 请注意每个commit的名称:应能反映当前commit的内容,不能太随意。
+
+3)如果解决了某个Issue的问题,请在该Pull Request的第一个评论框中加上:fix #issue_number,这样当该Pull Request被合并后,会自动关闭对应的Issue。关键词包括:close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved,请选择合适的词汇。详细可参考[Closing issues via commit messages](https://help.github.com/articles/closing-issues-via-commit-messages)。
+
+此外,在回复评审人意见时,请您遵守以下约定:
+
+1)官方维护人员的每一个review意见都希望得到回复,这样会更好地提升开源社区的贡献。
+
+- 对评审意见同意且按其修改完的,给个简单的Done即可;
+- 对评审意见不同意的,请给出您自己的反驳理由。
+
+2)如果评审意见比较多:
+
+- 请给出总体的修改情况。
+- 请采用`start a review`进行回复,而非直接回复的方式。原因是每个回复都会发送一封邮件,会造成邮件灾难。
diff --git a/docs/community/community_contribution.md b/docs/community/community_contribution.md
new file mode 100644
index 0000000000..54c5b45957
--- /dev/null
+++ b/docs/community/community_contribution.md
@@ -0,0 +1,141 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# 社区贡献
+
+感谢大家长久以来对PaddleOCR的支持和关注,与广大开发者共同构建一个专业、和谐、相互帮助的开源社区是PaddleOCR的目标。本文档展示了已有的社区贡献、对于各类贡献说明、新的机会与流程,希望贡献流程更加高效、路径更加清晰。
+
+PaddleOCR希望可以通过AI的力量助力任何一位有梦想的开发者实现自己的想法,享受创造价值带来的愉悦。
+
+
+
+
+
+---
+
+## 1. 社区贡献
+
+### 1.1 基于PaddleOCR的社区项目
+
+| 类别 | 项目 | 描述 | 开发者 |
+| -------- | ------ | ------ | --------- |
+| 通用工具 | [FastOCRLabel](https://gitee.com/BaoJianQiang/FastOCRLabel) | 完整的C#版本标注GUI | [包建强](https://gitee.com/BaoJianQiang) |
+| 通用工具 | [DangoOCR离线版](https://github.com/PantsuDango/DangoOCR) | 通用型桌面级即时翻译GUI | [PantsuDango](https://github.com/PantsuDango) |
+| 通用工具 | [scr2txt](https://github.com/lstwzd/scr2txt) | 截屏转文字GUI | [lstwzd](https://github.com/lstwzd) |
+| 通用工具 | [ocr_sdk](https://github.com/mymagicpower/AIAS/blob/main/1_image_sdks/text_recognition/ocr_sdk) | OCR java SDK工具箱 | [Calvin](https://github.com/mymagicpower) |
+| 通用工具 | [iocr](https://github.com/mymagicpower/AIAS/blob/main/8_suite_hub/iocr) | IOCR 自定义模板识别(支持表格识别) | [Calvin](https://github.com/mymagicpower) |
+| 通用工具 | [Lmdb Dataset Format Conversion Tool](https://github.com/OneYearIsEnough/PaddleOCR-Recog-LmdbDataset-Conversion) | 文本识别任务中lmdb数据格式转换工具 | [OneYearIsEnough](https://github.com/OneYearIsEnough) |
+| 通用工具 | [用paddleocr打造一款“盗幕笔记”](https://github.com/kjf4096/paddleocr_dmbj) | 用PaddleOCR记笔记 | [kjf4096](https://github.com/kjf4096) |
+| 垂类工具 | [AI Studio项目](https://aistudio.baidu.com/aistudio/projectdetail/1054614?channelType=0&channel=0) | 英文视频自动生成字幕 | [叶月水狐](https://aistudio.baidu.com/aistudio/personalcenter/thirdview/322052) |
+| 垂类工具 | [id_card_ocr](https://github.com/baseli/id_card_ocr) | 身份证复印件识别 | [baseli](https://github.com/baseli) |
+| 垂类工具 | [Paddle_Table_Image_Reader](https://github.com/thunder95/Paddle_Table_Image_Reader) | 能看懂表格图片的数据助手 | [thunder95](https://github.com/thunder95]) |
+| 垂类工具 | [AI Studio项目](https://aistudio.baidu.com/aistudio/projectdetail/3382897) | OCR流程中对手写体进行过滤 | [daassh](https://github.com/daassh) |
+| 垂类场景调优 | [AI Studio项目](https://aistudio.baidu.com/aistudio/projectdetail/2803693) | 电表读数和编号识别 | [深渊上的坑](https://github.com/edencfc) |
+| 垂类场景调优 | [AI Studio项目](https://aistudio.baidu.com/aistudio/projectdetail/3284199) | LCD液晶字符检测 | [Dream拒杰](https://github.com/zhangyingying520) |
+| 前后处理 | [paddleOCRCorrectOutputs](https://github.com/yuranusduke/paddleOCRCorrectOutputs) | 获取OCR识别结果的key-value | [yuranusduke](https://github.com/yuranusduke) |
+|前处理| [optlab](https://github.com/GreatV/optlab) |OCR前处理工具箱,基于Qt和Leptonica。|[GreatV](https://github.com/GreatV)|
+|应用部署| [PaddleOCRSharp](https://github.com/raoyutian/PaddleOCRSharp) |PaddleOCR的.NET封装与应用部署。|[raoyutian](https://github.com/raoyutian/PaddleOCRSharp)|
+|应用部署| [PaddleSharp](https://github.com/sdcb/PaddleSharp) |PaddleOCR的.NET封装与应用部署,支持跨平台、GPU|[sdcb](https://github.com/sdcb)|
+| 应用部署 | [PaddleOCR-Streamlit-Demo](https://github.com/Lovely-Pig/PaddleOCR-Streamlit-Demo) | 使用Streamlit部署PaddleOCR | [Lovely-Pig](https://github.com/Lovely-Pig) |
+| 应用部署 | [PaddleOCR-PyWebIO-Demo](https://github.com/Lovely-Pig/PaddleOCR-PyWebIO-Demo) | 使用PyWebIO部署PaddleOCR | [Lovely-Pig](https://github.com/Lovely-Pig) |
+| 应用部署 | [PaddleOCR-Paddlejs-Vue-Demo](https://github.com/Lovely-Pig/PaddleOCR-Paddlejs-Vue-Demo) | 使用Paddle.js和Vue部署PaddleOCR | [Lovely-Pig](https://github.com/Lovely-Pig) |
+| 应用部署 | [PaddleOCR-Paddlejs-React-Demo](https://github.com/Lovely-Pig/PaddleOCR-Paddlejs-React-Demo) | 使用Paddle.js和React部署PaddleOCR | [Lovely-Pig](https://github.com/Lovely-Pig) |
+| 学术前沿模型训练与推理 | [AI Studio项目](https://aistudio.baidu.com/aistudio/projectdetail/3397137) | StarNet-MobileNetV3算法–中文训练 | [xiaoyangyang2](https://github.com/xiaoyangyang2) |
+| 学术前沿模型训练与推理 | [ABINet-paddle](https://github.com/Huntersdeng/abinet-paddle) | ABINet算法前向运算的paddle实现以及模型各部分的实现细节分析 | [Huntersdeng](https://github.com/Huntersdeng) |
+
+### 1.2 为PaddleOCR新增功能
+
+- 非常感谢 [authorfu](https://github.com/authorfu) 贡献Android([#340](https://github.com/PaddlePaddle/PaddleOCR/pull/340))和[xiadeye](https://github.com/xiadeye) 贡献IOS的demo代码([#325](https://github.com/PaddlePaddle/PaddleOCR/pull/325))
+- 非常感谢 [tangmq](https://gitee.com/tangmq) 给PaddleOCR增加Docker化部署服务,支持快速发布可调用的Restful API服务([#507](https://github.com/PaddlePaddle/PaddleOCR/pull/507))。
+- 非常感谢 [lijinhan](https://github.com/lijinhan) 给PaddleOCR增加java SpringBoot 调用OCR Hubserving接口完成对OCR服务化部署的使用([#1027](https://github.com/PaddlePaddle/PaddleOCR/pull/1027))。
+- 非常感谢 [Evezerest](https://github.com/Evezerest), [ninetailskim](https://github.com/ninetailskim), [edencfc](https://github.com/edencfc), [BeyondYourself](https://github.com/BeyondYourself), [1084667371](https://github.com/1084667371) 贡献了[PPOCRLabel](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/PPOCRLabel/README_ch.md) 的完整代码。
+- 非常感谢 [bupt906](https://github.com/bupt906) 贡献MicroNet结构代码([#5251](https://github.com/PaddlePaddle/PaddleOCR/pull/5251))和贡献OneCycle学习率策略代码([#5252](https://github.com/PaddlePaddle/PaddleOCR/pull/5252))
+
+### 1.3 代码修复
+
+- 非常感谢 [zhangxin](https://github.com/ZhangXinNan)([Blog](https://blog.csdn.net/sdlypyzq)) 贡献新的可视化方式、添加.gitgnore、处理手动设置PYTHONPATH环境变量的问题([#210](https://github.com/PaddlePaddle/PaddleOCR/pull/210))。
+- 非常感谢 [lyl120117](https://github.com/lyl120117) 贡献打印网络结构的代码([#304](https://github.com/PaddlePaddle/PaddleOCR/pull/304))。
+- 非常感谢 [BeyondYourself](https://github.com/BeyondYourself) 给PaddleOCR提了很多非常棒的建议,并简化了PaddleOCR的部分代码风格([so many commits)](https://github.com/PaddlePaddle/PaddleOCR/commits?author=BeyondYourself)。
+
+### 1.4 文档优化与翻译
+
+- 非常感谢 **[RangeKing](https://github.com/RangeKing),[HustBestCat](https://github.com/HustBestCat),[v3fc](https://github.com/v3fc),[1084667371](https://github.com/1084667371)** 贡献翻译《动手学OCR》notebook[电子书英文版](https://github.com/PaddlePaddle/PaddleOCR/tree/dygraph/notebook/notebook_en)。
+- 非常感谢 [thunderstudying](https://github.com/thunderstudying),[RangeKing](https://github.com/RangeKing),[livingbody](https://github.com/livingbody), [WZMIAOMIAO](https://github.com/WZMIAOMIAO),[haigang1975](https://github.com/haigang1975) 补充多个英文markdown文档。
+- 非常感谢 **[fanruinet](https://github.com/fanruinet)** 润色和修复35篇英文文档([#5205](https://github.com/PaddlePaddle/PaddleOCR/pull/5205))。
+- 非常感谢 [Khanh Tran](https://github.com/xxxpsyduck) 和 [Karl Horky](https://github.com/karlhorky) 贡献修改英文文档。
+
+### 1.5 多语言语料
+
+- 非常感谢 [xiangyubo](https://github.com/xiangyubo) 贡献手写中文OCR数据集([#321](https://github.com/PaddlePaddle/PaddleOCR/pull/321))。
+- 非常感谢 [Mejans](https://github.com/Mejans) 给PaddleOCR增加新语言奥克西坦语Occitan的字典和语料([#954](https://github.com/PaddlePaddle/PaddleOCR/pull/954))。
+
+## 2. 贡献说明
+
+### 2.1 新增功能类
+
+PaddleOCR非常欢迎社区贡献以PaddleOCR为核心的各种服务、部署实例与软件应用,经过认证的社区贡献会被添加在上述社区贡献表中,为广大开发者增加曝光,也是PaddleOCR的荣耀,其中:
+
+- 项目形式:官方社区认证的项目代码应有良好的规范和结构,同时,还应配备一个详细的README.md,说明项目的使用方法。通过在requirements.txt文件中增加一行 `paddleocr` 可以自动收录到PaddleOCR的usedby中。
+
+- 合入方式:如果是对PaddleOCR现有工具的更新升级,则会合入主repo。如果为PaddleOCR拓展了新功能,请先与官方人员联系,确认项目是否合入主repo,*即使新功能未合入主repo,我们同样也会以社区贡献的方式为您的个人项目增加曝光。*
+
+### 2.2 代码优化
+
+如果您在使用PaddleOCR时遇到了代码bug、功能不符合预期等问题,可以为PaddleOCR贡献您的修改,其中:
+
+- Python代码规范可参考[附录1:Python代码规范](./code_and_doc.md#附录1python代码规范)。
+
+- 提交代码前请再三确认不会引入新的bug,并在PR中描述优化点。如果该PR解决了某个issue,请在PR中连接到该issue。所有的PR都应该遵守附录3中的[3.2.10 提交代码的一些约定。](./code_and_doc.md#附录3pull-request说明)
+
+- 请在提交之前参考下方的[附录3:Pull Request说明](./code_and_doc.md#附录3pull-request说明)。如果您对git的提交流程不熟悉,同样可以参考附录3的3.2节。
+
+### 2.3 文档优化
+
+如果您在使用PaddleOCR时遇到了文档表述不清楚、描述缺失、链接失效等问题,可以为PaddleOCR贡献您的修改。文档书写规范请参考[附录2:文档规范](./code_and_doc.md#附录2文档规范)。
+
+## 3. 更多贡献机会
+
+我们非常鼓励开发者使用PaddleOCR实现自己的想法,同时我们也列出一些经过分析后认为有价值的拓展方向,整体收集在社区项目常规赛中。
+
+## 4. 联系我们
+
+我们非常欢迎广大开发者在有意向为PaddleOCR贡献代码、文档、语料等内容前与我们联系,这样可以大大降低PR过程中的沟通成本。同时,如果您觉得某些想法个人难以实现,我们也可以通过SIG的形式定向为项目招募志同道合的开发者一起共建。通过SIG渠道贡献的项目将会获得深层次的研发支持与运营资源(如公众号宣传、直播课等)。
+
+我们推荐的贡献流程是:
+
+- 通过在github issue的题目中增加 `【third-party】` 标记,说明遇到的问题(以及解决的思路)或想拓展的功能,等待值班人员回复。例如 `【third-party】为PaddleOCR贡献IOS示例`
+- 与我们沟通确认技术方案或bug、优化点准确无误后进行功能新增或相应的修改,代码与文档遵循相关规范。
+- PR链接到上述issue,等待review。
+
+## 5. 致谢与后续
+
+- 合入代码之后会在本文档第一节中更新信息,默认链接为github名字及主页,如果有需要更换主页,也可以联系我们。
+- 新增重要功能类,会在用户群广而告之,享受开源社区荣誉时刻。
+- **如果您有基于PaddleOCR的项目,但未出现在上述列表中,请按照 `4. 联系我们` 的步骤与我们联系。**
+
+## 附录:社区常规赛积分榜
+
+| 开发者| 总积分 | 开发者| 总积分 |
+| ---- | ------ | ----- | ------ |
+| [RangeKing](https://github.com/RangeKing) | 220 | [WZMIAOMIAO](https://github.com/WZMIAOMIAO) | 36 |
+| [hao6699](https://github.com/hao6699) | 145 | [v3fc](https://github.com/v3fc) | 35 |
+| [mymagicpower](https://github.com/mymagicpower) | 140 | [imiyu](https://github.com/imiyu) | 30 |
+| [raoyutian](https://github.com/raoyutian) | 90 | [haigang1975](https://github.com/haigang1975) | 29 |
+| [sdcb](https://github.com/sdcb) | 80 | [daassh](https://github.com/daassh) | 23 |
+| [zhiminzhang0830](https://github.com/zhiminzhang0830) | 70 | [xiaoyangyang2](https://github.com/xiaoyangyang2) | 20 |
+| [Lovely-Pig](https://github.com/Lovely-Pig) | 70 | [prettyocean85](https://github.com/prettyocean85) | 20 |
+| [livingbody](https://github.com/livingbody) | 70 | [nmusik](https://github.com/nmusik) | 20 |
+| [fanruinet](https://github.com/fanruinet) | 70 | [kjf4096](https://github.com/kjf4096) | 20 |
+| [bupt906](https://github.com/bupt906) | 60 | [chccc1994](https://github.com/chccc1994) | 20 |
+| [edencfc](https://github.com/edencfc) | 57 | [BeyondYourself](https://github.com/BeyondYourself) | 20 |
+| [zhangyingying520](https://github.com/zhangyingying520) | 57 | chenguoqi08161 | 18 |
+| [ITerydh](https://github.com/ITerydh) | 55 | [weiwenlan](https://github.com/weiwenlan) | 10 |
+| [telppa](https://github.com/telppa) | 40 | [shaoshenchen thinc](https://github.com/shaoshenchen) | 10 |
+| sosojust1984 | 40 | [jordan2013](https://github.com/jordan2013) | 10 |
+| [redearly123](https://github.com/redearly123) | 40 | [JimEverest](https://github.com/JimEverest) | 10 |
+| [OneYearIsEnough](https://github.com/OneYearIsEnough) | 40 | [HustBestCat](https://github.com/HustBestCat) | 10 |
+| [Huntersdeng](https://github.com/Huntersdeng) | 40 | | |
+| [GreatV](https://github.com/GreatV) | 40 | | |
+| CLXK294 | 40 | | |
diff --git a/docs/community/images/banner.png b/docs/community/images/banner.png
new file mode 100644
index 0000000000..d72b997a80
Binary files /dev/null and b/docs/community/images/banner.png differ
diff --git a/docs/community/images/pr.png b/docs/community/images/pr.png
new file mode 100644
index 0000000000..3d0d15f78d
Binary files /dev/null and b/docs/community/images/pr.png differ
diff --git a/docs/community/images/precommit_pass.png b/docs/community/images/precommit_pass.png
new file mode 100644
index 0000000000..067fb75ddb
Binary files /dev/null and b/docs/community/images/precommit_pass.png differ
diff --git a/docs/data_anno_synth/data_annotation.en.md b/docs/data_anno_synth/data_annotation.en.md
new file mode 100644
index 0000000000..772a5f46ee
--- /dev/null
+++ b/docs/data_anno_synth/data_annotation.en.md
@@ -0,0 +1,31 @@
+---
+comments: true
+---
+
+# DATA ANNOTATION TOOLS
+
+There are the commonly used data annotation tools, which will be continuously updated. Welcome to contribute tools~
+
+## 1. labelImg
+
+- Tool description: Rectangular label
+- Tool address:
+- Sketch diagram:
+
+ ![labelimg](./images/labelimg.jpg)
+
+## 2. roLabelImg
+
+- Tool description: Label tool rewritten based on labelImg, supporting rotating rectangular label
+- Tool address:
+- Sketch diagram:
+
+ ![roLabelImg](./images/roLabelImg.png)
+
+## 3. labelme
+
+- Tool description: Support four points, polygons, circles and other labels
+- Tool address:
+- Sketch diagram:
+
+ ![labelme](./images/labelme.jpg)
diff --git a/docs/data_anno_synth/data_annotation.md b/docs/data_anno_synth/data_annotation.md
new file mode 100644
index 0000000000..3446bfbbf7
--- /dev/null
+++ b/docs/data_anno_synth/data_annotation.md
@@ -0,0 +1,35 @@
+---
+comments: true
+---
+
+# 数据标注工具
+
+这里整理了常用的数据标注工具,持续更新中,欢迎各位小伙伴贡献工具~
+
+## 1. labelImg
+
+- 工具描述:矩形标注
+- 工具地址:
+- 示意图:
+ ![](./images/labelimg.jpg)
+
+## 2. roLabelImg
+
+- 工具描述:基于labelImg重写的标注工具,支持旋转矩形标注
+- 工具地址:
+- 示意图:
+ ![](./images/roLabelImg.png)
+
+## 3. labelme
+
+- 工具描述:支持四点、多边形、圆形等多种标注
+- 工具地址:
+- 示意图:
+ ![](./images/labelme.jpg)
+
+## 4. Vott
+
+- 工具描述:支持矩形,多边形等图片标注.支持视频标注.方便使用的快捷键以及比较好看的界面.同时支持导出多种标签格式.
+- 工具地址:
+- 示意图:
+ ![](./images/VoTT.jpg)
diff --git a/docs/data_anno_synth/data_synthesis.en.md b/docs/data_anno_synth/data_synthesis.en.md
new file mode 100644
index 0000000000..5d8a6fc2b8
--- /dev/null
+++ b/docs/data_anno_synth/data_synthesis.en.md
@@ -0,0 +1,17 @@
+---
+comments: true
+---
+
+
+# DATA SYNTHESIS TOOLS
+
+In addition to open source data, users can also use synthesis tools to synthesize data.
+There are the commonly used data synthesis tools, which will be continuously updated. Welcome to contribute tools~
+
+* [Text_renderer](https://github.com/Sanster/text_renderer)
+* [SynthText](https://github.com/ankush-me/SynthText)
+* [SynthText_Chinese_version](https://github.com/JarveeLee/SynthText_Chinese_version)
+* [TextRecognitionDataGenerator](https://github.com/Belval/TextRecognitionDataGenerator)
+* [SynthText3D](https://github.com/MhLiao/SynthText3D)
+* [UnrealText](https://github.com/Jyouhou/UnrealText/)
+* [SynthTIGER](https://github.com/clovaai/synthtiger)
diff --git a/docs/data_anno_synth/data_synthesis.md b/docs/data_anno_synth/data_synthesis.md
new file mode 100644
index 0000000000..7ebcf862fa
--- /dev/null
+++ b/docs/data_anno_synth/data_synthesis.md
@@ -0,0 +1,15 @@
+---
+comments: true
+---
+
+# 数据合成工具
+
+除了开源数据,用户还可使用合成工具自行合成。这里整理了常用的数据合成工具,持续更新中,欢迎各位小伙伴贡献工具~
+
+- [text_renderer](https://github.com/Sanster/text_renderer)
+- [SynthText](https://github.com/ankush-me/SynthText)
+- [SynthText_Chinese_version](https://github.com/JarveeLee/SynthText_Chinese_version)
+- [TextRecognitionDataGenerator](https://github.com/Belval/TextRecognitionDataGenerator)
+- [SynthText3D](https://github.com/MhLiao/SynthText3D)
+- [UnrealText](https://github.com/Jyouhou/UnrealText/)
+- [SynthTIGER](https://github.com/clovaai/synthtiger)
diff --git a/docs/data_anno_synth/images/VoTT.jpg b/docs/data_anno_synth/images/VoTT.jpg
new file mode 100644
index 0000000000..7c5c27ba84
Binary files /dev/null and b/docs/data_anno_synth/images/VoTT.jpg differ
diff --git a/docs/data_anno_synth/images/labelimg.jpg b/docs/data_anno_synth/images/labelimg.jpg
new file mode 100644
index 0000000000..8d58a445ca
Binary files /dev/null and b/docs/data_anno_synth/images/labelimg.jpg differ
diff --git a/docs/data_anno_synth/images/labelme.jpg b/docs/data_anno_synth/images/labelme.jpg
new file mode 100644
index 0000000000..ce44e504df
Binary files /dev/null and b/docs/data_anno_synth/images/labelme.jpg differ
diff --git a/docs/data_anno_synth/images/roLabelImg.png b/docs/data_anno_synth/images/roLabelImg.png
new file mode 100644
index 0000000000..3b02a3226d
Binary files /dev/null and b/docs/data_anno_synth/images/roLabelImg.png differ
diff --git a/docs/data_anno_synth/overview.en.md b/docs/data_anno_synth/overview.en.md
new file mode 100644
index 0000000000..186157e233
--- /dev/null
+++ b/docs/data_anno_synth/overview.en.md
@@ -0,0 +1,6 @@
+---
+comments: true
+---
+
+- Semi-automatic Annotation Tool: PPOCRLabel:
+- Data Synthesis Tool: Style-Text:
diff --git a/docs/data_anno_synth/overview.md b/docs/data_anno_synth/overview.md
new file mode 100644
index 0000000000..1068cda83e
--- /dev/null
+++ b/docs/data_anno_synth/overview.md
@@ -0,0 +1,7 @@
+---
+comments: true
+---
+
+
+- 半自动标注工具 PPOCRLabel:
+- 数据合成工具 Style-Text:
diff --git a/docs/datasets/datasets.en.md b/docs/datasets/datasets.en.md
new file mode 100644
index 0000000000..f233ddfb84
--- /dev/null
+++ b/docs/datasets/datasets.en.md
@@ -0,0 +1,59 @@
+---
+comments: true
+---
+
+This is a collection of commonly used Chinese datasets, which is being updated continuously. You are welcome to contribute to this list~
+
+In addition to opensource data, users can also use synthesis tools to synthesize data themselves. Current available synthesis tools include [text_renderer](https://github.com/Sanster/text_renderer), [SynthText](https://github.com/ankush-me/SynthText), [TextRecognitionDataGenerator](https://github.com/Belval/TextRecognitionDataGenerator), etc.
+
+#### 1. ICDAR2019-LSVT
+
+- **Data sources**:
+- **Introduction**: A total of 45w Chinese street view images, including 5w (2w test + 3w training) fully labeled data (text coordinates + text content), 40w weakly labeled data (text content only), as shown in the following figure:
+ ![](./images/LSVT_1.jpg)
+
+ (a) Fully labeled data
+
+ ![](./images/LSVT_2.jpg)
+
+ (b) Weakly labeled data
+- **Download link**:
+
+#### 2. ICDAR2017-RCTW-17
+
+- **Data sources**:
+- **Introduction**:It contains 12000 + images, most of them are collected in the wild through mobile camera. Some are screenshots. These images show a variety of scenes, including street views, posters, menus, indoor scenes and screenshots of mobile applications.
+ ![](./images/rctw.jpg)
+- **Download link**:
+
+#### 3. Chinese Street View Text Recognition
+
+- **Data sources**:
+- **Introduction**:A total of 290000 pictures are included, of which 210000 are used as training sets (with labels) and 80000 are used as test sets (without labels). The dataset is collected from the Chinese street view, and is formed by by cutting out the text line area (such as shop signs, landmarks, etc.) in the street view picture. All the images are preprocessed: by using affine transform, the text area is proportionally mapped to a picture with a height of 48 pixels, as shown in the figure:
+
+ ![](./images/ch_street_rec_1.png)
+ (a) Label: 魅派集成吊顶
+ ![](./images/ch_street_rec_2.png)
+ (b) Label: 母婴用品连锁
+- **Download link**
+
+
+#### 4. Chinese Document Text Recognition
+
+- **Data sources**:
+- **Introduction**:
+ - A total of 3.64 million pictures are divided into training set and validation set according to 99:1.
+ - Using Chinese corpus (news + classical Chinese), the data is randomly generated through changes in font, size, grayscale, blur, perspective, stretching, etc.
+ - 5990 characters including Chinese characters, English letters, numbers and punctuation(Characters set: )
+ - Each sample is fixed with 10 characters, and the characters are randomly intercepted from the sentences in the corpus
+ - Image resolution is 280x32
+ ![](./images/ch_doc1.jpg)
+ ![](./images/ch_doc3.jpg)
+- **Download link**: (Password: lu7m)
+
+#### 5、ICDAR2019-ArT
+
+- **Data source**:
+- **Introduction**:It includes 10166 images, 5603 in training sets and 4563 in test sets. It is composed of three parts: total text, scut-ctw1500 and Baidu curved scene text, including text with various shapes such as horizontal, multi-directional and curved.
+ ![](./images/ArT.jpg)
+- **Download link**:
diff --git a/docs/datasets/datasets.md b/docs/datasets/datasets.md
new file mode 100644
index 0000000000..e7b55327c8
--- /dev/null
+++ b/docs/datasets/datasets.md
@@ -0,0 +1,88 @@
+---
+comments: true
+---
+
+这里整理了常用中文数据集,持续更新中,欢迎各位小伙伴贡献数据集~
+
+除了开源数据,用户还可使用合成工具自行合成,可参考[数据合成工具](../data_anno_synth/data_synthesis.md);
+
+如果需要标注自己的数据,可参考[数据标注工具](../data_anno_synth/data_annotation.md)。
+
+#### 1、ICDAR2019-LSVT
+
+- **数据来源**:
+- **数据简介**: 共45w中文街景图像,包含5w(2w测试+3w训练)全标注数据(文本坐标+文本内容),40w弱标注数据(仅文本内容),如下图所示:
+ ![](./images/LSVT_1.jpg)
+ (a) 全标注数据
+ ![](./images/LSVT_2.jpg)
+ (b) 弱标注数据
+- **下载地址**:
+- **说明**:其中,test数据集的label目前没有开源,如要评估结果,可以去官网提交:
+
+#### 2、ICDAR2017-RCTW-17
+
+- **数据来源**:
+- **数据简介**:共包含12,000+图像,大部分图片是通过手机摄像头在野外采集的。有些是截图。这些图片展示了各种各样的场景,包括街景、海报、菜单、室内场景和手机应用程序的截图。
+ ![](./images/rctw.jpg)
+- **下载地址**:
+
+#### 3、中文街景文字识别
+
+- **数据来源**:
+- **数据简介**:ICDAR2019-LSVT行识别任务,共包括29万张图片,其中21万张图片作为训练集(带标注),8万张作为测试集(无标注)。数据集采自中国街景,并由街景图片中的文字行区域(例如店铺标牌、地标等等)截取出来而形成。所有图像都经过一些预处理,将文字区域利用仿射变化,等比映射为一张高为48像素的图片,如图所示:
+ ![](./images/ch_street_rec_1.png)
+ (a) 标注:魅派集成吊顶
+ ![](./images/ch_street_rec_2.png)
+ (b) 标注:母婴用品连锁
+- **下载地址**
+
+
+#### 4、中文文档文字识别
+
+- **数据来源**:
+- **数据简介**:
+ - 共约364万张图片,按照99:1划分成训练集和验证集。
+ - 数据利用中文语料库(新闻 + 文言文),通过字体、大小、灰度、模糊、透视、拉伸等变化随机生成
+ - 包含汉字、英文字母、数字和标点共5990个字符(字符集合: )
+ - 每个样本固定10个字符,字符随机截取自语料库中的句子
+ - 图片分辨率统一为280x32
+ ![](./images/ch_doc1.jpg)
+ ![](./images/ch_doc3.jpg)
+- **下载地址**: (密码:lu7m)
+
+#### 5、ICDAR2019-ArT
+
+- **数据来源**:
+- **数据简介**:共包含10,166张图像,训练集5603图,测试集4563图。由Total-Text、SCUT-CTW1500、Baidu Curved Scene Text (ICDAR2019-LSVT部分弯曲数据) 三部分组成,包含水平、多方向和弯曲等多种形状的文本。
+ ![](./images/ArT.jpg)
+- **下载地址**:
+
+#### 6、电子印章数据集
+
+- **数据来源**:
+- **数据简介**:共包含10000张图像,训练集8000图,测试集2000图。数据集是用程序合成的,并不涉及隐私安全,主要用于印章弯曲文本的训练与检测。由开发者[jingsongliujing](https://github.com/jingsongliujing)贡献
+- **下载地址**:
+
+## 参考文献
+
+**ICDAR 2019-LSVT Challenge**
+
+```bibtex
+@article{sun2019icdar,
+ title={ICDAR 2019 Competition on Large-scale Street View Text with Partial Labeling--RRC-LSVT},
+ author={Sun, Yipeng and Ni, Zihan and Chng, Chee-Kheng and Liu, Yuliang and Luo, Canjie and Ng, Chun Chet and Han, Junyu and Ding, Errui and Liu, Jingtuo and Karatzas, Dimosthenis and others},
+ journal={arXiv preprint arXiv:1909.07741},
+ year={2019}
+}
+```
+
+**ICDAR 2019-ArT Challenge**
+
+```bibtex
+@article{chng2019icdar2019,
+ title={ICDAR2019 Robust Reading Challenge on Arbitrary-Shaped Text (RRC-ArT)},
+ author={Chng, Chee-Kheng and Liu, Yuliang and Sun, Yipeng and Ng, Chun Chet and Luo, Canjie and Ni, Zihan and Fang, ChuanMing and Zhang, Shuaitao and Han, Junyu and Ding, Errui and others},
+ journal={arXiv preprint arXiv:1909.07145},
+ year={2019}
+}
+```
diff --git a/docs/datasets/handwritten_datasets.en.md b/docs/datasets/handwritten_datasets.en.md
new file mode 100644
index 0000000000..8ee1d0fb67
--- /dev/null
+++ b/docs/datasets/handwritten_datasets.en.md
@@ -0,0 +1,31 @@
+---
+comments: true
+---
+
+
+# Handwritten OCR dataset
+
+Here we have sorted out the commonly used handwritten OCR dataset datasets, which are being updated continuously. We welcome you to contribute datasets ~
+
+- [Institute of automation, Chinese Academy of Sciences - handwritten Chinese dataset](#Institute of automation, Chinese Academy of Sciences - handwritten Chinese dataset)
+- [NIST handwritten single character dataset - English](#NIST handwritten single character dataset - English)
+
+## Institute of automation, Chinese Academy of Sciences - handwritten Chinese dataset
+
+- **Data source**:
+- **Data introduction**:
+ - It includes online and offline handwritten data,`HWDB1.0~1.2` has totally 3895135 handwritten single character samples, which belong to 7356 categories (7185 Chinese characters and 171 English letters, numbers and symbols);`HWDB2.0~2.2` has totally 5091 pages of images, which are divided into 52230 text lines and 1349414 words. All text and text samples are stored as grayscale images. Some sample words are shown below.
+
+ ![](./images/CASIA_0.jpg)
+
+- **Download address**:
+- **使用建议**:Data for single character, white background, can form a large number of text lines for training. White background can be processed into transparent state, which is convenient to add various backgrounds. For the case of semantic needs, it is suggested to extract single character from real corpus to form text lines.
+
+## NIST handwritten single character dataset - English(NIST Handprinted Forms and Characters Database)
+
+- **Data source**: [https://www.nist.gov/srd/nist-special-database-19](https://www.nist.gov/srd/nist-special-database-19)
+- **Data introduction**: NIST19 dataset is suitable for handwritten document and character recognition model training. It is extracted from the handwritten sample form of 3600 authors and contains 810000 character images in total. Nine of them are shown below.
+
+ ![](./images/nist_demo.png)
+
+- **Download address**: [https://www.nist.gov/srd/nist-special-database-19](https://www.nist.gov/srd/nist-special-database-19)
diff --git a/docs/datasets/handwritten_datasets.md b/docs/datasets/handwritten_datasets.md
new file mode 100644
index 0000000000..e4adb10314
--- /dev/null
+++ b/docs/datasets/handwritten_datasets.md
@@ -0,0 +1,28 @@
+---
+comments: true
+---
+
+
+# 手写OCR数据集
+
+这里整理了常用手写数据集,持续更新中,欢迎各位小伙伴贡献数据集~
+
+## 中科院自动化研究所-手写中文数据集
+
+- **数据来源**:
+- **数据简介**:
+ - 包含在线和离线两类手写数据,`HWDB1.0~1.2`总共有3895135个手写单字样本,分属7356类(7185个汉字和171个英文字母、数字、符号);`HWDB2.0~2.2`总共有5091页图像,分割为52230个文本行和1349414个文字。所有文字和文本样本均存为灰度图像。部分单字样本图片如下所示。
+
+ ![](./images/CASIA_0.jpg)
+
+- **下载地址**:
+- **使用建议**:数据为单字,白色背景,可以大量合成文字行进行训练。白色背景可以处理成透明状态,方便添加各种背景。对于需要语义的情况,建议从真实语料出发,抽取单字组成文字行
+
+## NIST手写单字数据集-英文(NIST Handprinted Forms and Characters Database)
+
+- **数据来源**: [https://www.nist.gov/srd/nist-special-database-19](https://www.nist.gov/srd/nist-special-database-19)
+- **数据简介**: NIST19数据集适用于手写文档和字符识别的模型训练,从3600位作者的手写样本表格中提取得到,总共包含81万张字符图片。其中9张图片示例如下:
+
+ ![](./images/nist_demo.png)
+
+- **下载地址**: [https://www.nist.gov/srd/nist-special-database-19](https://www.nist.gov/srd/nist-special-database-19)
diff --git a/docs/datasets/images/20210816_210413.gif b/docs/datasets/images/20210816_210413.gif
new file mode 100644
index 0000000000..288f615620
Binary files /dev/null and b/docs/datasets/images/20210816_210413.gif differ
diff --git a/docs/datasets/images/ArT.png b/docs/datasets/images/ArT.png
new file mode 100644
index 0000000000..dfb9d0a581
Binary files /dev/null and b/docs/datasets/images/ArT.png differ
diff --git a/docs/datasets/images/CASIA_0.jpg b/docs/datasets/images/CASIA_0.jpg
new file mode 100644
index 0000000000..d65924b2e0
Binary files /dev/null and b/docs/datasets/images/CASIA_0.jpg differ
diff --git a/docs/datasets/images/CDLA_demo/val_0633.jpg b/docs/datasets/images/CDLA_demo/val_0633.jpg
new file mode 100644
index 0000000000..834848547a
Binary files /dev/null and b/docs/datasets/images/CDLA_demo/val_0633.jpg differ
diff --git a/docs/datasets/images/CDLA_demo/val_0941.jpg b/docs/datasets/images/CDLA_demo/val_0941.jpg
new file mode 100644
index 0000000000..f7d548e120
Binary files /dev/null and b/docs/datasets/images/CDLA_demo/val_0941.jpg differ
diff --git a/docs/datasets/images/LSVT_1.jpg b/docs/datasets/images/LSVT_1.jpg
new file mode 100644
index 0000000000..ea11a7da59
Binary files /dev/null and b/docs/datasets/images/LSVT_1.jpg differ
diff --git a/docs/datasets/images/LSVT_2.jpg b/docs/datasets/images/LSVT_2.jpg
new file mode 100644
index 0000000000..67bfbe5259
Binary files /dev/null and b/docs/datasets/images/LSVT_2.jpg differ
diff --git a/docs/datasets/images/VoTT.jpg b/docs/datasets/images/VoTT.jpg
new file mode 100644
index 0000000000..7c5c27ba84
Binary files /dev/null and b/docs/datasets/images/VoTT.jpg differ
diff --git a/docs/datasets/images/captcha_demo.png b/docs/datasets/images/captcha_demo.png
new file mode 100644
index 0000000000..047a72648c
Binary files /dev/null and b/docs/datasets/images/captcha_demo.png differ
diff --git a/docs/datasets/images/ccpd_demo.png b/docs/datasets/images/ccpd_demo.png
new file mode 100644
index 0000000000..a750d054f6
Binary files /dev/null and b/docs/datasets/images/ccpd_demo.png differ
diff --git a/docs/datasets/images/ch_doc1.jpg b/docs/datasets/images/ch_doc1.jpg
new file mode 100644
index 0000000000..53534400ab
Binary files /dev/null and b/docs/datasets/images/ch_doc1.jpg differ
diff --git a/docs/datasets/images/ch_doc3.jpg b/docs/datasets/images/ch_doc3.jpg
new file mode 100644
index 0000000000..c0c2053643
Binary files /dev/null and b/docs/datasets/images/ch_doc3.jpg differ
diff --git a/docs/datasets/images/ch_street_rec_1.png b/docs/datasets/images/ch_street_rec_1.png
new file mode 100644
index 0000000000..a0e158cbd1
Binary files /dev/null and b/docs/datasets/images/ch_street_rec_1.png differ
diff --git a/docs/datasets/images/ch_street_rec_2.png b/docs/datasets/images/ch_street_rec_2.png
new file mode 100644
index 0000000000..bfa0fd0188
Binary files /dev/null and b/docs/datasets/images/ch_street_rec_2.png differ
diff --git a/docs/datasets/images/cmb_demo.jpg b/docs/datasets/images/cmb_demo.jpg
new file mode 100644
index 0000000000..8299149a7c
Binary files /dev/null and b/docs/datasets/images/cmb_demo.jpg differ
diff --git a/docs/datasets/images/crohme_demo/hme_00.jpg b/docs/datasets/images/crohme_demo/hme_00.jpg
new file mode 100644
index 0000000000..66ff27db26
Binary files /dev/null and b/docs/datasets/images/crohme_demo/hme_00.jpg differ
diff --git a/docs/datasets/images/crohme_demo/hme_01.jpg b/docs/datasets/images/crohme_demo/hme_01.jpg
new file mode 100644
index 0000000000..68b7f09fc2
Binary files /dev/null and b/docs/datasets/images/crohme_demo/hme_01.jpg differ
diff --git a/docs/datasets/images/crohme_demo/hme_02.jpg b/docs/datasets/images/crohme_demo/hme_02.jpg
new file mode 100644
index 0000000000..ecc760f538
Binary files /dev/null and b/docs/datasets/images/crohme_demo/hme_02.jpg differ
diff --git a/docs/datasets/images/doc.jpg b/docs/datasets/images/doc.jpg
new file mode 100644
index 0000000000..f57e62abe1
Binary files /dev/null and b/docs/datasets/images/doc.jpg differ
diff --git a/docs/datasets/images/funsd_demo/gt_train_00040534.jpg b/docs/datasets/images/funsd_demo/gt_train_00040534.jpg
new file mode 100644
index 0000000000..9f7cf4d497
Binary files /dev/null and b/docs/datasets/images/funsd_demo/gt_train_00040534.jpg differ
diff --git a/docs/datasets/images/funsd_demo/gt_train_00070353.jpg b/docs/datasets/images/funsd_demo/gt_train_00070353.jpg
new file mode 100644
index 0000000000..36d3345e5e
Binary files /dev/null and b/docs/datasets/images/funsd_demo/gt_train_00070353.jpg differ
diff --git a/docs/datasets/images/ic15_location_download.png b/docs/datasets/images/ic15_location_download.png
new file mode 100644
index 0000000000..7cb8540e5e
Binary files /dev/null and b/docs/datasets/images/ic15_location_download.png differ
diff --git a/docs/datasets/images/icdar_rec.png b/docs/datasets/images/icdar_rec.png
new file mode 100644
index 0000000000..a840d6af59
Binary files /dev/null and b/docs/datasets/images/icdar_rec.png differ
diff --git a/docs/datasets/images/labelimg.jpg b/docs/datasets/images/labelimg.jpg
new file mode 100644
index 0000000000..8d58a445ca
Binary files /dev/null and b/docs/datasets/images/labelimg.jpg differ
diff --git a/docs/datasets/images/labelme.jpg b/docs/datasets/images/labelme.jpg
new file mode 100644
index 0000000000..97cdb237a0
Binary files /dev/null and b/docs/datasets/images/labelme.jpg differ
diff --git a/docs/datasets/images/nist_demo.png b/docs/datasets/images/nist_demo.png
new file mode 100644
index 0000000000..4c2ce11e26
Binary files /dev/null and b/docs/datasets/images/nist_demo.png differ
diff --git a/docs/datasets/images/publaynet_demo/gt_PMC3724501_00006.jpg b/docs/datasets/images/publaynet_demo/gt_PMC3724501_00006.jpg
new file mode 100644
index 0000000000..3b7ee8921e
Binary files /dev/null and b/docs/datasets/images/publaynet_demo/gt_PMC3724501_00006.jpg differ
diff --git a/docs/datasets/images/publaynet_demo/gt_PMC5086060_00002.jpg b/docs/datasets/images/publaynet_demo/gt_PMC5086060_00002.jpg
new file mode 100644
index 0000000000..cad8f3035b
Binary files /dev/null and b/docs/datasets/images/publaynet_demo/gt_PMC5086060_00002.jpg differ
diff --git a/docs/datasets/images/rctw.jpg b/docs/datasets/images/rctw.jpg
new file mode 100644
index 0000000000..1e1f945b10
Binary files /dev/null and b/docs/datasets/images/rctw.jpg differ
diff --git a/docs/datasets/images/roLabelImg.png b/docs/datasets/images/roLabelImg.png
new file mode 100644
index 0000000000..9e354b0bf6
Binary files /dev/null and b/docs/datasets/images/roLabelImg.png differ
diff --git a/docs/datasets/images/table_PubTabNet_demo/PMC524509_007_00.png b/docs/datasets/images/table_PubTabNet_demo/PMC524509_007_00.png
new file mode 100755
index 0000000000..5b9d631cba
Binary files /dev/null and b/docs/datasets/images/table_PubTabNet_demo/PMC524509_007_00.png differ
diff --git a/docs/datasets/images/table_PubTabNet_demo/PMC535543_007_01.png b/docs/datasets/images/table_PubTabNet_demo/PMC535543_007_01.png
new file mode 100755
index 0000000000..e808de72d6
Binary files /dev/null and b/docs/datasets/images/table_PubTabNet_demo/PMC535543_007_01.png differ
diff --git a/docs/datasets/images/table_tal_demo/1.jpg b/docs/datasets/images/table_tal_demo/1.jpg
new file mode 100644
index 0000000000..e7ddd6d1db
Binary files /dev/null and b/docs/datasets/images/table_tal_demo/1.jpg differ
diff --git a/docs/datasets/images/table_tal_demo/2.jpg b/docs/datasets/images/table_tal_demo/2.jpg
new file mode 100644
index 0000000000..e7ddd6d1db
Binary files /dev/null and b/docs/datasets/images/table_tal_demo/2.jpg differ
diff --git a/docs/datasets/images/tablebank_demo/004.png b/docs/datasets/images/tablebank_demo/004.png
new file mode 100644
index 0000000000..c1a2d36dfe
Binary files /dev/null and b/docs/datasets/images/tablebank_demo/004.png differ
diff --git a/docs/datasets/images/tablebank_demo/005.png b/docs/datasets/images/tablebank_demo/005.png
new file mode 100644
index 0000000000..0d4d6ab46a
Binary files /dev/null and b/docs/datasets/images/tablebank_demo/005.png differ
diff --git a/docs/datasets/images/wildreceipt_demo/1bbe854b8817dedb8585e0732089fd1f752d2cec.jpeg b/docs/datasets/images/wildreceipt_demo/1bbe854b8817dedb8585e0732089fd1f752d2cec.jpeg
new file mode 100644
index 0000000000..dfed3a0c0e
Binary files /dev/null and b/docs/datasets/images/wildreceipt_demo/1bbe854b8817dedb8585e0732089fd1f752d2cec.jpeg differ
diff --git a/docs/datasets/images/wildreceipt_demo/2769.jpeg b/docs/datasets/images/wildreceipt_demo/2769.jpeg
new file mode 100644
index 0000000000..d5a28763c9
Binary files /dev/null and b/docs/datasets/images/wildreceipt_demo/2769.jpeg differ
diff --git a/docs/datasets/images/xfund_demo/gt_zh_train_0.jpg b/docs/datasets/images/xfund_demo/gt_zh_train_0.jpg
new file mode 100644
index 0000000000..95e0cf8201
Binary files /dev/null and b/docs/datasets/images/xfund_demo/gt_zh_train_0.jpg differ
diff --git a/docs/datasets/images/xfund_demo/gt_zh_train_1.jpg b/docs/datasets/images/xfund_demo/gt_zh_train_1.jpg
new file mode 100644
index 0000000000..6a1e53a3ba
Binary files /dev/null and b/docs/datasets/images/xfund_demo/gt_zh_train_1.jpg differ
diff --git a/docs/datasets/kie_datasets.en.md b/docs/datasets/kie_datasets.en.md
new file mode 100644
index 0000000000..7ac9705033
--- /dev/null
+++ b/docs/datasets/kie_datasets.en.md
@@ -0,0 +1,47 @@
+---
+comments: true
+---
+
+
+## Key Information Extraction dataset
+
+Here are the common datasets key information extraction, which are being updated continuously. Welcome to contribute datasets.
+
+### 1. FUNSD dataset
+
+- **Data source**:
+- **Data Introduction**: The FUNSD dataset is a dataset for form comprehension. It contains 199 real, fully annotated scanned images, including market reports, advertisements, and academic reports, etc., and is divided into 149 training set and 50 test set. The FUNSD dataset is suitable for many types of DocVQA tasks, such as field-level entity classification, field-level entity connection, etc. Part of the image and the annotation box visualization are shown below:
+
+ ![](./images/funsd_demo/gt_train_00040534.jpg)
+
+ ![](./images/funsd_demo/gt_train_00070353.jpg)
+
+ In the figure, the orange area represents `header`, the light blue area represents `question`, the green area represents `answer`, and the pink area represents `other`.
+
+- **Download address**:
+
+### 2. XFUND dataset
+
+- **Data source**:
+- **Data introduction**: XFUND is a multilingual form comprehension dataset, which contains form data in 7 different languages, and all are manually annotated in the form of key-value pairs. The data for each language contains 199 form data, which are divided into 149 training sets and 50 test sets. Part of the image and the annotation box visualization are shown below.
+
+ ![](./images/xfund_demo/gt_zh_train_0.jpg)
+
+ ![](./images/xfund_demo/gt_zh_train_1.jpg)
+
+- **Download address**:
+
+### 3. wildreceipt dataset
+
+- **Data source**:
+- **Data introduction**: wildreceipt is an English receipt dataset, which contains 26 different categories. There are 1267 training images and 472 evaluation images, in which 50,000 textlines and boxes are annotated. Part of the image and the annotation box visualization are shown below.
+
+ ![](./images/wildreceipt_demo/2769.jpeg)
+
+ ![](./images/wildreceipt_demo/1bbe854b8817dedb8585e0732089fd1f752d2cec.jpeg)
+
+**Note:** Boxes with category `Ignore` or `Others` are not visualized here.
+
+- **Download address**:
+ - Offical dataset: [link](https://download.openmmlab.com/mmocr/data/wildreceipt.tar)
+ - Dataset converted for PaddleOCR training process: [link](https://paddleocr.bj.bcebos.com/ppstructure/dataset/wildreceipt.tar)
diff --git a/docs/datasets/kie_datasets.md b/docs/datasets/kie_datasets.md
new file mode 100644
index 0000000000..328ae28bf9
--- /dev/null
+++ b/docs/datasets/kie_datasets.md
@@ -0,0 +1,49 @@
+---
+comments: true
+---
+
+
+# 关键信息抽取数据集
+
+这里整理了常见的关键信息抽取数据集,持续更新中,欢迎各位小伙伴贡献数据集~
+
+## 1. FUNSD数据集
+
+- **数据来源**:
+- **数据简介**:FUNSD数据集是一个用于表单理解的数据集,它包含199张真实的、完全标注的扫描版图片,类型包括市场报告、广告以及学术报告等,并分为149张训练集以及50张测试集。FUNSD数据集适用于多种类型的DocVQA任务,如字段级实体分类、字段级实体连接等。部分图像以及标注框可视化如下所示:
+
+
+
+**注:** 这里对于类别为`Ignore`或者`Others`的文本,没有进行可视化。
+
+- **下载地址**:
+ - 原始数据下载地址:[链接](https://download.openmmlab.com/mmocr/data/wildreceipt.tar)
+ - 数据格式转换后适配于PaddleOCR训练的数据下载地址:[链接](https://paddleocr.bj.bcebos.com/ppstructure/dataset/wildreceipt.tar)
diff --git a/docs/datasets/layout_datasets.en.md b/docs/datasets/layout_datasets.en.md
new file mode 100644
index 0000000000..d3de87a537
--- /dev/null
+++ b/docs/datasets/layout_datasets.en.md
@@ -0,0 +1,46 @@
+---
+comments: true
+---
+
+
+## Layout Analysis Dataset
+
+Here are the common datasets of layout anlysis, which are being updated continuously. Welcome to contribute datasets.
+
+Most of the layout analysis datasets are object detection datasets. In addition to open source datasets, you can also label or synthesize datasets using tools such as [labelme](https://github.com/wkentaro/labelme) and so on.
+
+### 1. PubLayNet dataset
+
+- **Data source**:
+- **Data introduction**: The PubLayNet dataset contains 350000 training images and 11000 validation images. There are 5 categories in total, namely: `text, title, list, table, figure`. Some images and their annotations as shown below.
+
+ ![](./images/publaynet_demo/gt_PMC3724501_00006.jpg)
+
+ ![](./images/publaynet_demo/gt_PMC5086060_00002.jpg)
+
+- **Download address**:
+- **Note**: When using this dataset, you need to follow [CDLA-Permissive](https://cdla.io/permissive-1-0/) license.
+
+### 2、CDLA dataset
+
+- **Data source**:
+- **Data introduction**: CDLA dataset contains 5000 training images and 1000 validation images with 10 categories, which are `Text, Title, Figure, Figure caption, Table, Table caption, Header, Footer, Reference, Equation`. Some images and their annotations as shown below.
+
+ ![](./images/CDLA_demo/val_0633.jpg)
+
+ ![](./images/CDLA_demo/val_0941.jpg)
+
+- **Download address**:
+- **Note**: When you train detection model on CDLA dataset using [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection/tree/develop), you need to remove the label `__ignore__` and `_background_`.
+
+### 3、TableBank dataet
+
+- **Data source**:
+- **Data introduction**: TableBank dataset contains 2 types of document: Latex (187199 training images, 7265 validation images and 5719 testing images) and Word (73383 training images 2735 validation images and 2281 testing images). Some images and their annotations as shown below.
+
+ ![](./images/tablebank_demo/004.png)
+
+ ![](./images/tablebank_demo/005.png)
+
+- **Data source**:
+- **Note**: When using this dataset, you need to follow [Apache-2.0](https://github.com/doc-analysis/TableBank/blob/master/LICENSE) license.
diff --git a/docs/datasets/layout_datasets.md b/docs/datasets/layout_datasets.md
new file mode 100644
index 0000000000..7396048eda
--- /dev/null
+++ b/docs/datasets/layout_datasets.md
@@ -0,0 +1,49 @@
+---
+comments: true
+---
+
+
+## 版面分析数据集
+
+这里整理了常用版面分析数据集,持续更新中,欢迎各位小伙伴贡献数据集~
+
+版面分析数据集多为目标检测数据集,除了开源数据,用户还可使用合成工具自行合成,如[labelme](https://github.com/wkentaro/labelme)等。
+
+### 1、publaynet数据集
+
+- **数据来源**:
+- **数据简介**:publaynet数据集的训练集合中包含35万张图像,验证集合中包含1.1万张图像。总共包含5个类别,分别是: `text, title, list, table, figure`。部分图像以及标注框可视化如下所示。
+
+
+
+- **下载地址**:
+- **说明**:使用该数据集时,需要遵守[Apache-2.0](https://github.com/doc-analysis/TableBank/blob/master/LICENSE)协议。
diff --git a/docs/datasets/ocr_datasets.en.md b/docs/datasets/ocr_datasets.en.md
new file mode 100644
index 0000000000..11825d4fda
--- /dev/null
+++ b/docs/datasets/ocr_datasets.en.md
@@ -0,0 +1,154 @@
+---
+comments: true
+---
+
+
+# OCR datasets
+
+Here is a list of public datasets commonly used in OCR, which are being continuously updated. Welcome to contribute datasets~
+
+## 1. Text detection
+
+### 1.1 PaddleOCR text detection format annotation
+
+The annotation file formats supported by the PaddleOCR text detection algorithm are as follows, separated by "\t":
+
+```text linenums="1"
+" Image file name Image annotation information encoded by json.dumps"
+ch4_test_images/img_61.jpg [{"transcription": "MASA", "points": [[310, 104], [416, 141], [418, 216], [312, 179]]}, {...}]
+```
+
+The image annotation after **json.dumps()** encoding is a list containing multiple dictionaries.
+
+The `points` in the dictionary represent the coordinates (x, y) of the four points of the text box, arranged clockwise from the point at the upper left corner.
+
+`transcription` represents the text of the current text box. **When its content is "###" it means that the text box is invalid and will be skipped during training.**
+
+If you want to train PaddleOCR on other datasets, please build the annotation file according to the above format.
+
+### 1.2 Public dataset
+
+| dataset | Image download link | PaddleOCR format annotation download link |
+|---|---|---|
+| ICDAR 2015 | | [train](https://paddleocr.bj.bcebos.com/dataset/train_icdar2015_label.txt) / [test](https://paddleocr.bj.bcebos.com/dataset/test_icdar2015_label.txt) |
+| ctw1500 | | Included in the downloaded image zip |
+| total text | | Included in the downloaded image zip |
+
+#### 1.2.1 ICDAR 2015
+
+The icdar2015 dataset contains train set which has 1000 images obtained with wearable cameras and test set which has 500 images obtained with wearable cameras. The icdar2015 dataset can be downloaded from the link in the table above. Registration is required for downloading.
+
+After registering and logging in, download the part marked in the red box in the figure below. And, the content downloaded by `Training Set Images` should be saved as the folder `icdar_c4_train_imgs`, and the content downloaded by `Test Set Images` is saved as the folder `ch4_test_images`
+
+![](./images/ic15_location_download.png)
+
+Decompress the downloaded dataset to the working directory, assuming it is decompressed under PaddleOCR/train_data/. Then download the PaddleOCR format annotation file from the table above.
+
+PaddleOCR also provides a data format conversion script, which can convert the official website label to the PaddleOCR format. The data conversion tool is in `ppocr/utils/gen_label.py`, here is the training set as an example:
+
+```bash linenums="1"
+# Convert the label file downloaded from the official website to train_icdar2015_label.txt
+python gen_label.py --mode="det" --root_path="/path/to/icdar_c4_train_imgs/" \
+ --input_path="/path/to/ch4_training_localization_transcription_gt" \
+ --output_label="/path/to/train_icdar2015_label.txt"
+```
+
+After decompressing the data set and downloading the annotation file, PaddleOCR/train_data/ has two folders and two files, which are:
+
+```text linenums="1"
+/PaddleOCR/train_data/icdar2015/text_localization/
+ └─ icdar_c4_train_imgs/ Training data of icdar dataset
+ └─ ch4_test_images/ Testing data of icdar dataset
+ └─ train_icdar2015_label.txt Training annotation of icdar dataset
+ └─ test_icdar2015_label.txt Test annotation of icdar dataset
+```
+
+## 2. Text recognition
+
+### 2.1 PaddleOCR text recognition format annotation
+
+The text recognition algorithm in PaddleOCR supports two data formats:
+
+- `lmdb` is used to train data sets stored in lmdb format, use [lmdb_dataset.py](../../../ppocr/data/lmdb_dataset.py) to load;
+- `common dataset` is used to train data sets stored in text files, use [simple_dataset.py](../../../ppocr/data/simple_dataset.py) to load.
+
+If you want to use your own data for training, please refer to the following to organize your data.
+
+#### Training set
+
+It is recommended to put the training images in the same folder, and use a txt file (rec_gt_train.txt) to store the image path and label. The contents of the txt file are as follows:
+
+- Note: by default, the image path and image label are split with \t, if you use other methods to split, it will cause training error
+
+```text linenums="1"
+" Image file name Image annotation "
+
+train_data/rec/train/word_001.jpg 简单可依赖
+train_data/rec/train/word_002.jpg 用科技让复杂的世界更简单
+...
+```
+
+The final training set should have the following file structure:
+
+```text linenums="1"
+|-train_data
+ |-rec
+ |- rec_gt_train.txt
+ |- train
+ |- word_001.png
+ |- word_002.jpg
+ |- word_003.jpg
+ | ...
+```
+
+#### Test set
+
+Similar to the training set, the test set also needs to be provided a folder containing all images (test) and a rec_gt_test.txt. The structure of the test set is as follows:
+
+```text linenums="1"
+|-train_data
+ |-rec
+ |-ic15_data
+ |- rec_gt_test.txt
+ |- test
+ |- word_001.jpg
+ |- word_002.jpg
+ |- word_003.jpg
+ | ...
+```
+
+### 2.2 Public dataset
+
+| dataset | Image download link | PaddleOCR format annotation download link |
+|---|---|---|
+| en benchmark(MJ, SJ, IIIT, SVT, IC03, IC13, IC15, SVTP, and CUTE.) | [DTRB](https://github.com/clovaai/deep-text-recognition-benchmark#download-lmdb-dataset-for-traininig-and-evaluation-from-here) | LMDB format, which can be loaded directly with [lmdb_dataset.py](../../../ppocr/data/lmdb_dataset.py) |
+|ICDAR 2015| | [train](https://paddleocr.bj.bcebos.com/dataset/rec_gt_train.txt)/ [test](https://paddleocr.bj.bcebos.com/dataset/rec_gt_test.txt) |
+| Multilingual datasets |[Baidu network disk](https://pan.baidu.com/s/1bS_u207Rm7YbY33wOECKDA) Extraction code: frgi [google drive](https://drive.google.com/file/d/18cSWX7wXSy4G0tbKJ0d9PuIaiwRLHpjA/view) | Included in the downloaded image zip |
+
+#### 2.1 ICDAR 2015
+
+The ICDAR 2015 dataset can be downloaded from the link in the table above for quick validation. The lmdb format dataset required by en benchmark can also be downloaded from the table above.
+
+Then download the PaddleOCR format annotation file from the table above.
+
+PaddleOCR also provides a data format conversion script, which can convert the ICDAR official website label to the data format supported by PaddleOCR. The data conversion tool is in `ppocr/utils/gen_label.py`, here is the training set as an example:
+
+```bash linenums="1"
+# Convert the label file downloaded from the official website to rec_gt_label.txt
+python gen_label.py --mode="rec" --input_path="{path/of/origin/label}" --output_label="rec_gt_label.txt"
+```
+
+The data format is as follows, (a) is the original picture, (b) is the Ground Truth text file corresponding to each picture:
+
+![](./images/icdar_rec.png)
+
+## 3. Data storage path
+
+The default storage path for PaddleOCR training data is `PaddleOCR/train_data`, if you already have a dataset on your disk, just create a soft link to the dataset directory:
+
+```bash linenums="1"
+# linux and mac os
+ln -sf /train_data/dataset
+# windows
+mklink /d /train_data/dataset
+```
diff --git a/docs/datasets/ocr_datasets.md b/docs/datasets/ocr_datasets.md
new file mode 100644
index 0000000000..1925d847b2
--- /dev/null
+++ b/docs/datasets/ocr_datasets.md
@@ -0,0 +1,160 @@
+---
+comments: true
+---
+
+
+# OCR数据集
+
+这里整理了OCR中常用的公开数据集,持续更新中,欢迎各位小伙伴贡献数据集~
+
+## 1. 文本检测
+
+### 1.1 PaddleOCR 文字检测数据格式
+
+PaddleOCR 中的文本检测算法支持的标注文件格式如下,中间用"\t"分隔:
+
+```text linenums="1"
+" 图像文件名 json.dumps编码的图像标注信息"
+ch4_test_images/img_61.jpg [{"transcription": "MASA", "points": [[310, 104], [416, 141], [418, 216], [312, 179]]}, {...}]
+```
+
+json.dumps编码前的图像标注信息是包含多个字典的list,字典中的 `points` 表示文本框的四个点的坐标(x, y),从左上角的点开始顺时针排列。
+`transcription` 表示当前文本框的文字,**当其内容为“###”时,表示该文本框无效,在训练时会跳过。**
+
+如果您想在我们未提供的数据集上训练,可以按照上述形式构建标注文件。
+
+### 1.2 公开数据集
+
+| 数据集名称 |图片下载地址| PaddleOCR 标注下载地址 |
+|---|---|---|
+| ICDAR 2015 || [train](https://paddleocr.bj.bcebos.com/dataset/train_icdar2015_label.txt) / [test](https://paddleocr.bj.bcebos.com/dataset/test_icdar2015_label.txt) |
+| ctw1500 || 图片下载地址中已包含 |
+| total text || 图片下载地址中已包含 |
+| td tr || 图片下载地址中已包含 |
+
+#### 1.2.1 ICDAR 2015
+
+ICDAR 2015 数据集包含1000张训练图像和500张测试图像。ICDAR 2015 数据集可以从上表中链接下载,首次下载需注册。
+注册完成登陆后,下载下图中红色框标出的部分,其中, `Training Set Images`下载的内容保存在`icdar_c4_train_imgs`文件夹下,`Test Set Images` 下载的内容保存早`ch4_test_images`文件夹下
+
+
+
+将下载到的数据集解压到工作目录下,假设解压在 PaddleOCR/train_data/下。然后从上表中下载转换好的标注文件。
+
+PaddleOCR 也提供了数据格式转换脚本,可以将官网 label 转换支持的数据格式。 数据转换工具在 `ppocr/utils/gen_label.py`, 这里以训练集为例:
+
+```bash linenums="1"
+# 将官网下载的标签文件转换为 train_icdar2015_label.txt
+python gen_label.py --mode="det" --root_path="/path/to/icdar_c4_train_imgs/" \
+ --input_path="/path/to/ch4_training_localization_transcription_gt" \
+ --output_label="/path/to/train_icdar2015_label.txt"
+```
+
+解压数据集和下载标注文件后,PaddleOCR/train_data/ 有两个文件夹和两个文件,按照如下方式组织icdar2015数据集:
+
+```text linenums="1"
+/PaddleOCR/train_data/icdar2015/text_localization/
+ └─ icdar_c4_train_imgs/ icdar 2015 数据集的训练数据
+ └─ ch4_test_images/ icdar 2015 数据集的测试数据
+ └─ train_icdar2015_label.txt icdar 2015 数据集的训练标注
+ └─ test_icdar2015_label.txt icdar 2015 数据集的测试标注
+```
+
+## 2. 文本识别
+
+### 2.1 PaddleOCR 文字识别数据格式
+
+PaddleOCR 中的文字识别算法支持两种数据格式:
+
+- `lmdb` 用于训练以lmdb格式存储的数据集,使用 [lmdb_dataset.py](../../../ppocr/data/lmdb_dataset.py) 进行读取;
+- `通用数据` 用于训练以文本文件存储的数据集,使用 [simple_dataset.py](../../../ppocr/data/simple_dataset.py)进行读取。
+
+下面以通用数据集为例, 介绍如何准备数据集:
+
+#### 训练集
+
+建议将训练图片放入同一个文件夹,并用一个txt文件(rec_gt_train.txt)记录图片路径和标签,txt文件里的内容如下:
+
+**注意:** txt文件中默认请将图片路径和图片标签用 \t 分割,如用其他方式分割将造成训练报错。
+
+```text linenums="1"
+" 图像文件名 图像标注信息 "
+
+train_data/rec/train/word_001.jpg 简单可依赖
+train_data/rec/train/word_002.jpg 用科技让复杂的世界更简单
+...
+```
+
+最终训练集应有如下文件结构:
+
+```text linenums="1"
+|-train_data
+ |-rec
+ |- rec_gt_train.txt
+ |- train
+ |- word_001.png
+ |- word_002.jpg
+ |- word_003.jpg
+ | ...
+```
+
+除上述单张图像为一行格式之外,PaddleOCR也支持对离线增广后的数据进行训练,为了防止相同样本在同一个batch中被多次采样,我们可以将相同标签对应的图片路径写在一行中,以列表的形式给出,在训练中,PaddleOCR会随机选择列表中的一张图片进行训练。对应地,标注文件的格式如下:
+
+```text linenums="1"
+["11.jpg", "12.jpg"] 简单可依赖
+["21.jpg", "22.jpg", "23.jpg"] 用科技让复杂的世界更简单
+3.jpg ocr
+```
+
+上述示例标注文件中,"11.jpg"和"12.jpg"的标签相同,都是`简单可依赖`,在训练的时候,对于该行标注,会随机选择其中的一张图片进行训练。
+
+#### 验证集
+
+同训练集类似,验证集也需要提供一个包含所有图片的文件夹(test)和一个rec_gt_test.txt,验证集的结构如下所示:
+
+```text linenums="1"
+|-train_data
+ |-rec
+ |- rec_gt_test.txt
+ |- test
+ |- word_001.jpg
+ |- word_002.jpg
+ |- word_003.jpg
+ | ...
+```
+
+### 2.2 公开数据集
+
+| 数据集名称 | 图片下载地址 | PaddleOCR 标注下载地址 |
+|---|---|---------------------------------------------------------------------|
+| en benchmark(MJ, SJ, IIIT, SVT, IC03, IC13, IC15, SVTP, and CUTE.) | [DTRB](https://github.com/clovaai/deep-text-recognition-benchmark#download-lmdb-dataset-for-traininig-and-evaluation-from-here) | LMDB格式,可直接用[lmdb_dataset.py](../../../ppocr/data/lmdb_dataset.py)加载 |
+|ICDAR 2015| | [train](https://paddleocr.bj.bcebos.com/dataset/rec_gt_train.txt)/ [test](https://paddleocr.bj.bcebos.com/dataset/rec_gt_test.txt) |
+| 多语言数据集 |[百度网盘](https://pan.baidu.com/s/1bS_u207Rm7YbY33wOECKDA) 提取码:frgi [google drive](https://drive.google.com/file/d/18cSWX7wXSy4G0tbKJ0d9PuIaiwRLHpjA/view) | 图片下载地址中已包含 |
+
+#### 2.1 ICDAR 2015
+
+ICDAR 2015 数据集可以在上表中链接下载,用于快速验证。也可以从上表中下载 en benchmark 所需的lmdb格式数据集。
+
+下载完图片后从上表中下载转换好的标注文件。
+
+PaddleOCR 也提供了数据格式转换脚本,可以将ICDAR官网 label 转换为PaddleOCR支持的数据格式。 数据转换工具在 `ppocr/utils/gen_label.py`, 这里以训练集为例:
+
+```bash linenums="1"
+# 将官网下载的标签文件转换为 rec_gt_label.txt
+python gen_label.py --mode="rec" --input_path="{path/of/origin/label}" --output_label="rec_gt_label.txt"
+```
+
+数据样式格式如下,(a)为原始图片,(b)为每张图片对应的 Ground Truth 文本文件:
+
+![](./images/icdar_rec.png)
+
+## 3. 数据存放路径
+
+PaddleOCR训练数据的默认存储路径是 `PaddleOCR/train_data`,如果您的磁盘上已有数据集,只需创建软链接至数据集目录:
+
+```bash linenums="1"
+# linux and mac os
+ln -sf /train_data/dataset
+# windows
+mklink /d /train_data/dataset
+```
diff --git a/docs/datasets/table_datasets.en.md b/docs/datasets/table_datasets.en.md
new file mode 100644
index 0000000000..b4f1322cc7
--- /dev/null
+++ b/docs/datasets/table_datasets.en.md
@@ -0,0 +1,40 @@
+---
+comments: true
+---
+
+
+# Table Recognition Datasets
+
+Here are the commonly used table recognition datasets, which are being updated continuously. Welcome to contribute datasets~
+
+## Dataset Summary
+
+| dataset | Image download link | PPOCR format annotation download link |
+|---|---|---|
+| PubTabNet || jsonl format, which can be loaded directly with [pubtab_dataset.py](../../../ppocr/data/pubtab_dataset.py) |
+| TAL Table Recognition Competition Dataset || jsonl format, which can be loaded directly with [pubtab_dataset.py](../../../ppocr/data/pubtab_dataset.py) |
+| WTW Chinese scene table dataset || Conversion is required to load with [pubtab_dataset.py](../../../ppocr/data/pubtab_dataset.py)|
+
+## 1. PubTabNet
+
+- **Data Introduction**:The training set of the PubTabNet dataset contains 500,000 images and the validation set contains 9000 images. Part of the image visualization is shown below.
+
+ ![](./images/table_PubTabNet_demo/PMC524509_007_00.png)
+
+ ![](./images/table_PubTabNet_demo/PMC535543_007_01.png)
+
+- **illustrate**:When using this dataset, the [CDLA-Permissive](https://cdla.io/permissive-1-0/) protocol is required.
+
+## 2. TAL Table Recognition Competition Dataset
+
+- **Data Introduction**:The training set of the TAL table recognition competition dataset contains 16,000 images. The validation set does not give trainable annotations.
+
+ ![](./images/table_tal_demo/1.jpg)
+
+ ![](./images/table_tal_demo/2.jpg)
+
+## 3. WTW Chinese scene table dataset
+
+- **Data Introduction**:The WTW Chinese scene table dataset consists of two parts: table detection and table data. The dataset contains images of two scenes, scanned and photographed.
+
+ ![img](./images/20210816_210413.gif)
diff --git a/docs/datasets/table_datasets.md b/docs/datasets/table_datasets.md
new file mode 100644
index 0000000000..f55a4847b9
--- /dev/null
+++ b/docs/datasets/table_datasets.md
@@ -0,0 +1,43 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+
+# 表格识别数据集
+
+这里整理了常用表格识别数据集,持续更新中,欢迎各位小伙伴贡献数据集~
+
+## 数据集汇总
+
+| 数据集名称 |图片下载地址| PPOCR标注下载地址 |
+|---|---|---|
+| PubTabNet || jsonl格式,可直接用[pubtab_dataset.py](../../../ppocr/data/pubtab_dataset.py)加载 |
+| 好未来表格识别竞赛数据集 || jsonl格式,可直接用[pubtab_dataset.py](../../../ppocr/data/pubtab_dataset.py)加载 |
+| WTW中文场景表格数据集 || 需要进行转换后才能用[pubtab_dataset.py](../../../ppocr/data/pubtab_dataset.py)加载 |
+
+## 1. PubTabNet数据集
+
+- **数据简介**:PubTabNet数据集的训练集合中包含50万张图像,验证集合中包含0.9万张图像。部分图像可视化如下所示。
+
+
+
+## 3. WTW中文场景表格数据集
+
+- **数据简介**:WTW中文场景表格数据集包含表格检测和表格数据两部分数据,数据集中同时包含扫描和拍照两张场景的图像。
+
+ ![img](./images/20210816_210413.gif)
diff --git a/docs/datasets/vertical_and_multilingual_datasets.en.md b/docs/datasets/vertical_and_multilingual_datasets.en.md
new file mode 100644
index 0000000000..321c66c51a
--- /dev/null
+++ b/docs/datasets/vertical_and_multilingual_datasets.en.md
@@ -0,0 +1,72 @@
+---
+comments: true
+---
+
+
+# Vertical multi-language OCR dataset
+
+Here we have sorted out the commonly used vertical multi-language OCR dataset datasets, which are being updated continuously. We welcome you to contribute datasets ~
+
+- [Chinese urban license plate dataset](#Chinese urban license plate dataset)
+- [Bank credit card dataset](#Bank credit card dataset)
+- [Captcha dataset-Captcha](#Captcha dataset-Captcha)
+- [multi-language dataset](#multi-language dataset)
+
+## Chinese urban license plate dataset
+
+- **Data source**:[CCPD](https://github.com/detectRecog/CCPD)
+
+- **Data introduction**: It contains more than 250000 vehicle license plate images and vehicle license plate detection and recognition information labeling. It contains the following license plate image information in different scenes.
+
+ - CCPD-Base: General license plate picture
+ - CCPD-DB: The brightness of license plate area is bright, dark or uneven
+ - CCPD-FN: The license plate is farther or closer to the camera location
+ - CCPD-Rotate: License plate includes rotation (horizontal 20\~50 degrees, vertical-10\~10 degrees)
+ - CCPD-Tilt: License plate includes rotation (horizontal 15\~45 degrees, vertical 15\~45 degrees)
+ - CCPD-Blur: The license plate contains blurring due to camera lens jitter
+ - CCPD-Weather: The license plate is photographed on rainy, snowy or foggy days
+ - CCPD-Challenge: So far, some of the most challenging images in license plate detection and recognition tasks
+ - CCPD-NP: Pictures of new cars without license plates.
+
+ ![](./images/ccpd_demo.png)
+
+- **Download address**
+ - Baidu cloud download address (extracted code is hm0U): [https://pan.baidu.com/s/1i5AOjAbtkwb17Zy-NQGqkw](https://pan.baidu.com/s/1i5AOjAbtkwb17Zy-NQGqkw)
+ - Google drive download address:[https://drive.google.com/file/d/1rdEsCUcIUaYOVRkx5IMTRNA7PcGMmSgc/view](https://drive.google.com/file/d/1rdEsCUcIUaYOVRkx5IMTRNA7PcGMmSgc/view)
+
+## Bank credit card dataset
+
+- **Data source**: [source](https://www.kesci.com/home/dataset/5954cf1372ead054a5e25870)
+
+- **Data introduction**: There are three types of training data
+ - 1.Sample card data of China Merchants Bank: including card image data and annotation data, a total of 618 pictures
+ - 2.Single character data: including pictures and annotation data, 37 pictures in total.
+ - 3.There are only other bank cards, no more detailed information, a total of 50 pictures.
+
+ - The demo image is shown as follows. The annotation information is stored in excel, and the demo image below is marked as
+ - Top 8 card number: 62257583
+ - Card type: card of our bank
+ - End of validity: 07/41
+ - Chinese phonetic alphabet of card users: MICHAEL
+
+ ![](./images/cmb_demo.jpg)
+
+- **Download address**: [cmb2017-2.zip](https://cdn.kesci.com/cmb2017-2.zip)
+
+## Captcha dataset-Captcha
+
+- **Data source**: [captcha](https://github.com/lepture/captcha)
+- **Data introduction**: This is a toolkit for data synthesis. You can output captcha images according to the input text. Use the toolkit to generate several demo images as follows.
+
+ ![](./images/captcha_demo.png)
+
+- **Download address**: The dataset is generated and has no download address.
+
+## multi-language dataset(Multi-lingual scene text detection and recognition)
+
+- **Data source**: [source](https://rrc.cvc.uab.es/?ch=15&com=downloads)
+- **Data introduction**: Multi language detection dataset MLT contains both language recognition and detection tasks.
+ - In the detection task, the training set contains 10000 images in 10 languages, and each language contains 1000 training images. The test set contains 10000 images.
+ - In the recognition task, the training set contains 111998 samples.
+- **Download address**: The training set is large and can be downloaded in two parts. It can only be downloaded after registering on the website:
+[source](https://rrc.cvc.uab.es/?ch=15&com=downloads)
diff --git a/docs/datasets/vertical_and_multilingual_datasets.md b/docs/datasets/vertical_and_multilingual_datasets.md
new file mode 100644
index 0000000000..f5386b7bb8
--- /dev/null
+++ b/docs/datasets/vertical_and_multilingual_datasets.md
@@ -0,0 +1,65 @@
+---
+comments: true
+---
+
+
+# 垂类多语言OCR数据集
+
+这里整理了常用垂类和多语言OCR数据集,持续更新中,欢迎各位小伙伴贡献数据集~
+
+## 中国城市车牌数据集
+
+- **数据来源**:[CCPD](https://github.com/detectRecog/CCPD)
+- **数据简介**: 包含超过25万张中国城市车牌图片及车牌检测、识别信息的标注。包含以下几种不同场景中的车牌图片信息。
+ - CCPD-Base: 通用车牌图片
+ - CCPD-DB: 车牌区域亮度较亮、较暗或者不均匀
+ - CCPD-FN: 车牌离摄像头拍摄位置相对更远或者更近
+ - CCPD-Rotate: 车牌包含旋转(水平20\~50度,竖直-10\~10度)
+ - CCPD-Tilt: 车牌包含旋转(水平15\~45度,竖直15\~45度)
+ - CCPD-Blur: 车牌包含由于摄像机镜头抖动导致的模糊情况
+ - CCPD-Weather: 车牌在雨天、雪天或者雾天拍摄得到
+ - CCPD-Challenge: 至今在车牌检测识别任务中最有挑战性的一些图片
+ - CCPD-NP: 没有安装车牌的新车图片。
+
+ ![](./images/ccpd_demo.png)
+
+- **下载地址**
+ - 百度云下载地址(提取码是hm0U): [link](https://pan.baidu.com/s/1i5AOjAbtkwb17Zy-NQGqkw)
+ - Google drive下载地址:[link](https://drive.google.com/file/d/1rdEsCUcIUaYOVRkx5IMTRNA7PcGMmSgc/view)
+
+## 银行信用卡数据集
+
+- **数据来源**: [source](https://www.kesci.com/home/dataset/5954cf1372ead054a5e25870)
+
+- **数据简介**: 训练数据共提供了三类数据
+ - 1.招行样卡数据: 包括卡面图片数据及标注数据,总共618张图片
+ - 2.单字符数据: 包括图片及标注数据,总共37张图片。
+ - 3.仅包含其他银行卡面,不具有更细致的信息,总共50张图片。
+
+ - demo图片展示如下,标注信息存储在excel表格中,下面的demo图片标注为
+ - 前8位卡号:62257583
+ - 卡片种类:本行卡
+ - 有效期结束:07/41
+ - 卡用户拼音:MICHAEL
+
+ ![](./images/cmb_demo.jpg)
+
+- **下载地址**: [cmb2017-2.zip](https://cdn.kesci.com/cmb2017-2.zip)
+
+## 验证码数据集-Captcha
+
+- **数据来源**: [captcha](https://github.com/lepture/captcha)
+- **数据简介**: 这是一个数据合成的工具包,可以根据输入的文本,输出验证码图片,使用该工具包生成几张demo图片如下:
+
+ ![](./images/captcha_demo.png)
+
+- **下载地址**: 该数据集是生成得到,无下载地址。
+
+## 多语言数据集(Multi-lingual scene text detection and recognition)
+
+- **数据来源**: [source](https://rrc.cvc.uab.es/?ch=15&com=downloads)
+- **数据简介**: 多语言检测数据集MLT同时包含了语种识别和检测任务。
+ - 在检测任务中,训练集包含10000张图片,共有10种语言,每种语言包含1000张训练图片。测试集包含10000张图片。
+ - 在识别任务中,训练集包含111998个样本。
+- **下载地址**: 训练集较大,分2部分下载,需要在网站上注册之后才能下载:
+[link](https://rrc.cvc.uab.es/?ch=15&com=downloads)
diff --git a/docs/images/00006737.jpg b/docs/images/00006737.jpg
new file mode 100644
index 0000000000..d7762d2e2c
Binary files /dev/null and b/docs/images/00006737.jpg differ
diff --git a/docs/images/185310636-6ce02f7c-790d-479f-b163-ea97a5a04808-20240708082238739.jpg b/docs/images/185310636-6ce02f7c-790d-479f-b163-ea97a5a04808-20240708082238739.jpg
new file mode 100644
index 0000000000..6a5fd84c52
Binary files /dev/null and b/docs/images/185310636-6ce02f7c-790d-479f-b163-ea97a5a04808-20240708082238739.jpg differ
diff --git a/docs/images/185393805-c67ff571-cf7e-4217-a4b0-8b396c4f22bb-20240708082310650.jpg b/docs/images/185393805-c67ff571-cf7e-4217-a4b0-8b396c4f22bb-20240708082310650.jpg
new file mode 100644
index 0000000000..d406a52da8
Binary files /dev/null and b/docs/images/185393805-c67ff571-cf7e-4217-a4b0-8b396c4f22bb-20240708082310650.jpg differ
diff --git a/docs/images/185539517-ccf2372a-f026-4a7c-ad28-c741c770f60a-20240708082247529.png b/docs/images/185539517-ccf2372a-f026-4a7c-ad28-c741c770f60a-20240708082247529.png
new file mode 100644
index 0000000000..429343418e
Binary files /dev/null and b/docs/images/185539517-ccf2372a-f026-4a7c-ad28-c741c770f60a-20240708082247529.png differ
diff --git a/docs/images/185540080-0431e006-9235-4b6d-b63d-0b3c6e1de48f-20240708082316558.jpg b/docs/images/185540080-0431e006-9235-4b6d-b63d-0b3c6e1de48f-20240708082316558.jpg
new file mode 100644
index 0000000000..bdef2ed23b
Binary files /dev/null and b/docs/images/185540080-0431e006-9235-4b6d-b63d-0b3c6e1de48f-20240708082316558.jpg differ
diff --git a/docs/images/186094813-3a8e16cc-42e5-4982-b9f4-0134dfb5688d-20240708082323916.png b/docs/images/186094813-3a8e16cc-42e5-4982-b9f4-0134dfb5688d-20240708082323916.png
new file mode 100644
index 0000000000..44845fb5ce
Binary files /dev/null and b/docs/images/186094813-3a8e16cc-42e5-4982-b9f4-0134dfb5688d-20240708082323916.png differ
diff --git a/docs/images/186171245-40abc4d7-904f-4949-ade1-250f86ed3a90.jpg b/docs/images/186171245-40abc4d7-904f-4949-ade1-250f86ed3a90.jpg
new file mode 100644
index 0000000000..3e3fde4e69
Binary files /dev/null and b/docs/images/186171245-40abc4d7-904f-4949-ade1-250f86ed3a90.jpg differ
diff --git a/docs/images/197464552-69de557f-edff-4c7f-acbf-069df1ba097f-20240708082253634.png b/docs/images/197464552-69de557f-edff-4c7f-acbf-069df1ba097f-20240708082253634.png
new file mode 100644
index 0000000000..7b4c271cf2
Binary files /dev/null and b/docs/images/197464552-69de557f-edff-4c7f-acbf-069df1ba097f-20240708082253634.png differ
diff --git a/docs/images/PP-OCRv3-pic001.jpg b/docs/images/PP-OCRv3-pic001.jpg
new file mode 100644
index 0000000000..c35936cc1a
Binary files /dev/null and b/docs/images/PP-OCRv3-pic001.jpg differ
diff --git a/docs/images/PP-OCRv3-pic002.jpg b/docs/images/PP-OCRv3-pic002.jpg
new file mode 100644
index 0000000000..e5ad6a4b2a
Binary files /dev/null and b/docs/images/PP-OCRv3-pic002.jpg differ
diff --git a/docs/images/PP-OCRv3-pic003.jpg b/docs/images/PP-OCRv3-pic003.jpg
new file mode 100644
index 0000000000..dc024296bd
Binary files /dev/null and b/docs/images/PP-OCRv3-pic003.jpg differ
diff --git a/docs/images/en_1.png b/docs/images/en_1.png
new file mode 100644
index 0000000000..36245613e3
Binary files /dev/null and b/docs/images/en_1.png differ
diff --git a/docs/images/en_2.png b/docs/images/en_2.png
new file mode 100644
index 0000000000..d2df8556ad
Binary files /dev/null and b/docs/images/en_2.png differ
diff --git a/docs/images/en_3-0398013.png b/docs/images/en_3-0398013.png
new file mode 100644
index 0000000000..baf146c010
Binary files /dev/null and b/docs/images/en_3-0398013.png differ
diff --git a/docs/images/en_3.png b/docs/images/en_3.png
new file mode 100644
index 0000000000..baf146c010
Binary files /dev/null and b/docs/images/en_3.png differ
diff --git a/docs/images/japan_2.jpg b/docs/images/japan_2.jpg
new file mode 100644
index 0000000000..076ced92ad
Binary files /dev/null and b/docs/images/japan_2.jpg differ
diff --git a/docs/images/korean_1.jpg b/docs/images/korean_1.jpg
new file mode 100644
index 0000000000..f93de40e18
Binary files /dev/null and b/docs/images/korean_1.jpg differ
diff --git a/docs/images/ppocrv4.png b/docs/images/ppocrv4.png
new file mode 100644
index 0000000000..6be449cf53
Binary files /dev/null and b/docs/images/ppocrv4.png differ
diff --git a/docs/images/ppstructure-20240708082235651.gif b/docs/images/ppstructure-20240708082235651.gif
new file mode 100644
index 0000000000..bff836e3ea
Binary files /dev/null and b/docs/images/ppstructure-20240708082235651.gif differ
diff --git a/docs/images/test_add_91.jpg b/docs/images/test_add_91.jpg
new file mode 100644
index 0000000000..b5ded6e1de
Binary files /dev/null and b/docs/images/test_add_91.jpg differ
diff --git a/docs/index.en.md b/docs/index.en.md
new file mode 100644
index 0000000000..9c8baf14ad
--- /dev/null
+++ b/docs/index.en.md
@@ -0,0 +1,154 @@
+---
+comments: true
+typora-copy-images-to: images
+hide:
+ - navigation
+ - toc
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Introduction
+
+PaddleOCR aims to create multilingual, awesome, leading, and practical OCR tools that help users train better models and apply them into practice.
+
+## 🚀 Community
+
+PaddleOCR is being oversight by a [PMC](https://github.com/PaddlePaddle/PaddleOCR/issues/12122). Issues and PRs will be reviewed on a best-effort basis. For a complete overview of PaddlePaddle community, please visit [community](https://github.com/PaddlePaddle/community).
+
+⚠️ Note: The [Issues](https://github.com/PaddlePaddle/PaddleOCR/issues) module is only for reporting program 🐞 bugs, for the rest of the questions, please move to the [Discussions](https://github.com/PaddlePaddle/PaddleOCR/discussions). Please note that if the Issue mentioned is not a bug, it will be moved to the Discussions module.
+
+## 📣 Recent updates
+
+- **🔥2023.8.7 Release PaddleOCR[release/2.7](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.7)**
+
+ - Release [PP-OCRv4](./ppocr/overview.en.md), support mobile version and server version
+
+ - PP-OCRv4-mobile:When the speed is comparable, the effect of the Chinese scene is improved by 4.5% compared with PP-OCRv3, the English scene is improved by 10%, and the average recognition accuracy of the 80-language multilingual model is increased by more than 8%.
+ - PP-OCRv4-server:Release the OCR model with the highest accuracy at present, the detection model accuracy increased by 4.9% in the Chinese and English scenes, and the recognition model accuracy increased by 2%
+ refer [quickstart](./quick_start.en.md) quick use by one line command, At the same time, the whole process of model training, reasoning, and high-performance deployment can also be completed with few code in the [General OCR Industry Solution](https://aistudio.baidu.com/aistudio/modelsdetail?modelId=286) in PaddleX.
+
+ - Release[PP-ChatOCR](https://aistudio.baidu.com/aistudio/modelsdetail?modelId=332), a new scheme for extracting key information of general scenes using PP-OCR model and ERNIE LLM.
+
+- 🔨**2022.11 Add implementation of [4 cutting-edge algorithms](./algorithm/overview.en.md)**:Text Detection [DRRG](./algorithm/text_detection/algorithm_det_drrg.en.md), Text Recognition [RFL](./algorithm/text_recognition/algorithm_rec_rfl.en.md), Image Super-Resolution [Text Telescope](./algorithm/super_resolution/algorithm_sr_telescope.en.md),Handwritten Mathematical Expression Recognition [CAN](./algorithm/formula_recognition/algorithm_rec_can.en.md)
+
+- **2022.10 release [optimized JS version PP-OCRv3 model](./ppocr/infer_deploy/paddle_js.en.md)** with 4.3M model size, 8x faster inference time, and a ready-to-use web demo
+
+ - 💥 **Live Playback: Introduction to PP-StructureV2 optimization strategy**. Scan [the QR code below](#Community) using WeChat, follow the PaddlePaddle official account and fill out the questionnaire to join the WeChat group, get the live link and 20G OCR learning materials (including PDF2Word application, 10 models in vertical scenarios, etc.)
+
+- **🔥2022.8.24 Release PaddleOCR [release/2.6](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.6)**
+
+ - Release [PP-StructureV2](./ppstructure/),with functions and performance fully upgraded, adapted to Chinese scenes, and new support for [Layout Recovery](./ppstructure/model_train/recovery_to_doc.en.md) and **one line command to convert PDF to Word**;
+ - [Layout Analysis](./ppstructure/model_train/train_layout.en.md) optimization: model storage reduced by 95%, while speed increased by 11 times, and the average CPU time-cost is only 41ms;
+ - [Table Recognition](./ppstructure/model_train/train_table.en.md) optimization: 3 optimization strategies are designed, and the model accuracy is improved by 6% under comparable time consumption;
+ - [Key Information Extraction](./ppstructure/model_train/train_kie.en.md) optimization:a visual-independent model structure is designed, the accuracy of semantic entity recognition is increased by 2.8%, and the accuracy of relation extraction is increased by 9.1%.
+
+- **🔥2022.8 Release [OCR scene application collection](./applications/overview.md)**
+
+ - Release **9 vertical models** such as digital tube, LCD screen, license plate, handwriting recognition model, high-precision SVTR model, etc, covering the main OCR vertical applications in general, manufacturing, finance, and transportation industries.
+
+- **2022.8 Add implementation of [8 cutting-edge algorithms](./algorithm/overview.en.md)**
+
+ - Text Detection: [FCENet](./algorithm/text_detection/algorithm_det_fcenet.en.md), [DB++](./algorithm/text_detection/algorithm_det_db.en.md)
+ - Text Recognition: [ViTSTR](./algorithm/text_recognition/algorithm_rec_vitstr.en.md), [ABINet](./algorithm/text_recognition/algorithm_rec_abinet.en.md), [VisionLAN](./algorithm/text_recognition/algorithm_rec_visionlan.en.md), [SPIN](./algorithm/text_recognition/algorithm_rec_spin.en.md), [RobustScanner](./algorithm/text_recognition/algorithm_rec_robustscanner.en.md)
+ - Table Recognition: [TableMaster](./algorithm/table_recognition/algorithm_table_master.en.md)
+- **2022.5.9 Release PaddleOCR [release/2.5](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.5)**
+
+ - Release [PP-OCRv3](./ppocr/overview.en.md#pp-ocrv3): With comparable speed, the effect of Chinese scene is further improved by 5% compared with PP-OCRv2, the effect of English scene is improved by 11%, and the average recognition accuracy of 80 language multilingual models is improved by more than 5%.
+ - Release [PPOCRLabelv2](https://github.com/PFCCLab/PPOCRLabel): Add the annotation function for table recognition task, key information extraction task and irregular text image.
+ - Release interactive e-book [*"Dive into OCR"*](./blog/ocr_book.en.md), covers the cutting-edge theory and code practice of OCR full stack technology.
+- [more](./update.en.md)
+
+## 🌟 Features
+
+PaddleOCR support a variety of cutting-edge algorithms related to OCR, and developed industrial featured models/solution [PP-OCR](./ppocr/overview.md)、[PP-Structure](./ppstructure/overview.md) and [PP-ChatOCR](https://aistudio.baidu.com/aistudio/projectdetail/6488689) on this basis, and get through the whole process of data production, model training, compression, inference and deployment.
+
+![img](./images/186171245-40abc4d7-904f-4949-ade1-250f86ed3a90.jpg)
+
+> It is recommended to start with the “quick experience” in the document tutorial
+
+## 📖 Technical exchange and cooperation
+
+- PaddleX provides a one-stop full-process high-efficiency development platform for flying paddle ecological model training, pressure, and push. Its mission is to help AI technology quickly land, and its vision is to make everyone an AI Developer!
+
+ - PaddleX currently covers areas such as image classification, object detection, image segmentation, 3D, OCR, and time series prediction, and has built-in 36 basic single models, such as RP-DETR, PP-YOLOE, PP-HGNet, PP-LCNet, PP- LiteSeg, etc.; integrated 12 practical industrial solutions, such as PP-OCRv4, PP-ChatOCR, PP-ShiTu, PP-TS, vehicle-mounted road waste detection, identification of prohibited wildlife products, etc.
+ - PaddleX provides two AI development modes: "Toolbox" and "Developer". The toolbox mode can tune key hyperparameters without code, and the developer mode can perform single-model training, push and multi-model serial inference with low code, and supports both cloud and local terminals.
+ - PaddleX also supports joint innovation and development, profit sharing! At present, PaddleX is rapidly iterating, and welcomes the participation of individual developers and enterprise developers to create a prosperous AI technology ecosystem!
+
+## 🇺🇳 Guideline for New Language Requests
+
+If you want to request a new language support, a PR with 1 following files are needed:
+
+- In folder [ppocr/utils/dict](./ppocr/utils/dict),
+it is necessary to submit the dict text to this path and name it with `{language}_dict.txt` that contains a list of all characters. Please see the format example from other files in that folder.
+
+If your language has unique elements, please tell me in advance within any way, such as useful links, wikipedia and so on.
+
+More details, please refer to [Multilingual OCR Development Plan](https://github.com/PaddlePaddle/PaddleOCR/issues/1048).
+
+## Visualization
+
+## PP-OCRv3
+
+### PP-OCRv3 Chinese model
+
+![img](./images/test_add_91.jpg)
+
+![img](./images/00006737.jpg)
+
+![](./images/PP-OCRv3-pic001.jpg)
+
+![](./images/PP-OCRv3-pic002.jpg)
+
+![](./images/PP-OCRv3-pic003.jpg)
+
+### PP-OCRv3 English model
+
+![](./images/en_1.png)
+
+![](./images/en_2.png)
+
+![](./images/en_3-0398013.png)
+
+### PP-OCRv3 Multilingual model
+
+![img](./images/japan_2.jpg)
+
+![img](./images/korean_1.jpg)
+
+#### PP-StructureV2
+
+- layout analysis + table recognition
+
+ ![img](./images/ppstructure-20240708082235651.gif)
+
+- SER (Semantic entity recognition)
+
+ ![img](./images/185310636-6ce02f7c-790d-479f-b163-ea97a5a04808-20240708082238739.jpg)
+
+ ![img](./images/185539517-ccf2372a-f026-4a7c-ad28-c741c770f60a-20240708082247529.png)
+
+ ![img](./images/197464552-69de557f-edff-4c7f-acbf-069df1ba097f-20240708082253634.png)
+
+- RE (Relation Extraction)
+
+ ![img](./images/185393805-c67ff571-cf7e-4217-a4b0-8b396c4f22bb-20240708082310650.jpg)
+
+ ![img](./images/185540080-0431e006-9235-4b6d-b63d-0b3c6e1de48f-20240708082316558.jpg)
+
+ ![img](./images/186094813-3a8e16cc-42e5-4982-b9f4-0134dfb5688d-20240708082323916.png)
+
+## 📄 License
+
+This project is released under Apache 2.0 license
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000000..93e112a929
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,137 @@
+---
+comments: true
+typora-copy-images-to: images
+hide:
+ - navigation
+ - toc
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 简介
+
+PaddleOCR 旨在打造一套丰富、领先、且实用的 OCR 工具库,助力开发者训练出更好的模型,并应用落地。
+
+## 🚀 社区
+
+PaddleOCR 由 [PMC](https://github.com/PaddlePaddle/PaddleOCR/issues/12122) 监督。Issues 和 PRs 将在尽力的基础上进行审查。欲了解 PaddlePaddle 社区的完整概况,请访问 [community](https://github.com/PaddlePaddle/community)。
+
+⚠️注意:[Issues](https://github.com/PaddlePaddle/PaddleOCR/issues)模块仅用来报告程序🐞Bug,其余提问请移步[Discussions](https://github.com/PaddlePaddle/PaddleOCR/discussions)模块提问。如所提Issue不是Bug,会被移到Discussions模块,敬请谅解。
+
+## 📣 近期更新
+
+- **🔥2024.7 添加 PaddleOCR 算法模型挑战赛冠军方案**:
+
+ - 赛题一:OCR 端到端识别任务冠军方案——[场景文本识别算法-SVTRv2](./algorithm/text_recognition/algorithm_rec_svtrv2.md);
+ - 赛题二:通用表格识别任务冠军方案——[表格识别算法-SLANet-LCNetV2](./algorithm/table_recognition/algorithm_table_slanet.md)。
+
+- **💥2024.6.27 飞桨低代码开发工具 [PaddleX 3.0](https://github.com/paddlepaddle/paddlex) 重磅更新!**
+
+ - 低代码开发范式:支持 OCR 模型全流程低代码开发,提供 Python API,支持用户自定义串联模型;
+ - 多硬件训推支持:支持英伟达 GPU、昆仑芯、昇腾和寒武纪等多种硬件进行模型训练与推理。PaddleOCR支持的模型见 [模型列表](./model/hardware/install_other_devices.md)
+
+- **📚直播和OCR实战打卡营预告**:《PP-ChatOCRv2赋能金融报告信息智能化抽取,新金融效率再升级》课程上线,破解复杂版面、表格识别、信息抽取OCR解析难题,直播时间:6月6日(周四)19:00。并于6月11日启动【政务采购合同信息抽取】实战打卡营。报名链接:
+
+- **🔥2024.5.10 上线星河零代码产线(OCR 相关)**:全面覆盖了以下四大 OCR 核心任务,提供极便捷的 Badcase 分析和实用的在线体验:
+
+ - [通用 OCR](https://aistudio.baidu.com/community/app/91660) (PP-OCRv4)。
+ - [通用表格识别](https://aistudio.baidu.com/community/app/91661) (SLANet)。
+ - [通用图像信息抽取](https://aistudio.baidu.com/community/app/91662) (PP-ChatOCRv2-common)。
+ - [文档场景信息抽取](https://aistudio.baidu.com/community/app/70303) (PP-ChatOCRv2-doc)。
+
+ 同时采用了 **[全新的场景任务开发范式](https://aistudio.baidu.com/pipeline/mine)** ,将模型统一汇聚,实现训练部署的零代码开发,并支持在线服务化部署和导出离线服务化部署包。
+
+- **🔥2023.8.7 发布 PaddleOCR [release/2.7](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.7)**
+
+ - 发布[PP-OCRv4](./ppocr/blog/PP-OCRv4_introduction.md),提供 mobile 和 server 两种模型
+ - PP-OCRv4-mobile:速度可比情况下,中文场景效果相比于 PP-OCRv3 再提升 4.5%,英文场景提升 10%,80 语种多语言模型平均识别准确率提升 8%以上
+ - PP-OCRv4-server:发布了目前精度最高的 OCR 模型,中英文场景上检测模型精度提升 4.9%, 识别模型精度提升 2%
+ 可参考[快速开始](./quick_start.md) 一行命令快速使用,同时也可在飞桨 AI 套件(PaddleX)中的[通用 OCR 产业方案](https://aistudio.baidu.com/aistudio/modelsdetail?modelId=286)中低代码完成模型训练、推理、高性能部署全流程
+
+- 🔨**2022.11 新增实现[4 种前沿算法](./algorithm/overview.md)**:文本检测 [DRRG](./algorithm/text_detection/algorithm_det_drrg.md), 文本识别 [RFL](./algorithm/text_recognition/algorithm_rec_rfl.md), 文本超分[Text Telescope](./algorithm/super_resolution/algorithm_sr_telescope.md),公式识别[CAN](./algorithm/formula_recognition/algorithm_rec_can.md)
+- **2022.10 优化[JS 版 PP-OCRv3 模型](./ppocr/infer_deploy/paddle_js.md)**:模型大小仅 4.3M,预测速度提升 8 倍,配套 web demo 开箱即用
+
+- **💥 直播回放:PaddleOCR 研发团队详解 PP-StructureV2 优化策略**。微信扫描[下方二维码](#开源社区),关注公众号并填写问卷后进入官方交流群,获取直播回放链接与 20G 重磅 OCR 学习大礼包(内含 PDF 转 Word 应用程序、10 种垂类模型、《动手学 OCR》电子书等)
+
+- **🔥2022.8.24 发布 PaddleOCR [release/2.6](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.6)**:
+
+ - 发布[PP-StructureV2](./ppstructure/overview.md),系统功能性能全面升级,适配中文场景,新增支持[版面复原](./ppstructure/model_train/recovery_to_doc.md),支持**一行命令完成 PDF 转 Word**;
+ - [版面分析](./ppstructure/model_train/train_layout.md)模型优化:模型存储减少 95%,速度提升 11 倍,平均 CPU 耗时仅需 41ms;
+ - [表格识别](./ppstructure/model_train/train_table.md)模型优化:设计 3 大优化策略,预测耗时不变情况下,模型精度提升 6%;
+ - [关键信息抽取](./ppstructure/model_train/train_kie.md)模型优化:设计视觉无关模型结构,语义实体识别精度提升 2.8%,关系抽取精度提升 9.1%。
+- 🔥**2022.8 发布 [OCR 场景应用集合](./applications/overview.md)**:包含数码管、液晶屏、车牌、高精度 SVTR 模型、手写体识别等**9 个垂类模型**,覆盖通用,制造、金融、交通行业的主要 OCR 垂类应用。
+
+> [更多](./update.md)
+
+## 🌟 特性
+
+支持多种 OCR 相关前沿算法,在此基础上打造产业级特色模型[PP-OCR](./ppocr/overview.md)、[PP-Structure](./ppstructure/overview.md)和[PP-ChatOCRv2](https://aistudio.baidu.com/community/app/70303),并打通数据生产、模型训练、压缩、预测部署全流程。
+
+
+
+## 效果展示
+
+### 超轻量PP-OCRv3效果展示
+
+#### PP-OCRv3中文模型
+
+![img](./images/test_add_91.jpg)
+
+
+
+
+
+
+
+
+
+#### PP-OCRv3英文数字模型
+
+
+
+
+
+
+
+#### PP-OCRv3多语言模型
+
+
+
+
+
+#### PP-Structure 文档分析
+
+- 版面分析+表格识别
+
+
+
+- SER(语义实体识别)
+
+
+
+
+
+
+
+- RE(关系提取)
+
+
+
+
+
+
+
+## 许可证书
+
+本项目的发布受Apache 2.0 license许可认证。
diff --git a/docs/javascripts/katex.min.js b/docs/javascripts/katex.min.js
new file mode 100644
index 0000000000..fee59b98a5
--- /dev/null
+++ b/docs/javascripts/katex.min.js
@@ -0,0 +1,12 @@
+document$.subscribe(({ body }) => {
+ renderMathInElement(body, {
+ delimiters: [
+ { left: "$$", right: "$$", display: true },
+ { left: "$", right: "$", display: false },
+ { left: "\\(", right: "\\)", display: false },
+ { left: "\\[", right: "\\]", display: true }
+ ],
+ })
+})
+
+!function (e, t) { "object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.katex = t() : e.katex = t() }("undefined" != typeof self ? self : this, (function () { return function () { "use strict"; var e = { d: function (t, r) { for (var n in r) e.o(r, n) && !e.o(t, n) && Object.defineProperty(t, n, { enumerable: !0, get: r[n] }) }, o: function (e, t) { return Object.prototype.hasOwnProperty.call(e, t) } }, t = {}; e.d(t, { default: function () { return Yn } }); class r { constructor(e, t) { this.name = void 0, this.position = void 0, this.length = void 0, this.rawMessage = void 0; let n, o, s = "KaTeX parse error: " + e; const i = t && t.loc; if (i && i.start <= i.end) { const e = i.lexer.input; n = i.start, o = i.end, n === e.length ? s += " at end of input: " : s += " at position " + (n + 1) + ": "; const t = e.slice(n, o).replace(/[^]/g, "$&\u0332"); let r, a; r = n > 15 ? "\u2026" + e.slice(n - 15, n) : e.slice(0, n), a = o + 15 < e.length ? e.slice(o, o + 15) + "\u2026" : e.slice(o), s += r + t + a } const a = new Error(s); return a.name = "ParseError", a.__proto__ = r.prototype, a.position = n, null != n && null != o && (a.length = o - n), a.rawMessage = e, a } } r.prototype.__proto__ = Error.prototype; var n = r; const o = /([A-Z])/g, s = { "&": "&", ">": ">", "<": "<", '"': """, "'": "'" }, i = /[&><"']/g; const a = function (e) { return "ordgroup" === e.type || "color" === e.type ? 1 === e.body.length ? a(e.body[0]) : e : "font" === e.type ? a(e.body) : e }; var l = { contains: function (e, t) { return -1 !== e.indexOf(t) }, deflt: function (e, t) { return void 0 === e ? t : e }, escape: function (e) { return String(e).replace(i, (e => s[e])) }, hyphenate: function (e) { return e.replace(o, "-$1").toLowerCase() }, getBaseElem: a, isCharacterBox: function (e) { const t = a(e); return "mathord" === t.type || "textord" === t.type || "atom" === t.type }, protocolFromUrl: function (e) { const t = /^[\x00-\x20]*([^\\/#?]*?)(:|*58|*3a|&colon)/i.exec(e); return t ? ":" !== t[2] ? null : /^[a-zA-Z][a-zA-Z0-9+\-.]*$/.test(t[1]) ? t[1].toLowerCase() : null : "_relative" } }; const h = { displayMode: { type: "boolean", description: "Render math in display mode, which puts the math in display style (so \\int and \\sum are large, for example), and centers the math on the page on its own line.", cli: "-d, --display-mode" }, output: { type: { enum: ["htmlAndMathml", "html", "mathml"] }, description: "Determines the markup language of the output.", cli: "-F, --format " }, leqno: { type: "boolean", description: "Render display math in leqno style (left-justified tags)." }, fleqn: { type: "boolean", description: "Render display math flush left." }, throwOnError: { type: "boolean", default: !0, cli: "-t, --no-throw-on-error", cliDescription: "Render errors (in the color given by --error-color) instead of throwing a ParseError exception when encountering an error." }, errorColor: { type: "string", default: "#cc0000", cli: "-c, --error-color ", cliDescription: "A color string given in the format 'rgb' or 'rrggbb' (no #). This option determines the color of errors rendered by the -t option.", cliProcessor: e => "#" + e }, macros: { type: "object", cli: "-m, --macro ", cliDescription: "Define custom macro of the form '\\foo:expansion' (use multiple -m arguments for multiple macros).", cliDefault: [], cliProcessor: (e, t) => (t.push(e), t) }, minRuleThickness: { type: "number", description: "Specifies a minimum thickness, in ems, for fraction lines, `\\sqrt` top lines, `{array}` vertical lines, `\\hline`, `\\hdashline`, `\\underline`, `\\overline`, and the borders of `\\fbox`, `\\boxed`, and `\\fcolorbox`.", processor: e => Math.max(0, e), cli: "--min-rule-thickness ", cliProcessor: parseFloat }, colorIsTextColor: { type: "boolean", description: "Makes \\color behave like LaTeX's 2-argument \\textcolor, instead of LaTeX's one-argument \\color mode change.", cli: "-b, --color-is-text-color" }, strict: { type: [{ enum: ["warn", "ignore", "error"] }, "boolean", "function"], description: "Turn on strict / LaTeX faithfulness mode, which throws an error if the input uses features that are not supported by LaTeX.", cli: "-S, --strict", cliDefault: !1 }, trust: { type: ["boolean", "function"], description: "Trust the input, enabling all HTML features such as \\url.", cli: "-T, --trust" }, maxSize: { type: "number", default: 1 / 0, description: "If non-zero, all user-specified sizes, e.g. in \\rule{500em}{500em}, will be capped to maxSize ems. Otherwise, elements and spaces can be arbitrarily large", processor: e => Math.max(0, e), cli: "-s, --max-size ", cliProcessor: parseInt }, maxExpand: { type: "number", default: 1e3, description: "Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to Infinity, the macro expander will try to fully expand as in LaTeX.", processor: e => Math.max(0, e), cli: "-e, --max-expand ", cliProcessor: e => "Infinity" === e ? 1 / 0 : parseInt(e) }, globalGroup: { type: "boolean", cli: !1 } }; function c(e) { if (e.default) return e.default; const t = e.type, r = Array.isArray(t) ? t[0] : t; if ("string" != typeof r) return r.enum[0]; switch (r) { case "boolean": return !1; case "string": return ""; case "number": return 0; case "object": return {} } } class m { constructor(e) { this.displayMode = void 0, this.output = void 0, this.leqno = void 0, this.fleqn = void 0, this.throwOnError = void 0, this.errorColor = void 0, this.macros = void 0, this.minRuleThickness = void 0, this.colorIsTextColor = void 0, this.strict = void 0, this.trust = void 0, this.maxSize = void 0, this.maxExpand = void 0, this.globalGroup = void 0, e = e || {}; for (const t in h) if (h.hasOwnProperty(t)) { const r = h[t]; this[t] = void 0 !== e[t] ? r.processor ? r.processor(e[t]) : e[t] : c(r) } } reportNonstrict(e, t, r) { let o = this.strict; if ("function" == typeof o && (o = o(e, t, r)), o && "ignore" !== o) { if (!0 === o || "error" === o) throw new n("LaTeX-incompatible input and strict mode is set to 'error': " + t + " [" + e + "]", r); "warn" === o ? "undefined" != typeof console && console.warn("LaTeX-incompatible input and strict mode is set to 'warn': " + t + " [" + e + "]") : "undefined" != typeof console && console.warn("LaTeX-incompatible input and strict mode is set to unrecognized '" + o + "': " + t + " [" + e + "]") } } useStrictBehavior(e, t, r) { let n = this.strict; if ("function" == typeof n) try { n = n(e, t, r) } catch (e) { n = "error" } return !(!n || "ignore" === n) && (!0 === n || "error" === n || ("warn" === n ? ("undefined" != typeof console && console.warn("LaTeX-incompatible input and strict mode is set to 'warn': " + t + " [" + e + "]"), !1) : ("undefined" != typeof console && console.warn("LaTeX-incompatible input and strict mode is set to unrecognized '" + n + "': " + t + " [" + e + "]"), !1))) } isTrusted(e) { if (e.url && !e.protocol) { const t = l.protocolFromUrl(e.url); if (null == t) return !1; e.protocol = t } const t = "function" == typeof this.trust ? this.trust(e) : this.trust; return Boolean(t) } } class p { constructor(e, t, r) { this.id = void 0, this.size = void 0, this.cramped = void 0, this.id = e, this.size = t, this.cramped = r } sup() { return u[d[this.id]] } sub() { return u[g[this.id]] } fracNum() { return u[f[this.id]] } fracDen() { return u[b[this.id]] } cramp() { return u[y[this.id]] } text() { return u[x[this.id]] } isTight() { return this.size >= 2 } } const u = [new p(0, 0, !1), new p(1, 0, !0), new p(2, 1, !1), new p(3, 1, !0), new p(4, 2, !1), new p(5, 2, !0), new p(6, 3, !1), new p(7, 3, !0)], d = [4, 5, 4, 5, 6, 7, 6, 7], g = [5, 5, 5, 5, 7, 7, 7, 7], f = [2, 3, 4, 5, 6, 7, 6, 7], b = [3, 3, 5, 5, 7, 7, 7, 7], y = [1, 1, 3, 3, 5, 5, 7, 7], x = [0, 1, 2, 3, 2, 3, 2, 3]; var w = { DISPLAY: u[0], TEXT: u[2], SCRIPT: u[4], SCRIPTSCRIPT: u[6] }; const v = [{ name: "latin", blocks: [[256, 591], [768, 879]] }, { name: "cyrillic", blocks: [[1024, 1279]] }, { name: "armenian", blocks: [[1328, 1423]] }, { name: "brahmic", blocks: [[2304, 4255]] }, { name: "georgian", blocks: [[4256, 4351]] }, { name: "cjk", blocks: [[12288, 12543], [19968, 40879], [65280, 65376]] }, { name: "hangul", blocks: [[44032, 55215]] }]; const k = []; function S(e) { for (let t = 0; t < k.length; t += 2)if (e >= k[t] && e <= k[t + 1]) return !0; return !1 } v.forEach((e => e.blocks.forEach((e => k.push(...e))))); const M = 80, z = { doubleleftarrow: "M262 157\nl10-10c34-36 62.7-77 86-123 3.3-8 5-13.3 5-16 0-5.3-6.7-8-20-8-7.3\n 0-12.2.5-14.5 1.5-2.3 1-4.8 4.5-7.5 10.5-49.3 97.3-121.7 169.3-217 216-28\n 14-57.3 25-88 33-6.7 2-11 3.8-13 5.5-2 1.7-3 4.2-3 7.5s1 5.8 3 7.5\nc2 1.7 6.3 3.5 13 5.5 68 17.3 128.2 47.8 180.5 91.5 52.3 43.7 93.8 96.2 124.5\n 157.5 9.3 8 15.3 12.3 18 13h6c12-.7 18-4 18-10 0-2-1.7-7-5-15-23.3-46-52-87\n-86-123l-10-10h399738v-40H218c328 0 0 0 0 0l-10-8c-26.7-20-65.7-43-117-69 2.7\n-2 6-3.7 10-5 36.7-16 72.3-37.3 107-64l10-8h399782v-40z\nm8 0v40h399730v-40zm0 194v40h399730v-40z", doublerightarrow: "M399738 392l\n-10 10c-34 36-62.7 77-86 123-3.3 8-5 13.3-5 16 0 5.3 6.7 8 20 8 7.3 0 12.2-.5\n 14.5-1.5 2.3-1 4.8-4.5 7.5-10.5 49.3-97.3 121.7-169.3 217-216 28-14 57.3-25 88\n-33 6.7-2 11-3.8 13-5.5 2-1.7 3-4.2 3-7.5s-1-5.8-3-7.5c-2-1.7-6.3-3.5-13-5.5-68\n-17.3-128.2-47.8-180.5-91.5-52.3-43.7-93.8-96.2-124.5-157.5-9.3-8-15.3-12.3-18\n-13h-6c-12 .7-18 4-18 10 0 2 1.7 7 5 15 23.3 46 52 87 86 123l10 10H0v40h399782\nc-328 0 0 0 0 0l10 8c26.7 20 65.7 43 117 69-2.7 2-6 3.7-10 5-36.7 16-72.3 37.3\n-107 64l-10 8H0v40zM0 157v40h399730v-40zm0 194v40h399730v-40z", leftarrow: "M400000 241H110l3-3c68.7-52.7 113.7-120\n 135-202 4-14.7 6-23 6-25 0-7.3-7-11-21-11-8 0-13.2.8-15.5 2.5-2.3 1.7-4.2 5.8\n-5.5 12.5-1.3 4.7-2.7 10.3-4 17-12 48.7-34.8 92-68.5 130S65.3 228.3 18 247\nc-10 4-16 7.7-18 11 0 8.7 6 14.3 18 17 47.3 18.7 87.8 47 121.5 85S196 441.3 208\n 490c.7 2 1.3 5 2 9s1.2 6.7 1.5 8c.3 1.3 1 3.3 2 6s2.2 4.5 3.5 5.5c1.3 1 3.3\n 1.8 6 2.5s6 1 10 1c14 0 21-3.7 21-11 0-2-2-10.3-6-25-20-79.3-65-146.7-135-202\n l-3-3h399890zM100 241v40h399900v-40z", leftbrace: "M6 548l-6-6v-35l6-11c56-104 135.3-181.3 238-232 57.3-28.7 117\n-45 179-50h399577v120H403c-43.3 7-81 15-113 26-100.7 33-179.7 91-237 174-2.7\n 5-6 9-10 13-.7 1-7.3 1-20 1H6z", leftbraceunder: "M0 6l6-6h17c12.688 0 19.313.3 20 1 4 4 7.313 8.3 10 13\n 35.313 51.3 80.813 93.8 136.5 127.5 55.688 33.7 117.188 55.8 184.5 66.5.688\n 0 2 .3 4 1 18.688 2.7 76 4.3 172 5h399450v120H429l-6-1c-124.688-8-235-61.7\n-331-161C60.687 138.7 32.312 99.3 7 54L0 41V6z", leftgroup: "M400000 80\nH435C64 80 168.3 229.4 21 260c-5.9 1.2-18 0-18 0-2 0-3-1-3-3v-38C76 61 257 0\n 435 0h399565z", leftgroupunder: "M400000 262\nH435C64 262 168.3 112.6 21 82c-5.9-1.2-18 0-18 0-2 0-3 1-3 3v38c76 158 257 219\n 435 219h399565z", leftharpoon: "M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3\n-3.3 10.2-9.5 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5\n-18.3 3-21-1.3-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7\n-196 228-6.7 4.7-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40z", leftharpoonplus: "M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3-3.3 10.2-9.5\n 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5-18.3 3-21-1.3\n-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7-196 228-6.7 4.7\n-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40zM0 435v40h400000v-40z\nm0 0v40h400000v-40z", leftharpoondown: "M7 241c-4 4-6.333 8.667-7 14 0 5.333.667 9 2 11s5.333\n 5.333 12 10c90.667 54 156 130 196 228 3.333 10.667 6.333 16.333 9 17 2 .667 5\n 1 9 1h5c10.667 0 16.667-2 18-6 2-2.667 1-9.667-3-21-32-87.333-82.667-157.667\n-152-211l-3-3h399907v-40zM93 281 H400000 v-40L7 241z", leftharpoondownplus: "M7 435c-4 4-6.3 8.7-7 14 0 5.3.7 9 2 11s5.3 5.3 12\n 10c90.7 54 156 130 196 228 3.3 10.7 6.3 16.3 9 17 2 .7 5 1 9 1h5c10.7 0 16.7\n-2 18-6 2-2.7 1-9.7-3-21-32-87.3-82.7-157.7-152-211l-3-3h399907v-40H7zm93 0\nv40h399900v-40zM0 241v40h399900v-40zm0 0v40h399900v-40z", lefthook: "M400000 281 H103s-33-11.2-61-33.5S0 197.3 0 164s14.2-61.2 42.5\n-83.5C70.8 58.2 104 47 142 47 c16.7 0 25 6.7 25 20 0 12-8.7 18.7-26 20-40 3.3\n-68.7 15.7-86 37-10 12-15 25.3-15 40 0 22.7 9.8 40.7 29.5 54 19.7 13.3 43.5 21\n 71.5 23h399859zM103 281v-40h399897v40z", leftlinesegment: "M40 281 V428 H0 V94 H40 V241 H400000 v40z\nM40 281 V428 H0 V94 H40 V241 H400000 v40z", leftmapsto: "M40 281 V448H0V74H40V241H400000v40z\nM40 281 V448H0V74H40V241H400000v40z", leftToFrom: "M0 147h400000v40H0zm0 214c68 40 115.7 95.7 143 167h22c15.3 0 23\n-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69-70-101l-7-8h399905v-40H95l7-8\nc28.7-32 52-65.7 70-101 10.7-23.3 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 265.3\n 68 321 0 361zm0-174v-40h399900v40zm100 154v40h399900v-40z", longequal: "M0 50 h400000 v40H0z m0 194h40000v40H0z\nM0 50 h400000 v40H0z m0 194h40000v40H0z", midbrace: "M200428 334\nc-100.7-8.3-195.3-44-280-108-55.3-42-101.7-93-139-153l-9-14c-2.7 4-5.7 8.7-9 14\n-53.3 86.7-123.7 153-211 199-66.7 36-137.3 56.3-212 62H0V214h199568c178.3-11.7\n 311.7-78.3 403-201 6-8 9.7-12 11-12 .7-.7 6.7-1 18-1s17.3.3 18 1c1.3 0 5 4 11\n 12 44.7 59.3 101.3 106.3 170 141s145.3 54.3 229 60h199572v120z", midbraceunder: "M199572 214\nc100.7 8.3 195.3 44 280 108 55.3 42 101.7 93 139 153l9 14c2.7-4 5.7-8.7 9-14\n 53.3-86.7 123.7-153 211-199 66.7-36 137.3-56.3 212-62h199568v120H200432c-178.3\n 11.7-311.7 78.3-403 201-6 8-9.7 12-11 12-.7.7-6.7 1-18 1s-17.3-.3-18-1c-1.3 0\n-5-4-11-12-44.7-59.3-101.3-106.3-170-141s-145.3-54.3-229-60H0V214z", oiintSize1: "M512.6 71.6c272.6 0 320.3 106.8 320.3 178.2 0 70.8-47.7 177.6\n-320.3 177.6S193.1 320.6 193.1 249.8c0-71.4 46.9-178.2 319.5-178.2z\nm368.1 178.2c0-86.4-60.9-215.4-368.1-215.4-306.4 0-367.3 129-367.3 215.4 0 85.8\n60.9 214.8 367.3 214.8 307.2 0 368.1-129 368.1-214.8z", oiintSize2: "M757.8 100.1c384.7 0 451.1 137.6 451.1 230 0 91.3-66.4 228.8\n-451.1 228.8-386.3 0-452.7-137.5-452.7-228.8 0-92.4 66.4-230 452.7-230z\nm502.4 230c0-111.2-82.4-277.2-502.4-277.2s-504 166-504 277.2\nc0 110 84 276 504 276s502.4-166 502.4-276z", oiiintSize1: "M681.4 71.6c408.9 0 480.5 106.8 480.5 178.2 0 70.8-71.6 177.6\n-480.5 177.6S202.1 320.6 202.1 249.8c0-71.4 70.5-178.2 479.3-178.2z\nm525.8 178.2c0-86.4-86.8-215.4-525.7-215.4-437.9 0-524.7 129-524.7 215.4 0\n85.8 86.8 214.8 524.7 214.8 438.9 0 525.7-129 525.7-214.8z", oiiintSize2: "M1021.2 53c603.6 0 707.8 165.8 707.8 277.2 0 110-104.2 275.8\n-707.8 275.8-606 0-710.2-165.8-710.2-275.8C311 218.8 415.2 53 1021.2 53z\nm770.4 277.1c0-131.2-126.4-327.6-770.5-327.6S248.4 198.9 248.4 330.1\nc0 130 128.8 326.4 772.7 326.4s770.5-196.4 770.5-326.4z", rightarrow: "M0 241v40h399891c-47.3 35.3-84 78-110 128\n-16.7 32-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20\n 11 8 0 13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7\n 39-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85\n-40.5-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5\n-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67\n 151.7 139 205zm0 0v40h399900v-40z", rightbrace: "M400000 542l\n-6 6h-17c-12.7 0-19.3-.3-20-1-4-4-7.3-8.3-10-13-35.3-51.3-80.8-93.8-136.5-127.5\ns-117.2-55.8-184.5-66.5c-.7 0-2-.3-4-1-18.7-2.7-76-4.3-172-5H0V214h399571l6 1\nc124.7 8 235 61.7 331 161 31.3 33.3 59.7 72.7 85 118l7 13v35z", rightbraceunder: "M399994 0l6 6v35l-6 11c-56 104-135.3 181.3-238 232-57.3\n 28.7-117 45-179 50H-300V214h399897c43.3-7 81-15 113-26 100.7-33 179.7-91 237\n-174 2.7-5 6-9 10-13 .7-1 7.3-1 20-1h17z", rightgroup: "M0 80h399565c371 0 266.7 149.4 414 180 5.9 1.2 18 0 18 0 2 0\n 3-1 3-3v-38c-76-158-257-219-435-219H0z", rightgroupunder: "M0 262h399565c371 0 266.7-149.4 414-180 5.9-1.2 18 0 18\n 0 2 0 3 1 3 3v38c-76 158-257 219-435 219H0z", rightharpoon: "M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3\n-3.7-15.3-11-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2\n-10.7 0-16.7 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58\n 69.2 92 94.5zm0 0v40h399900v-40z", rightharpoonplus: "M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3-3.7-15.3-11\n-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2-10.7 0-16.7\n 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 69.2 92 94.5z\nm0 0v40h399900v-40z m100 194v40h399900v-40zm0 0v40h399900v-40z", rightharpoondown: "M399747 511c0 7.3 6.7 11 20 11 8 0 13-.8 15-2.5s4.7-6.8\n 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 8.5-5.8 9.5\n-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3-64.7 57-92 95\n-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 241v40h399900v-40z", rightharpoondownplus: "M399747 705c0 7.3 6.7 11 20 11 8 0 13-.8\n 15-2.5s4.7-6.8 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3\n 8.5-5.8 9.5-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3\n-64.7 57-92 95-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 435v40h399900v-40z\nm0-194v40h400000v-40zm0 0v40h400000v-40z", righthook: "M399859 241c-764 0 0 0 0 0 40-3.3 68.7-15.7 86-37 10-12 15-25.3\n 15-40 0-22.7-9.8-40.7-29.5-54-19.7-13.3-43.5-21-71.5-23-17.3-1.3-26-8-26-20 0\n-13.3 8.7-20 26-20 38 0 71 11.2 99 33.5 0 0 7 5.6 21 16.7 14 11.2 21 33.5 21\n 66.8s-14 61.2-42 83.5c-28 22.3-61 33.5-99 33.5L0 241z M0 281v-40h399859v40z", rightlinesegment: "M399960 241 V94 h40 V428 h-40 V281 H0 v-40z\nM399960 241 V94 h40 V428 h-40 V281 H0 v-40z", rightToFrom: "M400000 167c-70.7-42-118-97.7-142-167h-23c-15.3 0-23 .3-23\n 1 0 1.3 5.3 13.7 16 37 18 35.3 41.3 69 70 101l7 8H0v40h399905l-7 8c-28.7 32\n-52 65.7-70 101-10.7 23.3-16 35.7-16 37 0 .7 7.7 1 23 1h23c24-69.3 71.3-125 142\n-167z M100 147v40h399900v-40zM0 341v40h399900v-40z", twoheadleftarrow: "M0 167c68 40\n 115.7 95.7 143 167h22c15.3 0 23-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69\n-70-101l-7-8h125l9 7c50.7 39.3 85 86 103 140h46c0-4.7-6.3-18.7-19-42-18-35.3\n-40-67.3-66-96l-9-9h399716v-40H284l9-9c26-28.7 48-60.7 66-96 12.7-23.333 19\n-37.333 19-42h-46c-18 54-52.3 100.7-103 140l-9 7H95l7-8c28.7-32 52-65.7 70-101\n 10.7-23.333 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 71.3 68 127 0 167z", twoheadrightarrow: "M400000 167\nc-68-40-115.7-95.7-143-167h-22c-15.3 0-23 .3-23 1 0 1.3 5.3 13.7 16 37 18 35.3\n 41.3 69 70 101l7 8h-125l-9-7c-50.7-39.3-85-86-103-140h-46c0 4.7 6.3 18.7 19 42\n 18 35.3 40 67.3 66 96l9 9H0v40h399716l-9 9c-26 28.7-48 60.7-66 96-12.7 23.333\n-19 37.333-19 42h46c18-54 52.3-100.7 103-140l9-7h125l-7 8c-28.7 32-52 65.7-70\n 101-10.7 23.333-16 35.7-16 37 0 .7 7.7 1 23 1h22c27.3-71.3 75-127 143-167z", tilde1: "M200 55.538c-77 0-168 73.953-177 73.953-3 0-7\n-2.175-9-5.437L2 97c-1-2-2-4-2-6 0-4 2-7 5-9l20-12C116 12 171 0 207 0c86 0\n 114 68 191 68 78 0 168-68 177-68 4 0 7 2 9 5l12 19c1 2.175 2 4.35 2 6.525 0\n 4.35-2 7.613-5 9.788l-19 13.05c-92 63.077-116.937 75.308-183 76.128\n-68.267.847-113-73.952-191-73.952z", tilde2: "M344 55.266c-142 0-300.638 81.316-311.5 86.418\n-8.01 3.762-22.5 10.91-23.5 5.562L1 120c-1-2-1-3-1-4 0-5 3-9 8-10l18.4-9C160.9\n 31.9 283 0 358 0c148 0 188 122 331 122s314-97 326-97c4 0 8 2 10 7l7 21.114\nc1 2.14 1 3.21 1 4.28 0 5.347-3 9.626-7 10.696l-22.3 12.622C852.6 158.372 751\n 181.476 676 181.476c-149 0-189-126.21-332-126.21z", tilde3: "M786 59C457 59 32 175.242 13 175.242c-6 0-10-3.457\n-11-10.37L.15 138c-1-7 3-12 10-13l19.2-6.4C378.4 40.7 634.3 0 804.3 0c337 0\n 411.8 157 746.8 157 328 0 754-112 773-112 5 0 10 3 11 9l1 14.075c1 8.066-.697\n 16.595-6.697 17.492l-21.052 7.31c-367.9 98.146-609.15 122.696-778.15 122.696\n -338 0-409-156.573-744-156.573z", tilde4: "M786 58C457 58 32 177.487 13 177.487c-6 0-10-3.345\n-11-10.035L.15 143c-1-7 3-12 10-13l22-6.7C381.2 35 637.15 0 807.15 0c337 0 409\n 177 744 177 328 0 754-127 773-127 5 0 10 3 11 9l1 14.794c1 7.805-3 13.38-9\n 14.495l-20.7 5.574c-366.85 99.79-607.3 139.372-776.3 139.372-338 0-409\n -175.236-744-175.236z", vec: "M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5\n3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11\n10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63\n-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1\n-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59\nH213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359\nc-16-25.333-24-45-24-59z", widehat1: "M529 0h5l519 115c5 1 9 5 9 10 0 1-1 2-1 3l-4 22\nc-1 5-5 9-11 9h-2L532 67 19 159h-2c-5 0-9-4-11-9l-5-22c-1-6 2-12 8-13z", widehat2: "M1181 0h2l1171 176c6 0 10 5 10 11l-2 23c-1 6-5 10\n-11 10h-1L1182 67 15 220h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z", widehat3: "M1181 0h2l1171 236c6 0 10 5 10 11l-2 23c-1 6-5 10\n-11 10h-1L1182 67 15 280h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z", widehat4: "M1181 0h2l1171 296c6 0 10 5 10 11l-2 23c-1 6-5 10\n-11 10h-1L1182 67 15 340h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z", widecheck1: "M529,159h5l519,-115c5,-1,9,-5,9,-10c0,-1,-1,-2,-1,-3l-4,-22c-1,\n-5,-5,-9,-11,-9h-2l-512,92l-513,-92h-2c-5,0,-9,4,-11,9l-5,22c-1,6,2,12,8,13z", widecheck2: "M1181,220h2l1171,-176c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\n-11,-10h-1l-1168,153l-1167,-153h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z", widecheck3: "M1181,280h2l1171,-236c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\n-11,-10h-1l-1168,213l-1167,-213h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z", widecheck4: "M1181,340h2l1171,-296c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10,\n-11,-10h-1l-1168,273l-1167,-273h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z", baraboveleftarrow: "M400000 620h-399890l3 -3c68.7 -52.7 113.7 -120 135 -202\nc4 -14.7 6 -23 6 -25c0 -7.3 -7 -11 -21 -11c-8 0 -13.2 0.8 -15.5 2.5\nc-2.3 1.7 -4.2 5.8 -5.5 12.5c-1.3 4.7 -2.7 10.3 -4 17c-12 48.7 -34.8 92 -68.5 130\ns-74.2 66.3 -121.5 85c-10 4 -16 7.7 -18 11c0 8.7 6 14.3 18 17c47.3 18.7 87.8 47\n121.5 85s56.5 81.3 68.5 130c0.7 2 1.3 5 2 9s1.2 6.7 1.5 8c0.3 1.3 1 3.3 2 6\ns2.2 4.5 3.5 5.5c1.3 1 3.3 1.8 6 2.5s6 1 10 1c14 0 21 -3.7 21 -11\nc0 -2 -2 -10.3 -6 -25c-20 -79.3 -65 -146.7 -135 -202l-3 -3h399890z\nM100 620v40h399900v-40z M0 241v40h399900v-40zM0 241v40h399900v-40z", rightarrowabovebar: "M0 241v40h399891c-47.3 35.3-84 78-110 128-16.7 32\n-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 11 8 0\n13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 39\n-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85-40.5\n-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5\n-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67\n151.7 139 205zm96 379h399894v40H0zm0 0h399904v40H0z", baraboveshortleftharpoon: "M507,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11\nc1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17\nc2,0.7,5,1,9,1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21\nc-32,-87.3,-82.7,-157.7,-152,-211c0,0,-3,-3,-3,-3l399351,0l0,-40\nc-398570,0,-399437,0,-399437,0z M593 435 v40 H399500 v-40z\nM0 281 v-40 H399908 v40z M0 281 v-40 H399908 v40z", rightharpoonaboveshortbar: "M0,241 l0,40c399126,0,399993,0,399993,0\nc4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199,\n-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6\nc-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z\nM0 241 v40 H399908 v-40z M0 475 v-40 H399500 v40z M0 475 v-40 H399500 v40z", shortbaraboveleftharpoon: "M7,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11\nc1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17c2,0.7,5,1,9,\n1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21c-32,-87.3,-82.7,-157.7,\n-152,-211c0,0,-3,-3,-3,-3l399907,0l0,-40c-399126,0,-399993,0,-399993,0z\nM93 435 v40 H400000 v-40z M500 241 v40 H400000 v-40z M500 241 v40 H400000 v-40z", shortrightharpoonabovebar: "M53,241l0,40c398570,0,399437,0,399437,0\nc4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199,\n-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6\nc-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z\nM500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z" }; class A { constructor(e) { this.children = void 0, this.classes = void 0, this.height = void 0, this.depth = void 0, this.maxFontSize = void 0, this.style = void 0, this.children = e, this.classes = [], this.height = 0, this.depth = 0, this.maxFontSize = 0, this.style = {} } hasClass(e) { return l.contains(this.classes, e) } toNode() { const e = document.createDocumentFragment(); for (let t = 0; t < this.children.length; t++)e.appendChild(this.children[t].toNode()); return e } toMarkup() { let e = ""; for (let t = 0; t < this.children.length; t++)e += this.children[t].toMarkup(); return e } toText() { return this.children.map((e => e.toText())).join("") } } var T = { "AMS-Regular": { 32: [0, 0, 0, 0, .25], 65: [0, .68889, 0, 0, .72222], 66: [0, .68889, 0, 0, .66667], 67: [0, .68889, 0, 0, .72222], 68: [0, .68889, 0, 0, .72222], 69: [0, .68889, 0, 0, .66667], 70: [0, .68889, 0, 0, .61111], 71: [0, .68889, 0, 0, .77778], 72: [0, .68889, 0, 0, .77778], 73: [0, .68889, 0, 0, .38889], 74: [.16667, .68889, 0, 0, .5], 75: [0, .68889, 0, 0, .77778], 76: [0, .68889, 0, 0, .66667], 77: [0, .68889, 0, 0, .94445], 78: [0, .68889, 0, 0, .72222], 79: [.16667, .68889, 0, 0, .77778], 80: [0, .68889, 0, 0, .61111], 81: [.16667, .68889, 0, 0, .77778], 82: [0, .68889, 0, 0, .72222], 83: [0, .68889, 0, 0, .55556], 84: [0, .68889, 0, 0, .66667], 85: [0, .68889, 0, 0, .72222], 86: [0, .68889, 0, 0, .72222], 87: [0, .68889, 0, 0, 1], 88: [0, .68889, 0, 0, .72222], 89: [0, .68889, 0, 0, .72222], 90: [0, .68889, 0, 0, .66667], 107: [0, .68889, 0, 0, .55556], 160: [0, 0, 0, 0, .25], 165: [0, .675, .025, 0, .75], 174: [.15559, .69224, 0, 0, .94666], 240: [0, .68889, 0, 0, .55556], 295: [0, .68889, 0, 0, .54028], 710: [0, .825, 0, 0, 2.33334], 732: [0, .9, 0, 0, 2.33334], 770: [0, .825, 0, 0, 2.33334], 771: [0, .9, 0, 0, 2.33334], 989: [.08167, .58167, 0, 0, .77778], 1008: [0, .43056, .04028, 0, .66667], 8245: [0, .54986, 0, 0, .275], 8463: [0, .68889, 0, 0, .54028], 8487: [0, .68889, 0, 0, .72222], 8498: [0, .68889, 0, 0, .55556], 8502: [0, .68889, 0, 0, .66667], 8503: [0, .68889, 0, 0, .44445], 8504: [0, .68889, 0, 0, .66667], 8513: [0, .68889, 0, 0, .63889], 8592: [-.03598, .46402, 0, 0, .5], 8594: [-.03598, .46402, 0, 0, .5], 8602: [-.13313, .36687, 0, 0, 1], 8603: [-.13313, .36687, 0, 0, 1], 8606: [.01354, .52239, 0, 0, 1], 8608: [.01354, .52239, 0, 0, 1], 8610: [.01354, .52239, 0, 0, 1.11111], 8611: [.01354, .52239, 0, 0, 1.11111], 8619: [0, .54986, 0, 0, 1], 8620: [0, .54986, 0, 0, 1], 8621: [-.13313, .37788, 0, 0, 1.38889], 8622: [-.13313, .36687, 0, 0, 1], 8624: [0, .69224, 0, 0, .5], 8625: [0, .69224, 0, 0, .5], 8630: [0, .43056, 0, 0, 1], 8631: [0, .43056, 0, 0, 1], 8634: [.08198, .58198, 0, 0, .77778], 8635: [.08198, .58198, 0, 0, .77778], 8638: [.19444, .69224, 0, 0, .41667], 8639: [.19444, .69224, 0, 0, .41667], 8642: [.19444, .69224, 0, 0, .41667], 8643: [.19444, .69224, 0, 0, .41667], 8644: [.1808, .675, 0, 0, 1], 8646: [.1808, .675, 0, 0, 1], 8647: [.1808, .675, 0, 0, 1], 8648: [.19444, .69224, 0, 0, .83334], 8649: [.1808, .675, 0, 0, 1], 8650: [.19444, .69224, 0, 0, .83334], 8651: [.01354, .52239, 0, 0, 1], 8652: [.01354, .52239, 0, 0, 1], 8653: [-.13313, .36687, 0, 0, 1], 8654: [-.13313, .36687, 0, 0, 1], 8655: [-.13313, .36687, 0, 0, 1], 8666: [.13667, .63667, 0, 0, 1], 8667: [.13667, .63667, 0, 0, 1], 8669: [-.13313, .37788, 0, 0, 1], 8672: [-.064, .437, 0, 0, 1.334], 8674: [-.064, .437, 0, 0, 1.334], 8705: [0, .825, 0, 0, .5], 8708: [0, .68889, 0, 0, .55556], 8709: [.08167, .58167, 0, 0, .77778], 8717: [0, .43056, 0, 0, .42917], 8722: [-.03598, .46402, 0, 0, .5], 8724: [.08198, .69224, 0, 0, .77778], 8726: [.08167, .58167, 0, 0, .77778], 8733: [0, .69224, 0, 0, .77778], 8736: [0, .69224, 0, 0, .72222], 8737: [0, .69224, 0, 0, .72222], 8738: [.03517, .52239, 0, 0, .72222], 8739: [.08167, .58167, 0, 0, .22222], 8740: [.25142, .74111, 0, 0, .27778], 8741: [.08167, .58167, 0, 0, .38889], 8742: [.25142, .74111, 0, 0, .5], 8756: [0, .69224, 0, 0, .66667], 8757: [0, .69224, 0, 0, .66667], 8764: [-.13313, .36687, 0, 0, .77778], 8765: [-.13313, .37788, 0, 0, .77778], 8769: [-.13313, .36687, 0, 0, .77778], 8770: [-.03625, .46375, 0, 0, .77778], 8774: [.30274, .79383, 0, 0, .77778], 8776: [-.01688, .48312, 0, 0, .77778], 8778: [.08167, .58167, 0, 0, .77778], 8782: [.06062, .54986, 0, 0, .77778], 8783: [.06062, .54986, 0, 0, .77778], 8785: [.08198, .58198, 0, 0, .77778], 8786: [.08198, .58198, 0, 0, .77778], 8787: [.08198, .58198, 0, 0, .77778], 8790: [0, .69224, 0, 0, .77778], 8791: [.22958, .72958, 0, 0, .77778], 8796: [.08198, .91667, 0, 0, .77778], 8806: [.25583, .75583, 0, 0, .77778], 8807: [.25583, .75583, 0, 0, .77778], 8808: [.25142, .75726, 0, 0, .77778], 8809: [.25142, .75726, 0, 0, .77778], 8812: [.25583, .75583, 0, 0, .5], 8814: [.20576, .70576, 0, 0, .77778], 8815: [.20576, .70576, 0, 0, .77778], 8816: [.30274, .79383, 0, 0, .77778], 8817: [.30274, .79383, 0, 0, .77778], 8818: [.22958, .72958, 0, 0, .77778], 8819: [.22958, .72958, 0, 0, .77778], 8822: [.1808, .675, 0, 0, .77778], 8823: [.1808, .675, 0, 0, .77778], 8828: [.13667, .63667, 0, 0, .77778], 8829: [.13667, .63667, 0, 0, .77778], 8830: [.22958, .72958, 0, 0, .77778], 8831: [.22958, .72958, 0, 0, .77778], 8832: [.20576, .70576, 0, 0, .77778], 8833: [.20576, .70576, 0, 0, .77778], 8840: [.30274, .79383, 0, 0, .77778], 8841: [.30274, .79383, 0, 0, .77778], 8842: [.13597, .63597, 0, 0, .77778], 8843: [.13597, .63597, 0, 0, .77778], 8847: [.03517, .54986, 0, 0, .77778], 8848: [.03517, .54986, 0, 0, .77778], 8858: [.08198, .58198, 0, 0, .77778], 8859: [.08198, .58198, 0, 0, .77778], 8861: [.08198, .58198, 0, 0, .77778], 8862: [0, .675, 0, 0, .77778], 8863: [0, .675, 0, 0, .77778], 8864: [0, .675, 0, 0, .77778], 8865: [0, .675, 0, 0, .77778], 8872: [0, .69224, 0, 0, .61111], 8873: [0, .69224, 0, 0, .72222], 8874: [0, .69224, 0, 0, .88889], 8876: [0, .68889, 0, 0, .61111], 8877: [0, .68889, 0, 0, .61111], 8878: [0, .68889, 0, 0, .72222], 8879: [0, .68889, 0, 0, .72222], 8882: [.03517, .54986, 0, 0, .77778], 8883: [.03517, .54986, 0, 0, .77778], 8884: [.13667, .63667, 0, 0, .77778], 8885: [.13667, .63667, 0, 0, .77778], 8888: [0, .54986, 0, 0, 1.11111], 8890: [.19444, .43056, 0, 0, .55556], 8891: [.19444, .69224, 0, 0, .61111], 8892: [.19444, .69224, 0, 0, .61111], 8901: [0, .54986, 0, 0, .27778], 8903: [.08167, .58167, 0, 0, .77778], 8905: [.08167, .58167, 0, 0, .77778], 8906: [.08167, .58167, 0, 0, .77778], 8907: [0, .69224, 0, 0, .77778], 8908: [0, .69224, 0, 0, .77778], 8909: [-.03598, .46402, 0, 0, .77778], 8910: [0, .54986, 0, 0, .76042], 8911: [0, .54986, 0, 0, .76042], 8912: [.03517, .54986, 0, 0, .77778], 8913: [.03517, .54986, 0, 0, .77778], 8914: [0, .54986, 0, 0, .66667], 8915: [0, .54986, 0, 0, .66667], 8916: [0, .69224, 0, 0, .66667], 8918: [.0391, .5391, 0, 0, .77778], 8919: [.0391, .5391, 0, 0, .77778], 8920: [.03517, .54986, 0, 0, 1.33334], 8921: [.03517, .54986, 0, 0, 1.33334], 8922: [.38569, .88569, 0, 0, .77778], 8923: [.38569, .88569, 0, 0, .77778], 8926: [.13667, .63667, 0, 0, .77778], 8927: [.13667, .63667, 0, 0, .77778], 8928: [.30274, .79383, 0, 0, .77778], 8929: [.30274, .79383, 0, 0, .77778], 8934: [.23222, .74111, 0, 0, .77778], 8935: [.23222, .74111, 0, 0, .77778], 8936: [.23222, .74111, 0, 0, .77778], 8937: [.23222, .74111, 0, 0, .77778], 8938: [.20576, .70576, 0, 0, .77778], 8939: [.20576, .70576, 0, 0, .77778], 8940: [.30274, .79383, 0, 0, .77778], 8941: [.30274, .79383, 0, 0, .77778], 8994: [.19444, .69224, 0, 0, .77778], 8995: [.19444, .69224, 0, 0, .77778], 9416: [.15559, .69224, 0, 0, .90222], 9484: [0, .69224, 0, 0, .5], 9488: [0, .69224, 0, 0, .5], 9492: [0, .37788, 0, 0, .5], 9496: [0, .37788, 0, 0, .5], 9585: [.19444, .68889, 0, 0, .88889], 9586: [.19444, .74111, 0, 0, .88889], 9632: [0, .675, 0, 0, .77778], 9633: [0, .675, 0, 0, .77778], 9650: [0, .54986, 0, 0, .72222], 9651: [0, .54986, 0, 0, .72222], 9654: [.03517, .54986, 0, 0, .77778], 9660: [0, .54986, 0, 0, .72222], 9661: [0, .54986, 0, 0, .72222], 9664: [.03517, .54986, 0, 0, .77778], 9674: [.11111, .69224, 0, 0, .66667], 9733: [.19444, .69224, 0, 0, .94445], 10003: [0, .69224, 0, 0, .83334], 10016: [0, .69224, 0, 0, .83334], 10731: [.11111, .69224, 0, 0, .66667], 10846: [.19444, .75583, 0, 0, .61111], 10877: [.13667, .63667, 0, 0, .77778], 10878: [.13667, .63667, 0, 0, .77778], 10885: [.25583, .75583, 0, 0, .77778], 10886: [.25583, .75583, 0, 0, .77778], 10887: [.13597, .63597, 0, 0, .77778], 10888: [.13597, .63597, 0, 0, .77778], 10889: [.26167, .75726, 0, 0, .77778], 10890: [.26167, .75726, 0, 0, .77778], 10891: [.48256, .98256, 0, 0, .77778], 10892: [.48256, .98256, 0, 0, .77778], 10901: [.13667, .63667, 0, 0, .77778], 10902: [.13667, .63667, 0, 0, .77778], 10933: [.25142, .75726, 0, 0, .77778], 10934: [.25142, .75726, 0, 0, .77778], 10935: [.26167, .75726, 0, 0, .77778], 10936: [.26167, .75726, 0, 0, .77778], 10937: [.26167, .75726, 0, 0, .77778], 10938: [.26167, .75726, 0, 0, .77778], 10949: [.25583, .75583, 0, 0, .77778], 10950: [.25583, .75583, 0, 0, .77778], 10955: [.28481, .79383, 0, 0, .77778], 10956: [.28481, .79383, 0, 0, .77778], 57350: [.08167, .58167, 0, 0, .22222], 57351: [.08167, .58167, 0, 0, .38889], 57352: [.08167, .58167, 0, 0, .77778], 57353: [0, .43056, .04028, 0, .66667], 57356: [.25142, .75726, 0, 0, .77778], 57357: [.25142, .75726, 0, 0, .77778], 57358: [.41951, .91951, 0, 0, .77778], 57359: [.30274, .79383, 0, 0, .77778], 57360: [.30274, .79383, 0, 0, .77778], 57361: [.41951, .91951, 0, 0, .77778], 57366: [.25142, .75726, 0, 0, .77778], 57367: [.25142, .75726, 0, 0, .77778], 57368: [.25142, .75726, 0, 0, .77778], 57369: [.25142, .75726, 0, 0, .77778], 57370: [.13597, .63597, 0, 0, .77778], 57371: [.13597, .63597, 0, 0, .77778] }, "Caligraphic-Regular": { 32: [0, 0, 0, 0, .25], 65: [0, .68333, 0, .19445, .79847], 66: [0, .68333, .03041, .13889, .65681], 67: [0, .68333, .05834, .13889, .52653], 68: [0, .68333, .02778, .08334, .77139], 69: [0, .68333, .08944, .11111, .52778], 70: [0, .68333, .09931, .11111, .71875], 71: [.09722, .68333, .0593, .11111, .59487], 72: [0, .68333, .00965, .11111, .84452], 73: [0, .68333, .07382, 0, .54452], 74: [.09722, .68333, .18472, .16667, .67778], 75: [0, .68333, .01445, .05556, .76195], 76: [0, .68333, 0, .13889, .68972], 77: [0, .68333, 0, .13889, 1.2009], 78: [0, .68333, .14736, .08334, .82049], 79: [0, .68333, .02778, .11111, .79611], 80: [0, .68333, .08222, .08334, .69556], 81: [.09722, .68333, 0, .11111, .81667], 82: [0, .68333, 0, .08334, .8475], 83: [0, .68333, .075, .13889, .60556], 84: [0, .68333, .25417, 0, .54464], 85: [0, .68333, .09931, .08334, .62583], 86: [0, .68333, .08222, 0, .61278], 87: [0, .68333, .08222, .08334, .98778], 88: [0, .68333, .14643, .13889, .7133], 89: [.09722, .68333, .08222, .08334, .66834], 90: [0, .68333, .07944, .13889, .72473], 160: [0, 0, 0, 0, .25] }, "Fraktur-Regular": { 32: [0, 0, 0, 0, .25], 33: [0, .69141, 0, 0, .29574], 34: [0, .69141, 0, 0, .21471], 38: [0, .69141, 0, 0, .73786], 39: [0, .69141, 0, 0, .21201], 40: [.24982, .74947, 0, 0, .38865], 41: [.24982, .74947, 0, 0, .38865], 42: [0, .62119, 0, 0, .27764], 43: [.08319, .58283, 0, 0, .75623], 44: [0, .10803, 0, 0, .27764], 45: [.08319, .58283, 0, 0, .75623], 46: [0, .10803, 0, 0, .27764], 47: [.24982, .74947, 0, 0, .50181], 48: [0, .47534, 0, 0, .50181], 49: [0, .47534, 0, 0, .50181], 50: [0, .47534, 0, 0, .50181], 51: [.18906, .47534, 0, 0, .50181], 52: [.18906, .47534, 0, 0, .50181], 53: [.18906, .47534, 0, 0, .50181], 54: [0, .69141, 0, 0, .50181], 55: [.18906, .47534, 0, 0, .50181], 56: [0, .69141, 0, 0, .50181], 57: [.18906, .47534, 0, 0, .50181], 58: [0, .47534, 0, 0, .21606], 59: [.12604, .47534, 0, 0, .21606], 61: [-.13099, .36866, 0, 0, .75623], 63: [0, .69141, 0, 0, .36245], 65: [0, .69141, 0, 0, .7176], 66: [0, .69141, 0, 0, .88397], 67: [0, .69141, 0, 0, .61254], 68: [0, .69141, 0, 0, .83158], 69: [0, .69141, 0, 0, .66278], 70: [.12604, .69141, 0, 0, .61119], 71: [0, .69141, 0, 0, .78539], 72: [.06302, .69141, 0, 0, .7203], 73: [0, .69141, 0, 0, .55448], 74: [.12604, .69141, 0, 0, .55231], 75: [0, .69141, 0, 0, .66845], 76: [0, .69141, 0, 0, .66602], 77: [0, .69141, 0, 0, 1.04953], 78: [0, .69141, 0, 0, .83212], 79: [0, .69141, 0, 0, .82699], 80: [.18906, .69141, 0, 0, .82753], 81: [.03781, .69141, 0, 0, .82699], 82: [0, .69141, 0, 0, .82807], 83: [0, .69141, 0, 0, .82861], 84: [0, .69141, 0, 0, .66899], 85: [0, .69141, 0, 0, .64576], 86: [0, .69141, 0, 0, .83131], 87: [0, .69141, 0, 0, 1.04602], 88: [0, .69141, 0, 0, .71922], 89: [.18906, .69141, 0, 0, .83293], 90: [.12604, .69141, 0, 0, .60201], 91: [.24982, .74947, 0, 0, .27764], 93: [.24982, .74947, 0, 0, .27764], 94: [0, .69141, 0, 0, .49965], 97: [0, .47534, 0, 0, .50046], 98: [0, .69141, 0, 0, .51315], 99: [0, .47534, 0, 0, .38946], 100: [0, .62119, 0, 0, .49857], 101: [0, .47534, 0, 0, .40053], 102: [.18906, .69141, 0, 0, .32626], 103: [.18906, .47534, 0, 0, .5037], 104: [.18906, .69141, 0, 0, .52126], 105: [0, .69141, 0, 0, .27899], 106: [0, .69141, 0, 0, .28088], 107: [0, .69141, 0, 0, .38946], 108: [0, .69141, 0, 0, .27953], 109: [0, .47534, 0, 0, .76676], 110: [0, .47534, 0, 0, .52666], 111: [0, .47534, 0, 0, .48885], 112: [.18906, .52396, 0, 0, .50046], 113: [.18906, .47534, 0, 0, .48912], 114: [0, .47534, 0, 0, .38919], 115: [0, .47534, 0, 0, .44266], 116: [0, .62119, 0, 0, .33301], 117: [0, .47534, 0, 0, .5172], 118: [0, .52396, 0, 0, .5118], 119: [0, .52396, 0, 0, .77351], 120: [.18906, .47534, 0, 0, .38865], 121: [.18906, .47534, 0, 0, .49884], 122: [.18906, .47534, 0, 0, .39054], 160: [0, 0, 0, 0, .25], 8216: [0, .69141, 0, 0, .21471], 8217: [0, .69141, 0, 0, .21471], 58112: [0, .62119, 0, 0, .49749], 58113: [0, .62119, 0, 0, .4983], 58114: [.18906, .69141, 0, 0, .33328], 58115: [.18906, .69141, 0, 0, .32923], 58116: [.18906, .47534, 0, 0, .50343], 58117: [0, .69141, 0, 0, .33301], 58118: [0, .62119, 0, 0, .33409], 58119: [0, .47534, 0, 0, .50073] }, "Main-Bold": { 32: [0, 0, 0, 0, .25], 33: [0, .69444, 0, 0, .35], 34: [0, .69444, 0, 0, .60278], 35: [.19444, .69444, 0, 0, .95833], 36: [.05556, .75, 0, 0, .575], 37: [.05556, .75, 0, 0, .95833], 38: [0, .69444, 0, 0, .89444], 39: [0, .69444, 0, 0, .31944], 40: [.25, .75, 0, 0, .44722], 41: [.25, .75, 0, 0, .44722], 42: [0, .75, 0, 0, .575], 43: [.13333, .63333, 0, 0, .89444], 44: [.19444, .15556, 0, 0, .31944], 45: [0, .44444, 0, 0, .38333], 46: [0, .15556, 0, 0, .31944], 47: [.25, .75, 0, 0, .575], 48: [0, .64444, 0, 0, .575], 49: [0, .64444, 0, 0, .575], 50: [0, .64444, 0, 0, .575], 51: [0, .64444, 0, 0, .575], 52: [0, .64444, 0, 0, .575], 53: [0, .64444, 0, 0, .575], 54: [0, .64444, 0, 0, .575], 55: [0, .64444, 0, 0, .575], 56: [0, .64444, 0, 0, .575], 57: [0, .64444, 0, 0, .575], 58: [0, .44444, 0, 0, .31944], 59: [.19444, .44444, 0, 0, .31944], 60: [.08556, .58556, 0, 0, .89444], 61: [-.10889, .39111, 0, 0, .89444], 62: [.08556, .58556, 0, 0, .89444], 63: [0, .69444, 0, 0, .54305], 64: [0, .69444, 0, 0, .89444], 65: [0, .68611, 0, 0, .86944], 66: [0, .68611, 0, 0, .81805], 67: [0, .68611, 0, 0, .83055], 68: [0, .68611, 0, 0, .88194], 69: [0, .68611, 0, 0, .75555], 70: [0, .68611, 0, 0, .72361], 71: [0, .68611, 0, 0, .90416], 72: [0, .68611, 0, 0, .9], 73: [0, .68611, 0, 0, .43611], 74: [0, .68611, 0, 0, .59444], 75: [0, .68611, 0, 0, .90138], 76: [0, .68611, 0, 0, .69166], 77: [0, .68611, 0, 0, 1.09166], 78: [0, .68611, 0, 0, .9], 79: [0, .68611, 0, 0, .86388], 80: [0, .68611, 0, 0, .78611], 81: [.19444, .68611, 0, 0, .86388], 82: [0, .68611, 0, 0, .8625], 83: [0, .68611, 0, 0, .63889], 84: [0, .68611, 0, 0, .8], 85: [0, .68611, 0, 0, .88472], 86: [0, .68611, .01597, 0, .86944], 87: [0, .68611, .01597, 0, 1.18888], 88: [0, .68611, 0, 0, .86944], 89: [0, .68611, .02875, 0, .86944], 90: [0, .68611, 0, 0, .70277], 91: [.25, .75, 0, 0, .31944], 92: [.25, .75, 0, 0, .575], 93: [.25, .75, 0, 0, .31944], 94: [0, .69444, 0, 0, .575], 95: [.31, .13444, .03194, 0, .575], 97: [0, .44444, 0, 0, .55902], 98: [0, .69444, 0, 0, .63889], 99: [0, .44444, 0, 0, .51111], 100: [0, .69444, 0, 0, .63889], 101: [0, .44444, 0, 0, .52708], 102: [0, .69444, .10903, 0, .35139], 103: [.19444, .44444, .01597, 0, .575], 104: [0, .69444, 0, 0, .63889], 105: [0, .69444, 0, 0, .31944], 106: [.19444, .69444, 0, 0, .35139], 107: [0, .69444, 0, 0, .60694], 108: [0, .69444, 0, 0, .31944], 109: [0, .44444, 0, 0, .95833], 110: [0, .44444, 0, 0, .63889], 111: [0, .44444, 0, 0, .575], 112: [.19444, .44444, 0, 0, .63889], 113: [.19444, .44444, 0, 0, .60694], 114: [0, .44444, 0, 0, .47361], 115: [0, .44444, 0, 0, .45361], 116: [0, .63492, 0, 0, .44722], 117: [0, .44444, 0, 0, .63889], 118: [0, .44444, .01597, 0, .60694], 119: [0, .44444, .01597, 0, .83055], 120: [0, .44444, 0, 0, .60694], 121: [.19444, .44444, .01597, 0, .60694], 122: [0, .44444, 0, 0, .51111], 123: [.25, .75, 0, 0, .575], 124: [.25, .75, 0, 0, .31944], 125: [.25, .75, 0, 0, .575], 126: [.35, .34444, 0, 0, .575], 160: [0, 0, 0, 0, .25], 163: [0, .69444, 0, 0, .86853], 168: [0, .69444, 0, 0, .575], 172: [0, .44444, 0, 0, .76666], 176: [0, .69444, 0, 0, .86944], 177: [.13333, .63333, 0, 0, .89444], 184: [.17014, 0, 0, 0, .51111], 198: [0, .68611, 0, 0, 1.04166], 215: [.13333, .63333, 0, 0, .89444], 216: [.04861, .73472, 0, 0, .89444], 223: [0, .69444, 0, 0, .59722], 230: [0, .44444, 0, 0, .83055], 247: [.13333, .63333, 0, 0, .89444], 248: [.09722, .54167, 0, 0, .575], 305: [0, .44444, 0, 0, .31944], 338: [0, .68611, 0, 0, 1.16944], 339: [0, .44444, 0, 0, .89444], 567: [.19444, .44444, 0, 0, .35139], 710: [0, .69444, 0, 0, .575], 711: [0, .63194, 0, 0, .575], 713: [0, .59611, 0, 0, .575], 714: [0, .69444, 0, 0, .575], 715: [0, .69444, 0, 0, .575], 728: [0, .69444, 0, 0, .575], 729: [0, .69444, 0, 0, .31944], 730: [0, .69444, 0, 0, .86944], 732: [0, .69444, 0, 0, .575], 733: [0, .69444, 0, 0, .575], 915: [0, .68611, 0, 0, .69166], 916: [0, .68611, 0, 0, .95833], 920: [0, .68611, 0, 0, .89444], 923: [0, .68611, 0, 0, .80555], 926: [0, .68611, 0, 0, .76666], 928: [0, .68611, 0, 0, .9], 931: [0, .68611, 0, 0, .83055], 933: [0, .68611, 0, 0, .89444], 934: [0, .68611, 0, 0, .83055], 936: [0, .68611, 0, 0, .89444], 937: [0, .68611, 0, 0, .83055], 8211: [0, .44444, .03194, 0, .575], 8212: [0, .44444, .03194, 0, 1.14999], 8216: [0, .69444, 0, 0, .31944], 8217: [0, .69444, 0, 0, .31944], 8220: [0, .69444, 0, 0, .60278], 8221: [0, .69444, 0, 0, .60278], 8224: [.19444, .69444, 0, 0, .51111], 8225: [.19444, .69444, 0, 0, .51111], 8242: [0, .55556, 0, 0, .34444], 8407: [0, .72444, .15486, 0, .575], 8463: [0, .69444, 0, 0, .66759], 8465: [0, .69444, 0, 0, .83055], 8467: [0, .69444, 0, 0, .47361], 8472: [.19444, .44444, 0, 0, .74027], 8476: [0, .69444, 0, 0, .83055], 8501: [0, .69444, 0, 0, .70277], 8592: [-.10889, .39111, 0, 0, 1.14999], 8593: [.19444, .69444, 0, 0, .575], 8594: [-.10889, .39111, 0, 0, 1.14999], 8595: [.19444, .69444, 0, 0, .575], 8596: [-.10889, .39111, 0, 0, 1.14999], 8597: [.25, .75, 0, 0, .575], 8598: [.19444, .69444, 0, 0, 1.14999], 8599: [.19444, .69444, 0, 0, 1.14999], 8600: [.19444, .69444, 0, 0, 1.14999], 8601: [.19444, .69444, 0, 0, 1.14999], 8636: [-.10889, .39111, 0, 0, 1.14999], 8637: [-.10889, .39111, 0, 0, 1.14999], 8640: [-.10889, .39111, 0, 0, 1.14999], 8641: [-.10889, .39111, 0, 0, 1.14999], 8656: [-.10889, .39111, 0, 0, 1.14999], 8657: [.19444, .69444, 0, 0, .70277], 8658: [-.10889, .39111, 0, 0, 1.14999], 8659: [.19444, .69444, 0, 0, .70277], 8660: [-.10889, .39111, 0, 0, 1.14999], 8661: [.25, .75, 0, 0, .70277], 8704: [0, .69444, 0, 0, .63889], 8706: [0, .69444, .06389, 0, .62847], 8707: [0, .69444, 0, 0, .63889], 8709: [.05556, .75, 0, 0, .575], 8711: [0, .68611, 0, 0, .95833], 8712: [.08556, .58556, 0, 0, .76666], 8715: [.08556, .58556, 0, 0, .76666], 8722: [.13333, .63333, 0, 0, .89444], 8723: [.13333, .63333, 0, 0, .89444], 8725: [.25, .75, 0, 0, .575], 8726: [.25, .75, 0, 0, .575], 8727: [-.02778, .47222, 0, 0, .575], 8728: [-.02639, .47361, 0, 0, .575], 8729: [-.02639, .47361, 0, 0, .575], 8730: [.18, .82, 0, 0, .95833], 8733: [0, .44444, 0, 0, .89444], 8734: [0, .44444, 0, 0, 1.14999], 8736: [0, .69224, 0, 0, .72222], 8739: [.25, .75, 0, 0, .31944], 8741: [.25, .75, 0, 0, .575], 8743: [0, .55556, 0, 0, .76666], 8744: [0, .55556, 0, 0, .76666], 8745: [0, .55556, 0, 0, .76666], 8746: [0, .55556, 0, 0, .76666], 8747: [.19444, .69444, .12778, 0, .56875], 8764: [-.10889, .39111, 0, 0, .89444], 8768: [.19444, .69444, 0, 0, .31944], 8771: [.00222, .50222, 0, 0, .89444], 8773: [.027, .638, 0, 0, .894], 8776: [.02444, .52444, 0, 0, .89444], 8781: [.00222, .50222, 0, 0, .89444], 8801: [.00222, .50222, 0, 0, .89444], 8804: [.19667, .69667, 0, 0, .89444], 8805: [.19667, .69667, 0, 0, .89444], 8810: [.08556, .58556, 0, 0, 1.14999], 8811: [.08556, .58556, 0, 0, 1.14999], 8826: [.08556, .58556, 0, 0, .89444], 8827: [.08556, .58556, 0, 0, .89444], 8834: [.08556, .58556, 0, 0, .89444], 8835: [.08556, .58556, 0, 0, .89444], 8838: [.19667, .69667, 0, 0, .89444], 8839: [.19667, .69667, 0, 0, .89444], 8846: [0, .55556, 0, 0, .76666], 8849: [.19667, .69667, 0, 0, .89444], 8850: [.19667, .69667, 0, 0, .89444], 8851: [0, .55556, 0, 0, .76666], 8852: [0, .55556, 0, 0, .76666], 8853: [.13333, .63333, 0, 0, .89444], 8854: [.13333, .63333, 0, 0, .89444], 8855: [.13333, .63333, 0, 0, .89444], 8856: [.13333, .63333, 0, 0, .89444], 8857: [.13333, .63333, 0, 0, .89444], 8866: [0, .69444, 0, 0, .70277], 8867: [0, .69444, 0, 0, .70277], 8868: [0, .69444, 0, 0, .89444], 8869: [0, .69444, 0, 0, .89444], 8900: [-.02639, .47361, 0, 0, .575], 8901: [-.02639, .47361, 0, 0, .31944], 8902: [-.02778, .47222, 0, 0, .575], 8968: [.25, .75, 0, 0, .51111], 8969: [.25, .75, 0, 0, .51111], 8970: [.25, .75, 0, 0, .51111], 8971: [.25, .75, 0, 0, .51111], 8994: [-.13889, .36111, 0, 0, 1.14999], 8995: [-.13889, .36111, 0, 0, 1.14999], 9651: [.19444, .69444, 0, 0, 1.02222], 9657: [-.02778, .47222, 0, 0, .575], 9661: [.19444, .69444, 0, 0, 1.02222], 9667: [-.02778, .47222, 0, 0, .575], 9711: [.19444, .69444, 0, 0, 1.14999], 9824: [.12963, .69444, 0, 0, .89444], 9825: [.12963, .69444, 0, 0, .89444], 9826: [.12963, .69444, 0, 0, .89444], 9827: [.12963, .69444, 0, 0, .89444], 9837: [0, .75, 0, 0, .44722], 9838: [.19444, .69444, 0, 0, .44722], 9839: [.19444, .69444, 0, 0, .44722], 10216: [.25, .75, 0, 0, .44722], 10217: [.25, .75, 0, 0, .44722], 10815: [0, .68611, 0, 0, .9], 10927: [.19667, .69667, 0, 0, .89444], 10928: [.19667, .69667, 0, 0, .89444], 57376: [.19444, .69444, 0, 0, 0] }, "Main-BoldItalic": { 32: [0, 0, 0, 0, .25], 33: [0, .69444, .11417, 0, .38611], 34: [0, .69444, .07939, 0, .62055], 35: [.19444, .69444, .06833, 0, .94444], 37: [.05556, .75, .12861, 0, .94444], 38: [0, .69444, .08528, 0, .88555], 39: [0, .69444, .12945, 0, .35555], 40: [.25, .75, .15806, 0, .47333], 41: [.25, .75, .03306, 0, .47333], 42: [0, .75, .14333, 0, .59111], 43: [.10333, .60333, .03306, 0, .88555], 44: [.19444, .14722, 0, 0, .35555], 45: [0, .44444, .02611, 0, .41444], 46: [0, .14722, 0, 0, .35555], 47: [.25, .75, .15806, 0, .59111], 48: [0, .64444, .13167, 0, .59111], 49: [0, .64444, .13167, 0, .59111], 50: [0, .64444, .13167, 0, .59111], 51: [0, .64444, .13167, 0, .59111], 52: [.19444, .64444, .13167, 0, .59111], 53: [0, .64444, .13167, 0, .59111], 54: [0, .64444, .13167, 0, .59111], 55: [.19444, .64444, .13167, 0, .59111], 56: [0, .64444, .13167, 0, .59111], 57: [0, .64444, .13167, 0, .59111], 58: [0, .44444, .06695, 0, .35555], 59: [.19444, .44444, .06695, 0, .35555], 61: [-.10889, .39111, .06833, 0, .88555], 63: [0, .69444, .11472, 0, .59111], 64: [0, .69444, .09208, 0, .88555], 65: [0, .68611, 0, 0, .86555], 66: [0, .68611, .0992, 0, .81666], 67: [0, .68611, .14208, 0, .82666], 68: [0, .68611, .09062, 0, .87555], 69: [0, .68611, .11431, 0, .75666], 70: [0, .68611, .12903, 0, .72722], 71: [0, .68611, .07347, 0, .89527], 72: [0, .68611, .17208, 0, .8961], 73: [0, .68611, .15681, 0, .47166], 74: [0, .68611, .145, 0, .61055], 75: [0, .68611, .14208, 0, .89499], 76: [0, .68611, 0, 0, .69777], 77: [0, .68611, .17208, 0, 1.07277], 78: [0, .68611, .17208, 0, .8961], 79: [0, .68611, .09062, 0, .85499], 80: [0, .68611, .0992, 0, .78721], 81: [.19444, .68611, .09062, 0, .85499], 82: [0, .68611, .02559, 0, .85944], 83: [0, .68611, .11264, 0, .64999], 84: [0, .68611, .12903, 0, .7961], 85: [0, .68611, .17208, 0, .88083], 86: [0, .68611, .18625, 0, .86555], 87: [0, .68611, .18625, 0, 1.15999], 88: [0, .68611, .15681, 0, .86555], 89: [0, .68611, .19803, 0, .86555], 90: [0, .68611, .14208, 0, .70888], 91: [.25, .75, .1875, 0, .35611], 93: [.25, .75, .09972, 0, .35611], 94: [0, .69444, .06709, 0, .59111], 95: [.31, .13444, .09811, 0, .59111], 97: [0, .44444, .09426, 0, .59111], 98: [0, .69444, .07861, 0, .53222], 99: [0, .44444, .05222, 0, .53222], 100: [0, .69444, .10861, 0, .59111], 101: [0, .44444, .085, 0, .53222], 102: [.19444, .69444, .21778, 0, .4], 103: [.19444, .44444, .105, 0, .53222], 104: [0, .69444, .09426, 0, .59111], 105: [0, .69326, .11387, 0, .35555], 106: [.19444, .69326, .1672, 0, .35555], 107: [0, .69444, .11111, 0, .53222], 108: [0, .69444, .10861, 0, .29666], 109: [0, .44444, .09426, 0, .94444], 110: [0, .44444, .09426, 0, .64999], 111: [0, .44444, .07861, 0, .59111], 112: [.19444, .44444, .07861, 0, .59111], 113: [.19444, .44444, .105, 0, .53222], 114: [0, .44444, .11111, 0, .50167], 115: [0, .44444, .08167, 0, .48694], 116: [0, .63492, .09639, 0, .385], 117: [0, .44444, .09426, 0, .62055], 118: [0, .44444, .11111, 0, .53222], 119: [0, .44444, .11111, 0, .76777], 120: [0, .44444, .12583, 0, .56055], 121: [.19444, .44444, .105, 0, .56166], 122: [0, .44444, .13889, 0, .49055], 126: [.35, .34444, .11472, 0, .59111], 160: [0, 0, 0, 0, .25], 168: [0, .69444, .11473, 0, .59111], 176: [0, .69444, 0, 0, .94888], 184: [.17014, 0, 0, 0, .53222], 198: [0, .68611, .11431, 0, 1.02277], 216: [.04861, .73472, .09062, 0, .88555], 223: [.19444, .69444, .09736, 0, .665], 230: [0, .44444, .085, 0, .82666], 248: [.09722, .54167, .09458, 0, .59111], 305: [0, .44444, .09426, 0, .35555], 338: [0, .68611, .11431, 0, 1.14054], 339: [0, .44444, .085, 0, .82666], 567: [.19444, .44444, .04611, 0, .385], 710: [0, .69444, .06709, 0, .59111], 711: [0, .63194, .08271, 0, .59111], 713: [0, .59444, .10444, 0, .59111], 714: [0, .69444, .08528, 0, .59111], 715: [0, .69444, 0, 0, .59111], 728: [0, .69444, .10333, 0, .59111], 729: [0, .69444, .12945, 0, .35555], 730: [0, .69444, 0, 0, .94888], 732: [0, .69444, .11472, 0, .59111], 733: [0, .69444, .11472, 0, .59111], 915: [0, .68611, .12903, 0, .69777], 916: [0, .68611, 0, 0, .94444], 920: [0, .68611, .09062, 0, .88555], 923: [0, .68611, 0, 0, .80666], 926: [0, .68611, .15092, 0, .76777], 928: [0, .68611, .17208, 0, .8961], 931: [0, .68611, .11431, 0, .82666], 933: [0, .68611, .10778, 0, .88555], 934: [0, .68611, .05632, 0, .82666], 936: [0, .68611, .10778, 0, .88555], 937: [0, .68611, .0992, 0, .82666], 8211: [0, .44444, .09811, 0, .59111], 8212: [0, .44444, .09811, 0, 1.18221], 8216: [0, .69444, .12945, 0, .35555], 8217: [0, .69444, .12945, 0, .35555], 8220: [0, .69444, .16772, 0, .62055], 8221: [0, .69444, .07939, 0, .62055] }, "Main-Italic": { 32: [0, 0, 0, 0, .25], 33: [0, .69444, .12417, 0, .30667], 34: [0, .69444, .06961, 0, .51444], 35: [.19444, .69444, .06616, 0, .81777], 37: [.05556, .75, .13639, 0, .81777], 38: [0, .69444, .09694, 0, .76666], 39: [0, .69444, .12417, 0, .30667], 40: [.25, .75, .16194, 0, .40889], 41: [.25, .75, .03694, 0, .40889], 42: [0, .75, .14917, 0, .51111], 43: [.05667, .56167, .03694, 0, .76666], 44: [.19444, .10556, 0, 0, .30667], 45: [0, .43056, .02826, 0, .35778], 46: [0, .10556, 0, 0, .30667], 47: [.25, .75, .16194, 0, .51111], 48: [0, .64444, .13556, 0, .51111], 49: [0, .64444, .13556, 0, .51111], 50: [0, .64444, .13556, 0, .51111], 51: [0, .64444, .13556, 0, .51111], 52: [.19444, .64444, .13556, 0, .51111], 53: [0, .64444, .13556, 0, .51111], 54: [0, .64444, .13556, 0, .51111], 55: [.19444, .64444, .13556, 0, .51111], 56: [0, .64444, .13556, 0, .51111], 57: [0, .64444, .13556, 0, .51111], 58: [0, .43056, .0582, 0, .30667], 59: [.19444, .43056, .0582, 0, .30667], 61: [-.13313, .36687, .06616, 0, .76666], 63: [0, .69444, .1225, 0, .51111], 64: [0, .69444, .09597, 0, .76666], 65: [0, .68333, 0, 0, .74333], 66: [0, .68333, .10257, 0, .70389], 67: [0, .68333, .14528, 0, .71555], 68: [0, .68333, .09403, 0, .755], 69: [0, .68333, .12028, 0, .67833], 70: [0, .68333, .13305, 0, .65277], 71: [0, .68333, .08722, 0, .77361], 72: [0, .68333, .16389, 0, .74333], 73: [0, .68333, .15806, 0, .38555], 74: [0, .68333, .14028, 0, .525], 75: [0, .68333, .14528, 0, .76888], 76: [0, .68333, 0, 0, .62722], 77: [0, .68333, .16389, 0, .89666], 78: [0, .68333, .16389, 0, .74333], 79: [0, .68333, .09403, 0, .76666], 80: [0, .68333, .10257, 0, .67833], 81: [.19444, .68333, .09403, 0, .76666], 82: [0, .68333, .03868, 0, .72944], 83: [0, .68333, .11972, 0, .56222], 84: [0, .68333, .13305, 0, .71555], 85: [0, .68333, .16389, 0, .74333], 86: [0, .68333, .18361, 0, .74333], 87: [0, .68333, .18361, 0, .99888], 88: [0, .68333, .15806, 0, .74333], 89: [0, .68333, .19383, 0, .74333], 90: [0, .68333, .14528, 0, .61333], 91: [.25, .75, .1875, 0, .30667], 93: [.25, .75, .10528, 0, .30667], 94: [0, .69444, .06646, 0, .51111], 95: [.31, .12056, .09208, 0, .51111], 97: [0, .43056, .07671, 0, .51111], 98: [0, .69444, .06312, 0, .46], 99: [0, .43056, .05653, 0, .46], 100: [0, .69444, .10333, 0, .51111], 101: [0, .43056, .07514, 0, .46], 102: [.19444, .69444, .21194, 0, .30667], 103: [.19444, .43056, .08847, 0, .46], 104: [0, .69444, .07671, 0, .51111], 105: [0, .65536, .1019, 0, .30667], 106: [.19444, .65536, .14467, 0, .30667], 107: [0, .69444, .10764, 0, .46], 108: [0, .69444, .10333, 0, .25555], 109: [0, .43056, .07671, 0, .81777], 110: [0, .43056, .07671, 0, .56222], 111: [0, .43056, .06312, 0, .51111], 112: [.19444, .43056, .06312, 0, .51111], 113: [.19444, .43056, .08847, 0, .46], 114: [0, .43056, .10764, 0, .42166], 115: [0, .43056, .08208, 0, .40889], 116: [0, .61508, .09486, 0, .33222], 117: [0, .43056, .07671, 0, .53666], 118: [0, .43056, .10764, 0, .46], 119: [0, .43056, .10764, 0, .66444], 120: [0, .43056, .12042, 0, .46389], 121: [.19444, .43056, .08847, 0, .48555], 122: [0, .43056, .12292, 0, .40889], 126: [.35, .31786, .11585, 0, .51111], 160: [0, 0, 0, 0, .25], 168: [0, .66786, .10474, 0, .51111], 176: [0, .69444, 0, 0, .83129], 184: [.17014, 0, 0, 0, .46], 198: [0, .68333, .12028, 0, .88277], 216: [.04861, .73194, .09403, 0, .76666], 223: [.19444, .69444, .10514, 0, .53666], 230: [0, .43056, .07514, 0, .71555], 248: [.09722, .52778, .09194, 0, .51111], 338: [0, .68333, .12028, 0, .98499], 339: [0, .43056, .07514, 0, .71555], 710: [0, .69444, .06646, 0, .51111], 711: [0, .62847, .08295, 0, .51111], 713: [0, .56167, .10333, 0, .51111], 714: [0, .69444, .09694, 0, .51111], 715: [0, .69444, 0, 0, .51111], 728: [0, .69444, .10806, 0, .51111], 729: [0, .66786, .11752, 0, .30667], 730: [0, .69444, 0, 0, .83129], 732: [0, .66786, .11585, 0, .51111], 733: [0, .69444, .1225, 0, .51111], 915: [0, .68333, .13305, 0, .62722], 916: [0, .68333, 0, 0, .81777], 920: [0, .68333, .09403, 0, .76666], 923: [0, .68333, 0, 0, .69222], 926: [0, .68333, .15294, 0, .66444], 928: [0, .68333, .16389, 0, .74333], 931: [0, .68333, .12028, 0, .71555], 933: [0, .68333, .11111, 0, .76666], 934: [0, .68333, .05986, 0, .71555], 936: [0, .68333, .11111, 0, .76666], 937: [0, .68333, .10257, 0, .71555], 8211: [0, .43056, .09208, 0, .51111], 8212: [0, .43056, .09208, 0, 1.02222], 8216: [0, .69444, .12417, 0, .30667], 8217: [0, .69444, .12417, 0, .30667], 8220: [0, .69444, .1685, 0, .51444], 8221: [0, .69444, .06961, 0, .51444], 8463: [0, .68889, 0, 0, .54028] }, "Main-Regular": { 32: [0, 0, 0, 0, .25], 33: [0, .69444, 0, 0, .27778], 34: [0, .69444, 0, 0, .5], 35: [.19444, .69444, 0, 0, .83334], 36: [.05556, .75, 0, 0, .5], 37: [.05556, .75, 0, 0, .83334], 38: [0, .69444, 0, 0, .77778], 39: [0, .69444, 0, 0, .27778], 40: [.25, .75, 0, 0, .38889], 41: [.25, .75, 0, 0, .38889], 42: [0, .75, 0, 0, .5], 43: [.08333, .58333, 0, 0, .77778], 44: [.19444, .10556, 0, 0, .27778], 45: [0, .43056, 0, 0, .33333], 46: [0, .10556, 0, 0, .27778], 47: [.25, .75, 0, 0, .5], 48: [0, .64444, 0, 0, .5], 49: [0, .64444, 0, 0, .5], 50: [0, .64444, 0, 0, .5], 51: [0, .64444, 0, 0, .5], 52: [0, .64444, 0, 0, .5], 53: [0, .64444, 0, 0, .5], 54: [0, .64444, 0, 0, .5], 55: [0, .64444, 0, 0, .5], 56: [0, .64444, 0, 0, .5], 57: [0, .64444, 0, 0, .5], 58: [0, .43056, 0, 0, .27778], 59: [.19444, .43056, 0, 0, .27778], 60: [.0391, .5391, 0, 0, .77778], 61: [-.13313, .36687, 0, 0, .77778], 62: [.0391, .5391, 0, 0, .77778], 63: [0, .69444, 0, 0, .47222], 64: [0, .69444, 0, 0, .77778], 65: [0, .68333, 0, 0, .75], 66: [0, .68333, 0, 0, .70834], 67: [0, .68333, 0, 0, .72222], 68: [0, .68333, 0, 0, .76389], 69: [0, .68333, 0, 0, .68056], 70: [0, .68333, 0, 0, .65278], 71: [0, .68333, 0, 0, .78472], 72: [0, .68333, 0, 0, .75], 73: [0, .68333, 0, 0, .36111], 74: [0, .68333, 0, 0, .51389], 75: [0, .68333, 0, 0, .77778], 76: [0, .68333, 0, 0, .625], 77: [0, .68333, 0, 0, .91667], 78: [0, .68333, 0, 0, .75], 79: [0, .68333, 0, 0, .77778], 80: [0, .68333, 0, 0, .68056], 81: [.19444, .68333, 0, 0, .77778], 82: [0, .68333, 0, 0, .73611], 83: [0, .68333, 0, 0, .55556], 84: [0, .68333, 0, 0, .72222], 85: [0, .68333, 0, 0, .75], 86: [0, .68333, .01389, 0, .75], 87: [0, .68333, .01389, 0, 1.02778], 88: [0, .68333, 0, 0, .75], 89: [0, .68333, .025, 0, .75], 90: [0, .68333, 0, 0, .61111], 91: [.25, .75, 0, 0, .27778], 92: [.25, .75, 0, 0, .5], 93: [.25, .75, 0, 0, .27778], 94: [0, .69444, 0, 0, .5], 95: [.31, .12056, .02778, 0, .5], 97: [0, .43056, 0, 0, .5], 98: [0, .69444, 0, 0, .55556], 99: [0, .43056, 0, 0, .44445], 100: [0, .69444, 0, 0, .55556], 101: [0, .43056, 0, 0, .44445], 102: [0, .69444, .07778, 0, .30556], 103: [.19444, .43056, .01389, 0, .5], 104: [0, .69444, 0, 0, .55556], 105: [0, .66786, 0, 0, .27778], 106: [.19444, .66786, 0, 0, .30556], 107: [0, .69444, 0, 0, .52778], 108: [0, .69444, 0, 0, .27778], 109: [0, .43056, 0, 0, .83334], 110: [0, .43056, 0, 0, .55556], 111: [0, .43056, 0, 0, .5], 112: [.19444, .43056, 0, 0, .55556], 113: [.19444, .43056, 0, 0, .52778], 114: [0, .43056, 0, 0, .39167], 115: [0, .43056, 0, 0, .39445], 116: [0, .61508, 0, 0, .38889], 117: [0, .43056, 0, 0, .55556], 118: [0, .43056, .01389, 0, .52778], 119: [0, .43056, .01389, 0, .72222], 120: [0, .43056, 0, 0, .52778], 121: [.19444, .43056, .01389, 0, .52778], 122: [0, .43056, 0, 0, .44445], 123: [.25, .75, 0, 0, .5], 124: [.25, .75, 0, 0, .27778], 125: [.25, .75, 0, 0, .5], 126: [.35, .31786, 0, 0, .5], 160: [0, 0, 0, 0, .25], 163: [0, .69444, 0, 0, .76909], 167: [.19444, .69444, 0, 0, .44445], 168: [0, .66786, 0, 0, .5], 172: [0, .43056, 0, 0, .66667], 176: [0, .69444, 0, 0, .75], 177: [.08333, .58333, 0, 0, .77778], 182: [.19444, .69444, 0, 0, .61111], 184: [.17014, 0, 0, 0, .44445], 198: [0, .68333, 0, 0, .90278], 215: [.08333, .58333, 0, 0, .77778], 216: [.04861, .73194, 0, 0, .77778], 223: [0, .69444, 0, 0, .5], 230: [0, .43056, 0, 0, .72222], 247: [.08333, .58333, 0, 0, .77778], 248: [.09722, .52778, 0, 0, .5], 305: [0, .43056, 0, 0, .27778], 338: [0, .68333, 0, 0, 1.01389], 339: [0, .43056, 0, 0, .77778], 567: [.19444, .43056, 0, 0, .30556], 710: [0, .69444, 0, 0, .5], 711: [0, .62847, 0, 0, .5], 713: [0, .56778, 0, 0, .5], 714: [0, .69444, 0, 0, .5], 715: [0, .69444, 0, 0, .5], 728: [0, .69444, 0, 0, .5], 729: [0, .66786, 0, 0, .27778], 730: [0, .69444, 0, 0, .75], 732: [0, .66786, 0, 0, .5], 733: [0, .69444, 0, 0, .5], 915: [0, .68333, 0, 0, .625], 916: [0, .68333, 0, 0, .83334], 920: [0, .68333, 0, 0, .77778], 923: [0, .68333, 0, 0, .69445], 926: [0, .68333, 0, 0, .66667], 928: [0, .68333, 0, 0, .75], 931: [0, .68333, 0, 0, .72222], 933: [0, .68333, 0, 0, .77778], 934: [0, .68333, 0, 0, .72222], 936: [0, .68333, 0, 0, .77778], 937: [0, .68333, 0, 0, .72222], 8211: [0, .43056, .02778, 0, .5], 8212: [0, .43056, .02778, 0, 1], 8216: [0, .69444, 0, 0, .27778], 8217: [0, .69444, 0, 0, .27778], 8220: [0, .69444, 0, 0, .5], 8221: [0, .69444, 0, 0, .5], 8224: [.19444, .69444, 0, 0, .44445], 8225: [.19444, .69444, 0, 0, .44445], 8230: [0, .123, 0, 0, 1.172], 8242: [0, .55556, 0, 0, .275], 8407: [0, .71444, .15382, 0, .5], 8463: [0, .68889, 0, 0, .54028], 8465: [0, .69444, 0, 0, .72222], 8467: [0, .69444, 0, .11111, .41667], 8472: [.19444, .43056, 0, .11111, .63646], 8476: [0, .69444, 0, 0, .72222], 8501: [0, .69444, 0, 0, .61111], 8592: [-.13313, .36687, 0, 0, 1], 8593: [.19444, .69444, 0, 0, .5], 8594: [-.13313, .36687, 0, 0, 1], 8595: [.19444, .69444, 0, 0, .5], 8596: [-.13313, .36687, 0, 0, 1], 8597: [.25, .75, 0, 0, .5], 8598: [.19444, .69444, 0, 0, 1], 8599: [.19444, .69444, 0, 0, 1], 8600: [.19444, .69444, 0, 0, 1], 8601: [.19444, .69444, 0, 0, 1], 8614: [.011, .511, 0, 0, 1], 8617: [.011, .511, 0, 0, 1.126], 8618: [.011, .511, 0, 0, 1.126], 8636: [-.13313, .36687, 0, 0, 1], 8637: [-.13313, .36687, 0, 0, 1], 8640: [-.13313, .36687, 0, 0, 1], 8641: [-.13313, .36687, 0, 0, 1], 8652: [.011, .671, 0, 0, 1], 8656: [-.13313, .36687, 0, 0, 1], 8657: [.19444, .69444, 0, 0, .61111], 8658: [-.13313, .36687, 0, 0, 1], 8659: [.19444, .69444, 0, 0, .61111], 8660: [-.13313, .36687, 0, 0, 1], 8661: [.25, .75, 0, 0, .61111], 8704: [0, .69444, 0, 0, .55556], 8706: [0, .69444, .05556, .08334, .5309], 8707: [0, .69444, 0, 0, .55556], 8709: [.05556, .75, 0, 0, .5], 8711: [0, .68333, 0, 0, .83334], 8712: [.0391, .5391, 0, 0, .66667], 8715: [.0391, .5391, 0, 0, .66667], 8722: [.08333, .58333, 0, 0, .77778], 8723: [.08333, .58333, 0, 0, .77778], 8725: [.25, .75, 0, 0, .5], 8726: [.25, .75, 0, 0, .5], 8727: [-.03472, .46528, 0, 0, .5], 8728: [-.05555, .44445, 0, 0, .5], 8729: [-.05555, .44445, 0, 0, .5], 8730: [.2, .8, 0, 0, .83334], 8733: [0, .43056, 0, 0, .77778], 8734: [0, .43056, 0, 0, 1], 8736: [0, .69224, 0, 0, .72222], 8739: [.25, .75, 0, 0, .27778], 8741: [.25, .75, 0, 0, .5], 8743: [0, .55556, 0, 0, .66667], 8744: [0, .55556, 0, 0, .66667], 8745: [0, .55556, 0, 0, .66667], 8746: [0, .55556, 0, 0, .66667], 8747: [.19444, .69444, .11111, 0, .41667], 8764: [-.13313, .36687, 0, 0, .77778], 8768: [.19444, .69444, 0, 0, .27778], 8771: [-.03625, .46375, 0, 0, .77778], 8773: [-.022, .589, 0, 0, .778], 8776: [-.01688, .48312, 0, 0, .77778], 8781: [-.03625, .46375, 0, 0, .77778], 8784: [-.133, .673, 0, 0, .778], 8801: [-.03625, .46375, 0, 0, .77778], 8804: [.13597, .63597, 0, 0, .77778], 8805: [.13597, .63597, 0, 0, .77778], 8810: [.0391, .5391, 0, 0, 1], 8811: [.0391, .5391, 0, 0, 1], 8826: [.0391, .5391, 0, 0, .77778], 8827: [.0391, .5391, 0, 0, .77778], 8834: [.0391, .5391, 0, 0, .77778], 8835: [.0391, .5391, 0, 0, .77778], 8838: [.13597, .63597, 0, 0, .77778], 8839: [.13597, .63597, 0, 0, .77778], 8846: [0, .55556, 0, 0, .66667], 8849: [.13597, .63597, 0, 0, .77778], 8850: [.13597, .63597, 0, 0, .77778], 8851: [0, .55556, 0, 0, .66667], 8852: [0, .55556, 0, 0, .66667], 8853: [.08333, .58333, 0, 0, .77778], 8854: [.08333, .58333, 0, 0, .77778], 8855: [.08333, .58333, 0, 0, .77778], 8856: [.08333, .58333, 0, 0, .77778], 8857: [.08333, .58333, 0, 0, .77778], 8866: [0, .69444, 0, 0, .61111], 8867: [0, .69444, 0, 0, .61111], 8868: [0, .69444, 0, 0, .77778], 8869: [0, .69444, 0, 0, .77778], 8872: [.249, .75, 0, 0, .867], 8900: [-.05555, .44445, 0, 0, .5], 8901: [-.05555, .44445, 0, 0, .27778], 8902: [-.03472, .46528, 0, 0, .5], 8904: [.005, .505, 0, 0, .9], 8942: [.03, .903, 0, 0, .278], 8943: [-.19, .313, 0, 0, 1.172], 8945: [-.1, .823, 0, 0, 1.282], 8968: [.25, .75, 0, 0, .44445], 8969: [.25, .75, 0, 0, .44445], 8970: [.25, .75, 0, 0, .44445], 8971: [.25, .75, 0, 0, .44445], 8994: [-.14236, .35764, 0, 0, 1], 8995: [-.14236, .35764, 0, 0, 1], 9136: [.244, .744, 0, 0, .412], 9137: [.244, .745, 0, 0, .412], 9651: [.19444, .69444, 0, 0, .88889], 9657: [-.03472, .46528, 0, 0, .5], 9661: [.19444, .69444, 0, 0, .88889], 9667: [-.03472, .46528, 0, 0, .5], 9711: [.19444, .69444, 0, 0, 1], 9824: [.12963, .69444, 0, 0, .77778], 9825: [.12963, .69444, 0, 0, .77778], 9826: [.12963, .69444, 0, 0, .77778], 9827: [.12963, .69444, 0, 0, .77778], 9837: [0, .75, 0, 0, .38889], 9838: [.19444, .69444, 0, 0, .38889], 9839: [.19444, .69444, 0, 0, .38889], 10216: [.25, .75, 0, 0, .38889], 10217: [.25, .75, 0, 0, .38889], 10222: [.244, .744, 0, 0, .412], 10223: [.244, .745, 0, 0, .412], 10229: [.011, .511, 0, 0, 1.609], 10230: [.011, .511, 0, 0, 1.638], 10231: [.011, .511, 0, 0, 1.859], 10232: [.024, .525, 0, 0, 1.609], 10233: [.024, .525, 0, 0, 1.638], 10234: [.024, .525, 0, 0, 1.858], 10236: [.011, .511, 0, 0, 1.638], 10815: [0, .68333, 0, 0, .75], 10927: [.13597, .63597, 0, 0, .77778], 10928: [.13597, .63597, 0, 0, .77778], 57376: [.19444, .69444, 0, 0, 0] }, "Math-BoldItalic": { 32: [0, 0, 0, 0, .25], 48: [0, .44444, 0, 0, .575], 49: [0, .44444, 0, 0, .575], 50: [0, .44444, 0, 0, .575], 51: [.19444, .44444, 0, 0, .575], 52: [.19444, .44444, 0, 0, .575], 53: [.19444, .44444, 0, 0, .575], 54: [0, .64444, 0, 0, .575], 55: [.19444, .44444, 0, 0, .575], 56: [0, .64444, 0, 0, .575], 57: [.19444, .44444, 0, 0, .575], 65: [0, .68611, 0, 0, .86944], 66: [0, .68611, .04835, 0, .8664], 67: [0, .68611, .06979, 0, .81694], 68: [0, .68611, .03194, 0, .93812], 69: [0, .68611, .05451, 0, .81007], 70: [0, .68611, .15972, 0, .68889], 71: [0, .68611, 0, 0, .88673], 72: [0, .68611, .08229, 0, .98229], 73: [0, .68611, .07778, 0, .51111], 74: [0, .68611, .10069, 0, .63125], 75: [0, .68611, .06979, 0, .97118], 76: [0, .68611, 0, 0, .75555], 77: [0, .68611, .11424, 0, 1.14201], 78: [0, .68611, .11424, 0, .95034], 79: [0, .68611, .03194, 0, .83666], 80: [0, .68611, .15972, 0, .72309], 81: [.19444, .68611, 0, 0, .86861], 82: [0, .68611, .00421, 0, .87235], 83: [0, .68611, .05382, 0, .69271], 84: [0, .68611, .15972, 0, .63663], 85: [0, .68611, .11424, 0, .80027], 86: [0, .68611, .25555, 0, .67778], 87: [0, .68611, .15972, 0, 1.09305], 88: [0, .68611, .07778, 0, .94722], 89: [0, .68611, .25555, 0, .67458], 90: [0, .68611, .06979, 0, .77257], 97: [0, .44444, 0, 0, .63287], 98: [0, .69444, 0, 0, .52083], 99: [0, .44444, 0, 0, .51342], 100: [0, .69444, 0, 0, .60972], 101: [0, .44444, 0, 0, .55361], 102: [.19444, .69444, .11042, 0, .56806], 103: [.19444, .44444, .03704, 0, .5449], 104: [0, .69444, 0, 0, .66759], 105: [0, .69326, 0, 0, .4048], 106: [.19444, .69326, .0622, 0, .47083], 107: [0, .69444, .01852, 0, .6037], 108: [0, .69444, .0088, 0, .34815], 109: [0, .44444, 0, 0, 1.0324], 110: [0, .44444, 0, 0, .71296], 111: [0, .44444, 0, 0, .58472], 112: [.19444, .44444, 0, 0, .60092], 113: [.19444, .44444, .03704, 0, .54213], 114: [0, .44444, .03194, 0, .5287], 115: [0, .44444, 0, 0, .53125], 116: [0, .63492, 0, 0, .41528], 117: [0, .44444, 0, 0, .68102], 118: [0, .44444, .03704, 0, .56666], 119: [0, .44444, .02778, 0, .83148], 120: [0, .44444, 0, 0, .65903], 121: [.19444, .44444, .03704, 0, .59028], 122: [0, .44444, .04213, 0, .55509], 160: [0, 0, 0, 0, .25], 915: [0, .68611, .15972, 0, .65694], 916: [0, .68611, 0, 0, .95833], 920: [0, .68611, .03194, 0, .86722], 923: [0, .68611, 0, 0, .80555], 926: [0, .68611, .07458, 0, .84125], 928: [0, .68611, .08229, 0, .98229], 931: [0, .68611, .05451, 0, .88507], 933: [0, .68611, .15972, 0, .67083], 934: [0, .68611, 0, 0, .76666], 936: [0, .68611, .11653, 0, .71402], 937: [0, .68611, .04835, 0, .8789], 945: [0, .44444, 0, 0, .76064], 946: [.19444, .69444, .03403, 0, .65972], 947: [.19444, .44444, .06389, 0, .59003], 948: [0, .69444, .03819, 0, .52222], 949: [0, .44444, 0, 0, .52882], 950: [.19444, .69444, .06215, 0, .50833], 951: [.19444, .44444, .03704, 0, .6], 952: [0, .69444, .03194, 0, .5618], 953: [0, .44444, 0, 0, .41204], 954: [0, .44444, 0, 0, .66759], 955: [0, .69444, 0, 0, .67083], 956: [.19444, .44444, 0, 0, .70787], 957: [0, .44444, .06898, 0, .57685], 958: [.19444, .69444, .03021, 0, .50833], 959: [0, .44444, 0, 0, .58472], 960: [0, .44444, .03704, 0, .68241], 961: [.19444, .44444, 0, 0, .6118], 962: [.09722, .44444, .07917, 0, .42361], 963: [0, .44444, .03704, 0, .68588], 964: [0, .44444, .13472, 0, .52083], 965: [0, .44444, .03704, 0, .63055], 966: [.19444, .44444, 0, 0, .74722], 967: [.19444, .44444, 0, 0, .71805], 968: [.19444, .69444, .03704, 0, .75833], 969: [0, .44444, .03704, 0, .71782], 977: [0, .69444, 0, 0, .69155], 981: [.19444, .69444, 0, 0, .7125], 982: [0, .44444, .03194, 0, .975], 1009: [.19444, .44444, 0, 0, .6118], 1013: [0, .44444, 0, 0, .48333], 57649: [0, .44444, 0, 0, .39352], 57911: [.19444, .44444, 0, 0, .43889] }, "Math-Italic": { 32: [0, 0, 0, 0, .25], 48: [0, .43056, 0, 0, .5], 49: [0, .43056, 0, 0, .5], 50: [0, .43056, 0, 0, .5], 51: [.19444, .43056, 0, 0, .5], 52: [.19444, .43056, 0, 0, .5], 53: [.19444, .43056, 0, 0, .5], 54: [0, .64444, 0, 0, .5], 55: [.19444, .43056, 0, 0, .5], 56: [0, .64444, 0, 0, .5], 57: [.19444, .43056, 0, 0, .5], 65: [0, .68333, 0, .13889, .75], 66: [0, .68333, .05017, .08334, .75851], 67: [0, .68333, .07153, .08334, .71472], 68: [0, .68333, .02778, .05556, .82792], 69: [0, .68333, .05764, .08334, .7382], 70: [0, .68333, .13889, .08334, .64306], 71: [0, .68333, 0, .08334, .78625], 72: [0, .68333, .08125, .05556, .83125], 73: [0, .68333, .07847, .11111, .43958], 74: [0, .68333, .09618, .16667, .55451], 75: [0, .68333, .07153, .05556, .84931], 76: [0, .68333, 0, .02778, .68056], 77: [0, .68333, .10903, .08334, .97014], 78: [0, .68333, .10903, .08334, .80347], 79: [0, .68333, .02778, .08334, .76278], 80: [0, .68333, .13889, .08334, .64201], 81: [.19444, .68333, 0, .08334, .79056], 82: [0, .68333, .00773, .08334, .75929], 83: [0, .68333, .05764, .08334, .6132], 84: [0, .68333, .13889, .08334, .58438], 85: [0, .68333, .10903, .02778, .68278], 86: [0, .68333, .22222, 0, .58333], 87: [0, .68333, .13889, 0, .94445], 88: [0, .68333, .07847, .08334, .82847], 89: [0, .68333, .22222, 0, .58056], 90: [0, .68333, .07153, .08334, .68264], 97: [0, .43056, 0, 0, .52859], 98: [0, .69444, 0, 0, .42917], 99: [0, .43056, 0, .05556, .43276], 100: [0, .69444, 0, .16667, .52049], 101: [0, .43056, 0, .05556, .46563], 102: [.19444, .69444, .10764, .16667, .48959], 103: [.19444, .43056, .03588, .02778, .47697], 104: [0, .69444, 0, 0, .57616], 105: [0, .65952, 0, 0, .34451], 106: [.19444, .65952, .05724, 0, .41181], 107: [0, .69444, .03148, 0, .5206], 108: [0, .69444, .01968, .08334, .29838], 109: [0, .43056, 0, 0, .87801], 110: [0, .43056, 0, 0, .60023], 111: [0, .43056, 0, .05556, .48472], 112: [.19444, .43056, 0, .08334, .50313], 113: [.19444, .43056, .03588, .08334, .44641], 114: [0, .43056, .02778, .05556, .45116], 115: [0, .43056, 0, .05556, .46875], 116: [0, .61508, 0, .08334, .36111], 117: [0, .43056, 0, .02778, .57246], 118: [0, .43056, .03588, .02778, .48472], 119: [0, .43056, .02691, .08334, .71592], 120: [0, .43056, 0, .02778, .57153], 121: [.19444, .43056, .03588, .05556, .49028], 122: [0, .43056, .04398, .05556, .46505], 160: [0, 0, 0, 0, .25], 915: [0, .68333, .13889, .08334, .61528], 916: [0, .68333, 0, .16667, .83334], 920: [0, .68333, .02778, .08334, .76278], 923: [0, .68333, 0, .16667, .69445], 926: [0, .68333, .07569, .08334, .74236], 928: [0, .68333, .08125, .05556, .83125], 931: [0, .68333, .05764, .08334, .77986], 933: [0, .68333, .13889, .05556, .58333], 934: [0, .68333, 0, .08334, .66667], 936: [0, .68333, .11, .05556, .61222], 937: [0, .68333, .05017, .08334, .7724], 945: [0, .43056, .0037, .02778, .6397], 946: [.19444, .69444, .05278, .08334, .56563], 947: [.19444, .43056, .05556, 0, .51773], 948: [0, .69444, .03785, .05556, .44444], 949: [0, .43056, 0, .08334, .46632], 950: [.19444, .69444, .07378, .08334, .4375], 951: [.19444, .43056, .03588, .05556, .49653], 952: [0, .69444, .02778, .08334, .46944], 953: [0, .43056, 0, .05556, .35394], 954: [0, .43056, 0, 0, .57616], 955: [0, .69444, 0, 0, .58334], 956: [.19444, .43056, 0, .02778, .60255], 957: [0, .43056, .06366, .02778, .49398], 958: [.19444, .69444, .04601, .11111, .4375], 959: [0, .43056, 0, .05556, .48472], 960: [0, .43056, .03588, 0, .57003], 961: [.19444, .43056, 0, .08334, .51702], 962: [.09722, .43056, .07986, .08334, .36285], 963: [0, .43056, .03588, 0, .57141], 964: [0, .43056, .1132, .02778, .43715], 965: [0, .43056, .03588, .02778, .54028], 966: [.19444, .43056, 0, .08334, .65417], 967: [.19444, .43056, 0, .05556, .62569], 968: [.19444, .69444, .03588, .11111, .65139], 969: [0, .43056, .03588, 0, .62245], 977: [0, .69444, 0, .08334, .59144], 981: [.19444, .69444, 0, .08334, .59583], 982: [0, .43056, .02778, 0, .82813], 1009: [.19444, .43056, 0, .08334, .51702], 1013: [0, .43056, 0, .05556, .4059], 57649: [0, .43056, 0, .02778, .32246], 57911: [.19444, .43056, 0, .08334, .38403] }, "SansSerif-Bold": { 32: [0, 0, 0, 0, .25], 33: [0, .69444, 0, 0, .36667], 34: [0, .69444, 0, 0, .55834], 35: [.19444, .69444, 0, 0, .91667], 36: [.05556, .75, 0, 0, .55], 37: [.05556, .75, 0, 0, 1.02912], 38: [0, .69444, 0, 0, .83056], 39: [0, .69444, 0, 0, .30556], 40: [.25, .75, 0, 0, .42778], 41: [.25, .75, 0, 0, .42778], 42: [0, .75, 0, 0, .55], 43: [.11667, .61667, 0, 0, .85556], 44: [.10556, .13056, 0, 0, .30556], 45: [0, .45833, 0, 0, .36667], 46: [0, .13056, 0, 0, .30556], 47: [.25, .75, 0, 0, .55], 48: [0, .69444, 0, 0, .55], 49: [0, .69444, 0, 0, .55], 50: [0, .69444, 0, 0, .55], 51: [0, .69444, 0, 0, .55], 52: [0, .69444, 0, 0, .55], 53: [0, .69444, 0, 0, .55], 54: [0, .69444, 0, 0, .55], 55: [0, .69444, 0, 0, .55], 56: [0, .69444, 0, 0, .55], 57: [0, .69444, 0, 0, .55], 58: [0, .45833, 0, 0, .30556], 59: [.10556, .45833, 0, 0, .30556], 61: [-.09375, .40625, 0, 0, .85556], 63: [0, .69444, 0, 0, .51945], 64: [0, .69444, 0, 0, .73334], 65: [0, .69444, 0, 0, .73334], 66: [0, .69444, 0, 0, .73334], 67: [0, .69444, 0, 0, .70278], 68: [0, .69444, 0, 0, .79445], 69: [0, .69444, 0, 0, .64167], 70: [0, .69444, 0, 0, .61111], 71: [0, .69444, 0, 0, .73334], 72: [0, .69444, 0, 0, .79445], 73: [0, .69444, 0, 0, .33056], 74: [0, .69444, 0, 0, .51945], 75: [0, .69444, 0, 0, .76389], 76: [0, .69444, 0, 0, .58056], 77: [0, .69444, 0, 0, .97778], 78: [0, .69444, 0, 0, .79445], 79: [0, .69444, 0, 0, .79445], 80: [0, .69444, 0, 0, .70278], 81: [.10556, .69444, 0, 0, .79445], 82: [0, .69444, 0, 0, .70278], 83: [0, .69444, 0, 0, .61111], 84: [0, .69444, 0, 0, .73334], 85: [0, .69444, 0, 0, .76389], 86: [0, .69444, .01528, 0, .73334], 87: [0, .69444, .01528, 0, 1.03889], 88: [0, .69444, 0, 0, .73334], 89: [0, .69444, .0275, 0, .73334], 90: [0, .69444, 0, 0, .67223], 91: [.25, .75, 0, 0, .34306], 93: [.25, .75, 0, 0, .34306], 94: [0, .69444, 0, 0, .55], 95: [.35, .10833, .03056, 0, .55], 97: [0, .45833, 0, 0, .525], 98: [0, .69444, 0, 0, .56111], 99: [0, .45833, 0, 0, .48889], 100: [0, .69444, 0, 0, .56111], 101: [0, .45833, 0, 0, .51111], 102: [0, .69444, .07639, 0, .33611], 103: [.19444, .45833, .01528, 0, .55], 104: [0, .69444, 0, 0, .56111], 105: [0, .69444, 0, 0, .25556], 106: [.19444, .69444, 0, 0, .28611], 107: [0, .69444, 0, 0, .53056], 108: [0, .69444, 0, 0, .25556], 109: [0, .45833, 0, 0, .86667], 110: [0, .45833, 0, 0, .56111], 111: [0, .45833, 0, 0, .55], 112: [.19444, .45833, 0, 0, .56111], 113: [.19444, .45833, 0, 0, .56111], 114: [0, .45833, .01528, 0, .37222], 115: [0, .45833, 0, 0, .42167], 116: [0, .58929, 0, 0, .40417], 117: [0, .45833, 0, 0, .56111], 118: [0, .45833, .01528, 0, .5], 119: [0, .45833, .01528, 0, .74445], 120: [0, .45833, 0, 0, .5], 121: [.19444, .45833, .01528, 0, .5], 122: [0, .45833, 0, 0, .47639], 126: [.35, .34444, 0, 0, .55], 160: [0, 0, 0, 0, .25], 168: [0, .69444, 0, 0, .55], 176: [0, .69444, 0, 0, .73334], 180: [0, .69444, 0, 0, .55], 184: [.17014, 0, 0, 0, .48889], 305: [0, .45833, 0, 0, .25556], 567: [.19444, .45833, 0, 0, .28611], 710: [0, .69444, 0, 0, .55], 711: [0, .63542, 0, 0, .55], 713: [0, .63778, 0, 0, .55], 728: [0, .69444, 0, 0, .55], 729: [0, .69444, 0, 0, .30556], 730: [0, .69444, 0, 0, .73334], 732: [0, .69444, 0, 0, .55], 733: [0, .69444, 0, 0, .55], 915: [0, .69444, 0, 0, .58056], 916: [0, .69444, 0, 0, .91667], 920: [0, .69444, 0, 0, .85556], 923: [0, .69444, 0, 0, .67223], 926: [0, .69444, 0, 0, .73334], 928: [0, .69444, 0, 0, .79445], 931: [0, .69444, 0, 0, .79445], 933: [0, .69444, 0, 0, .85556], 934: [0, .69444, 0, 0, .79445], 936: [0, .69444, 0, 0, .85556], 937: [0, .69444, 0, 0, .79445], 8211: [0, .45833, .03056, 0, .55], 8212: [0, .45833, .03056, 0, 1.10001], 8216: [0, .69444, 0, 0, .30556], 8217: [0, .69444, 0, 0, .30556], 8220: [0, .69444, 0, 0, .55834], 8221: [0, .69444, 0, 0, .55834] }, "SansSerif-Italic": { 32: [0, 0, 0, 0, .25], 33: [0, .69444, .05733, 0, .31945], 34: [0, .69444, .00316, 0, .5], 35: [.19444, .69444, .05087, 0, .83334], 36: [.05556, .75, .11156, 0, .5], 37: [.05556, .75, .03126, 0, .83334], 38: [0, .69444, .03058, 0, .75834], 39: [0, .69444, .07816, 0, .27778], 40: [.25, .75, .13164, 0, .38889], 41: [.25, .75, .02536, 0, .38889], 42: [0, .75, .11775, 0, .5], 43: [.08333, .58333, .02536, 0, .77778], 44: [.125, .08333, 0, 0, .27778], 45: [0, .44444, .01946, 0, .33333], 46: [0, .08333, 0, 0, .27778], 47: [.25, .75, .13164, 0, .5], 48: [0, .65556, .11156, 0, .5], 49: [0, .65556, .11156, 0, .5], 50: [0, .65556, .11156, 0, .5], 51: [0, .65556, .11156, 0, .5], 52: [0, .65556, .11156, 0, .5], 53: [0, .65556, .11156, 0, .5], 54: [0, .65556, .11156, 0, .5], 55: [0, .65556, .11156, 0, .5], 56: [0, .65556, .11156, 0, .5], 57: [0, .65556, .11156, 0, .5], 58: [0, .44444, .02502, 0, .27778], 59: [.125, .44444, .02502, 0, .27778], 61: [-.13, .37, .05087, 0, .77778], 63: [0, .69444, .11809, 0, .47222], 64: [0, .69444, .07555, 0, .66667], 65: [0, .69444, 0, 0, .66667], 66: [0, .69444, .08293, 0, .66667], 67: [0, .69444, .11983, 0, .63889], 68: [0, .69444, .07555, 0, .72223], 69: [0, .69444, .11983, 0, .59722], 70: [0, .69444, .13372, 0, .56945], 71: [0, .69444, .11983, 0, .66667], 72: [0, .69444, .08094, 0, .70834], 73: [0, .69444, .13372, 0, .27778], 74: [0, .69444, .08094, 0, .47222], 75: [0, .69444, .11983, 0, .69445], 76: [0, .69444, 0, 0, .54167], 77: [0, .69444, .08094, 0, .875], 78: [0, .69444, .08094, 0, .70834], 79: [0, .69444, .07555, 0, .73611], 80: [0, .69444, .08293, 0, .63889], 81: [.125, .69444, .07555, 0, .73611], 82: [0, .69444, .08293, 0, .64584], 83: [0, .69444, .09205, 0, .55556], 84: [0, .69444, .13372, 0, .68056], 85: [0, .69444, .08094, 0, .6875], 86: [0, .69444, .1615, 0, .66667], 87: [0, .69444, .1615, 0, .94445], 88: [0, .69444, .13372, 0, .66667], 89: [0, .69444, .17261, 0, .66667], 90: [0, .69444, .11983, 0, .61111], 91: [.25, .75, .15942, 0, .28889], 93: [.25, .75, .08719, 0, .28889], 94: [0, .69444, .0799, 0, .5], 95: [.35, .09444, .08616, 0, .5], 97: [0, .44444, .00981, 0, .48056], 98: [0, .69444, .03057, 0, .51667], 99: [0, .44444, .08336, 0, .44445], 100: [0, .69444, .09483, 0, .51667], 101: [0, .44444, .06778, 0, .44445], 102: [0, .69444, .21705, 0, .30556], 103: [.19444, .44444, .10836, 0, .5], 104: [0, .69444, .01778, 0, .51667], 105: [0, .67937, .09718, 0, .23889], 106: [.19444, .67937, .09162, 0, .26667], 107: [0, .69444, .08336, 0, .48889], 108: [0, .69444, .09483, 0, .23889], 109: [0, .44444, .01778, 0, .79445], 110: [0, .44444, .01778, 0, .51667], 111: [0, .44444, .06613, 0, .5], 112: [.19444, .44444, .0389, 0, .51667], 113: [.19444, .44444, .04169, 0, .51667], 114: [0, .44444, .10836, 0, .34167], 115: [0, .44444, .0778, 0, .38333], 116: [0, .57143, .07225, 0, .36111], 117: [0, .44444, .04169, 0, .51667], 118: [0, .44444, .10836, 0, .46111], 119: [0, .44444, .10836, 0, .68334], 120: [0, .44444, .09169, 0, .46111], 121: [.19444, .44444, .10836, 0, .46111], 122: [0, .44444, .08752, 0, .43472], 126: [.35, .32659, .08826, 0, .5], 160: [0, 0, 0, 0, .25], 168: [0, .67937, .06385, 0, .5], 176: [0, .69444, 0, 0, .73752], 184: [.17014, 0, 0, 0, .44445], 305: [0, .44444, .04169, 0, .23889], 567: [.19444, .44444, .04169, 0, .26667], 710: [0, .69444, .0799, 0, .5], 711: [0, .63194, .08432, 0, .5], 713: [0, .60889, .08776, 0, .5], 714: [0, .69444, .09205, 0, .5], 715: [0, .69444, 0, 0, .5], 728: [0, .69444, .09483, 0, .5], 729: [0, .67937, .07774, 0, .27778], 730: [0, .69444, 0, 0, .73752], 732: [0, .67659, .08826, 0, .5], 733: [0, .69444, .09205, 0, .5], 915: [0, .69444, .13372, 0, .54167], 916: [0, .69444, 0, 0, .83334], 920: [0, .69444, .07555, 0, .77778], 923: [0, .69444, 0, 0, .61111], 926: [0, .69444, .12816, 0, .66667], 928: [0, .69444, .08094, 0, .70834], 931: [0, .69444, .11983, 0, .72222], 933: [0, .69444, .09031, 0, .77778], 934: [0, .69444, .04603, 0, .72222], 936: [0, .69444, .09031, 0, .77778], 937: [0, .69444, .08293, 0, .72222], 8211: [0, .44444, .08616, 0, .5], 8212: [0, .44444, .08616, 0, 1], 8216: [0, .69444, .07816, 0, .27778], 8217: [0, .69444, .07816, 0, .27778], 8220: [0, .69444, .14205, 0, .5], 8221: [0, .69444, .00316, 0, .5] }, "SansSerif-Regular": { 32: [0, 0, 0, 0, .25], 33: [0, .69444, 0, 0, .31945], 34: [0, .69444, 0, 0, .5], 35: [.19444, .69444, 0, 0, .83334], 36: [.05556, .75, 0, 0, .5], 37: [.05556, .75, 0, 0, .83334], 38: [0, .69444, 0, 0, .75834], 39: [0, .69444, 0, 0, .27778], 40: [.25, .75, 0, 0, .38889], 41: [.25, .75, 0, 0, .38889], 42: [0, .75, 0, 0, .5], 43: [.08333, .58333, 0, 0, .77778], 44: [.125, .08333, 0, 0, .27778], 45: [0, .44444, 0, 0, .33333], 46: [0, .08333, 0, 0, .27778], 47: [.25, .75, 0, 0, .5], 48: [0, .65556, 0, 0, .5], 49: [0, .65556, 0, 0, .5], 50: [0, .65556, 0, 0, .5], 51: [0, .65556, 0, 0, .5], 52: [0, .65556, 0, 0, .5], 53: [0, .65556, 0, 0, .5], 54: [0, .65556, 0, 0, .5], 55: [0, .65556, 0, 0, .5], 56: [0, .65556, 0, 0, .5], 57: [0, .65556, 0, 0, .5], 58: [0, .44444, 0, 0, .27778], 59: [.125, .44444, 0, 0, .27778], 61: [-.13, .37, 0, 0, .77778], 63: [0, .69444, 0, 0, .47222], 64: [0, .69444, 0, 0, .66667], 65: [0, .69444, 0, 0, .66667], 66: [0, .69444, 0, 0, .66667], 67: [0, .69444, 0, 0, .63889], 68: [0, .69444, 0, 0, .72223], 69: [0, .69444, 0, 0, .59722], 70: [0, .69444, 0, 0, .56945], 71: [0, .69444, 0, 0, .66667], 72: [0, .69444, 0, 0, .70834], 73: [0, .69444, 0, 0, .27778], 74: [0, .69444, 0, 0, .47222], 75: [0, .69444, 0, 0, .69445], 76: [0, .69444, 0, 0, .54167], 77: [0, .69444, 0, 0, .875], 78: [0, .69444, 0, 0, .70834], 79: [0, .69444, 0, 0, .73611], 80: [0, .69444, 0, 0, .63889], 81: [.125, .69444, 0, 0, .73611], 82: [0, .69444, 0, 0, .64584], 83: [0, .69444, 0, 0, .55556], 84: [0, .69444, 0, 0, .68056], 85: [0, .69444, 0, 0, .6875], 86: [0, .69444, .01389, 0, .66667], 87: [0, .69444, .01389, 0, .94445], 88: [0, .69444, 0, 0, .66667], 89: [0, .69444, .025, 0, .66667], 90: [0, .69444, 0, 0, .61111], 91: [.25, .75, 0, 0, .28889], 93: [.25, .75, 0, 0, .28889], 94: [0, .69444, 0, 0, .5], 95: [.35, .09444, .02778, 0, .5], 97: [0, .44444, 0, 0, .48056], 98: [0, .69444, 0, 0, .51667], 99: [0, .44444, 0, 0, .44445], 100: [0, .69444, 0, 0, .51667], 101: [0, .44444, 0, 0, .44445], 102: [0, .69444, .06944, 0, .30556], 103: [.19444, .44444, .01389, 0, .5], 104: [0, .69444, 0, 0, .51667], 105: [0, .67937, 0, 0, .23889], 106: [.19444, .67937, 0, 0, .26667], 107: [0, .69444, 0, 0, .48889], 108: [0, .69444, 0, 0, .23889], 109: [0, .44444, 0, 0, .79445], 110: [0, .44444, 0, 0, .51667], 111: [0, .44444, 0, 0, .5], 112: [.19444, .44444, 0, 0, .51667], 113: [.19444, .44444, 0, 0, .51667], 114: [0, .44444, .01389, 0, .34167], 115: [0, .44444, 0, 0, .38333], 116: [0, .57143, 0, 0, .36111], 117: [0, .44444, 0, 0, .51667], 118: [0, .44444, .01389, 0, .46111], 119: [0, .44444, .01389, 0, .68334], 120: [0, .44444, 0, 0, .46111], 121: [.19444, .44444, .01389, 0, .46111], 122: [0, .44444, 0, 0, .43472], 126: [.35, .32659, 0, 0, .5], 160: [0, 0, 0, 0, .25], 168: [0, .67937, 0, 0, .5], 176: [0, .69444, 0, 0, .66667], 184: [.17014, 0, 0, 0, .44445], 305: [0, .44444, 0, 0, .23889], 567: [.19444, .44444, 0, 0, .26667], 710: [0, .69444, 0, 0, .5], 711: [0, .63194, 0, 0, .5], 713: [0, .60889, 0, 0, .5], 714: [0, .69444, 0, 0, .5], 715: [0, .69444, 0, 0, .5], 728: [0, .69444, 0, 0, .5], 729: [0, .67937, 0, 0, .27778], 730: [0, .69444, 0, 0, .66667], 732: [0, .67659, 0, 0, .5], 733: [0, .69444, 0, 0, .5], 915: [0, .69444, 0, 0, .54167], 916: [0, .69444, 0, 0, .83334], 920: [0, .69444, 0, 0, .77778], 923: [0, .69444, 0, 0, .61111], 926: [0, .69444, 0, 0, .66667], 928: [0, .69444, 0, 0, .70834], 931: [0, .69444, 0, 0, .72222], 933: [0, .69444, 0, 0, .77778], 934: [0, .69444, 0, 0, .72222], 936: [0, .69444, 0, 0, .77778], 937: [0, .69444, 0, 0, .72222], 8211: [0, .44444, .02778, 0, .5], 8212: [0, .44444, .02778, 0, 1], 8216: [0, .69444, 0, 0, .27778], 8217: [0, .69444, 0, 0, .27778], 8220: [0, .69444, 0, 0, .5], 8221: [0, .69444, 0, 0, .5] }, "Script-Regular": { 32: [0, 0, 0, 0, .25], 65: [0, .7, .22925, 0, .80253], 66: [0, .7, .04087, 0, .90757], 67: [0, .7, .1689, 0, .66619], 68: [0, .7, .09371, 0, .77443], 69: [0, .7, .18583, 0, .56162], 70: [0, .7, .13634, 0, .89544], 71: [0, .7, .17322, 0, .60961], 72: [0, .7, .29694, 0, .96919], 73: [0, .7, .19189, 0, .80907], 74: [.27778, .7, .19189, 0, 1.05159], 75: [0, .7, .31259, 0, .91364], 76: [0, .7, .19189, 0, .87373], 77: [0, .7, .15981, 0, 1.08031], 78: [0, .7, .3525, 0, .9015], 79: [0, .7, .08078, 0, .73787], 80: [0, .7, .08078, 0, 1.01262], 81: [0, .7, .03305, 0, .88282], 82: [0, .7, .06259, 0, .85], 83: [0, .7, .19189, 0, .86767], 84: [0, .7, .29087, 0, .74697], 85: [0, .7, .25815, 0, .79996], 86: [0, .7, .27523, 0, .62204], 87: [0, .7, .27523, 0, .80532], 88: [0, .7, .26006, 0, .94445], 89: [0, .7, .2939, 0, .70961], 90: [0, .7, .24037, 0, .8212], 160: [0, 0, 0, 0, .25] }, "Size1-Regular": { 32: [0, 0, 0, 0, .25], 40: [.35001, .85, 0, 0, .45834], 41: [.35001, .85, 0, 0, .45834], 47: [.35001, .85, 0, 0, .57778], 91: [.35001, .85, 0, 0, .41667], 92: [.35001, .85, 0, 0, .57778], 93: [.35001, .85, 0, 0, .41667], 123: [.35001, .85, 0, 0, .58334], 125: [.35001, .85, 0, 0, .58334], 160: [0, 0, 0, 0, .25], 710: [0, .72222, 0, 0, .55556], 732: [0, .72222, 0, 0, .55556], 770: [0, .72222, 0, 0, .55556], 771: [0, .72222, 0, 0, .55556], 8214: [-99e-5, .601, 0, 0, .77778], 8593: [1e-5, .6, 0, 0, .66667], 8595: [1e-5, .6, 0, 0, .66667], 8657: [1e-5, .6, 0, 0, .77778], 8659: [1e-5, .6, 0, 0, .77778], 8719: [.25001, .75, 0, 0, .94445], 8720: [.25001, .75, 0, 0, .94445], 8721: [.25001, .75, 0, 0, 1.05556], 8730: [.35001, .85, 0, 0, 1], 8739: [-.00599, .606, 0, 0, .33333], 8741: [-.00599, .606, 0, 0, .55556], 8747: [.30612, .805, .19445, 0, .47222], 8748: [.306, .805, .19445, 0, .47222], 8749: [.306, .805, .19445, 0, .47222], 8750: [.30612, .805, .19445, 0, .47222], 8896: [.25001, .75, 0, 0, .83334], 8897: [.25001, .75, 0, 0, .83334], 8898: [.25001, .75, 0, 0, .83334], 8899: [.25001, .75, 0, 0, .83334], 8968: [.35001, .85, 0, 0, .47222], 8969: [.35001, .85, 0, 0, .47222], 8970: [.35001, .85, 0, 0, .47222], 8971: [.35001, .85, 0, 0, .47222], 9168: [-99e-5, .601, 0, 0, .66667], 10216: [.35001, .85, 0, 0, .47222], 10217: [.35001, .85, 0, 0, .47222], 10752: [.25001, .75, 0, 0, 1.11111], 10753: [.25001, .75, 0, 0, 1.11111], 10754: [.25001, .75, 0, 0, 1.11111], 10756: [.25001, .75, 0, 0, .83334], 10758: [.25001, .75, 0, 0, .83334] }, "Size2-Regular": { 32: [0, 0, 0, 0, .25], 40: [.65002, 1.15, 0, 0, .59722], 41: [.65002, 1.15, 0, 0, .59722], 47: [.65002, 1.15, 0, 0, .81111], 91: [.65002, 1.15, 0, 0, .47222], 92: [.65002, 1.15, 0, 0, .81111], 93: [.65002, 1.15, 0, 0, .47222], 123: [.65002, 1.15, 0, 0, .66667], 125: [.65002, 1.15, 0, 0, .66667], 160: [0, 0, 0, 0, .25], 710: [0, .75, 0, 0, 1], 732: [0, .75, 0, 0, 1], 770: [0, .75, 0, 0, 1], 771: [0, .75, 0, 0, 1], 8719: [.55001, 1.05, 0, 0, 1.27778], 8720: [.55001, 1.05, 0, 0, 1.27778], 8721: [.55001, 1.05, 0, 0, 1.44445], 8730: [.65002, 1.15, 0, 0, 1], 8747: [.86225, 1.36, .44445, 0, .55556], 8748: [.862, 1.36, .44445, 0, .55556], 8749: [.862, 1.36, .44445, 0, .55556], 8750: [.86225, 1.36, .44445, 0, .55556], 8896: [.55001, 1.05, 0, 0, 1.11111], 8897: [.55001, 1.05, 0, 0, 1.11111], 8898: [.55001, 1.05, 0, 0, 1.11111], 8899: [.55001, 1.05, 0, 0, 1.11111], 8968: [.65002, 1.15, 0, 0, .52778], 8969: [.65002, 1.15, 0, 0, .52778], 8970: [.65002, 1.15, 0, 0, .52778], 8971: [.65002, 1.15, 0, 0, .52778], 10216: [.65002, 1.15, 0, 0, .61111], 10217: [.65002, 1.15, 0, 0, .61111], 10752: [.55001, 1.05, 0, 0, 1.51112], 10753: [.55001, 1.05, 0, 0, 1.51112], 10754: [.55001, 1.05, 0, 0, 1.51112], 10756: [.55001, 1.05, 0, 0, 1.11111], 10758: [.55001, 1.05, 0, 0, 1.11111] }, "Size3-Regular": { 32: [0, 0, 0, 0, .25], 40: [.95003, 1.45, 0, 0, .73611], 41: [.95003, 1.45, 0, 0, .73611], 47: [.95003, 1.45, 0, 0, 1.04445], 91: [.95003, 1.45, 0, 0, .52778], 92: [.95003, 1.45, 0, 0, 1.04445], 93: [.95003, 1.45, 0, 0, .52778], 123: [.95003, 1.45, 0, 0, .75], 125: [.95003, 1.45, 0, 0, .75], 160: [0, 0, 0, 0, .25], 710: [0, .75, 0, 0, 1.44445], 732: [0, .75, 0, 0, 1.44445], 770: [0, .75, 0, 0, 1.44445], 771: [0, .75, 0, 0, 1.44445], 8730: [.95003, 1.45, 0, 0, 1], 8968: [.95003, 1.45, 0, 0, .58334], 8969: [.95003, 1.45, 0, 0, .58334], 8970: [.95003, 1.45, 0, 0, .58334], 8971: [.95003, 1.45, 0, 0, .58334], 10216: [.95003, 1.45, 0, 0, .75], 10217: [.95003, 1.45, 0, 0, .75] }, "Size4-Regular": { 32: [0, 0, 0, 0, .25], 40: [1.25003, 1.75, 0, 0, .79167], 41: [1.25003, 1.75, 0, 0, .79167], 47: [1.25003, 1.75, 0, 0, 1.27778], 91: [1.25003, 1.75, 0, 0, .58334], 92: [1.25003, 1.75, 0, 0, 1.27778], 93: [1.25003, 1.75, 0, 0, .58334], 123: [1.25003, 1.75, 0, 0, .80556], 125: [1.25003, 1.75, 0, 0, .80556], 160: [0, 0, 0, 0, .25], 710: [0, .825, 0, 0, 1.8889], 732: [0, .825, 0, 0, 1.8889], 770: [0, .825, 0, 0, 1.8889], 771: [0, .825, 0, 0, 1.8889], 8730: [1.25003, 1.75, 0, 0, 1], 8968: [1.25003, 1.75, 0, 0, .63889], 8969: [1.25003, 1.75, 0, 0, .63889], 8970: [1.25003, 1.75, 0, 0, .63889], 8971: [1.25003, 1.75, 0, 0, .63889], 9115: [.64502, 1.155, 0, 0, .875], 9116: [1e-5, .6, 0, 0, .875], 9117: [.64502, 1.155, 0, 0, .875], 9118: [.64502, 1.155, 0, 0, .875], 9119: [1e-5, .6, 0, 0, .875], 9120: [.64502, 1.155, 0, 0, .875], 9121: [.64502, 1.155, 0, 0, .66667], 9122: [-99e-5, .601, 0, 0, .66667], 9123: [.64502, 1.155, 0, 0, .66667], 9124: [.64502, 1.155, 0, 0, .66667], 9125: [-99e-5, .601, 0, 0, .66667], 9126: [.64502, 1.155, 0, 0, .66667], 9127: [1e-5, .9, 0, 0, .88889], 9128: [.65002, 1.15, 0, 0, .88889], 9129: [.90001, 0, 0, 0, .88889], 9130: [0, .3, 0, 0, .88889], 9131: [1e-5, .9, 0, 0, .88889], 9132: [.65002, 1.15, 0, 0, .88889], 9133: [.90001, 0, 0, 0, .88889], 9143: [.88502, .915, 0, 0, 1.05556], 10216: [1.25003, 1.75, 0, 0, .80556], 10217: [1.25003, 1.75, 0, 0, .80556], 57344: [-.00499, .605, 0, 0, 1.05556], 57345: [-.00499, .605, 0, 0, 1.05556], 57680: [0, .12, 0, 0, .45], 57681: [0, .12, 0, 0, .45], 57682: [0, .12, 0, 0, .45], 57683: [0, .12, 0, 0, .45] }, "Typewriter-Regular": { 32: [0, 0, 0, 0, .525], 33: [0, .61111, 0, 0, .525], 34: [0, .61111, 0, 0, .525], 35: [0, .61111, 0, 0, .525], 36: [.08333, .69444, 0, 0, .525], 37: [.08333, .69444, 0, 0, .525], 38: [0, .61111, 0, 0, .525], 39: [0, .61111, 0, 0, .525], 40: [.08333, .69444, 0, 0, .525], 41: [.08333, .69444, 0, 0, .525], 42: [0, .52083, 0, 0, .525], 43: [-.08056, .53055, 0, 0, .525], 44: [.13889, .125, 0, 0, .525], 45: [-.08056, .53055, 0, 0, .525], 46: [0, .125, 0, 0, .525], 47: [.08333, .69444, 0, 0, .525], 48: [0, .61111, 0, 0, .525], 49: [0, .61111, 0, 0, .525], 50: [0, .61111, 0, 0, .525], 51: [0, .61111, 0, 0, .525], 52: [0, .61111, 0, 0, .525], 53: [0, .61111, 0, 0, .525], 54: [0, .61111, 0, 0, .525], 55: [0, .61111, 0, 0, .525], 56: [0, .61111, 0, 0, .525], 57: [0, .61111, 0, 0, .525], 58: [0, .43056, 0, 0, .525], 59: [.13889, .43056, 0, 0, .525], 60: [-.05556, .55556, 0, 0, .525], 61: [-.19549, .41562, 0, 0, .525], 62: [-.05556, .55556, 0, 0, .525], 63: [0, .61111, 0, 0, .525], 64: [0, .61111, 0, 0, .525], 65: [0, .61111, 0, 0, .525], 66: [0, .61111, 0, 0, .525], 67: [0, .61111, 0, 0, .525], 68: [0, .61111, 0, 0, .525], 69: [0, .61111, 0, 0, .525], 70: [0, .61111, 0, 0, .525], 71: [0, .61111, 0, 0, .525], 72: [0, .61111, 0, 0, .525], 73: [0, .61111, 0, 0, .525], 74: [0, .61111, 0, 0, .525], 75: [0, .61111, 0, 0, .525], 76: [0, .61111, 0, 0, .525], 77: [0, .61111, 0, 0, .525], 78: [0, .61111, 0, 0, .525], 79: [0, .61111, 0, 0, .525], 80: [0, .61111, 0, 0, .525], 81: [.13889, .61111, 0, 0, .525], 82: [0, .61111, 0, 0, .525], 83: [0, .61111, 0, 0, .525], 84: [0, .61111, 0, 0, .525], 85: [0, .61111, 0, 0, .525], 86: [0, .61111, 0, 0, .525], 87: [0, .61111, 0, 0, .525], 88: [0, .61111, 0, 0, .525], 89: [0, .61111, 0, 0, .525], 90: [0, .61111, 0, 0, .525], 91: [.08333, .69444, 0, 0, .525], 92: [.08333, .69444, 0, 0, .525], 93: [.08333, .69444, 0, 0, .525], 94: [0, .61111, 0, 0, .525], 95: [.09514, 0, 0, 0, .525], 96: [0, .61111, 0, 0, .525], 97: [0, .43056, 0, 0, .525], 98: [0, .61111, 0, 0, .525], 99: [0, .43056, 0, 0, .525], 100: [0, .61111, 0, 0, .525], 101: [0, .43056, 0, 0, .525], 102: [0, .61111, 0, 0, .525], 103: [.22222, .43056, 0, 0, .525], 104: [0, .61111, 0, 0, .525], 105: [0, .61111, 0, 0, .525], 106: [.22222, .61111, 0, 0, .525], 107: [0, .61111, 0, 0, .525], 108: [0, .61111, 0, 0, .525], 109: [0, .43056, 0, 0, .525], 110: [0, .43056, 0, 0, .525], 111: [0, .43056, 0, 0, .525], 112: [.22222, .43056, 0, 0, .525], 113: [.22222, .43056, 0, 0, .525], 114: [0, .43056, 0, 0, .525], 115: [0, .43056, 0, 0, .525], 116: [0, .55358, 0, 0, .525], 117: [0, .43056, 0, 0, .525], 118: [0, .43056, 0, 0, .525], 119: [0, .43056, 0, 0, .525], 120: [0, .43056, 0, 0, .525], 121: [.22222, .43056, 0, 0, .525], 122: [0, .43056, 0, 0, .525], 123: [.08333, .69444, 0, 0, .525], 124: [.08333, .69444, 0, 0, .525], 125: [.08333, .69444, 0, 0, .525], 126: [0, .61111, 0, 0, .525], 127: [0, .61111, 0, 0, .525], 160: [0, 0, 0, 0, .525], 176: [0, .61111, 0, 0, .525], 184: [.19445, 0, 0, 0, .525], 305: [0, .43056, 0, 0, .525], 567: [.22222, .43056, 0, 0, .525], 711: [0, .56597, 0, 0, .525], 713: [0, .56555, 0, 0, .525], 714: [0, .61111, 0, 0, .525], 715: [0, .61111, 0, 0, .525], 728: [0, .61111, 0, 0, .525], 730: [0, .61111, 0, 0, .525], 770: [0, .61111, 0, 0, .525], 771: [0, .61111, 0, 0, .525], 776: [0, .61111, 0, 0, .525], 915: [0, .61111, 0, 0, .525], 916: [0, .61111, 0, 0, .525], 920: [0, .61111, 0, 0, .525], 923: [0, .61111, 0, 0, .525], 926: [0, .61111, 0, 0, .525], 928: [0, .61111, 0, 0, .525], 931: [0, .61111, 0, 0, .525], 933: [0, .61111, 0, 0, .525], 934: [0, .61111, 0, 0, .525], 936: [0, .61111, 0, 0, .525], 937: [0, .61111, 0, 0, .525], 8216: [0, .61111, 0, 0, .525], 8217: [0, .61111, 0, 0, .525], 8242: [0, .61111, 0, 0, .525], 9251: [.11111, .21944, 0, 0, .525] } }; const B = { slant: [.25, .25, .25], space: [0, 0, 0], stretch: [0, 0, 0], shrink: [0, 0, 0], xHeight: [.431, .431, .431], quad: [1, 1.171, 1.472], extraSpace: [0, 0, 0], num1: [.677, .732, .925], num2: [.394, .384, .387], num3: [.444, .471, .504], denom1: [.686, .752, 1.025], denom2: [.345, .344, .532], sup1: [.413, .503, .504], sup2: [.363, .431, .404], sup3: [.289, .286, .294], sub1: [.15, .143, .2], sub2: [.247, .286, .4], supDrop: [.386, .353, .494], subDrop: [.05, .071, .1], delim1: [2.39, 1.7, 1.98], delim2: [1.01, 1.157, 1.42], axisHeight: [.25, .25, .25], defaultRuleThickness: [.04, .049, .049], bigOpSpacing1: [.111, .111, .111], bigOpSpacing2: [.166, .166, .166], bigOpSpacing3: [.2, .2, .2], bigOpSpacing4: [.6, .611, .611], bigOpSpacing5: [.1, .143, .143], sqrtRuleThickness: [.04, .04, .04], ptPerEm: [10, 10, 10], doubleRuleSep: [.2, .2, .2], arrayRuleWidth: [.04, .04, .04], fboxsep: [.3, .3, .3], fboxrule: [.04, .04, .04] }, C = { "\xc5": "A", "\xd0": "D", "\xde": "o", "\xe5": "a", "\xf0": "d", "\xfe": "o", "\u0410": "A", "\u0411": "B", "\u0412": "B", "\u0413": "F", "\u0414": "A", "\u0415": "E", "\u0416": "K", "\u0417": "3", "\u0418": "N", "\u0419": "N", "\u041a": "K", "\u041b": "N", "\u041c": "M", "\u041d": "H", "\u041e": "O", "\u041f": "N", "\u0420": "P", "\u0421": "C", "\u0422": "T", "\u0423": "y", "\u0424": "O", "\u0425": "X", "\u0426": "U", "\u0427": "h", "\u0428": "W", "\u0429": "W", "\u042a": "B", "\u042b": "X", "\u042c": "B", "\u042d": "3", "\u042e": "X", "\u042f": "R", "\u0430": "a", "\u0431": "b", "\u0432": "a", "\u0433": "r", "\u0434": "y", "\u0435": "e", "\u0436": "m", "\u0437": "e", "\u0438": "n", "\u0439": "n", "\u043a": "n", "\u043b": "n", "\u043c": "m", "\u043d": "n", "\u043e": "o", "\u043f": "n", "\u0440": "p", "\u0441": "c", "\u0442": "o", "\u0443": "y", "\u0444": "b", "\u0445": "x", "\u0446": "n", "\u0447": "n", "\u0448": "w", "\u0449": "w", "\u044a": "a", "\u044b": "m", "\u044c": "a", "\u044d": "e", "\u044e": "m", "\u044f": "r" }; function N(e, t, r) { if (!T[t]) throw new Error("Font metrics not found for font: " + t + "."); let n = e.charCodeAt(0), o = T[t][n]; if (!o && e[0] in C && (n = C[e[0]].charCodeAt(0), o = T[t][n]), o || "text" !== r || S(n) && (o = T[t][77]), o) return { depth: o[0], height: o[1], italic: o[2], skew: o[3], width: o[4] } } const q = {}; const I = [[1, 1, 1], [2, 1, 1], [3, 1, 1], [4, 2, 1], [5, 2, 1], [6, 3, 1], [7, 4, 2], [8, 6, 3], [9, 7, 6], [10, 8, 7], [11, 10, 9]], R = [.5, .6, .7, .8, .9, 1, 1.2, 1.44, 1.728, 2.074, 2.488], H = function (e, t) { return t.size < 2 ? e : I[e - 1][t.size - 1] }; class O { constructor(e) { this.style = void 0, this.color = void 0, this.size = void 0, this.textSize = void 0, this.phantom = void 0, this.font = void 0, this.fontFamily = void 0, this.fontWeight = void 0, this.fontShape = void 0, this.sizeMultiplier = void 0, this.maxSize = void 0, this.minRuleThickness = void 0, this._fontMetrics = void 0, this.style = e.style, this.color = e.color, this.size = e.size || O.BASESIZE, this.textSize = e.textSize || this.size, this.phantom = !!e.phantom, this.font = e.font || "", this.fontFamily = e.fontFamily || "", this.fontWeight = e.fontWeight || "", this.fontShape = e.fontShape || "", this.sizeMultiplier = R[this.size - 1], this.maxSize = e.maxSize, this.minRuleThickness = e.minRuleThickness, this._fontMetrics = void 0 } extend(e) { const t = { style: this.style, size: this.size, textSize: this.textSize, color: this.color, phantom: this.phantom, font: this.font, fontFamily: this.fontFamily, fontWeight: this.fontWeight, fontShape: this.fontShape, maxSize: this.maxSize, minRuleThickness: this.minRuleThickness }; for (const r in e) e.hasOwnProperty(r) && (t[r] = e[r]); return new O(t) } havingStyle(e) { return this.style === e ? this : this.extend({ style: e, size: H(this.textSize, e) }) } havingCrampedStyle() { return this.havingStyle(this.style.cramp()) } havingSize(e) { return this.size === e && this.textSize === e ? this : this.extend({ style: this.style.text(), size: e, textSize: e, sizeMultiplier: R[e - 1] }) } havingBaseStyle(e) { e = e || this.style.text(); const t = H(O.BASESIZE, e); return this.size === t && this.textSize === O.BASESIZE && this.style === e ? this : this.extend({ style: e, size: t }) } havingBaseSizing() { let e; switch (this.style.id) { case 4: case 5: e = 3; break; case 6: case 7: e = 1; break; default: e = 6 }return this.extend({ style: this.style.text(), size: e }) } withColor(e) { return this.extend({ color: e }) } withPhantom() { return this.extend({ phantom: !0 }) } withFont(e) { return this.extend({ font: e }) } withTextFontFamily(e) { return this.extend({ fontFamily: e, font: "" }) } withTextFontWeight(e) { return this.extend({ fontWeight: e, font: "" }) } withTextFontShape(e) { return this.extend({ fontShape: e, font: "" }) } sizingClasses(e) { return e.size !== this.size ? ["sizing", "reset-size" + e.size, "size" + this.size] : [] } baseSizingClasses() { return this.size !== O.BASESIZE ? ["sizing", "reset-size" + this.size, "size" + O.BASESIZE] : [] } fontMetrics() { return this._fontMetrics || (this._fontMetrics = function (e) { let t; if (t = e >= 5 ? 0 : e >= 3 ? 1 : 2, !q[t]) { const e = q[t] = { cssEmPerMu: B.quad[t] / 18 }; for (const r in B) B.hasOwnProperty(r) && (e[r] = B[r][t]) } return q[t] }(this.size)), this._fontMetrics } getColor() { return this.phantom ? "transparent" : this.color } } O.BASESIZE = 6; var E = O; const L = { pt: 1, mm: 7227 / 2540, cm: 7227 / 254, in: 72.27, bp: 1.00375, pc: 12, dd: 1238 / 1157, cc: 14856 / 1157, nd: 685 / 642, nc: 1370 / 107, sp: 1 / 65536, px: 1.00375 }, D = { ex: !0, em: !0, mu: !0 }, V = function (e) { return "string" != typeof e && (e = e.unit), e in L || e in D || "ex" === e }, P = function (e, t) { let r; if (e.unit in L) r = L[e.unit] / t.fontMetrics().ptPerEm / t.sizeMultiplier; else if ("mu" === e.unit) r = t.fontMetrics().cssEmPerMu; else { let o; if (o = t.style.isTight() ? t.havingStyle(t.style.text()) : t, "ex" === e.unit) r = o.fontMetrics().xHeight; else { if ("em" !== e.unit) throw new n("Invalid unit: '" + e.unit + "'"); r = o.fontMetrics().quad } o !== t && (r *= o.sizeMultiplier / t.sizeMultiplier) } return Math.min(e.number * r, t.maxSize) }, F = function (e) { return +e.toFixed(4) + "em" }, G = function (e) { return e.filter((e => e)).join(" ") }, U = function (e, t, r) { if (this.classes = e || [], this.attributes = {}, this.height = 0, this.depth = 0, this.maxFontSize = 0, this.style = r || {}, t) { t.style.isTight() && this.classes.push("mtight"); const e = t.getColor(); e && (this.style.color = e) } }, Y = function (e) { const t = document.createElement(e); t.className = G(this.classes); for (const e in this.style) this.style.hasOwnProperty(e) && (t.style[e] = this.style[e]); for (const e in this.attributes) this.attributes.hasOwnProperty(e) && t.setAttribute(e, this.attributes[e]); for (let e = 0; e < this.children.length; e++)t.appendChild(this.children[e].toNode()); return t }, X = function (e) { let t = "<" + e; this.classes.length && (t += ' class="' + l.escape(G(this.classes)) + '"'); let r = ""; for (const e in this.style) this.style.hasOwnProperty(e) && (r += l.hyphenate(e) + ":" + this.style[e] + ";"); r && (t += ' style="' + l.escape(r) + '"'); for (const e in this.attributes) this.attributes.hasOwnProperty(e) && (t += " " + e + '="' + l.escape(this.attributes[e]) + '"'); t += ">"; for (let e = 0; e < this.children.length; e++)t += this.children[e].toMarkup(); return t += "" + e + ">", t }; class W { constructor(e, t, r, n) { this.children = void 0, this.attributes = void 0, this.classes = void 0, this.height = void 0, this.depth = void 0, this.width = void 0, this.maxFontSize = void 0, this.style = void 0, U.call(this, e, r, n), this.children = t || [] } setAttribute(e, t) { this.attributes[e] = t } hasClass(e) { return l.contains(this.classes, e) } toNode() { return Y.call(this, "span") } toMarkup() { return X.call(this, "span") } } class _ { constructor(e, t, r, n) { this.children = void 0, this.attributes = void 0, this.classes = void 0, this.height = void 0, this.depth = void 0, this.maxFontSize = void 0, this.style = void 0, U.call(this, t, n), this.children = r || [], this.setAttribute("href", e) } setAttribute(e, t) { this.attributes[e] = t } hasClass(e) { return l.contains(this.classes, e) } toNode() { return Y.call(this, "a") } toMarkup() { return X.call(this, "a") } } class j { constructor(e, t, r) { this.src = void 0, this.alt = void 0, this.classes = void 0, this.height = void 0, this.depth = void 0, this.maxFontSize = void 0, this.style = void 0, this.alt = t, this.src = e, this.classes = ["mord"], this.style = r } hasClass(e) { return l.contains(this.classes, e) } toNode() { const e = document.createElement("img"); e.src = this.src, e.alt = this.alt, e.className = "mord"; for (const t in this.style) this.style.hasOwnProperty(t) && (e.style[t] = this.style[t]); return e } toMarkup() { let e = '", e } } const $ = { "\xee": "\u0131\u0302", "\xef": "\u0131\u0308", "\xed": "\u0131\u0301", "\xec": "\u0131\u0300" }; class Z { constructor(e, t, r, n, o, s, i, a) { this.text = void 0, this.height = void 0, this.depth = void 0, this.italic = void 0, this.skew = void 0, this.width = void 0, this.maxFontSize = void 0, this.classes = void 0, this.style = void 0, this.text = e, this.height = t || 0, this.depth = r || 0, this.italic = n || 0, this.skew = o || 0, this.width = s || 0, this.classes = i || [], this.style = a || {}, this.maxFontSize = 0; const l = function (e) { for (let t = 0; t < v.length; t++) { const r = v[t]; for (let t = 0; t < r.blocks.length; t++) { const n = r.blocks[t]; if (e >= n[0] && e <= n[1]) return r.name } } return null }(this.text.charCodeAt(0)); l && this.classes.push(l + "_fallback"), /[\xee\xef\xed\xec]/.test(this.text) && (this.text = $[this.text]) } hasClass(e) { return l.contains(this.classes, e) } toNode() { const e = document.createTextNode(this.text); let t = null; this.italic > 0 && (t = document.createElement("span"), t.style.marginRight = F(this.italic)), this.classes.length > 0 && (t = t || document.createElement("span"), t.className = G(this.classes)); for (const e in this.style) this.style.hasOwnProperty(e) && (t = t || document.createElement("span"), t.style[e] = this.style[e]); return t ? (t.appendChild(e), t) : e } toMarkup() { let e = !1, t = " 0 && (r += "margin-right:" + this.italic + "em;"); for (const e in this.style) this.style.hasOwnProperty(e) && (r += l.hyphenate(e) + ":" + this.style[e] + ";"); r && (e = !0, t += ' style="' + l.escape(r) + '"'); const n = l.escape(this.text); return e ? (t += ">", t += n, t += "", t) : n } } class K { constructor(e, t) { this.children = void 0, this.attributes = void 0, this.children = e || [], this.attributes = t || {} } toNode() { const e = document.createElementNS("http://www.w3.org/2000/svg", "svg"); for (const t in this.attributes) Object.prototype.hasOwnProperty.call(this.attributes, t) && e.setAttribute(t, this.attributes[t]); for (let t = 0; t < this.children.length; t++)e.appendChild(this.children[t].toNode()); return e } toMarkup() { let e = '", e } } class J { constructor(e, t) { this.pathName = void 0, this.alternate = void 0, this.pathName = e, this.alternate = t } toNode() { const e = document.createElementNS("http://www.w3.org/2000/svg", "path"); return this.alternate ? e.setAttribute("d", this.alternate) : e.setAttribute("d", z[this.pathName]), e } toMarkup() { return this.alternate ? '' : '' } } class Q { constructor(e) { this.attributes = void 0, this.attributes = e || {} } toNode() { const e = document.createElementNS("http://www.w3.org/2000/svg", "line"); for (const t in this.attributes) Object.prototype.hasOwnProperty.call(this.attributes, t) && e.setAttribute(t, this.attributes[t]); return e } toMarkup() { let e = "", e } } function ee(e) { if (e instanceof Z) return e; throw new Error("Expected symbolNode but got " + String(e) + ".") } const te = { bin: 1, close: 1, inner: 1, open: 1, punct: 1, rel: 1 }, re = { "accent-token": 1, mathord: 1, "op-token": 1, spacing: 1, textord: 1 }, ne = { math: {}, text: {} }; var oe = ne; function se(e, t, r, n, o, s) { ne[e][o] = { font: t, group: r, replace: n }, s && n && (ne[e][n] = ne[e][o]) } const ie = "math", ae = "text", le = "main", he = "ams", ce = "accent-token", me = "bin", pe = "close", ue = "inner", de = "mathord", ge = "op-token", fe = "open", be = "punct", ye = "rel", xe = "spacing", we = "textord"; se(ie, le, ye, "\u2261", "\\equiv", !0), se(ie, le, ye, "\u227a", "\\prec", !0), se(ie, le, ye, "\u227b", "\\succ", !0), se(ie, le, ye, "\u223c", "\\sim", !0), se(ie, le, ye, "\u22a5", "\\perp"), se(ie, le, ye, "\u2aaf", "\\preceq", !0), se(ie, le, ye, "\u2ab0", "\\succeq", !0), se(ie, le, ye, "\u2243", "\\simeq", !0), se(ie, le, ye, "\u2223", "\\mid", !0), se(ie, le, ye, "\u226a", "\\ll", !0), se(ie, le, ye, "\u226b", "\\gg", !0), se(ie, le, ye, "\u224d", "\\asymp", !0), se(ie, le, ye, "\u2225", "\\parallel"), se(ie, le, ye, "\u22c8", "\\bowtie", !0), se(ie, le, ye, "\u2323", "\\smile", !0), se(ie, le, ye, "\u2291", "\\sqsubseteq", !0), se(ie, le, ye, "\u2292", "\\sqsupseteq", !0), se(ie, le, ye, "\u2250", "\\doteq", !0), se(ie, le, ye, "\u2322", "\\frown", !0), se(ie, le, ye, "\u220b", "\\ni", !0), se(ie, le, ye, "\u221d", "\\propto", !0), se(ie, le, ye, "\u22a2", "\\vdash", !0), se(ie, le, ye, "\u22a3", "\\dashv", !0), se(ie, le, ye, "\u220b", "\\owns"), se(ie, le, be, ".", "\\ldotp"), se(ie, le, be, "\u22c5", "\\cdotp"), se(ie, le, we, "#", "\\#"), se(ae, le, we, "#", "\\#"), se(ie, le, we, "&", "\\&"), se(ae, le, we, "&", "\\&"), se(ie, le, we, "\u2135", "\\aleph", !0), se(ie, le, we, "\u2200", "\\forall", !0), se(ie, le, we, "\u210f", "\\hbar", !0), se(ie, le, we, "\u2203", "\\exists", !0), se(ie, le, we, "\u2207", "\\nabla", !0), se(ie, le, we, "\u266d", "\\flat", !0), se(ie, le, we, "\u2113", "\\ell", !0), se(ie, le, we, "\u266e", "\\natural", !0), se(ie, le, we, "\u2663", "\\clubsuit", !0), se(ie, le, we, "\u2118", "\\wp", !0), se(ie, le, we, "\u266f", "\\sharp", !0), se(ie, le, we, "\u2662", "\\diamondsuit", !0), se(ie, le, we, "\u211c", "\\Re", !0), se(ie, le, we, "\u2661", "\\heartsuit", !0), se(ie, le, we, "\u2111", "\\Im", !0), se(ie, le, we, "\u2660", "\\spadesuit", !0), se(ie, le, we, "\xa7", "\\S", !0), se(ae, le, we, "\xa7", "\\S"), se(ie, le, we, "\xb6", "\\P", !0), se(ae, le, we, "\xb6", "\\P"), se(ie, le, we, "\u2020", "\\dag"), se(ae, le, we, "\u2020", "\\dag"), se(ae, le, we, "\u2020", "\\textdagger"), se(ie, le, we, "\u2021", "\\ddag"), se(ae, le, we, "\u2021", "\\ddag"), se(ae, le, we, "\u2021", "\\textdaggerdbl"), se(ie, le, pe, "\u23b1", "\\rmoustache", !0), se(ie, le, fe, "\u23b0", "\\lmoustache", !0), se(ie, le, pe, "\u27ef", "\\rgroup", !0), se(ie, le, fe, "\u27ee", "\\lgroup", !0), se(ie, le, me, "\u2213", "\\mp", !0), se(ie, le, me, "\u2296", "\\ominus", !0), se(ie, le, me, "\u228e", "\\uplus", !0), se(ie, le, me, "\u2293", "\\sqcap", !0), se(ie, le, me, "\u2217", "\\ast"), se(ie, le, me, "\u2294", "\\sqcup", !0), se(ie, le, me, "\u25ef", "\\bigcirc", !0), se(ie, le, me, "\u2219", "\\bullet", !0), se(ie, le, me, "\u2021", "\\ddagger"), se(ie, le, me, "\u2240", "\\wr", !0), se(ie, le, me, "\u2a3f", "\\amalg"), se(ie, le, me, "&", "\\And"), se(ie, le, ye, "\u27f5", "\\longleftarrow", !0), se(ie, le, ye, "\u21d0", "\\Leftarrow", !0), se(ie, le, ye, "\u27f8", "\\Longleftarrow", !0), se(ie, le, ye, "\u27f6", "\\longrightarrow", !0), se(ie, le, ye, "\u21d2", "\\Rightarrow", !0), se(ie, le, ye, "\u27f9", "\\Longrightarrow", !0), se(ie, le, ye, "\u2194", "\\leftrightarrow", !0), se(ie, le, ye, "\u27f7", "\\longleftrightarrow", !0), se(ie, le, ye, "\u21d4", "\\Leftrightarrow", !0), se(ie, le, ye, "\u27fa", "\\Longleftrightarrow", !0), se(ie, le, ye, "\u21a6", "\\mapsto", !0), se(ie, le, ye, "\u27fc", "\\longmapsto", !0), se(ie, le, ye, "\u2197", "\\nearrow", !0), se(ie, le, ye, "\u21a9", "\\hookleftarrow", !0), se(ie, le, ye, "\u21aa", "\\hookrightarrow", !0), se(ie, le, ye, "\u2198", "\\searrow", !0), se(ie, le, ye, "\u21bc", "\\leftharpoonup", !0), se(ie, le, ye, "\u21c0", "\\rightharpoonup", !0), se(ie, le, ye, "\u2199", "\\swarrow", !0), se(ie, le, ye, "\u21bd", "\\leftharpoondown", !0), se(ie, le, ye, "\u21c1", "\\rightharpoondown", !0), se(ie, le, ye, "\u2196", "\\nwarrow", !0), se(ie, le, ye, "\u21cc", "\\rightleftharpoons", !0), se(ie, he, ye, "\u226e", "\\nless", !0), se(ie, he, ye, "\ue010", "\\@nleqslant"), se(ie, he, ye, "\ue011", "\\@nleqq"), se(ie, he, ye, "\u2a87", "\\lneq", !0), se(ie, he, ye, "\u2268", "\\lneqq", !0), se(ie, he, ye, "\ue00c", "\\@lvertneqq"), se(ie, he, ye, "\u22e6", "\\lnsim", !0), se(ie, he, ye, "\u2a89", "\\lnapprox", !0), se(ie, he, ye, "\u2280", "\\nprec", !0), se(ie, he, ye, "\u22e0", "\\npreceq", !0), se(ie, he, ye, "\u22e8", "\\precnsim", !0), se(ie, he, ye, "\u2ab9", "\\precnapprox", !0), se(ie, he, ye, "\u2241", "\\nsim", !0), se(ie, he, ye, "\ue006", "\\@nshortmid"), se(ie, he, ye, "\u2224", "\\nmid", !0), se(ie, he, ye, "\u22ac", "\\nvdash", !0), se(ie, he, ye, "\u22ad", "\\nvDash", !0), se(ie, he, ye, "\u22ea", "\\ntriangleleft"), se(ie, he, ye, "\u22ec", "\\ntrianglelefteq", !0), se(ie, he, ye, "\u228a", "\\subsetneq", !0), se(ie, he, ye, "\ue01a", "\\@varsubsetneq"), se(ie, he, ye, "\u2acb", "\\subsetneqq", !0), se(ie, he, ye, "\ue017", "\\@varsubsetneqq"), se(ie, he, ye, "\u226f", "\\ngtr", !0), se(ie, he, ye, "\ue00f", "\\@ngeqslant"), se(ie, he, ye, "\ue00e", "\\@ngeqq"), se(ie, he, ye, "\u2a88", "\\gneq", !0), se(ie, he, ye, "\u2269", "\\gneqq", !0), se(ie, he, ye, "\ue00d", "\\@gvertneqq"), se(ie, he, ye, "\u22e7", "\\gnsim", !0), se(ie, he, ye, "\u2a8a", "\\gnapprox", !0), se(ie, he, ye, "\u2281", "\\nsucc", !0), se(ie, he, ye, "\u22e1", "\\nsucceq", !0), se(ie, he, ye, "\u22e9", "\\succnsim", !0), se(ie, he, ye, "\u2aba", "\\succnapprox", !0), se(ie, he, ye, "\u2246", "\\ncong", !0), se(ie, he, ye, "\ue007", "\\@nshortparallel"), se(ie, he, ye, "\u2226", "\\nparallel", !0), se(ie, he, ye, "\u22af", "\\nVDash", !0), se(ie, he, ye, "\u22eb", "\\ntriangleright"), se(ie, he, ye, "\u22ed", "\\ntrianglerighteq", !0), se(ie, he, ye, "\ue018", "\\@nsupseteqq"), se(ie, he, ye, "\u228b", "\\supsetneq", !0), se(ie, he, ye, "\ue01b", "\\@varsupsetneq"), se(ie, he, ye, "\u2acc", "\\supsetneqq", !0), se(ie, he, ye, "\ue019", "\\@varsupsetneqq"), se(ie, he, ye, "\u22ae", "\\nVdash", !0), se(ie, he, ye, "\u2ab5", "\\precneqq", !0), se(ie, he, ye, "\u2ab6", "\\succneqq", !0), se(ie, he, ye, "\ue016", "\\@nsubseteqq"), se(ie, he, me, "\u22b4", "\\unlhd"), se(ie, he, me, "\u22b5", "\\unrhd"), se(ie, he, ye, "\u219a", "\\nleftarrow", !0), se(ie, he, ye, "\u219b", "\\nrightarrow", !0), se(ie, he, ye, "\u21cd", "\\nLeftarrow", !0), se(ie, he, ye, "\u21cf", "\\nRightarrow", !0), se(ie, he, ye, "\u21ae", "\\nleftrightarrow", !0), se(ie, he, ye, "\u21ce", "\\nLeftrightarrow", !0), se(ie, he, ye, "\u25b3", "\\vartriangle"), se(ie, he, we, "\u210f", "\\hslash"), se(ie, he, we, "\u25bd", "\\triangledown"), se(ie, he, we, "\u25ca", "\\lozenge"), se(ie, he, we, "\u24c8", "\\circledS"), se(ie, he, we, "\xae", "\\circledR"), se(ae, he, we, "\xae", "\\circledR"), se(ie, he, we, "\u2221", "\\measuredangle", !0), se(ie, he, we, "\u2204", "\\nexists"), se(ie, he, we, "\u2127", "\\mho"), se(ie, he, we, "\u2132", "\\Finv", !0), se(ie, he, we, "\u2141", "\\Game", !0), se(ie, he, we, "\u2035", "\\backprime"), se(ie, he, we, "\u25b2", "\\blacktriangle"), se(ie, he, we, "\u25bc", "\\blacktriangledown"), se(ie, he, we, "\u25a0", "\\blacksquare"), se(ie, he, we, "\u29eb", "\\blacklozenge"), se(ie, he, we, "\u2605", "\\bigstar"), se(ie, he, we, "\u2222", "\\sphericalangle", !0), se(ie, he, we, "\u2201", "\\complement", !0), se(ie, he, we, "\xf0", "\\eth", !0), se(ae, le, we, "\xf0", "\xf0"), se(ie, he, we, "\u2571", "\\diagup"), se(ie, he, we, "\u2572", "\\diagdown"), se(ie, he, we, "\u25a1", "\\square"), se(ie, he, we, "\u25a1", "\\Box"), se(ie, he, we, "\u25ca", "\\Diamond"), se(ie, he, we, "\xa5", "\\yen", !0), se(ae, he, we, "\xa5", "\\yen", !0), se(ie, he, we, "\u2713", "\\checkmark", !0), se(ae, he, we, "\u2713", "\\checkmark"), se(ie, he, we, "\u2136", "\\beth", !0), se(ie, he, we, "\u2138", "\\daleth", !0), se(ie, he, we, "\u2137", "\\gimel", !0), se(ie, he, we, "\u03dd", "\\digamma", !0), se(ie, he, we, "\u03f0", "\\varkappa"), se(ie, he, fe, "\u250c", "\\@ulcorner", !0), se(ie, he, pe, "\u2510", "\\@urcorner", !0), se(ie, he, fe, "\u2514", "\\@llcorner", !0), se(ie, he, pe, "\u2518", "\\@lrcorner", !0), se(ie, he, ye, "\u2266", "\\leqq", !0), se(ie, he, ye, "\u2a7d", "\\leqslant", !0), se(ie, he, ye, "\u2a95", "\\eqslantless", !0), se(ie, he, ye, "\u2272", "\\lesssim", !0), se(ie, he, ye, "\u2a85", "\\lessapprox", !0), se(ie, he, ye, "\u224a", "\\approxeq", !0), se(ie, he, me, "\u22d6", "\\lessdot"), se(ie, he, ye, "\u22d8", "\\lll", !0), se(ie, he, ye, "\u2276", "\\lessgtr", !0), se(ie, he, ye, "\u22da", "\\lesseqgtr", !0), se(ie, he, ye, "\u2a8b", "\\lesseqqgtr", !0), se(ie, he, ye, "\u2251", "\\doteqdot"), se(ie, he, ye, "\u2253", "\\risingdotseq", !0), se(ie, he, ye, "\u2252", "\\fallingdotseq", !0), se(ie, he, ye, "\u223d", "\\backsim", !0), se(ie, he, ye, "\u22cd", "\\backsimeq", !0), se(ie, he, ye, "\u2ac5", "\\subseteqq", !0), se(ie, he, ye, "\u22d0", "\\Subset", !0), se(ie, he, ye, "\u228f", "\\sqsubset", !0), se(ie, he, ye, "\u227c", "\\preccurlyeq", !0), se(ie, he, ye, "\u22de", "\\curlyeqprec", !0), se(ie, he, ye, "\u227e", "\\precsim", !0), se(ie, he, ye, "\u2ab7", "\\precapprox", !0), se(ie, he, ye, "\u22b2", "\\vartriangleleft"), se(ie, he, ye, "\u22b4", "\\trianglelefteq"), se(ie, he, ye, "\u22a8", "\\vDash", !0), se(ie, he, ye, "\u22aa", "\\Vvdash", !0), se(ie, he, ye, "\u2323", "\\smallsmile"), se(ie, he, ye, "\u2322", "\\smallfrown"), se(ie, he, ye, "\u224f", "\\bumpeq", !0), se(ie, he, ye, "\u224e", "\\Bumpeq", !0), se(ie, he, ye, "\u2267", "\\geqq", !0), se(ie, he, ye, "\u2a7e", "\\geqslant", !0), se(ie, he, ye, "\u2a96", "\\eqslantgtr", !0), se(ie, he, ye, "\u2273", "\\gtrsim", !0), se(ie, he, ye, "\u2a86", "\\gtrapprox", !0), se(ie, he, me, "\u22d7", "\\gtrdot"), se(ie, he, ye, "\u22d9", "\\ggg", !0), se(ie, he, ye, "\u2277", "\\gtrless", !0), se(ie, he, ye, "\u22db", "\\gtreqless", !0), se(ie, he, ye, "\u2a8c", "\\gtreqqless", !0), se(ie, he, ye, "\u2256", "\\eqcirc", !0), se(ie, he, ye, "\u2257", "\\circeq", !0), se(ie, he, ye, "\u225c", "\\triangleq", !0), se(ie, he, ye, "\u223c", "\\thicksim"), se(ie, he, ye, "\u2248", "\\thickapprox"), se(ie, he, ye, "\u2ac6", "\\supseteqq", !0), se(ie, he, ye, "\u22d1", "\\Supset", !0), se(ie, he, ye, "\u2290", "\\sqsupset", !0), se(ie, he, ye, "\u227d", "\\succcurlyeq", !0), se(ie, he, ye, "\u22df", "\\curlyeqsucc", !0), se(ie, he, ye, "\u227f", "\\succsim", !0), se(ie, he, ye, "\u2ab8", "\\succapprox", !0), se(ie, he, ye, "\u22b3", "\\vartriangleright"), se(ie, he, ye, "\u22b5", "\\trianglerighteq"), se(ie, he, ye, "\u22a9", "\\Vdash", !0), se(ie, he, ye, "\u2223", "\\shortmid"), se(ie, he, ye, "\u2225", "\\shortparallel"), se(ie, he, ye, "\u226c", "\\between", !0), se(ie, he, ye, "\u22d4", "\\pitchfork", !0), se(ie, he, ye, "\u221d", "\\varpropto"), se(ie, he, ye, "\u25c0", "\\blacktriangleleft"), se(ie, he, ye, "\u2234", "\\therefore", !0), se(ie, he, ye, "\u220d", "\\backepsilon"), se(ie, he, ye, "\u25b6", "\\blacktriangleright"), se(ie, he, ye, "\u2235", "\\because", !0), se(ie, he, ye, "\u22d8", "\\llless"), se(ie, he, ye, "\u22d9", "\\gggtr"), se(ie, he, me, "\u22b2", "\\lhd"), se(ie, he, me, "\u22b3", "\\rhd"), se(ie, he, ye, "\u2242", "\\eqsim", !0), se(ie, le, ye, "\u22c8", "\\Join"), se(ie, he, ye, "\u2251", "\\Doteq", !0), se(ie, he, me, "\u2214", "\\dotplus", !0), se(ie, he, me, "\u2216", "\\smallsetminus"), se(ie, he, me, "\u22d2", "\\Cap", !0), se(ie, he, me, "\u22d3", "\\Cup", !0), se(ie, he, me, "\u2a5e", "\\doublebarwedge", !0), se(ie, he, me, "\u229f", "\\boxminus", !0), se(ie, he, me, "\u229e", "\\boxplus", !0), se(ie, he, me, "\u22c7", "\\divideontimes", !0), se(ie, he, me, "\u22c9", "\\ltimes", !0), se(ie, he, me, "\u22ca", "\\rtimes", !0), se(ie, he, me, "\u22cb", "\\leftthreetimes", !0), se(ie, he, me, "\u22cc", "\\rightthreetimes", !0), se(ie, he, me, "\u22cf", "\\curlywedge", !0), se(ie, he, me, "\u22ce", "\\curlyvee", !0), se(ie, he, me, "\u229d", "\\circleddash", !0), se(ie, he, me, "\u229b", "\\circledast", !0), se(ie, he, me, "\u22c5", "\\centerdot"), se(ie, he, me, "\u22ba", "\\intercal", !0), se(ie, he, me, "\u22d2", "\\doublecap"), se(ie, he, me, "\u22d3", "\\doublecup"), se(ie, he, me, "\u22a0", "\\boxtimes", !0), se(ie, he, ye, "\u21e2", "\\dashrightarrow", !0), se(ie, he, ye, "\u21e0", "\\dashleftarrow", !0), se(ie, he, ye, "\u21c7", "\\leftleftarrows", !0), se(ie, he, ye, "\u21c6", "\\leftrightarrows", !0), se(ie, he, ye, "\u21da", "\\Lleftarrow", !0), se(ie, he, ye, "\u219e", "\\twoheadleftarrow", !0), se(ie, he, ye, "\u21a2", "\\leftarrowtail", !0), se(ie, he, ye, "\u21ab", "\\looparrowleft", !0), se(ie, he, ye, "\u21cb", "\\leftrightharpoons", !0), se(ie, he, ye, "\u21b6", "\\curvearrowleft", !0), se(ie, he, ye, "\u21ba", "\\circlearrowleft", !0), se(ie, he, ye, "\u21b0", "\\Lsh", !0), se(ie, he, ye, "\u21c8", "\\upuparrows", !0), se(ie, he, ye, "\u21bf", "\\upharpoonleft", !0), se(ie, he, ye, "\u21c3", "\\downharpoonleft", !0), se(ie, le, ye, "\u22b6", "\\origof", !0), se(ie, le, ye, "\u22b7", "\\imageof", !0), se(ie, he, ye, "\u22b8", "\\multimap", !0), se(ie, he, ye, "\u21ad", "\\leftrightsquigarrow", !0), se(ie, he, ye, "\u21c9", "\\rightrightarrows", !0), se(ie, he, ye, "\u21c4", "\\rightleftarrows", !0), se(ie, he, ye, "\u21a0", "\\twoheadrightarrow", !0), se(ie, he, ye, "\u21a3", "\\rightarrowtail", !0), se(ie, he, ye, "\u21ac", "\\looparrowright", !0), se(ie, he, ye, "\u21b7", "\\curvearrowright", !0), se(ie, he, ye, "\u21bb", "\\circlearrowright", !0), se(ie, he, ye, "\u21b1", "\\Rsh", !0), se(ie, he, ye, "\u21ca", "\\downdownarrows", !0), se(ie, he, ye, "\u21be", "\\upharpoonright", !0), se(ie, he, ye, "\u21c2", "\\downharpoonright", !0), se(ie, he, ye, "\u21dd", "\\rightsquigarrow", !0), se(ie, he, ye, "\u21dd", "\\leadsto"), se(ie, he, ye, "\u21db", "\\Rrightarrow", !0), se(ie, he, ye, "\u21be", "\\restriction"), se(ie, le, we, "\u2018", "`"), se(ie, le, we, "$", "\\$"), se(ae, le, we, "$", "\\$"), se(ae, le, we, "$", "\\textdollar"), se(ie, le, we, "%", "\\%"), se(ae, le, we, "%", "\\%"), se(ie, le, we, "_", "\\_"), se(ae, le, we, "_", "\\_"), se(ae, le, we, "_", "\\textunderscore"), se(ie, le, we, "\u2220", "\\angle", !0), se(ie, le, we, "\u221e", "\\infty", !0), se(ie, le, we, "\u2032", "\\prime"), se(ie, le, we, "\u25b3", "\\triangle"), se(ie, le, we, "\u0393", "\\Gamma", !0), se(ie, le, we, "\u0394", "\\Delta", !0), se(ie, le, we, "\u0398", "\\Theta", !0), se(ie, le, we, "\u039b", "\\Lambda", !0), se(ie, le, we, "\u039e", "\\Xi", !0), se(ie, le, we, "\u03a0", "\\Pi", !0), se(ie, le, we, "\u03a3", "\\Sigma", !0), se(ie, le, we, "\u03a5", "\\Upsilon", !0), se(ie, le, we, "\u03a6", "\\Phi", !0), se(ie, le, we, "\u03a8", "\\Psi", !0), se(ie, le, we, "\u03a9", "\\Omega", !0), se(ie, le, we, "A", "\u0391"), se(ie, le, we, "B", "\u0392"), se(ie, le, we, "E", "\u0395"), se(ie, le, we, "Z", "\u0396"), se(ie, le, we, "H", "\u0397"), se(ie, le, we, "I", "\u0399"), se(ie, le, we, "K", "\u039a"), se(ie, le, we, "M", "\u039c"), se(ie, le, we, "N", "\u039d"), se(ie, le, we, "O", "\u039f"), se(ie, le, we, "P", "\u03a1"), se(ie, le, we, "T", "\u03a4"), se(ie, le, we, "X", "\u03a7"), se(ie, le, we, "\xac", "\\neg", !0), se(ie, le, we, "\xac", "\\lnot"), se(ie, le, we, "\u22a4", "\\top"), se(ie, le, we, "\u22a5", "\\bot"), se(ie, le, we, "\u2205", "\\emptyset"), se(ie, he, we, "\u2205", "\\varnothing"), se(ie, le, de, "\u03b1", "\\alpha", !0), se(ie, le, de, "\u03b2", "\\beta", !0), se(ie, le, de, "\u03b3", "\\gamma", !0), se(ie, le, de, "\u03b4", "\\delta", !0), se(ie, le, de, "\u03f5", "\\epsilon", !0), se(ie, le, de, "\u03b6", "\\zeta", !0), se(ie, le, de, "\u03b7", "\\eta", !0), se(ie, le, de, "\u03b8", "\\theta", !0), se(ie, le, de, "\u03b9", "\\iota", !0), se(ie, le, de, "\u03ba", "\\kappa", !0), se(ie, le, de, "\u03bb", "\\lambda", !0), se(ie, le, de, "\u03bc", "\\mu", !0), se(ie, le, de, "\u03bd", "\\nu", !0), se(ie, le, de, "\u03be", "\\xi", !0), se(ie, le, de, "\u03bf", "\\omicron", !0), se(ie, le, de, "\u03c0", "\\pi", !0), se(ie, le, de, "\u03c1", "\\rho", !0), se(ie, le, de, "\u03c3", "\\sigma", !0), se(ie, le, de, "\u03c4", "\\tau", !0), se(ie, le, de, "\u03c5", "\\upsilon", !0), se(ie, le, de, "\u03d5", "\\phi", !0), se(ie, le, de, "\u03c7", "\\chi", !0), se(ie, le, de, "\u03c8", "\\psi", !0), se(ie, le, de, "\u03c9", "\\omega", !0), se(ie, le, de, "\u03b5", "\\varepsilon", !0), se(ie, le, de, "\u03d1", "\\vartheta", !0), se(ie, le, de, "\u03d6", "\\varpi", !0), se(ie, le, de, "\u03f1", "\\varrho", !0), se(ie, le, de, "\u03c2", "\\varsigma", !0), se(ie, le, de, "\u03c6", "\\varphi", !0), se(ie, le, me, "\u2217", "*", !0), se(ie, le, me, "+", "+"), se(ie, le, me, "\u2212", "-", !0), se(ie, le, me, "\u22c5", "\\cdot", !0), se(ie, le, me, "\u2218", "\\circ", !0), se(ie, le, me, "\xf7", "\\div", !0), se(ie, le, me, "\xb1", "\\pm", !0), se(ie, le, me, "\xd7", "\\times", !0), se(ie, le, me, "\u2229", "\\cap", !0), se(ie, le, me, "\u222a", "\\cup", !0), se(ie, le, me, "\u2216", "\\setminus", !0), se(ie, le, me, "\u2227", "\\land"), se(ie, le, me, "\u2228", "\\lor"), se(ie, le, me, "\u2227", "\\wedge", !0), se(ie, le, me, "\u2228", "\\vee", !0), se(ie, le, we, "\u221a", "\\surd"), se(ie, le, fe, "\u27e8", "\\langle", !0), se(ie, le, fe, "\u2223", "\\lvert"), se(ie, le, fe, "\u2225", "\\lVert"), se(ie, le, pe, "?", "?"), se(ie, le, pe, "!", "!"), se(ie, le, pe, "\u27e9", "\\rangle", !0), se(ie, le, pe, "\u2223", "\\rvert"), se(ie, le, pe, "\u2225", "\\rVert"), se(ie, le, ye, "=", "="), se(ie, le, ye, ":", ":"), se(ie, le, ye, "\u2248", "\\approx", !0), se(ie, le, ye, "\u2245", "\\cong", !0), se(ie, le, ye, "\u2265", "\\ge"), se(ie, le, ye, "\u2265", "\\geq", !0), se(ie, le, ye, "\u2190", "\\gets"), se(ie, le, ye, ">", "\\gt", !0), se(ie, le, ye, "\u2208", "\\in", !0), se(ie, le, ye, "\ue020", "\\@not"), se(ie, le, ye, "\u2282", "\\subset", !0), se(ie, le, ye, "\u2283", "\\supset", !0), se(ie, le, ye, "\u2286", "\\subseteq", !0), se(ie, le, ye, "\u2287", "\\supseteq", !0), se(ie, he, ye, "\u2288", "\\nsubseteq", !0), se(ie, he, ye, "\u2289", "\\nsupseteq", !0), se(ie, le, ye, "\u22a8", "\\models"), se(ie, le, ye, "\u2190", "\\leftarrow", !0), se(ie, le, ye, "\u2264", "\\le"), se(ie, le, ye, "\u2264", "\\leq", !0), se(ie, le, ye, "<", "\\lt", !0), se(ie, le, ye, "\u2192", "\\rightarrow", !0), se(ie, le, ye, "\u2192", "\\to"), se(ie, he, ye, "\u2271", "\\ngeq", !0), se(ie, he, ye, "\u2270", "\\nleq", !0), se(ie, le, xe, "\xa0", "\\ "), se(ie, le, xe, "\xa0", "\\space"), se(ie, le, xe, "\xa0", "\\nobreakspace"), se(ae, le, xe, "\xa0", "\\ "), se(ae, le, xe, "\xa0", " "), se(ae, le, xe, "\xa0", "\\space"), se(ae, le, xe, "\xa0", "\\nobreakspace"), se(ie, le, xe, null, "\\nobreak"), se(ie, le, xe, null, "\\allowbreak"), se(ie, le, be, ",", ","), se(ie, le, be, ";", ";"), se(ie, he, me, "\u22bc", "\\barwedge", !0), se(ie, he, me, "\u22bb", "\\veebar", !0), se(ie, le, me, "\u2299", "\\odot", !0), se(ie, le, me, "\u2295", "\\oplus", !0), se(ie, le, me, "\u2297", "\\otimes", !0), se(ie, le, we, "\u2202", "\\partial", !0), se(ie, le, me, "\u2298", "\\oslash", !0), se(ie, he, me, "\u229a", "\\circledcirc", !0), se(ie, he, me, "\u22a1", "\\boxdot", !0), se(ie, le, me, "\u25b3", "\\bigtriangleup"), se(ie, le, me, "\u25bd", "\\bigtriangledown"), se(ie, le, me, "\u2020", "\\dagger"), se(ie, le, me, "\u22c4", "\\diamond"), se(ie, le, me, "\u22c6", "\\star"), se(ie, le, me, "\u25c3", "\\triangleleft"), se(ie, le, me, "\u25b9", "\\triangleright"), se(ie, le, fe, "{", "\\{"), se(ae, le, we, "{", "\\{"), se(ae, le, we, "{", "\\textbraceleft"), se(ie, le, pe, "}", "\\}"), se(ae, le, we, "}", "\\}"), se(ae, le, we, "}", "\\textbraceright"), se(ie, le, fe, "{", "\\lbrace"), se(ie, le, pe, "}", "\\rbrace"), se(ie, le, fe, "[", "\\lbrack", !0), se(ae, le, we, "[", "\\lbrack", !0), se(ie, le, pe, "]", "\\rbrack", !0), se(ae, le, we, "]", "\\rbrack", !0), se(ie, le, fe, "(", "\\lparen", !0), se(ie, le, pe, ")", "\\rparen", !0), se(ae, le, we, "<", "\\textless", !0), se(ae, le, we, ">", "\\textgreater", !0), se(ie, le, fe, "\u230a", "\\lfloor", !0), se(ie, le, pe, "\u230b", "\\rfloor", !0), se(ie, le, fe, "\u2308", "\\lceil", !0), se(ie, le, pe, "\u2309", "\\rceil", !0), se(ie, le, we, "\\", "\\backslash"), se(ie, le, we, "\u2223", "|"), se(ie, le, we, "\u2223", "\\vert"), se(ae, le, we, "|", "\\textbar", !0), se(ie, le, we, "\u2225", "\\|"), se(ie, le, we, "\u2225", "\\Vert"), se(ae, le, we, "\u2225", "\\textbardbl"), se(ae, le, we, "~", "\\textasciitilde"), se(ae, le, we, "\\", "\\textbackslash"), se(ae, le, we, "^", "\\textasciicircum"), se(ie, le, ye, "\u2191", "\\uparrow", !0), se(ie, le, ye, "\u21d1", "\\Uparrow", !0), se(ie, le, ye, "\u2193", "\\downarrow", !0), se(ie, le, ye, "\u21d3", "\\Downarrow", !0), se(ie, le, ye, "\u2195", "\\updownarrow", !0), se(ie, le, ye, "\u21d5", "\\Updownarrow", !0), se(ie, le, ge, "\u2210", "\\coprod"), se(ie, le, ge, "\u22c1", "\\bigvee"), se(ie, le, ge, "\u22c0", "\\bigwedge"), se(ie, le, ge, "\u2a04", "\\biguplus"), se(ie, le, ge, "\u22c2", "\\bigcap"), se(ie, le, ge, "\u22c3", "\\bigcup"), se(ie, le, ge, "\u222b", "\\int"), se(ie, le, ge, "\u222b", "\\intop"), se(ie, le, ge, "\u222c", "\\iint"), se(ie, le, ge, "\u222d", "\\iiint"), se(ie, le, ge, "\u220f", "\\prod"), se(ie, le, ge, "\u2211", "\\sum"), se(ie, le, ge, "\u2a02", "\\bigotimes"), se(ie, le, ge, "\u2a01", "\\bigoplus"), se(ie, le, ge, "\u2a00", "\\bigodot"), se(ie, le, ge, "\u222e", "\\oint"), se(ie, le, ge, "\u222f", "\\oiint"), se(ie, le, ge, "\u2230", "\\oiiint"), se(ie, le, ge, "\u2a06", "\\bigsqcup"), se(ie, le, ge, "\u222b", "\\smallint"), se(ae, le, ue, "\u2026", "\\textellipsis"), se(ie, le, ue, "\u2026", "\\mathellipsis"), se(ae, le, ue, "\u2026", "\\ldots", !0), se(ie, le, ue, "\u2026", "\\ldots", !0), se(ie, le, ue, "\u22ef", "\\@cdots", !0), se(ie, le, ue, "\u22f1", "\\ddots", !0), se(ie, le, we, "\u22ee", "\\varvdots"), se(ie, le, ce, "\u02ca", "\\acute"), se(ie, le, ce, "\u02cb", "\\grave"), se(ie, le, ce, "\xa8", "\\ddot"), se(ie, le, ce, "~", "\\tilde"), se(ie, le, ce, "\u02c9", "\\bar"), se(ie, le, ce, "\u02d8", "\\breve"), se(ie, le, ce, "\u02c7", "\\check"), se(ie, le, ce, "^", "\\hat"), se(ie, le, ce, "\u20d7", "\\vec"), se(ie, le, ce, "\u02d9", "\\dot"), se(ie, le, ce, "\u02da", "\\mathring"), se(ie, le, de, "\ue131", "\\@imath"), se(ie, le, de, "\ue237", "\\@jmath"), se(ie, le, we, "\u0131", "\u0131"), se(ie, le, we, "\u0237", "\u0237"), se(ae, le, we, "\u0131", "\\i", !0), se(ae, le, we, "\u0237", "\\j", !0), se(ae, le, we, "\xdf", "\\ss", !0), se(ae, le, we, "\xe6", "\\ae", !0), se(ae, le, we, "\u0153", "\\oe", !0), se(ae, le, we, "\xf8", "\\o", !0), se(ae, le, we, "\xc6", "\\AE", !0), se(ae, le, we, "\u0152", "\\OE", !0), se(ae, le, we, "\xd8", "\\O", !0), se(ae, le, ce, "\u02ca", "\\'"), se(ae, le, ce, "\u02cb", "\\`"), se(ae, le, ce, "\u02c6", "\\^"), se(ae, le, ce, "\u02dc", "\\~"), se(ae, le, ce, "\u02c9", "\\="), se(ae, le, ce, "\u02d8", "\\u"), se(ae, le, ce, "\u02d9", "\\."), se(ae, le, ce, "\xb8", "\\c"), se(ae, le, ce, "\u02da", "\\r"), se(ae, le, ce, "\u02c7", "\\v"), se(ae, le, ce, "\xa8", '\\"'), se(ae, le, ce, "\u02dd", "\\H"), se(ae, le, ce, "\u25ef", "\\textcircled"); const ve = { "--": !0, "---": !0, "``": !0, "''": !0 }; se(ae, le, we, "\u2013", "--", !0), se(ae, le, we, "\u2013", "\\textendash"), se(ae, le, we, "\u2014", "---", !0), se(ae, le, we, "\u2014", "\\textemdash"), se(ae, le, we, "\u2018", "`", !0), se(ae, le, we, "\u2018", "\\textquoteleft"), se(ae, le, we, "\u2019", "'", !0), se(ae, le, we, "\u2019", "\\textquoteright"), se(ae, le, we, "\u201c", "``", !0), se(ae, le, we, "\u201c", "\\textquotedblleft"), se(ae, le, we, "\u201d", "''", !0), se(ae, le, we, "\u201d", "\\textquotedblright"), se(ie, le, we, "\xb0", "\\degree", !0), se(ae, le, we, "\xb0", "\\degree"), se(ae, le, we, "\xb0", "\\textdegree", !0), se(ie, le, we, "\xa3", "\\pounds"), se(ie, le, we, "\xa3", "\\mathsterling", !0), se(ae, le, we, "\xa3", "\\pounds"), se(ae, le, we, "\xa3", "\\textsterling", !0), se(ie, he, we, "\u2720", "\\maltese"), se(ae, he, we, "\u2720", "\\maltese"); const ke = '0123456789/@."'; for (let e = 0; e < ke.length; e++) { const t = ke.charAt(e); se(ie, le, we, t, t) } const Se = '0123456789!@*()-=+";:?/.,'; for (let e = 0; e < Se.length; e++) { const t = Se.charAt(e); se(ae, le, we, t, t) } const Me = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; for (let e = 0; e < Me.length; e++) { const t = Me.charAt(e); se(ie, le, de, t, t), se(ae, le, we, t, t) } se(ie, he, we, "C", "\u2102"), se(ae, he, we, "C", "\u2102"), se(ie, he, we, "H", "\u210d"), se(ae, he, we, "H", "\u210d"), se(ie, he, we, "N", "\u2115"), se(ae, he, we, "N", "\u2115"), se(ie, he, we, "P", "\u2119"), se(ae, he, we, "P", "\u2119"), se(ie, he, we, "Q", "\u211a"), se(ae, he, we, "Q", "\u211a"), se(ie, he, we, "R", "\u211d"), se(ae, he, we, "R", "\u211d"), se(ie, he, we, "Z", "\u2124"), se(ae, he, we, "Z", "\u2124"), se(ie, le, de, "h", "\u210e"), se(ae, le, de, "h", "\u210e"); let ze = ""; for (let e = 0; e < Me.length; e++) { const t = Me.charAt(e); ze = String.fromCharCode(55349, 56320 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56372 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56424 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56580 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56684 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56736 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56788 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56840 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56944 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), e < 26 && (ze = String.fromCharCode(55349, 56632 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 56476 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze)) } ze = String.fromCharCode(55349, 56668), se(ie, le, de, "k", ze), se(ae, le, we, "k", ze); for (let e = 0; e < 10; e++) { const t = e.toString(); ze = String.fromCharCode(55349, 57294 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 57314 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 57324 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze), ze = String.fromCharCode(55349, 57334 + e), se(ie, le, de, t, ze), se(ae, le, we, t, ze) } const Ae = "\xd0\xde\xfe"; for (let e = 0; e < Ae.length; e++) { const t = Ae.charAt(e); se(ie, le, de, t, t), se(ae, le, we, t, t) } const Te = [["mathbf", "textbf", "Main-Bold"], ["mathbf", "textbf", "Main-Bold"], ["mathnormal", "textit", "Math-Italic"], ["mathnormal", "textit", "Math-Italic"], ["boldsymbol", "boldsymbol", "Main-BoldItalic"], ["boldsymbol", "boldsymbol", "Main-BoldItalic"], ["mathscr", "textscr", "Script-Regular"], ["", "", ""], ["", "", ""], ["", "", ""], ["mathfrak", "textfrak", "Fraktur-Regular"], ["mathfrak", "textfrak", "Fraktur-Regular"], ["mathbb", "textbb", "AMS-Regular"], ["mathbb", "textbb", "AMS-Regular"], ["mathboldfrak", "textboldfrak", "Fraktur-Regular"], ["mathboldfrak", "textboldfrak", "Fraktur-Regular"], ["mathsf", "textsf", "SansSerif-Regular"], ["mathsf", "textsf", "SansSerif-Regular"], ["mathboldsf", "textboldsf", "SansSerif-Bold"], ["mathboldsf", "textboldsf", "SansSerif-Bold"], ["mathitsf", "textitsf", "SansSerif-Italic"], ["mathitsf", "textitsf", "SansSerif-Italic"], ["", "", ""], ["", "", ""], ["mathtt", "texttt", "Typewriter-Regular"], ["mathtt", "texttt", "Typewriter-Regular"]], Be = [["mathbf", "textbf", "Main-Bold"], ["", "", ""], ["mathsf", "textsf", "SansSerif-Regular"], ["mathboldsf", "textboldsf", "SansSerif-Bold"], ["mathtt", "texttt", "Typewriter-Regular"]], Ce = function (e, t, r) { return oe[r][e] && oe[r][e].replace && (e = oe[r][e].replace), { value: e, metrics: N(e, t, r) } }, Ne = function (e, t, r, n, o) { const s = Ce(e, t, r), i = s.metrics; let a; if (e = s.value, i) { let t = i.italic; ("text" === r || n && "mathit" === n.font) && (t = 0), a = new Z(e, i.height, i.depth, t, i.skew, i.width, o) } else "undefined" != typeof console && console.warn("No character metrics for '" + e + "' in style '" + t + "' and mode '" + r + "'"), a = new Z(e, 0, 0, 0, 0, 0, o); if (n) { a.maxFontSize = n.sizeMultiplier, n.style.isTight() && a.classes.push("mtight"); const e = n.getColor(); e && (a.style.color = e) } return a }, qe = (e, t) => { if (G(e.classes) !== G(t.classes) || e.skew !== t.skew || e.maxFontSize !== t.maxFontSize) return !1; if (1 === e.classes.length) { const t = e.classes[0]; if ("mbin" === t || "mord" === t) return !1 } for (const r in e.style) if (e.style.hasOwnProperty(r) && e.style[r] !== t.style[r]) return !1; for (const r in t.style) if (t.style.hasOwnProperty(r) && e.style[r] !== t.style[r]) return !1; return !0 }, Ie = function (e) { let t = 0, r = 0, n = 0; for (let o = 0; o < e.children.length; o++) { const s = e.children[o]; s.height > t && (t = s.height), s.depth > r && (r = s.depth), s.maxFontSize > n && (n = s.maxFontSize) } e.height = t, e.depth = r, e.maxFontSize = n }, Re = function (e, t, r, n) { const o = new W(e, t, r, n); return Ie(o), o }, He = (e, t, r, n) => new W(e, t, r, n), Oe = function (e) { const t = new A(e); return Ie(t), t }, Ee = function (e, t, r) { let n, o = ""; switch (e) { case "amsrm": o = "AMS"; break; case "textrm": o = "Main"; break; case "textsf": o = "SansSerif"; break; case "texttt": o = "Typewriter"; break; default: o = e }return n = "textbf" === t && "textit" === r ? "BoldItalic" : "textbf" === t ? "Bold" : "textit" === t ? "Italic" : "Regular", o + "-" + n }, Le = { mathbf: { variant: "bold", fontName: "Main-Bold" }, mathrm: { variant: "normal", fontName: "Main-Regular" }, textit: { variant: "italic", fontName: "Main-Italic" }, mathit: { variant: "italic", fontName: "Main-Italic" }, mathnormal: { variant: "italic", fontName: "Math-Italic" }, mathbb: { variant: "double-struck", fontName: "AMS-Regular" }, mathcal: { variant: "script", fontName: "Caligraphic-Regular" }, mathfrak: { variant: "fraktur", fontName: "Fraktur-Regular" }, mathscr: { variant: "script", fontName: "Script-Regular" }, mathsf: { variant: "sans-serif", fontName: "SansSerif-Regular" }, mathtt: { variant: "monospace", fontName: "Typewriter-Regular" } }, De = { vec: ["vec", .471, .714], oiintSize1: ["oiintSize1", .957, .499], oiintSize2: ["oiintSize2", 1.472, .659], oiiintSize1: ["oiiintSize1", 1.304, .499], oiiintSize2: ["oiiintSize2", 1.98, .659] }; var Ve = { fontMap: Le, makeSymbol: Ne, mathsym: function (e, t, r, n) { return void 0 === n && (n = []), "boldsymbol" === r.font && Ce(e, "Main-Bold", t).metrics ? Ne(e, "Main-Bold", t, r, n.concat(["mathbf"])) : "\\" === e || "main" === oe[t][e].font ? Ne(e, "Main-Regular", t, r, n) : Ne(e, "AMS-Regular", t, r, n.concat(["amsrm"])) }, makeSpan: Re, makeSvgSpan: He, makeLineSpan: function (e, t, r) { const n = Re([e], [], t); return n.height = Math.max(r || t.fontMetrics().defaultRuleThickness, t.minRuleThickness), n.style.borderBottomWidth = F(n.height), n.maxFontSize = 1, n }, makeAnchor: function (e, t, r, n) { const o = new _(e, t, r, n); return Ie(o), o }, makeFragment: Oe, wrapFragment: function (e, t) { return e instanceof A ? Re([], [e], t) : e }, makeVList: function (e, t) { const { children: r, depth: n } = function (e) { if ("individualShift" === e.positionType) { const t = e.children, r = [t[0]], n = -t[0].shift - t[0].elem.depth; let o = n; for (let e = 1; e < t.length; e++) { const n = -t[e].shift - o - t[e].elem.depth, s = n - (t[e - 1].elem.height + t[e - 1].elem.depth); o += n, r.push({ type: "kern", size: s }), r.push(t[e]) } return { children: r, depth: n } } let t; if ("top" === e.positionType) { let r = e.positionData; for (let t = 0; t < e.children.length; t++) { const n = e.children[t]; r -= "kern" === n.type ? n.size : n.elem.height + n.elem.depth } t = r } else if ("bottom" === e.positionType) t = -e.positionData; else { const r = e.children[0]; if ("elem" !== r.type) throw new Error('First child must have type "elem".'); if ("shift" === e.positionType) t = -r.elem.depth - e.positionData; else { if ("firstBaseline" !== e.positionType) throw new Error("Invalid positionType " + e.positionType + "."); t = -r.elem.depth } } return { children: e.children, depth: t } }(e); let o = 0; for (let e = 0; e < r.length; e++) { const t = r[e]; if ("elem" === t.type) { const e = t.elem; o = Math.max(o, e.maxFontSize, e.height) } } o += 2; const s = Re(["pstrut"], []); s.style.height = F(o); const i = []; let a = n, l = n, h = n; for (let e = 0; e < r.length; e++) { const t = r[e]; if ("kern" === t.type) h += t.size; else { const e = t.elem, r = t.wrapperClasses || [], n = t.wrapperStyle || {}, a = Re(r, [s, e], void 0, n); a.style.top = F(-o - h - e.depth), t.marginLeft && (a.style.marginLeft = t.marginLeft), t.marginRight && (a.style.marginRight = t.marginRight), i.push(a), h += e.height + e.depth } a = Math.min(a, h), l = Math.max(l, h) } const c = Re(["vlist"], i); let m; if (c.style.height = F(l), a < 0) { const e = Re([], []), t = Re(["vlist"], [e]); t.style.height = F(-a); const r = Re(["vlist-s"], [new Z("\u200b")]); m = [Re(["vlist-r"], [c, r]), Re(["vlist-r"], [t])] } else m = [Re(["vlist-r"], [c])]; const p = Re(["vlist-t"], m); return 2 === m.length && p.classes.push("vlist-t2"), p.height = l, p.depth = -a, p }, makeOrd: function (e, t, r) { const o = e.mode, s = e.text, i = ["mord"], a = "math" === o || "text" === o && t.font, l = a ? t.font : t.fontFamily; let h = "", c = ""; if (55349 === s.charCodeAt(0) && ([h, c] = function (e, t) { const r = 1024 * (e.charCodeAt(0) - 55296) + (e.charCodeAt(1) - 56320) + 65536, o = "math" === t ? 0 : 1; if (119808 <= r && r < 120484) { const e = Math.floor((r - 119808) / 26); return [Te[e][2], Te[e][o]] } if (120782 <= r && r <= 120831) { const e = Math.floor((r - 120782) / 10); return [Be[e][2], Be[e][o]] } if (120485 === r || 120486 === r) return [Te[0][2], Te[0][o]]; if (120486 < r && r < 120782) return ["", ""]; throw new n("Unsupported character: " + e) }(s, o)), h.length > 0) return Ne(s, h, o, t, i.concat(c)); if (l) { let e, n; if ("boldsymbol" === l) { const t = function (e, t, r, n, o) { return "textord" !== o && Ce(e, "Math-BoldItalic", t).metrics ? { fontName: "Math-BoldItalic", fontClass: "boldsymbol" } : { fontName: "Main-Bold", fontClass: "mathbf" } }(s, o, 0, 0, r); e = t.fontName, n = [t.fontClass] } else a ? (e = Le[l].fontName, n = [l]) : (e = Ee(l, t.fontWeight, t.fontShape), n = [l, t.fontWeight, t.fontShape]); if (Ce(s, e, o).metrics) return Ne(s, e, o, t, i.concat(n)); if (ve.hasOwnProperty(s) && "Typewriter" === e.slice(0, 10)) { const r = []; for (let a = 0; a < s.length; a++)r.push(Ne(s[a], e, o, t, i.concat(n))); return Oe(r) } } if ("mathord" === r) return Ne(s, "Math-Italic", o, t, i.concat(["mathnormal"])); if ("textord" === r) { const e = oe[o][s] && oe[o][s].font; if ("ams" === e) { const e = Ee("amsrm", t.fontWeight, t.fontShape); return Ne(s, e, o, t, i.concat("amsrm", t.fontWeight, t.fontShape)) } if ("main" !== e && e) { const r = Ee(e, t.fontWeight, t.fontShape); return Ne(s, r, o, t, i.concat(r, t.fontWeight, t.fontShape)) } { const e = Ee("textrm", t.fontWeight, t.fontShape); return Ne(s, e, o, t, i.concat(t.fontWeight, t.fontShape)) } } throw new Error("unexpected type: " + r + " in makeOrd") }, makeGlue: (e, t) => { const r = Re(["mspace"], [], t), n = P(e, t); return r.style.marginRight = F(n), r }, staticSvg: function (e, t) { const [r, n, o] = De[e], s = new J(r), i = new K([s], { width: F(n), height: F(o), style: "width:" + F(n), viewBox: "0 0 " + 1e3 * n + " " + 1e3 * o, preserveAspectRatio: "xMinYMin" }), a = He(["overlay"], [i], t); return a.height = o, a.style.height = F(o), a.style.width = F(n), a }, svgData: De, tryCombineChars: e => { for (let t = 0; t < e.length - 1; t++) { const r = e[t], n = e[t + 1]; r instanceof Z && n instanceof Z && qe(r, n) && (r.text += n.text, r.height = Math.max(r.height, n.height), r.depth = Math.max(r.depth, n.depth), r.italic = n.italic, e.splice(t + 1, 1), t--) } return e } }; const Pe = { number: 3, unit: "mu" }, Fe = { number: 4, unit: "mu" }, Ge = { number: 5, unit: "mu" }, Ue = { mord: { mop: Pe, mbin: Fe, mrel: Ge, minner: Pe }, mop: { mord: Pe, mop: Pe, mrel: Ge, minner: Pe }, mbin: { mord: Fe, mop: Fe, mopen: Fe, minner: Fe }, mrel: { mord: Ge, mop: Ge, mopen: Ge, minner: Ge }, mopen: {}, mclose: { mop: Pe, mbin: Fe, mrel: Ge, minner: Pe }, mpunct: { mord: Pe, mop: Pe, mrel: Ge, mopen: Pe, mclose: Pe, mpunct: Pe, minner: Pe }, minner: { mord: Pe, mop: Pe, mbin: Fe, mrel: Ge, mopen: Pe, mpunct: Pe, minner: Pe } }, Ye = { mord: { mop: Pe }, mop: { mord: Pe, mop: Pe }, mbin: {}, mrel: {}, mopen: {}, mclose: { mop: Pe }, mpunct: {}, minner: { mop: Pe } }, Xe = {}, We = {}, _e = {}; function je(e) { let { type: t, names: r, props: n, handler: o, htmlBuilder: s, mathmlBuilder: i } = e; const a = { type: t, numArgs: n.numArgs, argTypes: n.argTypes, allowedInArgument: !!n.allowedInArgument, allowedInText: !!n.allowedInText, allowedInMath: void 0 === n.allowedInMath || n.allowedInMath, numOptionalArgs: n.numOptionalArgs || 0, infix: !!n.infix, primitive: !!n.primitive, handler: o }; for (let e = 0; e < r.length; ++e)Xe[r[e]] = a; t && (s && (We[t] = s), i && (_e[t] = i)) } function $e(e) { let { type: t, htmlBuilder: r, mathmlBuilder: n } = e; je({ type: t, names: [], props: { numArgs: 0 }, handler() { throw new Error("Should never be called.") }, htmlBuilder: r, mathmlBuilder: n }) } const Ze = function (e) { return "ordgroup" === e.type && 1 === e.body.length ? e.body[0] : e }, Ke = function (e) { return "ordgroup" === e.type ? e.body : [e] }, Je = Ve.makeSpan, Qe = ["leftmost", "mbin", "mopen", "mrel", "mop", "mpunct"], et = ["rightmost", "mrel", "mclose", "mpunct"], tt = { display: w.DISPLAY, text: w.TEXT, script: w.SCRIPT, scriptscript: w.SCRIPTSCRIPT }, rt = { mord: "mord", mop: "mop", mbin: "mbin", mrel: "mrel", mopen: "mopen", mclose: "mclose", mpunct: "mpunct", minner: "minner" }, nt = function (e, t, r, n) { void 0 === n && (n = [null, null]); const o = []; for (let r = 0; r < e.length; r++) { const n = ht(e[r], t); if (n instanceof A) { const e = n.children; o.push(...e) } else o.push(n) } if (Ve.tryCombineChars(o), !r) return o; let s = t; if (1 === e.length) { const r = e[0]; "sizing" === r.type ? s = t.havingSize(r.size) : "styling" === r.type && (s = t.havingStyle(tt[r.style])) } const i = Je([n[0] || "leftmost"], [], t), a = Je([n[1] || "rightmost"], [], t), h = "root" === r; return ot(o, ((e, t) => { const r = t.classes[0], n = e.classes[0]; "mbin" === r && l.contains(et, n) ? t.classes[0] = "mord" : "mbin" === n && l.contains(Qe, r) && (e.classes[0] = "mord") }), { node: i }, a, h), ot(o, ((e, t) => { const r = at(t), n = at(e), o = r && n ? e.hasClass("mtight") ? Ye[r][n] : Ue[r][n] : null; if (o) return Ve.makeGlue(o, s) }), { node: i }, a, h), o }, ot = function (e, t, r, n, o) { n && e.push(n); let s = 0; for (; s < e.length; s++) { const n = e[s], i = st(n); if (i) { ot(i.children, t, r, null, o); continue } const a = !n.hasClass("mspace"); if (a) { const o = t(n, r.node); o && (r.insertAfter ? r.insertAfter(o) : (e.unshift(o), s++)) } a ? r.node = n : o && n.hasClass("newline") && (r.node = Je(["leftmost"])), r.insertAfter = (t => r => { e.splice(t + 1, 0, r), s++ })(s) } n && e.pop() }, st = function (e) { return e instanceof A || e instanceof _ || e instanceof W && e.hasClass("enclosing") ? e : null }, it = function (e, t) { const r = st(e); if (r) { const e = r.children; if (e.length) { if ("right" === t) return it(e[e.length - 1], "right"); if ("left" === t) return it(e[0], "left") } } return e }, at = function (e, t) { return e ? (t && (e = it(e, t)), rt[e.classes[0]] || null) : null }, lt = function (e, t) { const r = ["nulldelimiter"].concat(e.baseSizingClasses()); return Je(t.concat(r)) }, ht = function (e, t, r) { if (!e) return Je(); if (We[e.type]) { let n = We[e.type](e, t); if (r && t.size !== r.size) { n = Je(t.sizingClasses(r), [n], t); const e = t.sizeMultiplier / r.sizeMultiplier; n.height *= e, n.depth *= e } return n } throw new n("Got group of unknown type: '" + e.type + "'") }; function ct(e, t) { const r = Je(["base"], e, t), n = Je(["strut"]); return n.style.height = F(r.height + r.depth), r.depth && (n.style.verticalAlign = F(-r.depth)), r.children.unshift(n), r } function mt(e, t) { let r = null; 1 === e.length && "tag" === e[0].type && (r = e[0].tag, e = e[0].body); const n = nt(e, t, "root"); let o; 2 === n.length && n[1].hasClass("tag") && (o = n.pop()); const s = []; let i, a = []; for (let e = 0; e < n.length; e++)if (a.push(n[e]), n[e].hasClass("mbin") || n[e].hasClass("mrel") || n[e].hasClass("allowbreak")) { let r = !1; for (; e < n.length - 1 && n[e + 1].hasClass("mspace") && !n[e + 1].hasClass("newline");)e++, a.push(n[e]), n[e].hasClass("nobreak") && (r = !0); r || (s.push(ct(a, t)), a = []) } else n[e].hasClass("newline") && (a.pop(), a.length > 0 && (s.push(ct(a, t)), a = []), s.push(n[e])); a.length > 0 && s.push(ct(a, t)), r ? (i = ct(nt(r, t, !0)), i.classes = ["tag"], s.push(i)) : o && s.push(o); const l = Je(["katex-html"], s); if (l.setAttribute("aria-hidden", "true"), i) { const e = i.children[0]; e.style.height = F(l.height + l.depth), l.depth && (e.style.verticalAlign = F(-l.depth)) } return l } function pt(e) { return new A(e) } class ut { constructor(e, t, r) { this.type = void 0, this.attributes = void 0, this.children = void 0, this.classes = void 0, this.type = e, this.attributes = {}, this.children = t || [], this.classes = r || [] } setAttribute(e, t) { this.attributes[e] = t } getAttribute(e) { return this.attributes[e] } toNode() { const e = document.createElementNS("http://www.w3.org/1998/Math/MathML", this.type); for (const t in this.attributes) Object.prototype.hasOwnProperty.call(this.attributes, t) && e.setAttribute(t, this.attributes[t]); this.classes.length > 0 && (e.className = G(this.classes)); for (let t = 0; t < this.children.length; t++)e.appendChild(this.children[t].toNode()); return e } toMarkup() { let e = "<" + this.type; for (const t in this.attributes) Object.prototype.hasOwnProperty.call(this.attributes, t) && (e += " " + t + '="', e += l.escape(this.attributes[t]), e += '"'); this.classes.length > 0 && (e += ' class ="' + l.escape(G(this.classes)) + '"'), e += ">"; for (let t = 0; t < this.children.length; t++)e += this.children[t].toMarkup(); return e += "" + this.type + ">", e } toText() { return this.children.map((e => e.toText())).join("") } } class dt { constructor(e) { this.text = void 0, this.text = e } toNode() { return document.createTextNode(this.text) } toMarkup() { return l.escape(this.toText()) } toText() { return this.text } } var gt = { MathNode: ut, TextNode: dt, SpaceNode: class { constructor(e) { this.width = void 0, this.character = void 0, this.width = e, this.character = e >= .05555 && e <= .05556 ? "\u200a" : e >= .1666 && e <= .1667 ? "\u2009" : e >= .2222 && e <= .2223 ? "\u2005" : e >= .2777 && e <= .2778 ? "\u2005\u200a" : e >= -.05556 && e <= -.05555 ? "\u200a\u2063" : e >= -.1667 && e <= -.1666 ? "\u2009\u2063" : e >= -.2223 && e <= -.2222 ? "\u205f\u2063" : e >= -.2778 && e <= -.2777 ? "\u2005\u2063" : null } toNode() { if (this.character) return document.createTextNode(this.character); { const e = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mspace"); return e.setAttribute("width", F(this.width)), e } } toMarkup() { return this.character ? "" + this.character + "" : '' } toText() { return this.character ? this.character : " " } }, newDocumentFragment: pt }; const ft = function (e, t, r) { return !oe[t][e] || !oe[t][e].replace || 55349 === e.charCodeAt(0) || ve.hasOwnProperty(e) && r && (r.fontFamily && "tt" === r.fontFamily.slice(4, 6) || r.font && "tt" === r.font.slice(4, 6)) || (e = oe[t][e].replace), new gt.TextNode(e) }, bt = function (e) { return 1 === e.length ? e[0] : new gt.MathNode("mrow", e) }, yt = function (e, t) { if ("texttt" === t.fontFamily) return "monospace"; if ("textsf" === t.fontFamily) return "textit" === t.fontShape && "textbf" === t.fontWeight ? "sans-serif-bold-italic" : "textit" === t.fontShape ? "sans-serif-italic" : "textbf" === t.fontWeight ? "bold-sans-serif" : "sans-serif"; if ("textit" === t.fontShape && "textbf" === t.fontWeight) return "bold-italic"; if ("textit" === t.fontShape) return "italic"; if ("textbf" === t.fontWeight) return "bold"; const r = t.font; if (!r || "mathnormal" === r) return null; const n = e.mode; if ("mathit" === r) return "italic"; if ("boldsymbol" === r) return "textord" === e.type ? "bold" : "bold-italic"; if ("mathbf" === r) return "bold"; if ("mathbb" === r) return "double-struck"; if ("mathfrak" === r) return "fraktur"; if ("mathscr" === r || "mathcal" === r) return "script"; if ("mathsf" === r) return "sans-serif"; if ("mathtt" === r) return "monospace"; let o = e.text; if (l.contains(["\\imath", "\\jmath"], o)) return null; oe[n][o] && oe[n][o].replace && (o = oe[n][o].replace); return N(o, Ve.fontMap[r].fontName, n) ? Ve.fontMap[r].variant : null }, xt = function (e, t, r) { if (1 === e.length) { const n = vt(e[0], t); return r && n instanceof ut && "mo" === n.type && (n.setAttribute("lspace", "0em"), n.setAttribute("rspace", "0em")), [n] } const n = []; let o; for (let r = 0; r < e.length; r++) { const s = vt(e[r], t); if (s instanceof ut && o instanceof ut) { if ("mtext" === s.type && "mtext" === o.type && s.getAttribute("mathvariant") === o.getAttribute("mathvariant")) { o.children.push(...s.children); continue } if ("mn" === s.type && "mn" === o.type) { o.children.push(...s.children); continue } if ("mi" === s.type && 1 === s.children.length && "mn" === o.type) { const e = s.children[0]; if (e instanceof dt && "." === e.text) { o.children.push(...s.children); continue } } else if ("mi" === o.type && 1 === o.children.length) { const e = o.children[0]; if (e instanceof dt && "\u0338" === e.text && ("mo" === s.type || "mi" === s.type || "mn" === s.type)) { const e = s.children[0]; e instanceof dt && e.text.length > 0 && (e.text = e.text.slice(0, 1) + "\u0338" + e.text.slice(1), n.pop()) } } } n.push(s), o = s } return n }, wt = function (e, t, r) { return bt(xt(e, t, r)) }, vt = function (e, t) { if (!e) return new gt.MathNode("mrow"); if (_e[e.type]) { return _e[e.type](e, t) } throw new n("Got group of unknown type: '" + e.type + "'") }; function kt(e, t, r, n, o) { const s = xt(e, r); let i; i = 1 === s.length && s[0] instanceof ut && l.contains(["mrow", "mtable"], s[0].type) ? s[0] : new gt.MathNode("mrow", s); const a = new gt.MathNode("annotation", [new gt.TextNode(t)]); a.setAttribute("encoding", "application/x-tex"); const h = new gt.MathNode("semantics", [i, a]), c = new gt.MathNode("math", [h]); c.setAttribute("xmlns", "http://www.w3.org/1998/Math/MathML"), n && c.setAttribute("display", "block"); const m = o ? "katex" : "katex-mathml"; return Ve.makeSpan([m], [c]) } const St = function (e) { return new E({ style: e.displayMode ? w.DISPLAY : w.TEXT, maxSize: e.maxSize, minRuleThickness: e.minRuleThickness }) }, Mt = function (e, t) { if (t.displayMode) { const r = ["katex-display"]; t.leqno && r.push("leqno"), t.fleqn && r.push("fleqn"), e = Ve.makeSpan(r, [e]) } return e }, zt = function (e, t, r) { const n = St(r); let o; if ("mathml" === r.output) return kt(e, t, n, r.displayMode, !0); if ("html" === r.output) { const t = mt(e, n); o = Ve.makeSpan(["katex"], [t]) } else { const s = kt(e, t, n, r.displayMode, !1), i = mt(e, n); o = Ve.makeSpan(["katex"], [s, i]) } return Mt(o, r) }; const At = { widehat: "^", widecheck: "\u02c7", widetilde: "~", utilde: "~", overleftarrow: "\u2190", underleftarrow: "\u2190", xleftarrow: "\u2190", overrightarrow: "\u2192", underrightarrow: "\u2192", xrightarrow: "\u2192", underbrace: "\u23df", overbrace: "\u23de", overgroup: "\u23e0", undergroup: "\u23e1", overleftrightarrow: "\u2194", underleftrightarrow: "\u2194", xleftrightarrow: "\u2194", Overrightarrow: "\u21d2", xRightarrow: "\u21d2", overleftharpoon: "\u21bc", xleftharpoonup: "\u21bc", overrightharpoon: "\u21c0", xrightharpoonup: "\u21c0", xLeftarrow: "\u21d0", xLeftrightarrow: "\u21d4", xhookleftarrow: "\u21a9", xhookrightarrow: "\u21aa", xmapsto: "\u21a6", xrightharpoondown: "\u21c1", xleftharpoondown: "\u21bd", xrightleftharpoons: "\u21cc", xleftrightharpoons: "\u21cb", xtwoheadleftarrow: "\u219e", xtwoheadrightarrow: "\u21a0", xlongequal: "=", xtofrom: "\u21c4", xrightleftarrows: "\u21c4", xrightequilibrium: "\u21cc", xleftequilibrium: "\u21cb", "\\cdrightarrow": "\u2192", "\\cdleftarrow": "\u2190", "\\cdlongequal": "=" }, Tt = { overrightarrow: [["rightarrow"], .888, 522, "xMaxYMin"], overleftarrow: [["leftarrow"], .888, 522, "xMinYMin"], underrightarrow: [["rightarrow"], .888, 522, "xMaxYMin"], underleftarrow: [["leftarrow"], .888, 522, "xMinYMin"], xrightarrow: [["rightarrow"], 1.469, 522, "xMaxYMin"], "\\cdrightarrow": [["rightarrow"], 3, 522, "xMaxYMin"], xleftarrow: [["leftarrow"], 1.469, 522, "xMinYMin"], "\\cdleftarrow": [["leftarrow"], 3, 522, "xMinYMin"], Overrightarrow: [["doublerightarrow"], .888, 560, "xMaxYMin"], xRightarrow: [["doublerightarrow"], 1.526, 560, "xMaxYMin"], xLeftarrow: [["doubleleftarrow"], 1.526, 560, "xMinYMin"], overleftharpoon: [["leftharpoon"], .888, 522, "xMinYMin"], xleftharpoonup: [["leftharpoon"], .888, 522, "xMinYMin"], xleftharpoondown: [["leftharpoondown"], .888, 522, "xMinYMin"], overrightharpoon: [["rightharpoon"], .888, 522, "xMaxYMin"], xrightharpoonup: [["rightharpoon"], .888, 522, "xMaxYMin"], xrightharpoondown: [["rightharpoondown"], .888, 522, "xMaxYMin"], xlongequal: [["longequal"], .888, 334, "xMinYMin"], "\\cdlongequal": [["longequal"], 3, 334, "xMinYMin"], xtwoheadleftarrow: [["twoheadleftarrow"], .888, 334, "xMinYMin"], xtwoheadrightarrow: [["twoheadrightarrow"], .888, 334, "xMaxYMin"], overleftrightarrow: [["leftarrow", "rightarrow"], .888, 522], overbrace: [["leftbrace", "midbrace", "rightbrace"], 1.6, 548], underbrace: [["leftbraceunder", "midbraceunder", "rightbraceunder"], 1.6, 548], underleftrightarrow: [["leftarrow", "rightarrow"], .888, 522], xleftrightarrow: [["leftarrow", "rightarrow"], 1.75, 522], xLeftrightarrow: [["doubleleftarrow", "doublerightarrow"], 1.75, 560], xrightleftharpoons: [["leftharpoondownplus", "rightharpoonplus"], 1.75, 716], xleftrightharpoons: [["leftharpoonplus", "rightharpoondownplus"], 1.75, 716], xhookleftarrow: [["leftarrow", "righthook"], 1.08, 522], xhookrightarrow: [["lefthook", "rightarrow"], 1.08, 522], overlinesegment: [["leftlinesegment", "rightlinesegment"], .888, 522], underlinesegment: [["leftlinesegment", "rightlinesegment"], .888, 522], overgroup: [["leftgroup", "rightgroup"], .888, 342], undergroup: [["leftgroupunder", "rightgroupunder"], .888, 342], xmapsto: [["leftmapsto", "rightarrow"], 1.5, 522], xtofrom: [["leftToFrom", "rightToFrom"], 1.75, 528], xrightleftarrows: [["baraboveleftarrow", "rightarrowabovebar"], 1.75, 901], xrightequilibrium: [["baraboveshortleftharpoon", "rightharpoonaboveshortbar"], 1.75, 716], xleftequilibrium: [["shortbaraboveleftharpoon", "shortrightharpoonabovebar"], 1.75, 716] }; var Bt = function (e, t, r, n, o) { let s; const i = e.height + e.depth + r + n; if (/fbox|color|angl/.test(t)) { if (s = Ve.makeSpan(["stretchy", t], [], o), "fbox" === t) { const e = o.color && o.getColor(); e && (s.style.borderColor = e) } } else { const e = []; /^[bx]cancel$/.test(t) && e.push(new Q({ x1: "0", y1: "0", x2: "100%", y2: "100%", "stroke-width": "0.046em" })), /^x?cancel$/.test(t) && e.push(new Q({ x1: "0", y1: "100%", x2: "100%", y2: "0", "stroke-width": "0.046em" })); const r = new K(e, { width: "100%", height: F(i) }); s = Ve.makeSvgSpan([], [r], o) } return s.height = i, s.style.height = F(i), s }, Ct = function (e) { const t = new gt.MathNode("mo", [new gt.TextNode(At[e.replace(/^\\/, "")])]); return t.setAttribute("stretchy", "true"), t }, Nt = function (e, t) { const { span: r, minWidth: n, height: o } = function () { let r = 4e5; const n = e.label.slice(1); if (l.contains(["widehat", "widecheck", "widetilde", "utilde"], n)) { const s = "ordgroup" === (o = e.base).type ? o.body.length : 1; let i, a, l; if (s > 5) "widehat" === n || "widecheck" === n ? (i = 420, r = 2364, l = .42, a = n + "4") : (i = 312, r = 2340, l = .34, a = "tilde4"); else { const e = [1, 1, 2, 2, 3, 3][s]; "widehat" === n || "widecheck" === n ? (r = [0, 1062, 2364, 2364, 2364][e], i = [0, 239, 300, 360, 420][e], l = [0, .24, .3, .3, .36, .42][e], a = n + e) : (r = [0, 600, 1033, 2339, 2340][e], i = [0, 260, 286, 306, 312][e], l = [0, .26, .286, .3, .306, .34][e], a = "tilde" + e) } const h = new J(a), c = new K([h], { width: "100%", height: F(l), viewBox: "0 0 " + r + " " + i, preserveAspectRatio: "none" }); return { span: Ve.makeSvgSpan([], [c], t), minWidth: 0, height: l } } { const e = [], o = Tt[n], [s, i, a] = o, l = a / 1e3, h = s.length; let c, m; if (1 === h) { c = ["hide-tail"], m = [o[3]] } else if (2 === h) c = ["halfarrow-left", "halfarrow-right"], m = ["xMinYMin", "xMaxYMin"]; else { if (3 !== h) throw new Error("Correct katexImagesData or update code here to support\n " + h + " children."); c = ["brace-left", "brace-center", "brace-right"], m = ["xMinYMin", "xMidYMin", "xMaxYMin"] } for (let n = 0; n < h; n++) { const o = new J(s[n]), p = new K([o], { width: "400em", height: F(l), viewBox: "0 0 " + r + " " + a, preserveAspectRatio: m[n] + " slice" }), u = Ve.makeSvgSpan([c[n]], [p], t); if (1 === h) return { span: u, minWidth: i, height: l }; u.style.height = F(l), e.push(u) } return { span: Ve.makeSpan(["stretchy"], e, t), minWidth: i, height: l } } var o }(); return r.height = o, r.style.height = F(o), n > 0 && (r.style.minWidth = F(n)), r }; function qt(e, t) { if (!e || e.type !== t) throw new Error("Expected node of type " + t + ", but got " + (e ? "node of type " + e.type : String(e))); return e } function It(e) { const t = Rt(e); if (!t) throw new Error("Expected node of symbol group type, but got " + (e ? "node of type " + e.type : String(e))); return t } function Rt(e) { return e && ("atom" === e.type || re.hasOwnProperty(e.type)) ? e : null } const Ht = (e, t) => { let r, n, o; e && "supsub" === e.type ? (n = qt(e.base, "accent"), r = n.base, e.base = r, o = function (e) { if (e instanceof W) return e; throw new Error("Expected span but got " + String(e) + ".") }(ht(e, t)), e.base = n) : (n = qt(e, "accent"), r = n.base); const s = ht(r, t.havingCrampedStyle()); let i = 0; if (n.isShifty && l.isCharacterBox(r)) { const e = l.getBaseElem(r); i = ee(ht(e, t.havingCrampedStyle())).skew } const a = "\\c" === n.label; let h, c = a ? s.height + s.depth : Math.min(s.height, t.fontMetrics().xHeight); if (n.isStretchy) h = Nt(n, t), h = Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: s }, { type: "elem", elem: h, wrapperClasses: ["svg-align"], wrapperStyle: i > 0 ? { width: "calc(100% - " + F(2 * i) + ")", marginLeft: F(2 * i) } : void 0 }] }, t); else { let e, r; "\\vec" === n.label ? (e = Ve.staticSvg("vec", t), r = Ve.svgData.vec[1]) : (e = Ve.makeOrd({ mode: n.mode, text: n.label }, t, "textord"), e = ee(e), e.italic = 0, r = e.width, a && (c += e.depth)), h = Ve.makeSpan(["accent-body"], [e]); const o = "\\textcircled" === n.label; o && (h.classes.push("accent-full"), c = s.height); let l = i; o || (l -= r / 2), h.style.left = F(l), "\\textcircled" === n.label && (h.style.top = ".2em"), h = Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: s }, { type: "kern", size: -c }, { type: "elem", elem: h }] }, t) } const m = Ve.makeSpan(["mord", "accent"], [h], t); return o ? (o.children[0] = m, o.height = Math.max(m.height, o.height), o.classes[0] = "mord", o) : m }, Ot = (e, t) => { const r = e.isStretchy ? Ct(e.label) : new gt.MathNode("mo", [ft(e.label, e.mode)]), n = new gt.MathNode("mover", [vt(e.base, t), r]); return n.setAttribute("accent", "true"), n }, Et = new RegExp(["\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", "\\check", "\\hat", "\\vec", "\\dot", "\\mathring"].map((e => "\\" + e)).join("|")); je({ type: "accent", names: ["\\acute", "\\grave", "\\ddot", "\\tilde", "\\bar", "\\breve", "\\check", "\\hat", "\\vec", "\\dot", "\\mathring", "\\widecheck", "\\widehat", "\\widetilde", "\\overrightarrow", "\\overleftarrow", "\\Overrightarrow", "\\overleftrightarrow", "\\overgroup", "\\overlinesegment", "\\overleftharpoon", "\\overrightharpoon"], props: { numArgs: 1 }, handler: (e, t) => { const r = Ze(t[0]), n = !Et.test(e.funcName), o = !n || "\\widehat" === e.funcName || "\\widetilde" === e.funcName || "\\widecheck" === e.funcName; return { type: "accent", mode: e.parser.mode, label: e.funcName, isStretchy: n, isShifty: o, base: r } }, htmlBuilder: Ht, mathmlBuilder: Ot }), je({ type: "accent", names: ["\\'", "\\`", "\\^", "\\~", "\\=", "\\u", "\\.", '\\"', "\\c", "\\r", "\\H", "\\v", "\\textcircled"], props: { numArgs: 1, allowedInText: !0, allowedInMath: !0, argTypes: ["primitive"] }, handler: (e, t) => { const r = t[0]; let n = e.parser.mode; return "math" === n && (e.parser.settings.reportNonstrict("mathVsTextAccents", "LaTeX's accent " + e.funcName + " works only in text mode"), n = "text"), { type: "accent", mode: n, label: e.funcName, isStretchy: !1, isShifty: !0, base: r } }, htmlBuilder: Ht, mathmlBuilder: Ot }), je({ type: "accentUnder", names: ["\\underleftarrow", "\\underrightarrow", "\\underleftrightarrow", "\\undergroup", "\\underlinesegment", "\\utilde"], props: { numArgs: 1 }, handler: (e, t) => { let { parser: r, funcName: n } = e; const o = t[0]; return { type: "accentUnder", mode: r.mode, label: n, base: o } }, htmlBuilder: (e, t) => { const r = ht(e.base, t), n = Nt(e, t), o = "\\utilde" === e.label ? .12 : 0, s = Ve.makeVList({ positionType: "top", positionData: r.height, children: [{ type: "elem", elem: n, wrapperClasses: ["svg-align"] }, { type: "kern", size: o }, { type: "elem", elem: r }] }, t); return Ve.makeSpan(["mord", "accentunder"], [s], t) }, mathmlBuilder: (e, t) => { const r = Ct(e.label), n = new gt.MathNode("munder", [vt(e.base, t), r]); return n.setAttribute("accentunder", "true"), n } }); const Lt = e => { const t = new gt.MathNode("mpadded", e ? [e] : []); return t.setAttribute("width", "+0.6em"), t.setAttribute("lspace", "0.3em"), t }; je({ type: "xArrow", names: ["\\xleftarrow", "\\xrightarrow", "\\xLeftarrow", "\\xRightarrow", "\\xleftrightarrow", "\\xLeftrightarrow", "\\xhookleftarrow", "\\xhookrightarrow", "\\xmapsto", "\\xrightharpoondown", "\\xrightharpoonup", "\\xleftharpoondown", "\\xleftharpoonup", "\\xrightleftharpoons", "\\xleftrightharpoons", "\\xlongequal", "\\xtwoheadrightarrow", "\\xtwoheadleftarrow", "\\xtofrom", "\\xrightleftarrows", "\\xrightequilibrium", "\\xleftequilibrium", "\\\\cdrightarrow", "\\\\cdleftarrow", "\\\\cdlongequal"], props: { numArgs: 1, numOptionalArgs: 1 }, handler(e, t, r) { let { parser: n, funcName: o } = e; return { type: "xArrow", mode: n.mode, label: o, body: t[0], below: r[0] } }, htmlBuilder(e, t) { const r = t.style; let n = t.havingStyle(r.sup()); const o = Ve.wrapFragment(ht(e.body, n, t), t), s = "\\x" === e.label.slice(0, 2) ? "x" : "cd"; let i; o.classes.push(s + "-arrow-pad"), e.below && (n = t.havingStyle(r.sub()), i = Ve.wrapFragment(ht(e.below, n, t), t), i.classes.push(s + "-arrow-pad")); const a = Nt(e, t), l = -t.fontMetrics().axisHeight + .5 * a.height; let h, c = -t.fontMetrics().axisHeight - .5 * a.height - .111; if ((o.depth > .25 || "\\xleftequilibrium" === e.label) && (c -= o.depth), i) { const e = -t.fontMetrics().axisHeight + i.height + .5 * a.height + .111; h = Ve.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: o, shift: c }, { type: "elem", elem: a, shift: l }, { type: "elem", elem: i, shift: e }] }, t) } else h = Ve.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: o, shift: c }, { type: "elem", elem: a, shift: l }] }, t); return h.children[0].children[0].children[1].classes.push("svg-align"), Ve.makeSpan(["mrel", "x-arrow"], [h], t) }, mathmlBuilder(e, t) { const r = Ct(e.label); let n; if (r.setAttribute("minsize", "x" === e.label.charAt(0) ? "1.75em" : "3.0em"), e.body) { const o = Lt(vt(e.body, t)); if (e.below) { const s = Lt(vt(e.below, t)); n = new gt.MathNode("munderover", [r, s, o]) } else n = new gt.MathNode("mover", [r, o]) } else if (e.below) { const o = Lt(vt(e.below, t)); n = new gt.MathNode("munder", [r, o]) } else n = Lt(), n = new gt.MathNode("mover", [r, n]); return n } }); const Dt = Ve.makeSpan; function Vt(e, t) { const r = nt(e.body, t, !0); return Dt([e.mclass], r, t) } function Pt(e, t) { let r; const n = xt(e.body, t); return "minner" === e.mclass ? r = new gt.MathNode("mpadded", n) : "mord" === e.mclass ? e.isCharacterBox ? (r = n[0], r.type = "mi") : r = new gt.MathNode("mi", n) : (e.isCharacterBox ? (r = n[0], r.type = "mo") : r = new gt.MathNode("mo", n), "mbin" === e.mclass ? (r.attributes.lspace = "0.22em", r.attributes.rspace = "0.22em") : "mpunct" === e.mclass ? (r.attributes.lspace = "0em", r.attributes.rspace = "0.17em") : "mopen" === e.mclass || "mclose" === e.mclass ? (r.attributes.lspace = "0em", r.attributes.rspace = "0em") : "minner" === e.mclass && (r.attributes.lspace = "0.0556em", r.attributes.width = "+0.1111em")), r } je({ type: "mclass", names: ["\\mathord", "\\mathbin", "\\mathrel", "\\mathopen", "\\mathclose", "\\mathpunct", "\\mathinner"], props: { numArgs: 1, primitive: !0 }, handler(e, t) { let { parser: r, funcName: n } = e; const o = t[0]; return { type: "mclass", mode: r.mode, mclass: "m" + n.slice(5), body: Ke(o), isCharacterBox: l.isCharacterBox(o) } }, htmlBuilder: Vt, mathmlBuilder: Pt }); const Ft = e => { const t = "ordgroup" === e.type && e.body.length ? e.body[0] : e; return "atom" !== t.type || "bin" !== t.family && "rel" !== t.family ? "mord" : "m" + t.family }; je({ type: "mclass", names: ["\\@binrel"], props: { numArgs: 2 }, handler(e, t) { let { parser: r } = e; return { type: "mclass", mode: r.mode, mclass: Ft(t[0]), body: Ke(t[1]), isCharacterBox: l.isCharacterBox(t[1]) } } }), je({ type: "mclass", names: ["\\stackrel", "\\overset", "\\underset"], props: { numArgs: 2 }, handler(e, t) { let { parser: r, funcName: n } = e; const o = t[1], s = t[0]; let i; i = "\\stackrel" !== n ? Ft(o) : "mrel"; const a = { type: "op", mode: o.mode, limits: !0, alwaysHandleSupSub: !0, parentIsSupSub: !1, symbol: !1, suppressBaseShift: "\\stackrel" !== n, body: Ke(o) }, h = { type: "supsub", mode: s.mode, base: a, sup: "\\underset" === n ? null : s, sub: "\\underset" === n ? s : null }; return { type: "mclass", mode: r.mode, mclass: i, body: [h], isCharacterBox: l.isCharacterBox(h) } }, htmlBuilder: Vt, mathmlBuilder: Pt }), je({ type: "pmb", names: ["\\pmb"], props: { numArgs: 1, allowedInText: !0 }, handler(e, t) { let { parser: r } = e; return { type: "pmb", mode: r.mode, mclass: Ft(t[0]), body: Ke(t[0]) } }, htmlBuilder(e, t) { const r = nt(e.body, t, !0), n = Ve.makeSpan([e.mclass], r, t); return n.style.textShadow = "0.02em 0.01em 0.04px", n }, mathmlBuilder(e, t) { const r = xt(e.body, t), n = new gt.MathNode("mstyle", r); return n.setAttribute("style", "text-shadow: 0.02em 0.01em 0.04px"), n } }); const Gt = { ">": "\\\\cdrightarrow", "<": "\\\\cdleftarrow", "=": "\\\\cdlongequal", A: "\\uparrow", V: "\\downarrow", "|": "\\Vert", ".": "no arrow" }, Ut = e => "textord" === e.type && "@" === e.text; function Yt(e, t, r) { const n = Gt[e]; switch (n) { case "\\\\cdrightarrow": case "\\\\cdleftarrow": return r.callFunction(n, [t[0]], [t[1]]); case "\\uparrow": case "\\downarrow": { const e = { type: "atom", text: n, mode: "math", family: "rel" }, o = { type: "ordgroup", mode: "math", body: [r.callFunction("\\\\cdleft", [t[0]], []), r.callFunction("\\Big", [e], []), r.callFunction("\\\\cdright", [t[1]], [])] }; return r.callFunction("\\\\cdparent", [o], []) } case "\\\\cdlongequal": return r.callFunction("\\\\cdlongequal", [], []); case "\\Vert": { const e = { type: "textord", text: "\\Vert", mode: "math" }; return r.callFunction("\\Big", [e], []) } default: return { type: "textord", text: " ", mode: "math" } } } je({ type: "cdlabel", names: ["\\\\cdleft", "\\\\cdright"], props: { numArgs: 1 }, handler(e, t) { let { parser: r, funcName: n } = e; return { type: "cdlabel", mode: r.mode, side: n.slice(4), label: t[0] } }, htmlBuilder(e, t) { const r = t.havingStyle(t.style.sup()), n = Ve.wrapFragment(ht(e.label, r, t), t); return n.classes.push("cd-label-" + e.side), n.style.bottom = F(.8 - n.depth), n.height = 0, n.depth = 0, n }, mathmlBuilder(e, t) { let r = new gt.MathNode("mrow", [vt(e.label, t)]); return r = new gt.MathNode("mpadded", [r]), r.setAttribute("width", "0"), "left" === e.side && r.setAttribute("lspace", "-1width"), r.setAttribute("voffset", "0.7em"), r = new gt.MathNode("mstyle", [r]), r.setAttribute("displaystyle", "false"), r.setAttribute("scriptlevel", "1"), r } }), je({ type: "cdlabelparent", names: ["\\\\cdparent"], props: { numArgs: 1 }, handler(e, t) { let { parser: r } = e; return { type: "cdlabelparent", mode: r.mode, fragment: t[0] } }, htmlBuilder(e, t) { const r = Ve.wrapFragment(ht(e.fragment, t), t); return r.classes.push("cd-vert-arrow"), r }, mathmlBuilder(e, t) { return new gt.MathNode("mrow", [vt(e.fragment, t)]) } }), je({ type: "textord", names: ["\\@char"], props: { numArgs: 1, allowedInText: !0 }, handler(e, t) { let { parser: r } = e; const o = qt(t[0], "ordgroup").body; let s = ""; for (let e = 0; e < o.length; e++) { s += qt(o[e], "textord").text } let i, a = parseInt(s); if (isNaN(a)) throw new n("\\@char has non-numeric argument " + s); if (a < 0 || a >= 1114111) throw new n("\\@char with invalid code point " + s); return a <= 65535 ? i = String.fromCharCode(a) : (a -= 65536, i = String.fromCharCode(55296 + (a >> 10), 56320 + (1023 & a))), { type: "textord", mode: r.mode, text: i } } }); const Xt = (e, t) => { const r = nt(e.body, t.withColor(e.color), !1); return Ve.makeFragment(r) }, Wt = (e, t) => { const r = xt(e.body, t.withColor(e.color)), n = new gt.MathNode("mstyle", r); return n.setAttribute("mathcolor", e.color), n }; je({ type: "color", names: ["\\textcolor"], props: { numArgs: 2, allowedInText: !0, argTypes: ["color", "original"] }, handler(e, t) { let { parser: r } = e; const n = qt(t[0], "color-token").color, o = t[1]; return { type: "color", mode: r.mode, color: n, body: Ke(o) } }, htmlBuilder: Xt, mathmlBuilder: Wt }), je({ type: "color", names: ["\\color"], props: { numArgs: 1, allowedInText: !0, argTypes: ["color"] }, handler(e, t) { let { parser: r, breakOnTokenText: n } = e; const o = qt(t[0], "color-token").color; r.gullet.macros.set("\\current@color", o); const s = r.parseExpression(!0, n); return { type: "color", mode: r.mode, color: o, body: s } }, htmlBuilder: Xt, mathmlBuilder: Wt }), je({ type: "cr", names: ["\\\\"], props: { numArgs: 0, numOptionalArgs: 0, allowedInText: !0 }, handler(e, t, r) { let { parser: n } = e; const o = "[" === n.gullet.future().text ? n.parseSizeGroup(!0) : null, s = !n.settings.displayMode || !n.settings.useStrictBehavior("newLineInDisplayMode", "In LaTeX, \\\\ or \\newline does nothing in display mode"); return { type: "cr", mode: n.mode, newLine: s, size: o && qt(o, "size").value } }, htmlBuilder(e, t) { const r = Ve.makeSpan(["mspace"], [], t); return e.newLine && (r.classes.push("newline"), e.size && (r.style.marginTop = F(P(e.size, t)))), r }, mathmlBuilder(e, t) { const r = new gt.MathNode("mspace"); return e.newLine && (r.setAttribute("linebreak", "newline"), e.size && r.setAttribute("height", F(P(e.size, t)))), r } }); const _t = { "\\global": "\\global", "\\long": "\\\\globallong", "\\\\globallong": "\\\\globallong", "\\def": "\\gdef", "\\gdef": "\\gdef", "\\edef": "\\xdef", "\\xdef": "\\xdef", "\\let": "\\\\globallet", "\\futurelet": "\\\\globalfuture" }, jt = e => { const t = e.text; if (/^(?:[\\{}$^_]|EOF)$/.test(t)) throw new n("Expected a control sequence", e); return t }, $t = (e, t, r, n) => { let o = e.gullet.macros.get(r.text); null == o && (r.noexpand = !0, o = { tokens: [r], numArgs: 0, unexpandable: !e.gullet.isExpandable(r.text) }), e.gullet.macros.set(t, o, n) }; je({ type: "internal", names: ["\\global", "\\long", "\\\\globallong"], props: { numArgs: 0, allowedInText: !0 }, handler(e) { let { parser: t, funcName: r } = e; t.consumeSpaces(); const o = t.fetch(); if (_t[o.text]) return "\\global" !== r && "\\\\globallong" !== r || (o.text = _t[o.text]), qt(t.parseFunction(), "internal"); throw new n("Invalid token after macro prefix", o) } }), je({ type: "internal", names: ["\\def", "\\gdef", "\\edef", "\\xdef"], props: { numArgs: 0, allowedInText: !0, primitive: !0 }, handler(e) { let { parser: t, funcName: r } = e, o = t.gullet.popToken(); const s = o.text; if (/^(?:[\\{}$^_]|EOF)$/.test(s)) throw new n("Expected a control sequence", o); let i, a = 0; const l = [[]]; for (; "{" !== t.gullet.future().text;)if (o = t.gullet.popToken(), "#" === o.text) { if ("{" === t.gullet.future().text) { i = t.gullet.future(), l[a].push("{"); break } if (o = t.gullet.popToken(), !/^[1-9]$/.test(o.text)) throw new n('Invalid argument number "' + o.text + '"'); if (parseInt(o.text) !== a + 1) throw new n('Argument number "' + o.text + '" out of order'); a++, l.push([]) } else { if ("EOF" === o.text) throw new n("Expected a macro definition"); l[a].push(o.text) } let { tokens: h } = t.gullet.consumeArg(); return i && h.unshift(i), "\\edef" !== r && "\\xdef" !== r || (h = t.gullet.expandTokens(h), h.reverse()), t.gullet.macros.set(s, { tokens: h, numArgs: a, delimiters: l }, r === _t[r]), { type: "internal", mode: t.mode } } }), je({ type: "internal", names: ["\\let", "\\\\globallet"], props: { numArgs: 0, allowedInText: !0, primitive: !0 }, handler(e) { let { parser: t, funcName: r } = e; const n = jt(t.gullet.popToken()); t.gullet.consumeSpaces(); const o = (e => { let t = e.gullet.popToken(); return "=" === t.text && (t = e.gullet.popToken(), " " === t.text && (t = e.gullet.popToken())), t })(t); return $t(t, n, o, "\\\\globallet" === r), { type: "internal", mode: t.mode } } }), je({ type: "internal", names: ["\\futurelet", "\\\\globalfuture"], props: { numArgs: 0, allowedInText: !0, primitive: !0 }, handler(e) { let { parser: t, funcName: r } = e; const n = jt(t.gullet.popToken()), o = t.gullet.popToken(), s = t.gullet.popToken(); return $t(t, n, s, "\\\\globalfuture" === r), t.gullet.pushToken(s), t.gullet.pushToken(o), { type: "internal", mode: t.mode } } }); const Zt = function (e, t, r) { const n = N(oe.math[e] && oe.math[e].replace || e, t, r); if (!n) throw new Error("Unsupported symbol " + e + " and font size " + t + "."); return n }, Kt = function (e, t, r, n) { const o = r.havingBaseStyle(t), s = Ve.makeSpan(n.concat(o.sizingClasses(r)), [e], r), i = o.sizeMultiplier / r.sizeMultiplier; return s.height *= i, s.depth *= i, s.maxFontSize = o.sizeMultiplier, s }, Jt = function (e, t, r) { const n = t.havingBaseStyle(r), o = (1 - t.sizeMultiplier / n.sizeMultiplier) * t.fontMetrics().axisHeight; e.classes.push("delimcenter"), e.style.top = F(o), e.height -= o, e.depth += o }, Qt = function (e, t, r, n, o, s) { const i = function (e, t, r, n) { return Ve.makeSymbol(e, "Size" + t + "-Regular", r, n) }(e, t, o, n), a = Kt(Ve.makeSpan(["delimsizing", "size" + t], [i], n), w.TEXT, n, s); return r && Jt(a, n, w.TEXT), a }, er = function (e, t, r) { let n; n = "Size1-Regular" === t ? "delim-size1" : "delim-size4"; return { type: "elem", elem: Ve.makeSpan(["delimsizinginner", n], [Ve.makeSpan([], [Ve.makeSymbol(e, t, r)])]) } }, tr = function (e, t, r) { const n = T["Size4-Regular"][e.charCodeAt(0)] ? T["Size4-Regular"][e.charCodeAt(0)][4] : T["Size1-Regular"][e.charCodeAt(0)][4], o = new J("inner", function (e, t) { switch (e) { case "\u239c": return "M291 0 H417 V" + t + " H291z M291 0 H417 V" + t + " H291z"; case "\u2223": return "M145 0 H188 V" + t + " H145z M145 0 H188 V" + t + " H145z"; case "\u2225": return "M145 0 H188 V" + t + " H145z M145 0 H188 V" + t + " H145zM367 0 H410 V" + t + " H367z M367 0 H410 V" + t + " H367z"; case "\u239f": return "M457 0 H583 V" + t + " H457z M457 0 H583 V" + t + " H457z"; case "\u23a2": return "M319 0 H403 V" + t + " H319z M319 0 H403 V" + t + " H319z"; case "\u23a5": return "M263 0 H347 V" + t + " H263z M263 0 H347 V" + t + " H263z"; case "\u23aa": return "M384 0 H504 V" + t + " H384z M384 0 H504 V" + t + " H384z"; case "\u23d0": return "M312 0 H355 V" + t + " H312z M312 0 H355 V" + t + " H312z"; case "\u2016": return "M257 0 H300 V" + t + " H257z M257 0 H300 V" + t + " H257zM478 0 H521 V" + t + " H478z M478 0 H521 V" + t + " H478z"; default: return "" } }(e, Math.round(1e3 * t))), s = new K([o], { width: F(n), height: F(t), style: "width:" + F(n), viewBox: "0 0 " + 1e3 * n + " " + Math.round(1e3 * t), preserveAspectRatio: "xMinYMin" }), i = Ve.makeSvgSpan([], [s], r); return i.height = t, i.style.height = F(t), i.style.width = F(n), { type: "elem", elem: i } }, rr = { type: "kern", size: -.008 }, nr = ["|", "\\lvert", "\\rvert", "\\vert"], or = ["\\|", "\\lVert", "\\rVert", "\\Vert"], sr = function (e, t, r, n, o, s) { let i, a, h, c, m = "", p = 0; i = h = c = e, a = null; let u = "Size1-Regular"; "\\uparrow" === e ? h = c = "\u23d0" : "\\Uparrow" === e ? h = c = "\u2016" : "\\downarrow" === e ? i = h = "\u23d0" : "\\Downarrow" === e ? i = h = "\u2016" : "\\updownarrow" === e ? (i = "\\uparrow", h = "\u23d0", c = "\\downarrow") : "\\Updownarrow" === e ? (i = "\\Uparrow", h = "\u2016", c = "\\Downarrow") : l.contains(nr, e) ? (h = "\u2223", m = "vert", p = 333) : l.contains(or, e) ? (h = "\u2225", m = "doublevert", p = 556) : "[" === e || "\\lbrack" === e ? (i = "\u23a1", h = "\u23a2", c = "\u23a3", u = "Size4-Regular", m = "lbrack", p = 667) : "]" === e || "\\rbrack" === e ? (i = "\u23a4", h = "\u23a5", c = "\u23a6", u = "Size4-Regular", m = "rbrack", p = 667) : "\\lfloor" === e || "\u230a" === e ? (h = i = "\u23a2", c = "\u23a3", u = "Size4-Regular", m = "lfloor", p = 667) : "\\lceil" === e || "\u2308" === e ? (i = "\u23a1", h = c = "\u23a2", u = "Size4-Regular", m = "lceil", p = 667) : "\\rfloor" === e || "\u230b" === e ? (h = i = "\u23a5", c = "\u23a6", u = "Size4-Regular", m = "rfloor", p = 667) : "\\rceil" === e || "\u2309" === e ? (i = "\u23a4", h = c = "\u23a5", u = "Size4-Regular", m = "rceil", p = 667) : "(" === e || "\\lparen" === e ? (i = "\u239b", h = "\u239c", c = "\u239d", u = "Size4-Regular", m = "lparen", p = 875) : ")" === e || "\\rparen" === e ? (i = "\u239e", h = "\u239f", c = "\u23a0", u = "Size4-Regular", m = "rparen", p = 875) : "\\{" === e || "\\lbrace" === e ? (i = "\u23a7", a = "\u23a8", c = "\u23a9", h = "\u23aa", u = "Size4-Regular") : "\\}" === e || "\\rbrace" === e ? (i = "\u23ab", a = "\u23ac", c = "\u23ad", h = "\u23aa", u = "Size4-Regular") : "\\lgroup" === e || "\u27ee" === e ? (i = "\u23a7", c = "\u23a9", h = "\u23aa", u = "Size4-Regular") : "\\rgroup" === e || "\u27ef" === e ? (i = "\u23ab", c = "\u23ad", h = "\u23aa", u = "Size4-Regular") : "\\lmoustache" === e || "\u23b0" === e ? (i = "\u23a7", c = "\u23ad", h = "\u23aa", u = "Size4-Regular") : "\\rmoustache" !== e && "\u23b1" !== e || (i = "\u23ab", c = "\u23a9", h = "\u23aa", u = "Size4-Regular"); const d = Zt(i, u, o), g = d.height + d.depth, f = Zt(h, u, o), b = f.height + f.depth, y = Zt(c, u, o), x = y.height + y.depth; let v = 0, k = 1; if (null !== a) { const e = Zt(a, u, o); v = e.height + e.depth, k = 2 } const S = g + x + v, M = S + Math.max(0, Math.ceil((t - S) / (k * b))) * k * b; let z = n.fontMetrics().axisHeight; r && (z *= n.sizeMultiplier); const A = M / 2 - z, T = []; if (m.length > 0) { const e = M - g - x, t = Math.round(1e3 * M), r = function (e, t) { switch (e) { case "lbrack": return "M403 1759 V84 H666 V0 H319 V1759 v" + t + " v1759 h347 v-84\nH403z M403 1759 V0 H319 V1759 v" + t + " v1759 h84z"; case "rbrack": return "M347 1759 V0 H0 V84 H263 V1759 v" + t + " v1759 H0 v84 H347z\nM347 1759 V0 H263 V1759 v" + t + " v1759 h84z"; case "vert": return "M145 15 v585 v" + t + " v585 c2.667,10,9.667,15,21,15\nc10,0,16.667,-5,20,-15 v-585 v" + -t + " v-585 c-2.667,-10,-9.667,-15,-21,-15\nc-10,0,-16.667,5,-20,15z M188 15 H145 v585 v" + t + " v585 h43z"; case "doublevert": return "M145 15 v585 v" + t + " v585 c2.667,10,9.667,15,21,15\nc10,0,16.667,-5,20,-15 v-585 v" + -t + " v-585 c-2.667,-10,-9.667,-15,-21,-15\nc-10,0,-16.667,5,-20,15z M188 15 H145 v585 v" + t + " v585 h43z\nM367 15 v585 v" + t + " v585 c2.667,10,9.667,15,21,15\nc10,0,16.667,-5,20,-15 v-585 v" + -t + " v-585 c-2.667,-10,-9.667,-15,-21,-15\nc-10,0,-16.667,5,-20,15z M410 15 H367 v585 v" + t + " v585 h43z"; case "lfloor": return "M319 602 V0 H403 V602 v" + t + " v1715 h263 v84 H319z\nMM319 602 V0 H403 V602 v" + t + " v1715 H319z"; case "rfloor": return "M319 602 V0 H403 V602 v" + t + " v1799 H0 v-84 H319z\nMM319 602 V0 H403 V602 v" + t + " v1715 H319z"; case "lceil": return "M403 1759 V84 H666 V0 H319 V1759 v" + t + " v602 h84z\nM403 1759 V0 H319 V1759 v" + t + " v602 h84z"; case "rceil": return "M347 1759 V0 H0 V84 H263 V1759 v" + t + " v602 h84z\nM347 1759 V0 h-84 V1759 v" + t + " v602 h84z"; case "lparen": return "M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1\nc-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349,\n-36,557 l0," + (t + 84) + "c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210,\n949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9\nc0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5,\n-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189\nl0,-" + (t + 92) + "c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3,\n-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z"; case "rparen": return "M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3,\n63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5\nc11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0," + (t + 9) + "\nc-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664\nc-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11\nc0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17\nc242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558\nl0,-" + (t + 144) + "c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7,\n-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z"; default: throw new Error("Unknown stretchy delimiter.") } }(m, Math.round(1e3 * e)), o = new J(m, r), s = (p / 1e3).toFixed(3) + "em", i = (t / 1e3).toFixed(3) + "em", a = new K([o], { width: s, height: i, viewBox: "0 0 " + p + " " + t }), l = Ve.makeSvgSpan([], [a], n); l.height = t / 1e3, l.style.width = s, l.style.height = i, T.push({ type: "elem", elem: l }) } else { if (T.push(er(c, u, o)), T.push(rr), null === a) { const e = M - g - x + .016; T.push(tr(h, e, n)) } else { const e = (M - g - x - v) / 2 + .016; T.push(tr(h, e, n)), T.push(rr), T.push(er(a, u, o)), T.push(rr), T.push(tr(h, e, n)) } T.push(rr), T.push(er(i, u, o)) } const B = n.havingBaseStyle(w.TEXT), C = Ve.makeVList({ positionType: "bottom", positionData: A, children: T }, B); return Kt(Ve.makeSpan(["delimsizing", "mult"], [C], B), w.TEXT, n, s) }, ir = .08, ar = function (e, t, r, n, o) { const s = function (e, t, r) { t *= 1e3; let n = ""; switch (e) { case "sqrtMain": n = function (e, t) { return "M95," + (622 + e + t) + "\nc-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14\nc0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54\nc44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10\ns173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429\nc69,-144,104.5,-217.7,106.5,-221\nl" + e / 2.075 + " -" + e + "\nc5.3,-9.3,12,-14,20,-14\nH400000v" + (40 + e) + "H845.2724\ns-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7\nc-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z\nM" + (834 + e) + " " + t + "h400000v" + (40 + e) + "h-400000z" }(t, M); break; case "sqrtSize1": n = function (e, t) { return "M263," + (601 + e + t) + "c0.7,0,18,39.7,52,119\nc34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120\nc340,-704.7,510.7,-1060.3,512,-1067\nl" + e / 2.084 + " -" + e + "\nc4.7,-7.3,11,-11,19,-11\nH40000v" + (40 + e) + "H1012.3\ns-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232\nc-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1\ns-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26\nc-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z\nM" + (1001 + e) + " " + t + "h400000v" + (40 + e) + "h-400000z" }(t, M); break; case "sqrtSize2": n = function (e, t) { return "M983 " + (10 + e + t) + "\nl" + e / 3.13 + " -" + e + "\nc4,-6.7,10,-10,18,-10 H400000v" + (40 + e) + "\nH1013.1s-83.4,268,-264.1,840c-180.7,572,-277,876.3,-289,913c-4.7,4.7,-12.7,7,-24,7\ns-12,0,-12,0c-1.3,-3.3,-3.7,-11.7,-7,-25c-35.3,-125.3,-106.7,-373.3,-214,-744\nc-10,12,-21,25,-33,39s-32,39,-32,39c-6,-5.3,-15,-14,-27,-26s25,-30,25,-30\nc26.7,-32.7,52,-63,76,-91s52,-60,52,-60s208,722,208,722\nc56,-175.3,126.3,-397.3,211,-666c84.7,-268.7,153.8,-488.2,207.5,-658.5\nc53.7,-170.3,84.5,-266.8,92.5,-289.5z\nM" + (1001 + e) + " " + t + "h400000v" + (40 + e) + "h-400000z" }(t, M); break; case "sqrtSize3": n = function (e, t) { return "M424," + (2398 + e + t) + "\nc-1.3,-0.7,-38.5,-172,-111.5,-514c-73,-342,-109.8,-513.3,-110.5,-514\nc0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5,25c-5.7,9.3,-9.8,16,-12.5,20\ns-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13,-13s76,-122,76,-122s77,-121,77,-121\ns209,968,209,968c0,-2,84.7,-361.7,254,-1079c169.3,-717.3,254.7,-1077.7,256,-1081\nl" + e / 4.223 + " -" + e + "c4,-6.7,10,-10,18,-10 H400000\nv" + (40 + e) + "H1014.6\ns-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185\nc-2,6,-10,9,-24,9\nc-8,0,-12,-0.7,-12,-2z M" + (1001 + e) + " " + t + "\nh400000v" + (40 + e) + "h-400000z" }(t, M); break; case "sqrtSize4": n = function (e, t) { return "M473," + (2713 + e + t) + "\nc339.3,-1799.3,509.3,-2700,510,-2702 l" + e / 5.298 + " -" + e + "\nc3.3,-7.3,9.3,-11,18,-11 H400000v" + (40 + e) + "H1017.7\ns-90.5,478,-276.2,1466c-185.7,988,-279.5,1483,-281.5,1485c-2,6,-10,9,-24,9\nc-8,0,-12,-0.7,-12,-2c0,-1.3,-5.3,-32,-16,-92c-50.7,-293.3,-119.7,-693.3,-207,-1200\nc0,-1.3,-5.3,8.7,-16,30c-10.7,21.3,-21.3,42.7,-32,64s-16,33,-16,33s-26,-26,-26,-26\ns76,-153,76,-153s77,-151,77,-151c0.7,0.7,35.7,202,105,604c67.3,400.7,102,602.7,104,\n606zM" + (1001 + e) + " " + t + "h400000v" + (40 + e) + "H1017.7z" }(t, M); break; case "sqrtTall": n = function (e, t, r) { return "M702 " + (e + t) + "H400000" + (40 + e) + "\nH742v" + (r - 54 - t - e) + "l-4 4-4 4c-.667.7 -2 1.5-4 2.5s-4.167 1.833-6.5 2.5-5.5 1-9.5 1\nh-12l-28-84c-16.667-52-96.667 -294.333-240-727l-212 -643 -85 170\nc-4-3.333-8.333-7.667-13 -13l-13-13l77-155 77-156c66 199.333 139 419.667\n219 661 l218 661zM702 " + t + "H400000v" + (40 + e) + "H742z" }(t, M, r) }return n }(e, n, r), i = new J(e, s), a = new K([i], { width: "400em", height: F(t), viewBox: "0 0 400000 " + r, preserveAspectRatio: "xMinYMin slice" }); return Ve.makeSvgSpan(["hide-tail"], [a], o) }, lr = ["(", "\\lparen", ")", "\\rparen", "[", "\\lbrack", "]", "\\rbrack", "\\{", "\\lbrace", "\\}", "\\rbrace", "\\lfloor", "\\rfloor", "\u230a", "\u230b", "\\lceil", "\\rceil", "\u2308", "\u2309", "\\surd"], hr = ["\\uparrow", "\\downarrow", "\\updownarrow", "\\Uparrow", "\\Downarrow", "\\Updownarrow", "|", "\\|", "\\vert", "\\Vert", "\\lvert", "\\rvert", "\\lVert", "\\rVert", "\\lgroup", "\\rgroup", "\u27ee", "\u27ef", "\\lmoustache", "\\rmoustache", "\u23b0", "\u23b1"], cr = ["<", ">", "\\langle", "\\rangle", "/", "\\backslash", "\\lt", "\\gt"], mr = [0, 1.2, 1.8, 2.4, 3], pr = [{ type: "small", style: w.SCRIPTSCRIPT }, { type: "small", style: w.SCRIPT }, { type: "small", style: w.TEXT }, { type: "large", size: 1 }, { type: "large", size: 2 }, { type: "large", size: 3 }, { type: "large", size: 4 }], ur = [{ type: "small", style: w.SCRIPTSCRIPT }, { type: "small", style: w.SCRIPT }, { type: "small", style: w.TEXT }, { type: "stack" }], dr = [{ type: "small", style: w.SCRIPTSCRIPT }, { type: "small", style: w.SCRIPT }, { type: "small", style: w.TEXT }, { type: "large", size: 1 }, { type: "large", size: 2 }, { type: "large", size: 3 }, { type: "large", size: 4 }, { type: "stack" }], gr = function (e) { if ("small" === e.type) return "Main-Regular"; if ("large" === e.type) return "Size" + e.size + "-Regular"; if ("stack" === e.type) return "Size4-Regular"; throw new Error("Add support for delim type '" + e.type + "' here.") }, fr = function (e, t, r, n) { for (let o = Math.min(2, 3 - n.style.size); o < r.length && "stack" !== r[o].type; o++) { const s = Zt(e, gr(r[o]), "math"); let i = s.height + s.depth; if ("small" === r[o].type) { i *= n.havingBaseStyle(r[o].style).sizeMultiplier } if (i > t) return r[o] } return r[r.length - 1] }, br = function (e, t, r, n, o, s) { let i; "<" === e || "\\lt" === e || "\u27e8" === e ? e = "\\langle" : ">" !== e && "\\gt" !== e && "\u27e9" !== e || (e = "\\rangle"), i = l.contains(cr, e) ? pr : l.contains(lr, e) ? dr : ur; const a = fr(e, t, i, n); return "small" === a.type ? function (e, t, r, n, o, s) { const i = Ve.makeSymbol(e, "Main-Regular", o, n), a = Kt(i, t, n, s); return r && Jt(a, n, t), a }(e, a.style, r, n, o, s) : "large" === a.type ? Qt(e, a.size, r, n, o, s) : sr(e, t, r, n, o, s) }; var yr = { sqrtImage: function (e, t) { const r = t.havingBaseSizing(), n = fr("\\surd", e * r.sizeMultiplier, dr, r); let o = r.sizeMultiplier; const s = Math.max(0, t.minRuleThickness - t.fontMetrics().sqrtRuleThickness); let i, a, l = 0, h = 0, c = 0; return "small" === n.type ? (c = 1e3 + 1e3 * s + 80, e < 1 ? o = 1 : e < 1.4 && (o = .7), l = (1 + s + ir) / o, h = (1 + s) / o, i = ar("sqrtMain", l, c, s, t), i.style.minWidth = "0.853em", a = .833 / o) : "large" === n.type ? (c = 1080 * mr[n.size], h = (mr[n.size] + s) / o, l = (mr[n.size] + s + ir) / o, i = ar("sqrtSize" + n.size, l, c, s, t), i.style.minWidth = "1.02em", a = 1 / o) : (l = e + s + ir, h = e + s, c = Math.floor(1e3 * e + s) + 80, i = ar("sqrtTall", l, c, s, t), i.style.minWidth = "0.742em", a = 1.056), i.height = h, i.style.height = F(l), { span: i, advanceWidth: a, ruleWidth: (t.fontMetrics().sqrtRuleThickness + s) * o } }, sizedDelim: function (e, t, r, o, s) { if ("<" === e || "\\lt" === e || "\u27e8" === e ? e = "\\langle" : ">" !== e && "\\gt" !== e && "\u27e9" !== e || (e = "\\rangle"), l.contains(lr, e) || l.contains(cr, e)) return Qt(e, t, !1, r, o, s); if (l.contains(hr, e)) return sr(e, mr[t], !1, r, o, s); throw new n("Illegal delimiter: '" + e + "'") }, sizeToMaxHeight: mr, customSizedDelim: br, leftRightDelim: function (e, t, r, n, o, s) { const i = n.fontMetrics().axisHeight * n.sizeMultiplier, a = 5 / n.fontMetrics().ptPerEm, l = Math.max(t - i, r + i), h = Math.max(l / 500 * 901, 2 * l - a); return br(e, h, !0, n, o, s) } }; const xr = { "\\bigl": { mclass: "mopen", size: 1 }, "\\Bigl": { mclass: "mopen", size: 2 }, "\\biggl": { mclass: "mopen", size: 3 }, "\\Biggl": { mclass: "mopen", size: 4 }, "\\bigr": { mclass: "mclose", size: 1 }, "\\Bigr": { mclass: "mclose", size: 2 }, "\\biggr": { mclass: "mclose", size: 3 }, "\\Biggr": { mclass: "mclose", size: 4 }, "\\bigm": { mclass: "mrel", size: 1 }, "\\Bigm": { mclass: "mrel", size: 2 }, "\\biggm": { mclass: "mrel", size: 3 }, "\\Biggm": { mclass: "mrel", size: 4 }, "\\big": { mclass: "mord", size: 1 }, "\\Big": { mclass: "mord", size: 2 }, "\\bigg": { mclass: "mord", size: 3 }, "\\Bigg": { mclass: "mord", size: 4 } }, wr = ["(", "\\lparen", ")", "\\rparen", "[", "\\lbrack", "]", "\\rbrack", "\\{", "\\lbrace", "\\}", "\\rbrace", "\\lfloor", "\\rfloor", "\u230a", "\u230b", "\\lceil", "\\rceil", "\u2308", "\u2309", "<", ">", "\\langle", "\u27e8", "\\rangle", "\u27e9", "\\lt", "\\gt", "\\lvert", "\\rvert", "\\lVert", "\\rVert", "\\lgroup", "\\rgroup", "\u27ee", "\u27ef", "\\lmoustache", "\\rmoustache", "\u23b0", "\u23b1", "/", "\\backslash", "|", "\\vert", "\\|", "\\Vert", "\\uparrow", "\\Uparrow", "\\downarrow", "\\Downarrow", "\\updownarrow", "\\Updownarrow", "."]; function vr(e, t) { const r = Rt(e); if (r && l.contains(wr, r.text)) return r; throw new n(r ? "Invalid delimiter '" + r.text + "' after '" + t.funcName + "'" : "Invalid delimiter type '" + e.type + "'", e) } function kr(e) { if (!e.body) throw new Error("Bug: The leftright ParseNode wasn't fully parsed.") } je({ type: "delimsizing", names: ["\\bigl", "\\Bigl", "\\biggl", "\\Biggl", "\\bigr", "\\Bigr", "\\biggr", "\\Biggr", "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", "\\big", "\\Big", "\\bigg", "\\Bigg"], props: { numArgs: 1, argTypes: ["primitive"] }, handler: (e, t) => { const r = vr(t[0], e); return { type: "delimsizing", mode: e.parser.mode, size: xr[e.funcName].size, mclass: xr[e.funcName].mclass, delim: r.text } }, htmlBuilder: (e, t) => "." === e.delim ? Ve.makeSpan([e.mclass]) : yr.sizedDelim(e.delim, e.size, t, e.mode, [e.mclass]), mathmlBuilder: e => { const t = []; "." !== e.delim && t.push(ft(e.delim, e.mode)); const r = new gt.MathNode("mo", t); "mopen" === e.mclass || "mclose" === e.mclass ? r.setAttribute("fence", "true") : r.setAttribute("fence", "false"), r.setAttribute("stretchy", "true"); const n = F(yr.sizeToMaxHeight[e.size]); return r.setAttribute("minsize", n), r.setAttribute("maxsize", n), r } }), je({ type: "leftright-right", names: ["\\right"], props: { numArgs: 1, primitive: !0 }, handler: (e, t) => { const r = e.parser.gullet.macros.get("\\current@color"); if (r && "string" != typeof r) throw new n("\\current@color set to non-string in \\right"); return { type: "leftright-right", mode: e.parser.mode, delim: vr(t[0], e).text, color: r } } }), je({ type: "leftright", names: ["\\left"], props: { numArgs: 1, primitive: !0 }, handler: (e, t) => { const r = vr(t[0], e), n = e.parser; ++n.leftrightDepth; const o = n.parseExpression(!1); --n.leftrightDepth, n.expect("\\right", !1); const s = qt(n.parseFunction(), "leftright-right"); return { type: "leftright", mode: n.mode, body: o, left: r.text, right: s.delim, rightColor: s.color } }, htmlBuilder: (e, t) => { kr(e); const r = nt(e.body, t, !0, ["mopen", "mclose"]); let n, o, s = 0, i = 0, a = !1; for (let e = 0; e < r.length; e++)r[e].isMiddle ? a = !0 : (s = Math.max(r[e].height, s), i = Math.max(r[e].depth, i)); if (s *= t.sizeMultiplier, i *= t.sizeMultiplier, n = "." === e.left ? lt(t, ["mopen"]) : yr.leftRightDelim(e.left, s, i, t, e.mode, ["mopen"]), r.unshift(n), a) for (let t = 1; t < r.length; t++) { const n = r[t].isMiddle; n && (r[t] = yr.leftRightDelim(n.delim, s, i, n.options, e.mode, [])) } if ("." === e.right) o = lt(t, ["mclose"]); else { const r = e.rightColor ? t.withColor(e.rightColor) : t; o = yr.leftRightDelim(e.right, s, i, r, e.mode, ["mclose"]) } return r.push(o), Ve.makeSpan(["minner"], r, t) }, mathmlBuilder: (e, t) => { kr(e); const r = xt(e.body, t); if ("." !== e.left) { const t = new gt.MathNode("mo", [ft(e.left, e.mode)]); t.setAttribute("fence", "true"), r.unshift(t) } if ("." !== e.right) { const t = new gt.MathNode("mo", [ft(e.right, e.mode)]); t.setAttribute("fence", "true"), e.rightColor && t.setAttribute("mathcolor", e.rightColor), r.push(t) } return bt(r) } }), je({ type: "middle", names: ["\\middle"], props: { numArgs: 1, primitive: !0 }, handler: (e, t) => { const r = vr(t[0], e); if (!e.parser.leftrightDepth) throw new n("\\middle without preceding \\left", r); return { type: "middle", mode: e.parser.mode, delim: r.text } }, htmlBuilder: (e, t) => { let r; if ("." === e.delim) r = lt(t, []); else { r = yr.sizedDelim(e.delim, 1, t, e.mode, []); const n = { delim: e.delim, options: t }; r.isMiddle = n } return r }, mathmlBuilder: (e, t) => { const r = "\\vert" === e.delim || "|" === e.delim ? ft("|", "text") : ft(e.delim, e.mode), n = new gt.MathNode("mo", [r]); return n.setAttribute("fence", "true"), n.setAttribute("lspace", "0.05em"), n.setAttribute("rspace", "0.05em"), n } }); const Sr = (e, t) => { const r = Ve.wrapFragment(ht(e.body, t), t), n = e.label.slice(1); let o, s = t.sizeMultiplier, i = 0; const a = l.isCharacterBox(e.body); if ("sout" === n) o = Ve.makeSpan(["stretchy", "sout"]), o.height = t.fontMetrics().defaultRuleThickness / s, i = -.5 * t.fontMetrics().xHeight; else if ("phase" === n) { const e = P({ number: .6, unit: "pt" }, t), n = P({ number: .35, unit: "ex" }, t); s /= t.havingBaseSizing().sizeMultiplier; const a = r.height + r.depth + e + n; r.style.paddingLeft = F(a / 2 + e); const l = Math.floor(1e3 * a * s), c = "M400000 " + (h = l) + " H0 L" + h / 2 + " 0 l65 45 L145 " + (h - 80) + " H400000z", m = new K([new J("phase", c)], { width: "400em", height: F(l / 1e3), viewBox: "0 0 400000 " + l, preserveAspectRatio: "xMinYMin slice" }); o = Ve.makeSvgSpan(["hide-tail"], [m], t), o.style.height = F(a), i = r.depth + e + n } else { /cancel/.test(n) ? a || r.classes.push("cancel-pad") : "angl" === n ? r.classes.push("anglpad") : r.classes.push("boxpad"); let s = 0, l = 0, h = 0; /box/.test(n) ? (h = Math.max(t.fontMetrics().fboxrule, t.minRuleThickness), s = t.fontMetrics().fboxsep + ("colorbox" === n ? 0 : h), l = s) : "angl" === n ? (h = Math.max(t.fontMetrics().defaultRuleThickness, t.minRuleThickness), s = 4 * h, l = Math.max(0, .25 - r.depth)) : (s = a ? .2 : 0, l = s), o = Bt(r, n, s, l, t), /fbox|boxed|fcolorbox/.test(n) ? (o.style.borderStyle = "solid", o.style.borderWidth = F(h)) : "angl" === n && .049 !== h && (o.style.borderTopWidth = F(h), o.style.borderRightWidth = F(h)), i = r.depth + l, e.backgroundColor && (o.style.backgroundColor = e.backgroundColor, e.borderColor && (o.style.borderColor = e.borderColor)) } var h; let c; if (e.backgroundColor) c = Ve.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: o, shift: i }, { type: "elem", elem: r, shift: 0 }] }, t); else { const e = /cancel|phase/.test(n) ? ["svg-align"] : []; c = Ve.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: r, shift: 0 }, { type: "elem", elem: o, shift: i, wrapperClasses: e }] }, t) } return /cancel/.test(n) && (c.height = r.height, c.depth = r.depth), /cancel/.test(n) && !a ? Ve.makeSpan(["mord", "cancel-lap"], [c], t) : Ve.makeSpan(["mord"], [c], t) }, Mr = (e, t) => { let r = 0; const n = new gt.MathNode(e.label.indexOf("colorbox") > -1 ? "mpadded" : "menclose", [vt(e.body, t)]); switch (e.label) { case "\\cancel": n.setAttribute("notation", "updiagonalstrike"); break; case "\\bcancel": n.setAttribute("notation", "downdiagonalstrike"); break; case "\\phase": n.setAttribute("notation", "phasorangle"); break; case "\\sout": n.setAttribute("notation", "horizontalstrike"); break; case "\\fbox": n.setAttribute("notation", "box"); break; case "\\angl": n.setAttribute("notation", "actuarial"); break; case "\\fcolorbox": case "\\colorbox": if (r = t.fontMetrics().fboxsep * t.fontMetrics().ptPerEm, n.setAttribute("width", "+" + 2 * r + "pt"), n.setAttribute("height", "+" + 2 * r + "pt"), n.setAttribute("lspace", r + "pt"), n.setAttribute("voffset", r + "pt"), "\\fcolorbox" === e.label) { const r = Math.max(t.fontMetrics().fboxrule, t.minRuleThickness); n.setAttribute("style", "border: " + r + "em solid " + String(e.borderColor)) } break; case "\\xcancel": n.setAttribute("notation", "updiagonalstrike downdiagonalstrike") }return e.backgroundColor && n.setAttribute("mathbackground", e.backgroundColor), n }; je({ type: "enclose", names: ["\\colorbox"], props: { numArgs: 2, allowedInText: !0, argTypes: ["color", "text"] }, handler(e, t, r) { let { parser: n, funcName: o } = e; const s = qt(t[0], "color-token").color, i = t[1]; return { type: "enclose", mode: n.mode, label: o, backgroundColor: s, body: i } }, htmlBuilder: Sr, mathmlBuilder: Mr }), je({ type: "enclose", names: ["\\fcolorbox"], props: { numArgs: 3, allowedInText: !0, argTypes: ["color", "color", "text"] }, handler(e, t, r) { let { parser: n, funcName: o } = e; const s = qt(t[0], "color-token").color, i = qt(t[1], "color-token").color, a = t[2]; return { type: "enclose", mode: n.mode, label: o, backgroundColor: i, borderColor: s, body: a } }, htmlBuilder: Sr, mathmlBuilder: Mr }), je({ type: "enclose", names: ["\\fbox"], props: { numArgs: 1, argTypes: ["hbox"], allowedInText: !0 }, handler(e, t) { let { parser: r } = e; return { type: "enclose", mode: r.mode, label: "\\fbox", body: t[0] } } }), je({ type: "enclose", names: ["\\cancel", "\\bcancel", "\\xcancel", "\\sout", "\\phase"], props: { numArgs: 1 }, handler(e, t) { let { parser: r, funcName: n } = e; const o = t[0]; return { type: "enclose", mode: r.mode, label: n, body: o } }, htmlBuilder: Sr, mathmlBuilder: Mr }), je({ type: "enclose", names: ["\\angl"], props: { numArgs: 1, argTypes: ["hbox"], allowedInText: !1 }, handler(e, t) { let { parser: r } = e; return { type: "enclose", mode: r.mode, label: "\\angl", body: t[0] } } }); const zr = {}; function Ar(e) { let { type: t, names: r, props: n, handler: o, htmlBuilder: s, mathmlBuilder: i } = e; const a = { type: t, numArgs: n.numArgs || 0, allowedInText: !1, numOptionalArgs: 0, handler: o }; for (let e = 0; e < r.length; ++e)zr[r[e]] = a; s && (We[t] = s), i && (_e[t] = i) } const Tr = {}; function Br(e, t) { Tr[e] = t } class Cr { constructor(e, t, r) { this.lexer = void 0, this.start = void 0, this.end = void 0, this.lexer = e, this.start = t, this.end = r } static range(e, t) { return t ? e && e.loc && t.loc && e.loc.lexer === t.loc.lexer ? new Cr(e.loc.lexer, e.loc.start, t.loc.end) : null : e && e.loc } } class Nr { constructor(e, t) { this.text = void 0, this.loc = void 0, this.noexpand = void 0, this.treatAsRelax = void 0, this.text = e, this.loc = t } range(e, t) { return new Nr(t, Cr.range(this, e)) } } function qr(e) { const t = []; e.consumeSpaces(); let r = e.fetch().text; for ("\\relax" === r && (e.consume(), e.consumeSpaces(), r = e.fetch().text); "\\hline" === r || "\\hdashline" === r;)e.consume(), t.push("\\hdashline" === r), e.consumeSpaces(), r = e.fetch().text; return t } const Ir = e => { if (!e.parser.settings.displayMode) throw new n("{" + e.envName + "} can be used only in display mode.") }; function Rr(e) { if (-1 === e.indexOf("ed")) return -1 === e.indexOf("*") } function Hr(e, t, r) { let { hskipBeforeAndAfter: o, addJot: s, cols: i, arraystretch: a, colSeparationType: l, autoTag: h, singleRow: c, emptySingleRow: m, maxNumCols: p, leqno: u } = t; if (e.gullet.beginGroup(), c || e.gullet.macros.set("\\cr", "\\\\\\relax"), !a) { const t = e.gullet.expandMacroAsText("\\arraystretch"); if (null == t) a = 1; else if (a = parseFloat(t), !a || a < 0) throw new n("Invalid \\arraystretch: " + t) } e.gullet.beginGroup(); let d = []; const g = [d], f = [], b = [], y = null != h ? [] : void 0; function x() { h && e.gullet.macros.set("\\@eqnsw", "1", !0) } function w() { y && (e.gullet.macros.get("\\df@tag") ? (y.push(e.subparse([new Nr("\\df@tag")])), e.gullet.macros.set("\\df@tag", void 0, !0)) : y.push(Boolean(h) && "1" === e.gullet.macros.get("\\@eqnsw"))) } for (x(), b.push(qr(e)); ;) { let t = e.parseExpression(!1, c ? "\\end" : "\\\\"); e.gullet.endGroup(), e.gullet.beginGroup(), t = { type: "ordgroup", mode: e.mode, body: t }, r && (t = { type: "styling", mode: e.mode, style: r, body: [t] }), d.push(t); const o = e.fetch().text; if ("&" === o) { if (p && d.length === p) { if (c || l) throw new n("Too many tab characters: &", e.nextToken); e.settings.reportNonstrict("textEnv", "Too few columns specified in the {array} column argument.") } e.consume() } else { if ("\\end" === o) { w(), 1 === d.length && "styling" === t.type && 0 === t.body[0].body.length && (g.length > 1 || !m) && g.pop(), b.length < g.length + 1 && b.push([]); break } if ("\\\\" !== o) throw new n("Expected & or \\\\ or \\cr or \\end", e.nextToken); { let t; e.consume(), " " !== e.gullet.future().text && (t = e.parseSizeGroup(!0)), f.push(t ? t.value : null), w(), b.push(qr(e)), d = [], g.push(d), x() } } } return e.gullet.endGroup(), e.gullet.endGroup(), { type: "array", mode: e.mode, addJot: s, arraystretch: a, body: g, cols: i, rowGaps: f, hskipBeforeAndAfter: o, hLinesBeforeRow: b, colSeparationType: l, tags: y, leqno: u } } function Or(e) { return "d" === e.slice(0, 1) ? "display" : "text" } const Er = function (e, t) { let r, o; const s = e.body.length, i = e.hLinesBeforeRow; let a = 0, h = new Array(s); const c = [], m = Math.max(t.fontMetrics().arrayRuleWidth, t.minRuleThickness), p = 1 / t.fontMetrics().ptPerEm; let u = 5 * p; if (e.colSeparationType && "small" === e.colSeparationType) { u = t.havingStyle(w.SCRIPT).sizeMultiplier / t.sizeMultiplier * .2778 } const d = "CD" === e.colSeparationType ? P({ number: 3, unit: "ex" }, t) : 12 * p, g = 3 * p, f = e.arraystretch * d, b = .7 * f, y = .3 * f; let x = 0; function v(e) { for (let t = 0; t < e.length; ++t)t > 0 && (x += .25), c.push({ pos: x, isDashed: e[t] }) } for (v(i[0]), r = 0; r < e.body.length; ++r) { const n = e.body[r]; let s = b, l = y; a < n.length && (a = n.length); const c = new Array(n.length); for (o = 0; o < n.length; ++o) { const e = ht(n[o], t); l < e.depth && (l = e.depth), s < e.height && (s = e.height), c[o] = e } const m = e.rowGaps[r]; let p = 0; m && (p = P(m, t), p > 0 && (p += y, l < p && (l = p), p = 0)), e.addJot && (l += g), c.height = s, c.depth = l, x += s, c.pos = x, x += l + p, h[r] = c, v(i[r + 1]) } const k = x / 2 + t.fontMetrics().axisHeight, S = e.cols || [], M = []; let z, A; const T = []; if (e.tags && e.tags.some((e => e))) for (r = 0; r < s; ++r) { const n = h[r], o = n.pos - k, s = e.tags[r]; let i; i = !0 === s ? Ve.makeSpan(["eqn-num"], [], t) : !1 === s ? Ve.makeSpan([], [], t) : Ve.makeSpan([], nt(s, t, !0), t), i.depth = n.depth, i.height = n.height, T.push({ type: "elem", elem: i, shift: o }) } for (o = 0, A = 0; o < a || A < S.length; ++o, ++A) { let i, c = S[A] || {}, p = !0; for (; "separator" === c.type;) { if (p || (z = Ve.makeSpan(["arraycolsep"], []), z.style.width = F(t.fontMetrics().doubleRuleSep), M.push(z)), "|" !== c.separator && ":" !== c.separator) throw new n("Invalid separator type: " + c.separator); { const e = "|" === c.separator ? "solid" : "dashed", r = Ve.makeSpan(["vertical-separator"], [], t); r.style.height = F(x), r.style.borderRightWidth = F(m), r.style.borderRightStyle = e, r.style.margin = "0 " + F(-m / 2); const n = x - k; n && (r.style.verticalAlign = F(-n)), M.push(r) } A++, c = S[A] || {}, p = !1 } if (o >= a) continue; (o > 0 || e.hskipBeforeAndAfter) && (i = l.deflt(c.pregap, u), 0 !== i && (z = Ve.makeSpan(["arraycolsep"], []), z.style.width = F(i), M.push(z))); let d = []; for (r = 0; r < s; ++r) { const e = h[r], t = e[o]; if (!t) continue; const n = e.pos - k; t.depth = e.depth, t.height = e.height, d.push({ type: "elem", elem: t, shift: n }) } d = Ve.makeVList({ positionType: "individualShift", children: d }, t), d = Ve.makeSpan(["col-align-" + (c.align || "c")], [d]), M.push(d), (o < a - 1 || e.hskipBeforeAndAfter) && (i = l.deflt(c.postgap, u), 0 !== i && (z = Ve.makeSpan(["arraycolsep"], []), z.style.width = F(i), M.push(z))) } if (h = Ve.makeSpan(["mtable"], M), c.length > 0) { const e = Ve.makeLineSpan("hline", t, m), r = Ve.makeLineSpan("hdashline", t, m), n = [{ type: "elem", elem: h, shift: 0 }]; for (; c.length > 0;) { const t = c.pop(), o = t.pos - k; t.isDashed ? n.push({ type: "elem", elem: r, shift: o }) : n.push({ type: "elem", elem: e, shift: o }) } h = Ve.makeVList({ positionType: "individualShift", children: n }, t) } if (0 === T.length) return Ve.makeSpan(["mord"], [h], t); { let e = Ve.makeVList({ positionType: "individualShift", children: T }, t); return e = Ve.makeSpan(["tag"], [e], t), Ve.makeFragment([h, e]) } }, Lr = { c: "center ", l: "left ", r: "right " }, Dr = function (e, t) { const r = [], n = new gt.MathNode("mtd", [], ["mtr-glue"]), o = new gt.MathNode("mtd", [], ["mml-eqn-num"]); for (let s = 0; s < e.body.length; s++) { const i = e.body[s], a = []; for (let e = 0; e < i.length; e++)a.push(new gt.MathNode("mtd", [vt(i[e], t)])); e.tags && e.tags[s] && (a.unshift(n), a.push(n), e.leqno ? a.unshift(o) : a.push(o)), r.push(new gt.MathNode("mtr", a)) } let s = new gt.MathNode("mtable", r); const i = .5 === e.arraystretch ? .1 : .16 + e.arraystretch - 1 + (e.addJot ? .09 : 0); s.setAttribute("rowspacing", F(i)); let a = "", l = ""; if (e.cols && e.cols.length > 0) { const t = e.cols; let r = "", n = !1, o = 0, i = t.length; "separator" === t[0].type && (a += "top ", o = 1), "separator" === t[t.length - 1].type && (a += "bottom ", i -= 1); for (let e = o; e < i; e++)"align" === t[e].type ? (l += Lr[t[e].align], n && (r += "none "), n = !0) : "separator" === t[e].type && n && (r += "|" === t[e].separator ? "solid " : "dashed ", n = !1); s.setAttribute("columnalign", l.trim()), /[sd]/.test(r) && s.setAttribute("columnlines", r.trim()) } if ("align" === e.colSeparationType) { const t = e.cols || []; let r = ""; for (let e = 1; e < t.length; e++)r += e % 2 ? "0em " : "1em "; s.setAttribute("columnspacing", r.trim()) } else "alignat" === e.colSeparationType || "gather" === e.colSeparationType ? s.setAttribute("columnspacing", "0em") : "small" === e.colSeparationType ? s.setAttribute("columnspacing", "0.2778em") : "CD" === e.colSeparationType ? s.setAttribute("columnspacing", "0.5em") : s.setAttribute("columnspacing", "1em"); let h = ""; const c = e.hLinesBeforeRow; a += c[0].length > 0 ? "left " : "", a += c[c.length - 1].length > 0 ? "right " : ""; for (let e = 1; e < c.length - 1; e++)h += 0 === c[e].length ? "none " : c[e][0] ? "dashed " : "solid "; return /[sd]/.test(h) && s.setAttribute("rowlines", h.trim()), "" !== a && (s = new gt.MathNode("menclose", [s]), s.setAttribute("notation", a.trim())), e.arraystretch && e.arraystretch < 1 && (s = new gt.MathNode("mstyle", [s]), s.setAttribute("scriptlevel", "1")), s }, Vr = function (e, t) { -1 === e.envName.indexOf("ed") && Ir(e); const r = [], o = e.envName.indexOf("at") > -1 ? "alignat" : "align", s = "split" === e.envName, i = Hr(e.parser, { cols: r, addJot: !0, autoTag: s ? void 0 : Rr(e.envName), emptySingleRow: !0, colSeparationType: o, maxNumCols: s ? 2 : void 0, leqno: e.parser.settings.leqno }, "display"); let a, l = 0; const h = { type: "ordgroup", mode: e.mode, body: [] }; if (t[0] && "ordgroup" === t[0].type) { let e = ""; for (let r = 0; r < t[0].body.length; r++) { e += qt(t[0].body[r], "textord").text } a = Number(e), l = 2 * a } const c = !l; i.body.forEach((function (e) { for (let t = 1; t < e.length; t += 2) { const r = qt(e[t], "styling"); qt(r.body[0], "ordgroup").body.unshift(h) } if (c) l < e.length && (l = e.length); else { const t = e.length / 2; if (a < t) throw new n("Too many math in a row: expected " + a + ", but got " + t, e[0]) } })); for (let e = 0; e < l; ++e) { let t = "r", n = 0; e % 2 == 1 ? t = "l" : e > 0 && c && (n = 1), r[e] = { type: "align", align: t, pregap: n, postgap: 0 } } return i.colSeparationType = c ? "align" : "alignat", i }; Ar({ type: "array", names: ["array", "darray"], props: { numArgs: 1 }, handler(e, t) { const r = (Rt(t[0]) ? [t[0]] : qt(t[0], "ordgroup").body).map((function (e) { const t = It(e).text; if (-1 !== "lcr".indexOf(t)) return { type: "align", align: t }; if ("|" === t) return { type: "separator", separator: "|" }; if (":" === t) return { type: "separator", separator: ":" }; throw new n("Unknown column alignment: " + t, e) })), o = { cols: r, hskipBeforeAndAfter: !0, maxNumCols: r.length }; return Hr(e.parser, o, Or(e.envName)) }, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["matrix", "pmatrix", "bmatrix", "Bmatrix", "vmatrix", "Vmatrix", "matrix*", "pmatrix*", "bmatrix*", "Bmatrix*", "vmatrix*", "Vmatrix*"], props: { numArgs: 0 }, handler(e) { const t = { matrix: null, pmatrix: ["(", ")"], bmatrix: ["[", "]"], Bmatrix: ["\\{", "\\}"], vmatrix: ["|", "|"], Vmatrix: ["\\Vert", "\\Vert"] }[e.envName.replace("*", "")]; let r = "c"; const o = { hskipBeforeAndAfter: !1, cols: [{ type: "align", align: r }] }; if ("*" === e.envName.charAt(e.envName.length - 1)) { const t = e.parser; if (t.consumeSpaces(), "[" === t.fetch().text) { if (t.consume(), t.consumeSpaces(), r = t.fetch().text, -1 === "lcr".indexOf(r)) throw new n("Expected l or c or r", t.nextToken); t.consume(), t.consumeSpaces(), t.expect("]"), t.consume(), o.cols = [{ type: "align", align: r }] } } const s = Hr(e.parser, o, Or(e.envName)), i = Math.max(0, ...s.body.map((e => e.length))); return s.cols = new Array(i).fill({ type: "align", align: r }), t ? { type: "leftright", mode: e.mode, body: [s], left: t[0], right: t[1], rightColor: void 0 } : s }, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["smallmatrix"], props: { numArgs: 0 }, handler(e) { const t = Hr(e.parser, { arraystretch: .5 }, "script"); return t.colSeparationType = "small", t }, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["subarray"], props: { numArgs: 1 }, handler(e, t) { const r = (Rt(t[0]) ? [t[0]] : qt(t[0], "ordgroup").body).map((function (e) { const t = It(e).text; if (-1 !== "lc".indexOf(t)) return { type: "align", align: t }; throw new n("Unknown column alignment: " + t, e) })); if (r.length > 1) throw new n("{subarray} can contain only one column"); let o = { cols: r, hskipBeforeAndAfter: !1, arraystretch: .5 }; if (o = Hr(e.parser, o, "script"), o.body.length > 0 && o.body[0].length > 1) throw new n("{subarray} can contain only one column"); return o }, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["cases", "dcases", "rcases", "drcases"], props: { numArgs: 0 }, handler(e) { const t = Hr(e.parser, { arraystretch: 1.2, cols: [{ type: "align", align: "l", pregap: 0, postgap: 1 }, { type: "align", align: "l", pregap: 0, postgap: 0 }] }, Or(e.envName)); return { type: "leftright", mode: e.mode, body: [t], left: e.envName.indexOf("r") > -1 ? "." : "\\{", right: e.envName.indexOf("r") > -1 ? "\\}" : ".", rightColor: void 0 } }, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["align", "align*", "aligned", "split"], props: { numArgs: 0 }, handler: Vr, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["gathered", "gather", "gather*"], props: { numArgs: 0 }, handler(e) { l.contains(["gather", "gather*"], e.envName) && Ir(e); const t = { cols: [{ type: "align", align: "c" }], addJot: !0, colSeparationType: "gather", autoTag: Rr(e.envName), emptySingleRow: !0, leqno: e.parser.settings.leqno }; return Hr(e.parser, t, "display") }, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["alignat", "alignat*", "alignedat"], props: { numArgs: 1 }, handler: Vr, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["equation", "equation*"], props: { numArgs: 0 }, handler(e) { Ir(e); const t = { autoTag: Rr(e.envName), emptySingleRow: !0, singleRow: !0, maxNumCols: 1, leqno: e.parser.settings.leqno }; return Hr(e.parser, t, "display") }, htmlBuilder: Er, mathmlBuilder: Dr }), Ar({ type: "array", names: ["CD"], props: { numArgs: 0 }, handler(e) { return Ir(e), function (e) { const t = []; for (e.gullet.beginGroup(), e.gullet.macros.set("\\cr", "\\\\\\relax"), e.gullet.beginGroup(); ;) { t.push(e.parseExpression(!1, "\\\\")), e.gullet.endGroup(), e.gullet.beginGroup(); const r = e.fetch().text; if ("&" !== r && "\\\\" !== r) { if ("\\end" === r) { 0 === t[t.length - 1].length && t.pop(); break } throw new n("Expected \\\\ or \\cr or \\end", e.nextToken) } e.consume() } let r = []; const o = [r]; for (let a = 0; a < t.length; a++) { const l = t[a]; let h = { type: "styling", body: [], mode: "math", style: "display" }; for (let t = 0; t < l.length; t++)if (Ut(l[t])) { r.push(h), t += 1; const o = It(l[t]).text, a = new Array(2); if (a[0] = { type: "ordgroup", mode: "math", body: [] }, a[1] = { type: "ordgroup", mode: "math", body: [] }, "=|.".indexOf(o) > -1); else { if (!("<>AV".indexOf(o) > -1)) throw new n('Expected one of "<>AV=|." after @', l[t]); for (let e = 0; e < 2; e++) { let r = !0; for (let h = t + 1; h < l.length; h++) { if (i = o, ("mathord" === (s = l[h]).type || "atom" === s.type) && s.text === i) { r = !1, t = h; break } if (Ut(l[h])) throw new n("Missing a " + o + " character to complete a CD arrow.", l[h]); a[e].body.push(l[h]) } if (r) throw new n("Missing a " + o + " character to complete a CD arrow.", l[t]) } } const c = { type: "styling", body: [Yt(o, a, e)], mode: "math", style: "display" }; r.push(c), h = { type: "styling", body: [], mode: "math", style: "display" } } else h.body.push(l[t]); a % 2 == 0 ? r.push(h) : r.shift(), r = [], o.push(r) } var s, i; return e.gullet.endGroup(), e.gullet.endGroup(), { type: "array", mode: "math", body: o, arraystretch: 1, addJot: !0, rowGaps: [null], cols: new Array(o[0].length).fill({ type: "align", align: "c", pregap: .25, postgap: .25 }), colSeparationType: "CD", hLinesBeforeRow: new Array(o.length + 1).fill([]) } }(e.parser) }, htmlBuilder: Er, mathmlBuilder: Dr }), Br("\\nonumber", "\\gdef\\@eqnsw{0}"), Br("\\notag", "\\nonumber"), je({ type: "text", names: ["\\hline", "\\hdashline"], props: { numArgs: 0, allowedInText: !0, allowedInMath: !0 }, handler(e, t) { throw new n(e.funcName + " valid only within array environment") } }); var Pr = zr; je({ type: "environment", names: ["\\begin", "\\end"], props: { numArgs: 1, argTypes: ["text"] }, handler(e, t) { let { parser: r, funcName: o } = e; const s = t[0]; if ("ordgroup" !== s.type) throw new n("Invalid environment name", s); let i = ""; for (let e = 0; e < s.body.length; ++e)i += qt(s.body[e], "textord").text; if ("\\begin" === o) { if (!Pr.hasOwnProperty(i)) throw new n("No such environment: " + i, s); const e = Pr[i], { args: t, optArgs: o } = r.parseArguments("\\begin{" + i + "}", e), a = { mode: r.mode, envName: i, parser: r }, l = e.handler(a, t, o); r.expect("\\end", !1); const h = r.nextToken, c = qt(r.parseFunction(), "environment"); if (c.name !== i) throw new n("Mismatch: \\begin{" + i + "} matched by \\end{" + c.name + "}", h); return l } return { type: "environment", mode: r.mode, name: i, nameGroup: s } } }); const Fr = (e, t) => { const r = e.font, n = t.withFont(r); return ht(e.body, n) }, Gr = (e, t) => { const r = e.font, n = t.withFont(r); return vt(e.body, n) }, Ur = { "\\Bbb": "\\mathbb", "\\bold": "\\mathbf", "\\frak": "\\mathfrak", "\\bm": "\\boldsymbol" }; je({ type: "font", names: ["\\mathrm", "\\mathit", "\\mathbf", "\\mathnormal", "\\mathbb", "\\mathcal", "\\mathfrak", "\\mathscr", "\\mathsf", "\\mathtt", "\\Bbb", "\\bold", "\\frak"], props: { numArgs: 1, allowedInArgument: !0 }, handler: (e, t) => { let { parser: r, funcName: n } = e; const o = Ze(t[0]); let s = n; return s in Ur && (s = Ur[s]), { type: "font", mode: r.mode, font: s.slice(1), body: o } }, htmlBuilder: Fr, mathmlBuilder: Gr }), je({ type: "mclass", names: ["\\boldsymbol", "\\bm"], props: { numArgs: 1 }, handler: (e, t) => { let { parser: r } = e; const n = t[0], o = l.isCharacterBox(n); return { type: "mclass", mode: r.mode, mclass: Ft(n), body: [{ type: "font", mode: r.mode, font: "boldsymbol", body: n }], isCharacterBox: o } } }), je({ type: "font", names: ["\\rm", "\\sf", "\\tt", "\\bf", "\\it", "\\cal"], props: { numArgs: 0, allowedInText: !0 }, handler: (e, t) => { let { parser: r, funcName: n, breakOnTokenText: o } = e; const { mode: s } = r, i = r.parseExpression(!0, o); return { type: "font", mode: s, font: "math" + n.slice(1), body: { type: "ordgroup", mode: r.mode, body: i } } }, htmlBuilder: Fr, mathmlBuilder: Gr }); const Yr = (e, t) => { let r = t; return "display" === e ? r = r.id >= w.SCRIPT.id ? r.text() : w.DISPLAY : "text" === e && r.size === w.DISPLAY.size ? r = w.TEXT : "script" === e ? r = w.SCRIPT : "scriptscript" === e && (r = w.SCRIPTSCRIPT), r }, Xr = (e, t) => { const r = Yr(e.size, t.style), n = r.fracNum(), o = r.fracDen(); let s; s = t.havingStyle(n); const i = ht(e.numer, s, t); if (e.continued) { const e = 8.5 / t.fontMetrics().ptPerEm, r = 3.5 / t.fontMetrics().ptPerEm; i.height = i.height < e ? e : i.height, i.depth = i.depth < r ? r : i.depth } s = t.havingStyle(o); const a = ht(e.denom, s, t); let l, h, c, m, p, u, d, g, f, b; if (e.hasBarLine ? (e.barSize ? (h = P(e.barSize, t), l = Ve.makeLineSpan("frac-line", t, h)) : l = Ve.makeLineSpan("frac-line", t), h = l.height, c = l.height) : (l = null, h = 0, c = t.fontMetrics().defaultRuleThickness), r.size === w.DISPLAY.size || "display" === e.size ? (m = t.fontMetrics().num1, p = h > 0 ? 3 * c : 7 * c, u = t.fontMetrics().denom1) : (h > 0 ? (m = t.fontMetrics().num2, p = c) : (m = t.fontMetrics().num3, p = 3 * c), u = t.fontMetrics().denom2), l) { const e = t.fontMetrics().axisHeight; m - i.depth - (e + .5 * h) < p && (m += p - (m - i.depth - (e + .5 * h))), e - .5 * h - (a.height - u) < p && (u += p - (e - .5 * h - (a.height - u))); const r = -(e - .5 * h); d = Ve.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: a, shift: u }, { type: "elem", elem: l, shift: r }, { type: "elem", elem: i, shift: -m }] }, t) } else { const e = m - i.depth - (a.height - u); e < p && (m += .5 * (p - e), u += .5 * (p - e)), d = Ve.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: a, shift: u }, { type: "elem", elem: i, shift: -m }] }, t) } return s = t.havingStyle(r), d.height *= s.sizeMultiplier / t.sizeMultiplier, d.depth *= s.sizeMultiplier / t.sizeMultiplier, g = r.size === w.DISPLAY.size ? t.fontMetrics().delim1 : r.size === w.SCRIPTSCRIPT.size ? t.havingStyle(w.SCRIPT).fontMetrics().delim2 : t.fontMetrics().delim2, f = null == e.leftDelim ? lt(t, ["mopen"]) : yr.customSizedDelim(e.leftDelim, g, !0, t.havingStyle(r), e.mode, ["mopen"]), b = e.continued ? Ve.makeSpan([]) : null == e.rightDelim ? lt(t, ["mclose"]) : yr.customSizedDelim(e.rightDelim, g, !0, t.havingStyle(r), e.mode, ["mclose"]), Ve.makeSpan(["mord"].concat(s.sizingClasses(t)), [f, Ve.makeSpan(["mfrac"], [d]), b], t) }, Wr = (e, t) => { let r = new gt.MathNode("mfrac", [vt(e.numer, t), vt(e.denom, t)]); if (e.hasBarLine) { if (e.barSize) { const n = P(e.barSize, t); r.setAttribute("linethickness", F(n)) } } else r.setAttribute("linethickness", "0px"); const n = Yr(e.size, t.style); if (n.size !== t.style.size) { r = new gt.MathNode("mstyle", [r]); const e = n.size === w.DISPLAY.size ? "true" : "false"; r.setAttribute("displaystyle", e), r.setAttribute("scriptlevel", "0") } if (null != e.leftDelim || null != e.rightDelim) { const t = []; if (null != e.leftDelim) { const r = new gt.MathNode("mo", [new gt.TextNode(e.leftDelim.replace("\\", ""))]); r.setAttribute("fence", "true"), t.push(r) } if (t.push(r), null != e.rightDelim) { const r = new gt.MathNode("mo", [new gt.TextNode(e.rightDelim.replace("\\", ""))]); r.setAttribute("fence", "true"), t.push(r) } return bt(t) } return r }; je({ type: "genfrac", names: ["\\dfrac", "\\frac", "\\tfrac", "\\dbinom", "\\binom", "\\tbinom", "\\\\atopfrac", "\\\\bracefrac", "\\\\brackfrac"], props: { numArgs: 2, allowedInArgument: !0 }, handler: (e, t) => { let { parser: r, funcName: n } = e; const o = t[0], s = t[1]; let i, a = null, l = null, h = "auto"; switch (n) { case "\\dfrac": case "\\frac": case "\\tfrac": i = !0; break; case "\\\\atopfrac": i = !1; break; case "\\dbinom": case "\\binom": case "\\tbinom": i = !1, a = "(", l = ")"; break; case "\\\\bracefrac": i = !1, a = "\\{", l = "\\}"; break; case "\\\\brackfrac": i = !1, a = "[", l = "]"; break; default: throw new Error("Unrecognized genfrac command") }switch (n) { case "\\dfrac": case "\\dbinom": h = "display"; break; case "\\tfrac": case "\\tbinom": h = "text" }return { type: "genfrac", mode: r.mode, continued: !1, numer: o, denom: s, hasBarLine: i, leftDelim: a, rightDelim: l, size: h, barSize: null } }, htmlBuilder: Xr, mathmlBuilder: Wr }), je({ type: "genfrac", names: ["\\cfrac"], props: { numArgs: 2 }, handler: (e, t) => { let { parser: r, funcName: n } = e; const o = t[0], s = t[1]; return { type: "genfrac", mode: r.mode, continued: !0, numer: o, denom: s, hasBarLine: !0, leftDelim: null, rightDelim: null, size: "display", barSize: null } } }), je({ type: "infix", names: ["\\over", "\\choose", "\\atop", "\\brace", "\\brack"], props: { numArgs: 0, infix: !0 }, handler(e) { let t, { parser: r, funcName: n, token: o } = e; switch (n) { case "\\over": t = "\\frac"; break; case "\\choose": t = "\\binom"; break; case "\\atop": t = "\\\\atopfrac"; break; case "\\brace": t = "\\\\bracefrac"; break; case "\\brack": t = "\\\\brackfrac"; break; default: throw new Error("Unrecognized infix genfrac command") }return { type: "infix", mode: r.mode, replaceWith: t, token: o } } }); const _r = ["display", "text", "script", "scriptscript"], jr = function (e) { let t = null; return e.length > 0 && (t = e, t = "." === t ? null : t), t }; je({ type: "genfrac", names: ["\\genfrac"], props: { numArgs: 6, allowedInArgument: !0, argTypes: ["math", "math", "size", "text", "math", "math"] }, handler(e, t) { let { parser: r } = e; const n = t[4], o = t[5], s = Ze(t[0]), i = "atom" === s.type && "open" === s.family ? jr(s.text) : null, a = Ze(t[1]), l = "atom" === a.type && "close" === a.family ? jr(a.text) : null, h = qt(t[2], "size"); let c, m = null; h.isBlank ? c = !0 : (m = h.value, c = m.number > 0); let p = "auto", u = t[3]; if ("ordgroup" === u.type) { if (u.body.length > 0) { const e = qt(u.body[0], "textord"); p = _r[Number(e.text)] } } else u = qt(u, "textord"), p = _r[Number(u.text)]; return { type: "genfrac", mode: r.mode, numer: n, denom: o, continued: !1, hasBarLine: c, barSize: m, leftDelim: i, rightDelim: l, size: p } }, htmlBuilder: Xr, mathmlBuilder: Wr }), je({ type: "infix", names: ["\\above"], props: { numArgs: 1, argTypes: ["size"], infix: !0 }, handler(e, t) { let { parser: r, funcName: n, token: o } = e; return { type: "infix", mode: r.mode, replaceWith: "\\\\abovefrac", size: qt(t[0], "size").value, token: o } } }), je({ type: "genfrac", names: ["\\\\abovefrac"], props: { numArgs: 3, argTypes: ["math", "size", "math"] }, handler: (e, t) => { let { parser: r, funcName: n } = e; const o = t[0], s = function (e) { if (!e) throw new Error("Expected non-null, but got " + String(e)); return e }(qt(t[1], "infix").size), i = t[2], a = s.number > 0; return { type: "genfrac", mode: r.mode, numer: o, denom: i, continued: !1, hasBarLine: a, barSize: s, leftDelim: null, rightDelim: null, size: "auto" } }, htmlBuilder: Xr, mathmlBuilder: Wr }); const $r = (e, t) => { const r = t.style; let n, o; "supsub" === e.type ? (n = e.sup ? ht(e.sup, t.havingStyle(r.sup()), t) : ht(e.sub, t.havingStyle(r.sub()), t), o = qt(e.base, "horizBrace")) : o = qt(e, "horizBrace"); const s = ht(o.base, t.havingBaseStyle(w.DISPLAY)), i = Nt(o, t); let a; if (o.isOver ? (a = Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: s }, { type: "kern", size: .1 }, { type: "elem", elem: i }] }, t), a.children[0].children[0].children[1].classes.push("svg-align")) : (a = Ve.makeVList({ positionType: "bottom", positionData: s.depth + .1 + i.height, children: [{ type: "elem", elem: i }, { type: "kern", size: .1 }, { type: "elem", elem: s }] }, t), a.children[0].children[0].children[0].classes.push("svg-align")), n) { const e = Ve.makeSpan(["mord", o.isOver ? "mover" : "munder"], [a], t); a = o.isOver ? Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: e }, { type: "kern", size: .2 }, { type: "elem", elem: n }] }, t) : Ve.makeVList({ positionType: "bottom", positionData: e.depth + .2 + n.height + n.depth, children: [{ type: "elem", elem: n }, { type: "kern", size: .2 }, { type: "elem", elem: e }] }, t) } return Ve.makeSpan(["mord", o.isOver ? "mover" : "munder"], [a], t) }; je({ type: "horizBrace", names: ["\\overbrace", "\\underbrace"], props: { numArgs: 1 }, handler(e, t) { let { parser: r, funcName: n } = e; return { type: "horizBrace", mode: r.mode, label: n, isOver: /^\\over/.test(n), base: t[0] } }, htmlBuilder: $r, mathmlBuilder: (e, t) => { const r = Ct(e.label); return new gt.MathNode(e.isOver ? "mover" : "munder", [vt(e.base, t), r]) } }), je({ type: "href", names: ["\\href"], props: { numArgs: 2, argTypes: ["url", "original"], allowedInText: !0 }, handler: (e, t) => { let { parser: r } = e; const n = t[1], o = qt(t[0], "url").url; return r.settings.isTrusted({ command: "\\href", url: o }) ? { type: "href", mode: r.mode, href: o, body: Ke(n) } : r.formatUnsupportedCmd("\\href") }, htmlBuilder: (e, t) => { const r = nt(e.body, t, !1); return Ve.makeAnchor(e.href, [], r, t) }, mathmlBuilder: (e, t) => { let r = wt(e.body, t); return r instanceof ut || (r = new ut("mrow", [r])), r.setAttribute("href", e.href), r } }), je({ type: "href", names: ["\\url"], props: { numArgs: 1, argTypes: ["url"], allowedInText: !0 }, handler: (e, t) => { let { parser: r } = e; const n = qt(t[0], "url").url; if (!r.settings.isTrusted({ command: "\\url", url: n })) return r.formatUnsupportedCmd("\\url"); const o = []; for (let e = 0; e < n.length; e++) { let t = n[e]; "~" === t && (t = "\\textasciitilde"), o.push({ type: "textord", mode: "text", text: t }) } const s = { type: "text", mode: r.mode, font: "\\texttt", body: o }; return { type: "href", mode: r.mode, href: n, body: Ke(s) } } }), je({ type: "hbox", names: ["\\hbox"], props: { numArgs: 1, argTypes: ["text"], allowedInText: !0, primitive: !0 }, handler(e, t) { let { parser: r } = e; return { type: "hbox", mode: r.mode, body: Ke(t[0]) } }, htmlBuilder(e, t) { const r = nt(e.body, t, !1); return Ve.makeFragment(r) }, mathmlBuilder(e, t) { return new gt.MathNode("mrow", xt(e.body, t)) } }), je({ type: "html", names: ["\\htmlClass", "\\htmlId", "\\htmlStyle", "\\htmlData"], props: { numArgs: 2, argTypes: ["raw", "original"], allowedInText: !0 }, handler: (e, t) => { let { parser: r, funcName: o, token: s } = e; const i = qt(t[0], "raw").string, a = t[1]; let l; r.settings.strict && r.settings.reportNonstrict("htmlExtension", "HTML extension is disabled on strict mode"); const h = {}; switch (o) { case "\\htmlClass": h.class = i, l = { command: "\\htmlClass", class: i }; break; case "\\htmlId": h.id = i, l = { command: "\\htmlId", id: i }; break; case "\\htmlStyle": h.style = i, l = { command: "\\htmlStyle", style: i }; break; case "\\htmlData": { const e = i.split(","); for (let t = 0; t < e.length; t++) { const r = e[t].split("="); if (2 !== r.length) throw new n("Error parsing key-value for \\htmlData"); h["data-" + r[0].trim()] = r[1].trim() } l = { command: "\\htmlData", attributes: h }; break } default: throw new Error("Unrecognized html command") }return r.settings.isTrusted(l) ? { type: "html", mode: r.mode, attributes: h, body: Ke(a) } : r.formatUnsupportedCmd(o) }, htmlBuilder: (e, t) => { const r = nt(e.body, t, !1), n = ["enclosing"]; e.attributes.class && n.push(...e.attributes.class.trim().split(/\s+/)); const o = Ve.makeSpan(n, r, t); for (const t in e.attributes) "class" !== t && e.attributes.hasOwnProperty(t) && o.setAttribute(t, e.attributes[t]); return o }, mathmlBuilder: (e, t) => wt(e.body, t) }), je({ type: "htmlmathml", names: ["\\html@mathml"], props: { numArgs: 2, allowedInText: !0 }, handler: (e, t) => { let { parser: r } = e; return { type: "htmlmathml", mode: r.mode, html: Ke(t[0]), mathml: Ke(t[1]) } }, htmlBuilder: (e, t) => { const r = nt(e.html, t, !1); return Ve.makeFragment(r) }, mathmlBuilder: (e, t) => wt(e.mathml, t) }); const Zr = function (e) { if (/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(e)) return { number: +e, unit: "bp" }; { const t = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(e); if (!t) throw new n("Invalid size: '" + e + "' in \\includegraphics"); const r = { number: +(t[1] + t[2]), unit: t[3] }; if (!V(r)) throw new n("Invalid unit: '" + r.unit + "' in \\includegraphics."); return r } }; je({ type: "includegraphics", names: ["\\includegraphics"], props: { numArgs: 1, numOptionalArgs: 1, argTypes: ["raw", "url"], allowedInText: !1 }, handler: (e, t, r) => { let { parser: o } = e, s = { number: 0, unit: "em" }, i = { number: .9, unit: "em" }, a = { number: 0, unit: "em" }, l = ""; if (r[0]) { const e = qt(r[0], "raw").string.split(","); for (let t = 0; t < e.length; t++) { const r = e[t].split("="); if (2 === r.length) { const e = r[1].trim(); switch (r[0].trim()) { case "alt": l = e; break; case "width": s = Zr(e); break; case "height": i = Zr(e); break; case "totalheight": a = Zr(e); break; default: throw new n("Invalid key: '" + r[0] + "' in \\includegraphics.") } } } } const h = qt(t[0], "url").url; return "" === l && (l = h, l = l.replace(/^.*[\\/]/, ""), l = l.substring(0, l.lastIndexOf("."))), o.settings.isTrusted({ command: "\\includegraphics", url: h }) ? { type: "includegraphics", mode: o.mode, alt: l, width: s, height: i, totalheight: a, src: h } : o.formatUnsupportedCmd("\\includegraphics") }, htmlBuilder: (e, t) => { const r = P(e.height, t); let n = 0; e.totalheight.number > 0 && (n = P(e.totalheight, t) - r); let o = 0; e.width.number > 0 && (o = P(e.width, t)); const s = { height: F(r + n) }; o > 0 && (s.width = F(o)), n > 0 && (s.verticalAlign = F(-n)); const i = new j(e.src, e.alt, s); return i.height = r, i.depth = n, i }, mathmlBuilder: (e, t) => { const r = new gt.MathNode("mglyph", []); r.setAttribute("alt", e.alt); const n = P(e.height, t); let o = 0; if (e.totalheight.number > 0 && (o = P(e.totalheight, t) - n, r.setAttribute("valign", F(-o))), r.setAttribute("height", F(n + o)), e.width.number > 0) { const n = P(e.width, t); r.setAttribute("width", F(n)) } return r.setAttribute("src", e.src), r } }), je({ type: "kern", names: ["\\kern", "\\mkern", "\\hskip", "\\mskip"], props: { numArgs: 1, argTypes: ["size"], primitive: !0, allowedInText: !0 }, handler(e, t) { let { parser: r, funcName: n } = e; const o = qt(t[0], "size"); if (r.settings.strict) { const e = "m" === n[1], t = "mu" === o.value.unit; e ? (t || r.settings.reportNonstrict("mathVsTextUnits", "LaTeX's " + n + " supports only mu units, not " + o.value.unit + " units"), "math" !== r.mode && r.settings.reportNonstrict("mathVsTextUnits", "LaTeX's " + n + " works only in math mode")) : t && r.settings.reportNonstrict("mathVsTextUnits", "LaTeX's " + n + " doesn't support mu units") } return { type: "kern", mode: r.mode, dimension: o.value } }, htmlBuilder(e, t) { return Ve.makeGlue(e.dimension, t) }, mathmlBuilder(e, t) { const r = P(e.dimension, t); return new gt.SpaceNode(r) } }), je({ type: "lap", names: ["\\mathllap", "\\mathrlap", "\\mathclap"], props: { numArgs: 1, allowedInText: !0 }, handler: (e, t) => { let { parser: r, funcName: n } = e; const o = t[0]; return { type: "lap", mode: r.mode, alignment: n.slice(5), body: o } }, htmlBuilder: (e, t) => { let r; "clap" === e.alignment ? (r = Ve.makeSpan([], [ht(e.body, t)]), r = Ve.makeSpan(["inner"], [r], t)) : r = Ve.makeSpan(["inner"], [ht(e.body, t)]); const n = Ve.makeSpan(["fix"], []); let o = Ve.makeSpan([e.alignment], [r, n], t); const s = Ve.makeSpan(["strut"]); return s.style.height = F(o.height + o.depth), o.depth && (s.style.verticalAlign = F(-o.depth)), o.children.unshift(s), o = Ve.makeSpan(["thinbox"], [o], t), Ve.makeSpan(["mord", "vbox"], [o], t) }, mathmlBuilder: (e, t) => { const r = new gt.MathNode("mpadded", [vt(e.body, t)]); if ("rlap" !== e.alignment) { const t = "llap" === e.alignment ? "-1" : "-0.5"; r.setAttribute("lspace", t + "width") } return r.setAttribute("width", "0px"), r } }), je({ type: "styling", names: ["\\(", "$"], props: { numArgs: 0, allowedInText: !0, allowedInMath: !1 }, handler(e, t) { let { funcName: r, parser: n } = e; const o = n.mode; n.switchMode("math"); const s = "\\(" === r ? "\\)" : "$", i = n.parseExpression(!1, s); return n.expect(s), n.switchMode(o), { type: "styling", mode: n.mode, style: "text", body: i } } }), je({ type: "text", names: ["\\)", "\\]"], props: { numArgs: 0, allowedInText: !0, allowedInMath: !1 }, handler(e, t) { throw new n("Mismatched " + e.funcName) } }); const Kr = (e, t) => { switch (t.style.size) { case w.DISPLAY.size: return e.display; case w.TEXT.size: return e.text; case w.SCRIPT.size: return e.script; case w.SCRIPTSCRIPT.size: return e.scriptscript; default: return e.text } }; je({ type: "mathchoice", names: ["\\mathchoice"], props: { numArgs: 4, primitive: !0 }, handler: (e, t) => { let { parser: r } = e; return { type: "mathchoice", mode: r.mode, display: Ke(t[0]), text: Ke(t[1]), script: Ke(t[2]), scriptscript: Ke(t[3]) } }, htmlBuilder: (e, t) => { const r = Kr(e, t), n = nt(r, t, !1); return Ve.makeFragment(n) }, mathmlBuilder: (e, t) => { const r = Kr(e, t); return wt(r, t) } }); const Jr = (e, t, r, n, o, s, i) => { e = Ve.makeSpan([], [e]); const a = r && l.isCharacterBox(r); let h, c, m; if (t) { const e = ht(t, n.havingStyle(o.sup()), n); c = { elem: e, kern: Math.max(n.fontMetrics().bigOpSpacing1, n.fontMetrics().bigOpSpacing3 - e.depth) } } if (r) { const e = ht(r, n.havingStyle(o.sub()), n); h = { elem: e, kern: Math.max(n.fontMetrics().bigOpSpacing2, n.fontMetrics().bigOpSpacing4 - e.height) } } if (c && h) { const t = n.fontMetrics().bigOpSpacing5 + h.elem.height + h.elem.depth + h.kern + e.depth + i; m = Ve.makeVList({ positionType: "bottom", positionData: t, children: [{ type: "kern", size: n.fontMetrics().bigOpSpacing5 }, { type: "elem", elem: h.elem, marginLeft: F(-s) }, { type: "kern", size: h.kern }, { type: "elem", elem: e }, { type: "kern", size: c.kern }, { type: "elem", elem: c.elem, marginLeft: F(s) }, { type: "kern", size: n.fontMetrics().bigOpSpacing5 }] }, n) } else if (h) { const t = e.height - i; m = Ve.makeVList({ positionType: "top", positionData: t, children: [{ type: "kern", size: n.fontMetrics().bigOpSpacing5 }, { type: "elem", elem: h.elem, marginLeft: F(-s) }, { type: "kern", size: h.kern }, { type: "elem", elem: e }] }, n) } else { if (!c) return e; { const t = e.depth + i; m = Ve.makeVList({ positionType: "bottom", positionData: t, children: [{ type: "elem", elem: e }, { type: "kern", size: c.kern }, { type: "elem", elem: c.elem, marginLeft: F(s) }, { type: "kern", size: n.fontMetrics().bigOpSpacing5 }] }, n) } } const p = [m]; if (h && 0 !== s && !a) { const e = Ve.makeSpan(["mspace"], [], n); e.style.marginRight = F(s), p.unshift(e) } return Ve.makeSpan(["mop", "op-limits"], p, n) }, Qr = ["\\smallint"], en = (e, t) => { let r, n, o, s = !1; "supsub" === e.type ? (r = e.sup, n = e.sub, o = qt(e.base, "op"), s = !0) : o = qt(e, "op"); const i = t.style; let a, h = !1; if (i.size === w.DISPLAY.size && o.symbol && !l.contains(Qr, o.name) && (h = !0), o.symbol) { const e = h ? "Size2-Regular" : "Size1-Regular"; let r = ""; if ("\\oiint" !== o.name && "\\oiiint" !== o.name || (r = o.name.slice(1), o.name = "oiint" === r ? "\\iint" : "\\iiint"), a = Ve.makeSymbol(o.name, e, "math", t, ["mop", "op-symbol", h ? "large-op" : "small-op"]), r.length > 0) { const e = a.italic, n = Ve.staticSvg(r + "Size" + (h ? "2" : "1"), t); a = Ve.makeVList({ positionType: "individualShift", children: [{ type: "elem", elem: a, shift: 0 }, { type: "elem", elem: n, shift: h ? .08 : 0 }] }, t), o.name = "\\" + r, a.classes.unshift("mop"), a.italic = e } } else if (o.body) { const e = nt(o.body, t, !0); 1 === e.length && e[0] instanceof Z ? (a = e[0], a.classes[0] = "mop") : a = Ve.makeSpan(["mop"], e, t) } else { const e = []; for (let r = 1; r < o.name.length; r++)e.push(Ve.mathsym(o.name[r], o.mode, t)); a = Ve.makeSpan(["mop"], e, t) } let c = 0, m = 0; return (a instanceof Z || "\\oiint" === o.name || "\\oiiint" === o.name) && !o.suppressBaseShift && (c = (a.height - a.depth) / 2 - t.fontMetrics().axisHeight, m = a.italic), s ? Jr(a, r, n, t, i, m, c) : (c && (a.style.position = "relative", a.style.top = F(c)), a) }, tn = (e, t) => { let r; if (e.symbol) r = new ut("mo", [ft(e.name, e.mode)]), l.contains(Qr, e.name) && r.setAttribute("largeop", "false"); else if (e.body) r = new ut("mo", xt(e.body, t)); else { r = new ut("mi", [new dt(e.name.slice(1))]); const t = new ut("mo", [ft("\u2061", "text")]); r = e.parentIsSupSub ? new ut("mrow", [r, t]) : pt([r, t]) } return r }, rn = { "\u220f": "\\prod", "\u2210": "\\coprod", "\u2211": "\\sum", "\u22c0": "\\bigwedge", "\u22c1": "\\bigvee", "\u22c2": "\\bigcap", "\u22c3": "\\bigcup", "\u2a00": "\\bigodot", "\u2a01": "\\bigoplus", "\u2a02": "\\bigotimes", "\u2a04": "\\biguplus", "\u2a06": "\\bigsqcup" }; je({ type: "op", names: ["\\coprod", "\\bigvee", "\\bigwedge", "\\biguplus", "\\bigcap", "\\bigcup", "\\intop", "\\prod", "\\sum", "\\bigotimes", "\\bigoplus", "\\bigodot", "\\bigsqcup", "\\smallint", "\u220f", "\u2210", "\u2211", "\u22c0", "\u22c1", "\u22c2", "\u22c3", "\u2a00", "\u2a01", "\u2a02", "\u2a04", "\u2a06"], props: { numArgs: 0 }, handler: (e, t) => { let { parser: r, funcName: n } = e, o = n; return 1 === o.length && (o = rn[o]), { type: "op", mode: r.mode, limits: !0, parentIsSupSub: !1, symbol: !0, name: o } }, htmlBuilder: en, mathmlBuilder: tn }), je({ type: "op", names: ["\\mathop"], props: { numArgs: 1, primitive: !0 }, handler: (e, t) => { let { parser: r } = e; const n = t[0]; return { type: "op", mode: r.mode, limits: !1, parentIsSupSub: !1, symbol: !1, body: Ke(n) } }, htmlBuilder: en, mathmlBuilder: tn }); const nn = { "\u222b": "\\int", "\u222c": "\\iint", "\u222d": "\\iiint", "\u222e": "\\oint", "\u222f": "\\oiint", "\u2230": "\\oiiint" }; je({ type: "op", names: ["\\arcsin", "\\arccos", "\\arctan", "\\arctg", "\\arcctg", "\\arg", "\\ch", "\\cos", "\\cosec", "\\cosh", "\\cot", "\\cotg", "\\coth", "\\csc", "\\ctg", "\\cth", "\\deg", "\\dim", "\\exp", "\\hom", "\\ker", "\\lg", "\\ln", "\\log", "\\sec", "\\sin", "\\sinh", "\\sh", "\\tan", "\\tanh", "\\tg", "\\th"], props: { numArgs: 0 }, handler(e) { let { parser: t, funcName: r } = e; return { type: "op", mode: t.mode, limits: !1, parentIsSupSub: !1, symbol: !1, name: r } }, htmlBuilder: en, mathmlBuilder: tn }), je({ type: "op", names: ["\\det", "\\gcd", "\\inf", "\\lim", "\\max", "\\min", "\\Pr", "\\sup"], props: { numArgs: 0 }, handler(e) { let { parser: t, funcName: r } = e; return { type: "op", mode: t.mode, limits: !0, parentIsSupSub: !1, symbol: !1, name: r } }, htmlBuilder: en, mathmlBuilder: tn }), je({ type: "op", names: ["\\int", "\\iint", "\\iiint", "\\oint", "\\oiint", "\\oiiint", "\u222b", "\u222c", "\u222d", "\u222e", "\u222f", "\u2230"], props: { numArgs: 0 }, handler(e) { let { parser: t, funcName: r } = e, n = r; return 1 === n.length && (n = nn[n]), { type: "op", mode: t.mode, limits: !1, parentIsSupSub: !1, symbol: !0, name: n } }, htmlBuilder: en, mathmlBuilder: tn }); const on = (e, t) => { let r, n, o, s, i = !1; if ("supsub" === e.type ? (r = e.sup, n = e.sub, o = qt(e.base, "operatorname"), i = !0) : o = qt(e, "operatorname"), o.body.length > 0) { const e = o.body.map((e => { const t = e.text; return "string" == typeof t ? { type: "textord", mode: e.mode, text: t } : e })), r = nt(e, t.withFont("mathrm"), !0); for (let e = 0; e < r.length; e++) { const t = r[e]; t instanceof Z && (t.text = t.text.replace(/\u2212/, "-").replace(/\u2217/, "*")) } s = Ve.makeSpan(["mop"], r, t) } else s = Ve.makeSpan(["mop"], [], t); return i ? Jr(s, r, n, t, t.style, 0, 0) : s }; function sn(e, t, r) { const n = nt(e, t, !1), o = t.sizeMultiplier / r.sizeMultiplier; for (let e = 0; e < n.length; e++) { const s = n[e].classes.indexOf("sizing"); s < 0 ? Array.prototype.push.apply(n[e].classes, t.sizingClasses(r)) : n[e].classes[s + 1] === "reset-size" + t.size && (n[e].classes[s + 1] = "reset-size" + r.size), n[e].height *= o, n[e].depth *= o } return Ve.makeFragment(n) } je({ type: "operatorname", names: ["\\operatorname@", "\\operatornamewithlimits"], props: { numArgs: 1 }, handler: (e, t) => { let { parser: r, funcName: n } = e; const o = t[0]; return { type: "operatorname", mode: r.mode, body: Ke(o), alwaysHandleSupSub: "\\operatornamewithlimits" === n, limits: !1, parentIsSupSub: !1 } }, htmlBuilder: on, mathmlBuilder: (e, t) => { let r = xt(e.body, t.withFont("mathrm")), n = !0; for (let e = 0; e < r.length; e++) { const t = r[e]; if (t instanceof gt.SpaceNode); else if (t instanceof gt.MathNode) switch (t.type) { case "mi": case "mn": case "ms": case "mspace": case "mtext": break; case "mo": { const e = t.children[0]; 1 === t.children.length && e instanceof gt.TextNode ? e.text = e.text.replace(/\u2212/, "-").replace(/\u2217/, "*") : n = !1; break } default: n = !1 } else n = !1 } if (n) { const e = r.map((e => e.toText())).join(""); r = [new gt.TextNode(e)] } const o = new gt.MathNode("mi", r); o.setAttribute("mathvariant", "normal"); const s = new gt.MathNode("mo", [ft("\u2061", "text")]); return e.parentIsSupSub ? new gt.MathNode("mrow", [o, s]) : gt.newDocumentFragment([o, s]) } }), Br("\\operatorname", "\\@ifstar\\operatornamewithlimits\\operatorname@"), $e({ type: "ordgroup", htmlBuilder(e, t) { return e.semisimple ? Ve.makeFragment(nt(e.body, t, !1)) : Ve.makeSpan(["mord"], nt(e.body, t, !0), t) }, mathmlBuilder(e, t) { return wt(e.body, t, !0) } }), je({ type: "overline", names: ["\\overline"], props: { numArgs: 1 }, handler(e, t) { let { parser: r } = e; const n = t[0]; return { type: "overline", mode: r.mode, body: n } }, htmlBuilder(e, t) { const r = ht(e.body, t.havingCrampedStyle()), n = Ve.makeLineSpan("overline-line", t), o = t.fontMetrics().defaultRuleThickness, s = Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: r }, { type: "kern", size: 3 * o }, { type: "elem", elem: n }, { type: "kern", size: o }] }, t); return Ve.makeSpan(["mord", "overline"], [s], t) }, mathmlBuilder(e, t) { const r = new gt.MathNode("mo", [new gt.TextNode("\u203e")]); r.setAttribute("stretchy", "true"); const n = new gt.MathNode("mover", [vt(e.body, t), r]); return n.setAttribute("accent", "true"), n } }), je({ type: "phantom", names: ["\\phantom"], props: { numArgs: 1, allowedInText: !0 }, handler: (e, t) => { let { parser: r } = e; const n = t[0]; return { type: "phantom", mode: r.mode, body: Ke(n) } }, htmlBuilder: (e, t) => { const r = nt(e.body, t.withPhantom(), !1); return Ve.makeFragment(r) }, mathmlBuilder: (e, t) => { const r = xt(e.body, t); return new gt.MathNode("mphantom", r) } }), je({ type: "hphantom", names: ["\\hphantom"], props: { numArgs: 1, allowedInText: !0 }, handler: (e, t) => { let { parser: r } = e; const n = t[0]; return { type: "hphantom", mode: r.mode, body: n } }, htmlBuilder: (e, t) => { let r = Ve.makeSpan([], [ht(e.body, t.withPhantom())]); if (r.height = 0, r.depth = 0, r.children) for (let e = 0; e < r.children.length; e++)r.children[e].height = 0, r.children[e].depth = 0; return r = Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: r }] }, t), Ve.makeSpan(["mord"], [r], t) }, mathmlBuilder: (e, t) => { const r = xt(Ke(e.body), t), n = new gt.MathNode("mphantom", r), o = new gt.MathNode("mpadded", [n]); return o.setAttribute("height", "0px"), o.setAttribute("depth", "0px"), o } }), je({ type: "vphantom", names: ["\\vphantom"], props: { numArgs: 1, allowedInText: !0 }, handler: (e, t) => { let { parser: r } = e; const n = t[0]; return { type: "vphantom", mode: r.mode, body: n } }, htmlBuilder: (e, t) => { const r = Ve.makeSpan(["inner"], [ht(e.body, t.withPhantom())]), n = Ve.makeSpan(["fix"], []); return Ve.makeSpan(["mord", "rlap"], [r, n], t) }, mathmlBuilder: (e, t) => { const r = xt(Ke(e.body), t), n = new gt.MathNode("mphantom", r), o = new gt.MathNode("mpadded", [n]); return o.setAttribute("width", "0px"), o } }), je({ type: "raisebox", names: ["\\raisebox"], props: { numArgs: 2, argTypes: ["size", "hbox"], allowedInText: !0 }, handler(e, t) { let { parser: r } = e; const n = qt(t[0], "size").value, o = t[1]; return { type: "raisebox", mode: r.mode, dy: n, body: o } }, htmlBuilder(e, t) { const r = ht(e.body, t), n = P(e.dy, t); return Ve.makeVList({ positionType: "shift", positionData: -n, children: [{ type: "elem", elem: r }] }, t) }, mathmlBuilder(e, t) { const r = new gt.MathNode("mpadded", [vt(e.body, t)]), n = e.dy.number + e.dy.unit; return r.setAttribute("voffset", n), r } }), je({ type: "internal", names: ["\\relax"], props: { numArgs: 0, allowedInText: !0 }, handler(e) { let { parser: t } = e; return { type: "internal", mode: t.mode } } }), je({ type: "rule", names: ["\\rule"], props: { numArgs: 2, numOptionalArgs: 1, argTypes: ["size", "size", "size"] }, handler(e, t, r) { let { parser: n } = e; const o = r[0], s = qt(t[0], "size"), i = qt(t[1], "size"); return { type: "rule", mode: n.mode, shift: o && qt(o, "size").value, width: s.value, height: i.value } }, htmlBuilder(e, t) { const r = Ve.makeSpan(["mord", "rule"], [], t), n = P(e.width, t), o = P(e.height, t), s = e.shift ? P(e.shift, t) : 0; return r.style.borderRightWidth = F(n), r.style.borderTopWidth = F(o), r.style.bottom = F(s), r.width = n, r.height = o + s, r.depth = -s, r.maxFontSize = 1.125 * o * t.sizeMultiplier, r }, mathmlBuilder(e, t) { const r = P(e.width, t), n = P(e.height, t), o = e.shift ? P(e.shift, t) : 0, s = t.color && t.getColor() || "black", i = new gt.MathNode("mspace"); i.setAttribute("mathbackground", s), i.setAttribute("width", F(r)), i.setAttribute("height", F(n)); const a = new gt.MathNode("mpadded", [i]); return o >= 0 ? a.setAttribute("height", F(o)) : (a.setAttribute("height", F(o)), a.setAttribute("depth", F(-o))), a.setAttribute("voffset", F(o)), a } }); const an = ["\\tiny", "\\sixptsize", "\\scriptsize", "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large", "\\LARGE", "\\huge", "\\Huge"]; je({ type: "sizing", names: an, props: { numArgs: 0, allowedInText: !0 }, handler: (e, t) => { let { breakOnTokenText: r, funcName: n, parser: o } = e; const s = o.parseExpression(!1, r); return { type: "sizing", mode: o.mode, size: an.indexOf(n) + 1, body: s } }, htmlBuilder: (e, t) => { const r = t.havingSize(e.size); return sn(e.body, r, t) }, mathmlBuilder: (e, t) => { const r = t.havingSize(e.size), n = xt(e.body, r), o = new gt.MathNode("mstyle", n); return o.setAttribute("mathsize", F(r.sizeMultiplier)), o } }), je({ type: "smash", names: ["\\smash"], props: { numArgs: 1, numOptionalArgs: 1, allowedInText: !0 }, handler: (e, t, r) => { let { parser: n } = e, o = !1, s = !1; const i = r[0] && qt(r[0], "ordgroup"); if (i) { let e = ""; for (let t = 0; t < i.body.length; ++t) { if (e = i.body[t].text, "t" === e) o = !0; else { if ("b" !== e) { o = !1, s = !1; break } s = !0 } } } else o = !0, s = !0; const a = t[0]; return { type: "smash", mode: n.mode, body: a, smashHeight: o, smashDepth: s } }, htmlBuilder: (e, t) => { const r = Ve.makeSpan([], [ht(e.body, t)]); if (!e.smashHeight && !e.smashDepth) return r; if (e.smashHeight && (r.height = 0, r.children)) for (let e = 0; e < r.children.length; e++)r.children[e].height = 0; if (e.smashDepth && (r.depth = 0, r.children)) for (let e = 0; e < r.children.length; e++)r.children[e].depth = 0; const n = Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: r }] }, t); return Ve.makeSpan(["mord"], [n], t) }, mathmlBuilder: (e, t) => { const r = new gt.MathNode("mpadded", [vt(e.body, t)]); return e.smashHeight && r.setAttribute("height", "0px"), e.smashDepth && r.setAttribute("depth", "0px"), r } }), je({ type: "sqrt", names: ["\\sqrt"], props: { numArgs: 1, numOptionalArgs: 1 }, handler(e, t, r) { let { parser: n } = e; const o = r[0], s = t[0]; return { type: "sqrt", mode: n.mode, body: s, index: o } }, htmlBuilder(e, t) { let r = ht(e.body, t.havingCrampedStyle()); 0 === r.height && (r.height = t.fontMetrics().xHeight), r = Ve.wrapFragment(r, t); const n = t.fontMetrics().defaultRuleThickness; let o = n; t.style.id < w.TEXT.id && (o = t.fontMetrics().xHeight); let s = n + o / 4; const i = r.height + r.depth + s + n, { span: a, ruleWidth: l, advanceWidth: h } = yr.sqrtImage(i, t), c = a.height - l; c > r.height + r.depth + s && (s = (s + c - r.height - r.depth) / 2); const m = a.height - r.height - s - l; r.style.paddingLeft = F(h); const p = Ve.makeVList({ positionType: "firstBaseline", children: [{ type: "elem", elem: r, wrapperClasses: ["svg-align"] }, { type: "kern", size: -(r.height + m) }, { type: "elem", elem: a }, { type: "kern", size: l }] }, t); if (e.index) { const r = t.havingStyle(w.SCRIPTSCRIPT), n = ht(e.index, r, t), o = .6 * (p.height - p.depth), s = Ve.makeVList({ positionType: "shift", positionData: -o, children: [{ type: "elem", elem: n }] }, t), i = Ve.makeSpan(["root"], [s]); return Ve.makeSpan(["mord", "sqrt"], [i, p], t) } return Ve.makeSpan(["mord", "sqrt"], [p], t) }, mathmlBuilder(e, t) { const { body: r, index: n } = e; return n ? new gt.MathNode("mroot", [vt(r, t), vt(n, t)]) : new gt.MathNode("msqrt", [vt(r, t)]) } }); const ln = { display: w.DISPLAY, text: w.TEXT, script: w.SCRIPT, scriptscript: w.SCRIPTSCRIPT }; je({ type: "styling", names: ["\\displaystyle", "\\textstyle", "\\scriptstyle", "\\scriptscriptstyle"], props: { numArgs: 0, allowedInText: !0, primitive: !0 }, handler(e, t) { let { breakOnTokenText: r, funcName: n, parser: o } = e; const s = o.parseExpression(!0, r), i = n.slice(1, n.length - 5); return { type: "styling", mode: o.mode, style: i, body: s } }, htmlBuilder(e, t) { const r = ln[e.style], n = t.havingStyle(r).withFont(""); return sn(e.body, n, t) }, mathmlBuilder(e, t) { const r = ln[e.style], n = t.havingStyle(r), o = xt(e.body, n), s = new gt.MathNode("mstyle", o), i = { display: ["0", "true"], text: ["0", "false"], script: ["1", "false"], scriptscript: ["2", "false"] }[e.style]; return s.setAttribute("scriptlevel", i[0]), s.setAttribute("displaystyle", i[1]), s } }); $e({ type: "supsub", htmlBuilder(e, t) { const r = function (e, t) { const r = e.base; if (r) return "op" === r.type ? r.limits && (t.style.size === w.DISPLAY.size || r.alwaysHandleSupSub) ? en : null : "operatorname" === r.type ? r.alwaysHandleSupSub && (t.style.size === w.DISPLAY.size || r.limits) ? on : null : "accent" === r.type ? l.isCharacterBox(r.base) ? Ht : null : "horizBrace" === r.type && !e.sub === r.isOver ? $r : null; return null }(e, t); if (r) return r(e, t); const { base: n, sup: o, sub: s } = e, i = ht(n, t); let a, h; const c = t.fontMetrics(); let m = 0, p = 0; const u = n && l.isCharacterBox(n); if (o) { const e = t.havingStyle(t.style.sup()); a = ht(o, e, t), u || (m = i.height - e.fontMetrics().supDrop * e.sizeMultiplier / t.sizeMultiplier) } if (s) { const e = t.havingStyle(t.style.sub()); h = ht(s, e, t), u || (p = i.depth + e.fontMetrics().subDrop * e.sizeMultiplier / t.sizeMultiplier) } let d; d = t.style === w.DISPLAY ? c.sup1 : t.style.cramped ? c.sup3 : c.sup2; const g = t.sizeMultiplier, f = F(.5 / c.ptPerEm / g); let b, y = null; if (h) { const t = e.base && "op" === e.base.type && e.base.name && ("\\oiint" === e.base.name || "\\oiiint" === e.base.name); (i instanceof Z || t) && (y = F(-i.italic)) } if (a && h) { m = Math.max(m, d, a.depth + .25 * c.xHeight), p = Math.max(p, c.sub2); const e = 4 * c.defaultRuleThickness; if (m - a.depth - (h.height - p) < e) { p = e - (m - a.depth) + h.height; const t = .8 * c.xHeight - (m - a.depth); t > 0 && (m += t, p -= t) } const r = [{ type: "elem", elem: h, shift: p, marginRight: f, marginLeft: y }, { type: "elem", elem: a, shift: -m, marginRight: f }]; b = Ve.makeVList({ positionType: "individualShift", children: r }, t) } else if (h) { p = Math.max(p, c.sub1, h.height - .8 * c.xHeight); const e = [{ type: "elem", elem: h, marginLeft: y, marginRight: f }]; b = Ve.makeVList({ positionType: "shift", positionData: p, children: e }, t) } else { if (!a) throw new Error("supsub must have either sup or sub."); m = Math.max(m, d, a.depth + .25 * c.xHeight), b = Ve.makeVList({ positionType: "shift", positionData: -m, children: [{ type: "elem", elem: a, marginRight: f }] }, t) } const x = at(i, "right") || "mord"; return Ve.makeSpan([x], [i, Ve.makeSpan(["msupsub"], [b])], t) }, mathmlBuilder(e, t) { let r, n, o = !1; e.base && "horizBrace" === e.base.type && (n = !!e.sup, n === e.base.isOver && (o = !0, r = e.base.isOver)), !e.base || "op" !== e.base.type && "operatorname" !== e.base.type || (e.base.parentIsSupSub = !0); const s = [vt(e.base, t)]; let i; if (e.sub && s.push(vt(e.sub, t)), e.sup && s.push(vt(e.sup, t)), o) i = r ? "mover" : "munder"; else if (e.sub) if (e.sup) { const r = e.base; i = r && "op" === r.type && r.limits && t.style === w.DISPLAY || r && "operatorname" === r.type && r.alwaysHandleSupSub && (t.style === w.DISPLAY || r.limits) ? "munderover" : "msubsup" } else { const r = e.base; i = r && "op" === r.type && r.limits && (t.style === w.DISPLAY || r.alwaysHandleSupSub) || r && "operatorname" === r.type && r.alwaysHandleSupSub && (r.limits || t.style === w.DISPLAY) ? "munder" : "msub" } else { const r = e.base; i = r && "op" === r.type && r.limits && (t.style === w.DISPLAY || r.alwaysHandleSupSub) || r && "operatorname" === r.type && r.alwaysHandleSupSub && (r.limits || t.style === w.DISPLAY) ? "mover" : "msup" } return new gt.MathNode(i, s) } }), $e({ type: "atom", htmlBuilder(e, t) { return Ve.mathsym(e.text, e.mode, t, ["m" + e.family]) }, mathmlBuilder(e, t) { const r = new gt.MathNode("mo", [ft(e.text, e.mode)]); if ("bin" === e.family) { const n = yt(e, t); "bold-italic" === n && r.setAttribute("mathvariant", n) } else "punct" === e.family ? r.setAttribute("separator", "true") : "open" !== e.family && "close" !== e.family || r.setAttribute("stretchy", "false"); return r } }); const hn = { mi: "italic", mn: "normal", mtext: "normal" }; $e({ type: "mathord", htmlBuilder(e, t) { return Ve.makeOrd(e, t, "mathord") }, mathmlBuilder(e, t) { const r = new gt.MathNode("mi", [ft(e.text, e.mode, t)]), n = yt(e, t) || "italic"; return n !== hn[r.type] && r.setAttribute("mathvariant", n), r } }), $e({ type: "textord", htmlBuilder(e, t) { return Ve.makeOrd(e, t, "textord") }, mathmlBuilder(e, t) { const r = ft(e.text, e.mode, t), n = yt(e, t) || "normal"; let o; return o = "text" === e.mode ? new gt.MathNode("mtext", [r]) : /[0-9]/.test(e.text) ? new gt.MathNode("mn", [r]) : "\\prime" === e.text ? new gt.MathNode("mo", [r]) : new gt.MathNode("mi", [r]), n !== hn[o.type] && o.setAttribute("mathvariant", n), o } }); const cn = { "\\nobreak": "nobreak", "\\allowbreak": "allowbreak" }, mn = { " ": {}, "\\ ": {}, "~": { className: "nobreak" }, "\\space": {}, "\\nobreakspace": { className: "nobreak" } }; $e({ type: "spacing", htmlBuilder(e, t) { if (mn.hasOwnProperty(e.text)) { const r = mn[e.text].className || ""; if ("text" === e.mode) { const n = Ve.makeOrd(e, t, "textord"); return n.classes.push(r), n } return Ve.makeSpan(["mspace", r], [Ve.mathsym(e.text, e.mode, t)], t) } if (cn.hasOwnProperty(e.text)) return Ve.makeSpan(["mspace", cn[e.text]], [], t); throw new n('Unknown type of space "' + e.text + '"') }, mathmlBuilder(e, t) { let r; if (!mn.hasOwnProperty(e.text)) { if (cn.hasOwnProperty(e.text)) return new gt.MathNode("mspace"); throw new n('Unknown type of space "' + e.text + '"') } return r = new gt.MathNode("mtext", [new gt.TextNode("\xa0")]), r } }); const pn = () => { const e = new gt.MathNode("mtd", []); return e.setAttribute("width", "50%"), e }; $e({ type: "tag", mathmlBuilder(e, t) { const r = new gt.MathNode("mtable", [new gt.MathNode("mtr", [pn(), new gt.MathNode("mtd", [wt(e.body, t)]), pn(), new gt.MathNode("mtd", [wt(e.tag, t)])])]); return r.setAttribute("width", "100%"), r } }); const un = { "\\text": void 0, "\\textrm": "textrm", "\\textsf": "textsf", "\\texttt": "texttt", "\\textnormal": "textrm" }, dn = { "\\textbf": "textbf", "\\textmd": "textmd" }, gn = { "\\textit": "textit", "\\textup": "textup" }, fn = (e, t) => { const r = e.font; return r ? un[r] ? t.withTextFontFamily(un[r]) : dn[r] ? t.withTextFontWeight(dn[r]) : "\\emph" === r ? "textit" === t.fontShape ? t.withTextFontShape("textup") : t.withTextFontShape("textit") : t.withTextFontShape(gn[r]) : t }; je({ type: "text", names: ["\\text", "\\textrm", "\\textsf", "\\texttt", "\\textnormal", "\\textbf", "\\textmd", "\\textit", "\\textup", "\\emph"], props: { numArgs: 1, argTypes: ["text"], allowedInArgument: !0, allowedInText: !0 }, handler(e, t) { let { parser: r, funcName: n } = e; const o = t[0]; return { type: "text", mode: r.mode, body: Ke(o), font: n } }, htmlBuilder(e, t) { const r = fn(e, t), n = nt(e.body, r, !0); return Ve.makeSpan(["mord", "text"], n, r) }, mathmlBuilder(e, t) { const r = fn(e, t); return wt(e.body, r) } }), je({ type: "underline", names: ["\\underline"], props: { numArgs: 1, allowedInText: !0 }, handler(e, t) { let { parser: r } = e; return { type: "underline", mode: r.mode, body: t[0] } }, htmlBuilder(e, t) { const r = ht(e.body, t), n = Ve.makeLineSpan("underline-line", t), o = t.fontMetrics().defaultRuleThickness, s = Ve.makeVList({ positionType: "top", positionData: r.height, children: [{ type: "kern", size: o }, { type: "elem", elem: n }, { type: "kern", size: 3 * o }, { type: "elem", elem: r }] }, t); return Ve.makeSpan(["mord", "underline"], [s], t) }, mathmlBuilder(e, t) { const r = new gt.MathNode("mo", [new gt.TextNode("\u203e")]); r.setAttribute("stretchy", "true"); const n = new gt.MathNode("munder", [vt(e.body, t), r]); return n.setAttribute("accentunder", "true"), n } }), je({ type: "vcenter", names: ["\\vcenter"], props: { numArgs: 1, argTypes: ["original"], allowedInText: !1 }, handler(e, t) { let { parser: r } = e; return { type: "vcenter", mode: r.mode, body: t[0] } }, htmlBuilder(e, t) { const r = ht(e.body, t), n = t.fontMetrics().axisHeight, o = .5 * (r.height - n - (r.depth + n)); return Ve.makeVList({ positionType: "shift", positionData: o, children: [{ type: "elem", elem: r }] }, t) }, mathmlBuilder(e, t) { return new gt.MathNode("mpadded", [vt(e.body, t)], ["vcenter"]) } }), je({ type: "verb", names: ["\\verb"], props: { numArgs: 0, allowedInText: !0 }, handler(e, t, r) { throw new n("\\verb ended by end of line instead of matching delimiter") }, htmlBuilder(e, t) { const r = bn(e), n = [], o = t.havingStyle(t.style.text()); for (let t = 0; t < r.length; t++) { let s = r[t]; "~" === s && (s = "\\textasciitilde"), n.push(Ve.makeSymbol(s, "Typewriter-Regular", e.mode, o, ["mord", "texttt"])) } return Ve.makeSpan(["mord", "text"].concat(o.sizingClasses(t)), Ve.tryCombineChars(n), o) }, mathmlBuilder(e, t) { const r = new gt.TextNode(bn(e)), n = new gt.MathNode("mtext", [r]); return n.setAttribute("mathvariant", "monospace"), n } }); const bn = e => e.body.replace(/ /g, e.star ? "\u2423" : "\xa0"); var yn = Xe; const xn = "[ \r\n\t]", wn = "(\\\\[a-zA-Z@]+)" + xn + "*", vn = "[\u0300-\u036f]", kn = new RegExp(vn + "+$"), Sn = "(" + xn + "+)|\\\\(\n|[ \r\t]+\n?)[ \r\t]*|([!-\\[\\]-\u2027\u202a-\ud7ff\uf900-\uffff]" + vn + "*|[\ud800-\udbff][\udc00-\udfff]" + vn + "*|\\\\verb\\*([^]).*?\\4|\\\\verb([^*a-zA-Z]).*?\\5|" + wn + "|\\\\[^\ud800-\udfff])"; class Mn { constructor(e, t) { this.input = void 0, this.settings = void 0, this.tokenRegex = void 0, this.catcodes = void 0, this.input = e, this.settings = t, this.tokenRegex = new RegExp(Sn, "g"), this.catcodes = { "%": 14, "~": 13 } } setCatcode(e, t) { this.catcodes[e] = t } lex() { const e = this.input, t = this.tokenRegex.lastIndex; if (t === e.length) return new Nr("EOF", new Cr(this, t, t)); const r = this.tokenRegex.exec(e); if (null === r || r.index !== t) throw new n("Unexpected character: '" + e[t] + "'", new Nr(e[t], new Cr(this, t, t + 1))); const o = r[6] || r[3] || (r[2] ? "\\ " : " "); if (14 === this.catcodes[o]) { const t = e.indexOf("\n", this.tokenRegex.lastIndex); return -1 === t ? (this.tokenRegex.lastIndex = e.length, this.settings.reportNonstrict("commentAtEnd", "% comment has no terminating newline; LaTeX would fail because of commenting the end of math mode (e.g. $)")) : this.tokenRegex.lastIndex = t + 1, this.lex() } return new Nr(o, new Cr(this, t, this.tokenRegex.lastIndex)) } } class zn { constructor(e, t) { void 0 === e && (e = {}), void 0 === t && (t = {}), this.current = void 0, this.builtins = void 0, this.undefStack = void 0, this.current = t, this.builtins = e, this.undefStack = [] } beginGroup() { this.undefStack.push({}) } endGroup() { if (0 === this.undefStack.length) throw new n("Unbalanced namespace destruction: attempt to pop global namespace; please report this as a bug"); const e = this.undefStack.pop(); for (const t in e) e.hasOwnProperty(t) && (null == e[t] ? delete this.current[t] : this.current[t] = e[t]) } endGroups() { for (; this.undefStack.length > 0;)this.endGroup() } has(e) { return this.current.hasOwnProperty(e) || this.builtins.hasOwnProperty(e) } get(e) { return this.current.hasOwnProperty(e) ? this.current[e] : this.builtins[e] } set(e, t, r) { if (void 0 === r && (r = !1), r) { for (let t = 0; t < this.undefStack.length; t++)delete this.undefStack[t][e]; this.undefStack.length > 0 && (this.undefStack[this.undefStack.length - 1][e] = t) } else { const t = this.undefStack[this.undefStack.length - 1]; t && !t.hasOwnProperty(e) && (t[e] = this.current[e]) } null == t ? delete this.current[e] : this.current[e] = t } } var An = Tr; Br("\\noexpand", (function (e) { const t = e.popToken(); return e.isExpandable(t.text) && (t.noexpand = !0, t.treatAsRelax = !0), { tokens: [t], numArgs: 0 } })), Br("\\expandafter", (function (e) { const t = e.popToken(); return e.expandOnce(!0), { tokens: [t], numArgs: 0 } })), Br("\\@firstoftwo", (function (e) { return { tokens: e.consumeArgs(2)[0], numArgs: 0 } })), Br("\\@secondoftwo", (function (e) { return { tokens: e.consumeArgs(2)[1], numArgs: 0 } })), Br("\\@ifnextchar", (function (e) { const t = e.consumeArgs(3); e.consumeSpaces(); const r = e.future(); return 1 === t[0].length && t[0][0].text === r.text ? { tokens: t[1], numArgs: 0 } : { tokens: t[2], numArgs: 0 } })), Br("\\@ifstar", "\\@ifnextchar *{\\@firstoftwo{#1}}"), Br("\\TextOrMath", (function (e) { const t = e.consumeArgs(2); return "text" === e.mode ? { tokens: t[0], numArgs: 0 } : { tokens: t[1], numArgs: 0 } })); const Tn = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, a: 10, A: 10, b: 11, B: 11, c: 12, C: 12, d: 13, D: 13, e: 14, E: 14, f: 15, F: 15 }; Br("\\char", (function (e) { let t, r = e.popToken(), o = ""; if ("'" === r.text) t = 8, r = e.popToken(); else if ('"' === r.text) t = 16, r = e.popToken(); else if ("`" === r.text) if (r = e.popToken(), "\\" === r.text[0]) o = r.text.charCodeAt(1); else { if ("EOF" === r.text) throw new n("\\char` missing argument"); o = r.text.charCodeAt(0) } else t = 10; if (t) { if (o = Tn[r.text], null == o || o >= t) throw new n("Invalid base-" + t + " digit " + r.text); let s; for (; null != (s = Tn[e.future().text]) && s < t;)o *= t, o += s, e.popToken() } return "\\@char{" + o + "}" })); const Bn = (e, t, r) => { let o = e.consumeArg().tokens; if (1 !== o.length) throw new n("\\newcommand's first argument must be a macro name"); const s = o[0].text, i = e.isDefined(s); if (i && !t) throw new n("\\newcommand{" + s + "} attempting to redefine " + s + "; use \\renewcommand"); if (!i && !r) throw new n("\\renewcommand{" + s + "} when command " + s + " does not yet exist; use \\newcommand"); let a = 0; if (o = e.consumeArg().tokens, 1 === o.length && "[" === o[0].text) { let t = "", r = e.expandNextToken(); for (; "]" !== r.text && "EOF" !== r.text;)t += r.text, r = e.expandNextToken(); if (!t.match(/^\s*[0-9]+\s*$/)) throw new n("Invalid number of arguments: " + t); a = parseInt(t), o = e.consumeArg().tokens } return e.macros.set(s, { tokens: o, numArgs: a }), "" }; Br("\\newcommand", (e => Bn(e, !1, !0))), Br("\\renewcommand", (e => Bn(e, !0, !1))), Br("\\providecommand", (e => Bn(e, !0, !0))), Br("\\message", (e => { const t = e.consumeArgs(1)[0]; return console.log(t.reverse().map((e => e.text)).join("")), "" })), Br("\\errmessage", (e => { const t = e.consumeArgs(1)[0]; return console.error(t.reverse().map((e => e.text)).join("")), "" })), Br("\\show", (e => { const t = e.popToken(), r = t.text; return console.log(t, e.macros.get(r), yn[r], oe.math[r], oe.text[r]), "" })), Br("\\bgroup", "{"), Br("\\egroup", "}"), Br("~", "\\nobreakspace"), Br("\\lq", "`"), Br("\\rq", "'"), Br("\\aa", "\\r a"), Br("\\AA", "\\r A"), Br("\\textcopyright", "\\html@mathml{\\textcircled{c}}{\\char`\xa9}"), Br("\\copyright", "\\TextOrMath{\\textcopyright}{\\text{\\textcopyright}}"), Br("\\textregistered", "\\html@mathml{\\textcircled{\\scriptsize R}}{\\char`\xae}"), Br("\u212c", "\\mathscr{B}"), Br("\u2130", "\\mathscr{E}"), Br("\u2131", "\\mathscr{F}"), Br("\u210b", "\\mathscr{H}"), Br("\u2110", "\\mathscr{I}"), Br("\u2112", "\\mathscr{L}"), Br("\u2133", "\\mathscr{M}"), Br("\u211b", "\\mathscr{R}"), Br("\u212d", "\\mathfrak{C}"), Br("\u210c", "\\mathfrak{H}"), Br("\u2128", "\\mathfrak{Z}"), Br("\\Bbbk", "\\Bbb{k}"), Br("\xb7", "\\cdotp"), Br("\\llap", "\\mathllap{\\textrm{#1}}"), Br("\\rlap", "\\mathrlap{\\textrm{#1}}"), Br("\\clap", "\\mathclap{\\textrm{#1}}"), Br("\\mathstrut", "\\vphantom{(}"), Br("\\underbar", "\\underline{\\text{#1}}"), Br("\\not", '\\html@mathml{\\mathrel{\\mathrlap\\@not}}{\\char"338}'), Br("\\neq", "\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`\u2260}}"), Br("\\ne", "\\neq"), Br("\u2260", "\\neq"), Br("\\notin", "\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}{\\mathrel{\\char`\u2209}}"), Br("\u2209", "\\notin"), Br("\u2258", "\\html@mathml{\\mathrel{=\\kern{-1em}\\raisebox{0.4em}{$\\scriptsize\\frown$}}}{\\mathrel{\\char`\u2258}}"), Br("\u2259", "\\html@mathml{\\stackrel{\\tiny\\wedge}{=}}{\\mathrel{\\char`\u2258}}"), Br("\u225a", "\\html@mathml{\\stackrel{\\tiny\\vee}{=}}{\\mathrel{\\char`\u225a}}"), Br("\u225b", "\\html@mathml{\\stackrel{\\scriptsize\\star}{=}}{\\mathrel{\\char`\u225b}}"), Br("\u225d", "\\html@mathml{\\stackrel{\\tiny\\mathrm{def}}{=}}{\\mathrel{\\char`\u225d}}"), Br("\u225e", "\\html@mathml{\\stackrel{\\tiny\\mathrm{m}}{=}}{\\mathrel{\\char`\u225e}}"), Br("\u225f", "\\html@mathml{\\stackrel{\\tiny?}{=}}{\\mathrel{\\char`\u225f}}"), Br("\u27c2", "\\perp"), Br("\u203c", "\\mathclose{!\\mkern-0.8mu!}"), Br("\u220c", "\\notni"), Br("\u231c", "\\ulcorner"), Br("\u231d", "\\urcorner"), Br("\u231e", "\\llcorner"), Br("\u231f", "\\lrcorner"), Br("\xa9", "\\copyright"), Br("\xae", "\\textregistered"), Br("\ufe0f", "\\textregistered"), Br("\\ulcorner", '\\html@mathml{\\@ulcorner}{\\mathop{\\char"231c}}'), Br("\\urcorner", '\\html@mathml{\\@urcorner}{\\mathop{\\char"231d}}'), Br("\\llcorner", '\\html@mathml{\\@llcorner}{\\mathop{\\char"231e}}'), Br("\\lrcorner", '\\html@mathml{\\@lrcorner}{\\mathop{\\char"231f}}'), Br("\\vdots", "\\mathord{\\varvdots\\rule{0pt}{15pt}}"), Br("\u22ee", "\\vdots"), Br("\\varGamma", "\\mathit{\\Gamma}"), Br("\\varDelta", "\\mathit{\\Delta}"), Br("\\varTheta", "\\mathit{\\Theta}"), Br("\\varLambda", "\\mathit{\\Lambda}"), Br("\\varXi", "\\mathit{\\Xi}"), Br("\\varPi", "\\mathit{\\Pi}"), Br("\\varSigma", "\\mathit{\\Sigma}"), Br("\\varUpsilon", "\\mathit{\\Upsilon}"), Br("\\varPhi", "\\mathit{\\Phi}"), Br("\\varPsi", "\\mathit{\\Psi}"), Br("\\varOmega", "\\mathit{\\Omega}"), Br("\\substack", "\\begin{subarray}{c}#1\\end{subarray}"), Br("\\colon", "\\nobreak\\mskip2mu\\mathpunct{}\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax"), Br("\\boxed", "\\fbox{$\\displaystyle{#1}$}"), Br("\\iff", "\\DOTSB\\;\\Longleftrightarrow\\;"), Br("\\implies", "\\DOTSB\\;\\Longrightarrow\\;"), Br("\\impliedby", "\\DOTSB\\;\\Longleftarrow\\;"); const Cn = { ",": "\\dotsc", "\\not": "\\dotsb", "+": "\\dotsb", "=": "\\dotsb", "<": "\\dotsb", ">": "\\dotsb", "-": "\\dotsb", "*": "\\dotsb", ":": "\\dotsb", "\\DOTSB": "\\dotsb", "\\coprod": "\\dotsb", "\\bigvee": "\\dotsb", "\\bigwedge": "\\dotsb", "\\biguplus": "\\dotsb", "\\bigcap": "\\dotsb", "\\bigcup": "\\dotsb", "\\prod": "\\dotsb", "\\sum": "\\dotsb", "\\bigotimes": "\\dotsb", "\\bigoplus": "\\dotsb", "\\bigodot": "\\dotsb", "\\bigsqcup": "\\dotsb", "\\And": "\\dotsb", "\\longrightarrow": "\\dotsb", "\\Longrightarrow": "\\dotsb", "\\longleftarrow": "\\dotsb", "\\Longleftarrow": "\\dotsb", "\\longleftrightarrow": "\\dotsb", "\\Longleftrightarrow": "\\dotsb", "\\mapsto": "\\dotsb", "\\longmapsto": "\\dotsb", "\\hookrightarrow": "\\dotsb", "\\doteq": "\\dotsb", "\\mathbin": "\\dotsb", "\\mathrel": "\\dotsb", "\\relbar": "\\dotsb", "\\Relbar": "\\dotsb", "\\xrightarrow": "\\dotsb", "\\xleftarrow": "\\dotsb", "\\DOTSI": "\\dotsi", "\\int": "\\dotsi", "\\oint": "\\dotsi", "\\iint": "\\dotsi", "\\iiint": "\\dotsi", "\\iiiint": "\\dotsi", "\\idotsint": "\\dotsi", "\\DOTSX": "\\dotsx" }; Br("\\dots", (function (e) { let t = "\\dotso"; const r = e.expandAfterFuture().text; return r in Cn ? t = Cn[r] : ("\\not" === r.slice(0, 4) || r in oe.math && l.contains(["bin", "rel"], oe.math[r].group)) && (t = "\\dotsb"), t })); const Nn = { ")": !0, "]": !0, "\\rbrack": !0, "\\}": !0, "\\rbrace": !0, "\\rangle": !0, "\\rceil": !0, "\\rfloor": !0, "\\rgroup": !0, "\\rmoustache": !0, "\\right": !0, "\\bigr": !0, "\\biggr": !0, "\\Bigr": !0, "\\Biggr": !0, $: !0, ";": !0, ".": !0, ",": !0 }; Br("\\dotso", (function (e) { return e.future().text in Nn ? "\\ldots\\," : "\\ldots" })), Br("\\dotsc", (function (e) { const t = e.future().text; return t in Nn && "," !== t ? "\\ldots\\," : "\\ldots" })), Br("\\cdots", (function (e) { return e.future().text in Nn ? "\\@cdots\\," : "\\@cdots" })), Br("\\dotsb", "\\cdots"), Br("\\dotsm", "\\cdots"), Br("\\dotsi", "\\!\\cdots"), Br("\\dotsx", "\\ldots\\,"), Br("\\DOTSI", "\\relax"), Br("\\DOTSB", "\\relax"), Br("\\DOTSX", "\\relax"), Br("\\tmspace", "\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax"), Br("\\,", "\\tmspace+{3mu}{.1667em}"), Br("\\thinspace", "\\,"), Br("\\>", "\\mskip{4mu}"), Br("\\:", "\\tmspace+{4mu}{.2222em}"), Br("\\medspace", "\\:"), Br("\\;", "\\tmspace+{5mu}{.2777em}"), Br("\\thickspace", "\\;"), Br("\\!", "\\tmspace-{3mu}{.1667em}"), Br("\\negthinspace", "\\!"), Br("\\negmedspace", "\\tmspace-{4mu}{.2222em}"), Br("\\negthickspace", "\\tmspace-{5mu}{.277em}"), Br("\\enspace", "\\kern.5em "), Br("\\enskip", "\\hskip.5em\\relax"), Br("\\quad", "\\hskip1em\\relax"), Br("\\qquad", "\\hskip2em\\relax"), Br("\\tag", "\\@ifstar\\tag@literal\\tag@paren"), Br("\\tag@paren", "\\tag@literal{({#1})}"), Br("\\tag@literal", (e => { if (e.macros.get("\\df@tag")) throw new n("Multiple \\tag"); return "\\gdef\\df@tag{\\text{#1}}" })), Br("\\bmod", "\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}"), Br("\\pod", "\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)"), Br("\\pmod", "\\pod{{\\rm mod}\\mkern6mu#1}"), Br("\\mod", "\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1"), Br("\\newline", "\\\\\\relax"), Br("\\TeX", "\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}"); const qn = F(T["Main-Regular"]["T".charCodeAt(0)][1] - .7 * T["Main-Regular"]["A".charCodeAt(0)][1]); Br("\\LaTeX", "\\textrm{\\html@mathml{L\\kern-.36em\\raisebox{" + qn + "}{\\scriptstyle A}\\kern-.15em\\TeX}{LaTeX}}"), Br("\\KaTeX", "\\textrm{\\html@mathml{K\\kern-.17em\\raisebox{" + qn + "}{\\scriptstyle A}\\kern-.15em\\TeX}{KaTeX}}"), Br("\\hspace", "\\@ifstar\\@hspacer\\@hspace"), Br("\\@hspace", "\\hskip #1\\relax"), Br("\\@hspacer", "\\rule{0pt}{0pt}\\hskip #1\\relax"), Br("\\ordinarycolon", ":"), Br("\\vcentcolon", "\\mathrel{\\mathop\\ordinarycolon}"), Br("\\dblcolon", '\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}'), Br("\\coloneqq", '\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}'), Br("\\Coloneqq", '\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}'), Br("\\coloneq", '\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}'), Br("\\Coloneq", '\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}'), Br("\\eqqcolon", '\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}'), Br("\\Eqqcolon", '\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}'), Br("\\eqcolon", '\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}'), Br("\\Eqcolon", '\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}'), Br("\\colonapprox", '\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}'), Br("\\Colonapprox", '\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}'), Br("\\colonsim", '\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}'), Br("\\Colonsim", '\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}'), Br("\u2237", "\\dblcolon"), Br("\u2239", "\\eqcolon"), Br("\u2254", "\\coloneqq"), Br("\u2255", "\\eqqcolon"), Br("\u2a74", "\\Coloneqq"), Br("\\ratio", "\\vcentcolon"), Br("\\coloncolon", "\\dblcolon"), Br("\\colonequals", "\\coloneqq"), Br("\\coloncolonequals", "\\Coloneqq"), Br("\\equalscolon", "\\eqqcolon"), Br("\\equalscoloncolon", "\\Eqqcolon"), Br("\\colonminus", "\\coloneq"), Br("\\coloncolonminus", "\\Coloneq"), Br("\\minuscolon", "\\eqcolon"), Br("\\minuscoloncolon", "\\Eqcolon"), Br("\\coloncolonapprox", "\\Colonapprox"), Br("\\coloncolonsim", "\\Colonsim"), Br("\\simcolon", "\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}"), Br("\\simcoloncolon", "\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}"), Br("\\approxcolon", "\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}"), Br("\\approxcoloncolon", "\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}"), Br("\\notni", "\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220c}}"), Br("\\limsup", "\\DOTSB\\operatorname*{lim\\,sup}"), Br("\\liminf", "\\DOTSB\\operatorname*{lim\\,inf}"), Br("\\injlim", "\\DOTSB\\operatorname*{inj\\,lim}"), Br("\\projlim", "\\DOTSB\\operatorname*{proj\\,lim}"), Br("\\varlimsup", "\\DOTSB\\operatorname*{\\overline{lim}}"), Br("\\varliminf", "\\DOTSB\\operatorname*{\\underline{lim}}"), Br("\\varinjlim", "\\DOTSB\\operatorname*{\\underrightarrow{lim}}"), Br("\\varprojlim", "\\DOTSB\\operatorname*{\\underleftarrow{lim}}"), Br("\\gvertneqq", "\\html@mathml{\\@gvertneqq}{\u2269}"), Br("\\lvertneqq", "\\html@mathml{\\@lvertneqq}{\u2268}"), Br("\\ngeqq", "\\html@mathml{\\@ngeqq}{\u2271}"), Br("\\ngeqslant", "\\html@mathml{\\@ngeqslant}{\u2271}"), Br("\\nleqq", "\\html@mathml{\\@nleqq}{\u2270}"), Br("\\nleqslant", "\\html@mathml{\\@nleqslant}{\u2270}"), Br("\\nshortmid", "\\html@mathml{\\@nshortmid}{\u2224}"), Br("\\nshortparallel", "\\html@mathml{\\@nshortparallel}{\u2226}"), Br("\\nsubseteqq", "\\html@mathml{\\@nsubseteqq}{\u2288}"), Br("\\nsupseteqq", "\\html@mathml{\\@nsupseteqq}{\u2289}"), Br("\\varsubsetneq", "\\html@mathml{\\@varsubsetneq}{\u228a}"), Br("\\varsubsetneqq", "\\html@mathml{\\@varsubsetneqq}{\u2acb}"), Br("\\varsupsetneq", "\\html@mathml{\\@varsupsetneq}{\u228b}"), Br("\\varsupsetneqq", "\\html@mathml{\\@varsupsetneqq}{\u2acc}"), Br("\\imath", "\\html@mathml{\\@imath}{\u0131}"), Br("\\jmath", "\\html@mathml{\\@jmath}{\u0237}"), Br("\\llbracket", "\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`\u27e6}}"), Br("\\rrbracket", "\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`\u27e7}}"), Br("\u27e6", "\\llbracket"), Br("\u27e7", "\\rrbracket"), Br("\\lBrace", "\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`\u2983}}"), Br("\\rBrace", "\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`\u2984}}"), Br("\u2983", "\\lBrace"), Br("\u2984", "\\rBrace"), Br("\\minuso", "\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u29b5}}"), Br("\u29b5", "\\minuso"), Br("\\darr", "\\downarrow"), Br("\\dArr", "\\Downarrow"), Br("\\Darr", "\\Downarrow"), Br("\\lang", "\\langle"), Br("\\rang", "\\rangle"), Br("\\uarr", "\\uparrow"), Br("\\uArr", "\\Uparrow"), Br("\\Uarr", "\\Uparrow"), Br("\\N", "\\mathbb{N}"), Br("\\R", "\\mathbb{R}"), Br("\\Z", "\\mathbb{Z}"), Br("\\alef", "\\aleph"), Br("\\alefsym", "\\aleph"), Br("\\Alpha", "\\mathrm{A}"), Br("\\Beta", "\\mathrm{B}"), Br("\\bull", "\\bullet"), Br("\\Chi", "\\mathrm{X}"), Br("\\clubs", "\\clubsuit"), Br("\\cnums", "\\mathbb{C}"), Br("\\Complex", "\\mathbb{C}"), Br("\\Dagger", "\\ddagger"), Br("\\diamonds", "\\diamondsuit"), Br("\\empty", "\\emptyset"), Br("\\Epsilon", "\\mathrm{E}"), Br("\\Eta", "\\mathrm{H}"), Br("\\exist", "\\exists"), Br("\\harr", "\\leftrightarrow"), Br("\\hArr", "\\Leftrightarrow"), Br("\\Harr", "\\Leftrightarrow"), Br("\\hearts", "\\heartsuit"), Br("\\image", "\\Im"), Br("\\infin", "\\infty"), Br("\\Iota", "\\mathrm{I}"), Br("\\isin", "\\in"), Br("\\Kappa", "\\mathrm{K}"), Br("\\larr", "\\leftarrow"), Br("\\lArr", "\\Leftarrow"), Br("\\Larr", "\\Leftarrow"), Br("\\lrarr", "\\leftrightarrow"), Br("\\lrArr", "\\Leftrightarrow"), Br("\\Lrarr", "\\Leftrightarrow"), Br("\\Mu", "\\mathrm{M}"), Br("\\natnums", "\\mathbb{N}"), Br("\\Nu", "\\mathrm{N}"), Br("\\Omicron", "\\mathrm{O}"), Br("\\plusmn", "\\pm"), Br("\\rarr", "\\rightarrow"), Br("\\rArr", "\\Rightarrow"), Br("\\Rarr", "\\Rightarrow"), Br("\\real", "\\Re"), Br("\\reals", "\\mathbb{R}"), Br("\\Reals", "\\mathbb{R}"), Br("\\Rho", "\\mathrm{P}"), Br("\\sdot", "\\cdot"), Br("\\sect", "\\S"), Br("\\spades", "\\spadesuit"), Br("\\sub", "\\subset"), Br("\\sube", "\\subseteq"), Br("\\supe", "\\supseteq"), Br("\\Tau", "\\mathrm{T}"), Br("\\thetasym", "\\vartheta"), Br("\\weierp", "\\wp"), Br("\\Zeta", "\\mathrm{Z}"), Br("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}"), Br("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}"), Br("\\plim", "\\DOTSB\\mathop{\\operatorname{plim}}\\limits"), Br("\\bra", "\\mathinner{\\langle{#1}|}"), Br("\\ket", "\\mathinner{|{#1}\\rangle}"), Br("\\braket", "\\mathinner{\\langle{#1}\\rangle}"), Br("\\Bra", "\\left\\langle#1\\right|"), Br("\\Ket", "\\left|#1\\right\\rangle"); const In = e => t => { const r = t.consumeArg().tokens, n = t.consumeArg().tokens, o = t.consumeArg().tokens, s = t.consumeArg().tokens, i = t.macros.get("|"), a = t.macros.get("\\|"); t.macros.beginGroup(); const l = t => r => { e && (r.macros.set("|", i), o.length && r.macros.set("\\|", a)); let s = t; if (!t && o.length) { "|" === r.future().text && (r.popToken(), s = !0) } return { tokens: s ? o : n, numArgs: 0 } }; t.macros.set("|", l(!1)), o.length && t.macros.set("\\|", l(!0)); const h = t.consumeArg().tokens, c = t.expandTokens([...s, ...h, ...r]); return t.macros.endGroup(), { tokens: c.reverse(), numArgs: 0 } }; Br("\\bra@ket", In(!1)), Br("\\bra@set", In(!0)), Br("\\Braket", "\\bra@ket{\\left\\langle}{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}"), Br("\\Set", "\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}"), Br("\\set", "\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}"), Br("\\angln", "{\\angl n}"), Br("\\blue", "\\textcolor{##6495ed}{#1}"), Br("\\orange", "\\textcolor{##ffa500}{#1}"), Br("\\pink", "\\textcolor{##ff00af}{#1}"), Br("\\red", "\\textcolor{##df0030}{#1}"), Br("\\green", "\\textcolor{##28ae7b}{#1}"), Br("\\gray", "\\textcolor{gray}{#1}"), Br("\\purple", "\\textcolor{##9d38bd}{#1}"), Br("\\blueA", "\\textcolor{##ccfaff}{#1}"), Br("\\blueB", "\\textcolor{##80f6ff}{#1}"), Br("\\blueC", "\\textcolor{##63d9ea}{#1}"), Br("\\blueD", "\\textcolor{##11accd}{#1}"), Br("\\blueE", "\\textcolor{##0c7f99}{#1}"), Br("\\tealA", "\\textcolor{##94fff5}{#1}"), Br("\\tealB", "\\textcolor{##26edd5}{#1}"), Br("\\tealC", "\\textcolor{##01d1c1}{#1}"), Br("\\tealD", "\\textcolor{##01a995}{#1}"), Br("\\tealE", "\\textcolor{##208170}{#1}"), Br("\\greenA", "\\textcolor{##b6ffb0}{#1}"), Br("\\greenB", "\\textcolor{##8af281}{#1}"), Br("\\greenC", "\\textcolor{##74cf70}{#1}"), Br("\\greenD", "\\textcolor{##1fab54}{#1}"), Br("\\greenE", "\\textcolor{##0d923f}{#1}"), Br("\\goldA", "\\textcolor{##ffd0a9}{#1}"), Br("\\goldB", "\\textcolor{##ffbb71}{#1}"), Br("\\goldC", "\\textcolor{##ff9c39}{#1}"), Br("\\goldD", "\\textcolor{##e07d10}{#1}"), Br("\\goldE", "\\textcolor{##a75a05}{#1}"), Br("\\redA", "\\textcolor{##fca9a9}{#1}"), Br("\\redB", "\\textcolor{##ff8482}{#1}"), Br("\\redC", "\\textcolor{##f9685d}{#1}"), Br("\\redD", "\\textcolor{##e84d39}{#1}"), Br("\\redE", "\\textcolor{##bc2612}{#1}"), Br("\\maroonA", "\\textcolor{##ffbde0}{#1}"), Br("\\maroonB", "\\textcolor{##ff92c6}{#1}"), Br("\\maroonC", "\\textcolor{##ed5fa6}{#1}"), Br("\\maroonD", "\\textcolor{##ca337c}{#1}"), Br("\\maroonE", "\\textcolor{##9e034e}{#1}"), Br("\\purpleA", "\\textcolor{##ddd7ff}{#1}"), Br("\\purpleB", "\\textcolor{##c6b9fc}{#1}"), Br("\\purpleC", "\\textcolor{##aa87ff}{#1}"), Br("\\purpleD", "\\textcolor{##7854ab}{#1}"), Br("\\purpleE", "\\textcolor{##543b78}{#1}"), Br("\\mintA", "\\textcolor{##f5f9e8}{#1}"), Br("\\mintB", "\\textcolor{##edf2df}{#1}"), Br("\\mintC", "\\textcolor{##e0e5cc}{#1}"), Br("\\grayA", "\\textcolor{##f6f7f7}{#1}"), Br("\\grayB", "\\textcolor{##f0f1f2}{#1}"), Br("\\grayC", "\\textcolor{##e3e5e6}{#1}"), Br("\\grayD", "\\textcolor{##d6d8da}{#1}"), Br("\\grayE", "\\textcolor{##babec2}{#1}"), Br("\\grayF", "\\textcolor{##888d93}{#1}"), Br("\\grayG", "\\textcolor{##626569}{#1}"), Br("\\grayH", "\\textcolor{##3b3e40}{#1}"), Br("\\grayI", "\\textcolor{##21242c}{#1}"), Br("\\kaBlue", "\\textcolor{##314453}{#1}"), Br("\\kaGreen", "\\textcolor{##71B307}{#1}"); const Rn = { "^": !0, _: !0, "\\limits": !0, "\\nolimits": !0 }; class Hn { constructor(e, t, r) { this.settings = void 0, this.expansionCount = void 0, this.lexer = void 0, this.macros = void 0, this.stack = void 0, this.mode = void 0, this.settings = t, this.expansionCount = 0, this.feed(e), this.macros = new zn(An, t.macros), this.mode = r, this.stack = [] } feed(e) { this.lexer = new Mn(e, this.settings) } switchMode(e) { this.mode = e } beginGroup() { this.macros.beginGroup() } endGroup() { this.macros.endGroup() } endGroups() { this.macros.endGroups() } future() { return 0 === this.stack.length && this.pushToken(this.lexer.lex()), this.stack[this.stack.length - 1] } popToken() { return this.future(), this.stack.pop() } pushToken(e) { this.stack.push(e) } pushTokens(e) { this.stack.push(...e) } scanArgument(e) { let t, r, n; if (e) { if (this.consumeSpaces(), "[" !== this.future().text) return null; t = this.popToken(), ({ tokens: n, end: r } = this.consumeArg(["]"])) } else ({ tokens: n, start: t, end: r } = this.consumeArg()); return this.pushToken(new Nr("EOF", r.loc)), this.pushTokens(n), t.range(r, "") } consumeSpaces() { for (; ;) { if (" " !== this.future().text) break; this.stack.pop() } } consumeArg(e) { const t = [], r = e && e.length > 0; r || this.consumeSpaces(); const o = this.future(); let s, i = 0, a = 0; do { if (s = this.popToken(), t.push(s), "{" === s.text) ++i; else if ("}" === s.text) { if (--i, -1 === i) throw new n("Extra }", s) } else if ("EOF" === s.text) throw new n("Unexpected end of input in a macro argument, expected '" + (e && r ? e[a] : "}") + "'", s); if (e && r) if ((0 === i || 1 === i && "{" === e[a]) && s.text === e[a]) { if (++a, a === e.length) { t.splice(-a, a); break } } else a = 0 } while (0 !== i || r); return "{" === o.text && "}" === t[t.length - 1].text && (t.pop(), t.shift()), t.reverse(), { tokens: t, start: o, end: s } } consumeArgs(e, t) { if (t) { if (t.length !== e + 1) throw new n("The length of delimiters doesn't match the number of args!"); const r = t[0]; for (let e = 0; e < r.length; e++) { const t = this.popToken(); if (r[e] !== t.text) throw new n("Use of the macro doesn't match its definition", t) } } const r = []; for (let n = 0; n < e; n++)r.push(this.consumeArg(t && t[n + 1]).tokens); return r } countExpansion(e) { if (this.expansionCount += e, this.expansionCount > this.settings.maxExpand) throw new n("Too many expansions: infinite loop or need to increase maxExpand setting") } expandOnce(e) { const t = this.popToken(), r = t.text, o = t.noexpand ? null : this._getExpansion(r); if (null == o || e && o.unexpandable) { if (e && null == o && "\\" === r[0] && !this.isDefined(r)) throw new n("Undefined control sequence: " + r); return this.pushToken(t), !1 } this.countExpansion(1); let s = o.tokens; const i = this.consumeArgs(o.numArgs, o.delimiters); if (o.numArgs) { s = s.slice(); for (let e = s.length - 1; e >= 0; --e) { let t = s[e]; if ("#" === t.text) { if (0 === e) throw new n("Incomplete placeholder at end of macro body", t); if (t = s[--e], "#" === t.text) s.splice(e + 1, 1); else { if (!/^[1-9]$/.test(t.text)) throw new n("Not a valid argument number", t); s.splice(e, 2, ...i[+t.text - 1]) } } } } return this.pushTokens(s), s.length } expandAfterFuture() { return this.expandOnce(), this.future() } expandNextToken() { for (; ;)if (!1 === this.expandOnce()) { const e = this.stack.pop(); return e.treatAsRelax && (e.text = "\\relax"), e } throw new Error } expandMacro(e) { return this.macros.has(e) ? this.expandTokens([new Nr(e)]) : void 0 } expandTokens(e) { const t = [], r = this.stack.length; for (this.pushTokens(e); this.stack.length > r;)if (!1 === this.expandOnce(!0)) { const e = this.stack.pop(); e.treatAsRelax && (e.noexpand = !1, e.treatAsRelax = !1), t.push(e) } return this.countExpansion(t.length), t } expandMacroAsText(e) { const t = this.expandMacro(e); return t ? t.map((e => e.text)).join("") : t } _getExpansion(e) { const t = this.macros.get(e); if (null == t) return t; if (1 === e.length) { const t = this.lexer.catcodes[e]; if (null != t && 13 !== t) return } const r = "function" == typeof t ? t(this) : t; if ("string" == typeof r) { let e = 0; if (-1 !== r.indexOf("#")) { const t = r.replace(/##/g, ""); for (; -1 !== t.indexOf("#" + (e + 1));)++e } const t = new Mn(r, this.settings), n = []; let o = t.lex(); for (; "EOF" !== o.text;)n.push(o), o = t.lex(); n.reverse(); return { tokens: n, numArgs: e } } return r } isDefined(e) { return this.macros.has(e) || yn.hasOwnProperty(e) || oe.math.hasOwnProperty(e) || oe.text.hasOwnProperty(e) || Rn.hasOwnProperty(e) } isExpandable(e) { const t = this.macros.get(e); return null != t ? "string" == typeof t || "function" == typeof t || !t.unexpandable : yn.hasOwnProperty(e) && !yn[e].primitive } } const On = /^[\u208a\u208b\u208c\u208d\u208e\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2090\u2091\u2095\u1d62\u2c7c\u2096\u2097\u2098\u2099\u2092\u209a\u1d63\u209b\u209c\u1d64\u1d65\u2093\u1d66\u1d67\u1d68\u1d69\u1d6a]/, En = Object.freeze({ "\u208a": "+", "\u208b": "-", "\u208c": "=", "\u208d": "(", "\u208e": ")", "\u2080": "0", "\u2081": "1", "\u2082": "2", "\u2083": "3", "\u2084": "4", "\u2085": "5", "\u2086": "6", "\u2087": "7", "\u2088": "8", "\u2089": "9", "\u2090": "a", "\u2091": "e", "\u2095": "h", "\u1d62": "i", "\u2c7c": "j", "\u2096": "k", "\u2097": "l", "\u2098": "m", "\u2099": "n", "\u2092": "o", "\u209a": "p", "\u1d63": "r", "\u209b": "s", "\u209c": "t", "\u1d64": "u", "\u1d65": "v", "\u2093": "x", "\u1d66": "\u03b2", "\u1d67": "\u03b3", "\u1d68": "\u03c1", "\u1d69": "\u03d5", "\u1d6a": "\u03c7", "\u207a": "+", "\u207b": "-", "\u207c": "=", "\u207d": "(", "\u207e": ")", "\u2070": "0", "\xb9": "1", "\xb2": "2", "\xb3": "3", "\u2074": "4", "\u2075": "5", "\u2076": "6", "\u2077": "7", "\u2078": "8", "\u2079": "9", "\u1d2c": "A", "\u1d2e": "B", "\u1d30": "D", "\u1d31": "E", "\u1d33": "G", "\u1d34": "H", "\u1d35": "I", "\u1d36": "J", "\u1d37": "K", "\u1d38": "L", "\u1d39": "M", "\u1d3a": "N", "\u1d3c": "O", "\u1d3e": "P", "\u1d3f": "R", "\u1d40": "T", "\u1d41": "U", "\u2c7d": "V", "\u1d42": "W", "\u1d43": "a", "\u1d47": "b", "\u1d9c": "c", "\u1d48": "d", "\u1d49": "e", "\u1da0": "f", "\u1d4d": "g", "\u02b0": "h", "\u2071": "i", "\u02b2": "j", "\u1d4f": "k", "\u02e1": "l", "\u1d50": "m", "\u207f": "n", "\u1d52": "o", "\u1d56": "p", "\u02b3": "r", "\u02e2": "s", "\u1d57": "t", "\u1d58": "u", "\u1d5b": "v", "\u02b7": "w", "\u02e3": "x", "\u02b8": "y", "\u1dbb": "z", "\u1d5d": "\u03b2", "\u1d5e": "\u03b3", "\u1d5f": "\u03b4", "\u1d60": "\u03d5", "\u1d61": "\u03c7", "\u1dbf": "\u03b8" }), Ln = { "\u0301": { text: "\\'", math: "\\acute" }, "\u0300": { text: "\\`", math: "\\grave" }, "\u0308": { text: '\\"', math: "\\ddot" }, "\u0303": { text: "\\~", math: "\\tilde" }, "\u0304": { text: "\\=", math: "\\bar" }, "\u0306": { text: "\\u", math: "\\breve" }, "\u030c": { text: "\\v", math: "\\check" }, "\u0302": { text: "\\^", math: "\\hat" }, "\u0307": { text: "\\.", math: "\\dot" }, "\u030a": { text: "\\r", math: "\\mathring" }, "\u030b": { text: "\\H" }, "\u0327": { text: "\\c" } }, Dn = { "\xe1": "a\u0301", "\xe0": "a\u0300", "\xe4": "a\u0308", "\u01df": "a\u0308\u0304", "\xe3": "a\u0303", "\u0101": "a\u0304", "\u0103": "a\u0306", "\u1eaf": "a\u0306\u0301", "\u1eb1": "a\u0306\u0300", "\u1eb5": "a\u0306\u0303", "\u01ce": "a\u030c", "\xe2": "a\u0302", "\u1ea5": "a\u0302\u0301", "\u1ea7": "a\u0302\u0300", "\u1eab": "a\u0302\u0303", "\u0227": "a\u0307", "\u01e1": "a\u0307\u0304", "\xe5": "a\u030a", "\u01fb": "a\u030a\u0301", "\u1e03": "b\u0307", "\u0107": "c\u0301", "\u1e09": "c\u0327\u0301", "\u010d": "c\u030c", "\u0109": "c\u0302", "\u010b": "c\u0307", "\xe7": "c\u0327", "\u010f": "d\u030c", "\u1e0b": "d\u0307", "\u1e11": "d\u0327", "\xe9": "e\u0301", "\xe8": "e\u0300", "\xeb": "e\u0308", "\u1ebd": "e\u0303", "\u0113": "e\u0304", "\u1e17": "e\u0304\u0301", "\u1e15": "e\u0304\u0300", "\u0115": "e\u0306", "\u1e1d": "e\u0327\u0306", "\u011b": "e\u030c", "\xea": "e\u0302", "\u1ebf": "e\u0302\u0301", "\u1ec1": "e\u0302\u0300", "\u1ec5": "e\u0302\u0303", "\u0117": "e\u0307", "\u0229": "e\u0327", "\u1e1f": "f\u0307", "\u01f5": "g\u0301", "\u1e21": "g\u0304", "\u011f": "g\u0306", "\u01e7": "g\u030c", "\u011d": "g\u0302", "\u0121": "g\u0307", "\u0123": "g\u0327", "\u1e27": "h\u0308", "\u021f": "h\u030c", "\u0125": "h\u0302", "\u1e23": "h\u0307", "\u1e29": "h\u0327", "\xed": "i\u0301", "\xec": "i\u0300", "\xef": "i\u0308", "\u1e2f": "i\u0308\u0301", "\u0129": "i\u0303", "\u012b": "i\u0304", "\u012d": "i\u0306", "\u01d0": "i\u030c", "\xee": "i\u0302", "\u01f0": "j\u030c", "\u0135": "j\u0302", "\u1e31": "k\u0301", "\u01e9": "k\u030c", "\u0137": "k\u0327", "\u013a": "l\u0301", "\u013e": "l\u030c", "\u013c": "l\u0327", "\u1e3f": "m\u0301", "\u1e41": "m\u0307", "\u0144": "n\u0301", "\u01f9": "n\u0300", "\xf1": "n\u0303", "\u0148": "n\u030c", "\u1e45": "n\u0307", "\u0146": "n\u0327", "\xf3": "o\u0301", "\xf2": "o\u0300", "\xf6": "o\u0308", "\u022b": "o\u0308\u0304", "\xf5": "o\u0303", "\u1e4d": "o\u0303\u0301", "\u1e4f": "o\u0303\u0308", "\u022d": "o\u0303\u0304", "\u014d": "o\u0304", "\u1e53": "o\u0304\u0301", "\u1e51": "o\u0304\u0300", "\u014f": "o\u0306", "\u01d2": "o\u030c", "\xf4": "o\u0302", "\u1ed1": "o\u0302\u0301", "\u1ed3": "o\u0302\u0300", "\u1ed7": "o\u0302\u0303", "\u022f": "o\u0307", "\u0231": "o\u0307\u0304", "\u0151": "o\u030b", "\u1e55": "p\u0301", "\u1e57": "p\u0307", "\u0155": "r\u0301", "\u0159": "r\u030c", "\u1e59": "r\u0307", "\u0157": "r\u0327", "\u015b": "s\u0301", "\u1e65": "s\u0301\u0307", "\u0161": "s\u030c", "\u1e67": "s\u030c\u0307", "\u015d": "s\u0302", "\u1e61": "s\u0307", "\u015f": "s\u0327", "\u1e97": "t\u0308", "\u0165": "t\u030c", "\u1e6b": "t\u0307", "\u0163": "t\u0327", "\xfa": "u\u0301", "\xf9": "u\u0300", "\xfc": "u\u0308", "\u01d8": "u\u0308\u0301", "\u01dc": "u\u0308\u0300", "\u01d6": "u\u0308\u0304", "\u01da": "u\u0308\u030c", "\u0169": "u\u0303", "\u1e79": "u\u0303\u0301", "\u016b": "u\u0304", "\u1e7b": "u\u0304\u0308", "\u016d": "u\u0306", "\u01d4": "u\u030c", "\xfb": "u\u0302", "\u016f": "u\u030a", "\u0171": "u\u030b", "\u1e7d": "v\u0303", "\u1e83": "w\u0301", "\u1e81": "w\u0300", "\u1e85": "w\u0308", "\u0175": "w\u0302", "\u1e87": "w\u0307", "\u1e98": "w\u030a", "\u1e8d": "x\u0308", "\u1e8b": "x\u0307", "\xfd": "y\u0301", "\u1ef3": "y\u0300", "\xff": "y\u0308", "\u1ef9": "y\u0303", "\u0233": "y\u0304", "\u0177": "y\u0302", "\u1e8f": "y\u0307", "\u1e99": "y\u030a", "\u017a": "z\u0301", "\u017e": "z\u030c", "\u1e91": "z\u0302", "\u017c": "z\u0307", "\xc1": "A\u0301", "\xc0": "A\u0300", "\xc4": "A\u0308", "\u01de": "A\u0308\u0304", "\xc3": "A\u0303", "\u0100": "A\u0304", "\u0102": "A\u0306", "\u1eae": "A\u0306\u0301", "\u1eb0": "A\u0306\u0300", "\u1eb4": "A\u0306\u0303", "\u01cd": "A\u030c", "\xc2": "A\u0302", "\u1ea4": "A\u0302\u0301", "\u1ea6": "A\u0302\u0300", "\u1eaa": "A\u0302\u0303", "\u0226": "A\u0307", "\u01e0": "A\u0307\u0304", "\xc5": "A\u030a", "\u01fa": "A\u030a\u0301", "\u1e02": "B\u0307", "\u0106": "C\u0301", "\u1e08": "C\u0327\u0301", "\u010c": "C\u030c", "\u0108": "C\u0302", "\u010a": "C\u0307", "\xc7": "C\u0327", "\u010e": "D\u030c", "\u1e0a": "D\u0307", "\u1e10": "D\u0327", "\xc9": "E\u0301", "\xc8": "E\u0300", "\xcb": "E\u0308", "\u1ebc": "E\u0303", "\u0112": "E\u0304", "\u1e16": "E\u0304\u0301", "\u1e14": "E\u0304\u0300", "\u0114": "E\u0306", "\u1e1c": "E\u0327\u0306", "\u011a": "E\u030c", "\xca": "E\u0302", "\u1ebe": "E\u0302\u0301", "\u1ec0": "E\u0302\u0300", "\u1ec4": "E\u0302\u0303", "\u0116": "E\u0307", "\u0228": "E\u0327", "\u1e1e": "F\u0307", "\u01f4": "G\u0301", "\u1e20": "G\u0304", "\u011e": "G\u0306", "\u01e6": "G\u030c", "\u011c": "G\u0302", "\u0120": "G\u0307", "\u0122": "G\u0327", "\u1e26": "H\u0308", "\u021e": "H\u030c", "\u0124": "H\u0302", "\u1e22": "H\u0307", "\u1e28": "H\u0327", "\xcd": "I\u0301", "\xcc": "I\u0300", "\xcf": "I\u0308", "\u1e2e": "I\u0308\u0301", "\u0128": "I\u0303", "\u012a": "I\u0304", "\u012c": "I\u0306", "\u01cf": "I\u030c", "\xce": "I\u0302", "\u0130": "I\u0307", "\u0134": "J\u0302", "\u1e30": "K\u0301", "\u01e8": "K\u030c", "\u0136": "K\u0327", "\u0139": "L\u0301", "\u013d": "L\u030c", "\u013b": "L\u0327", "\u1e3e": "M\u0301", "\u1e40": "M\u0307", "\u0143": "N\u0301", "\u01f8": "N\u0300", "\xd1": "N\u0303", "\u0147": "N\u030c", "\u1e44": "N\u0307", "\u0145": "N\u0327", "\xd3": "O\u0301", "\xd2": "O\u0300", "\xd6": "O\u0308", "\u022a": "O\u0308\u0304", "\xd5": "O\u0303", "\u1e4c": "O\u0303\u0301", "\u1e4e": "O\u0303\u0308", "\u022c": "O\u0303\u0304", "\u014c": "O\u0304", "\u1e52": "O\u0304\u0301", "\u1e50": "O\u0304\u0300", "\u014e": "O\u0306", "\u01d1": "O\u030c", "\xd4": "O\u0302", "\u1ed0": "O\u0302\u0301", "\u1ed2": "O\u0302\u0300", "\u1ed6": "O\u0302\u0303", "\u022e": "O\u0307", "\u0230": "O\u0307\u0304", "\u0150": "O\u030b", "\u1e54": "P\u0301", "\u1e56": "P\u0307", "\u0154": "R\u0301", "\u0158": "R\u030c", "\u1e58": "R\u0307", "\u0156": "R\u0327", "\u015a": "S\u0301", "\u1e64": "S\u0301\u0307", "\u0160": "S\u030c", "\u1e66": "S\u030c\u0307", "\u015c": "S\u0302", "\u1e60": "S\u0307", "\u015e": "S\u0327", "\u0164": "T\u030c", "\u1e6a": "T\u0307", "\u0162": "T\u0327", "\xda": "U\u0301", "\xd9": "U\u0300", "\xdc": "U\u0308", "\u01d7": "U\u0308\u0301", "\u01db": "U\u0308\u0300", "\u01d5": "U\u0308\u0304", "\u01d9": "U\u0308\u030c", "\u0168": "U\u0303", "\u1e78": "U\u0303\u0301", "\u016a": "U\u0304", "\u1e7a": "U\u0304\u0308", "\u016c": "U\u0306", "\u01d3": "U\u030c", "\xdb": "U\u0302", "\u016e": "U\u030a", "\u0170": "U\u030b", "\u1e7c": "V\u0303", "\u1e82": "W\u0301", "\u1e80": "W\u0300", "\u1e84": "W\u0308", "\u0174": "W\u0302", "\u1e86": "W\u0307", "\u1e8c": "X\u0308", "\u1e8a": "X\u0307", "\xdd": "Y\u0301", "\u1ef2": "Y\u0300", "\u0178": "Y\u0308", "\u1ef8": "Y\u0303", "\u0232": "Y\u0304", "\u0176": "Y\u0302", "\u1e8e": "Y\u0307", "\u0179": "Z\u0301", "\u017d": "Z\u030c", "\u1e90": "Z\u0302", "\u017b": "Z\u0307", "\u03ac": "\u03b1\u0301", "\u1f70": "\u03b1\u0300", "\u1fb1": "\u03b1\u0304", "\u1fb0": "\u03b1\u0306", "\u03ad": "\u03b5\u0301", "\u1f72": "\u03b5\u0300", "\u03ae": "\u03b7\u0301", "\u1f74": "\u03b7\u0300", "\u03af": "\u03b9\u0301", "\u1f76": "\u03b9\u0300", "\u03ca": "\u03b9\u0308", "\u0390": "\u03b9\u0308\u0301", "\u1fd2": "\u03b9\u0308\u0300", "\u1fd1": "\u03b9\u0304", "\u1fd0": "\u03b9\u0306", "\u03cc": "\u03bf\u0301", "\u1f78": "\u03bf\u0300", "\u03cd": "\u03c5\u0301", "\u1f7a": "\u03c5\u0300", "\u03cb": "\u03c5\u0308", "\u03b0": "\u03c5\u0308\u0301", "\u1fe2": "\u03c5\u0308\u0300", "\u1fe1": "\u03c5\u0304", "\u1fe0": "\u03c5\u0306", "\u03ce": "\u03c9\u0301", "\u1f7c": "\u03c9\u0300", "\u038e": "\u03a5\u0301", "\u1fea": "\u03a5\u0300", "\u03ab": "\u03a5\u0308", "\u1fe9": "\u03a5\u0304", "\u1fe8": "\u03a5\u0306", "\u038f": "\u03a9\u0301", "\u1ffa": "\u03a9\u0300" }; class Vn { constructor(e, t) { this.mode = void 0, this.gullet = void 0, this.settings = void 0, this.leftrightDepth = void 0, this.nextToken = void 0, this.mode = "math", this.gullet = new Hn(e, t, this.mode), this.settings = t, this.leftrightDepth = 0 } expect(e, t) { if (void 0 === t && (t = !0), this.fetch().text !== e) throw new n("Expected '" + e + "', got '" + this.fetch().text + "'", this.fetch()); t && this.consume() } consume() { this.nextToken = null } fetch() { return null == this.nextToken && (this.nextToken = this.gullet.expandNextToken()), this.nextToken } switchMode(e) { this.mode = e, this.gullet.switchMode(e) } parse() { this.settings.globalGroup || this.gullet.beginGroup(), this.settings.colorIsTextColor && this.gullet.macros.set("\\color", "\\textcolor"); try { const e = this.parseExpression(!1); return this.expect("EOF"), this.settings.globalGroup || this.gullet.endGroup(), e } finally { this.gullet.endGroups() } } subparse(e) { const t = this.nextToken; this.consume(), this.gullet.pushToken(new Nr("}")), this.gullet.pushTokens(e); const r = this.parseExpression(!1); return this.expect("}"), this.nextToken = t, r } parseExpression(e, t) { const r = []; for (; ;) { "math" === this.mode && this.consumeSpaces(); const n = this.fetch(); if (-1 !== Vn.endOfExpression.indexOf(n.text)) break; if (t && n.text === t) break; if (e && yn[n.text] && yn[n.text].infix) break; const o = this.parseAtom(t); if (!o) break; "internal" !== o.type && r.push(o) } return "text" === this.mode && this.formLigatures(r), this.handleInfixNodes(r) } handleInfixNodes(e) { let t, r = -1; for (let o = 0; o < e.length; o++)if ("infix" === e[o].type) { if (-1 !== r) throw new n("only one infix operator per group", e[o].token); r = o, t = e[o].replaceWith } if (-1 !== r && t) { let n, o; const s = e.slice(0, r), i = e.slice(r + 1); let a; return n = 1 === s.length && "ordgroup" === s[0].type ? s[0] : { type: "ordgroup", mode: this.mode, body: s }, o = 1 === i.length && "ordgroup" === i[0].type ? i[0] : { type: "ordgroup", mode: this.mode, body: i }, a = "\\\\abovefrac" === t ? this.callFunction(t, [n, e[r], o], []) : this.callFunction(t, [n, o], []), [a] } return e } handleSupSubscript(e) { const t = this.fetch(), r = t.text; this.consume(), this.consumeSpaces(); const o = this.parseGroup(e); if (!o) throw new n("Expected group after '" + r + "'", t); return o } formatUnsupportedCmd(e) { const t = []; for (let r = 0; r < e.length; r++)t.push({ type: "textord", mode: "text", text: e[r] }); const r = { type: "text", mode: this.mode, body: t }; return { type: "color", mode: this.mode, color: this.settings.errorColor, body: [r] } } parseAtom(e) { const t = this.parseGroup("atom", e); if ("text" === this.mode) return t; let r, o; for (; ;) { this.consumeSpaces(); const e = this.fetch(); if ("\\limits" === e.text || "\\nolimits" === e.text) { if (t && "op" === t.type) { const r = "\\limits" === e.text; t.limits = r, t.alwaysHandleSupSub = !0 } else { if (!t || "operatorname" !== t.type) throw new n("Limit controls must follow a math operator", e); t.alwaysHandleSupSub && (t.limits = "\\limits" === e.text) } this.consume() } else if ("^" === e.text) { if (r) throw new n("Double superscript", e); r = this.handleSupSubscript("superscript") } else if ("_" === e.text) { if (o) throw new n("Double subscript", e); o = this.handleSupSubscript("subscript") } else if ("'" === e.text) { if (r) throw new n("Double superscript", e); const t = { type: "textord", mode: this.mode, text: "\\prime" }, o = [t]; for (this.consume(); "'" === this.fetch().text;)o.push(t), this.consume(); "^" === this.fetch().text && o.push(this.handleSupSubscript("superscript")), r = { type: "ordgroup", mode: this.mode, body: o } } else { if (!En[e.text]) break; { const t = On.test(e.text), n = []; for (n.push(new Nr(En[e.text])), this.consume(); ;) { const e = this.fetch().text; if (!En[e]) break; if (On.test(e) !== t) break; n.unshift(new Nr(En[e])), this.consume() } const s = this.subparse(n); t ? o = { type: "ordgroup", mode: "math", body: s } : r = { type: "ordgroup", mode: "math", body: s } } } } return r || o ? { type: "supsub", mode: this.mode, base: t, sup: r, sub: o } : t } parseFunction(e, t) { const r = this.fetch(), o = r.text, s = yn[o]; if (!s) return null; if (this.consume(), t && "atom" !== t && !s.allowedInArgument) throw new n("Got function '" + o + "' with no arguments" + (t ? " as " + t : ""), r); if ("text" === this.mode && !s.allowedInText) throw new n("Can't use function '" + o + "' in text mode", r); if ("math" === this.mode && !1 === s.allowedInMath) throw new n("Can't use function '" + o + "' in math mode", r); const { args: i, optArgs: a } = this.parseArguments(o, s); return this.callFunction(o, i, a, r, e) } callFunction(e, t, r, o, s) { const i = { funcName: e, parser: this, token: o, breakOnTokenText: s }, a = yn[e]; if (a && a.handler) return a.handler(i, t, r); throw new n("No function handler for " + e) } parseArguments(e, t) { const r = t.numArgs + t.numOptionalArgs; if (0 === r) return { args: [], optArgs: [] }; const o = [], s = []; for (let i = 0; i < r; i++) { let r = t.argTypes && t.argTypes[i]; const a = i < t.numOptionalArgs; (t.primitive && null == r || "sqrt" === t.type && 1 === i && null == s[0]) && (r = "primitive"); const l = this.parseGroupOfType("argument to '" + e + "'", r, a); if (a) s.push(l); else { if (null == l) throw new n("Null argument, please report this as a bug"); o.push(l) } } return { args: o, optArgs: s } } parseGroupOfType(e, t, r) { switch (t) { case "color": return this.parseColorGroup(r); case "size": return this.parseSizeGroup(r); case "url": return this.parseUrlGroup(r); case "math": case "text": return this.parseArgumentGroup(r, t); case "hbox": { const e = this.parseArgumentGroup(r, "text"); return null != e ? { type: "styling", mode: e.mode, body: [e], style: "text" } : null } case "raw": { const e = this.parseStringGroup("raw", r); return null != e ? { type: "raw", mode: "text", string: e.text } : null } case "primitive": { if (r) throw new n("A primitive argument cannot be optional"); const t = this.parseGroup(e); if (null == t) throw new n("Expected group as " + e, this.fetch()); return t } case "original": case null: case void 0: return this.parseArgumentGroup(r); default: throw new n("Unknown group type as " + e, this.fetch()) } } consumeSpaces() { for (; " " === this.fetch().text;)this.consume() } parseStringGroup(e, t) { const r = this.gullet.scanArgument(t); if (null == r) return null; let n, o = ""; for (; "EOF" !== (n = this.fetch()).text;)o += n.text, this.consume(); return this.consume(), r.text = o, r } parseRegexGroup(e, t) { const r = this.fetch(); let o, s = r, i = ""; for (; "EOF" !== (o = this.fetch()).text && e.test(i + o.text);)s = o, i += s.text, this.consume(); if ("" === i) throw new n("Invalid " + t + ": '" + r.text + "'", r); return r.range(s, i) } parseColorGroup(e) { const t = this.parseStringGroup("color", e); if (null == t) return null; const r = /^(#[a-f0-9]{3}|#?[a-f0-9]{6}|[a-z]+)$/i.exec(t.text); if (!r) throw new n("Invalid color: '" + t.text + "'", t); let o = r[0]; return /^[0-9a-f]{6}$/i.test(o) && (o = "#" + o), { type: "color-token", mode: this.mode, color: o } } parseSizeGroup(e) { let t, r = !1; if (this.gullet.consumeSpaces(), t = e || "{" === this.gullet.future().text ? this.parseStringGroup("size", e) : this.parseRegexGroup(/^[-+]? *(?:$|\d+|\d+\.\d*|\.\d*) *[a-z]{0,2} *$/, "size"), !t) return null; e || 0 !== t.text.length || (t.text = "0pt", r = !0); const o = /([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(t.text); if (!o) throw new n("Invalid size: '" + t.text + "'", t); const s = { number: +(o[1] + o[2]), unit: o[3] }; if (!V(s)) throw new n("Invalid unit: '" + s.unit + "'", t); return { type: "size", mode: this.mode, value: s, isBlank: r } } parseUrlGroup(e) { this.gullet.lexer.setCatcode("%", 13), this.gullet.lexer.setCatcode("~", 12); const t = this.parseStringGroup("url", e); if (this.gullet.lexer.setCatcode("%", 14), this.gullet.lexer.setCatcode("~", 13), null == t) return null; const r = t.text.replace(/\\([#$%&~_^{}])/g, "$1"); return { type: "url", mode: this.mode, url: r } } parseArgumentGroup(e, t) { const r = this.gullet.scanArgument(e); if (null == r) return null; const n = this.mode; t && this.switchMode(t), this.gullet.beginGroup(); const o = this.parseExpression(!1, "EOF"); this.expect("EOF"), this.gullet.endGroup(); const s = { type: "ordgroup", mode: this.mode, loc: r.loc, body: o }; return t && this.switchMode(n), s } parseGroup(e, t) { const r = this.fetch(), o = r.text; let s; if ("{" === o || "\\begingroup" === o) { this.consume(); const e = "{" === o ? "}" : "\\endgroup"; this.gullet.beginGroup(); const t = this.parseExpression(!1, e), n = this.fetch(); this.expect(e), this.gullet.endGroup(), s = { type: "ordgroup", mode: this.mode, loc: Cr.range(r, n), body: t, semisimple: "\\begingroup" === o || void 0 } } else if (s = this.parseFunction(t, e) || this.parseSymbol(), null == s && "\\" === o[0] && !Rn.hasOwnProperty(o)) { if (this.settings.throwOnError) throw new n("Undefined control sequence: " + o, r); s = this.formatUnsupportedCmd(o), this.consume() } return s } formLigatures(e) { let t = e.length - 1; for (let r = 0; r < t; ++r) { const n = e[r], o = n.text; "-" === o && "-" === e[r + 1].text && (r + 1 < t && "-" === e[r + 2].text ? (e.splice(r, 3, { type: "textord", mode: "text", loc: Cr.range(n, e[r + 2]), text: "---" }), t -= 2) : (e.splice(r, 2, { type: "textord", mode: "text", loc: Cr.range(n, e[r + 1]), text: "--" }), t -= 1)), "'" !== o && "`" !== o || e[r + 1].text !== o || (e.splice(r, 2, { type: "textord", mode: "text", loc: Cr.range(n, e[r + 1]), text: o + o }), t -= 1) } } parseSymbol() { const e = this.fetch(); let t = e.text; if (/^\\verb[^a-zA-Z]/.test(t)) { this.consume(); let e = t.slice(5); const r = "*" === e.charAt(0); if (r && (e = e.slice(1)), e.length < 2 || e.charAt(0) !== e.slice(-1)) throw new n("\\verb assertion failed --\n please report what input caused this bug"); return e = e.slice(1, -1), { type: "verb", mode: "text", body: e, star: r } } Dn.hasOwnProperty(t[0]) && !oe[this.mode][t[0]] && (this.settings.strict && "math" === this.mode && this.settings.reportNonstrict("unicodeTextInMathMode", 'Accented Unicode text character "' + t[0] + '" used in math mode', e), t = Dn[t[0]] + t.slice(1)); const r = kn.exec(t); let o; if (r && (t = t.substring(0, r.index), "i" === t ? t = "\u0131" : "j" === t && (t = "\u0237")), oe[this.mode][t]) { this.settings.strict && "math" === this.mode && Ae.indexOf(t) >= 0 && this.settings.reportNonstrict("unicodeTextInMathMode", 'Latin-1/Unicode text character "' + t[0] + '" used in math mode', e); const r = oe[this.mode][t].group, n = Cr.range(e); let s; if (te.hasOwnProperty(r)) { const e = r; s = { type: "atom", mode: this.mode, family: e, loc: n, text: t } } else s = { type: r, mode: this.mode, loc: n, text: t }; o = s } else { if (!(t.charCodeAt(0) >= 128)) return null; this.settings.strict && (S(t.charCodeAt(0)) ? "math" === this.mode && this.settings.reportNonstrict("unicodeTextInMathMode", 'Unicode text character "' + t[0] + '" used in math mode', e) : this.settings.reportNonstrict("unknownSymbol", 'Unrecognized Unicode character "' + t[0] + '" (' + t.charCodeAt(0) + ")", e)), o = { type: "textord", mode: "text", loc: Cr.range(e), text: t } } if (this.consume(), r) for (let t = 0; t < r[0].length; t++) { const s = r[0][t]; if (!Ln[s]) throw new n("Unknown accent ' " + s + "'", e); const i = Ln[s][this.mode] || Ln[s].text; if (!i) throw new n("Accent " + s + " unsupported in " + this.mode + " mode", e); o = { type: "accent", mode: this.mode, loc: Cr.range(e), label: i, isStretchy: !1, isShifty: !0, base: o } } return o } } Vn.endOfExpression = ["}", "\\endgroup", "\\end", "\\right", "&"]; var Pn = function (e, t) { if (!("string" == typeof e || e instanceof String)) throw new TypeError("KaTeX can only parse string typed expression"); const r = new Vn(e, t); delete r.gullet.macros.current["\\df@tag"]; let o = r.parse(); if (delete r.gullet.macros.current["\\current@color"], delete r.gullet.macros.current["\\color"], r.gullet.macros.get("\\df@tag")) { if (!t.displayMode) throw new n("\\tag works only in display equations"); o = [{ type: "tag", mode: "text", body: o, tag: r.subparse([new Nr("\\df@tag")]) }] } return o }; let Fn = function (e, t, r) { t.textContent = ""; const n = Un(e, r).toNode(); t.appendChild(n) }; "undefined" != typeof document && "CSS1Compat" !== document.compatMode && ("undefined" != typeof console && console.warn("Warning: KaTeX doesn't work in quirks mode. Make sure your website has a suitable doctype."), Fn = function () { throw new n("KaTeX doesn't work in quirks mode.") }); const Gn = function (e, t, r) { if (r.throwOnError || !(e instanceof n)) throw e; const o = Ve.makeSpan(["katex-error"], [new Z(t)]); return o.setAttribute("title", e.toString()), o.setAttribute("style", "color:" + r.errorColor), o }, Un = function (e, t) { const r = new m(t); try { const t = Pn(e, r); return zt(t, e, r) } catch (t) { return Gn(t, e, r) } }; var Yn = { version: "0.16.11", render: Fn, renderToString: function (e, t) { return Un(e, t).toMarkup() }, ParseError: n, SETTINGS_SCHEMA: h, __parse: function (e, t) { const r = new m(t); return Pn(e, r) }, __renderToDomTree: Un, __renderToHTMLTree: function (e, t) { const r = new m(t); try { return function (e, t, r) { const n = mt(e, St(r)), o = Ve.makeSpan(["katex"], [n]); return Mt(o, r) }(Pn(e, r), 0, r) } catch (t) { return Gn(t, e, r) } }, __setFontMetrics: function (e, t) { T[e] = t }, __defineSymbol: se, __defineFunction: je, __defineMacro: Br, __domTree: { Span: W, Anchor: _, SymbolNode: Z, SvgNode: K, PathNode: J, LineNode: Q } }; return t = t.default }() }));
diff --git a/docs/model/hardware/install_other_devices.md b/docs/model/hardware/install_other_devices.md
new file mode 100644
index 0000000000..f5fbb4b723
--- /dev/null
+++ b/docs/model/hardware/install_other_devices.md
@@ -0,0 +1,66 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# 多硬件安装飞桨
+
+本文档主要针对昇腾 NPU 硬件平台,介绍如何安装飞桨。
+
+## 1. 昇腾 NPU 飞桨安装
+
+### 1.1 环境准备
+
+当前 PaddleOCR 支持昇腾 910B 芯片,昇腾驱动版本为 23.0.3。考虑到环境差异性,我们推荐使用飞桨官方提供的标准镜像完成环境准备。
+
+#### 拉取镜像
+
+此镜像仅为开发环境,镜像中不包含预编译的飞桨安装包,镜像中已经默认安装了昇腾算子库 CANN-8.0.RC1。
+
+```bash linenums="1"
+# 适用于 X86 架构,暂时不提供 Arch64 架构镜像
+docker pull registry.baidubce.com/device/paddle-npu:cann80RC1-ubuntu20-x86_64-gcc84-py39
+```
+
+#### 启动容器
+
+ASCEND_RT_VISIBLE_DEVICES 指定可见的 NPU 卡号
+
+```bash linenums="1"
+docker run -it --name paddle-npu-dev -v $(pwd):/work \
+ --privileged --network=host --shm-size=128G -w=/work \
+ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
+ -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
+ -v /usr/local/dcmi:/usr/local/dcmi \
+ -e ASCEND_RT_VISIBLE_DEVICES="0,1,2,3,4,5,6,7" \
+ registry.baidubce.com/device/paddle-npu:cann80RC1-ubuntu20-x86_64-gcc84-py39 /bin/bash
+```
+
+### 1.2 安装 paddle 包
+
+当前提供 Python3.9 的 wheel 安装包。如有其他 Python 版本需求,可以参考[飞桨官方文档](https://www.paddlepaddle.org.cn/install/quick)自行编译安装。
+
+#### 1. 下载安装 Python3.9 的 wheel 安装包
+
+```bash linenums="1"
+# 注意需要先安装飞桨 cpu 版本
+pip install https://paddle-model-ecology.bj.bcebos.com/paddlex/whl/paddle-device/npu/paddlepaddle-0.0.0-cp39-cp39-linux_x86_64.whl
+pip install https://paddle-model-ecology.bj.bcebos.com/paddlex/whl/paddle-device/npu/paddle_custom_npu-0.0.0-cp39-cp39-linux_x86_64.whl
+```
+
+#### 2. 验证安装包
+
+安装完成之后,运行如下命令。
+
+```bash linenums="1"
+python -c "import paddle; paddle.utils.run_check()"
+```
+
+预期得到如下输出结果
+
+```bash linenums="1"
+Running verify PaddlePaddle program ...
+PaddlePaddle works well on 1 npu.
+PaddlePaddle works well on 8 npus.
+PaddlePaddle is installed successfully! Let's start deep learning with PaddlePaddle now.
+```
diff --git a/docs/model/hardware/supported_models.md b/docs/model/hardware/supported_models.md
new file mode 100644
index 0000000000..9ca4bd5f97
--- /dev/null
+++ b/docs/model/hardware/supported_models.md
@@ -0,0 +1,12 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# PaddleOCR模型列表
+
+*多硬件安装方式请参考[多硬件安装文档](./install_other_devices.md)*
+
+| 模型名称 | 昇腾NPU |
+| ---------------- | -------- |
+| PP-OCRv4 | √ |
diff --git a/docs/model/index.md b/docs/model/index.md
new file mode 100644
index 0000000000..7929863cfb
--- /dev/null
+++ b/docs/model/index.md
@@ -0,0 +1,26 @@
+---
+comments: true
+hide:
+ - toc
+# - navigation
+---
+
+## PP-OCR 系列模型列表(更新中)
+
+| 模型简介 | 模型名称 | 推荐场景 | 检测模型| 方向分类器 | 识别模型|
+| ----- | -------------- | --------------- | ------- | ------- | ---------- |
+| 中英文超轻量 PP-OCRv4 模型(15.8M) | ch_PP-OCRv4_xx | 移动端&服务器端 | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_train.tar) |
+| 中英文超轻量 PP-OCRv3 模型(16.2M) | ch_PP-OCRv3_xx | 移动端&服务器端 | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_distill_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar) |
+| 英文超轻量 PP-OCRv3 模型(13.4M) | en_PP-OCRv3_xx | 移动端&服务器端 | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_det_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_det_distill_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_train.tar) | [推理模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_infer.tar) / [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_train.tar) |
+
+- 超轻量 OCR 系列更多模型下载(包括多语言),可以参考[PP-OCR 系列模型下载](../ppocr/model_list.md),文档分析相关模型参考[PP-Structure 系列模型下载](../ppstructure/models_list.md)
+
+### PaddleOCR 场景应用模型
+
+| 行业 | 类别 | 亮点| 文档说明| 模型下载 |
+| ---- | ---- | -------- | ---- | ----- |
+| 制造 | 数码管识别 | 数码管数据合成、漏识别调优 | [光功率计数码管字符识别](../applications/光功率计数码管字符识别.md) | [下载链接](../applications/overview.md) |
+| 金融 | 通用表单识别 | 多模态通用表单结构化提取 | [多模态表单识别](../applications/多模态表单识别.md) | [下载链接](../applications/overview.md) |
+| 交通 | 车牌识别 | 多角度图像处理、轻量模型、端侧部署 | [轻量级车牌识别](../applications/轻量级车牌识别.md) | [下载链接](../applications/overview.md) |
+
+- 更多制造、金融、交通行业的主要 OCR 垂类应用模型(如电表、液晶屏、高精度 SVTR 模型等),可参考[场景应用模型下载](../applications/overview.md)
diff --git a/docs/ppocr/blog/PP-OCRv3_introduction.md b/docs/ppocr/blog/PP-OCRv3_introduction.md
new file mode 100644
index 0000000000..dd4257a24f
--- /dev/null
+++ b/docs/ppocr/blog/PP-OCRv3_introduction.md
@@ -0,0 +1,183 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# PP-OCRv3
+
+## 1. 简介
+
+PP-OCRv3在PP-OCRv2的基础上进一步升级。整体的框架图保持了与PP-OCRv2相同的pipeline,针对检测模型和识别模型进行了优化。其中,检测模块仍基于DB算法优化,而识别模块不再采用CRNN,换成了IJCAI 2022最新收录的文本识别算法[SVTR](https://arxiv.org/abs/2205.00159),并对其进行产业适配。PP-OCRv3系统框图如下所示(粉色框中为PP-OCRv3新增策略):
+
+![img](./images/ppocrv3_framework-0052468.png)
+
+从算法改进思路上看,分别针对检测和识别模型,进行了共9个方面的改进:
+
+- 检测模块:
+ - LK-PAN:大感受野的PAN结构;
+ - DML:教师模型互学习策略;
+ - RSE-FPN:残差注意力机制的FPN结构;
+
+- 识别模块:
+ - SVTR_LCNet:轻量级文本识别网络;
+ - GTC:Attention指导CTC训练策略;
+ - TextConAug:挖掘文字上下文信息的数据增广策略;
+ - TextRotNet:自监督的预训练模型;
+ - UDML:联合互学习策略;
+ - UIM:无标注数据挖掘方案。
+
+从效果上看,速度可比情况下,多种场景精度均有大幅提升:
+
+- 中文场景,相对于PP-OCRv2中文模型提升超5%;
+- 英文数字场景,相比于PP-OCRv2英文模型提升11%;
+- 多语言场景,优化80+语种识别效果,平均准确率提升超5%。
+
+## 2. 检测优化
+
+PP-OCRv3检测模型是对PP-OCRv2中的[CML](https://arxiv.org/pdf/2109.03144.pdf)(Collaborative Mutual Learning) 协同互学习文本检测蒸馏策略进行了升级。如下图所示,CML的核心思想结合了①传统的Teacher指导Student的标准蒸馏与 ②Students网络之间的DML互学习,可以让Students网络互学习的同时,Teacher网络予以指导。PP-OCRv3分别针对教师模型和学生模型进行进一步效果优化。其中,在对教师模型优化时,提出了大感受野的PAN结构LK-PAN和引入了DML(Deep Mutual Learning)蒸馏策略;在对学生模型优化时,提出了残差注意力机制的FPN结构RSE-FPN。
+
+![img](./images/ppocrv3_det_cml.png)
+
+消融实验如下:
+
+|序号|策略|模型大小|hmean|速度(cpu + mkldnn)|
+|-|-|-|-|-|
+|baseline teacher|PP-OCR server|49.0M|83.20%|171ms|
+|teacher1|DB-R50-LK-PAN|124.0M|85.00%|396ms|
+|teacher2|DB-R50-LK-PAN-DML|124.0M|86.00%|396ms|
+|baseline student|PP-OCRv2|3.0M|83.20%|117ms|
+|student0|DB-MV3-RSE-FPN|3.6M|84.50%|124ms|
+|student1|DB-MV3-CML(teacher2)|3.0M|84.30%|117ms|
+|student2|DB-MV3-RSE-FPN-CML(teacher2)|3.60M|85.40%|124ms|
+
+测试环境: Intel Gold 6148 CPU,预测时开启MKLDNN加速。
+
+### (1)LK-PAN:大感受野的PAN结构
+
+LK-PAN (Large Kernel PAN) 是一个具有更大感受野的轻量级[PAN](https://arxiv.org/pdf/1803.01534.pdf)结构,核心是将PAN结构的path augmentation中卷积核从`3*3`改为`9*9`。通过增大卷积核,提升特征图每个位置覆盖的感受野,更容易检测大字体的文字以及极端长宽比的文字。使用LK-PAN结构,可以将教师模型的hmean从83.2%提升到85.0%。
+
+![img](./images/LKPAN.png)
+
+### (2)DML:教师模型互学习策略
+
+[DML](https://arxiv.org/abs/1706.00384) (Deep Mutual Learning)互学习蒸馏方法,如下图所示,通过两个结构相同的模型互相学习,可以有效提升文本检测模型的精度。教师模型采用DML策略,hmean从85%提升到86%。将PP-OCRv2中CML的教师模型更新为上述更高精度的教师模型,学生模型的hmean可以进一步从83.2%提升到84.3%。
+
+![img](./images/teacher_dml.png)
+
+### (3)RSE-FPN:残差注意力机制的FPN结构
+
+RSE-FPN(Residual Squeeze-and-Excitation FPN)如下图所示,引入残差结构和通道注意力结构,将FPN中的卷积层更换为通道注意力结构的RSEConv层,进一步提升特征图的表征能力。考虑到PP-OCRv2的检测模型中FPN通道数非常小,仅为96,如果直接用SEblock代替FPN中卷积会导致某些通道的特征被抑制,精度会下降。RSEConv引入残差结构会缓解上述问题,提升文本检测效果。进一步将PP-OCRv2中CML的学生模型的FPN结构更新为RSE-FPN,学生模型的hmean可以进一步从84.3%提升到85.4%。
+
+![img](./images/RSEFPN.png)
+
+## 3. 识别优化
+
+PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2205.00159)优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。直接将PP-OCRv2的识别模型,替换成SVTR_Tiny,识别准确率从74.8%提升到80.1%(+5.3%),但是预测速度慢了将近11倍,CPU上预测一条文本行,将近100ms。因此,如下图所示,PP-OCRv3采用如下6个优化策略进行识别模型加速。
+
+![img](./images/v3_rec_pipeline.png)
+
+基于上述策略,PP-OCRv3识别模型相比PP-OCRv2,在速度可比的情况下,精度进一步提升4.6%。 具体消融实验如下所示:
+
+| ID | 策略 | 模型大小 | 精度 | 预测耗时(CPU + MKLDNN)|
+|-----|-----|--------|----| --- |
+| 01 | PP-OCRv2 | 8.0M | 74.80% | 8.54ms |
+| 02 | SVTR_Tiny | 21.0M | 80.10% | 97.00ms |
+| 03 | SVTR_LCNet(h32) | 12.0M | 71.90% | 6.60ms |
+| 04 | SVTR_LCNet(h48) | 12.0M | 73.98% | 7.60ms |
+| 05 | + GTC | 12.0M | 75.80% | 7.60ms |
+| 06 | + TextConAug | 12.0M | 76.30% | 7.60ms |
+| 07 | + TextRotNet | 12.0M | 76.90% | 7.60ms |
+| 08 | + UDML | 12.0M | 78.40% | 7.60ms |
+| 09 | + UIM | 12.0M | 79.40% | 7.60ms |
+
+注: 测试速度时,实验01-03输入图片尺寸均为(3,32,320),04-08输入图片尺寸均为(3,48,320)。在实际预测时,图像为变长输入,速度会有所变化。测试环境: Intel Gold 6148 CPU,预测时开启MKLDNN加速。
+
+### (1)SVTR_LCNet:轻量级文本识别网络
+
+SVTR_LCNet是针对文本识别任务,将基于Transformer的[SVTR](https://arxiv.org/abs/2205.00159)网络和轻量级CNN网络[PP-LCNet](https://arxiv.org/abs/2109.15099) 融合的一种轻量级文本识别网络。使用该网络,预测速度优于PP-OCRv2的识别模型20%,但是由于没有采用蒸馏策略,该识别模型效果略差。此外,进一步将输入图片规范化高度从32提升到48,预测速度稍微变慢,但是模型效果大幅提升,识别准确率达到73.98%(+2.08%),接近PP-OCRv2采用蒸馏策略的识别模型效果。
+
+SVTR_Tiny 网络结构如下所示:
+
+![img](./images/svtr_tiny.png)
+
+由于 MKLDNN 加速库支持的模型结构有限,SVTR 在 CPU+MKLDNN 上相比 PP-OCRv2 慢了10倍。PP-OCRv3 期望在提升模型精度的同时,不带来额外的推理耗时。通过分析发现,SVTR_Tiny 结构的主要耗时模块为 Mixing Block,因此我们对 SVTR_Tiny 的结构进行了一系列优化(详细速度数据请参考下方消融实验表格):
+
+1. 将 SVTR 网络前半部分替换为 PP-LCNet 的前三个stage,保留4个 Global Mixing Block ,精度为76%,加速69%,网络结构如下所示:
+
+ ![img](./images/svtr_g4.png)
+
+2. 将4个 Global Mixing Block 减小到2个,精度为72.9%,加速69%,网络结构如下所示:
+
+ ![img](./images/svtr_g2.png)
+
+3. 实验发现 Global Mixing Block 的预测速度与输入其特征的shape有关,因此后移 Global Mixing Block 的位置到池化层之后,精度下降为71.9%,速度超越基于CNN结构的PP-OCRv2-baseline 22%,网络结构如下所示:
+
+ ![img](./images/LCNet_SVTR.png)
+
+具体消融实验如下所示:
+
+| ID | 策略 | 模型大小 | 精度 | 速度(CPU + MKLDNN)|
+|-----|-----|--------|----| --- |
+| 01 | PP-OCRv2-baseline | 8.0M | 69.30% | 8.54ms |
+| 02 | SVTR_Tiny | 21.0M | 80.10% | 97.00ms |
+| 03 | SVTR_LCNet(G4) | 9.2M | 76.00% | 30.00ms |
+| 04 | SVTR_LCNet(G2) | 13.0M | 72.98% | 9.37ms |
+| 05 | SVTR_LCNet(h32) | 12.0M | 71.90% | 6.60ms |
+| 06 | SVTR_LCNet(h48) | 12.0M | 73.98% | 7.60ms |
+
+注: 测试速度时,01-05输入图片尺寸均为(3,32,320); PP-OCRv2-baseline 代表没有借助蒸馏方法训练得到的模型
+
+### (2)GTC:Attention指导CTC训练策略
+
+[GTC](https://arxiv.org/pdf/2002.01276.pdf)(Guided Training of CTC),利用Attention模块CTC训练,融合多种文本特征的表达,是一种有效的提升文本识别的策略。使用该策略,预测时完全去除 Attention 模块,在推理阶段不增加任何耗时,识别模型的准确率进一步提升到75.8%(+1.82%)。训练流程如下所示:
+
+![img](./images/GTC.png)
+
+### (3)TextConAug:挖掘文字上下文信息的数据增广策略
+
+TextConAug是一种挖掘文字上下文信息的数据增广策略,主要思想来源于论文[ConCLR](https://www.cse.cuhk.edu.hk/~byu/papers/C139-AAAI2022-ConCLR.pdf),作者提出ConAug数据增广,在一个batch内对2张不同的图像进行联结,组成新的图像并进行自监督对比学习。PP-OCRv3将此方法应用到有监督的学习任务中,设计了TextConAug数据增强方法,可以丰富训练数据上下文信息,提升训练数据多样性。使用该策略,识别模型的准确率进一步提升到76.3%(+0.5%)。TextConAug示意图如下所示:
+
+![img](./images/recconaug.png)
+
+### (4)TextRotNet:自监督的预训练模型
+
+TextRotNet是使用大量无标注的文本行数据,通过自监督方式训练的预训练模型,参考于论文[STR-Fewer-Labels](https://github.com/ku21fan/STR-Fewer-Labels)。该模型可以初始化SVTR_LCNet的初始权重,从而帮助文本识别模型收敛到更佳位置。使用该策略,识别模型的准确率进一步提升到76.9%(+0.6%)。TextRotNet训练流程如下图所示:
+
+
+
+### (5)UDML:联合互学习策略
+
+UDML(Unified-Deep Mutual Learning)联合互学习是PP-OCRv2中就采用的对于文本识别非常有效的提升模型效果的策略。在PP-OCRv3中,针对两个不同的SVTR_LCNet和Attention结构,对他们之间的PP-LCNet的特征图、SVTR模块的输出和Attention模块的输出同时进行监督训练。使用该策略,识别模型的准确率进一步提升到78.4%(+1.5%)。
+
+### (6)UIM:无标注数据挖掘方案
+
+UIM(Unlabeled Images Mining)是一种非常简单的无标注数据挖掘方案。核心思想是利用高精度的文本识别大模型对无标注数据进行预测,获取伪标签,并且选择预测置信度高的样本作为训练数据,用于训练小模型。使用该策略,识别模型的准确率进一步提升到79.4%(+1%)。实际操作中,我们使用全量数据集训练高精度SVTR-Tiny模型(acc=82.5%)进行数据挖掘,点击获取[模型下载地址和使用教程](../applications/高精度中文识别模型.md)。
+
+
+
+## 4. 端到端评估
+
+经过以上优化,最终PP-OCRv3在速度可比情况下,中文场景端到端Hmean指标相比于PP-OCRv2提升5%,效果大幅提升。具体指标如下表所示:
+
+| Model | Hmean | Model Size (M) | Time Cost (CPU, ms) | Time Cost (T4 GPU, ms) |
+|-----|-----|--------|----| --- |
+| PP-OCR mobile | 50.30% | 8.1 | 356.00 | 116.00 |
+| PP-OCR server | 57.00% | 155.1 | 1056.00 | 200.00 |
+| PP-OCRv2 | 57.60% | 11.6 | 330.00 | 111.00 |
+| PP-OCRv3 | 62.90% | 15.6 | 331.00 | 86.64 |
+
+测试环境:CPU型号为Intel Gold 6148,CPU预测时开启MKLDNN加速。
+
+除了更新中文模型,本次升级也同步优化了英文数字模型,端到端效果提升11%,如下表所示:
+
+| Model | Recall | Precision | Hmean |
+|-----|-----|--------|----|
+| PP-OCR_en | 38.99% | 45.91% | 42.17% |
+| PP-OCRv3_en | 50.95% | 55.53% | 53.14% |
+
+同时,也对已支持的80余种语言识别模型进行了升级更新,在有评估集的四种语系识别准确率平均提升5%以上,如下表所示:
+
+| Model | 拉丁语系 | 阿拉伯语系 | 日语 | 韩语 |
+|-----|-----|--------|----| --- |
+| PP-OCR_mul | 69.60% | 40.50% | 38.50% | 55.40% |
+| PP-OCRv3_mul | 75.20%| 45.37% | 45.80% | 60.10% |
diff --git a/docs/ppocr/blog/PP-OCRv4_introduction.md b/docs/ppocr/blog/PP-OCRv4_introduction.md
new file mode 100644
index 0000000000..0579a607d9
--- /dev/null
+++ b/docs/ppocr/blog/PP-OCRv4_introduction.md
@@ -0,0 +1,153 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# PP-OCRv4
+
+## 1. 简介
+
+PP-OCRv4在PP-OCRv3的基础上进一步升级。整体的框架图保持了与PP-OCRv3相同的pipeline,针对检测模型和识别模型进行了数据、网络结构、训练策略等多个模块的优化。 PP-OCRv4系统框图如下所示:
+
+![img](./images/ppocrv4_framework.png)
+
+从算法改进思路上看,分别针对检测和识别模型,进行了共10个方面的改进:
+
+* 检测模块:
+ * LCNetV3:精度更高的骨干网络
+ * PFHead:并行head分支融合结构
+ * DSR: 训练中动态增加shrink ratio
+ * CML:添加Student和Teacher网络输出的KL div loss
+
+* 识别模块:
+ * SVTR_LCNetV3:精度更高的骨干网络
+ * Lite-Neck:精简的Neck结构
+ * GTC-NRTR:稳定的Attention指导分支
+ * Multi-Scale:多尺度训练策略
+ * DF: 数据挖掘方案
+ * DKD :DKD蒸馏策略
+
+从效果上看,速度可比情况下,多种场景精度均有大幅提升:
+
+* 中文场景,相对于PP-OCRv3中文模型提升超4%;
+* 英文数字场景,相比于PP-OCRv3英文模型提升6%;
+* 多语言场景,优化80个语种识别效果,平均准确率提升超8%。
+
+## 2. 检测优化
+
+PP-OCRv4检测模型在PP-OCRv3检测模型的基础上,在网络结构,训练策略,蒸馏策略三个方面做了优化。首先,PP-OCRv4检测模型使用PP-LCNetV3替换MobileNetv3,并提出并行分支融合的PFhead结构;其次,训练时动态调整shrink ratio的比例;最后,PP-OCRv4对CML的蒸馏loss进行优化,进一步提升文字检测效果。
+
+消融实验如下:
+
+|序号|策略|模型大小|hmean|速度(cpu + mkldnn)|
+|-|-|-|-|-|
+|baseline|PP-OCRv3|3.4M|78.84%|69ms|
+|baseline student|PP-OCRv3 student|3.4M|76.22%|69ms|
+|01|+PFHead|3.6M|76.97%|96ms|
+|02|+Dynamic Shrink Ratio|3.6M|78.24%|96ms|
+|03|+PP-LCNetv3|4.8M|79.08%|94ms|
+|03|+CML|4.8M|79.87%|67ms|
+
+测试环境: Intel Gold 6148 CPU,预测引擎使用openvino。
+
+### (1)PFhead:多分支融合Head结构
+
+PFhead结构如下图所示,PFHead在经过第一个转置卷积后,分别进行上采样和转置卷积,上采样的输出通过3x3卷积得到输出结果,然后和转置卷积的分支的结果级联并经过1x1卷积层,最后1x1卷积的结果和转置卷积的结果相加得到最后输出的概率图。PP-OCRv4学生检测模型使用PFhead,hmean从76.22%增加到76.97%。
+
+ ![img](./images/PFHead.png)
+
+### (2)DSR: 收缩比例动态调整策略
+
+动态shrink ratio(dynamic shrink ratio): 在训练中,shrink ratio由固定值调整为动态变化,随着训练epoch的增加,shrink ratio从0.4线性增加到0.6。该策略在PP-OCRv4学生检测模型上,hmean从76.97%提升到78.24%。
+
+### (3) PP-LCNetV3:精度更高的骨干网络
+
+PP-LCNetV3系列模型是PP-LCNet系列模型的延续,覆盖了更大的精度范围,能够适应不同下游任务的需要。PP-LCNetV3系列模型从多个方面进行了优化,提出了可学习仿射变换模块,对重参数化策略、激活函数进行了改进,同时调整了网络深度与宽度。最终,PP-LCNetV3系列模型能够在性能与效率之间达到最佳的平衡,在不同精度范围内取得极致的推理速度。使用PP-LCNetV3替换MobileNetv3 backbone,PP-OCRv4学生检测模型hmean从78.24%提升到79.08%。
+
+### (4)CML: 融合KD的互学习策略
+
+PP-OCRv4检测模型对PP-OCRv3中的CML(Collaborative Mutual Learning) 协同互学习文本检测蒸馏策略进行了优化。如下图所示,在计算Student Model和Teacher Model的distill Loss时,额外添加KL div loss,让两者输出的response maps分布接近,由此进一步提升Student网络的精度,检测Hmean从79.08%增加到79.56%,端到端指标从61.31%增加到61.87%。
+
+ ![img](./images/ppocrv4_det_cml.png)
+
+## 3. 识别优化
+
+PP-OCRv4识别模型在PP-OCRv3的基础上进一步升级。如下图所示,整体的框架图保持了与PP-OCRv3识别模型相同的pipeline,分别进行了数据、网络结构、训练策略等方面的优化。
+
+![img](./images/v4_rec_pipeline.png)
+
+经过如图所示的策略优化,PP-OCRv4识别模型相比PP-OCRv3,在速度可比的情况下,精度进一步提升4%。 具体消融实验如下所示:
+
+| ID | 策略 | 模型大小 | 精度 | 预测耗时(CPU openvino)|
+|-----|-----|--------|----| --- |
+| 01 | PP-OCRv3 | 12M | 71.50% | 8.54ms |
+| 02 | +DF | 12M | 72.70% | 8.54ms |
+| 03 | + LiteNeck + GTC | 9.6M | 73.21% | 9.09ms |
+| 04 | + PP-LCNetV3 | 11M | 74.18% | 9.8ms |
+| 05 | + multi-scale | 11M | 74.20% | 9.8ms |
+| 06 | + TextConAug | 11M | 74.72% | 9.8ms |
+| 08 | + UDML | 11M | 75.45% | 9.8ms |
+
+注: 测试速度时,输入图片尺寸均为(3,48,320)。在实际预测时,图像为变长输入,速度会有所变化。测试环境: Intel Gold 6148 CPU,预测时使用Openvino预测引擎。
+
+### (1)DF:数据挖掘方案
+
+DF(Data Filter) 是一种简单有效的数据挖掘方案。核心思想是利用已有模型预测训练数据,通过置信度和预测结果等信息,对全量的训练数据进行筛选。具体的:首先使用少量数据快速训练得到一个低精度模型,使用该低精度模型对千万级的数据进行预测,去除置信度大于0.95的样本,该部分被认为是对提升模型精度无效的冗余样本。其次使用PP-OCRv3作为高精度模型,对剩余数据进行预测,去除置信度小于0.15的样本,该部分被认为是难以识别或质量很差的样本。
+使用该策略,千万级别训练数据被精简至百万级,模型训练时间从2周减少到5天,显著提升了训练效率,同时精度提升至72.7%(+1.2%)。
+
+![img](./images/DF.png)
+
+### (2)PP-LCNetV3:精度更优的骨干网络
+
+PP-LCNetV3系列模型是PP-LCNet系列模型的延续,覆盖了更大的精度范围,能够适应不同下游任务的需要。PP-LCNetV3系列模型从多个方面进行了优化,提出了可学习仿射变换模块,对重参数化策略、激活函数进行了改进,同时调整了网络深度与宽度。最终,PP-LCNetV3系列模型能够在性能与效率之间达到最佳的平衡,在不同精度范围内取得极致的推理速度。
+
+### (3)Lite-Neck:精简参数的Neck结构
+
+Lite-Neck整体结构沿用PP-OCRv3版本的结构,在参数上稍作精简,识别模型整体的模型大小可从12M降低到8.5M,而精度不变;在CTCHead中,将Neck输出特征的维度从64提升到120,此时模型大小从8.5M提升到9.6M。
+
+### (4)GTC-NRTR:Attention指导CTC训练策略
+
+GTC(Guided Training of CTC),是PP-OCRv3识别模型的最有效的策略之一,融合多种文本特征的表达,有效的提升文本识别精度。在PP-OCRv4中使用训练更稳定的Transformer模型NRTR作为指导分支,相比V3版本中的SAR基于循环神经网络的结构,NRTR基于Transformer实现解码过程泛化能力更强,能有效指导CTC分支学习,解决简单场景下快速过拟合的问题。使用Lite-Neck和GTC-NRTR两个策略,识别精度提升至73.21%(+0.5%)。
+
+![img](./images/ppocrv4_gtc.png)
+
+### (5)Multi-Scale:多尺度训练策略
+
+动态尺度训练策略,是在训练过程中随机resize输入图片的高度,以增强识别模型在端到端串联使用时的鲁棒性。在训练时,每个iter从(32,48,64)三种高度中随机选择一种高度进行resize。实验证明,使用该策略,尽管在识别测试集上准确率没有提升,但在端到端串联评估时,指标提升0.5%。
+
+![img](./images/multi_scale.png)
+
+### (6)DKD:蒸馏策略
+
+识别模型的蒸馏包含两个部分,NRTRhead蒸馏和CTCHead蒸馏;
+
+对于NRTR head,使用了DKD loss蒸馏,拉近学生模型和教师模型的NRTR head logits。最终NRTR head的loss是学生与教师间的DKD loss和与ground truth的cross entropy loss的加权和,用于监督学生模型的backbone训练。通过实验,我们发现加入DKD loss后,计算与ground truth的cross entropy loss时去除label smoothing可以进一步提高精度,因此我们在这里使用的是不带label smoothing的cross entropy loss。
+
+对于CTCHead,由于CTC的输出中存在Blank位,即使教师模型和学生模型的预测结果一样,二者的输出的logits分布也会存在差异,影响教师模型向学生模型的知识传递。PP-OCRv4识别模型蒸馏策略中,将CTC输出logits沿着文本长度维度计算均值,将多字符识别问题转换为多字符分类问题,用于监督CTC Head的训练。使用该策略融合NRTRhead DKD蒸馏策略,指标从74.72%提升到75.45%。
+
+## 4. 端到端评估
+
+经过以上优化,最终PP-OCRv4在速度可比情况下,中文场景端到端Hmean指标相比于PP-OCRv3提升4.5%,效果大幅提升。具体指标如下表所示:
+
+| Model | Hmean | Model Size (M) | Time Cost (CPU, ms) |
+|-----|-----|--------|----|
+| PP-OCRv3 | 57.99% | 15.6 | 78 |
+| PP-OCRv4 | 62.24% | 15.8 | 76 |
+
+测试环境:CPU型号为Intel Gold 6148,CPU预测时使用openvino。
+
+除了更新中文模型,本次升级也优化了英文数字模型,在自有评估集上文本识别准确率提升6%,如下表所示:
+
+| Model | ACC |
+|-----|-----|
+| PP-OCR_en | 54.38% |
+| PP-OCRv3_en | 64.04% |
+| PP-OCRv4_en | 70.1% |
+
+同时,对已支持的80余种语言识别模型进行了升级更新,在有评估集的四种语系识别准确率平均提升8%以上,如下表所示:
+
+| Model | 拉丁语系 | 阿拉伯语系 | 日语 | 韩语 |
+|-----|-----|--------|----| --- |
+| PP-OCR_mul | 69.60% | 40.50% | 38.50% | 55.40% |
+| PP-OCRv3_mul | 71.57%| 72.90% | 45.85% | 77.23% |
+| PP-OCRv4_mul | 80.00%| 75.48% | 56.50% | 83.25% |
diff --git a/docs/ppocr/blog/clone.en.md b/docs/ppocr/blog/clone.en.md
new file mode 100644
index 0000000000..f72ce0431c
--- /dev/null
+++ b/docs/ppocr/blog/clone.en.md
@@ -0,0 +1,31 @@
+---
+comments: true
+---
+
+# Project Clone
+
+## 1. Clone PaddleOCR
+
+```bash linenums="1"
+# Recommend
+git clone https://github.com/PaddlePaddle/PaddleOCR
+
+# If you cannot pull successfully due to network problems, you can switch to the mirror hosted on Gitee:
+
+git clone https://gitee.com/paddlepaddle/PaddleOCR
+
+# Note: The mirror on Gitee may not keep in synchronization with the latest project on GitHub. There might be a delay of 3-5 days. Please try GitHub at first.
+```
+
+## 2. Install third-party libraries
+
+```bash linenums="1"
+cd PaddleOCR
+pip3 install -r requirements.txt
+```
+
+If you getting this error `OSError: [WinError 126] The specified module could not be found` when you install shapely on windows.
+
+Please try to download Shapely whl file from [http://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely](http://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely).
+
+Reference: [Solve shapely installation on windows](https://stackoverflow.com/questions/44398265/install-shapely-oserror-winerror-126-the-specified-module-could-not-be-found)
diff --git a/docs/ppocr/blog/clone.md b/docs/ppocr/blog/clone.md
new file mode 100644
index 0000000000..efc4b658d7
--- /dev/null
+++ b/docs/ppocr/blog/clone.md
@@ -0,0 +1,26 @@
+---
+comments: true
+---
+
+# 项目克隆
+
+## 1. 克隆PaddleOCR repo代码
+
+```bash linenums="1"
+git clone https://github.com/PaddlePaddle/PaddleOCR
+```
+
+如果因为网络问题无法pull成功,也可选择使用码云上的托管:
+
+```bash linenums="1"
+git clone https://gitee.com/paddlepaddle/PaddleOCR
+```
+
+注:码云托管代码可能无法实时同步本github项目更新,存在3~5天延时,请优先使用推荐方式。
+
+## 2. 安装第三方库
+
+```bash linenums="1"
+cd PaddleOCR
+pip3 install -r requirements.txt
+```
diff --git a/docs/ppocr/blog/config.en.md b/docs/ppocr/blog/config.en.md
new file mode 100644
index 0000000000..21584b403a
--- /dev/null
+++ b/docs/ppocr/blog/config.en.md
@@ -0,0 +1,245 @@
+---
+comments: true
+---
+
+# Configuration
+
+## 1. Optional Parameter List
+
+The following list can be viewed through `--help`
+
+| FLAG | Supported script | Use | Defaults | Note |
+| :----------------------: | :------------: | :---------------: | :--------------: | :-----------------: |
+| -c | ALL | Specify configuration file to use | None | **Please refer to the parameter introduction for configuration file usage** |
+| -o | ALL | set configuration options | None | Configuration using -o has higher priority than the configuration file selected with -c. E.g: -o Global.use_gpu=false |
+
+## 2. Introduction to Global Parameters of Configuration File
+
+Take rec_chinese_lite_train_v2.0.yml as an example
+
+### Global
+
+| Parameter | Use | Defaults | Note |
+| :----------------------: | :---------------------: | :--------------: | :--------------------: |
+| use_gpu | Set using GPU or not | true | \ |
+| epoch_num | Maximum training epoch number | 500 | \ |
+| log_smooth_window | Log queue length, the median value in the queue each time will be printed | 20 | \ |
+| print_batch_step | Set print log interval | 10 | \ |
+| save_model_dir | Set model save path | output/{算法名称} | \ |
+| save_epoch_step | Set model save interval | 3 | \ |
+| eval_batch_step | Set the model evaluation interval | 2000 or [1000, 2000] | running evaluation every 2000 iters or evaluation is run every 2000 iterations after the 1000th iteration |
+| cal_metric_during_train | Set whether to evaluate the metric during the training process. At this time, the metric of the model under the current batch is evaluated | true | \ |
+| load_static_weights | Set whether the pre-training model is saved in static graph mode (currently only required by the detection algorithm) | true | \ |
+| pretrained_model | Set the path of the pre-trained model | ./pretrain_models/CRNN/best_accuracy | \ |
+| checkpoints | set model parameter path | None | Used to load parameters after interruption to continue training|
+| use_visualdl | Set whether to enable visualdl for visual log display | False | [Tutorial](https://www.paddlepaddle.org.cn/paddle/visualdl) |
+| use_wandb | Set whether to enable W&B for visual log display | False | [Documentation](https://docs.wandb.ai/)
+| infer_img | Set inference image path or folder path | ./infer_img | \||
+| character_dict_path | Set dictionary path | ./ppocr/utils/ppocr_keys_v1.txt | If the character_dict_path is None, model can only recognize number and lower letters |
+| max_text_length | Set the maximum length of text | 25 | \ |
+| use_space_char | Set whether to recognize spaces | True | \| |
+| label_list | Set the angle supported by the direction classifier | ['0','180'] | Only valid in angle classifier model |
+| save_res_path | Set the save address of the test model results | ./output/det_db/predicts_db.txt | Only valid in the text detection model |
+
+### Optimizer ([ppocr/optimizer](../../ppocr/optimizer))
+
+| Parameter | Use | Defaults | Note |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| name | Optimizer class name | Adam | Currently supports`Momentum`,`Adam`,`RMSProp`, see [ppocr/optimizer/optimizer.py](../../ppocr/optimizer/optimizer.py) |
+| beta1 | Set the exponential decay rate for the 1st moment estimates | 0.9 | \ |
+| beta2 | Set the exponential decay rate for the 2nd moment estimates | 0.999 | \ |
+| clip_norm | The maximum norm value | - | \ |
+| **lr** | Set the learning rate decay method | - | \ |
+| name | Learning rate decay class name | Cosine | Currently supports`Linear`,`Cosine`,`Step`,`Piecewise`, see[ppocr/optimizer/learning_rate.py](../../ppocr/optimizer/learning_rate.py) |
+| learning_rate | Set the base learning rate | 0.001 | \ |
+| **regularizer** | Set network regularization method | - | \ |
+| name | Regularizer class name | L2 | Currently support`L1`,`L2`, see[ppocr/optimizer/regularizer.py](../../ppocr/optimizer/regularizer.py) |
+| factor | Regularizer coefficient | 0.00001 | \ |
+
+### Architecture ([ppocr/modeling](../../ppocr/modeling))
+
+In PaddleOCR, the network is divided into four stages: Transform, Backbone, Neck and Head
+
+| Parameter | Use | Defaults | Note |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| model_type | Network Type | rec | Currently support`rec`,`det`,`cls` |
+| algorithm | Model name | CRNN | See [algorithm_overview](./algorithm_overview_en.md) for the support list |
+| **Transform** | Set the transformation method | - | Currently only recognition algorithms are supported, see [ppocr/modeling/transform](../../ppocr/modeling/transforms) for details |
+| name | Transformation class name | TPS | Currently supports `TPS` |
+| num_fiducial | Number of TPS control points | 20 | Ten on the top and bottom |
+| loc_lr | Localization network learning rate | 0.1 | \ |
+| model_name | Localization network size | small | Currently support`small`,`large` |
+| **Backbone** | Set the network backbone class name | - | see [ppocr/modeling/backbones](../../ppocr/modeling/backbones) |
+| name | backbone class name | ResNet | Currently support`MobileNetV3`,`ResNet` |
+| layers | resnet layers | 34 | Currently support18,34,50,101,152,200 |
+| model_name | MobileNetV3 network size | small | Currently support`small`,`large` |
+| **Neck** | Set network neck | - | see[ppocr/modeling/necks](../../ppocr/modeling/necks) |
+| name | neck class name | SequenceEncoder | Currently support`SequenceEncoder`,`DBFPN` |
+| encoder_type | SequenceEncoder encoder type | rnn | Currently support`reshape`,`fc`,`rnn` |
+| hidden_size | rnn number of internal units | 48 | \ |
+| out_channels | Number of DBFPN output channels | 256 | \ |
+| **Head** | Set the network head | - | see[ppocr/modeling/heads](../../ppocr/modeling/heads) |
+| name | head class name | CTCHead | Currently support`CTCHead`,`DBHead`,`ClsHead` |
+| fc_decay | CTCHead regularization coefficient | 0.0004 | \ |
+| k | DBHead binarization coefficient | 50 | \ |
+| class_dim | ClsHead output category number | 2 | \ |
+
+### Loss ([ppocr/losses](../../ppocr/losses))
+
+| Parameter | Use | Defaults | Note |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| name | loss class name | CTCLoss | Currently support`CTCLoss`,`DBLoss`,`ClsLoss` |
+| balance_loss | Whether to balance the number of positive and negative samples in DBLossloss (using OHEM) | True | \ |
+| ohem_ratio | The negative and positive sample ratio of OHEM in DBLossloss | 3 | \ |
+| main_loss_type | The loss used by shrink_map in DBLossloss | DiceLoss | Currently support`DiceLoss`,`BCELoss` |
+| alpha | The coefficient of shrink_map_loss in DBLossloss | 5 | \ |
+| beta | The coefficient of threshold_map_loss in DBLossloss | 10 | \ |
+
+### PostProcess ([ppocr/postprocess](../../ppocr/postprocess))
+
+| Parameter | Use | Defaults | Note |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| name | Post-processing class name | CTCLabelDecode | Currently support`CTCLoss`,`AttnLabelDecode`,`DBPostProcess`,`ClsPostProcess` |
+| thresh | The threshold for binarization of the segmentation map in DBPostProcess | 0.3 | \ |
+| box_thresh | The threshold for filtering output boxes in DBPostProcess. Boxes below this threshold will not be output | 0.7 | \ |
+| max_candidates | The maximum number of text boxes output in DBPostProcess | 1000 | |
+| unclip_ratio | The unclip ratio of the text box in DBPostProcess | 2.0 | \ |
+
+### Metric ([ppocr/metrics](../../ppocr/metrics))
+
+| Parameter | Use | Defaults | Note |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| name | Metric method name | CTCLabelDecode | Currently support`DetMetric`,`RecMetric`,`ClsMetric` |
+| main_indicator | Main indicators, used to select the best model | acc | For the detection method is hmean, the recognition and classification method is acc |
+
+### Dataset ([ppocr/data](../../ppocr/data))
+
+| Parameter | Use | Defaults | Note |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| **dataset** | Return one sample per iteration | - | - |
+| name | dataset class name | SimpleDataSet | Currently support`SimpleDataSet`,`LMDBDataSet` |
+| data_dir | Image folder path | ./train_data | \ |
+| label_file_list | Groundtruth file path | ["./train_data/train_list.txt"] | This parameter is not required when dataset is LMDBDataSet |
+| ratio_list | Ratio of data set | [1.0] | If there are two train_lists in label_file_list and ratio_list is [0.4,0.6], 40% will be sampled from train_list1, and 60% will be sampled from train_list2 to combine the entire dataset |
+| transforms | List of methods to transform images and labels | [DecodeImage,CTCLabelEncode,RecResizeImg,KeepKeys] | see[ppocr/data/imaug](../../ppocr/data/imaug) |
+| **loader** | dataloader related | - | |
+| shuffle | Does each epoch disrupt the order of the data set | True | \ |
+| batch_size_per_card | Single card batch size during training | 256 | \ |
+| drop_last | Whether to discard the last incomplete mini-batch because the number of samples in the data set cannot be divisible by batch_size | True | \ |
+| num_workers | The number of sub-processes used to load data, if it is 0, the sub-process is not started, and the data is loaded in the main process | 8 | \ |
+
+### Weights & Biases ([W&B](../../ppocr/utils/loggers/wandb_logger.py))
+
+| Parameter | Use | Defaults | Note |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| project | Project to which the run is to be logged | uncategorized | \
+| name | Alias/Name of the run | Randomly generated by wandb | \
+| id | ID of the run | Randomly generated by wandb | \
+| entity | User or team to which the run is being logged | The logged in user | \
+| save_dir | local directory in which all the models and other data is saved | wandb | \
+| config | model configuration | None | \
+
+## 3. Multilingual Config File Generation
+
+PaddleOCR currently supports recognition for 80 languages (besides Chinese). A multi-language configuration file template is
+provided under the path `configs/rec/multi_languages`: [rec_multi_language_lite_train.yml](../../configs/rec/multi_language/rec_multi_language_lite_train.yml)。
+
+There are two ways to create the required configuration file:
+
+1. Automatically generated by script
+
+Script [generate_multi_language_configs.py](../../configs/rec/multi_language/generate_multi_language_configs.py) can help you generate configuration files for multi-language models.
+
+- Take Italian as an example, if your data is prepared in the following format:
+
+ ```text linenums="1"
+ |-train_data
+ |- it_train.txt # train_set label
+ |- it_val.txt # val_set label
+ |- data
+ |- word_001.jpg
+ |- word_002.jpg
+ |- word_003.jpg
+ | ...
+ ```
+
+ You can use the default parameters to generate a configuration file:
+
+ ```bash linenums="1"
+ # The code needs to be run in the specified directory
+ cd PaddleOCR/configs/rec/multi_language/
+ # Set the configuration file of the language to be generated through the -l or --language parameter.
+ # This command will write the default parameters into the configuration file
+ python3 generate_multi_language_configs.py -l it
+ ```
+
+- If your data is placed in another location, or you want to use your own dictionary, you can generate the configuration file by specifying the relevant parameters:
+
+ ```bash linenums="1"
+ # -l or --language field is required
+ # --train to modify the training set
+ # --val to modify the validation set
+ # --data_dir to modify the data set directory
+ # --dict to modify the dict path
+ # -o to modify the corresponding default parameters
+ cd PaddleOCR/configs/rec/multi_language/
+ python3 generate_multi_language_configs.py -l it \ # language
+ --train {path/of/train_label.txt} \ # path of train_label
+ --val {path/of/val_label.txt} \ # path of val_label
+ --data_dir {train_data/path} \ # root directory of training data
+ --dict {path/of/dict} \ # path of dict
+ -o Global.use_gpu=False # whether to use gpu
+ ...
+
+ ```
+
+Italian is made up of Latin letters, so after executing the command, you will get the rec_latin_lite_train.yml.
+
+2. Manually modify the configuration file
+
+ You can also manually modify the following fields in the template:
+
+ ```yaml linenums="1"
+ Global:
+ use_gpu: True
+ epoch_num: 500
+ ...
+ character_dict_path: {path/of/dict} # path of dict
+
+ Train:
+ dataset:
+ name: SimpleDataSet
+ data_dir: train_data/ # root directory of training data
+ label_file_list: ["./train_data/train_list.txt"] # train label path
+ ...
+
+ Eval:
+ dataset:
+ name: SimpleDataSet
+ data_dir: train_data/ # root directory of val data
+ label_file_list: ["./train_data/val_list.txt"] # val label path
+ ...
+
+ ```
+
+Currently, the multi-language algorithms supported by PaddleOCR are:
+
+| Configuration file | Algorithm name | backbone | trans | seq | pred | language |
+| :--------: | :-------: | :-------: | :-------: | :-----: | :-----: | :-----: |
+| rec_chinese_cht_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | chinese traditional |
+| rec_en_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | English(Case sensitive) |
+| rec_french_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | French |
+| rec_ger_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | German |
+| rec_japan_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | Japanese |
+| rec_korean_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | Korean |
+| rec_latin_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | Latin |
+| rec_arabic_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | arabic |
+| rec_cyrillic_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | cyrillic |
+| rec_devanagari_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | devanagari |
+
+For more supported languages, please refer to : [Multi-language model](./multi_languages.en.md)
+
+The multi-language model training method is the same as the Chinese model. The training data set is 100w synthetic data. A small amount of fonts and test data can be downloaded using the following two methods.
+
+- [Baidu Netdisk](https://pan.baidu.com/s/1bS_u207Rm7YbY33wOECKDA),Extraction code:frgi.
+- [Google drive](https://drive.google.com/file/d/18cSWX7wXSy4G0tbKJ0d9PuIaiwRLHpjA/view)
diff --git a/docs/ppocr/blog/config.md b/docs/ppocr/blog/config.md
new file mode 100644
index 0000000000..c68ebe75d2
--- /dev/null
+++ b/docs/ppocr/blog/config.md
@@ -0,0 +1,222 @@
+---
+comments: true
+---
+
+# 配置文件内容与生成
+
+## 1. 可选参数列表
+
+以下列表可以通过`--help`查看
+
+| FLAG | 支持脚本 | 用途 | 默认值 | 备注 |
+| :----------------------: | :------------: | :---------------: | :--------------: | :-----------------: |
+| -c | ALL | 指定配置文件 | None | **配置模块说明请参考 参数介绍** |
+| -o | ALL | 设置配置文件里的参数内容 | None | 使用-o配置相较于-c选择的配置文件具有更高的优先级。例如:`-o Global.use_gpu=false` |
+
+## 2. 配置文件参数介绍
+
+以 `rec_chinese_lite_train_v2.0.yml` 为例
+
+### Global
+
+| 字段 | 用途 | 默认值 | 备注 |
+| :----------------------: | :---------------------: | :--------------: | :--------------------: |
+| use_gpu | 设置代码是否在gpu运行 | true | \ |
+| epoch_num | 最大训练epoch数 | 500 | \ |
+| log_smooth_window | log队列长度,每次打印输出队列里的中间值 | 20 | \ |
+| print_batch_step | 设置打印log间隔 | 10 | \ |
+| save_model_dir | 设置模型保存路径 | output/{算法名称} | \ |
+| save_epoch_step | 设置模型保存间隔 | 3 | \ |
+| eval_batch_step | 设置模型评估间隔 | 2000 或 [1000, 2000] | 2000 表示每2000次迭代评估一次,[1000, 2000]表示从1000次迭代开始,每2000次评估一次 |
+| cal_metric_during_train | 设置是否在训练过程中评估指标,此时评估的是模型在当前batch下的指标 | true | \ |
+| load_static_weights | 设置预训练模型是否是静态图模式保存(目前仅检测算法需要) | true | \ |
+| pretrained_model | 设置加载预训练模型路径 | ./pretrain_models/CRNN/best_accuracy | \ |
+| checkpoints | 加载模型参数路径 | None | 用于中断后加载参数继续训练 |
+| use_visualdl | 设置是否启用visualdl进行可视化log展示 | False | [教程地址](https://www.paddlepaddle.org.cn/paddle/visualdl) |
+| infer_img | 设置预测图像路径或文件夹路径 | ./infer_img | \||
+| character_dict_path | 设置字典路径 | ./ppocr/utils/ppocr_keys_v1.txt | 如果为空,则默认使用小写字母+数字作为字典 |
+| max_text_length | 设置文本最大长度 | 25 | \ |
+| use_space_char | 设置是否识别空格 | True | \| |
+| label_list | 设置方向分类器支持的角度 | ['0','180'] | 仅在方向分类器中生效 |
+| save_res_path | 设置检测模型的结果保存地址 | ./output/det_db/predicts_db.txt | 仅在检测模型中生效 |
+
+### Optimizer ([ppocr/optimizer](../../ppocr/optimizer))
+
+| 字段 | 用途 | 默认值 | 备注 |
+| :---------------------: |:-------------:|:-------------:| :--------------------: |
+| name | 优化器类名 | Adam | 目前支持`Momentum`,`Adam`,`RMSProp`, 见[ppocr/optimizer/optimizer.py](../../ppocr/optimizer/optimizer.py) |
+| beta1 | 设置一阶矩估计的指数衰减率 | 0.9 | \ |
+| beta2 | 设置二阶矩估计的指数衰减率 | 0.999 | \ |
+| clip_norm | 所允许的二范数最大值 | | \ |
+| **lr** | 设置学习率decay方式 | - | \ |
+| name | 学习率decay类名 | Cosine | 目前支持`Linear`,`Cosine`,`Step`,`Piecewise`, 见[ppocr/optimizer/learning_rate.py](../../ppocr/optimizer/learning_rate.py) |
+| learning_rate | 基础学习率 | 0.001 | \ |
+| **regularizer** | 设置网络正则化方式 | - | \ |
+| name | 正则化类名 | L2 | 目前支持`L1`,`L2`, 见[ppocr/optimizer/regularizer.py](../../ppocr/optimizer/regularizer.py) |
+| factor | 正则化系数 | 0.00001 | \ |
+
+### Architecture ([ppocr/modeling](../../ppocr/modeling))
+
+在PaddleOCR中,网络被划分为Transform,Backbone,Neck和Head四个阶段
+
+| 字段 | 用途 | 默认值 | 备注 |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| model_type | 网络类型 | rec | 目前支持`rec`,`det`,`cls` |
+| algorithm | 模型名称 | CRNN | 支持列表见[algorithm_overview](./algorithm_overview.md) |
+| **Transform** | 设置变换方式 | - | 目前仅rec类型的算法支持, 具体见[ppocr/modeling/transforms](../../ppocr/modeling/transforms) |
+| name | 变换方式类名 | TPS | 目前支持`TPS` |
+| num_fiducial | TPS控制点数 | 20 | 上下边各十个 |
+| loc_lr | 定位网络学习率 | 0.1 | \ |
+| model_name | 定位网络大小 | small | 目前支持`small`,`large` |
+| **Backbone** | 设置网络backbone类名 | - | 具体见[ppocr/modeling/backbones](../../ppocr/modeling/backbones) |
+| name | backbone类名 | ResNet | 目前支持`MobileNetV3`,`ResNet` |
+| layers | resnet层数 | 34 | 支持18,34,50,101,152,200 |
+| model_name | MobileNetV3 网络大小 | small | 支持`small`,`large` |
+| **Neck** | 设置网络neck | - | 具体见[ppocr/modeling/necks](../../ppocr/modeling/necks) |
+| name | neck类名 | SequenceEncoder | 目前支持`SequenceEncoder`,`DBFPN` |
+| encoder_type | SequenceEncoder编码器类型 | rnn | 支持`reshape`,`fc`,`rnn` |
+| hidden_size | rnn内部单元数 | 48 | \ |
+| out_channels | DBFPN输出通道数 | 256 | \ |
+| **Head** | 设置网络Head | - | 具体见[ppocr/modeling/heads](../../ppocr/modeling/heads) |
+| name | head类名 | CTCHead | 目前支持`CTCHead`,`DBHead`,`ClsHead` |
+| fc_decay | CTCHead正则化系数 | 0.0004 | \ |
+| k | DBHead二值化系数 | 50 | \ |
+| class_dim | ClsHead输出分类数 | 2 | \ |
+
+### Loss ([ppocr/losses](../../ppocr/losses))
+
+| 字段 | 用途 | 默认值 | 备注 |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| name | 网络loss类名 | CTCLoss | 目前支持`CTCLoss`,`DBLoss`,`ClsLoss` |
+| balance_loss | DBLossloss中是否对正负样本数量进行均衡(使用OHEM) | True | \ |
+| ohem_ratio | DBLossloss中的OHEM的负正样本比例 | 3 | \ |
+| main_loss_type | DBLossloss中shrink_map所采用的loss | DiceLoss | 支持`DiceLoss`,`BCELoss` |
+| alpha | DBLossloss中shrink_map_loss的系数 | 5 | \ |
+| beta | DBLossloss中threshold_map_loss的系数 | 10 | \ |
+
+### PostProcess ([ppocr/postprocess](../../ppocr/postprocess))
+
+| 字段 | 用途 | 默认值 | 备注 |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| name | 后处理类名 | CTCLabelDecode | 目前支持`CTCLoss`,`AttnLabelDecode`,`DBPostProcess`,`ClsPostProcess` |
+| thresh | DBPostProcess中分割图进行二值化的阈值 | 0.3 | \ |
+| box_thresh | DBPostProcess中对输出框进行过滤的阈值,低于此阈值的框不会输出 | 0.7 | \ |
+| max_candidates | DBPostProcess中输出的最大文本框数量 | 1000 | |
+| unclip_ratio | DBPostProcess中对文本框进行放大的比例 | 2.0 | \ |
+
+### Metric ([ppocr/metrics](../../ppocr/metrics))
+
+| 字段 | 用途 | 默认值 | 备注 |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| name | 指标评估方法名称 | CTCLabelDecode | 目前支持`DetMetric`,`RecMetric`,`ClsMetric` |
+| main_indicator | 主要指标,用于选取最优模型 | acc | 对于检测方法为hmean,识别和分类方法为acc |
+
+### Dataset ([ppocr/data](../../ppocr/data))
+
+| 字段 | 用途 | 默认值 | 备注 |
+| :---------------------: | :---------------------: | :--------------: | :--------------------: |
+| **dataset** | 每次迭代返回一个样本 | - | - |
+| name | dataset类名 | SimpleDataSet | 目前支持`SimpleDataSet`和`LMDBDataSet` |
+| data_dir | 数据集图片存放路径 | ./train_data | \ |
+| label_file_list | 数据标签路径 | ["./train_data/train_list.txt"] | dataset为LMDBDataSet时不需要此参数 |
+| ratio_list | 数据集的比例 | [1.0] | 若label_file_list中有两个train_list,且ratio_list为[0.4,0.6],则从train_list1中采样40%,从train_list2中采样60%组合整个dataset |
+| transforms | 对图片和标签进行变换的方法列表 | [DecodeImage,CTCLabelEncode,RecResizeImg,KeepKeys] | 见[ppocr/data/imaug](../../ppocr/data/imaug) |
+| **loader** | dataloader相关 | - | |
+| shuffle | 每个epoch是否将数据集顺序打乱 | True | \ |
+| batch_size_per_card | 训练时单卡batch size | 256 | \ |
+| drop_last | 是否丢弃因数据集样本数不能被 batch_size 整除而产生的最后一个不完整的mini-batch | True | \ |
+| num_workers | 用于加载数据的子进程个数,若为0即为不开启子进程,在主进程中进行数据加载 | 8 | \ |
+
+## 3. 多语言配置文件生成
+
+PaddleOCR目前已支持80种(除中文外)语种识别,`configs/rec/multi_languages` 路径下提供了一个多语言的配置文件模版: [rec_multi_language_lite_train.yml](../../configs/rec/multi_language/rec_multi_language_lite_train.yml)。
+
+您有两种方式创建所需的配置文件:
+
+1. 通过脚本自动生成
+
+[generate_multi_language_configs.py](../../configs/rec/multi_language/generate_multi_language_configs.py) 可以帮助您生成多语言模型的配置文件
+
+* 以意大利语为例,如果您的数据是按如下格式准备的:
+
+ ```text linenums="1"
+ |-train_data
+ |- it_train.txt # 训练集标签
+ |- it_val.txt # 验证集标签
+ |- data
+ |- word_001.jpg
+ |- word_002.jpg
+ |- word_003.jpg
+ | ...
+ ```
+
+ 可以使用默认参数,生成配置文件:
+
+ ```bash linenums="1"
+ # 该代码需要在指定目录运行
+ cd PaddleOCR/configs/rec/multi_language/
+ # 通过-l或者--language参数设置需要生成的语种的配置文件,该命令会将默认参数写入配置文件
+ python3 generate_multi_language_configs.py -l it
+ ```
+
+* 如果您的数据放置在其他位置,或希望使用自己的字典,可以通过指定相关参数来生成配置文件:
+
+ ```bash linenums="1"
+ # -l或者--language字段是必须的
+ # --train修改训练集,--val修改验证集,--data_dir修改数据集目录,--dict修改字典路径, -o修改对应默认参数
+ cd PaddleOCR/configs/rec/multi_language/
+ python3 generate_multi_language_configs.py -l it \ # 语种
+ --train {path/of/train_label.txt} \ # 训练标签文件的路径
+ --val {path/of/val_label.txt} \ # 验证集标签文件的路径
+ --data_dir {train_data/path} \ # 训练数据的根目录
+ --dict {path/of/dict} \ # 字典文件路径
+ -o Global.use_gpu=False # 是否使用gpu
+ ...
+
+ ```
+
+意大利文由拉丁字母组成,因此执行完命令后会得到名为 rec_latin_lite_train.yml 的配置文件。
+
+2. 手动修改配置文件
+
+ 您也可以手动修改模版中的以下几个字段得到配置文件:
+
+ ```yaml linenums="1"
+ Global:
+ use_gpu: True
+ epoch_num: 500
+ ...
+ character_dict_path: {path/of/dict} # 字典文件所在路径
+
+ Train:
+ dataset:
+ name: SimpleDataSet
+ data_dir: train_data/ # 数据存放根目录
+ label_file_list: ["./train_data/train_list.txt"] # 训练集label路径
+ ...
+
+ Eval:
+ dataset:
+ name: SimpleDataSet
+ data_dir: train_data/ # 数据存放根目录
+ label_file_list: ["./train_data/val_list.txt"] # 验证集label路径
+ ...
+
+ ```
+
+目前PaddleOCR支持的多语言算法有:
+
+| 配置文件 | 算法名称 | backbone | trans | seq | pred | language |
+| :--------: | :-------: | :-------: | :-------: | :-----: | :-----: | :-----: |
+| rec_chinese_cht_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 中文繁体 |
+| rec_en_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 英语(区分大小写) |
+| rec_french_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 法语 |
+| rec_ger_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 德语 |
+| rec_japan_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 日语 |
+| rec_korean_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 韩语 |
+| rec_latin_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 拉丁字母 |
+| rec_arabic_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 阿拉伯字母 |
+| rec_cyrillic_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 斯拉夫字母 |
+| rec_devanagari_lite_train.yml | CRNN | Mobilenet_v3 small 0.5 | None | BiLSTM | ctc | 梵文字母 |
+
+更多支持语种请参考: [多语言模型](./multi_languages.md)
diff --git a/docs/ppocr/blog/customize.en.md b/docs/ppocr/blog/customize.en.md
new file mode 100644
index 0000000000..9385af7115
--- /dev/null
+++ b/docs/ppocr/blog/customize.en.md
@@ -0,0 +1,39 @@
+---
+comments: true
+---
+
+# HOW TO MAKE YOUR OWN LIGHTWEIGHT OCR MODEL?
+
+The process of making a customized ultra-lightweight OCR models can be divided into three steps: training text detection model, training text recognition model, and concatenate the predictions from previous steps.
+
+## STEP1: TRAIN TEXT DETECTION MODEL
+
+PaddleOCR provides two text detection algorithms: EAST and DB. Both support MobileNetV3 and ResNet50_vd backbone networks, select the corresponding configuration file as needed and start training. For example, to train with MobileNetV3 as the backbone network for DB detection model :
+
+```bash linenums="1"
+python3 tools/train.py -c configs/det/det_mv3_db.yml 2>&1 | tee det_db.log
+```
+
+For more details about data preparation and training tutorials, refer to the documentation [Text detection model training/evaluation/prediction](../model_train/detection.en.md)
+
+## STEP2: TRAIN TEXT RECOGNITION MODEL
+
+PaddleOCR provides four text recognition algorithms: CRNN, Rosetta, STAR-Net, and RARE. They all support two backbone networks: MobileNetV3 and ResNet34_vd, select the corresponding configuration files as needed to start training. For example, to train a CRNN recognition model that uses MobileNetV3 as the backbone network:
+
+```bash linenums="1"
+python3 tools/train.py -c configs/rec/rec_chinese_lite_train.yml 2>&1 | tee rec_ch_lite.log
+```
+
+For more details about data preparation and training tutorials, refer to the documentation [Text recognition model training/evaluation/prediction](../model_train/recognition.en.md)
+
+## STEP3: CONCATENATE PREDICTIONS
+
+PaddleOCR provides a concatenation tool for detection and recognition models, which can connect any trained detection model and any recognition model into a two-stage text recognition system. The input image goes through four main stages: text detection, text rectification, text recognition, and score filtering to output the text position and recognition results, and at the same time, you can choose to visualize the results.
+
+When performing prediction, you need to specify the path of a single image or a image folder through the parameter `image_dir`, the parameter `det_model_dir` specifies the path of detection model, and the parameter `rec_model_dir` specifies the path of recognition model. The visualized results are saved to the `./inference_results` folder by default.
+
+```bash linenums="1"
+python3 tools/infer/predict_system.py --image_dir="./doc/imgs/11.jpg" --det_model_dir="./inference/det/" --rec_model_dir="./inference/rec/"
+```
+
+For more details about text detection and recognition concatenation, please refer to the document [Inference](../infer_deploy/python_infer.en.md)
diff --git a/docs/ppocr/blog/customize.md b/docs/ppocr/blog/customize.md
new file mode 100644
index 0000000000..cf72b2d173
--- /dev/null
+++ b/docs/ppocr/blog/customize.md
@@ -0,0 +1,39 @@
+---
+comments: true
+---
+
+# 如何生产自定义超轻量模型?
+
+生产自定义的超轻量模型可分为三步:训练文本检测模型、训练文本识别模型、模型串联预测。
+
+## step1:训练文本检测模型
+
+PaddleOCR提供了EAST、DB两种文本检测算法,均支持MobileNetV3、ResNet50_vd两种骨干网络,根据需要选择相应的配置文件,启动训练。例如,训练使用MobileNetV3作为骨干网络的DB检测模型(即超轻量模型使用的配置):
+
+```bash linenums="1"
+python3 tools/train.py -c configs/det/det_mv3_db.yml 2>&1 | tee det_db.log
+```
+
+更详细的数据准备和训练教程参考文档教程中[文本检测模型训练/评估/预测](../model_train/detection.md)。
+
+## step2:训练文本识别模型
+
+PaddleOCR提供了CRNN、Rosetta、STAR-Net、RARE四种文本识别算法,均支持MobileNetV3、ResNet34_vd两种骨干网络,根据需要选择相应的配置文件,启动训练。例如,训练使用MobileNetV3作为骨干网络的CRNN识别模型(即超轻量模型使用的配置):
+
+```bash linenums="1"
+python3 tools/train.py -c configs/rec/rec_chinese_lite_train.yml 2>&1 | tee rec_ch_lite.log
+```
+
+更详细的数据准备和训练教程参考文档教程中[文本识别模型训练/评估/预测](../model_train/recognition.md)。
+
+## step3:模型串联预测
+
+PaddleOCR提供了检测和识别模型的串联工具,可以将训练好的任一检测模型和任一识别模型串联成两阶段的文本识别系统。输入图像经过文本检测、检测框矫正、文本识别、得分过滤四个主要阶段输出文本位置和识别结果,同时可选择对结果进行可视化。
+
+在执行预测时,需要通过参数image_dir指定单张图像或者图像集合的路径、参数det_model_dir指定检测inference模型的路径和参数rec_model_dir指定识别inference模型的路径。可视化识别结果默认保存到 ./inference_results 文件夹里面。
+
+```bash linenums="1"
+python3 tools/infer/predict_system.py --image_dir="./doc/imgs/11.jpg" --det_model_dir="./inference/det/" --rec_model_dir="./inference/rec/"
+```
+
+更多的文本检测、识别串联推理使用方式请参考文档教程中的[基于预测引擎推理](../infer_deploy/python_infer.md)。
diff --git a/docs/ppocr/blog/distributed_training.en.md b/docs/ppocr/blog/distributed_training.en.md
new file mode 100644
index 0000000000..dd225758a4
--- /dev/null
+++ b/docs/ppocr/blog/distributed_training.en.md
@@ -0,0 +1,65 @@
+---
+comments: true
+---
+
+# Distributed training
+
+## Introduction
+
+The high performance of distributed training is one of the core advantages of PaddlePaddle. In the classification task, distributed training can achieve almost linear speedup ratio. Generally, OCR training task need massive training data. Such as recognition, PP-OCR v2.0 model is trained based on 1800W dataset, which is very time-consuming if using single machine. Therefore, the distributed training is used in PaddleOCR to speedup the training task. For more information about distributed training, please refer to [distributed training quick start tutorial](https://fleet-x.readthedocs.io/en/latest/paddle_fleet_rst/parameter_server/ps_quick_start.html).
+
+## Quick Start
+
+### Training with single machine
+
+Take recognition as an example. After the data is prepared locally, start the training task with the interface of `paddle.distributed.launch`. The start command as follows:
+
+```bash linenums="1"
+python3 -m paddle.distributed.launch \
+ --log_dir=./log/ \
+ --gpus "0,1,2,3,4,5,6,7" \
+ tools/train.py \
+ -c configs/rec/rec_mv3_none_bilstm_ctc.yml
+```
+
+### Training with multi machine
+
+Compared with single machine, training with multi machine only needs to add the parameter `--ips` to start command, which represents the IP list of machines used for distributed training, and the IP of different machines are separated by commas. The start command as follows:
+
+```bash linenums="1"
+ip_list="192.168.0.1,192.168.0.2"
+python3 -m paddle.distributed.launch \
+ --log_dir=./log/ \
+ --ips="${ip_list}" \
+ --gpus="0,1,2,3,4,5,6,7" \
+ tools/train.py \
+ -c configs/rec/rec_mv3_none_bilstm_ctc.yml
+```
+
+**Notice:**
+
+* The IP addresses of different machines need to be separated by commas, which can be queried through `ifconfig` or `ipconfig`.
+* Different machines need to be set to be secret free and can `ping` success with others directly, otherwise communication cannot establish between them.
+* The code, data and start command between different machines must be completely consistent and then all machines need to run start command. The first machine in the `ip_list` is set to `trainer0`, and so on.
+
+## Performance comparison
+
+We conducted model training on 2x8 P40 GPUs. Accuracy, training time, and multi machine acceleration ratio of different models are shown below.
+
+| Model | Configuration | Configuration | 8 GPU training time / Accuracy | 3x8 GPU training time / Accuracy | Acceleration ratio |
+|:------:|:-----:|:--------:|:--------:|:--------:|:-----:|
+| CRNN | [rec_chinese_lite_train_v2.0.yml](../../configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml) | 260k Chinese dataset | 2.50d/66.70% | 1.67d/67.00% | **1.5** |
+
+We conducted model training on 3x8 V100 GPUs. Accuracy, training time, and multi machine acceleration ratio of different models are shown below.
+
+| Model | Configuration | Configuration | 8 GPU training time / Accuracy | 3x8 GPU training time / Accuracy | Acceleration ratio |
+|:------:|:-----:|:--------:|:--------:|:--------:|:-----:|
+| SLANet | [SLANet.yml](../../configs/table/SLANet.yml) | PubTabNet | 49.80h/76.20% | 19.75h/74.77% | **2.52** |
+
+Note: when training with 3x8 GPUs, the single card batch size is unchanged compared with the 1x8 GPUs' training process, and the learning rate is multiplied by 2 (if it is multiplied by 3 by default, the accuracy is only 73.42%).
+
+We conducted model training on 4x8 V100 GPUs. Accuracy, training time, and multi machine acceleration ratio of different models are shown below.
+
+| Model | Configuration | Configuration | 8 GPU training time / Accuracy | 4x8 GPU training time / Accuracy | Acceleration ratio |
+|:------:|:-----:|:--------:|:--------:|:--------:|:-----:|
+| SVTR | [ch_PP-OCRv3_rec_distillation.yml](../../configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml) | PP-OCRv3_rec data | 10d/- | 2.84d/74.00% | **3.5** |
diff --git a/docs/ppocr/blog/distributed_training.md b/docs/ppocr/blog/distributed_training.md
new file mode 100644
index 0000000000..182c67d17c
--- /dev/null
+++ b/docs/ppocr/blog/distributed_training.md
@@ -0,0 +1,67 @@
+---
+comments: true
+---
+
+# 分布式训练
+
+## 简介
+
+* 分布式训练的高性能,是飞桨的核心优势技术之一,在分类任务上,分布式训练可以达到几乎线性的加速比。OCR训练任务中往往包含大量训练数据,以识别为例,ppocrv2.0模型在训练时使用了1800W数据,如果使用单机训练,会非常耗时。因此,PaddleOCR中使用分布式训练接口完成训练任务,同时支持单机训练与多机训练。更多关于分布式训练的方法与文档可以参考:[分布式训练快速开始教程](https://fleet-x.readthedocs.io/en/latest/paddle_fleet_rst/parameter_server/ps_quick_start.html)。
+
+## 使用方法
+
+### 单机训练
+
+* 以识别为例,本地准备好数据之后,使用`paddle.distributed.launch`的接口启动训练任务即可。下面为运行代码示例。
+
+```bash linenums="1"
+python3 -m paddle.distributed.launch \
+ --log_dir=./log/ \
+ --gpus "0,1,2,3,4,5,6,7" \
+ tools/train.py \
+ -c configs/rec/rec_mv3_none_bilstm_ctc.yml
+```
+
+### 多机训练
+
+* 相比单机训练,多机训练时,只需要添加`--ips`的参数,该参数表示需要参与分布式训练的机器的ip列表,不同机器的ip用逗号隔开。下面为运行代码示例。
+
+```bash linenums="1"
+ip_list="192.168.0.1,192.168.0.2"
+python3 -m paddle.distributed.launch \
+ --log_dir=./log/ \
+ --ips="${ip_list}" \
+ --gpus="0,1,2,3,4,5,6,7" \
+ tools/train.py \
+ -c configs/rec/rec_mv3_none_bilstm_ctc.yml
+```
+
+**注:**
+
+* 不同机器的ip信息需要用逗号隔开,可以通过`ifconfig`或者`ipconfig`查看。
+* 不同机器之间需要做免密设置,且可以直接ping通,否则无法完成通信。
+* 不同机器之间的代码、数据与运行命令或脚本需要保持一致,且所有的机器上都需要运行设置好的训练命令或者脚本。最终`ip_list`中的第一台机器的第一块设备是trainer0,以此类推。
+
+## 性能效果测试
+
+在2机8卡P40的机器上进行模型训练,不同模型的精度、训练耗时、多机加速比情况如下所示。
+
+| 模型 | 配置 | 数据集 | 单机8卡耗时/精度 | 2机8卡耗时/精度 | 加速比 |
+|:------:|:-----:|:--------:|:--------:|:--------:|:-----:|
+| CRNN | [rec_chinese_lite_train_v2.0.yml](../../configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml) | 26W中文数据集 | 2.50d/66.7% | 1.67d/67.0% | **1.5** |
+
+在3机8卡V100的机器上进行模型训练,不同模型的精度、训练耗时、多机加速比情况如下所示。
+
+| 模型 | 配置 | 数据集 | 单机8卡耗时/精度 | 3机8卡耗时/精度 | 加速比 |
+|:------:|:-----:|:--------:|:--------:|:--------:|:-----:|
+| SLANet | [SLANet.yml](../../configs/table/SLANet.yml) | PubTabNet | 49.8h/76.2% | 19.75h/74.77% | **2.52** |
+
+注意:这里3机8卡训练时,单卡batch size相比于单机8卡不变,学习率乘以2 (默认乘以3的话,精度仅有73.42%)
+
+在4机8卡V100的机器上进行模型训练,不同模型的精度、训练耗时、多机加速比情况如下所示。
+
+| 模型 | 配置 | 数据集 | 单机8卡耗时/精度 | 4机8卡耗时/精度 | 加速比 |
+|:------:|:-----:|:--------:|:--------:|:--------:|:-----:|
+| SVTR | [ch_PP-OCRv3_rec_distillation.yml](../../configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml) | PP-OCRv3_rec data | 10d/- | 2.84d/74.0% | **3.5** |
+
+**注意**: 在训练的GPU卡数过多时,精度会稍微有所损失(1%左右),此时可以尝试通过添加warmup或者适当增加迭代轮数来弥补精度损失。
diff --git a/docs/ppocr/blog/enhanced_ctc_loss.en.md b/docs/ppocr/blog/enhanced_ctc_loss.en.md
new file mode 100644
index 0000000000..0bd4be56d5
--- /dev/null
+++ b/docs/ppocr/blog/enhanced_ctc_loss.en.md
@@ -0,0 +1,100 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# Enhanced CTC Loss
+
+In OCR recognition, CRNN is a text recognition algorithm widely applied in the industry. In the training phase, it uses CTCLoss to calculate the network loss. In the inference phase, it uses CTCDecode to obtain the decoding result. Although the CRNN algorithm has been proven to achieve reliable recognition results in actual business, users have endless requirements for recognition accuracy. So how to improve the accuracy of text recognition? Taking CTCLoss as the starting point, this paper explores the improved fusion scheme of CTCLoss from three different perspectives: Hard Example Mining, Multi-task Learning, and Metric Learning. Based on the exploration, we propose EnhancedCTCLoss, which includes the following 3 components: Focal-CTC Loss, A-CTC Loss, C-CTC Loss.
+
+## 1. Focal-CTC Loss
+
+Focal Loss was proposed by the paper, "[Focal Loss for Dense Object Detection](https://arxiv.org/abs/1708.02002)". When the loss was first proposed, it was mainly to solve the problem of a serious imbalance in the ratio of positive and negative samples in one-stage target detection. This loss function reduces the weight of a large number of simple negative samples in training and also can be understood as a kind of difficult sample mining.
+The form of the loss function is as follows:
+
+$$
+\begin{equation}
+L_{fl}=\left\{
+\begin{array}{cl}
+-\alpha(1 - y^{'})^{\gamma}logy^{'} ,& y = 1 \\
+-(1 - \alpha)y^{'\gamma}log(1 - y^{'}), & y = 0 \\
+\end{array} \right.
+\end{equation}
+$$
+
+Among them, y' is the output of the activation function, and the value is between 0-1. It adds a modulation factor (1-y’)^γ and a balance factor α on the basis of the original cross-entropy loss. When α = 1, y = 1, the comparison between the loss function and the cross-entropy loss is shown in the following figure:
+
+![img](./images/focal_loss_image.png)
+
+As can be seen from the above figure, when γ > 0, the adjustment coefficient (1-y’)^γ gives smaller weight to the easy-to-classify sample loss, making the network pay more attention to the difficult and misclassified samples. The adjustment factor γ is used to adjust the rate at which the weight of simple samples decreases. When γ = 0, it is the cross-entropy loss function. When γ increases, the influence of the adjustment factor will also increase. Experiments revealed that 2 is the optimal value of γ. The balance factor α is used to balance the uneven proportions of the positive and negative samples. In the text, α is taken as 0.25.
+
+For the classic CTC algorithm, suppose a certain feature sequence (f1, f2, ......ft), after CTC decoding, the probability that the result is equal to label is y', then the probability that the CTC decoding result is not equal to label is (1-y'); it is not difficult to find that the CTCLoss value and y' have the following relationship:
+
+$$
+L_{CTC} = -log(y^{'})
+$$
+
+Combining the idea of Focal Loss, assigning larger weights to difficult samples and smaller weights to simple samples can make the network focus more on the mining of difficult samples and further improve the accuracy of recognition. Therefore, we propose Focal-CTC Loss. Its definition is as follows:
+
+$$
+L_{Focal\_CTC} = \alpha * (1 - y^{'})^{\gamma} * L_{CTC}
+$$
+
+In the experiment, the value of γ is 2, α = 1, see this for specific implementation: [rec_ctc_loss.py](../../ppocr/losses/rec_ctc_loss.py)
+
+## 2. A-CTC Loss
+A-CTC Loss is short for CTC Loss + ACE Loss. Among them, ACE Loss was proposed by the paper, “[Aggregation Cross-Entropy for Sequence Recognition](https://arxiv.org/abs/1904.08364)”. Compared with CTCLoss, ACE Loss has the following two advantages:
++ ACE Loss can solve the recognition problem of 2-D text, while CTCLoss can only process 1-D text
++ ACE Loss is better than CTC loss in time complexity and space complexity
+
+The advantages and disadvantages of the OCR recognition algorithm summarized by the predecessors are shown in the following figure:
+
+![img](./images/rec_algo_compare.png)
+
+Although ACELoss does handle 2D predictions, as shown in the figure above, and has advantages in memory usage and inference speed, in practice, we found that using ACELoss alone, the recognition effect is not as good as CTCLoss. Consequently, we tried to combine CTCLoss and ACELoss, and CTCLoss is the mainstay while ACELoss acts as an auxiliary supervision loss. This attempt has achieved better results. On our internal experimental data set, compared to using CTCLoss alone, the recognition accuracy can be improved by about 1%.
+A_CTC Loss is defined as follows:
+
+$$
+L_{A-CTC} = L_{CTC} + \lambda * L_{ACE}
+$$
+
+In the experiment, λ = 0.1. See the ACE loss implementation code: [ace_loss.py](../../ppocr/losses/ace_loss.py)
+
+## 3. C-CTC Loss
+C-CTC Loss is short for CTC Loss + Center Loss. Among them, Center Loss was proposed by the paper, “[A Discriminative Feature Learning Approach for Deep Face Recognition](https://link.springer.com/chapter/10.1007/978-3-319-46478-7_31)“. It was first used in face recognition tasks to increase the distance between classes and reduce the distance within classes. It is an earlier and also widely used algorithm.
+
+In the task of Chinese OCR recognition, through the analysis of bad cases, we found that a major difficulty in Chinese recognition is that there are many similar characters, which are easy to misunderstand. From this, we thought about whether we can learn from the idea of n to increase the class spacing of similar characters, to improve recognition accuracy. However, Metric Learning is mainly used in the field of image recognition, and the label of the training data is a fixed value; for OCR recognition, it is a sequence recognition task essentially, and there is no explicit alignment between features and labels. Therefore, how to combine the two is still a direction worth exploring.
+
+By trying Arcmargin, Cosmargin and other methods, we finally found that Centerloss can help further improve the accuracy of recognition. C_CTC Loss is defined as follows:
+
+$$
+L_{C-CTC} = L_{CTC} + \lambda * L_{center}
+$$
+
+In the experiment, we set λ=0.25. See the center_loss implementation code: [center_loss.py](../../ppocr/losses/center_loss.py)
+
+It is worth mentioning that in C-CTC Loss, choosing to initialize the Center randomly does not bring significant improvement. Our Center initialization method is as follows:
++ Based on the original CTCLoss, a network N is obtained by training
++ Select the training set, identify the completely correct part, and form the set G
++ Send each sample in G to the network, perform forward calculation, and extract the correspondence between the input of the last FC layer (ie feature) and the result of argmax calculation (ie index)
++ Aggregate features with the same index, calculate the average, and get the initial center of each character.
+
+Taking the configuration file `configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml` as an example, the center extraction command is as follows:
+
+```bash linenums="1"
+python tools/export_center.py -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml -o Global.pretrained_model="./output/rec_mobile_pp-OCRv2/best_accuracy"
+```
+
+After running, `train_center.pkl` will be generated in the main directory of PaddleOCR.
+
+## 4. Experiment
+
+For the above three solutions, we conducted training and evaluation based on Baidu's internal data set. The experimental conditions are shown in the following table:
+
+| algorithm | Focal_CTC | A_CTC | C-CTC |
+| :-------- | :-------- | ----: | :---: |
+| gain | +0.3% | +0.7% | +1.7% |
+
+Based on the above experimental conclusions, we adopted the C-CTC strategy in PP-OCRv2. It is worth mentioning that, because PP-OCRv2 deals with the recognition task of 6625 Chinese characters, the character set is relatively large and there are many similar characters, so the C-CTC solution brings a significant improvement on this task. But if you switch to other OCR recognition tasks, the conclusion may be different. You can try Focal-CTC, A-CTC, C-CTC, and the combined solution EnhancedCTC. We believe it will bring different degrees of improvement.
+
+The unified combined plan is shown in the following file: [rec_enhanced_ctc_loss.py](../../ppocr/losses/rec_enhanced_ctc_loss.py)
diff --git a/docs/ppocr/blog/enhanced_ctc_loss.md b/docs/ppocr/blog/enhanced_ctc_loss.md
new file mode 100644
index 0000000000..5c28c306c8
--- /dev/null
+++ b/docs/ppocr/blog/enhanced_ctc_loss.md
@@ -0,0 +1,101 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# Enhanced CTC Loss
+
+在OCR识别中, CRNN是一种在工业界广泛使用的文字识别算法。 在训练阶段,其采用CTCLoss来计算网络损失; 在推理阶段,其采用CTCDecode来获得解码结果。虽然CRNN算法在实际业务中被证明能够获得很好的识别效果, 然而用户对识别准确率的要求却是无止境的,如何进一步提升文字识别的准确率呢? 本文以CTCLoss为切人点,分别从难例挖掘、 多任务学习、 Metric Learning 3个不同的角度探索了CTCLoss的改进融合方案,提出了EnhancedCTCLoss,其包括如下3个组成部分: Focal-CTC Loss,A-CTC Loss, C-CTC Loss。
+
+## 1. Focal-CTC Loss
+
+Focal Loss 出自论文《Focal Loss for Dense Object Detection》, 该loss最先提出的时候主要是为了解决one-stage目标检测中正负样本比例严重失衡的问题。该损失函数降低了大量简单负样本在训练中所占的权重,也可理解为一种困难样本挖掘。
+其损失函数形式如下:
+
+$$
+\begin{equation}
+L_{fl}=\left\{
+\begin{array}{cl}
+-\alpha(1 - y^{'})^{\gamma}logy^{'} ,& y = 1 \\
+-(1 - \alpha)y^{'\gamma}log(1 - y^{'}), & y = 0 \\
+\end{array} \right.
+\end{equation}
+$$
+
+其中, y' 是经过激活函数的输出,取值在0-1之间。其在原始的交叉熵损失的基础上加了一个调制系数(1 – y’)^ γ和平衡因子α。 当α = 1,y=1时,其损失函数与交叉熵损失的对比如下图所示:
+
+![img](./images/focal_loss_image.png)
+
+从上图可以看到, 当γ> 0时,调整系数(1-y’)^γ 赋予易分类样本损失一个更小的权重,使得网络更关注于困难的、错分的样本。 调整因子γ用于调节简单样本权重降低的速率,当γ为0时即为交叉熵损失函数,当γ增加时,调整因子的影响也会随之增大。实验发现γ为2是最优。平衡因子α用来平衡正负样本本身的比例不均,文中α取0.25。
+
+对于经典的CTC算法,假设某个特征序列(f1, f2, ......ft), 经过CTC解码之后结果等于label的概率为y’, 则CTC解码结果不为label的概率即为(1-y’);不难发现, CTCLoss值和y’有如下关系:
+
+$$
+L_{CTC} = -log(y^{'})
+$$
+
+结合Focal Loss的思想,赋予困难样本较大的权重,简单样本较小的权重,可以使网络更加聚焦于对困难样本的挖掘,进一步提升识别的准确率,由此我们提出了Focal-CTC Loss; 其定义如下所示:
+
+$$
+L_{Focal\_CTC} = \alpha * (1 - y^{'})^{\gamma} * L_{CTC}
+$$
+
+实验中,γ取值为2, α= 1, 具体实现见: [rec_ctc_loss.py](../../ppocr/losses/rec_ctc_loss.py)
+
+## 2. A-CTC Loss
+
+A-CTC Loss是CTC Loss + ACE Loss的简称。 其中ACE Loss出自论文< Aggregation Cross-Entropy for Sequence Recognition>. ACE Loss相比于CTCLoss,主要有如下两点优势:
+
++ ACE Loss能够解决2-D文本的识别问题; CTCLoss只能够处理1-D文本
++ ACE Loss 在时间复杂度和空间复杂度上优于CTC loss
+
+前人总结的OCR识别算法的优劣如下图所示:
+
+![img](./images/rec_algo_compare.png)
+
+虽然ACELoss确实如上图所说,可以处理2D预测,在内存占用及推理速度方面具备优势,但在实践过程中,我们发现单独使用ACE Loss, 识别效果并不如CTCLoss. 因此,我们尝试将CTCLoss和ACELoss进行结合,同时以CTCLoss为主,将ACELoss 定位为一个辅助监督loss。 这一尝试收到了效果,在我们内部的实验数据集上,相比单独使用CTCLoss,识别准确率可以提升1%左右。
+A_CTC Loss定义如下:
+
+$$
+L_{A-CTC} = L_{CTC} + \lambda * L_{ACE}
+$$
+
+实验中,λ = 0.1. ACE loss实现代码见: [ace_loss.py](../../ppocr/losses/ace_loss.py)
+
+## 3. C-CTC Loss
+
+C-CTC Loss是CTC Loss + Center Loss的简称。 其中Center Loss出自论文 < A Discriminative Feature Learning Approach for Deep Face Recognition>. 最早用于人脸识别任务,用于增大类间距离,减小类内距离, 是Metric Learning领域一种较早的、也比较常用的一种算法。
+在中文OCR识别任务中,通过对badcase分析, 我们发现中文识别的一大难点是相似字符多,容易误识。 由此我们想到是否可以借鉴Metric Learing的想法, 增大相似字符的类间距,从而提高识别准确率。然而,MetricLearning主要用于图像识别领域,训练数据的标签为一个固定的值;而对于OCR识别来说,其本质上是一个序列识别任务,特征和label之间并不具有显式的对齐关系,因此两者如何结合依然是一个值得探索的方向。
+通过尝试Arcmargin, Cosmargin等方法, 我们最终发现Centerloss 有助于进一步提升识别的准确率。C_CTC Loss定义如下:
+
+$$
+L_{C-CTC} = L_{CTC} + \lambda * L_{center}
+$$
+
+实验中,我们设置λ=0.25. center_loss实现代码见: [center_loss.py](../../ppocr/losses/center_loss.py)
+
+值得一提的是, 在C-CTC Loss中,选择随机初始化Center并不能够带来明显的提升. 我们的Center初始化方法如下:
+
++ 基于原始的CTCLoss, 训练得到一个网络N
++ 挑选出训练集中,识别完全正确的部分, 组成集合G
++ 将G中的每个样本送入网络,进行前向计算, 提取最后一个FC层的输入(即feature)及其经过argmax计算的结果(即index)之间的对应关系
++ 将相同index的feature进行聚合,计算平均值,得到各自字符的初始center.
+
+以配置文件`configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml`为例, center提取命令如下所示:
+
+```bash linenums="1"
+python tools/export_center.py -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml -o Global.pretrained_model="./output/rec_mobile_pp-OCRv2/best_accuracy"
+```
+
+运行完后,会在PaddleOCR主目录下生成`train_center.pkl`.
+
+## 4. 实验
+
+对于上述的三种方案,我们基于百度内部数据集进行了训练、评测,实验情况如下表所示:
+
+|algorithm| Focal_CTC | A_CTC | C-CTC |
+|:------| :------| ------: | :------: |
+|gain| +0.3% | +0.7% | +1.7% |
+
+基于上述实验结论,我们在PP-OCRv2中,采用了C-CTC的策略。 值得一提的是,由于PP-OCRv2 处理的是6625个中文字符的识别任务,字符集比较大,形似字较多,所以在该任务上C-CTC 方案带来的提升较大。 但如果换做其他OCR识别任务,结论可能会有所不同。大家可以尝试Focal-CTC,A-CTC, C-CTC以及组合方案EnhancedCTC,相信会带来不同程度的提升效果。
+统一的融合方案见如下文件: [rec_enhanced_ctc_loss.py](../../ppocr/losses/rec_enhanced_ctc_loss.py)
diff --git a/docs/ppocr/blog/images/11_det.jpg b/docs/ppocr/blog/images/11_det.jpg
new file mode 100644
index 0000000000..fe0cd23cc2
Binary files /dev/null and b/docs/ppocr/blog/images/11_det.jpg differ
diff --git a/docs/ppocr/blog/images/11_det_rec.jpg b/docs/ppocr/blog/images/11_det_rec.jpg
new file mode 100644
index 0000000000..31c566478f
Binary files /dev/null and b/docs/ppocr/blog/images/11_det_rec.jpg differ
diff --git a/docs/ppocr/blog/images/12_det.jpg b/docs/ppocr/blog/images/12_det.jpg
new file mode 100644
index 0000000000..71627f0b8d
Binary files /dev/null and b/docs/ppocr/blog/images/12_det.jpg differ
diff --git a/docs/ppocr/blog/images/12_det_rec.jpg b/docs/ppocr/blog/images/12_det_rec.jpg
new file mode 100644
index 0000000000..9db8b57e12
Binary files /dev/null and b/docs/ppocr/blog/images/12_det_rec.jpg differ
diff --git a/docs/ppocr/blog/images/187578511-9f3c351e-b68c-4359-a6e5-475810993c61.png b/docs/ppocr/blog/images/187578511-9f3c351e-b68c-4359-a6e5-475810993c61.png
new file mode 100644
index 0000000000..86c062684a
Binary files /dev/null and b/docs/ppocr/blog/images/187578511-9f3c351e-b68c-4359-a6e5-475810993c61.png differ
diff --git a/docs/ppocr/blog/images/254-20240709081442260.jpg b/docs/ppocr/blog/images/254-20240709081442260.jpg
new file mode 100644
index 0000000000..c871fb042c
Binary files /dev/null and b/docs/ppocr/blog/images/254-20240709081442260.jpg differ
diff --git a/docs/ppocr/blog/images/5e612.png b/docs/ppocr/blog/images/5e612.png
new file mode 100644
index 0000000000..79e3d0e1e2
Binary files /dev/null and b/docs/ppocr/blog/images/5e612.png differ
diff --git a/docs/ppocr/blog/images/DF.png b/docs/ppocr/blog/images/DF.png
new file mode 100644
index 0000000000..f14953d481
Binary files /dev/null and b/docs/ppocr/blog/images/DF.png differ
diff --git a/docs/ppocr/blog/images/GTC.png b/docs/ppocr/blog/images/GTC.png
new file mode 100644
index 0000000000..30a9cdd146
Binary files /dev/null and b/docs/ppocr/blog/images/GTC.png differ
diff --git a/docs/ppocr/blog/images/LCNet_SVTR.png b/docs/ppocr/blog/images/LCNet_SVTR.png
new file mode 100644
index 0000000000..7f0d701d27
Binary files /dev/null and b/docs/ppocr/blog/images/LCNet_SVTR.png differ
diff --git a/docs/ppocr/blog/images/LKPAN.png b/docs/ppocr/blog/images/LKPAN.png
new file mode 100644
index 0000000000..6b16053623
Binary files /dev/null and b/docs/ppocr/blog/images/LKPAN.png differ
diff --git a/docs/ppocr/blog/images/PFHead.png b/docs/ppocr/blog/images/PFHead.png
new file mode 100644
index 0000000000..3728dc44e5
Binary files /dev/null and b/docs/ppocr/blog/images/PFHead.png differ
diff --git a/docs/ppocr/blog/images/RSEFPN.png b/docs/ppocr/blog/images/RSEFPN.png
new file mode 100644
index 0000000000..ddf7c52fb5
Binary files /dev/null and b/docs/ppocr/blog/images/RSEFPN.png differ
diff --git a/docs/ppocr/blog/images/SSL.png b/docs/ppocr/blog/images/SSL.png
new file mode 100644
index 0000000000..1344a2a77c
Binary files /dev/null and b/docs/ppocr/blog/images/SSL.png differ
diff --git a/docs/ppocr/blog/images/UIM.png b/docs/ppocr/blog/images/UIM.png
new file mode 100644
index 0000000000..7479bdf4a9
Binary files /dev/null and b/docs/ppocr/blog/images/UIM.png differ
diff --git a/docs/ppocr/blog/images/arabic_0.jpg b/docs/ppocr/blog/images/arabic_0.jpg
new file mode 100644
index 0000000000..9941b90642
Binary files /dev/null and b/docs/ppocr/blog/images/arabic_0.jpg differ
diff --git a/docs/ppocr/blog/images/focal_loss_image.png b/docs/ppocr/blog/images/focal_loss_image.png
new file mode 100644
index 0000000000..430550a732
Binary files /dev/null and b/docs/ppocr/blog/images/focal_loss_image.png differ
diff --git a/docs/ppocr/blog/images/french_0.jpg b/docs/ppocr/blog/images/french_0.jpg
new file mode 100644
index 0000000000..3c2abe6304
Binary files /dev/null and b/docs/ppocr/blog/images/french_0.jpg differ
diff --git a/docs/ppocr/blog/images/img_02.jpg b/docs/ppocr/blog/images/img_02.jpg
new file mode 100644
index 0000000000..3e139c76bc
Binary files /dev/null and b/docs/ppocr/blog/images/img_02.jpg differ
diff --git a/docs/ppocr/blog/images/img_12.jpg b/docs/ppocr/blog/images/img_12.jpg
new file mode 100644
index 0000000000..822d562eda
Binary files /dev/null and b/docs/ppocr/blog/images/img_12.jpg differ
diff --git a/docs/ppocr/blog/images/japan_2-20240709081138234.jpg b/docs/ppocr/blog/images/japan_2-20240709081138234.jpg
new file mode 100644
index 0000000000..7038ba2eff
Binary files /dev/null and b/docs/ppocr/blog/images/japan_2-20240709081138234.jpg differ
diff --git a/docs/ppocr/blog/images/korean.jpg b/docs/ppocr/blog/images/korean.jpg
new file mode 100644
index 0000000000..e5d863cd86
Binary files /dev/null and b/docs/ppocr/blog/images/korean.jpg differ
diff --git a/docs/ppocr/blog/images/korean_0.jpg b/docs/ppocr/blog/images/korean_0.jpg
new file mode 100644
index 0000000000..3fe6305aa0
Binary files /dev/null and b/docs/ppocr/blog/images/korean_0.jpg differ
diff --git a/docs/ppocr/blog/images/multi_scale.png b/docs/ppocr/blog/images/multi_scale.png
new file mode 100644
index 0000000000..673d306399
Binary files /dev/null and b/docs/ppocr/blog/images/multi_scale.png differ
diff --git a/docs/ppocr/blog/images/ppocrv3_det_cml.png b/docs/ppocr/blog/images/ppocrv3_det_cml.png
new file mode 100644
index 0000000000..ccb5c8b21f
Binary files /dev/null and b/docs/ppocr/blog/images/ppocrv3_det_cml.png differ
diff --git a/docs/ppocr/blog/images/ppocrv3_framework-0052468.png b/docs/ppocr/blog/images/ppocrv3_framework-0052468.png
new file mode 100644
index 0000000000..e05279f7f5
Binary files /dev/null and b/docs/ppocr/blog/images/ppocrv3_framework-0052468.png differ
diff --git a/docs/ppocr/blog/images/ppocrv3_framework.png b/docs/ppocr/blog/images/ppocrv3_framework.png
new file mode 100644
index 0000000000..e05279f7f5
Binary files /dev/null and b/docs/ppocr/blog/images/ppocrv3_framework.png differ
diff --git a/docs/ppocr/blog/images/ppocrv4_det_cml.png b/docs/ppocr/blog/images/ppocrv4_det_cml.png
new file mode 100644
index 0000000000..9132c0a67c
Binary files /dev/null and b/docs/ppocr/blog/images/ppocrv4_det_cml.png differ
diff --git a/docs/ppocr/blog/images/ppocrv4_framework.png b/docs/ppocr/blog/images/ppocrv4_framework.png
new file mode 100644
index 0000000000..fa31f4c12e
Binary files /dev/null and b/docs/ppocr/blog/images/ppocrv4_framework.png differ
diff --git a/docs/ppocr/blog/images/ppocrv4_gtc.png b/docs/ppocr/blog/images/ppocrv4_gtc.png
new file mode 100644
index 0000000000..7e6a3f5c13
Binary files /dev/null and b/docs/ppocr/blog/images/ppocrv4_gtc.png differ
diff --git a/docs/ppocr/blog/images/rec_algo_compare.png b/docs/ppocr/blog/images/rec_algo_compare.png
new file mode 100644
index 0000000000..2dde496c75
Binary files /dev/null and b/docs/ppocr/blog/images/rec_algo_compare.png differ
diff --git a/docs/ppocr/blog/images/recconaug.png b/docs/ppocr/blog/images/recconaug.png
new file mode 100644
index 0000000000..899bc430de
Binary files /dev/null and b/docs/ppocr/blog/images/recconaug.png differ
diff --git a/docs/ppocr/blog/images/svtr_g2.png b/docs/ppocr/blog/images/svtr_g2.png
new file mode 100644
index 0000000000..2573afafbb
Binary files /dev/null and b/docs/ppocr/blog/images/svtr_g2.png differ
diff --git a/docs/ppocr/blog/images/svtr_g4.png b/docs/ppocr/blog/images/svtr_g4.png
new file mode 100644
index 0000000000..f85d66d97f
Binary files /dev/null and b/docs/ppocr/blog/images/svtr_g4.png differ
diff --git a/docs/ppocr/blog/images/svtr_tiny.png b/docs/ppocr/blog/images/svtr_tiny.png
new file mode 100644
index 0000000000..01e22e74b5
Binary files /dev/null and b/docs/ppocr/blog/images/svtr_tiny.png differ
diff --git a/docs/ppocr/blog/images/teacher_dml.png b/docs/ppocr/blog/images/teacher_dml.png
new file mode 100644
index 0000000000..ea09cacda8
Binary files /dev/null and b/docs/ppocr/blog/images/teacher_dml.png differ
diff --git a/docs/ppocr/blog/images/v3_rec_pipeline.png b/docs/ppocr/blog/images/v3_rec_pipeline.png
new file mode 100644
index 0000000000..aa61cc4f16
Binary files /dev/null and b/docs/ppocr/blog/images/v3_rec_pipeline.png differ
diff --git a/docs/ppocr/blog/images/v4_rec_pipeline.png b/docs/ppocr/blog/images/v4_rec_pipeline.png
new file mode 100644
index 0000000000..b1ec7a9689
Binary files /dev/null and b/docs/ppocr/blog/images/v4_rec_pipeline.png differ
diff --git a/docs/ppocr/blog/images/word_308.png b/docs/ppocr/blog/images/word_308.png
new file mode 100644
index 0000000000..a8d094faff
Binary files /dev/null and b/docs/ppocr/blog/images/word_308.png differ
diff --git a/docs/ppocr/blog/inference_args.en.md b/docs/ppocr/blog/inference_args.en.md
new file mode 100644
index 0000000000..d03de70dd9
--- /dev/null
+++ b/docs/ppocr/blog/inference_args.en.md
@@ -0,0 +1,131 @@
+---
+comments: true
+---
+
+
+# PaddleOCR Model Inference Parameter Explanation
+
+When using PaddleOCR for model inference, you can customize the modification parameters to modify the model, data, preprocessing, postprocessing, etc. (parameter file: [utility.py](../../tools/infer/utility.py)),The detailed parameter explanation is as follows:
+
+* Global parameters
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| image_dir | str | None, must be specified explicitly | Image or folder path |
+| page_num | int | 0 | Valid when the input type is pdf file, specify to predict the previous page_num pages, all pages are predicted by default |
+| vis_font_path | str | "./doc/fonts/simfang.ttf" | font path for visualization |
+| drop_score | float | 0.5 | Results with a recognition score less than this value will be discarded and will not be returned as results |
+| use_pdserving | bool | False | Whether to use Paddle Serving for prediction |
+| warmup | bool | False | Whether to enable warmup, this method can be used when statistical prediction time |
+| draw_img_save_dir | str | "./inference_results" | The saving folder of the system's tandem prediction OCR results |
+| save_crop_res | bool | False | Whether to save the recognized text image for OCR |
+| crop_res_save_dir | str | "./output" | Save the text image path recognized by OCR |
+| use_mp | bool | False | Whether to enable multi-process prediction |
+| total_process_num | int | 6 | The number of processes, which takes effect when `use_mp` is `True` |
+| process_id | int | 0 | The id number of the current process, no need to modify it yourself |
+| benchmark | bool | False | Whether to enable benchmark, and make statistics on prediction speed, memory usage, etc. |
+| save_log_path | str | "./log_output/" | Folder where log results are saved when `benchmark` is enabled |
+| show_log | bool | True | Whether to show the log information in the inference |
+| use_onnx | bool | False | Whether to enable onnx prediction |
+
+* Prediction engine related parameters
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| use_gpu | bool | True | Whether to use GPU for prediction |
+| ir_optim | bool | True | Whether to analyze and optimize the calculation graph. The prediction process can be accelerated when `ir_optim` is enabled |
+| use_tensorrt | bool | False | Whether to enable tensorrt |
+| min_subgraph_size | int | 15 | The minimum subgraph size in tensorrt. When the size of the subgraph is greater than this value, it will try to use the trt engine to calculate the subgraph. |
+| precision | str | fp32 | The precision of prediction, supports `fp32`, `fp16`, `int8` |
+| enable_mkldnn | bool | True | Whether to enable mkldnn |
+| cpu_threads | int | 10 | When mkldnn is enabled, the number of threads predicted by the cpu |
+
+* Text detection model related parameters
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| det_algorithm | str | "DB" | Text detection algorithm name, currently supports `DB`, `EAST`, `SAST`, `PSE`, `DB++`, `FCE` |
+| det_model_dir | str | xx | Detection inference model paths |
+| det_limit_side_len | int | 960 | image side length limit |
+| det_limit_type | str | "max" | The side length limit type, currently supports `min`and `max`. `min` means to ensure that the shortest side of the image is not less than `det_limit_side_len`, `max` means to ensure that the longest side of the image is not greater than `det_limit_side_len` |
+
+The relevant parameters of the DB algorithm are as follows
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| det_db_thresh | float | 0.3 | In the probability map output by DB, only pixels with a score greater than this threshold will be considered as text pixels |
+| det_db_box_thresh | float | 0.6 | Within the detection box, when the average score of all pixels is greater than the threshold, the result will be considered as a text area |
+| det_db_unclip_ratio | float | 1.5 | The expansion factor of the `Vatti clipping` algorithm, which is used to expand the text area |
+| max_batch_size | int | 10 | max batch size |
+| use_dilation | bool | False | Whether to inflate the segmentation results to obtain better detection results |
+| det_db_score_mode | str | "fast" | DB detection result score calculation method, supports `fast` and `slow`, `fast` calculates the average score according to all pixels within the bounding rectangle of the polygon, `slow` calculates the average score according to all pixels within the original polygon, The calculation speed is relatively slower, but more accurate. |
+
+The relevant parameters of the EAST algorithm are as follows
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| det_east_score_thresh | float | 0.8 | Threshold for score map in EAST postprocess |
+| det_east_cover_thresh | float | 0.1 | Average score threshold for text boxes in EAST postprocess |
+| det_east_nms_thresh | float | 0.2 | Threshold of nms in EAST postprocess |
+
+The relevant parameters of the SAST algorithm are as follows
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| det_sast_score_thresh | float | 0.5 | Score thresholds in SAST postprocess |
+| det_sast_nms_thresh | float | 0.5 | Thresholding of nms in SAST postprocess |
+| det_box_type | str | 'quad' | Whether polygon detection, curved text scene (such as Total-Text) is set to 'poly' |
+
+The relevant parameters of the PSE algorithm are as follows
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| det_pse_thresh | float | 0.0 | Threshold for binarizing the output image |
+| det_pse_box_thresh | float | 0.85 | Threshold for filtering boxes, below this threshold is discarded |
+| det_pse_min_area | float | 16 | The minimum area of the box, below this threshold is discarded |
+| det_box_type | str | "quad" | The type of the returned box, quad: four point coordinates, poly: all point coordinates of the curved text |
+| det_pse_scale | int | 1 | The ratio of the input image relative to the post-processed image, such as an image of `640*640`, the network output is `160*160`, and when the scale is 2, the shape of the post-processed image is `320*320`. Increasing this value can speed up the post-processing speed, but it will bring about a decrease in accuracy |
+
+* Text recognition model related parameters
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| rec_algorithm | str | "CRNN" | Text recognition algorithm name, currently supports `CRNN`, `SRN`, `RARE`, `NETR`, `SAR`, `ViTSTR`, `ABINet`, `VisionLAN`, `SPIN`, `RobustScanner`, `SVTR`, `SVTR_LCNet` |
+| rec_model_dir | str | None, it is required if using the recognition model | recognition inference model paths |
+| rec_image_shape | str | "3,48,320" ] | Image size at the time of recognition |
+| rec_batch_num | int | 6 | batch size |
+| max_text_length | int | 25 | The maximum length of the recognition result, valid in `SRN` |
+| rec_char_dict_path | str | "./ppocr/utils/ppocr_keys_v1.txt" | character dictionary file |
+| use_space_char | bool | True | Whether to include spaces, if `True`, the `space` character will be added at the end of the character dictionary |
+
+* End-to-end text detection and recognition model related parameters
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| e2e_algorithm | str | "PGNet" | End-to-end algorithm name, currently supports `PGNet` |
+| e2e_model_dir | str | None, it is required if using the end-to-end model | end-to-end model inference model path |
+| e2e_limit_side_len | int | 768 | End-to-end input image side length limit |
+| e2e_limit_type | str | "max" | End-to-end side length limit type, currently supports `min` and `max`. `min` means to ensure that the shortest side of the image is not less than `e2e_limit_side_len`, `max` means to ensure that the longest side of the image is not greater than `e2e_limit_side_len` |
+| e2e_pgnet_score_thresh | float | 0.5 | End-to-end score threshold, results below this threshold are discarded |
+| e2e_char_dict_path | str | "./ppocr/utils/ic15_dict.txt" | Recognition dictionary file path |
+| e2e_pgnet_valid_set | str | "totaltext" | The name of the validation set, currently supports `totaltext`, `partvgg`, the post-processing methods corresponding to different data sets are different, and it can be consistent with the training process |
+| e2e_pgnet_mode | str | "fast" | PGNet's detection result score calculation method, supports `fast` and `slow`, `fast` calculates the average score according to all pixels within the bounding rectangle of the polygon, `slow` calculates the average score according to all pixels within the original polygon, The calculation speed is relatively slower, but more accurate. |
+
+* Angle classifier model related parameters
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| use_angle_cls | bool | False | whether to use an angle classifier |
+| cls_model_dir | str | None, if you need to use, you must specify the path explicitly | angle classifier inference model path |
+| cls_image_shape | str | "3,48,192" | prediction shape |
+| label_list | list | ['0', '180'] | The angle value corresponding to the class id |
+| cls_batch_num | int | 6 | batch size |
+| cls_thresh | float | 0.9 | Prediction threshold, when the model prediction result is 180 degrees, and the score is greater than the threshold, the final prediction result is considered to be 180 degrees and needs to be flipped |
+
+* OCR image preprocessing parameters
+
+| parameters | type | default | implication |
+| :--: | :--: | :--: | :--: |
+| invert | bool | False | whether to invert image before processing |
+| binarize | bool | False | whether to threshold binarize image before processing |
+| alphacolor | tuple | "255,255,255" | Replacement color for the alpha channel, if the latter is present; R,G,B integers |
diff --git a/docs/ppocr/blog/inference_args.md b/docs/ppocr/blog/inference_args.md
new file mode 100644
index 0000000000..1eec868995
--- /dev/null
+++ b/docs/ppocr/blog/inference_args.md
@@ -0,0 +1,118 @@
+# PaddleOCR模型推理参数解释
+
+在使用PaddleOCR进行模型推理时,可以自定义修改参数,来修改模型、数据、预处理、后处理等内容(参数文件:[utility.py](../../tools/infer/utility.py)),详细的参数解释如下所示。
+
+* 全局信息
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| image_dir | str | 无,必须显式指定 | 图像或者文件夹路径 |
+| page_num | int | 0 | 当输入类型为pdf文件时有效,指定预测前面page_num页,默认预测所有页 |
+| vis_font_path | str | "./doc/fonts/simfang.ttf" | 用于可视化的字体路径 |
+| drop_score | float | 0.5 | 识别得分小于该值的结果会被丢弃,不会作为返回结果 |
+| use_pdserving | bool | False | 是否使用Paddle Serving进行预测 |
+| warmup | bool | False | 是否开启warmup,在统计预测耗时的时候,可以使用这种方法 |
+| draw_img_save_dir | str | "./inference_results" | 系统串联预测OCR结果的保存文件夹 |
+| save_crop_res | bool | False | 是否保存OCR的识别文本图像 |
+| crop_res_save_dir | str | "./output" | 保存OCR识别出来的文本图像路径 |
+| use_mp | bool | False | 是否开启多进程预测 |
+| total_process_num | int | 6 | 开启的进程数,`use_mp`为`True`时生效 |
+| process_id | int | 0 | 当前进程的id号,无需自己修改 |
+| benchmark | bool | False | 是否开启benchmark,对预测速度、显存占用等进行统计 |
+| save_log_path | str | "./log_output/" | 开启`benchmark`时,日志结果的保存文件夹 |
+| show_log | bool | True | 是否显示预测中的日志信息 |
+| use_onnx | bool | False | 是否开启onnx预测 |
+
+* 预测引擎相关
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| use_gpu | bool | True | 是否使用GPU进行预测 |
+| ir_optim | bool | True | 是否对计算图进行分析与优化,开启后可以加速预测过程 |
+| use_tensorrt | bool | False | 是否开启tensorrt |
+| min_subgraph_size | int | 15 | tensorrt中最小子图size,当子图的size大于该值时,才会尝试对该子图使用trt engine计算 |
+| precision | str | fp32 | 预测的精度,支持`fp32`, `fp16`, `int8` 3种输入 |
+| enable_mkldnn | bool | True | 是否开启mkldnn |
+| cpu_threads | int | 10 | 开启mkldnn时,cpu预测的线程数 |
+
+* 文本检测模型相关
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| det_algorithm | str | "DB" | 文本检测算法名称,目前支持`DB`, `EAST`, `SAST`, `PSE`, `DB++`, `FCE` |
+| det_model_dir | str | xx | 检测inference模型路径 |
+| det_limit_side_len | int | 960 | 检测的图像边长限制 |
+| det_limit_type | str | "max" | 检测的边长限制类型,目前支持`min`和`max`,`min`表示保证图像最短边不小于`det_limit_side_len`,`max`表示保证图像最长边不大于`det_limit_side_len` |
+
+其中,DB算法相关参数如下
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| det_db_thresh | float | 0.3 | DB输出的概率图中,得分大于该阈值的像素点才会被认为是文字像素点 |
+| det_db_box_thresh | float | 0.6 | 检测结果边框内,所有像素点的平均得分大于该阈值时,该结果会被认为是文字区域 |
+| det_db_unclip_ratio | float | 1.5 | `Vatti clipping`算法的扩张系数,使用该方法对文字区域进行扩张 |
+| max_batch_size | int | 10 | 预测的batch size |
+| use_dilation | bool | False | 是否对分割结果进行膨胀以获取更优检测效果 |
+| det_db_score_mode | str | "fast" | DB的检测结果得分计算方法,支持`fast`和`slow`,`fast`是根据polygon的外接矩形边框内的所有像素计算平均得分,`slow`是根据原始polygon内的所有像素计算平均得分,计算速度相对较慢一些,但是更加准确一些。 |
+
+EAST算法相关参数如下
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| det_east_score_thresh | float | 0.8 | EAST后处理中score map的阈值 |
+| det_east_cover_thresh | float | 0.1 | EAST后处理中文本框的平均得分阈值 |
+| det_east_nms_thresh | float | 0.2 | EAST后处理中nms的阈值 |
+
+SAST算法相关参数如下
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| det_sast_score_thresh | float | 0.5 | SAST后处理中的得分阈值 |
+| det_sast_nms_thresh | float | 0.5 | SAST后处理中nms的阈值 |
+| det_box_type | str | quad | 是否多边形检测,弯曲文本场景(如Total-Text)设置为'poly' |
+
+PSE算法相关参数如下
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| det_pse_thresh | float | 0.0 | 对输出图做二值化的阈值 |
+| det_pse_box_thresh | float | 0.85 | 对box进行过滤的阈值,低于此阈值的丢弃 |
+| det_pse_min_area | float | 16 | box的最小面积,低于此阈值的丢弃 |
+| det_box_type | str | "quad" | 返回框的类型,quad:四点坐标,poly: 弯曲文本的所有点坐标 |
+| det_pse_scale | int | 1 | 输入图像相对于进后处理的图的比例,如`640*640`的图像,网络输出为`160*160`,scale为2的情况下,进后处理的图片shape为`320*320`。这个值调大可以加快后处理速度,但是会带来精度的下降 |
+
+* 文本识别模型相关
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| rec_algorithm | str | "CRNN" | 文本识别算法名称,目前支持`CRNN`, `SRN`, `RARE`, `NETR`, `SAR`, `ViTSTR`, `ABINet`, `VisionLAN`, `SPIN`, `RobustScanner`, `SVTR`, `SVTR_LCNet` |
+| rec_model_dir | str | 无,如果使用识别模型,该项是必填项 | 识别inference模型路径 |
+| rec_image_shape | str | "3,48,320" | 识别时的图像尺寸 |
+| rec_batch_num | int | 6 | 识别的batch size |
+| max_text_length | int | 25 | 识别结果最大长度,在`SRN`中有效 |
+| rec_char_dict_path | str | "./ppocr/utils/ppocr_keys_v1.txt" | 识别的字符字典文件 |
+| use_space_char | bool | True | 是否包含空格,如果为`True`,则会在最后字符字典中补充`空格`字符 |
+
+* 端到端文本检测与识别模型相关
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| e2e_algorithm | str | "PGNet" | 端到端算法名称,目前支持`PGNet` |
+| e2e_model_dir | str | 无,如果使用端到端模型,该项是必填项 | 端到端模型inference模型路径 |
+| e2e_limit_side_len | int | 768 | 端到端的输入图像边长限制 |
+| e2e_limit_type | str | "max" | 端到端的边长限制类型,目前支持`min`, `max`,`min`表示保证图像最短边不小于`e2e_limit_side_len`,`max`表示保证图像最长边不大于`e2e_limit_side_len` |
+| e2e_pgnet_score_thresh | float | 0.5 | 端到端得分阈值,小于该阈值的结果会被丢弃 |
+| e2e_char_dict_path | str | "./ppocr/utils/ic15_dict.txt" | 识别的字典文件路径 |
+| e2e_pgnet_valid_set | str | "totaltext" | 验证集名称,目前支持`totaltext`, `partvgg`,不同数据集对应的后处理方式不同,与训练过程保持一致即可 |
+| e2e_pgnet_mode | str | "fast" | PGNet的检测结果得分计算方法,支持`fast`和`slow`,`fast`是根据polygon的外接矩形边框内的所有像素计算平均得分,`slow`是根据原始polygon内的所有像素计算平均得分,计算速度相对较慢一些,但是更加准确一些。 |
+
+* 方向分类器模型相关
+
+| 参数名称 | 类型 | 默认值 | 含义 |
+| :--: | :--: | :--: | :--: |
+| use_angle_cls | bool | False | 是否使用方向分类器 |
+| cls_model_dir | str | 无,如果需要使用,则必须显式指定路径 | 方向分类器inference模型路径 |
+| cls_image_shape | str | "3,48,192" | 预测尺度 |
+| label_list | list | ['0', '180'] | class id对应的角度值 |
+| cls_batch_num | int | 6 | 方向分类器预测的batch size |
+| cls_thresh | float | 0.9 | 预测阈值,模型预测结果为180度,且得分大于该阈值时,认为最终预测结果为180度,需要翻转 |
diff --git a/docs/ppocr/blog/multi_languages.en.md b/docs/ppocr/blog/multi_languages.en.md
new file mode 100644
index 0000000000..ed71a2d439
--- /dev/null
+++ b/docs/ppocr/blog/multi_languages.en.md
@@ -0,0 +1,223 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# Multi-language model
+
+**Recent Update**
+
+- 2022.5.8 update the `PP-OCRv3` version of the multi-language detection and recognition model, and the average recognition accuracy has increased by more than 5%.
+- 2021.4.9 supports the detection and recognition of 80 languages
+- 2021.4.9 supports **lightweight high-precision** English model detection and recognition
+
+PaddleOCR aims to create a rich, leading, and practical OCR tool library, which not only provides
+Chinese and English models in general scenarios, but also provides models specifically trained
+in English scenarios. And multilingual models covering [80 languages](#language_abbreviations).
+
+Among them, the English model supports the detection and recognition of uppercase and lowercase
+letters and common punctuation, and the recognition of space characters is optimized:
+
+![img](./images/img_12.jpg)
+
+The multilingual models cover Latin, Arabic, Traditional Chinese, Korean, Japanese, etc.:
+
+![img](./images/japan_2-20240709081138234.jpg)
+
+![img](./images/french_0.jpg)
+
+![img](./images/korean_0.jpg)
+
+![img](./images/arabic_0.jpg)
+
+This document will briefly introduce how to use the multilingual model.
+
+## 1 Installation
+
+### 1.1 Paddle installation
+
+```bash linenums="1"
+# cpu
+pip install paddlepaddle
+
+# gpu
+pip install paddlepaddle-gpu
+```
+
+### 1.2 PaddleOCR package installation
+
+```bash linenums="1"
+pip install paddleocr
+```
+
+Build and install locally
+
+```bash linenums="1"
+python3 -m build
+pip3 install dist/paddleocr-x.x.x-py3-none-any.whl # x.x.x is the version number of paddleocr
+```
+
+## 2 Quick use
+
+### 2.1 Command line operation
+
+View help information
+
+```bash linenums="1"
+paddleocr -h
+```
+
+- Whole image prediction (detection + recognition)
+
+PaddleOCR currently supports 80 languages, which can be specified by the --lang parameter.
+The supported languages are listed in the [table](#language_abbreviations).
+
+``` bash
+paddleocr --image_dir doc/imgs_en/254.jpg --lang=en
+```
+
+![](./images/254-20240709081442260.jpg)
+
+![img](./images/img_02.jpg)
+
+The result is a list. Each item contains a text box, text and recognition confidence
+
+```text linenums="1"
+[('PHO CAPITAL', 0.95723116), [[66.0, 50.0], [327.0, 44.0], [327.0, 76.0], [67.0, 82.0]]]
+[('107 State Street', 0.96311164), [[72.0, 90.0], [451.0, 84.0], [452.0, 116.0], [73.0, 121.0]]]
+[('Montpelier Vermont', 0.97389287), [[69.0, 132.0], [501.0, 126.0], [501.0, 158.0], [70.0, 164.0]]]
+[('8022256183', 0.99810505), [[71.0, 175.0], [363.0, 170.0], [364.0, 202.0], [72.0, 207.0]]]
+[('REG 07-24-201706:59 PM', 0.93537045), [[73.0, 299.0], [653.0, 281.0], [654.0, 318.0], [74.0, 336.0]]]
+[('045555', 0.99346405), [[509.0, 331.0], [651.0, 325.0], [652.0, 356.0], [511.0, 362.0]]]
+[('CT1', 0.9988654), [[535.0, 367.0], [654.0, 367.0], [654.0, 406.0], [535.0, 406.0]]]
+......
+```
+
+- Recognition
+
+```bash linenums="1"
+paddleocr --image_dir doc/imgs_words_en/word_308.png --det false --lang=en
+```
+
+![img](./images/word_308.png)
+
+The result is a 2-tuple, which contains the recognition result and recognition confidence
+
+```text linenums="1"
+(0.99879867, 'LITTLE')
+```
+
+- Detection
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs/11.jpg --rec false
+```
+
+The result is a list. Each item represents the coordinates of a text box.
+
+```bash linenums="1"
+[[26.0, 457.0], [137.0, 457.0], [137.0, 477.0], [26.0, 477.0]]
+[[25.0, 425.0], [372.0, 425.0], [372.0, 448.0], [25.0, 448.0]]
+[[128.0, 397.0], [273.0, 397.0], [273.0, 414.0], [128.0, 414.0]]
+......
+```
+
+### 2.2 Run with Python script
+
+PPOCR is able to run with Python scripts for easy integration with your own code:
+
+- Whole image prediction (detection + recognition)
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+# Also switch the language by modifying the lang parameter
+ocr = PaddleOCR(lang="korean") # The model file will be downloaded automatically when executed for the first time
+img_path ='doc/imgs/korean_1.jpg'
+result = ocr.ocr(img_path)
+# Recognition and detection can be performed separately through parameter control
+# result = ocr.ocr(img_path, det=False) Only perform recognition
+# result = ocr.ocr(img_path, rec=False) Only perform detection
+# Print detection frame and recognition result
+for line in result:
+ print(line)
+
+# Visualization
+from PIL import Image
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/korean.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+Visualization of results:
+
+![img](./images/korean.jpg)
+
+PPOCR also supports direction classification. For more detailed usage, please refer to: [whl package instructions](whl_en.md).
+
+## 3 Custom training
+
+PPOCR supports using your own data for custom training or fine-tune, where the recognition model can refer to [French configuration file](../../configs/rec/multi_language/rec_french_lite_train.yml)
+Modify the training data path, dictionary and other parameters.
+
+For specific data preparation and training process, please refer to: [Text Detection](../doc_en/detection_en.md), [Text Recognition](../doc_en/recognition_en.md), more functions such as predictive deployment,
+For functions such as data annotation, you can read the complete [Document Tutorial](../../README.md).
+
+## 4 Inference and Deployment
+
+In addition to installing the whl package for quick forecasting,
+PPOCR also provides a variety of forecasting deployment methods.
+If necessary, you can read related documents:
+
+- [Python Inference](./inference_ppocr_en.md)
+- [C++ Inference](../../deploy/cpp_infer/readme.md)
+- [Serving](../../deploy/hubserving/readme_en.md)
+- [Mobile](../../deploy/lite/readme.md)
+- [Benchmark](./benchmark_en.md)
+
+## 5 Support languages and abbreviations
+
+| Language | Abbreviation | | Language | Abbreviation |
+| --- | --- | --- | --- | --- |
+|Chinese & English|ch| |Arabic|ar|
+|English|en| |Hindi|hi|
+|French|fr| |Uyghur|ug|
+|German|german| |Persian|fa|
+|Japan|japan| |Urdu|ur|
+|Korean|korean| | Serbian(latin) |rs_latin|
+|Chinese Traditional |chinese_cht| |Occitan |oc|
+| Italian |it| |Marathi|mr|
+|Spanish |es| |Nepali|ne|
+| Portuguese|pt| |Serbian(cyrillic)|rs_cyrillic|
+|Russia|ru||Bulgarian |bg|
+|Ukranian|uk| |Estonian |et|
+|Belarusian|be| |Irish |ga|
+|Telugu |te| |Croatian |hr|
+|Saudi Arabia|sa| |Hungarian |hu|
+|Tamil |ta| |Indonesian|id|
+|Afrikaans |af| |Icelandic|is|
+|Azerbaijani |az||Kurdish|ku|
+|Bosnian|bs| |Lithuanian |lt|
+|Czech|cs| |Latvian |lv|
+|Welsh |cy| |Maori|mi|
+|Danish|da| |Malay|ms|
+|Maltese |mt| |Adyghe |ady|
+|Dutch |nl| |Kabardian |kbd|
+|Norwegian |no| |Avar |ava|
+|Polish |pl| |Dargwa |dar|
+|Romanian |ro| |Ingush |inh|
+|Slovak |sk| |Lak |lbe|
+|Slovenian |sl| |Lezghian |lez|
+|Albanian |sq| |Tabassaran |tab|
+|Swedish |sv| |Bihari |bh|
+|Swahili |sw| |Maithili |mai|
+|Tagalog |tl| |Angika |ang|
+|Turkish |tr| |Bhojpuri |bho|
+|Uzbek |uz| |Magahi |mah|
+|Vietnamese |vi| |Nagpur |sck|
+|Mongolian |mn| |Newari |new|
+|Abaza |abq| |Goan Konkani|gom|
diff --git a/docs/ppocr/blog/multi_languages.md b/docs/ppocr/blog/multi_languages.md
new file mode 100644
index 0000000000..22c331121f
--- /dev/null
+++ b/docs/ppocr/blog/multi_languages.md
@@ -0,0 +1,273 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# 多语言模型
+
+**近期更新**
+
+- 2022.5.8 更新`PP-OCRv3`版 多语言检测和识别模型,平均识别准确率提升5%以上。
+- 2021.4.9 支持**80种**语言的检测和识别
+- 2021.4.9 支持**轻量高精度**英文模型检测识别
+
+PaddleOCR 旨在打造一套丰富、领先、且实用的OCR工具库,不仅提供了通用场景下的中英文模型,也提供了专门在英文场景下训练的模型,
+和覆盖[80个语言](#语种缩写)的小语种模型。
+
+其中英文模型支持,大小写字母和常见标点的检测识别,并优化了空格字符的识别:
+
+![img](./images/img_12.jpg)
+
+小语种模型覆盖了拉丁语系、阿拉伯语系、中文繁体、韩语、日语等等:
+
+![img](./images/japan_2-20240709081138234.jpg)
+
+![img](./images/french_0.jpg)
+
+![img](./images/korean_0.jpg)
+
+![img](./images/arabic_0.jpg)
+
+本文档将简要介绍小语种模型的使用方法。
+
+## 1 安装
+
+### 1.1 paddle 安装
+
+```bash linenums="1"
+# cpu
+pip install paddlepaddle
+
+# gpu
+pip install paddlepaddle-gpu
+```
+
+### 1.2 paddleocr package 安装
+
+pip 安装
+
+```bash linenums="1"
+pip install paddleocr
+```
+
+本地构建并安装
+
+```bash linenums="1"
+python3 -m build
+pip3 install dist/paddleocr-x.x.x-py3-none-any.whl # x.x.x是paddleocr的版本号
+```
+
+## 2 快速使用
+
+### 2.1 命令行运行
+
+查看帮助信息
+
+```bash linenums="1"
+paddleocr -h
+```
+
+- 整图预测(检测+识别)
+
+Paddleocr目前支持80个语种,可以通过修改--lang参数进行切换,具体支持的[语种](#语种缩写)可查看表格。
+
+``` bash
+paddleocr --image_dir doc/imgs_en/254.jpg --lang=en
+```
+
+![](./images/254-20240709081442260.jpg)
+
+![img](./images/img_02.jpg)
+
+结果是一个list,每个item包含了文本框,文字和识别置信度
+
+```text linenums="1"
+[('PHO CAPITAL', 0.95723116), [[66.0, 50.0], [327.0, 44.0], [327.0, 76.0], [67.0, 82.0]]]
+[('107 State Street', 0.96311164), [[72.0, 90.0], [451.0, 84.0], [452.0, 116.0], [73.0, 121.0]]]
+[('Montpelier Vermont', 0.97389287), [[69.0, 132.0], [501.0, 126.0], [501.0, 158.0], [70.0, 164.0]]]
+[('8022256183', 0.99810505), [[71.0, 175.0], [363.0, 170.0], [364.0, 202.0], [72.0, 207.0]]]
+[('REG 07-24-201706:59 PM', 0.93537045), [[73.0, 299.0], [653.0, 281.0], [654.0, 318.0], [74.0, 336.0]]]
+[('045555', 0.99346405), [[509.0, 331.0], [651.0, 325.0], [652.0, 356.0], [511.0, 362.0]]]
+[('CT1', 0.9988654), [[535.0, 367.0], [654.0, 367.0], [654.0, 406.0], [535.0, 406.0]]]
+......
+```
+
+- 识别预测
+
+```bash linenums="1"
+paddleocr --image_dir doc/imgs_words_en/word_308.png --det false --lang=en
+```
+
+![img](./images/word_308.png)
+
+结果是一个tuple,返回识别结果和识别置信度
+
+```text linenums="1"
+(0.99879867, 'LITTLE')
+```
+
+- 检测预测
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs/11.jpg --rec false
+```
+
+结果是一个list,每个item只包含文本框
+
+```bash linenums="1"
+[[26.0, 457.0], [137.0, 457.0], [137.0, 477.0], [26.0, 477.0]]
+[[25.0, 425.0], [372.0, 425.0], [372.0, 448.0], [25.0, 448.0]]
+[[128.0, 397.0], [273.0, 397.0], [273.0, 414.0], [128.0, 414.0]]
+......
+```
+
+### 2.2 python 脚本运行
+
+ppocr 也支持在python脚本中运行,便于嵌入到您自己的代码中 :
+
+- 整图预测(检测+识别)
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+# 同样也是通过修改 lang 参数切换语种
+ocr = PaddleOCR(lang="korean") # 首次执行会自动下载模型文件
+img_path = 'doc/imgs/korean_1.jpg '
+result = ocr.ocr(img_path)
+# 可通过参数控制单独执行识别、检测
+# result = ocr.ocr(img_path, det=False) 只执行识别
+# result = ocr.ocr(img_path, rec=False) 只执行检测
+# 打印检测框和识别结果
+for line in result:
+ print(line)
+
+# 可视化
+from PIL import Image
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/korean.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+结果可视化:
+
+![img](./images/korean.jpg)
+
+ppocr 还支持方向分类, 更多使用方式请参考:[whl包使用说明](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.0/doc/doc_ch/whl.md)
+
+## 3 自定义训练
+
+ppocr 支持使用自己的数据进行自定义训练或finetune, 其中识别模型可以参考[法语配置文件](../../configs/rec/multi_language/rec_french_lite_train.yml)
+修改训练数据路径、字典等参数。
+
+详细数据准备、训练过程可参考:[文本识别](../doc_ch/recognition.md)、[文本检测](../doc_ch/detection.md)。
+
+假设已经准备好了训练数据,可根据以下步骤快速启动训练:
+
+- 修改配置文件
+
+以 `rec_french_lite_train.yml` 为例:
+
+```yaml linenums="1"
+Global:
+ ...
+ # 添加自定义字典,如修改字典请将路径指向新字典
+ character_dict_path: ./ppocr/utils/dict/french_dict.txt
+ ...
+ # 识别空格
+ use_space_char: True
+
+...
+
+Train:
+ dataset:
+ # 数据集格式,支持LMDBDataSet以及SimpleDataSet
+ name: SimpleDataSet
+ # 数据集路径
+ data_dir: ./train_data/
+ # 训练集标签文件
+ label_file_list: ["./train_data/french_train.txt"]
+ ...
+
+Eval:
+ dataset:
+ # 数据集格式,支持LMDBDataSet以及SimpleDataSet
+ name: SimpleDataSet
+ # 数据集路径
+ data_dir: ./train_data
+ # 验证集标签文件
+ label_file_list: ["./train_data/french_val.txt"]
+ ...
+```
+
+- 启动训练:
+
+```bash linenums="1"
+# 下载预训练模型
+wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/multilingual/french_mobile_v2.0_rec_train.tar
+tar -xf french_mobile_v2.0_rec_train.tar
+
+#加载预训练模型 单卡训练
+python3 tools/train.py -c configs/rec/rec_french_lite_train.yml -o Global.pretrained_model=french_mobile_v2.0_rec_train/best_accuracy
+
+#加载预训练模型 多卡训练,通过--gpus参数指定卡号
+python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_french_lite_train.yml -o Global.pretrained_model=french_mobile_v2.0_rec_train/best_accuracy
+```
+
+更多功能如预测部署、数据标注等功能可以阅读完整的[文档教程](../../README_ch.md)。
+
+## 4 预测部署
+
+除了安装whl包进行快速预测,ppocr 也提供了多种预测部署方式,如有需求可阅读相关文档:
+
+- [基于Python脚本预测引擎推理](./inference_ppocr.md)
+- [基于C++预测引擎推理](../../deploy/cpp_infer/readme_ch.md)
+- [服务化部署](../../deploy/hubserving/readme.md)
+- [端侧部署](../../deploy/lite/readme_ch.md)
+- [Benchmark](./benchmark.md)
+
+## 5 支持语种及缩写
+
+| 语种 | 描述 | 缩写 | | 语种 | 描述 | 缩写 |
+| --- | --- | --- | ---|--- | --- | --- |
+|中文|chinese and english|ch| |保加利亚文|Bulgarian |bg|
+|英文|english|en| |乌克兰文|Ukranian|uk|
+|法文|french|fr| |白俄罗斯文|Belarusian|be|
+|德文|german|german| |泰卢固文|Telugu |te|
+|日文|japan|japan| | 阿巴扎文 |Abaza | abq |
+|韩文|korean|korean| |泰米尔文|Tamil |ta|
+|中文繁体|chinese traditional |chinese_cht| |南非荷兰文 |Afrikaans |af|
+|意大利文| Italian |it| |阿塞拜疆文 |Azerbaijani |az|
+|西班牙文|Spanish |es| |波斯尼亚文|Bosnian|bs|
+|葡萄牙文| Portuguese|pt| |捷克文|Czech|cs|
+|俄罗斯文|Russia|ru| |威尔士文 |Welsh |cy|
+|阿拉伯文|Arabic|ar| |丹麦文 |Danish|da|
+|印地文|Hindi|hi| |爱沙尼亚文 |Estonian |et|
+|维吾尔|Uyghur|ug| |爱尔兰文 |Irish |ga|
+|波斯文|Persian|fa| |克罗地亚文|Croatian |hr|
+|乌尔都文|Urdu|ur| |匈牙利文|Hungarian |hu|
+|塞尔维亚文(latin)| Serbian(latin) |rs_latin| |印尼文|Indonesian|id|
+|欧西坦文|Occitan |oc| |冰岛文 |Icelandic|is|
+|马拉地文|Marathi|mr| |库尔德文 |Kurdish|ku|
+|尼泊尔文|Nepali|ne| |立陶宛文|Lithuanian |lt|
+|塞尔维亚文(cyrillic)|Serbian(cyrillic)|rs_cyrillic| |拉脱维亚文 |Latvian |lv|
+|毛利文|Maori|mi| | 达尔瓦文|Dargwa |dar|
+|马来文 |Malay|ms| | 因古什文|Ingush |inh|
+|马耳他文 |Maltese |mt| | 拉克文|Lak |lbe|
+|荷兰文 |Dutch |nl| | 莱兹甘文|Lezghian |lez|
+|挪威文 |Norwegian |no| |塔巴萨兰文 |Tabassaran |tab|
+|波兰文|Polish |pl| | 比尔哈文|Bihari |bh|
+| 罗马尼亚文|Romanian |ro| | 迈蒂利文|Maithili |mai|
+| 斯洛伐克文|Slovak |sk| | 昂加文|Angika |ang|
+| 斯洛文尼亚文|Slovenian |sl| | 孟加拉文|Bhojpuri |bho|
+| 阿尔巴尼亚文|Albanian |sq| | 摩揭陀文 |Magahi |mah|
+| 瑞典文|Swedish |sv| | 那格浦尔文|Nagpur |sck|
+| 西瓦希里文|Swahili |sw| | 尼瓦尔文|Newari |new|
+| 塔加洛文|Tagalog |tl| | 保加利亚文 |Goan Konkani|gom|
+| 土耳其文|Turkish |tr| | 沙特阿拉伯文|Saudi Arabia|sa|
+| 乌兹别克文|Uzbek |uz| | 阿瓦尔文|Avar |ava|
+| 越南文|Vietnamese |vi| | 阿瓦尔文|Avar |ava|
+| 蒙古文|Mongolian |mn| | 阿迪赫文|Adyghe |ady|
diff --git a/docs/ppocr/blog/ocr_book.en.md b/docs/ppocr/blog/ocr_book.en.md
new file mode 100644
index 0000000000..3228eb942e
--- /dev/null
+++ b/docs/ppocr/blog/ocr_book.en.md
@@ -0,0 +1,28 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# E-book: *Dive Into OCR*
+
+"Dive Into OCR" is a textbook that combines OCR theory and practice, written by the PaddleOCR community. The main features are as follows:
+
+- OCR full-stack technology covering text detection, recognition and document analysis
+- Closely integrate theory and practice, cross the code implementation gap, and supporting instructional videos
+- Jupyter Notebook textbook, flexibly modifying code for instant results
+
+## Structure
+
+![img](./images/187578511-9f3c351e-b68c-4359-a6e5-475810993c61.png)
+
+- The first part is the preliminary knowledge of the book, including the knowledge index and resource links needed in the process of positioning and using the book content of the book
+
+- The second part is chapters 4-8 of the book, which introduce the concepts, applications, and industry practices related to the detection and identification capabilities of the OCR engine. In the "Introduction to OCR Technology", the application scenarios and challenges of OCR, the basic concepts of technology, and the pain points in industrial applications are comprehensively explained. Then, in the two chapters of "Text Detection" and "Text Recognition", the two basic tasks of OCR are introduced. In each chapter, an algorithm is accompanied by a detailed explanation of the code and practical exercises. Chapters 6 and 7 are a detailed introduction to the PP-OCR series model, PP-OCR is a set of OCR systems for industrial applications, on the basis of the basic detection and identification model, after a series of optimization strategies to achieve the general field of industrial SOTA model, while opening up a variety of predictive deployment solutions, enabling enterprises to quickly land OCR applications.
+
+- The third part is chapter 9-12 of the book, which introduces applications other than the two-stage OCR engine, including data synthesis, preprocessing algorithm, and end-to-end model, focusing on OCR's layout analysis, table recognition, visual document question and answer capabilities in the document scene, and also through the combination of algorithm and code, so that readers can deeply understand and apply.
+
+## Address
+
+- [E-book: *Dive Into OCR* (PDF)](https://paddleocr.bj.bcebos.com/ebook/Dive_into_OCR.pdf)
+- [Notebook (.ipynb)](https://github.com/PaddleOCR-Community/Dive-into-OCR)
+- [Videos (Chinese only)](https://aistudio.baidu.com/aistudio/education/group/info/25207)
diff --git a/docs/ppocr/blog/ocr_book.md b/docs/ppocr/blog/ocr_book.md
new file mode 100644
index 0000000000..e3e51a77f0
--- /dev/null
+++ b/docs/ppocr/blog/ocr_book.md
@@ -0,0 +1,29 @@
+---
+comments: true
+typora-copy-images-to: images
+---
+
+# 《动手学OCR》电子书
+
+《动手学OCR》是PaddleOCR团队携手华中科技大学博导/教授,IAPR Fellow 白翔、复旦大学青年研究员陈智能、中国移动研究院视觉领域资深专家黄文辉、中国工商银行大数据人工智能实验室研究员等产学研同仁,以及OCR开发者共同打造的结合OCR前沿理论与代码实践的教材。主要特色如下:
+
+- 覆盖从文本检测识别到文档分析的OCR全栈技术
+- 紧密结合理论实践,跨越代码实现鸿沟,并配套教学视频
+- Notebook交互式学习,灵活修改代码,即刻获得结果
+
+## 本书结构
+
+![](./images/5e612.png)
+
+- 第一部分是本书的推荐序、序言与预备知识,包含本书的定位与使用书籍内容的过程中需要用到的知识索引、资源链接等
+- 第二部分是本书的4-8章,介绍与OCR核心的检测、识别能力相关的概念、应用与产业实践。在“OCR技术导论”中总括性的解释OCR的应用场景和挑战、技术基本概念以及在产业应用中的痛点问题。然后在
+“文本检测”与“文本识别”两章中介绍OCR的两个基本任务,并在每章中配套一个算法展开代码详解与实战练习。第6、7章是关于PP-OCR系列模型的详细介绍,PP-OCR是一套面向产业应用的OCR系统,在
+基础检测和识别模型的基础之上经过一系列优化策略达到通用领域的产业级SOTA模型,同时打通多种预测部署方案,赋能企业快速落地OCR应用。
+- 第三部分是本书的9-12章,介绍两阶段OCR引擎之外的应用,包括数据合成、预处理算法、端到端模型,重点展开了OCR在文档场景下的版面分析、表格识别、视觉文档问答的能力,同样通过算法与代码结
+合的方式使得读者能够深入理解并应用。
+
+## 资料地址
+
+- 中文版电子书下载请扫描首页二维码入群后领取
+- [notebook教程](https://github.com/PaddleOCR-Community/Dive-into-OCR)
+- [教学视频](https://aistudio.baidu.com/aistudio/education/group/info/25207)
diff --git a/docs/ppocr/blog/slice.en.md b/docs/ppocr/blog/slice.en.md
new file mode 100644
index 0000000000..0b8b95d135
--- /dev/null
+++ b/docs/ppocr/blog/slice.en.md
@@ -0,0 +1,22 @@
+---
+comments: true
+---
+
+
+# Slice Operator
+
+If you have a very large image/document that you would like to run PaddleOCR (detection and recognition) on, you can use the slice operation as follows:
+
+`ocr_inst = PaddleOCR(**ocr_settings)`
+`results = ocr_inst.ocr(img, det=True,rec=True, slice=slice, cls=False,bin=False,inv=False,alpha_color=False)`
+
+where
+`slice = {'horizontal_stride': h_stride, 'vertical_stride':v_stride, 'merge_x_thres':x_thres, 'merge_y_thres': y_thres}`
+
+Here, `h_stride`, `v_stride`, `x_thres`, and `y_thres` are user-configurable values and need to be set manually. The way the `slice` operator works is that it runs a sliding window across the large input image, creating slices of it and runs the OCR algorithms on it.
+
+The fragmented slice-level results are then merged together to output image-level detection and recognition results. The horizontal and vertical strides cannot be lower than a certain limit (as too low values would create so many slices it would be very computationally expensive to get results for each of them). However, as an example the recommended values for an image with dimensions 6616x14886 would be as follows.
+
+`slice = {'horizontal_stride': 300, 'vertical_stride':500, 'merge_x_thres':50, 'merge_y_thres': 35}`
+
+All slice-level detections with bounding boxes as close as `merge_x_thres` and `merge_y_thres` will be merged together.
diff --git a/docs/ppocr/blog/slice.md b/docs/ppocr/blog/slice.md
new file mode 100644
index 0000000000..eae91ad399
--- /dev/null
+++ b/docs/ppocr/blog/slice.md
@@ -0,0 +1,25 @@
+---
+comments: true
+---
+
+# 切片操作
+
+如果希望运行 PaddleOCR 处理一张非常大的图像或文档,对其进行检测和识别,可以使用切片操作,如下所示:
+
+```python linenums="1"
+ocr_inst = PaddleOCR(**ocr_settings)
+results = ocr_inst.ocr(img, det=True, rec=True, slice=slice, cls=False, bin=False, inv=False, alpha_color=False)
+```
+
+其中,
+`slice = {'horizontal_stride': h_stride, 'vertical_stride': v_stride, 'merge_x_thres': x_thres, 'merge_y_thres': y_thres}`
+
+这里的 `h_stride`、`v_stride`、`x_thres` 和 `y_thres` 是用户可配置的参数,需要手动设置。切片操作符的工作原理是,在大图像上运行一个滑动窗口,创建图像的切片,并在这些切片上运行 OCR 算法。
+
+然后将这些切片级别的零散结果合并,生成图像级别的检测和识别结果。水平和垂直步幅不能低于一定限度,因为过低的值会产生太多切片,导致计算结果非常耗时。例如,对于尺寸为 6616x14886 的图像,推荐使用以下参数:
+
+```python linenums="1"
+slice = {'horizontal_stride': 300, 'vertical_stride': 500, 'merge_x_thres': 50, 'merge_y_thres': 35}
+```
+
+所有边界框接近 `merge_x_thres` 和 `merge_y_thres` 的切片级检测结果将被合并在一起。
diff --git a/docs/ppocr/blog/whl.en.md b/docs/ppocr/blog/whl.en.md
new file mode 100644
index 0000000000..190a9372ca
--- /dev/null
+++ b/docs/ppocr/blog/whl.en.md
@@ -0,0 +1,488 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# Paddleocr Package
+
+## 1 Get started quickly
+
+### 1.1 install package
+
+install by pypi
+
+```bash linenums="1"
+pip install "paddleocr>=2.0.1" # Recommend to use version 2.0.1+
+```
+
+build own whl package and install
+
+```bash linenums="1"
+python3 -m build
+pip3 install dist/paddleocr-x.x.x-py3-none-any.whl # x.x.x is the version of paddleocr
+```
+
+## 2 Use
+
+### 2.1 Use by code
+
+The paddleocr whl package will automatically download the ppocr lightweight model as the default model, which can be customized and replaced according to the section 3 **Custom Model**.
+
+* detection angle classification and recognition
+
+```python linenums="1"
+from paddleocr import PaddleOCR,draw_ocr
+# Paddleocr supports Chinese, English, French, German, Korean and Japanese.
+# You can set the parameter `lang` as `ch`, `en`, `french`, `german`, `korean`, `japan`
+# to switch the language model in order.
+ocr = PaddleOCR(use_angle_cls=True, lang='en') # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs_en/img_12.jpg'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# draw result
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+Output will be a list, each item contains bounding box, text and recognition confidence
+
+```bash linenums="1"
+[[[442.0, 173.0], [1169.0, 173.0], [1169.0, 225.0], [442.0, 225.0]], ['ACKNOWLEDGEMENTS', 0.99283075]]
+[[[393.0, 340.0], [1207.0, 342.0], [1207.0, 389.0], [393.0, 387.0]], ['We would like to thank all the designers and', 0.9357758]]
+[[[399.0, 398.0], [1204.0, 398.0], [1204.0, 433.0], [399.0, 433.0]], ['contributors whohave been involved in the', 0.9592447]]
+......
+```
+
+Visualization of results
+
+![img](./images/12_det_rec.jpg)
+
+* detection and recognition
+
+```python linenums="1"
+from paddleocr import PaddleOCR,draw_ocr
+ocr = PaddleOCR(lang='en') # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs_en/img_12.jpg'
+result = ocr.ocr(img_path, cls=False)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# draw result
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+Output will be a list, each item contains bounding box, text and recognition confidence
+
+```bash linenums="1"
+[[[442.0, 173.0], [1169.0, 173.0], [1169.0, 225.0], [442.0, 225.0]], ['ACKNOWLEDGEMENTS', 0.99283075]]
+[[[393.0, 340.0], [1207.0, 342.0], [1207.0, 389.0], [393.0, 387.0]], ['We would like to thank all the designers and', 0.9357758]]
+[[[399.0, 398.0], [1204.0, 398.0], [1204.0, 433.0], [399.0, 433.0]], ['contributors whohave been involved in the', 0.9592447]]
+......
+```
+
+Visualization of results
+
+![img](./images/12_det_rec.jpg)
+
+* classification and recognition
+
+```python linenums="1"
+from paddleocr import PaddleOCR
+ocr = PaddleOCR(use_angle_cls=True, lang='en') # need to run only once to load model into memory
+img_path = 'PaddleOCR/doc/imgs_words_en/word_10.png'
+result = ocr.ocr(img_path, det=False, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+```
+
+Output will be a list, each item contains recognition text and confidence
+
+```bash linenums="1"
+['PAIN', 0.990372]
+```
+
+* only detection
+
+```python linenums="1"
+from paddleocr import PaddleOCR,draw_ocr
+ocr = PaddleOCR() # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs_en/img_12.jpg'
+result = ocr.ocr(img_path,rec=False)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# draw result
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+im_show = draw_ocr(image, result, txts=None, scores=None, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+Output will be a list, each item only contains bounding box
+
+```bash linenums="1"
+[[756.0, 812.0], [805.0, 812.0], [805.0, 830.0], [756.0, 830.0]]
+[[820.0, 803.0], [1085.0, 801.0], [1085.0, 836.0], [820.0, 838.0]]
+[[393.0, 801.0], [715.0, 805.0], [715.0, 839.0], [393.0, 836.0]]
+......
+```
+
+Visualization of results
+
+![img](./images/12_det.jpg)
+
+* only recognition
+
+```python linenums="1"
+from paddleocr import PaddleOCR
+ocr = PaddleOCR(lang='en') # need to run only once to load model into memory
+img_path = 'PaddleOCR/doc/imgs_words_en/word_10.png'
+result = ocr.ocr(img_path, det=False, cls=False)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+```
+
+Output will be a list, each item contains recognition text and confidence
+
+```bash linenums="1"
+['PAIN', 0.990372]
+```
+
+* only classification
+
+```python linenums="1"
+from paddleocr import PaddleOCR
+ocr = PaddleOCR(use_angle_cls=True) # need to run only once to load model into memory
+img_path = 'PaddleOCR/doc/imgs_words_en/word_10.png'
+result = ocr.ocr(img_path, det=False, rec=False, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+```
+
+Output will be a list, each item contains classification result and confidence
+
+```bash linenums="1"
+['0', 0.99999964]
+```
+
+### 2.2 Use by command line
+
+show help information
+
+```bash linenums="1"
+paddleocr -h
+```
+
+* detection classification and recognition
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_en/img_12.jpg --use_angle_cls true --lang en
+```
+
+Output will be a list, each item contains bounding box, text and recognition confidence
+
+```bash linenums="1"
+[[[441.0, 174.0], [1166.0, 176.0], [1165.0, 222.0], [441.0, 221.0]], ('ACKNOWLEDGEMENTS', 0.9971134662628174)]
+[[[403.0, 346.0], [1204.0, 348.0], [1204.0, 384.0], [402.0, 383.0]], ('We would like to thank all the designers and', 0.9761400818824768)]
+[[[403.0, 396.0], [1204.0, 398.0], [1204.0, 434.0], [402.0, 433.0]], ('contributors who have been involved in the', 0.9791957139968872)]
+......
+```
+
+pdf file is also supported, you can infer the first few pages by using the `page_num` parameter, the default is 0, which means infer all pages
+
+```bash linenums="1"
+paddleocr --image_dir ./xxx.pdf --use_angle_cls true --use_gpu false --page_num 2
+```
+
+* detection and recognition
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_en/img_12.jpg --lang en
+```
+
+Output will be a list, each item contains bounding box, text and recognition confidence
+
+```bash linenums="1"
+[[[441.0, 174.0], [1166.0, 176.0], [1165.0, 222.0], [441.0, 221.0]], ('ACKNOWLEDGEMENTS', 0.9971134662628174)]
+[[[403.0, 346.0], [1204.0, 348.0], [1204.0, 384.0], [402.0, 383.0]], ('We would like to thank all the designers and', 0.9761400818824768)]
+[[[403.0, 396.0], [1204.0, 398.0], [1204.0, 434.0], [402.0, 433.0]], ('contributors who have been involved in the', 0.9791957139968872)]
+......
+```
+
+* classification and recognition
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_words_en/word_10.png --use_angle_cls true --det false --lang en
+```
+
+Output will be a list, each item contains text and recognition confidence
+
+```bash linenums="1"
+['PAIN', 0.9934559464454651]
+```
+
+* only detection
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_en/img_12.jpg --rec false
+```
+
+Output will be a list, each item only contains bounding box
+
+```bash linenums="1"
+[[397.0, 802.0], [1092.0, 802.0], [1092.0, 841.0], [397.0, 841.0]]
+[[397.0, 750.0], [1211.0, 750.0], [1211.0, 789.0], [397.0, 789.0]]
+[[397.0, 702.0], [1209.0, 698.0], [1209.0, 734.0], [397.0, 738.0]]
+......
+```
+
+* only recognition
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_words_en/word_10.png --det false --lang en
+```
+
+Output will be a list, each item contains text and recognition confidence
+
+```bash linenums="1"
+['PAIN', 0.9934559464454651]
+```
+
+* only classification
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_words_en/word_10.png --use_angle_cls true --det false --rec false
+```
+
+Output will be a list, each item contains classification result and confidence
+
+```bash linenums="1"
+['0', 0.99999964]
+```
+
+## 3 Use custom model
+
+When the built-in model cannot meet the needs, you need to use your own trained model.
+First, refer to [export](../model_train/detection.en.md#4-inference) doc to convert your det and rec model to inference model, and then use it as follows
+
+### 3.1 Use by code
+
+```python linenums="1"
+from paddleocr import PaddleOCR,draw_ocr
+# The path of detection and recognition model must contain model and params files
+ocr = PaddleOCR(det_model_dir='{your_det_model_dir}', rec_model_dir='{your_rec_model_dir}', rec_char_dict_path='{your_rec_char_dict_path}', cls_model_dir='{your_cls_model_dir}', use_angle_cls=True)
+img_path = 'PaddleOCR/doc/imgs_en/img_12.jpg'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# draw result
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+### 3.2 Use by command line
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs/11.jpg --det_model_dir {your_det_model_dir} --rec_model_dir {your_rec_model_dir} --rec_char_dict_path {your_rec_char_dict_path} --cls_model_dir {your_cls_model_dir} --use_angle_cls true
+```
+
+## 4 Use web images or numpy array as input
+
+### 4.1 Web image
+
+* Use by code
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+ocr = PaddleOCR(use_angle_cls=True, lang="ch") # need to run only once to download and load model into memory
+img_path = 'http://n.sinaimg.cn/ent/transform/w630h933/20171222/o111-fypvuqf1838418.jpg'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# show result
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+* Use by command line
+
+```bash linenums="1"
+paddleocr --image_dir http://n.sinaimg.cn/ent/transform/w630h933/20171222/o111-fypvuqf1838418.jpg --use_angle_cls=true
+```
+
+### 4.2 Numpy array
+
+Support numpy array as input only when used by code
+
+```python linenums="1"
+import cv2
+from paddleocr import PaddleOCR, draw_ocr, download_with_progressbar
+ocr = PaddleOCR(use_angle_cls=True, lang="ch") # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs/11.jpg'
+img = cv2.imread(img_path)
+# img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY), If your own training model supports grayscale images, you can uncomment this line
+result = ocr.ocr(img, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# show result
+from PIL import Image
+result = result[0]
+download_with_progressbar(img_path, 'tmp.jpg')
+image = Image.open('tmp.jpg').convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+## 5 PDF file
+
+* Use by command line
+
+you can infer the first few pages by using the `page_num` parameter, the default is 0, which means infer all pages
+
+```bash linenums="1"
+paddleocr --image_dir ./xxx.pdf --use_angle_cls true --use_gpu false --page_num 2
+```
+
+* Use by code
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+# Paddleocr supports Chinese, English, French, German, Korean and Japanese.
+# You can set the parameter `lang` as `ch`, `en`, `fr`, `german`, `korean`, `japan`
+# to switch the language model in order.
+ocr = PaddleOCR(use_angle_cls=True, lang="ch", page_num=2) # need to run only once to download and load model into memory
+img_path = './xxx.pdf'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# draw result
+import fitz
+from PIL import Image
+import cv2
+import numpy as np
+imgs = []
+with fitz.open(img_path) as pdf:
+ for pg in range(0, pdf.pageCount):
+ page = pdf[pg]
+ mat = fitz.Matrix(2, 2)
+ pm = page.getPixmap(matrix=mat, alpha=False)
+ # if width or height > 2000 pixels, don't enlarge the image
+ if pm.width > 2000 or pm.height > 2000:
+ pm = page.getPixmap(matrix=fitz.Matrix(1, 1), alpha=False)
+
+ img = Image.frombytes("RGB", [pm.width, pm.height], pm.samples)
+ img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
+ imgs.append(img)
+for idx in range(len(result)):
+ res = result[idx]
+ image = imgs[idx]
+ boxes = [line[0] for line in res]
+ txts = [line[1][0] for line in res]
+ scores = [line[1][1] for line in res]
+ im_show = draw_ocr(image, boxes, txts, scores, font_path='doc/fonts/simfang.ttf')
+ im_show = Image.fromarray(im_show)
+ im_show.save('result_page_{}.jpg'.format(idx))
+```
+
+## 6 Parameter Description
+
+| Parameter | Description | Default value |
+|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
+| use_gpu | use GPU or not | TRUE |
+| gpu_mem | GPU memory size used for initialization | 8000M |
+| image_dir | The images path or folder path for predicting when used by the command line | |
+| page_num | Valid when the input type is pdf file, specify to predict the previous page_num pages, all pages are predicted by default | 0 |
+| det_algorithm | Type of detection algorithm selected | DB |
+| det_model_dir | the text detection inference model folder. There are two ways to transfer parameters, 1. None: Automatically download the built-in model to `~/.paddleocr/det`; 2. The path of the inference model converted by yourself, the model and params files must be included in the model path | None |
+| det_max_side_len | The maximum size of the long side of the image. When the long side exceeds this value, the long side will be resized to this size, and the short side will be scaled proportionally | 960 |
+| det_db_thresh | Binarization threshold value of DB output map | 0.3 |
+| det_db_box_thresh | The threshold value of the DB output box. Boxes score lower than this value will be discarded | 0.5 |
+| det_db_unclip_ratio | The expanded ratio of DB output box | 2 |
+| det_db_score_mode | The parameter that control how the score of the detection frame is calculated. There are 'fast' and 'slow' options. If the text to be detected is curved, it is recommended to use 'slow' | 'fast' |
+| det_east_score_thresh | Binarization threshold value of EAST output map | 0.8 |
+| det_east_cover_thresh | The threshold value of the EAST output box. Boxes score lower than this value will be discarded | 0.1 |
+| det_east_nms_thresh | The NMS threshold value of EAST model output box | 0.2 |
+| rec_algorithm | Type of recognition algorithm selected | CRNN |
+| rec_model_dir | the text recognition inference model folder. There are two ways to transfer parameters, 1. None: Automatically download the built-in model to `~/.paddleocr/rec`; 2. The path of the inference model converted by yourself, the model and params files must be included in the model path | None |
+| rec_image_shape | image shape of recognition algorithm | "3,32,320" |
+| rec_batch_num | When performing recognition, the batchsize of forward images | 30 |
+| max_text_length | The maximum text length that the recognition algorithm can recognize | 25 |
+| rec_char_dict_path | the alphabet path which needs to be modified to your own path when `rec_model_Name` use mode 2 | ./ppocr/utils/ppocr_keys_v1.txt |
+| use_space_char | Whether to recognize spaces | TRUE |
+| drop_score | Filter the output by score (from the recognition model), and those below this score will not be returned | 0.5 |
+| use_angle_cls | Whether to load classification model | FALSE |
+| cls_model_dir | the classification inference model folder. There are two ways to transfer parameters, 1. None: Automatically download the built-in model to `~/.paddleocr/cls`; 2. The path of the inference model converted by yourself, the model and params files must be included in the model path | None |
+| cls_image_shape | image shape of classification algorithm | "3,48,192" |
+| label_list | label list of classification algorithm | ['0','180'] |
+| cls_batch_num | When performing classification, the batchsize of forward images | 30 |
+| enable_mkldnn | Whether to enable mkldnn | FALSE |
+| use_zero_copy_run | Whether to forward by zero_copy_run | FALSE |
+| lang | The support language, now only Chinese(ch)、English(en)、French(french)、German(german)、Korean(korean)、Japanese(japan) are supported | ch |
+| det | Enable detction when `ppocr.ocr` func exec | TRUE |
+| rec | Enable recognition when `ppocr.ocr` func exec | TRUE |
+| cls | Enable classification when `ppocr.ocr` func exec((Use use_angle_cls in command line mode to control whether to start classification in the forward direction) | FALSE |
+| show_log | Whether to print log| FALSE |
+| type | Perform ocr or table structuring, the value is selected in ['ocr','structure'] | ocr |
+| ocr_version | OCR Model version number, the current model support list is as follows: PP-OCRv3 supports Chinese and English detection, recognition, multilingual recognition, direction classifier models, PP-OCRv2 support Chinese detection and recognition model, PP-OCR support Chinese detection, recognition and direction classifier, multilingual recognition model | PP-OCRv3 |
diff --git a/docs/ppocr/blog/whl.md b/docs/ppocr/blog/whl.md
new file mode 100644
index 0000000000..722c0462ed
--- /dev/null
+++ b/docs/ppocr/blog/whl.md
@@ -0,0 +1,501 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# paddleocr package使用说明
+
+## 1 快速上手
+
+### 1.1 安装whl包
+
+pip安装
+
+```bash linenums="1"
+pip install paddleocr
+```
+
+本地构建并安装
+
+```bash linenums="1"
+python3 -m build
+pip3 install dist/paddleocr-x.x.x-py3-none-any.whl # x.x.x是paddleocr的版本号
+```
+
+## 2 使用
+
+### 2.1 代码使用
+
+paddleocr whl包会自动下载ppocr轻量级模型作为默认模型,可以根据第3节**自定义模型**进行自定义更换。
+
+* 检测+方向分类器+识别全流程
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+# Paddleocr目前支持中英文、英文、法语、德语、韩语、日语,可以通过修改lang参数进行切换
+# 参数依次为`ch`, `en`, `french`, `german`, `korean`, `japan`。
+ocr = PaddleOCR(use_angle_cls=True, lang="ch") # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs/11.jpg'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# 显示结果
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+结果是一个list,每个item包含了文本框,文字和识别置信度
+
+```bash linenums="1"
+[[[24.0, 36.0], [304.0, 34.0], [304.0, 72.0], [24.0, 74.0]], ['纯臻营养护发素', 0.964739]]
+[[[24.0, 80.0], [172.0, 80.0], [172.0, 104.0], [24.0, 104.0]], ['产品信息/参数', 0.98069626]]
+[[[24.0, 109.0], [333.0, 109.0], [333.0, 136.0], [24.0, 136.0]], ['(45元/每公斤,100公斤起订)', 0.9676722]]
+......
+```
+
+结果可视化
+
+
+
+
+
+* 检测+识别
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+ocr = PaddleOCR() # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs/11.jpg'
+result = ocr.ocr(img_path, cls=False)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# 显示结果
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+结果是一个list,每个item包含了文本框,文字和识别置信度
+
+```bash linenums="1"
+[[[24.0, 36.0], [304.0, 34.0], [304.0, 72.0], [24.0, 74.0]], ['纯臻营养护发素', 0.964739]]
+[[[24.0, 80.0], [172.0, 80.0], [172.0, 104.0], [24.0, 104.0]], ['产品信息/参数', 0.98069626]]
+[[[24.0, 109.0], [333.0, 109.0], [333.0, 136.0], [24.0, 136.0]], ['(45元/每公斤,100公斤起订)', 0.9676722]]
+......
+```
+
+结果可视化
+
+
+
+
+
+* 方向分类器+识别
+
+```python linenums="1"
+from paddleocr import PaddleOCR
+
+ocr = PaddleOCR(use_angle_cls=True) # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs_words/ch/word_1.jpg'
+result = ocr.ocr(img_path, det=False, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+```
+
+结果是一个list,每个item只包含识别结果和识别置信度
+
+```bash linenums="1"
+['韩国小馆', 0.9907421]
+```
+
+* 单独执行检测
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+ocr = PaddleOCR() # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs/11.jpg'
+result = ocr.ocr(img_path, rec=False)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# 显示结果
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+im_show = draw_ocr(image, result, txts=None, scores=None, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+结果是一个list,每个item只包含文本框
+
+```bash linenums="1"
+[[26.0, 457.0], [137.0, 457.0], [137.0, 477.0], [26.0, 477.0]]
+[[25.0, 425.0], [372.0, 425.0], [372.0, 448.0], [25.0, 448.0]]
+[[128.0, 397.0], [273.0, 397.0], [273.0, 414.0], [128.0, 414.0]]
+......
+```
+
+结果可视化
+
+
+
+
+
+* 单独执行识别
+
+```python linenums="1"
+from paddleocr import PaddleOCR
+
+ocr = PaddleOCR() # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs_words/ch/word_1.jpg'
+result = ocr.ocr(img_path, det=False)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+```
+
+结果是一个list,每个item只包含识别结果和识别置信度
+
+```bash linenums="1"
+['韩国小馆', 0.9907421]
+```
+
+* 单独执行方向分类器
+
+```python linenums="1"
+from paddleocr import PaddleOCR
+
+ocr = PaddleOCR(use_angle_cls=True) # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs_words/ch/word_1.jpg'
+result = ocr.ocr(img_path, det=False, rec=False, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+```
+
+结果是一个list,每个item只包含分类结果和分类置信度
+
+```bash linenums="1"
+['0', 0.9999924]
+```
+
+### 2.2 通过命令行使用
+
+查看帮助信息
+
+```bash linenums="1"
+paddleocr -h
+```
+
+* 检测+方向分类器+识别全流程
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs/11.jpg --use_angle_cls true
+```
+
+结果是一个list,每个item包含了文本框,文字和识别置信度
+
+```bash linenums="1"
+[[[28.0, 37.0], [302.0, 39.0], [302.0, 72.0], [27.0, 70.0]], ('纯臻营养护发素', 0.9658738374710083)]
+......
+```
+
+此外,paddleocr也支持输入pdf文件,并且可以通过指定参数`page_num`来控制推理前面几页,默认为0,表示推理所有页。
+
+```bash linenums="1"
+paddleocr --image_dir ./xxx.pdf --use_angle_cls true --use_gpu false --page_num 2
+```
+
+* 检测+识别
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs/11.jpg
+```
+
+结果是一个list,每个item包含了文本框,文字和识别置信度
+
+```bash linenums="1"
+[[[28.0, 37.0], [302.0, 39.0], [302.0, 72.0], [27.0, 70.0]], ('纯臻营养护发素', 0.9658738374710083)]
+......
+```
+
+* 方向分类器+识别
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_words/ch/word_1.jpg --use_angle_cls true --det false
+```
+
+结果是一个list,每个item只包含识别结果和识别置信度
+
+```bash linenums="1"
+['韩国小馆', 0.994467]
+```
+
+* 单独执行检测
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs/11.jpg --rec false
+```
+
+结果是一个list,每个item只包含文本框
+
+```bash linenums="1"
+[[27.0, 459.0], [136.0, 459.0], [136.0, 479.0], [27.0, 479.0]]
+[[28.0, 429.0], [372.0, 429.0], [372.0, 445.0], [28.0, 445.0]]
+......
+```
+
+* 单独执行识别
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_words/ch/word_1.jpg --det false
+```
+
+结果是一个list,每个item只包含识别结果和识别置信度
+
+```bash linenums="1"
+['韩国小馆', 0.994467]
+```
+
+* 单独执行方向分类器
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs_words/ch/word_1.jpg --use_angle_cls true --det false --rec false
+```
+
+结果是一个list,每个item只包含分类结果和分类置信度
+
+```bash linenums="1"
+['0', 0.9999924]
+```
+
+## 3 自定义模型
+
+当内置模型无法满足需求时,需要使用到自己训练的模型。 首先,参照[模型导出](../model_train/detection.md#4-模型导出与预测)将检测、分类和识别模型转换为inference模型,然后按照如下方式使用
+
+### 3.1 代码使用
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+# 模型路径下必须含有model和params文件
+ocr = PaddleOCR(det_model_dir='{your_det_model_dir}', rec_model_dir='{your_rec_model_dir}',
+ rec_char_dict_path='{your_rec_char_dict_path}', cls_model_dir='{your_cls_model_dir}',
+ use_angle_cls=True)
+img_path = 'PaddleOCR/doc/imgs/11.jpg'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# 显示结果
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+### 3.2 通过命令行使用
+
+```bash linenums="1"
+paddleocr --image_dir PaddleOCR/doc/imgs/11.jpg --det_model_dir {your_det_model_dir} --rec_model_dir {your_rec_model_dir} --rec_char_dict_path {your_rec_char_dict_path} --cls_model_dir {your_cls_model_dir} --use_angle_cls true
+```
+
+## 4 使用网络图片或者numpy数组作为输入
+
+### 4.1 网络图片
+
+* 代码使用
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr, download_with_progressbar
+
+# Paddleocr目前支持中英文、英文、法语、德语、韩语、日语,可以通过修改lang参数进行切换
+# 参数依次为`ch`, `en`, `french`, `german`, `korean`, `japan`。
+ocr = PaddleOCR(use_angle_cls=True, lang="ch") # need to run only once to download and load model into memory
+img_path = 'http://n.sinaimg.cn/ent/transform/w630h933/20171222/o111-fypvuqf1838418.jpg'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# 显示结果
+from PIL import Image
+result = result[0]
+download_with_progressbar(img_path, 'tmp.jpg')
+image = Image.open('tmp.jpg').convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+* 命令行模式
+
+```bash linenums="1"
+paddleocr --image_dir http://n.sinaimg.cn/ent/transform/w630h933/20171222/o111-fypvuqf1838418.jpg --use_angle_cls=true
+```
+
+### 4.2 numpy数组
+
+仅通过代码使用时支持numpy数组作为输入
+
+```python linenums="1"
+import cv2
+from paddleocr import PaddleOCR, draw_ocr
+
+# Paddleocr目前支持中英文、英文、法语、德语、韩语、日语,可以通过修改lang参数进行切换
+# 参数依次为`ch`, `en`, `french`, `german`, `korean`, `japan`。
+ocr = PaddleOCR(use_angle_cls=True, lang="ch") # need to run only once to download and load model into memory
+img_path = 'PaddleOCR/doc/imgs/11.jpg'
+img = cv2.imread(img_path)
+# img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY), 如果你自己训练的模型支持灰度图,可以将这句话的注释取消
+result = ocr.ocr(img, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# 显示结果
+from PIL import Image
+result = result[0]
+image = Image.open(img_path).convert('RGB')
+boxes = [line[0] for line in result]
+txts = [line[1][0] for line in result]
+scores = [line[1][1] for line in result]
+im_show = draw_ocr(image, boxes, txts, scores, font_path='/path/to/PaddleOCR/doc/fonts/simfang.ttf')
+im_show = Image.fromarray(im_show)
+im_show.save('result.jpg')
+```
+
+## 5 PDF文件作为输入
+
+* 命令行模式
+
+可以通过指定参数`page_num`来控制推理前面几页,默认为0,表示推理所有页。
+
+```bash linenums="1"
+paddleocr --image_dir ./xxx.pdf --use_angle_cls true --use_gpu false --page_num 2
+```
+
+* 代码使用
+
+```python linenums="1"
+from paddleocr import PaddleOCR, draw_ocr
+
+# Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
+# 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
+ocr = PaddleOCR(use_angle_cls=True, lang="ch", page_num=2) # need to run only once to download and load model into memory
+img_path = './xxx.pdf'
+result = ocr.ocr(img_path, cls=True)
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# 显示结果
+import fitz
+from PIL import Image
+import cv2
+import numpy as np
+imgs = []
+with fitz.open(img_path) as pdf:
+ for pg in range(0, pdf.pageCount):
+ page = pdf[pg]
+ mat = fitz.Matrix(2, 2)
+ pm = page.getPixmap(matrix=mat, alpha=False)
+ # if width or height > 2000 pixels, don't enlarge the image
+ if pm.width > 2000 or pm.height > 2000:
+ pm = page.getPixmap(matrix=fitz.Matrix(1, 1), alpha=False)
+
+ img = Image.frombytes("RGB", [pm.width, pm.height], pm.samples)
+ img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
+ imgs.append(img)
+for idx in range(len(result)):
+ res = result[idx]
+ image = imgs[idx]
+ boxes = [line[0] for line in res]
+ txts = [line[1][0] for line in res]
+ scores = [line[1][1] for line in res]
+ im_show = draw_ocr(image, boxes, txts, scores, font_path='doc/fonts/simfang.ttf')
+ im_show = Image.fromarray(im_show)
+ im_show.save('result_page_{}.jpg'.format(idx))
+```
+
+## 6 参数说明
+
+| 字段 | 说明| 默认值 |
+|-------------------------|----------|-------------------------|
+| use_gpu| 是否使用GPU | TRUE |
+| gpu_mem| 初始化占用的GPU内存大小 | 8000M |
+| image_dir | 通过命令行调用时执行预测的图片或文件夹路径 |
+| page_num | 当输入类型为pdf文件时有效,指定预测前面page_num页,默认预测所有页 | 0|
+| det_algorithm | 使用的检测算法类型 | DB |
+| det_model_dir | 检测模型所在文件夹。传参方式有两种,1. None: 自动下载内置模型到 `~/.paddleocr/det`;2.自己转换好的inference模型路径,模型路径下必须包含model和params文件 | None |
+| det_max_side_len | 检测算法前向时图片长边的最大尺寸,当长边超出这个值时会将长边resize到这个大小,短边等比例缩放 | 960 |
+| det_db_thresh | DB模型输出预测图的二值化阈值 | 0.3 |
+| det_db_box_thresh | DB模型输出框的阈值,低于此值的预测框会被丢弃 | 0.5 |
+| det_db_unclip_ratio | DB模型输出框扩大的比例 | 2 |
+| det_db_score_mode | 计算检测框score的方式,有'fast'和'slow',如果要检测的文字有弯曲,建议用'slow','slow'模式计算的box的score偏大,box不容易被过滤掉 | 'fast' |
+| det_east_score_thresh | EAST模型输出预测图的二值化阈值 | 0.8 |
+| det_east_cover_thresh | EAST模型输出框的阈值,低于此值的预测框会被丢弃 | 0.1 |
+| det_east_nms_thresh | EAST模型输出框NMS的阈值 | 0.2 |
+| rec_algorithm | 使用的识别算法类型 | CRNN |
+| rec_model_dir | 识别模型所在文件夹。传参方式有两种,1. None: 自动下载内置模型到 `~/.paddleocr/rec`;2.自己转换好的inference模型路径,模型路径下必须包含model和params文件 | None |
+| rec_image_shape | 识别算法的输入图片尺寸 | "3,32,320" |
+| rec_batch_num | 进行识别时,同时前向的图片数 | 30 |
+| max_text_length | 识别算法能识别的最大文字长度 | 25 |
+| rec_char_dict_path | 识别模型字典路径,当rec_model_dir使用方式2传参时需要修改为自己的字典路径 | ./ppocr/utils/ppocr_keys_v1.txt |
+| use_space_char | 是否识别空格 | TRUE |
+| drop_score | 对输出按照分数(来自于识别模型)进行过滤,低于此分数的不返回 | 0.5 |
+| use_angle_cls | 是否加载分类模型 | FALSE |
+| cls_model_dir | 分类模型所在文件夹。传参方式有两种,1. None: 自动下载内置模型到 `~/.paddleocr/cls`;2.自己转换好的inference模型路径,模型路径下必须包含model和params文件 | None |
+| cls_image_shape | 分类算法的输入图片尺寸 | "3, 48, 192" |
+| label_list | 分类算法的标签列表 | ['0', '180'] |
+| cls_batch_num | 进行分类时,同时前向的图片数 |30|
+| enable_mkldnn | 是否启用mkldnn | FALSE |
+| use_zero_copy_run | 是否通过zero_copy_run的方式进行前向| FALSE |
+| lang | 模型语言类型,目前支持 目前支持中英文(ch)、英文(en)、法语(french)、德语(german)、韩语(korean)、日语(japan) | ch |
+| det | 前向时使用启动检测 | TRUE |
+| rec | 前向时是否启动识别 | TRUE |
+| cls | 前向时是否启动分类 (命令行模式下使用use_angle_cls控制前向是否启动分类)| FALSE |
+| show_log | 是否打印logger信息| FALSE |
+| type | 执行ocr或者表格结构化, 值可选['ocr','structure'] | ocr |
+| ocr_version | OCR模型版本,可选PP-OCRv3, PP-OCRv2, PP-OCR。PP-OCRv3 支持中、英文的检测、识别、多语种识别,方向分类器等模型;PP-OCRv2 目前仅支持中文的检测和识别模型;PP-OCR支持中文的检测,识别,多语种识别,方向分类器等模型 | PP-OCRv3 |
diff --git a/docs/ppocr/environment.en.md b/docs/ppocr/environment.en.md
new file mode 100644
index 0000000000..141f342015
--- /dev/null
+++ b/docs/ppocr/environment.en.md
@@ -0,0 +1,313 @@
+---
+comments: true
+---
+
+# Environment Preparation
+
+Windows and Mac users are recommended to use Anaconda to build a Python environment, and Linux users are recommended to use docker to build a Python environment.
+
+Recommended working environment:
+
+- PaddlePaddle >= 2.1.2
+- Python 3.7
+- CUDA 10.1 / CUDA 10.2
+- cuDNN 7.6
+
+> If you already have a Python environment installed, you can skip to [PaddleOCR Quick Start](./quickstart_en.md).
+
+## 1. Python Environment Setup
+
+### 1.1 Windows
+
+#### 1.1.1 Install Anaconda
+
+- Note: To use PaddlePaddle you need to install python environment first, here we choose python integrated environment Anaconda toolkit
+
+ - Anaconda is a common python package manager
+ - After installing Anaconda, you can install the python environment, as well as numpy and other required toolkit environment.
+
+- Anaconda download.
+
+ - Address:
+ - Most Win10 computers are 64-bit operating systems, choose x86_64 version; if the computer is a 32-bit operating system, choose x86.exe
+
+ ![anaconda download](./images/Anaconda_download.png)
+
+ - After the download is complete, double-click the installer to enter the graphical interface
+
+ - The default installation location is C drive, it is recommended to change the installation location to D drive.
+
+ ![](./images/anaconda_install_folder.png)
+
+ - Check Conda to add environment variables and ignore the warning that
+ ![](./images/anaconda_install_env.png)
+
+#### 1.1.2 Opening the terminal and creating the Conda environment
+
+- Open Anaconda Prompt terminal: bottom left Windows Start Menu -> Anaconda3 -> Anaconda Prompt start console
+
+ ![anaconda download](./images/anaconda_prompt.png)
+
+- Create a new Conda environment
+
+ ```bash linenums="1"
+ # Enter the following command at the command line to create an environment named paddle_env
+ # Here to speed up the download, use the Tsinghua source
+ conda create --name paddle_env python=3.8 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ # This is a one line command
+ ```
+
+ This command will create an executable environment named paddle_env with python version 3.8, which will take a while depending on the network status
+
+ The command line will then output a prompt, type y and enter to continue the installation
+
+ ![conda create](./images/conda_new_env.png)
+
+- To activate the Conda environment you just created, enter the following command at the command line.
+
+ ```bash linenums="1"
+ # Activate the paddle_env environment
+ conda activate paddle_env
+ # View the current location of python
+ where python
+ ```
+
+ ![create environment](./images/conda_list_env.png)
+
+The above anaconda environment and python environment are installed
+
+### 1.2 Mac
+
+#### 1.2.1 Installing Anaconda
+
+- Note: To use PaddlePaddle you need to install the python environment first, here we choose the python integrated environment Anaconda toolkit
+
+ - Anaconda is a common python package manager
+ - After installing Anaconda, you can install the python environment, as well as numpy and other required toolkit environment
+
+- Anaconda download:.
+
+ - Address:
+
+ ![anaconda download](./images/anaconda_start.png)
+
+ - Select `Anaconda3-2021.05-MacOSX-x86_64.pkg` at the bottom to download
+
+- After downloading, double click on the .pkg file to enter the graphical interface
+
+ - Just follow the default settings, it will take a while to install
+
+- It is recommended to install a code editor such as VSCode or PyCharm
+
+#### 1.2.2 Open a terminal and create a Conda environment
+
+- Open the terminal
+
+ - Press command and spacebar at the same time, type "terminal" in the focus search, double click to enter terminal
+
+- **Add Conda to the environment variables**
+
+ - Environment variables are added so that the system can recognize the Conda command
+
+ - Open `~/.bash_profile` in the terminal by typing the following command.
+
+ ```bash linenums="1"
+ vim ~/.bash_profile
+ ```
+
+ - Add Conda as an environment variable in `~/.bash_profile`.
+
+ ```bash linenums="1"
+ # Press i first to enter edit mode
+ # In the first line type.
+ export PATH="~/opt/anaconda3/bin:$PATH"
+ # If you customized the installation location during installation, change ~/opt/anaconda3/bin to the bin folder in the customized installation directory
+
+ # The modified ~/.bash_profile file should look like this (where xxx is the username)
+ export PATH="~/opt/anaconda3/bin:$PATH"
+ # >>> conda initialize >>>
+ # !!! Contents within this block are managed by 'conda init' !!!
+ __conda_setup="$('/Users/xxx/opt/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
+ if [ $? -eq 0 ]; then
+ eval "$__conda_setup"
+ else
+ if [ -f "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh" ]; then
+ . "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh"
+ else
+ export PATH="/Users/xxx/opt/anaconda3/bin:$PATH"
+ fi
+ fi
+ unset __conda_setup
+ # <<< conda initialize <<<
+ ```
+
+ - When you are done, press `esc` to exit edit mode, then type `:wq!` and enter to save and exit
+
+ - Verify that the Conda command is recognized.
+
+ - Enter `source ~/.bash_profile` in the terminal to update the environment variables
+ - Enter `conda info --envs` in the terminal again, if it shows that there is a base environment, then Conda has been added to the environment variables
+
+- Create a new Conda environment
+
+ ```bash linenums="1"
+ # Enter the following command at the command line to create an environment called paddle_env
+ # Here to speed up the download, use Tsinghua source
+ conda create --name paddle_env python=3.8 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
+ ```
+
+ - This command will create an executable environment named paddle_env with python version 3.8, which will take a while depending on the network status
+
+ - The command line will then output a prompt, type y and enter to continue the installation
+
+ ![conda_create](./images/conda_create.png)
+
+- To activate the Conda environment you just created, enter the following command at the command line.
+
+ ```bash linenums="1"
+ # Activate the paddle_env environment
+ conda activate paddle_env
+ # View the current location of python
+ where python
+ ```
+
+ ![conda_actviate](./images/conda_activate.png)
+
+The above anaconda environment and python environment are installed
+
+### 1.3 Linux
+
+Linux users can choose to run either Anaconda or Docker. If you are familiar with Docker and need to train the PaddleOCR model, it is recommended to use the Docker environment, where the development process of PaddleOCR is run. If you are not familiar with Docker, you can also use Anaconda to run the project.
+
+#### 1.3.1 Anaconda environment configuration
+
+- Note: To use PaddlePaddle you need to install the python environment first, here we choose the python integrated environment Anaconda toolkit
+
+ - Anaconda is a common python package manager
+ - After installing Anaconda, you can install the python environment, as well as numpy and other required toolkit environment
+
+- **Download Anaconda**.
+
+ - Download at:
+
+ ![img](./images/anaconda_download-20240704081644684.png)
+
+ - Select the appropriate version for your operating system
+ - Type `uname -m` in the terminal to check the command set used by your system
+
+ - Download method 1: Download locally, then transfer the installation package to the Linux server
+
+ - Download method 2: Directly use Linux command line to download
+
+ ```bash linenums="1"
+ # First install wget
+ sudo apt-get install wget # Ubuntu
+ sudo yum install wget # CentOS
+ ```
+
+ ```bash linenums="1"
+ # Then use wget to download from Tsinghua source
+ # If you want to download Anaconda3-2021.05-Linux-x86_64.sh, the download command is as follows
+ wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2021.05-Linux-x86_64.sh
+ # If you want to download another version, you need to change the file name after the last 1 / to the version you want to download
+ ```
+
+- To install Anaconda.
+
+ - Type `sh Anaconda3-2021.05-Linux-x86_64.sh` at the command line
+ - If you downloaded a different version, replace the file name of the command with the name of the file you downloaded
+ - Just follow the installation instructions
+ - You can exit by typing q when viewing the license
+
+- **Add conda to the environment variables**
+
+ - If you have already added conda to the environment variable path during the installation, you can skip this step
+
+ - Open `~/.bashrc` in a terminal.
+
+ ```bash linenums="1"
+ # Enter the following command in the terminal.
+ vim ~/.bashrc
+ ```
+
+ - Add conda as an environment variable in `~/.bashrc`.
+
+ ```bash linenums="1"
+ # Press i first to enter edit mode # In the first line enter.
+ export PATH="~/anaconda3/bin:$PATH"
+ # If you customized the installation location during installation, change ~/anaconda3/bin to the bin folder in the customized installation directory
+ ```
+
+ ```bash linenums="1"
+ # The modified ~/.bash_profile file should look like this (where xxx is the username)
+ export PATH="~/opt/anaconda3/bin:$PATH"
+ # >>> conda initialize >>>
+ # !!! Contents within this block are managed by 'conda init' !!!
+ __conda_setup="$('/Users/xxx/opt/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
+ if [ $? -eq 0 ]; then
+ eval "$__conda_setup"
+ else
+ if [ -f "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh" ]; then
+ . "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh"
+ else
+ export PATH="/Users/xxx/opt/anaconda3/bin:$PATH"
+ fi
+ fi
+ unset __conda_setup
+ # <<< conda initialize <<<
+ ```
+
+ - When you are done, press `esc` to exit edit mode, then type `:wq!` and enter to save and exit
+
+ - Verify that the Conda command is recognized.
+
+ - Enter `source ~/.bash_profile` in the terminal to update the environment variables
+ - Enter `conda info --envs` in the terminal again, if it shows that there is a base environment, then Conda has been added to the environment variables
+
+- Create a new Conda environment
+
+ ```bash linenums="1"
+ # Enter the following command at the command line to create an environment called paddle_env
+ # Here to speed up the download, use Tsinghua source
+ conda create --name paddle_env python=3.8 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
+ ```
+
+ - This command will create an executable environment named paddle_env with python version 3.8, which will take a while depending on the network status
+
+ - The command line will then output a prompt, type y and enter to continue the installation
+
+ ![conda_create](./images/conda_create-20240704081656378.png)
+
+- To activate the Conda environment you just created, enter the following command at the command line.
+
+ ```bash linenums="1"
+ # Activate the paddle_env environment
+ conda activate paddle_env
+ ```
+
+The above anaconda environment and python environment are installed
+
+#### 1.3.2 Docker environment preparation
+
+**The first time you use this docker image, it will be downloaded automatically. Please be patient.**
+
+```bash linenums="1"
+# Switch to the working directory
+cd /home/Projects
+# You need to create a docker container for the first run, and do not need to run the current command when you run it again
+# Create a docker container named ppocr and map the current directory to the /paddle directory of the container
+
+# If using CPU, use docker instead of nvidia-docker to create docker
+sudo docker run --name ppocr -v $PWD:/paddle --network=host -it registry.baidubce.com/paddlepaddle/paddle:2.1.3-gpu-cuda10.2-cudnn7 /bin/bash
+
+# If using GPU, use nvidia-docker to create docker
+# docker image registry.baidubce.com/paddlepaddle/paddle:2.1.3-gpu-cuda11.2-cudnn8 is recommended for CUDA11.2 + CUDNN8.
+sudo nvidia-docker run --name ppocr -v $PWD:/paddle --shm-size=64G --network=host -it registry.baidubce.com/paddlepaddle/paddle:2.1.3-gpu-cuda10.2-cudnn7 /bin/bash
+
+```
+
+You can also visit [DockerHub](https://hub.docker.com/r/paddlepaddle/paddle/tags/) to get the image that fits your machine.
+
+```bash linenums="1"
+# ctrl+P+Q to exit docker, to re-enter docker using the following command:
+sudo docker container exec -it ppocr /bin/bash
+```
diff --git a/docs/ppocr/environment.md b/docs/ppocr/environment.md
new file mode 100644
index 0000000000..8cd4bf7e4d
--- /dev/null
+++ b/docs/ppocr/environment.md
@@ -0,0 +1,302 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# 运行环境准备
+
+Windows和Mac用户推荐使用Anaconda搭建Python环境,Linux用户建议使用docker搭建Python环境。
+
+推荐环境:
+
+- PaddlePaddle >= 2.1.2
+- Python 3.7
+- CUDA10.1 / CUDA10.2
+- CUDNN 7.6
+
+> 如果您已经安装Python环境,可以直接参考[PaddleOCR快速开始](./quick_start.md)
+
+## 1. Python环境搭建
+
+### 1.1 Windows
+
+#### 1.1.1 安装Anaconda
+
+- 说明:使用paddlepaddle需要先安装python环境,这里我们选择python集成环境Anaconda工具包
+ - Anaconda是1个常用的python包管理程序
+ - 安装完Anaconda后,可以安装python环境,以及numpy等所需的工具包环境。
+
+- Anaconda下载:
+ - 地址:
+ - 大部分win10电脑均为64位操作系统,选择x86_64版本;若电脑为32位操作系统,则选择x86.exe
+
+ ![anaconda download](./images/Anaconda_download.png)
+ - 下载完成后,双击安装程序进入图形界面
+ - 默认安装位置为C盘,建议将安装位置更改到D盘:
+
+
+
+ - 勾选conda加入环境变量,忽略警告:
+
+
+
+#### 1.1.2 打开终端并创建conda环境
+
+- 打开Anaconda Prompt终端:左下角Windows Start Menu -> Anaconda3 -> Anaconda Prompt启动控制台
+
+ ![anaconda download](./images/anaconda_prompt.png)
+
+- 创建新的conda环境
+
+ ```bash linenums="1"
+ # 在命令行输入以下命令,创建名为paddle_env的环境
+ # 此处为加速下载,使用清华源
+ conda create --name paddle_env python=3.8 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ # 这是一行命令
+ ```
+
+ 该命令会创建1个名为paddle_env、python版本为3.8的可执行环境,根据网络状态,需要花费一段时间
+
+ 之后命令行中会输出提示信息,输入y并回车继续安装
+
+ ![conda create](./images/conda_new_env.png)
+
+- 激活刚创建的conda环境,在命令行中输入以下命令:
+
+ ```bash linenums="1"
+ # 激活paddle_env环境
+ conda activate paddle_env
+ # 查看当前python的位置
+ where python
+ ```
+
+ ![create environment](./images/conda_list_env.png)
+
+以上anaconda环境和python环境安装完毕
+
+### 1.2 Mac
+
+#### 1.2.1 安装Anaconda
+
+- 说明:使用paddlepaddle需要先安装python环境,这里我们选择python集成环境Anaconda工具包
+ - Anaconda是1个常用的python包管理程序
+ - 安装完Anaconda后,可以安装python环境,以及numpy等所需的工具包环境
+- Anaconda下载:
+ - 地址:
+
+ ![anaconda download](./images/anaconda_start.png)
+
+ - 选择最下方的`Anaconda3-2021.05-MacOSX-x86_64.pkg`下载
+- 下载完成后,双击.pkg文件进入图形界面
+ - 按默认设置即可,安装需要花费一段时间
+- 建议安装vscode或pycharm等代码编辑器
+
+#### 1.2.2 打开终端并创建conda环境
+
+- 打开终端
+ - 同时按下command键和空格键,在聚焦搜索中输入"终端",双击进入终端
+
+- **将conda加入环境变量**
+
+ - 加入环境变量是为了让系统能识别conda命令
+
+ - 输入以下命令,在终端中打开`~/.bash_profile`:
+
+ ```bash linenums="1"
+ vim ~/.bash_profile
+ ```
+
+ - 在`~/.bash_profile`中将conda添加为环境变量:
+
+ ```bash linenums="1"
+ # 先按i进入编辑模式
+ # 在第一行输入:
+ export PATH="~/opt/anaconda3/bin:$PATH"
+ # 若安装时自定义了安装位置,则将~/opt/anaconda3/bin改为自定义的安装目录下的bin文件夹
+ ```
+
+ ```bash linenums="1"
+ # 修改后的~/.bash_profile文件应如下(其中xxx为用户名):
+ export PATH="~/opt/anaconda3/bin:$PATH"
+ # >>> conda initialize >>>
+ # !! Contents within this block are managed by 'conda init' !!
+ __conda_setup="$('/Users/xxx/opt/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
+ if [ $? -eq 0 ]; then
+ eval "$__conda_setup"
+ else
+ if [ -f "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh" ]; then
+ . "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh"
+ else
+ export PATH="/Users/xxx/opt/anaconda3/bin:$PATH"
+ fi
+ fi
+ unset __conda_setup
+ # <<< conda initialize <<<
+ ```
+
+ - 修改完成后,先按`esc`键退出编辑模式,再输入`:wq!`并回车,以保存退出
+
+ - 验证是否能识别conda命令:
+
+ - 在终端中输入`source ~/.bash_profile`以更新环境变量
+ - 再在终端输入`conda info --envs`,若能显示当前有base环境,则conda已加入环境变量
+
+- 创建新的conda环境
+
+ ```bash linenums="1"
+ # 在命令行输入以下命令,创建名为paddle_env的环境
+ # 此处为加速下载,使用清华源
+ conda create --name paddle_env python=3.8 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
+ ```
+
+ - 该命令会创建1个名为paddle_env、python版本为3.8的可执行环境,根据网络状态,需要花费一段时间
+
+ - 之后命令行中会输出提示信息,输入y并回车继续安装
+
+ ![conda_create](./images/conda_create.png)
+
+- 激活刚创建的conda环境,在命令行中输入以下命令:
+
+ ```bash linenums="1"
+ # 激活paddle_env环境
+ conda activate paddle_env
+ # 查看当前python的位置
+ where python
+ ```
+
+ ![conda_actviate](./images/conda_activate.png)
+
+以上anaconda环境和python环境安装完毕
+
+### 1.3 Linux
+
+Linux用户可选择Anaconda或Docker两种方式运行。如果你熟悉Docker且需要训练PaddleOCR模型,推荐使用Docker环境,PaddleOCR的开发流程均在Docker环境下运行。如果你不熟悉Docker,也可以使用Anaconda来运行项目。
+
+#### 1.3.1 Anaconda环境配置
+
+- 说明:使用paddlepaddle需要先安装python环境,这里我们选择python集成环境Anaconda工具包
+ - Anaconda是1个常用的python包管理程序
+ - 安装完Anaconda后,可以安装python环境,以及numpy等所需的工具包环境
+
+- **下载Anaconda**:
+
+ - 下载地址:
+
+ ![img](./images/anaconda_download-20240704081644684.png)
+
+ - 选择适合您操作系统的版本
+ - 可在终端输入`uname -m`查询系统所用的指令集
+
+- 下载法1:本地下载,再将安装包传到linux服务器上
+
+- 下载法2:直接使用linux命令行下载
+
+ ```bash linenums="1"
+ # 首先安装wget
+ sudo apt-get install wget # Ubuntu
+ sudo yum install wget # CentOS
+ ```
+
+ ```bash linenums="1"
+ # 然后使用wget从清华源上下载
+ # 如要下载Anaconda3-2021.05-Linux-x86_64.sh,则下载命令如下:
+ wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2021.05-Linux-x86_64.sh
+
+ # 若您要下载其他版本,需要将最后1个/后的文件名改成您希望下载的版本
+ ```
+
+- 安装Anaconda:
+
+ - 在命令行输入`sh Anaconda3-2021.05-Linux-x86_64.sh`
+ - 若您下载的是其它版本,则将该命令的文件名替换为您下载的文件名
+ - 按照安装提示安装即可
+ - 查看许可时可输入q来退出
+
+- **将conda加入环境变量**
+
+ - 加入环境变量是为了让系统能识别conda命令,若您在安装时已将conda加入环境变量path,则可跳过本步
+
+ - 在终端中打开`~/.bashrc`:
+
+ ```bash linenums="1"
+ # 在终端中输入以下命令:
+ vim ~/.bashrc
+ ```
+
+ - 在`~/.bashrc`中将conda添加为环境变量:
+
+ ```bash linenums="1"
+ # 先按i进入编辑模式
+ # 在第一行输入:
+ export PATH="~/anaconda3/bin:$PATH"
+ # 若安装时自定义了安装位置,则将~/anaconda3/bin改为自定义的安装目录下的bin文件夹
+ ```
+
+ ```bash linenums="1"
+ # 修改后的~/.bash_profile文件应如下(其中xxx为用户名):
+ export PATH="~/opt/anaconda3/bin:$PATH"
+ # >>> conda initialize >>>
+ # !! Contents within this block are managed by 'conda init' !!
+ __conda_setup="$('/Users/xxx/opt/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
+ if [ $? -eq 0 ]; then
+ eval "$__conda_setup"
+ else
+ if [ -f "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh" ]; then
+ . "/Users/xxx/opt/anaconda3/etc/profile.d/conda.sh"
+ else
+ export PATH="/Users/xxx/opt/anaconda3/bin:$PATH"
+ fi
+ fi
+ unset __conda_setup
+ # <<< conda initialize <<<
+ ```
+
+ - 修改完成后,先按`esc`键退出编辑模式,再输入`:wq!`并回车,以保存退出
+
+ - 验证是否能识别conda命令:
+
+ - 在终端中输入`source ~/.bash_profile`以更新环境变量
+ - 再在终端输入`conda info --envs`,若能显示当前有base环境,则conda已加入环境变量
+
+- 创建新的conda环境
+
+ ```bash linenums="1"
+ # 在命令行输入以下命令,创建名为paddle_env的环境
+ # 此处为加速下载,使用清华源
+ conda create --name paddle_env python=3.8 --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
+ ```
+
+ - 该命令会创建1个名为paddle_env、python版本为3.8的可执行环境,根据网络状态,需要花费一段时间
+
+ - 之后命令行中会输出提示信息,输入y并回车继续安装
+
+ ![conda_create](./images/conda_create-20240704081656378.png)
+
+- 激活刚创建的conda环境,在命令行中输入以下命令:
+
+ ```bash linenums="1"
+ # 激活paddle_env环境
+ conda activate paddle_env
+ ```
+
+以上anaconda环境和python环境安装完毕
+
+#### 1.3.2 Docker环境配置
+
+**注意:第一次使用这个镜像,会自动下载该镜像,请耐心等待。您也可以访问[DockerHub](https://hub.docker.com/r/paddlepaddle/paddle/tags/)获取与您机器适配的镜像。**
+
+```bash linenums="1"
+# 切换到工作目录下
+cd /home/Projects
+# 首次运行需创建一个docker容器,再次运行时不需要运行当前命令
+# 创建一个名字为ppocr的docker容器,并将当前目录映射到容器的/paddle目录下
+
+#如果您希望在CPU环境下使用docker,使用docker而不是nvidia-docker创建docker
+sudo docker run --name ppocr -v $PWD:/paddle --network=host -it registry.baidubce.com/paddlepaddle/paddle:2.1.3-gpu-cuda10.2-cudnn7 /bin/bash
+
+#如果使用CUDA10,请运行以下命令创建容器,设置docker容器共享内存shm-size为64G,建议设置32G以上
+# 如果是CUDA11+CUDNN8,推荐使用镜像registry.baidubce.com/paddlepaddle/paddle:2.1.3-gpu-cuda11.2-cudnn8
+sudo nvidia-docker run --name ppocr -v $PWD:/paddle --shm-size=64G --network=host -it registry.baidubce.com/paddlepaddle/paddle:2.1.3-gpu-cuda10.2-cudnn7 /bin/bash
+
+# ctrl+P+Q可退出docker 容器,重新进入docker 容器使用如下命令
+sudo docker container exec -it ppocr /bin/bash
+```
diff --git a/docs/ppocr/images/254.jpg b/docs/ppocr/images/254.jpg
new file mode 100644
index 0000000000..c871fb042c
Binary files /dev/null and b/docs/ppocr/images/254.jpg differ
diff --git a/docs/ppocr/images/Anaconda_download.png b/docs/ppocr/images/Anaconda_download.png
new file mode 100644
index 0000000000..83a0341493
Binary files /dev/null and b/docs/ppocr/images/Anaconda_download.png differ
diff --git a/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic001.jpg b/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic001.jpg
new file mode 100644
index 0000000000..45ffdb53aa
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic001.jpg differ
diff --git a/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic002.jpg b/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic002.jpg
new file mode 100644
index 0000000000..7ac153aee0
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic002.jpg differ
diff --git a/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic003.jpg b/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic003.jpg
new file mode 100644
index 0000000000..781aade629
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv2/PP-OCRv2-pic003.jpg differ
diff --git a/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic001.jpg b/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic001.jpg
new file mode 100644
index 0000000000..c35936cc1a
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic001.jpg differ
diff --git a/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic002.jpg b/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic002.jpg
new file mode 100644
index 0000000000..e5ad6a4b2a
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic002.jpg differ
diff --git a/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic003.jpg b/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic003.jpg
new file mode 100644
index 0000000000..dc024296bd
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/ch/PP-OCRv3-pic003.jpg differ
diff --git a/docs/ppocr/images/PP-OCRv3/en/en_1.png b/docs/ppocr/images/PP-OCRv3/en/en_1.png
new file mode 100644
index 0000000000..36245613e3
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/en/en_1.png differ
diff --git a/docs/ppocr/images/PP-OCRv3/en/en_2.png b/docs/ppocr/images/PP-OCRv3/en/en_2.png
new file mode 100644
index 0000000000..d2df8556ad
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/en/en_2.png differ
diff --git a/docs/ppocr/images/PP-OCRv3/en/en_3.png b/docs/ppocr/images/PP-OCRv3/en/en_3.png
new file mode 100644
index 0000000000..baf146c010
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/en/en_3.png differ
diff --git a/docs/ppocr/images/PP-OCRv3/en/en_4.png b/docs/ppocr/images/PP-OCRv3/en/en_4.png
new file mode 100644
index 0000000000..f0f19db95b
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/en/en_4.png differ
diff --git a/docs/ppocr/images/PP-OCRv3/multi_lang/japan_2.jpg b/docs/ppocr/images/PP-OCRv3/multi_lang/japan_2.jpg
new file mode 100644
index 0000000000..076ced92ad
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/multi_lang/japan_2.jpg differ
diff --git a/docs/ppocr/images/PP-OCRv3/multi_lang/korean_1.jpg b/docs/ppocr/images/PP-OCRv3/multi_lang/korean_1.jpg
new file mode 100644
index 0000000000..f93de40e18
Binary files /dev/null and b/docs/ppocr/images/PP-OCRv3/multi_lang/korean_1.jpg differ
diff --git a/docs/ppocr/images/anaconda_download-20240704081644684.png b/docs/ppocr/images/anaconda_download-20240704081644684.png
new file mode 100644
index 0000000000..6ab6db3089
Binary files /dev/null and b/docs/ppocr/images/anaconda_download-20240704081644684.png differ
diff --git a/docs/ppocr/images/anaconda_install_env.png b/docs/ppocr/images/anaconda_install_env.png
new file mode 100644
index 0000000000..7a22542712
Binary files /dev/null and b/docs/ppocr/images/anaconda_install_env.png differ
diff --git a/docs/ppocr/images/anaconda_install_folder.png b/docs/ppocr/images/anaconda_install_folder.png
new file mode 100644
index 0000000000..e9fac29eaa
Binary files /dev/null and b/docs/ppocr/images/anaconda_install_folder.png differ
diff --git a/docs/ppocr/images/anaconda_prompt.png b/docs/ppocr/images/anaconda_prompt.png
new file mode 100644
index 0000000000..1087610ae0
Binary files /dev/null and b/docs/ppocr/images/anaconda_prompt.png differ
diff --git a/docs/ppocr/images/anaconda_start.png b/docs/ppocr/images/anaconda_start.png
new file mode 100644
index 0000000000..a860f5e56a
Binary files /dev/null and b/docs/ppocr/images/anaconda_start.png differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/00006737.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00006737.jpg
new file mode 100644
index 0000000000..d7762d2e2c
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00006737.jpg differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/00009282.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00009282.jpg
new file mode 100644
index 0000000000..0383d445bd
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00009282.jpg differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/00015504.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00015504.jpg
new file mode 100644
index 0000000000..9162cf1479
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00015504.jpg differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/00059985.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00059985.jpg
new file mode 100644
index 0000000000..03fd19784a
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00059985.jpg differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/00111002.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00111002.jpg
new file mode 100644
index 0000000000..7dae24a92d
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/00111002.jpg differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/img_12.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/img_12.jpg
new file mode 100644
index 0000000000..11ac4ed6ce
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/img_12.jpg differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/rotate_00052204.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/rotate_00052204.jpg
new file mode 100644
index 0000000000..643b850da8
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/rotate_00052204.jpg differ
diff --git a/docs/ppocr/images/ch_ppocr_mobile_v2.0/test_add_91.jpg b/docs/ppocr/images/ch_ppocr_mobile_v2.0/test_add_91.jpg
new file mode 100644
index 0000000000..b5ded6e1de
Binary files /dev/null and b/docs/ppocr/images/ch_ppocr_mobile_v2.0/test_add_91.jpg differ
diff --git a/docs/ppocr/images/conda_activate.png b/docs/ppocr/images/conda_activate.png
new file mode 100644
index 0000000000..a2e6074e91
Binary files /dev/null and b/docs/ppocr/images/conda_activate.png differ
diff --git a/docs/ppocr/images/conda_create-20240704081656378.png b/docs/ppocr/images/conda_create-20240704081656378.png
new file mode 100644
index 0000000000..533f592b7c
Binary files /dev/null and b/docs/ppocr/images/conda_create-20240704081656378.png differ
diff --git a/docs/ppocr/images/conda_create.png b/docs/ppocr/images/conda_create.png
new file mode 100644
index 0000000000..9ff10c241b
Binary files /dev/null and b/docs/ppocr/images/conda_create.png differ
diff --git a/docs/ppocr/images/conda_list_env.png b/docs/ppocr/images/conda_list_env.png
new file mode 100644
index 0000000000..5ffa0037c5
Binary files /dev/null and b/docs/ppocr/images/conda_list_env.png differ
diff --git a/docs/ppocr/images/conda_new_env.png b/docs/ppocr/images/conda_new_env.png
new file mode 100644
index 0000000000..eed667ec3d
Binary files /dev/null and b/docs/ppocr/images/conda_new_env.png differ
diff --git a/docs/ppocr/images/model_prod_flow_ch.png b/docs/ppocr/images/model_prod_flow_ch.png
new file mode 100644
index 0000000000..4906b2716e
Binary files /dev/null and b/docs/ppocr/images/model_prod_flow_ch.png differ
diff --git a/docs/ppocr/images/multi_lang/arabic_0.jpg b/docs/ppocr/images/multi_lang/arabic_0.jpg
new file mode 100644
index 0000000000..9941b90642
Binary files /dev/null and b/docs/ppocr/images/multi_lang/arabic_0.jpg differ
diff --git a/docs/ppocr/images/multi_lang/en_1.jpg b/docs/ppocr/images/multi_lang/en_1.jpg
new file mode 100644
index 0000000000..2dc84d3f04
Binary files /dev/null and b/docs/ppocr/images/multi_lang/en_1.jpg differ
diff --git a/docs/ppocr/images/multi_lang/en_2.jpg b/docs/ppocr/images/multi_lang/en_2.jpg
new file mode 100644
index 0000000000..455ec98ed3
Binary files /dev/null and b/docs/ppocr/images/multi_lang/en_2.jpg differ
diff --git a/docs/ppocr/images/multi_lang/en_3.jpg b/docs/ppocr/images/multi_lang/en_3.jpg
new file mode 100644
index 0000000000..36eb063d78
Binary files /dev/null and b/docs/ppocr/images/multi_lang/en_3.jpg differ
diff --git a/docs/ppocr/images/multi_lang/french_0.jpg b/docs/ppocr/images/multi_lang/french_0.jpg
new file mode 100644
index 0000000000..3c2abe6304
Binary files /dev/null and b/docs/ppocr/images/multi_lang/french_0.jpg differ
diff --git a/docs/ppocr/images/multi_lang/img_01.jpg b/docs/ppocr/images/multi_lang/img_01.jpg
new file mode 100644
index 0000000000..ee6ca69207
Binary files /dev/null and b/docs/ppocr/images/multi_lang/img_01.jpg differ
diff --git a/docs/ppocr/images/multi_lang/img_02.jpg b/docs/ppocr/images/multi_lang/img_02.jpg
new file mode 100644
index 0000000000..3e139c76bc
Binary files /dev/null and b/docs/ppocr/images/multi_lang/img_02.jpg differ
diff --git a/docs/ppocr/images/multi_lang/img_12.jpg b/docs/ppocr/images/multi_lang/img_12.jpg
new file mode 100644
index 0000000000..822d562eda
Binary files /dev/null and b/docs/ppocr/images/multi_lang/img_12.jpg differ
diff --git a/docs/ppocr/images/multi_lang/japan_2.jpg b/docs/ppocr/images/multi_lang/japan_2.jpg
new file mode 100644
index 0000000000..7038ba2eff
Binary files /dev/null and b/docs/ppocr/images/multi_lang/japan_2.jpg differ
diff --git a/docs/ppocr/images/multi_lang/korean_0.jpg b/docs/ppocr/images/multi_lang/korean_0.jpg
new file mode 100644
index 0000000000..3fe6305aa0
Binary files /dev/null and b/docs/ppocr/images/multi_lang/korean_0.jpg differ
diff --git a/docs/ppocr/images/ppocr_framework.png b/docs/ppocr/images/ppocr_framework.png
new file mode 100644
index 0000000000..ab51c88fe6
Binary files /dev/null and b/docs/ppocr/images/ppocr_framework.png differ
diff --git a/docs/ppocr/images/ppocrv2_framework.jpg b/docs/ppocr/images/ppocrv2_framework.jpg
new file mode 100644
index 0000000000..e5f1a2ef47
Binary files /dev/null and b/docs/ppocr/images/ppocrv2_framework.jpg differ
diff --git a/docs/ppocr/infer_deploy/Jetson_infer.en.md b/docs/ppocr/infer_deploy/Jetson_infer.en.md
new file mode 100644
index 0000000000..6b429d9358
--- /dev/null
+++ b/docs/ppocr/infer_deploy/Jetson_infer.en.md
@@ -0,0 +1,94 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# Jetson Deployment for PaddleOCR
+
+This section introduces the deployment of PaddleOCR on Jetson NX, TX2, nano, AGX and other series of hardware.
+
+## 1. Prepare Environment
+
+You need to prepare a Jetson development hardware. If you need TensorRT, you need to prepare the TensorRT environment. It is recommended to use TensorRT version 7.1.3;
+
+### 1. Install PaddlePaddle in Jetson
+
+The PaddlePaddle download [link](https://www.paddlepaddle.org.cn/inference/user_guides/download_lib.html#python)
+Please select the appropriate installation package for your Jetpack version, cuda version, and trt version. Here, we download paddlepaddle_gpu-2.3.0rc0-cp36-cp36m-linux_aarch64.whl.
+
+Install PaddlePaddle:
+
+```bash linenums="1"
+pip3 install -U paddlepaddle_gpu-2.3.0rc0-cp36-cp36m-linux_aarch64.whl
+```
+
+### 2. Download PaddleOCR code and install dependencies
+
+Clone the PaddleOCR code:
+
+```bash linenums="1"
+git clone https://github.com/PaddlePaddle/PaddleOCR
+```
+
+and install dependencies:
+
+```bash linenums="1"
+cd PaddleOCR
+pip3 install -r requirements.txt
+```
+
+- Note: Jetson hardware CPU is poor, dependency installation is slow, please wait patiently
+
+## 2. Perform prediction
+
+Obtain the PPOCR model from the [document](../model_list.en.md) model library. The following takes the PP-OCRv3 model as an example to introduce the use of the PPOCR model on Jetson:
+
+Download and unzip the PP-OCRv3 models.
+
+```bash linenums="1"
+wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar
+wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar
+tar xf ch_PP-OCRv3_det_infer.tar
+tar xf ch_PP-OCRv3_rec_infer.tar
+```
+
+The text detection inference:
+
+```bash linenums="1"
+cd PaddleOCR
+python3 tools/infer/predict_det.py --det_model_dir=./inference/ch_PP-OCRv2_det_infer/ --image_dir=./doc/imgs/french_0.jpg --use_gpu=True
+```
+
+After executing the command, the predicted information will be printed out in the terminal, and the visualization results will be saved in the `./inference_results/` directory.
+
+![](./images/det_res_french_0.jpg)
+
+The text recognition inference:
+
+```bash linenums="1"
+python3 tools/infer/predict_det.py --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --image_dir=./doc/imgs_words/en/word_2.png --use_gpu=True --rec_image_shape="3,48,320"
+```
+
+After executing the command, the predicted information will be printed on the terminal, and the output is as follows:
+
+```bash linenums="1"
+[2022/04/28 15:41:45] root INFO: Predicts of ./doc/imgs_words/en/word_2.png:('yourself', 0.98084533)
+```
+
+The text detection and text recognition inference:
+
+```bash linenums="1"
+python3 tools/infer/predict_system.py --det_model_dir=./inference/ch_PP-OCRv2_det_infer/ --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --image_dir=./doc/imgs/00057937.jpg --use_gpu=True --rec_image_shape="3,48,320"
+```
+
+After executing the command, the predicted information will be printed out in the terminal, and the visualization results will be saved in the `./inference_results/` directory.
+
+![](./images/00057937.jpg)
+
+To enable TRT prediction, you only need to set `--use_tensorrt=True` on the basis of the above command:
+
+```bash linenums="1"
+python3 tools/infer/predict_system.py --det_model_dir=./inference/ch_PP-OCRv2_det_infer/ --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --image_dir=./doc/imgs/ --rec_image_shape="3,48,320" --use_gpu=True --use_tensorrt=True
+```
+
+For more ppocr model predictions, please refer to[document](../model_list.en.md)
diff --git a/docs/ppocr/infer_deploy/Jetson_infer.md b/docs/ppocr/infer_deploy/Jetson_infer.md
new file mode 100644
index 0000000000..a8f6d74a24
--- /dev/null
+++ b/docs/ppocr/infer_deploy/Jetson_infer.md
@@ -0,0 +1,95 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# Jetson部署PaddleOCR模型
+
+本节介绍PaddleOCR在Jetson NX、TX2、nano、AGX等系列硬件的部署。
+
+## 1. 环境准备
+
+需要准备一台Jetson开发板,如果需要TensorRT预测,需准备好TensorRT环境,建议使用7.1.3版本的TensorRT;
+
+### 1. Jetson安装PaddlePaddle
+
+PaddlePaddle下载[链接](https://www.paddlepaddle.org.cn/inference/user_guides/download_lib.html#python)
+请选择适合的您Jetpack版本、cuda版本、trt版本的安装包。
+
+安装命令:
+
+```bash linenums="1"
+# 安装paddle,以paddlepaddle_gpu-2.3.0rc0-cp36-cp36m-linux_aarch64.whl 为例
+pip3 install -U paddlepaddle_gpu-2.3.0rc0-cp36-cp36m-linux_aarch64.whl
+```
+
+### 2. 下载PaddleOCR代码并安装依赖
+
+首先 clone PaddleOCR 代码:
+
+```bash linenums="1"
+git clone https://github.com/PaddlePaddle/PaddleOCR
+```
+
+然后,安装依赖:
+
+```bash linenums="1"
+cd PaddleOCR
+pip3 install -r requirements.txt
+```
+
+- 注:jetson硬件CPU较差,依赖安装较慢,请耐心等待
+
+## 2. 执行预测
+
+从[文档](../model_list.md) 模型库中获取PPOCR模型,下面以PP-OCRv3模型为例,介绍在PPOCR模型在jetson上的使用方式:
+
+下载并解压PP-OCRv3模型
+
+```bash linenums="1"
+wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar
+wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar
+tar xf ch_PP-OCRv3_det_infer.tar
+tar xf ch_PP-OCRv3_rec_infer.tar
+```
+
+执行文本检测预测:
+
+```bash linenums="1"
+cd PaddleOCR
+python3 tools/infer/predict_det.py --det_model_dir=./inference/ch_PP-OCRv2_det_infer/ --image_dir=./doc/imgs/french_0.jpg --use_gpu=True
+```
+
+执行命令后在终端会打印出预测的信息,并在 `./inference_results/` 下保存可视化结果。
+
+![img](./images/det_res_french_0.jpg)
+
+执行文本识别预测:
+
+```bash linenums="1"
+python3 tools/infer/predict_det.py --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --image_dir=./doc/imgs_words/en/word_2.png --use_gpu=True --rec_image_shape="3,48,320"
+```
+
+执行命令后在终端会打印出预测的信息,输出如下:
+
+```bash linenums="1"
+[2022/04/28 15:41:45] root INFO: Predicts of ./doc/imgs_words/en/word_2.png:('yourself', 0.98084533)
+```
+
+执行文本检测+文本识别串联预测:
+
+```bash linenums="1"
+python3 tools/infer/predict_system.py --det_model_dir=./inference/ch_PP-OCRv2_det_infer/ --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --image_dir=./doc/imgs/ --use_gpu=True --rec_image_shape="3,48,320"
+```
+
+执行命令后在终端会打印出预测的信息,并在 `./inference_results/` 下保存可视化结果
+
+![img](./images/00057937.jpg)
+
+开启TRT预测只需要在以上命令基础上设置`--use_tensorrt=True`即可:
+
+```bash linenums="1"
+python3 tools/infer/predict_system.py --det_model_dir=./inference/ch_PP-OCRv2_det_infer/ --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --image_dir=./doc/imgs/00057937.jpg --use_gpu=True --use_tensorrt=True --rec_image_shape="3,48,320"
+```
+
+更多ppocr模型预测请参考[文档](../model_list.md)
diff --git a/docs/ppocr/infer_deploy/benchmark.en.md b/docs/ppocr/infer_deploy/benchmark.en.md
new file mode 100755
index 0000000000..2d1a0ecc7a
--- /dev/null
+++ b/docs/ppocr/infer_deploy/benchmark.en.md
@@ -0,0 +1,41 @@
+---
+comments: true
+---
+
+# Benchmark
+
+This document gives the performance of the series models for Chinese and English recognition.
+
+## Test Data
+
+We collected 300 images for different real application scenarios to evaluate the overall OCR system, including contract samples, license plates, nameplates, train tickets, test sheets, forms, certificates, street view images, business cards, digital meter, etc. The following figure shows some images of the test set.
+
+![img](./images/doc.jpg)
+
+## Measurement
+
+Explanation:
+
+- The long size of the input for the text detector is 960.
+
+- The evaluation time-consuming stage is the complete stage from image input to result output, including image pre-processing and post-processing.
+
+- `Intel Xeon 6148` is the server-side CPU model. Intel MKL-DNN is used in the test to accelerate the CPU prediction speed.
+
+- `Snapdragon 855` is a mobile processing platform model.
+
+Compares the model size and F-score:
+
+| Model Name | Model Size of the Whole System\(M\) | Model Size of the Text Detector\(M\) | Model Size of the Direction Classifier\(M\) | Model Size of the Text Recognizer \(M\) | F\-score |
+|:-:|:-:|:-:|:-:|:-:|:-:|
+| PP-OCRv2 | 11\.6 | 3\.0 | 0\.9 | 8\.6 | 0\.5224 |
+| PP-OCR mobile | 8\.1 | 2\.6 | 0\.9 | 4\.6 | 0\.503 |
+| PP-OCR server | 155\.1 | 47\.2 | 0\.9 | 107 | 0\.570 |
+
+Compares the time-consuming on CPU and T4 GPU (ms):
+
+| Model Name | CPU | T4 GPU |
+|:-:|:-:|:-:|
+| PP-OCRv2 | 330 | 111 |
+| PP-OCR mobile | 356 | 116|
+| PP-OCR server | 1056 | 200 |
diff --git a/docs/ppocr/infer_deploy/benchmark.md b/docs/ppocr/infer_deploy/benchmark.md
new file mode 100644
index 0000000000..5de86e3647
--- /dev/null
+++ b/docs/ppocr/infer_deploy/benchmark.md
@@ -0,0 +1,39 @@
+---
+typora-copy-images-to: images
+comments: true
+---
+
+# Benchmark
+
+本文给出了中英文OCR系列模型精度指标和在各平台预测耗时的benchmark。
+
+## 测试数据
+
+针对OCR实际应用场景,包括合同,车牌,铭牌,火车票,化验单,表格,证书,街景文字,名片,数码显示屏等,收集的300张图像,每张图平均有17个文本框,下图给出了一些图像示例。
+
+![img](./images/doc.jpg)
+
+## 评估指标
+
+说明:
+
+- 检测输入图像的长边尺寸是960。
+- 评估耗时阶段为图像预测耗时,不包括图像的预处理和后处理。
+- `Intel至强6148`为服务器端CPU型号,测试中使用Intel MKL-DNN 加速。
+- `骁龙855`为移动端处理平台型号。
+
+预测模型大小和整体识别精度对比
+
+| 模型名称 | 整体模型 大小\(M\) | 检测模型 大小\(M\) | 方向分类器 模型大小\(M\) | 识别模型 大小\(M\) | 整体识别 F\-score |
+| :-----------: | :-------------------: | :-------------------: | :-------------------------: | :-------------------: | :------------------: |
+| PP-OCRv2 | 11\.6 | 3\.0 | 0\.9 | 8\.6 | 0\.5224 |
+| PP-OCR mobile | 8\.1 | 2\.6 | 0\.9 | 4\.6 | 0\.503 |
+| PP-OCR server | 155\.1 | 47\.2 | 0\.9 | 107 | 0\.570 |
+
+预测模型在CPU和GPU上的速度对比,单位ms
+
+| 模型名称 | CPU | T4 GPU |
+| :-----------: | :---: | :----: |
+| PP-OCRv2 | 330 | 111 |
+| PP-OCR mobile | 356 | 11 6 |
+| PP-OCR server | 1056 | 200 |
diff --git a/docs/ppocr/infer_deploy/cpp_infer.en.md b/docs/ppocr/infer_deploy/cpp_infer.en.md
new file mode 100644
index 0000000000..f18c8c8aae
--- /dev/null
+++ b/docs/ppocr/infer_deploy/cpp_infer.en.md
@@ -0,0 +1,433 @@
+---
+comments: true
+---
+
+# Server-side C++ Inference
+
+This chapter introduces the C++ deployment steps of the PaddleOCR model. C++ is better than Python in terms of performance. Therefore, in CPU and GPU deployment scenarios, C++ deployment is mostly used.
+This section will introduce how to configure the C++ environment and deploy PaddleOCR in Linux (CPU\GPU) environment. For Windows deployment please refer to [Windows](./windows_vs2019_build.en.md) compilation guidelines.
+
+## 1. Prepare the Environment
+
+### 1.1 Environment
+
+- Linux, docker is recommended.
+- Windows.
+
+### 1.2 Compile OpenCV
+
+- First of all, you need to download the source code compiled package in the Linux environment from the OpenCV official website. Taking OpenCV 3.4.7 as an example, the download command is as follows.
+
+```bash linenums="1"
+cd deploy/cpp_infer
+wget https://paddleocr.bj.bcebos.com/libs/opencv/opencv-3.4.7.tar.gz
+tar -xf opencv-3.4.7.tar.gz
+```
+
+Finally, you will see the folder of `opencv-3.4.7/` in the current directory.
+
+- Compile OpenCV, the OpenCV source path (`root_path`) and installation path (`install_path`) should be set by yourself. Enter the OpenCV source code path and compile it in the following way.
+
+```bash linenums="1"
+root_path=your_opencv_root_path
+install_path=${root_path}/opencv3
+
+rm -rf build
+mkdir build
+cd build
+
+cmake .. \
+ -DCMAKE_INSTALL_PREFIX=${install_path} \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DBUILD_SHARED_LIBS=OFF \
+ -DWITH_IPP=OFF \
+ -DBUILD_IPP_IW=OFF \
+ -DWITH_LAPACK=OFF \
+ -DWITH_EIGEN=OFF \
+ -DCMAKE_INSTALL_LIBDIR=lib64 \
+ -DWITH_ZLIB=ON \
+ -DBUILD_ZLIB=ON \
+ -DWITH_JPEG=ON \
+ -DBUILD_JPEG=ON \
+ -DWITH_PNG=ON \
+ -DBUILD_PNG=ON \
+ -DWITH_TIFF=ON \
+ -DBUILD_TIFF=ON
+
+make -j
+make install
+```
+
+In the above commands, `root_path` is the downloaded OpenCV source code path, and `install_path` is the installation path of OpenCV. After `make install` is completed, the OpenCV header file and library file will be generated in this folder for later OCR source code compilation.
+
+The final file structure under the OpenCV installation path is as follows.
+
+```text linenums="1"
+opencv3/
+|-- bin
+|-- include
+|-- lib
+|-- lib64
+|-- share
+```
+
+### 1.3 Compile or Download or the Paddle Inference Library
+
+- There are 2 ways to obtain the Paddle inference library, described in detail below.
+
+#### 1.3.1 Direct download and installation
+
+[Paddle inference library official website](https://www.paddlepaddle.org.cn/inference/master/guides/install/download_lib.html#linux). You can review and select the appropriate version of the inference library on the official website.
+
+- After downloading, use the following command to extract files.
+
+```bash linenums="1"
+tar -xf paddle_inference.tgz
+```
+
+Finally you will see the folder of `paddle_inference/` in the current path.
+
+#### 1.3.2 Compile the inference source code
+
+- If you want to get the latest Paddle inference library features, you can download the latest code from Paddle GitHub repository and compile the inference library from the source code. It is recommended to download the inference library with paddle version greater than or equal to 2.0.1.
+
+- You can refer to [Paddle inference library](https://www.paddlepaddle.org.cn/documentation/docs/en/advanced_guide/inference_deployment/inference/build_and_install_lib_en.html) to get the Paddle source code from GitHub, and then compile To generate the latest inference library. The method of using git to access the code is as follows.
+
+```bash linenums="1"
+git clone https://github.com/PaddlePaddle/Paddle.git
+git checkout develop
+```
+
+- Enter the Paddle directory and run the following commands to compile the paddle inference library.
+
+```bash linenums="1"
+rm -rf build
+mkdir build
+cd build
+
+cmake .. \
+ -DWITH_CONTRIB=OFF \
+ -DWITH_MKL=ON \
+ -DWITH_MKLDNN=ON \
+ -DWITH_TESTING=OFF \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DWITH_INFERENCE_API_TEST=OFF \
+ -DON_INFER=ON \
+ -DWITH_PYTHON=ON
+make -j
+make inference_lib_dist
+```
+
+For more compilation parameter options, please refer to the [document](https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#congyuanmabianyi).
+
+- After the compilation process, you can see the following files in the folder of `build/paddle_inference_install_dir/`.
+
+```text linenums="1"
+build/paddle_inference_install_dir/
+|-- CMakeCache.txt
+|-- paddle
+|-- third_party
+|-- version.txt
+```
+
+`paddle` is the Paddle library required for C++ prediction later, and `version.txt` contains the version information of the current inference library.
+
+## 2. Compile and Run the Demo
+
+### 2.1 Export the inference model
+
+- You can refer to [Model inference](./python_infer.en.md) and export the inference model. After the model is exported, assuming it is placed in the `inference` directory, the directory structure is as follows.
+
+```text linenums="1"
+inference/
+|-- det_db
+| |--inference.pdiparams
+| |--inference.pdmodel
+|-- rec_rcnn
+| |--inference.pdiparams
+| |--inference.pdmodel
+|-- cls
+| |--inference.pdiparams
+| |--inference.pdmodel
+|-- table
+| |--inference.pdiparams
+| |--inference.pdmodel
+|-- layout
+| |--inference.pdiparams
+| |--inference.pdmodel
+```
+
+### 2.2 Compile PaddleOCR C++ inference demo
+
+- The compilation commands are as follows. The addresses of Paddle C++ inference library, opencv and other Dependencies need to be replaced with the actual addresses on your own machines.
+
+```bash linenums="1"
+sh tools/build.sh
+```
+
+Specifically, you should modify the paths in `tools/build.sh`. The related content is as follows.
+
+```bash linenums="1"
+OPENCV_DIR=your_opencv_dir
+LIB_DIR=your_paddle_inference_dir
+CUDA_LIB_DIR=your_cuda_lib_dir
+CUDNN_LIB_DIR=your_cudnn_lib_dir
+```
+
+`OPENCV_DIR` is the OpenCV installation path; `LIB_DIR` is the download (`paddle_inference` folder)
+or the generated Paddle inference library path (`build/paddle_inference_install_dir` folder);
+`CUDA_LIB_DIR` is the CUDA library file path, in docker; it is `/usr/local/cuda/lib64`; `CUDNN_LIB_DIR` is the cuDNN library file path, in docker it is `/usr/lib/x86_64-linux-gnu/`.
+
+- After the compilation is completed, an executable file named `ppocr` will be generated in the `build` folder.
+
+### 2.3 Run the demo
+
+Execute the built executable file:
+
+```bash linenums="1"
+./build/ppocr [--param1] [--param2] [...]
+```
+
+**Note**:ppocr uses the `PP-OCRv3` model by default, and the input shape used by the recognition model is `3, 48, 320`, if you want to use the old version model, you should add the parameter `--rec_img_h=32`.
+
+Specifically,
+
+#### 1. det+cls+rec
+
+```bash linenums="1"
+./build/ppocr --det_model_dir=inference/det_db \
+ --rec_model_dir=inference/rec_rcnn \
+ --cls_model_dir=inference/cls \
+ --image_dir=../../doc/imgs/12.jpg \
+ --use_angle_cls=true \
+ --det=true \
+ --rec=true \
+ --cls=true \
+```
+
+#### 2. det+rec
+
+```bash linenums="1"
+./build/ppocr --det_model_dir=inference/det_db \
+ --rec_model_dir=inference/rec_rcnn \
+ --image_dir=../../doc/imgs/12.jpg \
+ --use_angle_cls=false \
+ --det=true \
+ --rec=true \
+ --cls=false \
+```
+
+#### 3. det
+
+```bash linenums="1"
+./build/ppocr --det_model_dir=inference/det_db \
+ --image_dir=../../doc/imgs/12.jpg \
+ --det=true \
+ --rec=false
+```
+
+#### 4. cls+rec
+
+```bash linenums="1"
+./build/ppocr --rec_model_dir=inference/rec_rcnn \
+ --cls_model_dir=inference/cls \
+ --image_dir=../../doc/imgs_words/ch/word_1.jpg \
+ --use_angle_cls=true \
+ --det=false \
+ --rec=true \
+ --cls=true \
+```
+
+#### 5. rec
+
+```bash linenums="1"
+./build/ppocr --rec_model_dir=inference/rec_rcnn \
+ --image_dir=../../doc/imgs_words/ch/word_1.jpg \
+ --use_angle_cls=false \
+ --det=false \
+ --rec=true \
+ --cls=false \
+```
+
+#### 6. cls
+
+```bash linenums="1"
+./build/ppocr --cls_model_dir=inference/cls \
+ --cls_model_dir=inference/cls \
+ --image_dir=../../doc/imgs_words/ch/word_1.jpg \
+ --use_angle_cls=true \
+ --det=false \
+ --rec=false \
+ --cls=true \
+```
+
+#### 7. layout+table
+
+```bash linenums="1"
+./build/ppocr --det_model_dir=inference/det_db \
+ --rec_model_dir=inference/rec_rcnn \
+ --table_model_dir=inference/table \
+ --image_dir=../../ppstructure/docs/table/table.jpg \
+ --layout_model_dir=inference/layout \
+ --type=structure \
+ --table=true \
+ --layout=true
+```
+
+#### 8. layout
+
+```bash linenums="1"
+./build/ppocr --layout_model_dir=inference/layout \
+ --image_dir=../../ppstructure/docs/table/1.png \
+ --type=structure \
+ --table=false \
+ --layout=true \
+ --det=false \
+ --rec=false
+```
+
+#### 9. table
+
+```bash linenums="1"
+./build/ppocr --det_model_dir=inference/det_db \
+ --rec_model_dir=inference/rec_rcnn \
+ --table_model_dir=inference/table \
+ --image_dir=../../ppstructure/docs/table/table.jpg \
+ --type=structure \
+ --table=true
+```
+
+More parameters are as follows,
+
+Common parameters
+
+|parameter|data type|default|meaning|
+| --- | --- | --- | --- |
+|use_gpu|bool|false|Whether to use GPU|
+|gpu_id|int|0|GPU id when use_gpu is true|
+|gpu_mem|int|4000|GPU memory requested|
+|cpu_math_library_num_threads|int|10|Number of threads when using CPU inference. When machine cores is enough, the large the value, the faster the inference speed|
+|enable_mkldnn|bool|true|Whether to use mkdlnn library|
+|output|str|./output|Path where visualization results are saved|
+
+forward
+
+|parameter|data type|default|meaning|
+| :---: | :---: | :---: | :---: |
+|det|bool|true|Whether to perform text detection in the forward direction|
+|rec|bool|true|Whether to perform text recognition in the forward direction|
+|cls|bool|false|Whether to perform text direction classification in the forward direction|
+
+Detection related parameters
+
+|parameter|data type|default|meaning|
+| --- | --- | --- | --- |
+|det_model_dir|string|-|Address of detection inference model|
+|max_side_len|int|960|Limit the maximum image height and width to 960|
+|det_db_thresh|float|0.3|Used to filter the binarized image of DB prediction, setting 0.-0.3 has no obvious effect on the result|
+|det_db_box_thresh|float|0.5|DB post-processing filter box threshold, if there is a missing box detected, it can be reduced as appropriate|
+|det_db_unclip_ratio|float|1.6|Indicates the compactness of the text box, the smaller the value, the closer the text box to the text|
+|det_db_score_mode|string|slow| slow: use polygon box to calculate bbox score, fast: use rectangle box to calculate. Use rectangular box to calculate faster, and polygonal box more accurate for curved text area.|
+|visualize|bool|true|Whether to visualize the results,when it is set as true, the prediction results will be saved in the folder specified by the `output` field on an image with the same name as the input image.|
+
+Classifier related parameters
+
+|parameter|data type|default|meaning|
+| --- | --- | --- | --- |
+|use_angle_cls|bool|false|Whether to use the direction classifier|
+|cls_model_dir|string|-|Address of direction classifier inference model|
+|cls_thresh|float|0.9|Score threshold of the direction classifier|
+|cls_batch_num|int|1|batch size of classifier|
+
+Recognition related parameters
+
+|parameter|data type|default|meaning|
+| --- | --- | --- | --- |
+|rec_model_dir|string|-|Address of recognition inference model|
+|rec_char_dict_path|string|../../ppocr/utils/ppocr_keys_v1.txt|dictionary file|
+|rec_batch_num|int|6|batch size of recognition|
+|rec_img_h|int|48|image height of recognition|
+|rec_img_w|int|320|image width of recognition|
+
+Layout related parameters
+
+|parameter|data type|default|meaning|
+| :---: | :---: | :---: | :---: |
+|layout_model_dir|string|-| Address of layout inference model|
+|layout_dict_path|string|../../ppocr/utils/dict/layout_dict/layout_publaynet_dict.txt|dictionary file|
+|layout_score_threshold|float|0.5|Threshold of score.|
+|layout_nms_threshold|float|0.5|Threshold of nms.|
+
+Table recognition related parameters
+
+|parameter|data type|default|meaning|
+| :---: | :---: | :---: | :---: |
+|table_model_dir|string|-|Address of table recognition inference model|
+|table_char_dict_path|string|../../ppocr/utils/dict/table_structure_dict.txt|dictionary file|
+|table_max_len|int|488|The size of the long side of the input image of the table recognition model, the final input image size of the network is(table_max_len,table_max_len)|
+|merge_no_span_structure|bool|true|Whether to merge