1. 程式人生 > >Qt顯示pdf系列4——封裝pdfium庫為動態庫,顯示pdf

Qt顯示pdf系列4——封裝pdfium庫為動態庫,顯示pdf

 承接上篇,pdfium的lib檔案是已經編譯出來了,理論上已經可以開始直接用了,官方提供的測試demo中基本上介紹了用法的整套流程,你可以選擇匯出一頁頁的(圖片)檔案,也可以直接取出Buffer丟給支援圖形庫去渲染。

但是需要注意的是,他在實際使用中依舊有很多不便:
1、我們能夠編譯出來的只有vs2015或以上版本的lib,如果我們需要在別的ide中引用,那麼就可能不行。
2、編譯出來的lib一共24個,所以確定要在專案中,光pdf庫就引用這麼多個嗎= =。

解決方式:基於這些lib的基礎上,再包一層,把他編譯成動態庫,介面用純C語言,不僅簡潔,而且理論上說是跨平臺的。

 關於動態庫,只簡單說幾句,不瞭解可以自己查:靜態連結庫(.lib)cpp裡所有程式碼被被編譯成2進位制檔案,使用時直接會連線到你的專案中,而動態連結庫,裡面是你專案的程式碼,也不會編譯進你的程式中。

開發環境:windows7+vs2015

一、新建專案:

 開啟vs2015,新建一個c++的win32專案,當然,你也可以直接新建成空專案,然後在設定裡手動修改輸出方式為dll。這裡我的專案叫pdf
這裡寫圖片描述
這裡寫圖片描述

二、封裝pdfium庫:

1、把上篇編譯出來的pdfium庫的lib和標頭檔案整理好,並新增到剛才新建的專案中去:
這裡寫圖片描述
這裡寫圖片描述
2、在專案中新建PdfManager.h,PdfManager.cpp。開始寫程式碼:
PdfManager.h:

#ifndef _PDF_MANAGER_H_
#define _PDF_MANAGER_H_
//filename是檔名,需要唯一,dat是開啟的pdf的內容(可以用fstream開啟,以二進位制形式),length是pdf長度
extern "C" _declspec(dllexport) bool __stdcall PDFMANAGER_Loadpdf(const char* filename, char* dat, int length); //載入pdf(即載入已經開啟的pdf) //filename是檔名,關閉該pdf extern "C" _declspec(dllexport) bool __stdcall PDFMANAGER_Closepdf(const char* filename); //關閉pdf //filename是檔名,關閉該pdf,page是需要顯示頁數,width是寬,height是高,size是當前頁的大小,outBmp是是否匯出bmp檔案,返回該頁buffer
extern "C" _declspec(dllexport) char* __stdcall PDFMANAGER_LoadPage(const char* filename, int page, float& width, float& height, int& size, bool OutBmp);//載入頁面 //filename是檔名,關閉該頁面,開啟後需要關閉,否則會記憶體洩漏 extern "C" _declspec(dllexport) void __stdcall PDFMANAGER_ClosePage(const char* filename, int page); //關閉頁面 //獲取總頁數 extern "C" _declspec(dllexport) int __stdcall PDFMANAGER_GetPageCount(const char* filename); //獲取下一頁該渲染的頁 extern "C" _declspec(dllexport) int __stdcall PDFMANAGER_GetCurrentPage(const char* filename); #endif

PdfManager.cpp:

#include "PdfManager.h"

#include "fpdfview.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "fpdf_dataavail.h"
#include "fpdf_edit.h"
#include "fpdf_ext.h"
#include "fpdf_formfill.h"
#include "fpdf_text.h"

#include <functional>
#include <fstream>



enum OutputFormat
{
    OUTPUT_STR,
    OUTPUT_BMP,
};

struct Options
{
    Options() :pages(false), output_format(OUTPUT_BMP) {}
    bool pages; //是否指定範圍
    OutputFormat output_format;
    int first_page = 0; //起始頁數
    int last_page = 0; //終止頁數
    int currentpage = 0; //要列印的頁面
    float width = 0; //目標寬度
    float height = 0; //目標高度
};


FPDF_BOOL Is_Data_Avail(FX_FILEAVAIL* avail, size_t offset, size_t size)
{
    return true;
}

class PDFManager
{
    static std::string WriteBmp(int num,
        const void* buffer,
        int stride,
        int width,
        int height)
    {
        if (stride < 0 || width < 0 || height < 0)
            return false;
        if (height > 0 && width > INT_MAX / height)
            return false;

        int out_len = stride * height;
        if (out_len > INT_MAX / 3)
            return "";

        char filename[256];
        snprintf(filename, sizeof(filename), "%d.bmp", num);
        FILE* fp = fopen(filename, "wb");
        if (!fp)
            return "";

        BITMAPINFO bmi = {};
        bmi.bmiHeader.biSize = sizeof(bmi) - sizeof(RGBQUAD);
        bmi.bmiHeader.biWidth = width;
        bmi.bmiHeader.biHeight = -height;  // top-down image
        bmi.bmiHeader.biPlanes = 1;
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biCompression = BI_RGB;
        bmi.bmiHeader.biSizeImage = 0;

        BITMAPFILEHEADER file_header = {};
        file_header.bfType = 0x4d42;
        file_header.bfSize = sizeof(file_header) + bmi.bmiHeader.biSize + out_len;
        file_header.bfOffBits = file_header.bfSize - out_len;

        fwrite(&file_header, sizeof(file_header), 1, fp);
        fwrite(&bmi, bmi.bmiHeader.biSize, 1, fp);
        fwrite(buffer, out_len, 1, fp);
        fclose(fp);
        return std::string(filename);
    }


    struct FPDF_FORMFILLINFO_PDFiumTest : public FPDF_FORMFILLINFO
    {
        // Hold a map of the currently loaded pages in order to avoid them
        // to get loaded twice.
        std::map<int, FPDF_PAGE> loaded_pages;
        // Hold a pointer of FPDF_FORMHANDLE so that PDFium app hooks can
        // make use of it.
        FPDF_FORMHANDLE form_handle;
    };

    struct AvailDeleter
    {
        inline void operator()(FPDF_AVAIL avail) const
        {
            FPDFAvail_Destroy(avail);
        }
    };

    static FPDF_FORMFILLINFO_PDFiumTest* ToPDFiumTestFormFillInfo(FPDF_FORMFILLINFO* form_fill_info)
    {
        return static_cast<FPDF_FORMFILLINFO_PDFiumTest*>(form_fill_info);
    }

    FPDF_PAGE GetPageForIndex(FPDF_FORMFILLINFO* param,
        FPDF_DOCUMENT& doc,
        int index)
    {
        FPDF_FORMFILLINFO_PDFiumTest* form_fill_info =
            ToPDFiumTestFormFillInfo(param);
        auto& loaded_pages = form_fill_info->loaded_pages;

        auto iter = loaded_pages.find(index);
        if (iter != loaded_pages.end())
            return iter->second;
        FPDF_PAGE page = FPDF_LoadPage(doc, index);
        if (!page)
            return nullptr;

        FPDF_FORMHANDLE& form_handle = form_fill_info->form_handle;

        FORM_OnAfterLoadPage(page, form_handle);
        FORM_DoPageAAction(page, form_handle, FPDFPAGE_AACTION_OPEN);

        loaded_pages[index] = page;
        return page;
    }

    char* RenderPage(FPDF_DOCUMENT& doc,
        FPDF_FORMFILLINFO_PDFiumTest& form_fill_info
        , float& width, float& height,
        const Options& options,
        int& size)
    {
        FPDF_PAGE page = GetPageForIndex(&form_fill_info, doc, options.currentpage);
        if (!page)
        {
            return nullptr;
        }
        FPDF_TEXTPAGE text_page = FPDFText_LoadPage(page);
        double scale = 1.0;
        int widthdraw = static_cast<int>(FPDF_GetPageWidth(page) * scale);
        int heightdraw = static_cast<int>(FPDF_GetPageHeight(page) * scale);
        if (options.width > 0 && options.height > 0)
        {
            if (options.width > options.height)
            {
                double scalewidth = (double)options.height / (double)heightdraw;
                heightdraw = options.height;
                widthdraw = scalewidth * widthdraw;
            }
            else
            {
                double scaleheight = (double)options.width / (double)widthdraw;
                widthdraw = options.width;
                heightdraw = scaleheight * heightdraw;
            }
        }
        //else
        //{
        //  widthdraw = static_cast<int>(FPDF_GetPageWidth(page) * scale);
        //  heightdraw = static_cast<int>(FPDF_GetPageHeight(page) * scale);
        //}

        int alpha = FPDFPage_HasTransparency(page) ? 1 : 0;
        FPDF_BITMAP bitmap = FPDFBitmap_Create(widthdraw, heightdraw, alpha);
        const char* buffer = nullptr;
        char* rtnBuffer = nullptr;
        int out_len = 0;
        if (bitmap)
        {
            FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF;
            FPDFBitmap_FillRect(bitmap, 0, 0, widthdraw, heightdraw, fill_color);
            FPDF_RenderPageBitmap(bitmap, page, 0, 0, widthdraw, heightdraw, 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER);
            //FPDF_FFLDraw(form, bitmap, page, 0, 0, width, height, 0, FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER);
            int stride = FPDFBitmap_GetStride(bitmap);
            int strheight = FPDFBitmap_GetHeight(bitmap);
            buffer = reinterpret_cast<const char*>(FPDFBitmap_GetBuffer(bitmap));
            //開闢新記憶體
            size = stride * strheight;
            rtnBuffer = new char[size];
            memset(rtnBuffer, 0, size);
            memcpy(rtnBuffer, buffer, size);

            width = FPDFBitmap_GetWidth(bitmap);
            height = FPDFBitmap_GetHeight(bitmap);
            Pages_.insert(std::make_pair(options.currentpage, rtnBuffer));
            switch (options.output_format)
            {
            case OUTPUT_BMP:
                WriteBmp(options.currentpage, buffer, stride, widthdraw, heightdraw);
                break;
            default:
                break;
            }
            FPDFBitmap_Destroy(bitmap);
        }
        /*else
            fprintf(stderr, "Page was too large to be rendered.\n");*/
        form_fill_info.loaded_pages.erase(options.currentpage);
        /*FORM_DoPageAAction(page, form, FPDFPAGE_AACTION_CLOSE);
        FORM_OnBeforeClosePage(page, form);*/
        FPDFText_ClosePage(text_page);
        FPDF_ClosePage(page);
        return rtnBuffer;
    }

    class TestLoader
    {
    public:
        TestLoader(const char* buff, size_t len)
        {
            m_pBuf = new char[len];
            memcpy(m_pBuf, buff, len);
            m_Len = len;
        }
        ~TestLoader()
        {
            delete[]m_pBuf;
        }

        static int GetBlock(void* param,
            unsigned long pos,
            unsigned char* pBuf,
            unsigned long size)
        {
            TestLoader* pLoader = static_cast<TestLoader*>(param);
            if (pos + size < pos || pos + size > pLoader->m_Len)
                return 0;
            memcpy(pBuf, pLoader->m_pBuf + pos, size);
            return 1;
        }
    private:
        char* m_pBuf;
        size_t m_Len;
    };

    void RenderPdf(const char* pBuf, size_t len, const Options& options)
    {
        memset(&platform_callbacks_, '\0', sizeof(platform_callbacks_));
        platform_callbacks_.version = 3;

        form_callbacks_.version = 1;
        form_callbacks_.m_pJsPlatform = &platform_callbacks_;

        Loader_ = std::move(std::unique_ptr<TestLoader>(new TestLoader(pBuf, len)));
        File_access_ = std::make_unique<FPDF_FILEACCESS>();
        memset(File_access_.get(), '\0', sizeof(File_access_.get()));
        File_access_->m_FileLen = static_cast<unsigned long>(len);
        File_access_->m_GetBlock = TestLoader::GetBlock;
        File_access_->m_Param = Loader_.get();

        memset(&File_avail_, '\0', sizeof(File_avail_));
        File_avail_.version = 1;
        File_avail_.IsDataAvail = Is_Data_Avail;

        memset(&Hints_, '\0', sizeof(Hints_));
        Hints_.version = 1;

        int nRet = PDF_DATA_NOTAVAIL;

        Pdf_avail_ = FPDFAvail_Create(&File_avail_, File_access_.get());
        std::unique_ptr<void, PDFManager::AvailDeleter> scoped_pdf_avail_deleter(Pdf_avail_);

        if (FPDFAvail_IsLinearized(Pdf_avail_) == PDF_LINEARIZED)
        {
            this->PdfDoc_ = FPDFAvail_GetDocument(Pdf_avail_, nullptr);
            if (this->PdfDoc_)
            {
                while (nRet == PDF_DATA_NOTAVAIL)
                    nRet = FPDFAvail_IsDocAvail(Pdf_avail_, &Hints_);
                if (nRet == PDF_DATA_ERROR)
                {
                    fprintf(stderr, "Unknown error in checking if doc was available.\n");
                    FPDF_CloseDocument(this->PdfDoc_);
                    return;
                }
                nRet = FPDFAvail_IsFormAvail(Pdf_avail_, &Hints_);
                if (nRet == PDF_FORM_ERROR || nRet == PDF_FORM_NOTAVAIL)
                {
                    fprintf(stderr,
                        "Error %d was returned in checking if form was available.\n",
                        nRet);
                    FPDF_CloseDocument(this->PdfDoc_);
                    return;
                }
                this->bIsLinearized_ = true;
            }
        }
        else
        {
            this->PdfDoc_ = FPDF_LoadCustomDocument(File_access_.get(), nullptr);
        }
        if (!this->PdfDoc_)
        {
            unsigned long err = FPDF_GetLastError();
            return;
        }
        (void)FPDF_GetDocPermissions(this->PdfDoc_);
        PageCount = FPDF_GetPageCount(this->PdfDoc_);
    }

public:
    PDFManager();
    ~PDFManager();
    void Init(char *dat, int length);
    void Close();
    char* GetPdfPage(int page, float& width, float& height, int& size, bool OutBmp = false);//返回長度
    void ClosePdfPage(int page);
    int PageCount = 0;
    int PageCurrent = 0;
    int PageBad = 0;
private:
    FPDF_DOCUMENT PdfDoc_ = nullptr;//文件
    //FPDF_FORMHANDLE Form_ = nullptr;
    bool bIsLinearized_ = false; //是否線性化
    FPDF_FORMFILLINFO_PDFiumTest form_callbacks_;
    IPDF_JSPLATFORM platform_callbacks_;
    FX_DOWNLOADHINTS Hints_;//
    FX_FILEAVAIL File_avail_;
    std::unique_ptr<FPDF_FILEACCESS> File_access_;
    FPDF_AVAIL Pdf_avail_;
    std::unique_ptr<TestLoader> Loader_;
    char* File_contents_ = nullptr;
    int File_length_ = 0;
    std::map<int, char*> Pages_; //儲存頁面資料

};

PDFManager::PDFManager()
{

}

PDFManager::~PDFManager()
{
    for (auto &buffer : Pages_)
    {
        delete[]buffer.second;
    }
    Pages_.clear();
    this->Close();
}

void PDFManager::Init(char *dat, int length)
{
    File_contents_ = dat;
    File_length_ = length;
    Options options;
    options.output_format = OutputFormat::OUTPUT_BMP;

    if (!File_contents_)
        return;
    RenderPdf(File_contents_, File_length_, options);
}

char* PDFManager::GetPdfPage(int page, float& width, float& height, int& size, bool OutBmp)
{
    if (this->bIsLinearized_)
    {
        this->Close();
        return 0;
    }
    if (page < 0)
        return 0;
    Options options;
    options.currentpage = page;
    options.width = width;
    options.height = height;
    if (!OutBmp)
        options.output_format = OUTPUT_STR;
    PageCurrent = page;
    PageCurrent++;
    return RenderPage(this->PdfDoc_, form_callbacks_, width, height, options, size);
}

void PDFManager::ClosePdfPage(int page)
{
    std::map<int, char*>::iterator itor = Pages_.find(page);
    if (itor != Pages_.end())
    {
        delete[]itor->second;
        Pages_.erase(page);
    }
}

void PDFManager::Close()
{
    if (!this->PdfDoc_)
        return;
    FPDF_CloseDocument(this->PdfDoc_);
}



//介面函式
std::map<std::string, PDFManager*> g_pdfmanagersmap;
bool __stdcall PDFMANAGER_Loadpdf(const char* filename, char* dat, int length)
{
    if (!filename)
    {
        return false;
    }
    if (g_pdfmanagersmap.size() == 0)
    {
        FPDF_LIBRARY_CONFIG config;
        config.version = 2;
        config.m_pUserFontPaths = nullptr;
        config.m_pIsolate = nullptr;
        config.m_v8EmbedderSlot = 0;
        FPDF_InitLibraryWithConfig(&config);
    }
    std::string file = filename;
    std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file);
    if (itor != g_pdfmanagersmap.end())
    {
        itor->second->Init(dat, length);
    }
    else
    {
        PDFManager* pdfmanager = new PDFManager();
        g_pdfmanagersmap.insert(std::make_pair(file, pdfmanager));
        pdfmanager->Init(dat, length);
    }
    return true;
}

bool __stdcall PDFMANAGER_Closepdf(const char* filename)
{
    if (!filename)
    {
        return false;
    }
    std::string file = filename;
    std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file);
    if (itor != g_pdfmanagersmap.end())
    {
        delete itor->second;
        g_pdfmanagersmap.erase(file);
        if (g_pdfmanagersmap.size() == 0)
        {
            FPDF_DestroyLibrary();
        }
    }
    return true;
}

char* __stdcall PDFMANAGER_LoadPage(const char* filename, int page, float& width, float& height, int& size, bool OutBmp)
{
    if (!filename)
    {
        return nullptr;
    }
    std::string file = filename;
    std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file);
    if (itor != g_pdfmanagersmap.end())
    {
        return itor->second->GetPdfPage(page, width, height, size, OutBmp);
    }
    return nullptr;
}

void __stdcall PDFMANAGER_ClosePage(const char* filename, int page)
{
    if (!filename)
    {
        return;
    }
    std::string file = filename;
    std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file);
    if (itor != g_pdfmanagersmap.end())
    {
        itor->second->ClosePdfPage(page);
    }
}

int __stdcall PDFMANAGER_GetPageCount(const char* filename)
{
    if (!filename)
    {
        return 0;
    }
    std::string file = filename;
    std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file);
    if (itor != g_pdfmanagersmap.end())
    {
        return itor->second->PageCount;
    }
    return 0;
}

int __stdcall PDFMANAGER_GetCurrentPage(const char* filename)
{
    if (!filename)
    {
        return 0;
    }
    std::string file = filename;
    std::map<std::string, PDFManager*>::iterator itor = g_pdfmanagersmap.find(file);
    if (itor != g_pdfmanagersmap.end())
    {
        return itor->second->PageCurrent;
    }
    return 0;
}

註釋說的比較清楚,就不解釋了。

3、新增模組檔案:

這裡寫圖片描述
我取名為PdfManager.def,在裡面新增如下程式碼:

LIBRARY "Pdf"
EXPORTS
PDFMANAGER_Loadpdf @ 1
PDFMANAGER_Closepdf @ 2
PDFMANAGER_LoadPage @ 3
PDFMANAGER_ClosePage @ 4
PDFMANAGER_GetPageCount @ 5
PDFMANAGER_GetCurrentPage @ 6

即指定要編譯成dll的介面函式。

4、編譯,最後整理成pdf庫:
這裡寫圖片描述
這裡寫圖片描述
直接編譯release版本就可以了,通用。

三、使用封裝好的pdf動態庫:

1、在vs中使用:

1.1 先新建測試專案
1.2 新增pdf庫
1.2 新增程式碼:
main.cpp:

#include <iostream>
#include <fstream>
#include "PdfManager.h"
#include <memory>

#include <windows.h>
char* GetFileContents(const char* filename, size_t *size)
{
    std::fstream instream;
    instream.open(filename, std::ios::_Nocreate | std::ios::binary);
    if (!instream.is_open())
    {
        std::printf("open file Failed!\n");
        std::printf("%s\n", filename);
        instream.close();
        return nullptr;
    }
    instream.seekg(0, std::ios::end);
    *size = instream.tellg();
    instream.seekg(0, std::ios::beg);
    char *buffer = new char[*size];
    instream.read(buffer, *size);
    instream.close();
    return buffer;
}


int main()
{



    char* name = "test.pdf";
    size_t length;
    std::unique_ptr<char>  filecontents(GetFileContents(name, &length));
    PDFMANAGER_Loadpdf(name, filecontents.get(), length);
    int counts = PDFMANAGER_GetPageCount(name);
    for (int i = 0; i < counts; ++i)
    {

        if (GetAsyncKeyState(VK_F2) & 0x8000)
            break;
        float width, height = 0;
        int length = 0;
        PDFMANAGER_LoadPage(name, i, width, height, length, true);
        PDFMANAGER_ClosePage(name, i);
    }
    PDFMANAGER_Closepdf(name);
    system("pause");

    return 0;
}

結果如圖(數字.bmp是渲染出來的每一頁,我只渲染完三頁就關了):
這裡寫圖片描述

2、在qt中使用:

2.1新建qt專案
2.2 匯入pdf動態庫
2.3關鍵程式碼(ui邏輯程式碼就不貼了,其實就是主要講怎麼用,怎麼渲染)

PDFForm::PDFForm(const char *filename, QByteArray &bytearray, QWidget *parent) :
    QWidget(parent),
    ui(new Ui::PDFForm)
{
    ui->setupUi(this);
    FileLength_ = bytearray.length();
    File_ = new char[FileLength_];
    std::memcpy(File_, bytearray.toStdString().c_str(), FileLength_);
    FileName_ = filename;
    Picture_ = new PictureBox();
    Picture_->setMode(PictureBox::PB_MODE::FIX_SIZE_CENTRED );
    this->ui->verticalLayout_2->addWidget(Picture_);
}


void PDFForm::InitFile()
{
    if(HaveLoadPdf_)
        return;

    int length = (int)FileLength_;
    PDFMANAGER_Loadpdf(FileName_.c_str(), File_, length);
    HaveLoadPdf_ = true;
    PageCount_ = PDFMANAGER_GetPageCount(FileName_.c_str());
    this->LoadPage(0);
}

void PDFForm::LoadPage(int page)
{
    if(!HaveLoadPdf_)
        return;
    int length = 0;
    QRect rect = this->ui->verticalLayout_2->geometry();
    float width = rect.width();
    float height = rect.height();
    char* Buffer = PDFMANAGER_LoadPage(FileName_.c_str(),page, width, height, length, false);
    QImage image((uchar*)Buffer, width, height, QImage::Format_RGBA8888);
    this->Picture_->setImage(image);
    PDFMANAGER_ClosePage(FileName_.c_str(),page);//釋放頁面記憶體
    UpdateLabelPage();
}

效果如下圖:
這裡寫圖片描述

到這裡就完成了需要在qt中顯示或開啟pdf的需求,還做了適配,可以直接拉伸縮小,而且最主要的是,因為其本質是點陣圖,放大縮小時重新loadpage可以實現解析度也不斷放大縮小

四、結語:

1、本系列到此結束,其實還有些東西,比如在qt裡做顯示適配,例如上面的Picture_物件就是自定義的一個image類,在介面上顯示時方便居中,要寫的話還可以寫第五篇,但是我暫時沒空,就打住了,以後會不會補需要再看看。

2、本次研究說實話讓我提升很多,很多是思維和經驗上的改變,獨立能力稍微有點加強,希望能早日成為大牛。

17.05.19更新:
程式碼地址:
傳送門

17.07.27更新:
大家不用留郵箱了,我不是經常上這個帳號,下載連線也貼了,也不花積分,直接下載就好了(ps:我自己下了,也叫別人下了,檔案沒問題,下載下來有問題重新試試,csdn是經常抽風,網路不是很穩定)

17.08.09更新:
鑑於很多人問我要在QT中開啟的demo,現在補上(如果覺得不錯,不妨點個頂或者關注,你的鼓勵是對我最大的支援):
傳送門
另外 需要1積分,不知道為啥,現在csdn資源最少1積分,包括我以前的0分資源全部自動調整為1分,特此說明。