当前位置:   article > 正文

Android实现OCR文字识别并且转换为Excel、PDF格式输出_开发apk扫描提取文字并生成excel

开发apk扫描提取文字并生成excel

由于最近有一个项目有需要用到拍照识别字体,且将识别后的字体转换为对应的Excel,Word,PDF格式的文本输出,所以特地去网上搜集了很多资料来制作,但是网上的资料很少有写到如何在手机端实现OCR技术,即使有实现的人,也不会给出一个很全面的源码,所以小弟特地花了点时间去实现该技术,测试可用后记录下步骤,以备以后查看。

后面我会将我的Demo链接也给出,供大家下载互相交流

前期准备

因为使用的是文字识别技术,所以我这里使用的是Tesseract的OCR引擎,这款相对于来说识别率还行。

tesseract是用c++实现的,需要封装Java API用于Android平台的调用,所以我们使用的是Tesseract Tools for Android的一个git分支 tesseract-two,它集成了图形处理工具leptonica,使用起来很方便,但是网上的都是比较麻烦,需要自己去下载下来编译,才能在Android上实现,小弟使用的是一个已经编译好的,全部内容可以直接使用,无需再引用其他什么tess-two项目作为lib。

  • 文字识别使用tess-two.tesseract3.01-leptonica1.68-LibJPEG6b.jar
  • 两个armeabi包
  • 中文字库使用chi_sim.traineddata
  • 英文字库使用eng.traineddata

而至于将扫描出来的文字转换部分,小弟只实现了Excel 及 PDF 的转换,对于Word的转换失败了,一部分原因是因为Word的包和Excel,PDF出现了冲突,一部分是因为IText包对Android的支持还不是很全,Android中有一些java的API的使用还不支持,会出错。

  • Excel使用jxl.jar
  • PDF使用iTextpdf.jar

其中iTextpdf包是某位大神重新对之前的iText进行了重新打包,使之能够支持在Android上使用中文的PDF,小弟就可耻的借来使用了

好了,下面开始进行实现。

代码实现

使用Tesseract必须要在手机SD卡中建立tesseract/tessdata/ 并且将你的识别字库放入该文件夹下,否则你运行就会出错,很多朋友都推荐使用 adb 命令在手机中进行新建文件夹并推送字库进去,但这操作明显不符合一款成品软件的操作,难道要每个用户都在下载软件的时候 都回家使用adb命令推送一次吗?

所以我们手动写一段代码,在启动软件时,自动创建路径并推送字库进去

初始化OCR环境

先在我们的项目res中建立raw文件夹,并将字库chi_sim.traineddata放进raw中等待稍后启动时推送

资源文件


将三个jar包下载下来,以及两个armeabi的so文件下载好后放入libs文件夹中
第三方包


操作完这几步后,我们要考虑几点
- SD卡的读写权限android.permission.WRITE_EXTERNAL_STORAGE
- SD卡中创建删除文件夹的权限android.permission.MOUNT_UNMOUNT_FILESYSTEMS
- 我们拍照识别文字需要的拍照权限android.permission.CAMERA

所以我们需要在清单文件中注册以下权限
权限注册

弄好这几步后,我们开始在Activity中实现推送代码吧,我们原理很简单,先判断有没有SD卡,有就再进行判断SD卡中是否有该字库,没有就进行推送。

判断是否有SD卡

    /**
     * 判断手机是否有SD卡。
     * 
     * @return 有SD卡返回true,没有返回false。
     */
    public static boolean hasSDCard() {
        return Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

将资源文件raw中的资源推送到目标位置

/**
     * 将资源转换成文件
     * 
     * @param context
     *            上下文
     * 
     * @param ResourcesId
     *            资源Id
     * 
     * @param filePath
     *            目标文件地址
     * 
     * @param fileName
     *            目标文件名
     * 
     * @return 是否成功
     */
    public static boolean ResourceToFile(Context context, int ResourceId,
            String filePath, String fileName) {
        InputStream is = null;
        FileOutputStream fs = null;
        try {
            if (!FileUtils.CreateFolder(filePath))
                return false;

            is = context.getResources().openRawResource(ResourceId);
            File file = new File(filePath + File.separator + fileName);
            fs = new FileOutputStream(file);
            while (is.available() > 0) {
                byte[] b = new byte[is.available()];
                is.read(b);
                fs.write(b);
            }

            return true;
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            return false;
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (Exception e) {
                Log.e(TAG, e.toString());
            }

            try {
                if (fs != null)
                    fs.close();
            } catch (Exception e) {
                Log.e(TAG, e.toString());
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

判断是否有该文件

    /**
     * 判断文件是否存在
     * 
     * @param fileName
     *            文件名,必须为文件完整路径
     * 
     * @return 文件存在返回true,文件不存在返回false
     */
    public static boolean IsFileExists(String fileName) {
        try {
            File file = new File(fileName);
            return file.exists();
        } catch (Exception e) {
            TrackerUtils.e(e);
            return false;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我们先定义好几个固定常量,然后在Activity执行onCreate()的时候进行操作

    private static final String TESSBASE_PATH = "/mnt/sdcard/tesseract/";
    private static final String DEFAULT_LANGUAGE = "chi_sim";
    private static final String IMAGE_PATH = "/mnt/sdcard/ccc.jpg";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findView();
        intercept();

        String path = TESSBASE_PATH;

        // 判断是否有SD卡
        if(FileUtils.hasSDCard()){
            // 判断字库是否存在
            if (!FileUtils.IsFileExists(path + DEFAULT_LANGUAGE))
                //推送字库到SD卡
                ResourceToFile(this, R.raw.chi_sim, path + "tessdata/",
                        DEFAULT_LANGUAGE +".traineddata");
        }


    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

OCR扫描解析代码实现

做好前期工作后,我们开始Ocr代码使用部分的编写

public void testOcr() {
        mHandler.post(new Runnable() {

            @Override
            public void run() {
                ocr();
            }
        });

    }

    protected void ocr() {

        //获取图片的显示方向,判断图片是否需要调整
        try {
            ExifInterface exif = new ExifInterface(IMAGE_PATH);

            int exifOrientation = exif.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL);

            int rotate = 0;
            switch (exifOrientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                rotate = 90;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                rotate = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                rotate = 270;
                break;
            }

            Log.v(TAG, "Rotation: " + rotate);

            int w = mBitmap.getWidth();
            int h = mBitmap.getHeight();
            Matrix mtx = new Matrix();
            mtx.preRotate(rotate);

            mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, w, h, mtx, false);
            mBitmap = mBitmap.copy(Bitmap.Config.ARGB_8888, true);

        } catch (IOException e) {
            Log.e(TAG, "Rotate or coversion failed: " + e.toString());
            Log.v(TAG, "in the exception");
        }
        //为了获得更好的解析效果,将图片二值化
        Bitmap data = handleBlackWitheBitmap(mBitmap);
        ImageView iv = (ImageView) findViewById(R.id.image);
        iv.setImageBitmap(data);
        iv.setVisibility(View.VISIBLE);

        //实例化TessBaseAPI
        TessBaseAPI baseApi = new TessBaseAPI();
        baseApi.setDebug(true);

        //初始化TessBaseAPI
        baseApi.init(TESSBASE_PATH, DEFAULT_LANGUAGE);

        //设置需要解析的图片
        baseApi.setImage(data);
        //解析图片
        recognizedText = baseApi.getUTF8Text();

        //解析完后清除内容
        baseApi.clear();
        //结束TessBaseAPI
        baseApi.end();

        if (!StringUtils.IsNullOrEmpty(recognizedText)) {
            ((TextView) findViewById(R.id.field))
                    .setText(recognizedText.trim());
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

从代码上可以看出来,其实tesseract的使用很简单,就几行代码而已,但是识别率不是很高,所以我在图片操作上增加了一段二值化处理,为了使他更容易被解析,如果你们不需要,可以注释掉


转出到Excel

OCR解析完后,我们开始操作下如何转换成对应的输出格式,先放出转换成Excel的方法

Excel的保存和读取,我使用的是jxl包,研究了一会后稍微写了个方法,简单的实现了保存和读取,但是在我写的Demo中没有引用 读取的方法,只使用了保存的方法 ,且保存的方法也是用固定值进行保存的,各位要是有使用请自行修改

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

import jxl.Range;
import jxl.Workbook;
import jxl.read.biff.BiffException;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import jxl.write.biff.RowsExceededException;
import android.os.Environment;
import android.util.Log;
/**
 *
 * Excel工具类
 *
 */
public class ExcelUtils {

    private static String TAG = ExcelUtils.class.getSimpleName();

    /**
     * 保存数据到Excel
     * @param tableName => 文件名,无需后缀
     */
    public static boolean saveToExcel(String content, String fileName) {
        WritableWorkbook wwb = null;
        boolean result = false;

        fileName = Environment.getExternalStorageDirectory() + File.separator
                + fileName + ".xls";

        Log.i(TAG, fileName);
        // 假设数据 五行五列
        int numrows = 5;
        int numcols = 5;
        String[][] records = {
                { "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan",
                        "lin.wufan" },
                { "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan",
                        "lin.wufan" },
                { "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan",
                        "lin.wufan" },
                { "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan",
                        "lin.wufan" },
                { "lin.yifan", "lin.erfan", "lin.sanfan", "lin.sifan",
                        "lin.wufan" } };

        Log.i(TAG, "开始保存到Excel");
        try {
            // 1 使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象
            wwb = Workbook.createWorkbook(new File(fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (wwb != null) {
            // 创建一个可写入的工作表
            // 2 创建工作表 Workbook的createSheet方法有两个参数,第一个是工作表的名称,第二个是工作表在工作薄中的位置
            WritableSheet ws = wwb.createSheet("sheet1", 0);

            // 3 添加单元格
            for (int i = 0; i < numrows; i++) {
                for (int j = 0; j < numcols; j++) {
                    // 这里需要注意的是,在Excel中,第一个参数表示列,第二个表示行
                    Label labelC = new Label(j, i, records[i][j]);
                    try {
                        // 将生成的单元格添加到工作表中
                        ws.addCell(labelC);
                    } catch (RowsExceededException e) {
                        e.printStackTrace();
                    } catch (WriteException e) {
                        e.printStackTrace();
                    }

                }
            }

            try {
                // 从内存中写入文件中
                wwb.write();
                // 关闭资源,释放内存
                wwb.close();
                result = true;
                Log.i(TAG, "保存到Excel成功");
            } catch (IOException e) {
                e.printStackTrace();
            } catch (WriteException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 读取数据
     * @param fileName => 文件名,无需后缀
     * @return
     */
    public static ArrayList<ArrayList<String>> getExcel(String fileName) {
        ArrayList<String> innerAList = null;
        ArrayList<ArrayList<String>> outerAlist = new ArrayList<ArrayList<String>>();

        fileName = Environment.getExternalStorageDirectory() + File.separator
                + fileName + ".xls";

        Log.i(TAG, fileName);

        if (!FileUtils.IsFileExists(fileName)) {
            Log.e(TAG, "the file is not exists! return");
            return outerAlist;
        }

        File file = new File(fileName);

        Workbook wb = null;

        try {
            // 1 创建一个工作簿对象wb,该对象的引用指向某个待读取的Excel文件
            wb = Workbook.getWorkbook(file);

            // 2 创建一个工作簿wb中的工作表对象
            jxl.Sheet sheet = wb.getSheet(0);
            int stRows = sheet.getRows(); // 得到当前工作表中所有非空行号的数目
            int stColumns = sheet.getColumns();// 得到当前工作表中所有非空列号的数目
            Range[] range = sheet.getMergedCells(); // 得到所有合并的单元格

            for (int i = 0; i < stRows; i++) {
                innerAList = new ArrayList<String>();
                for (int j = 0; j < stColumns; j++) {
                    // 3 创建一个单元格对象,来存放从sheet的(第j列,第i行)读取的单元格;
                    jxl.Cell cell = sheet.getCell(j, i);

                    if (range.length > 0) {
                        for (int z = 0; z < range.length; z++) {
                            int lr = range[z].getTopLeft().getRow();// 左上角单元格行号
                            int lc = range[z].getTopLeft().getColumn();// 左上角单元格列号
                            int rr = range[z].getBottomRight().getRow();// 右下角单元格行号
                            int rc = range[z].getBottomRight().getColumn();// 右下角单元格列号

                            // 判断是否是合并的单元格
                            if (i >= lr && i <= rr && j <= rc && j >= lc) {
                                if (i == lr && j == lc) {
                                    innerAList.add(cell.getContents());
                                } else {
                                    innerAList.add("><><");
                                }

                            } else {
                                // 4 从当前单元格中获得一个已经转换为字符串类型的字符串,详见API
                                innerAList.add(cell.getContents());
                            }
                        }
                    } else {
                        innerAList.add(cell.getContents());
                    }

                }
                outerAlist.add(innerAList);
            }

        } catch (FileNotFoundException fnf) {
            fnf.printStackTrace();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (BiffException be) {
            be.printStackTrace();
        } finally {
            wb.close();
        }

        return outerAlist;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177

转出到PDF

PDF的操作我也单独写了一个操作工具类,使用的是itextpdf包,由于是高手修改过的,所以可以使用中文,不会那么麻烦,但是麻烦的是,导致我无法对Word进行操作,不知道有没有好心的高手,帮忙封装一个支持 pdf 和 word 的 中文 Android的 itext包

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import android.os.Environment;
import android.util.Log;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.parser.PdfTextExtractor;
/**
 *
 * Pdf工具类
 *
 */
public class PdfUtils {

    private static String TAG = PdfUtils.class.getSimpleName();

    public static String getPDF(String fileName) {

        StringBuffer str = new StringBuffer();

        // 获取需要解析的文件路径
        fileName = Environment.getExternalStorageDirectory()
                + File.separator + fileName + ".pdf";

        if(!FileUtils.IsFileExists(fileName)){
            Log.e(TAG, fileName + "the file is not exists! return");
            return null;
        }

        try {
            // 使用PdfReader打开pdf文件
            PdfReader reader = new PdfReader(fileName);// 模板
            // 获取总共页数
            int numberOfPages = reader.getNumberOfPages();

            // 循环获取每页内容
            for (int i = 0; i < numberOfPages; i++) {
//              str.append(new String(reader.getPageContent(i + 1), "UTF-8"));
                str.append(PdfTextExtractor.getTextFromPage(reader, i + 1));
            }
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } 
        return str.toString();
    }

    /**
     * 转换为PDF格式
     * 
     * @param content => 内容
     * @param fileName => 文件名,无需后缀
     */
    public static boolean transformationToPDF(String content, String fileName) {
        fileName = Environment.getExternalStorageDirectory() + File.separator
                + fileName + ".pdf";
        boolean result = false;
        // 1.创建一个document
        Document doc = new Document();
        FileOutputStream fos;
        try {
            Log.i(TAG, "开始生成PDF文件!");

            fos = new FileOutputStream(new File(fileName));
            Log.i(TAG, "路径:" + fileName);
            // 2.定义pdfWriter,指明文件输出流输出到一个文件
            PdfWriter.getInstance(doc, fos);
            // 3.打开文档
            doc.open();
            doc.setPageCount(1);

            // 字体 注意,如果是中文,则必须选择字体
            Font font = setChineseFont();

            // 4.添加内容
            Paragraph paragraph = new Paragraph(content, font);

            doc.add(paragraph);

            // 添加段落
//          for (int i = 0; i < 100; i++) {
//              doc.add(new Paragraph("HelloWorld" + "," + "Hello iText" + ","
//                      + "HelloxDuan"));
//          }

            // 5.关闭
            doc.close();
            fos.flush();
            fos.close();
            Log.i(TAG, "PDF文件生成成功!");
            result = true;
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     *  产生PDF字体
     * @return
     */
    public static Font setChineseFont() {
        BaseFont bf = null;
        Font fontChinese = null;
        try {
            bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",
                    BaseFont.NOT_EMBEDDED);
            fontChinese = new Font(bf, 12, Font.NORMAL);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fontChinese;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

关键代码都给出来给大家参考了,可惜我使用的是官方给的中文字库,识别率实在不怎么样,但是,使用方法已经给出了,关于提升识别率的方法,过两天我会专门写一篇关于如何正确训练字库的方法,这次的内容如有错误的地方还请指出

下面给出小弟制作的Demo链接
Demo下载地址http://download.csdn.net/download/u014371093/8729833

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/319267?site
推荐阅读
相关标签
  

闽ICP备14008679号