主頁 > 移動端開發 > Puppeteer從HTML資料創建PDF檔案掛起Windows10系統

Puppeteer從HTML資料創建PDF檔案掛起Windows10系統

2022-02-20 05:12:51 移動端開發

我創建了一個應用程式,通過從多個 Excel 作業簿中提取資料來處理學生的結果。問題是使用 Puppeteer 生成 PDF 檔案會使系統陷入回圈,直到它掛起系統。

實際上,我已經使用捆綁為 pdf-creator-node 的 PhantomJs 測驗了以下相同的代碼,并且能夠在 3 分鐘內輕松生成 150 個 PDF 檔案。我拋棄 PhantomJs 的唯一挑戰是 CSS 檔案中的所有樣式都沒有包括在內,即使我將它作為行內樣式插入到標題中,起訴 JS 的替換功能。另一個是 PhantomJs 不再處于積極開發中。我在網上搜索,發現只有 Puppeteer 是有效的解決方案,并且具有積極的開發和支持。

我嘗試在回圈中的 pdfCreator() 末尾使用 page.close(),在 pdfGenerator() 末尾使用 browser.close()。我做錯了什么?

下面是 server.js 和 PdfGenerator.js 檔案中的代碼,帶有錯誤示例,以及系統爬出掛起狀態后我的任務管理器的螢屏截圖。對于 HTML 生成,我使用 Mustache。我排除了 server.js 中的一些代碼行,因為總字符數超過 60k。

服務器.js


// [codes were removed here]


        if(getCode == 'compute-result') {
          // declare variable
          let setData = null;
          let setTitle = 'Results Computation...';
          let setArgs = getArgs;
          // dataFromFile = ReadFile(pathCodeTextFile);
          // setArgs = Number(dataFromFile);
          setCode = 'compute-result';
          let setView = [];
          let setNext = true;
          let countTerms = [];
          
          // if(getArg > 0) {

            // Final Result computation
            const getJson = ReadFile(pathJsonResults);
            // const getCtrl = ReadFile(pathJsonCtrl);
            const getResultObject = JSON.parse(getJson);
            getResult = getResultObject;
            const totalResults = getResult.firstTerm.length   getResult.secondTerm.length   getResult.thirdTerm.length;

            if(setView.length < 1 && getResult != null) {
              setData = 'PDFs for Students Results initiating...';
              setView.unshift('Reading saved data...');
              client.emit('query', {data: setData, title: setTitle, code: setCode, next: setNext, args: null, view: JSON.stringify(setView)});
            }

          Sleep(2000).then(() => {

            if(getResult != null) {          
              setData = 'Students Results will be ready in a moment';
              client.emit('query', {data: setData, title: setTitle, code: setCode, next: setNext, args: setArgs, view: JSON.stringify(setView)});
            }

            const wacthFiles = (file, className, termName, sessionName, completed, pdfList) => {
              try {
                if(typeof file == 'string' && !FileExists(pathJsonPdfList)) {

                  if(pdfList.length < 2){
                    setData = 'Saving PDFs to downladable files...';
                  }

                  if(className != null && termName != null && sessionName != null) {
                    setTitle = `${pdfList.length} Result PDF${pdfList.length > 1?'s':''}...`;
                    setView.unshift(file);
                    if(!countTerms.includes(termName)) {
                      countTerms.push(termName)
                    }

                    // setCode = -1000 - pdfList.length;
                    // console.log('PDF PROGRESS: ', `${pdfList.length} Result PDF${pdfList.length > 1?'s':''}... ${setCode}`);
                  
                    // when all PDFs are created
                    if(completed) {
                      setTitle = setTitle.replace('...', ' [completed]');
                      setData = 'Result Download button is Active. You may click it now.';
                      setView.unshift('=== PDF GENERATION COMPLETED ===');
                      setView.unshift(`A total of ${pdfList.length} students' Results were generated`);
                      WriteFile(pathJsonPdfList, JSON.stringify(pdfList));

                      // set donwload button active
                      setCode = Number(codeTextFilePdfCompleted);
                      setNext = false;
                      getResult = null;
                      let termString = countTerms.toString();
                      termString = ReplaceAll(termString, '-term', '');
                      termString = ReplaceAll(termString, ',', '-');
                      const addTxt = `${className} _${termString} Term${countTerms.length>1?'s':''} (${sessionName})`;
                      WriteFile(pathCodeTextFile, addTxt);
                      // console.log('======== PDF GENERATION ENDS ================');
                    } else {
                      setCode = -1 * pdfList.length;
                    }
                      client.emit('query', {data: setData, title: setTitle, code: setCode, next: setNext, args: setArgs, view: JSON.stringify(setView)});
                    }
                }
                
              } catch (error) {
                console.log('ERROR ON WATCHER: ', error);
              }
            }


            if(!FileExists(pathJsonPdfList) && getResult !== null) {
              PdfGenerator(getResult, wacthFiles);
            }

            // Watcher(pathWatchResults, setCode, wacthDir, 10000);
          });
          // }
        }


      }
    } catch (error) {
  })

  client.on('disconnect', () => {
    console.log('SERVER: Disconnected');
});



server.listen(portApi, () =>{
  console.log('Server listens on port 8881')
});

// serve static files
app.use(express.static(pathPublic));

// [codes were removed here]

PdfGenerator.js 問題在于這些函式:PdfGenerator & createPdf

'use strict';
process.setMaxListeners(Infinity) // fix for Puppeteer MaxListenerExceededWarning
const Puppeteer = require('puppeteer')
const {HtmlGenerator} = require('../components/HtmlGenerator')
const {WriteFile, FileExists, RandomNumber, RoundNumber, IsNumberFraction, ReadFile} = require('../components/Functions')


if (process.env.NODE_ENV !== 'production') {
    require('dotenv').config();
}

const pathFirstTermResults = process.env.DIR_FIRST_TERM_RESULTS;
const pathSecondTermResults = process.env.DIR_SECOND_TERM_RESULTS;
const pathThirdTermResults = process.env.DIR_THIRD_TERM_RESULTS;
const publicDir = process.env.DIR_PUBLIC;
const cssFile = process.env.PATH_CSS_FILENAME;
const pathCssRaw = __dirname   '\\'   publicDir   '\\'   cssFile;
const pathCss = pathCssRaw.replace(`\\uploads`, '');
const tagCssReplace = process.env.TAG_CSS_REPLACE;
let jsonDir = process.env.PATH_JSON;
jsonDir = jsonDir.split('/').pop();
let htmlDir = process.env.DIR_HTML;
htmlDir = __dirname   '\\'   htmlDir.split('/').pop();
const htmlType1 = htmlDir    '\\'   process.env.HTML_TYPE1;
const htmlType2 = htmlDir    '\\'   process.env.HTML_TYPE2;
const htmlType3 = htmlDir    '\\'   process.env.HTML_TYPE3;
const pathJsonPdfList = './'   jsonDir   '/'   process.env.JSON_PDF_LIST_FILENAME;
const pathJsonPdfContent = __dirname   '\\'   jsonDir   '\\'   process.env.JSON_PDF_CONTENT;

const firstTermDir = 'first-term';
const secondTermDir = 'second-term';
const thirdTermDir = 'third-term';

let cumulativeFirstTermTotalList = {};
let cumulativeSecondTermTotalList = {};

let firstTermOnce = true;
let secondTermOnce = true;
let thirdTermOnce = true;
let isActive = false;

const getPath = (p, f) => {
    let dir = pathFirstTermResults;
    switch (p) {
        case firstTermDir:
            dir = pathFirstTermResults;
            break;
        case secondTermDir:
            dir = pathSecondTermResults;
            break;
        case thirdTermDir:
            dir = pathThirdTermResults;
            break;
    
        default:
            break;
    }
    return dir   f
}

const resolution = {
    x: 1920,
    y: 1080
}

const args = [
    '--disable-gpu',
    `--window-size=${resolution.x},${resolution.y}`,
    '--no-sandbox',
]

const createPdf = (page, content, templateType, filename, className, term, sessionName, isProcessActive, pdfFileList, cb) => {
    
    let path, document, options;
    path = getPath(term, filename);

    if(path != null) {

        let options = {
            path: path,
            format: 'A4',
            printBackground: true,
            margin: {
                left: '0px',
                top: '0px',
                right: '0px',
                bottom: '0px'
            }
        }
        
        let templateData = '';
        switch (templateType) {
            case '1':
                templateData = ReadFile(htmlType1);
                break;
            case '2':
                templateData = ReadFile(htmlType2);
                break;
            case '3':
                templateData = ReadFile(htmlType3);
                break;
        
            default:
                templateData = ReadFile(htmlType1);
                break;
        }
        
        (async() => {
            const html = HtmlGenerator(content, templateData);

            if(html != undefined && html !== '' && html != null) {
            // create PDF file
            cb(filename, className, term, sessionName, isProcessActive, pdfFileList);

                // get style from .css & replace
                const css = ReadFile(pathCss);

                await page.setContent(html, { waitUntil: 'networkidle0'});
                await page.addStyleTag(css);
                await page.pdf(options);
                page.close();
            }
        })()
    }
}


const pdfGenerator = (json, cb) => {
    let data  = {};
    let pdfFileList = [];

    if(typeof json == 'string') {
        data = JSON.parse(json)
    } else {
        data = json;
    }

    try {        

    // declare defaults
    let filename = 'Student'   '.pdf';
    let termName = firstTermDir;
    const templateType = data.keys.templateType;
    const session = data.classInfo.Session;
    const sessionName = session.replace('/', '-');
    const students = data.students;
    const className = data.classInfo.Class_Name;
    const recordFirstTerm = data.firstTerm;
    const recordSecondTerm = data.secondTerm;
    const recordThirdTerm = data.thirdTerm;
    
    let pdfCreatedList = [];
    let isReset = false;

    let totalResultsExpected = Object.keys(recordFirstTerm).length   Object.keys(recordSecondTerm).length   Object.keys(recordThirdTerm).length;
    let totalResultsCount = 0;
    let jsonForPdf = {};
    let record = {};
    let sRecord, path, id, fName, lName;

    // get each student 
    let logEndOnce = true;
    let logBeforeOnce = true;
    logBeforeOnce && console.log('==============    ***     ================');
    logBeforeOnce && console.log('======== PDF GENERATION BEGINS ================');
    

    const computeResult = (page, setTerm, setRecord, setReset) => {
        const termName = setTerm;
        const record = setRecord;
        let isReset = setReset;

        logBeforeOnce && console.log(`====== ${termName} RESULTS BEGINS ======`);
            for(let elem of students){
                id = elem.id;
                fName = elem.firstName;
                lName = elem.lastName;
                filename = `${lName} ${fName} _${termName} ${sessionName}.pdf`;
                // sRecord = record.filter(function (entry) { return entry[id] !== undefined; });
                sRecord = record[id];
                path = getPath(termName, filename);
    
                // create pdf
                if(!FileExists(path) && !FileExists(pathJsonPdfList)){
                
                    // generate final JSON for the student
                    // isReset = (pdfCreatedList.includes(id))? false: true;
                    
                    jsonForPdf = finalJson(elem, sRecord, data, termName);
                    (pdfFileList.length < 1) && WriteFile(pathJsonPdfContent, JSON.stringify(jsonForPdf));
        
                    pdfFileList.push({
                      'term': termName,
                      'file': filename
                    });
                    totalResultsCount = pdfFileList.length;
                    const pdfDate = new Date();
                    console.log(`${filename} (${totalResultsCount}/${totalResultsExpected}) at ${pdfDate.getHours()}hr${pdfDate.getHours()>1?'s':''} - ${pdfDate.getMinutes()}min${pdfDate.getMinutes()>1?'s':''} - ${pdfDate.getSeconds()}sec${pdfDate.getSeconds()>1?'s':''}`);

                    isActive = (totalResultsExpected === totalResultsCount)? true: false;
                    logEndOnce = false;
                    // cb(filename, className, termName, sessionName, isActive, pdfFileList);
                    // WriteFile(path, null);
                    isReset = true;
                    createPdf(page, jsonForPdf, templateType, filename, className, termName, sessionName, isActive, pdfFileList, cb);
                }
            }


            logBeforeOnce && console.log(`====== ${termName} RESULTS ENDS ======`);
    }

    // get each student result for First Term
    const computeFirstTerm = (p) => {
        return new Promise((resolve) => {
            if(data.keys.firstTerm === '1') {
                termName = firstTermDir;
                record = recordFirstTerm;
                pdfCreatedList = [];
                isReset = false;

                computeResult(p, termName, record, isReset)
            }
            resolve()
        })
    }

    // get each student result for Second Term
    const computeSecondTerm = (p) => {
        return new Promise((resolve) => {
            if(data.keys.secondTerm === '1') {
                termName = secondTermDir;
                record = recordSecondTerm;
                pdfCreatedList = [];
                isReset = false;

                computeResult(p, termName, record, isReset)
            }
            resolve()
        })
    }

    // get each student result for Third Term
    const computeThirdTerm = (p) => {
        return new Promise((resolve) => {
            if(data.keys.thirdTerm === '1') {
                termName = thirdTermDir;
                record = recordThirdTerm;
                pdfCreatedList = [];
                isReset = false;

                computeResult(p, termName, record, isReset)
            }
            resolve()
        })
    }

    (async () => {
        browser = await Puppeteer.launch({
            headless: true,
            handleSIGINT: false,
            args: args,
        });

        const page = await browser.newPage();
    
        await page.setViewport({
            width: resolution.x,
            height: resolution.y,
        })

        await computeFirstTerm(page);
        await computeSecondTerm(page);
        await computeThirdTerm(page);
        browser.close()
    })()
    

    
    if(totalResultsExpected === totalResultsCount && totalResultsCount !== 0 && !logEndOnce) {
        logEndOnce = true;
        logBeforeOnce = false;
        console.log('======== PDF GENERATION ENDS ================');
    }



    } catch (error) {
        console.log('==== ERROR IN PDF GENERATION: ', error)
    }


}

module.exports = {
    PdfGenerator: pdfGenerator
}

錯誤

info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

lerna ERR! yarn run start stderr:

<--- Last few GCs --->

[9884:000002D68A73C6B0]  1665171 ms: Scavenge 44.1 (45.8) -> 43.2 (45.8) MB, 223.9 / 0.0 ms  (average mu = 0.956, current mu = 0.952) allocation failure
[9884:000002D68A73C6B0]  1684089 ms: Scavenge 44.1 (45.8) -> 43.3 (45.8) MB, 587.3 / 0.0 ms  (average mu = 0.956, current mu = 0.952) allocation failure
[9884:000002D68A73C6B0]  1749901 ms: Scavenge 44.2 (45.8) -> 43.3 (45.8) MB, 5099.0 / 0.0 ms  (average mu = 0.956, current mu = 0.952) allocation failure


<--- JS stacktrace --->

FATAL ERROR: Committing semi space failed. Allocation failed - JavaScript heap out of memory
 1: 00007FF6ED61013F
 2: 00007FF6ED59F396
 3: 00007FF6ED5A024D
 4: 00007FF6EDED19EE
 5: 00007FF6EDEBBECD
 6: 00007FF6EDD5F61C
 7: 00007FF6EDD6933F
 8: 00007FF6EDD5BF19
 9: 00007FF6EDD5A0D0
10: 00007FF6EDD7EA06
11: 00007FF6EDAB1CD5
12: 00007FF6EDF5F3E1
13: 00007FF6EDF602E9
14: 000002D68C4EF69E
error Command failed with exit code 134.

任務管理器的螢屏截圖,Chromium 運行超過 50 個的多個實體。

Puppeteer 從 HTML 資料創建 PDF 檔案掛起 Windows 10 系統

我很感激任何幫助。我希望這可以解決給我一個流暢的 PDF 生成。謝謝你。

uj5u.com熱心網友回復:

示例解決方案(限制并行瀏覽器)

我為您創建了一個PdfPrinter類,您可以將其集成到您的設定中。它允許您限制并行 pdf 生成作業的數量,并允許設定限制并為您管理打開/關閉瀏覽器。該類PdfPrinter也是高度耦合的,需要進行一些修改才能將其用作通用佇列。從邏輯上講,這可以修改為一般佇列。

您可以嘗試將其集成到您的代碼中。這是一個帶有簡化 pdf 的完整作業測驗示例(沒有從 excel 獲取實際資料的部分..)

據我了解您的代碼,您不需要傳遞page所有函式。首先創建您的html css然后使用pdfPrinter并讓它處理page創建 瀏覽器啟動..

(我喜歡撰寫這樣的代碼,所以我直接開始了..)


var puppeteer = require('puppeteer')

const defaultPrinterOptions = {
    format: 'A4',
    printBackground: true,
    margin: {
        left: '0px',
        top: '0px',
        right: '0px',
        bottom: '0px'
    }
}

class PdfPrinter {

    maxBrowsers = 2
    enqueuedPrintJobs = []
    failedJobs = []
    browserInstances = 0

    // max browser instances in parallel 
    constructor(maxBrowsers) {
        this.maxBrowsers = maxBrowsers
    }

    /**
     * 
     * @param {*} html the html content to print
     * @param {*} css to apply to the page
     * @param {*} printOptions options passed to puppeteer
     */
    // enqueues a print but the exact end moment cannot be known..
    enqueuePrint = (html, css, path, done) => {
        // merge custom options with defaultOptions..
        const printOptions = {
            ...defaultPrinterOptions,

            // add the path to the options.
            path: path
        }

        // create a function which can be stored in an array
        // it will later be grabbed by startPrinter() OR at the time any 
        // brwoser freed up.. 
        // the function needs to be passed the actual used browser instance!
        this.enqueuedPrintJobs.push(async(browser) => {

            // catch the error which may be produced when printing something..
            try {
                // print the document
                await this.print(browser, html, css, printOptions)
            } catch (err) {
                console.error('error when printing document..CLosing browser and starting a new job!!', printOptions.path)
                console.error(err)

                // store someting so you now what failed and coudl be retried or something..
                this.failedJobs.push({ html, css, path: printOptions.path })

                // puppeteer can run into erros too!! 
                // so close the browser and launch a new one!
                await this.closeBrowser(browser)
                browser = await this.launchBrowser()
            }

            // after the print, call done() so the promise is resovled in the right moment when 
            // this particular print has ended.!
            done()

            // start the next job right now  if there are any left.
            const job = this.enqueuedPrintJobs.shift()

            if (!job) {
                console.log('No print jobs available anymore. CLosing this browser instance.. Remaining browsers now:', this.maxBrowsers - this.browserInstances   1)
                await this.closeBrowser(browser)
                return
            }

            // job is actually this function itself! It will be executed
            // and automatically grab a new job after completion :)
            // we pass the same browser instance to the next job!.
            await job(browser)
        })

        // whenever a print job added make sure to start the printer
        // this starts new browser instances if the limit is not exceeded resp. if no browser is instantiated yet,
        // and does nothing if maximum browser count is reached..
        this.tryStartPrinter()
    }

    // same as enqueuePrint except it wraps it in a promise so we can now the
    // exact end moment and await it..
    enqueuePrintPromise(html, css, path) {
        return new Promise((resolve, reject) => {
            try {
                this.enqueuePrint(html, css, path, resolve)
            } catch (err) {
                console.error('unexpected error when setting up print job..', err)
                reject(err)
            }
        })

    }

    // If browser instance limit is not reached will isntantiate a new one and run a print job with it.
    // a print job will automatically grab a next job with the created browser if there are any left.
    tryStartPrinter = async() => {

        // Max browser count in use OR no jobs left.
        if (this.browserInstances >= this.maxBrowsers || this.enqueuedPrintJobs.length === 0) {
            return
        }
        // browser instances available! 
        // create a new one 

        console.log('launching new browser. Available after launch:', this.maxBrowsers - this.browserInstances - 1)
        const browser = await this.launchBrowser()
        
        // run job
        const job = this.enqueuedPrintJobs.shift()
        await job(browser)

    }

    closeBrowser = async(browser) => {


        // decrement browsers in use!
        // important to call before closing browser!!
        this.browserInstances--
        await browser.close()

    }

    launchBrowser = async() => {
        // increment browsers in use!
        // important to increase before actualy launching (async stuff..)
        this.browserInstances  

        // this code you have to adjust according your enviromnemt..
        const browser = await puppeteer.launch({ headless: true })

        return browser
    }


    // The actual print function which creates a pdf.
    print = async(browser, html, css, printOptions) => {

        console.log('Converting page to pdf. path:', printOptions.path)
            // Run pdf creation in seperate page.
        const page = await browser.newPage()

        await page.setContent(html, { waitUntil: 'networkidle0' });
        await page.addStyleTag({ content: css });
        await page.pdf(printOptions);
        await page.close();

    }

}

// testing the PDFPrinter with some jobs.
// make sure to run the printer in an `async` function so u can 
// use await... 
const testPrinterQueue = async() => {

    // config
    const maxOpenedBrowsers = 5 // amount of browser instances which are allowed to be opened in parallel
    const testJobCount = 100 // amount of test pdf jobs to be created
    const destDir = 'C:\\somepath' // the directory to store the pdfs in..


    // create sample jobs for testing...
    const jobs = []
    for (let i = 0; i < testJobCount; i  ) {
        jobs.push({
            html: `<h1>job number [${i}]</h1>`,
            css: 'h1 { background-color: red; }',
            path: require('path').join(destDir, `pdf_${i}.pdf`)
        })
    }

    // track time
    const label = 'printed a total of '   testJobCount   ' pdfs!'
    console.time(label)

    // run the actual pdf generation..
    const printer = new PdfPrinter(maxOpenedBrowsers)

    const jobProms = []
    for (let job of jobs) {

        // run jobs in parallel. Each job wil be runned async and return a Promise therefor
        jobProms.push(
            printer.enqueuePrintPromise(job.html, job.css, job.path)
        )
    }

    console.log('All jobs enqueued!! Wating for finish now.')

    // helper function which awaits all the print jobs, resp. an array of promises.
    await Promise.all(jobProms)
    console.timeEnd(label)

    // failed jobs::
    console.log('jobs failed:', printer.failedJobs)

    // as file:
    await require('fs').promises.writeFile('failed-jobs.json', JSON.stringify(printer.failedJobs))
}


testPrinterQueue().then(() => {
    console.log('done with everyting..')
}).catch(err => {
    console.error('unexpected error occured while printing all pages...', err)
})

您只需要在開始時調整destDir/openedBrowserstestJobCountvarstestPrinterQueue()即可使其正常作業。

是什么導致您的代碼出現問題

讓我們來看看這件作品

(async () => {
        browser = await Puppeteer.launch({
            headless: true,
            handleSIGINT: false,
            args: args,
        });

        const page = await browser.newPage();
    
        await page.setViewport({
            width: resolution.x,
            height: resolution.y,
        })

        await computeFirstTerm(page);
        await computeSecondTerm(page);
        await computeThirdTerm(page);
        browser.close()
    })()

您創建了一個立即執行的匿名函式。在函式內,所有陳述句都使用正確等待await但是,如果您在應用程式的同步部分中運行這整個部分,則整個函式將立即啟動,但在運行下一個代碼之前不會等待。

簽出這個例子:

//utility
function wait(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms)
    })
}

const AsyncFunction = async() => {
    console.log('Async named function started')
        // simulate execution time of 2 seconds
    await wait(2000)

    console.log('Async named function ended')
};


function SyncFunction() {
    console.log('sync function started')

    // example of async function execution within a sync function..
    AsyncFunction();

    // what you have done in your code:
    (async() => {
        console.log('Async anonymus function started')
        await wait(3000)
        console.log('Async anonymus function ended')

    })()


    // what
    console.log('sync function ended.')
}

SyncFunction()
console.log('done')

注意輸出:

Async named function started
Async anonymus function started
sync function ended. // => sync function already ended 
done   // sync function ended and code continues execution.
Async named function ended
Async anonymus function ended

要正確等待您的async內容,您需要將整個應用程式置于異步范圍內:

//utility
function wait(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms)
    })
}

const AsyncFunction = async() => {
    console.log('Async named function started')
        // simulate execution time of 2 seconds
    await wait(2000)

    console.log('Async named function ended')
};

// this is now async!!
async function SyncFunction() {
    console.log('sync function started')

    // example of async function execution within a sync function..
    await AsyncFunction();

    // what you have done in your code:
    await (async() => {
        console.log('Async anonymus function started')
        await wait(3000)
        console.log('Async anonymus function ended')

    })()


    // what
    console.log('sync function ended.')
}

SyncFunction().then(() => {
    console.log('done')
}).catch(err => {
    console.error('unexpected error occured..')
})

這個輸出就是我們想要的

sync function started
Async named function started
Async named function ended
Async anonymus function started
Async anonymus function ended
sync function ended.
done

希望這可以幫助您理解。

歡迎發表評論。

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/428366.html

標籤:节点.js pdf 傀儡师

上一篇:ExceptionConverter:java.io.IOException:嘗試使用iText創建PDF時流已關閉

下一篇:Qt:使用HTML在QTextDocument上設定背景影像以生成pdf檔案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more