طباعة المستندات المخصصة

بالنسبة لبعض التطبيقات، مثل تطبيقات الرسم وتطبيقات تخطيط الصفحات والتطبيقات الأخرى التي تركز على فإن إنشاء صفحات مطبوعة جميلة يُعد ميزة أساسية. وفي هذه الحالة، لا يكفي لطباعة صورة أو مستند HTML. يتطلب ناتج الطباعة لهذه الأنواع من التطبيقات التحكم الدقيق في كل ما يدخل في الصفحة، بما في ذلك الخطوط وتدفق النص وفواصل الصفحات والرؤوس والتذييلات والعناصر الرسومية.

يتطلب إنشاء إخراج طباعة يتم تخصيصه بالكامل لتطبيقك والبرمجة من الأساليب التي تمت مناقشتها سابقًا. يجب عليك إنشاء مكونات التواصل مع إطار عمل الطباعة والتكيف مع إعدادات الطابعة ورسم عناصر الصفحة إدارة الطباعة على عدة صفحات.

يوضح هذا الدرس كيفية الاتصال بمدير الطباعة وإنشاء محول طباعة وإنشاء محتوى للطباعة.

عندما يدير تطبيقك عملية الطباعة بشكل مباشر، تتمثل الخطوة الأولى بعد استلام طلب الطباعة من المستخدم هو الاتصال بإطار عمل الطباعة في Android والحصول على نسخة من فئة PrintManager. تتيح لك هذه الفئة إعداد مهمة طباعة وتبدأ دورة حياة الطباعة. يوضح مثال الرمز التالي كيفية الحصول على مدير الطباعة وبدء عملية الطباعة.

Kotlin

private fun doPrint() {
    activity?.also { context ->
        // Get a PrintManager instance
        val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager
        // Set job name, which will be displayed in the print queue
        val jobName = "${context.getString(R.string.app_name)} Document"
        // Start a print job, passing in a PrintDocumentAdapter implementation
        // to handle the generation of a print document
        printManager.print(jobName, MyPrintDocumentAdapter(context), null)
    }
}

Java

private void doPrint() {
    // Get a PrintManager instance
    PrintManager printManager = (PrintManager) getActivity()
            .getSystemService(Context.PRINT_SERVICE);

    // Set job name, which will be displayed in the print queue
    String jobName = getActivity().getString(R.string.app_name) + " Document";

    // Start a print job, passing in a PrintDocumentAdapter implementation
    // to handle the generation of a print document
    printManager.print(jobName, new MyPrintDocumentAdapter(getActivity()),
            null); //
}

يوضّح مثال الرمز أعلاه كيفية تسمية مهمة طباعة وضبط مثيل لفئة PrintDocumentAdapter تتعامل مع خطوات دورة حياة الطباعة. تشير رسالة الأشكال البيانية سنناقش تنفيذ فئة مهايئات الطباعة في القسم التالي.

ملاحظة: المعلمة الأخيرة في print() تأخذ الطريقة الكائن PrintAttributes. يمكنك استخدام هذه المعلمة تقديم تلميحات لإطار عمل الطباعة وخيارات معدة مسبقًا بناءً على دورة الطباعة السابقة، وبالتالي تحسين تجربة المستخدم. ويمكنك أيضًا استخدام هذه المعلمة لتعيين الخيارات أكثر ملاءمة للمحتوى الذي تتم طباعته، مثل تعيين الاتجاه على الوضع الأفقي عند طباعة صورة في هذا الاتجاه.

يتفاعل محوّل الطباعة مع إطار عمل الطباعة في Android ويعالج خطوات عملية الطباعة. وتتطلّب هذه العملية من المستخدمين اختيار الطابعات وخيارات الطباعة قبل إنشائها. مستند للطباعة. يمكن أن تؤثر هذه الاختيارات على الناتج النهائي أثناء اختيار المستخدم الطابعات ذات إمكانات إخراج مختلفة أو أحجام صفحات مختلفة أو اتجاهات مختلفة للصفحة. وأثناء تحديد هذه الاختيارات، يطلب إطار عمل الطباعة من المحول تخطيط وإنشاء اطبع المستند، استعدادًا للمخرج النهائي. بمجرد أن ينقر المستخدم فوق زر الطباعة، فإن إطار العمل يحصل على مستند الطباعة النهائي ويمرره إلى موفر الطباعة للإخراج. أثناء الطباعة يمكن للمستخدمين اختيار إلغاء إجراء الطباعة، وبالتالي يجب على محوّل الطباعة أيضًا الاستماع إلى والاستجابة لطلبات الإلغاء.

صُممت الفئة التجريدية للفئة PrintDocumentAdapter للتعامل مع دورة حياة الطباعة، والتي تتضمن أربع طرق رئيسية لمعاودة الاتصال. يجب تنفيذ هذه الطرق في محوّل الطباعة من أجل التفاعل بشكل مناسب مع إطار عمل الطباعة:

  • onStart() - تم الاتصال مرة واحدة في بداية عملية الطباعة. إذا كان طلبك يحتوي على أي مهام تحضيرية لمرة واحدة تنفيذها، مثل الحصول على لقطة للبيانات المراد طباعتها، وتنفيذها هنا. التنفيذ لا يلزم استخدام هذه الطريقة في المحوّل.
  • onLayout() - يتم الاتصال به في كل مرة تغيير المستخدم لأحد إعدادات الطباعة مما يؤثر في الناتج، مثل تغيير حجم الصفحة، أو اتجاه الصفحة، مما يمنح تطبيقك فرصة لحساب تخطيط الصفحات التي ستتم طباعتها. يجب أن تعرض هذه الطريقة على الأقل عدد الصفحات المتوقّعة في المستند المطبوع.
  • onWrite() - تم طلبه لعرض الصور المطبوعة من الصفحات إلى ملف لطباعتها. يمكن استدعاء هذه الطريقة مرة واحدة أو أكثر بعد كل مكالمة onLayout().
  • onFinish() - تم الاتصال مرة واحدة في النهاية من عملية الطباعة. إذا كان تطبيقك يحتوي على أي مهام تقسيم لمرة واحدة لأداءها، وتنفيذها هنا. ولا يلزم استخدام هذه الطريقة في المحوّل.

توضح الأقسام التالية كيفية تنفيذ التخطيط وطرق الكتابة، وهي بالغ الأهمية لتشغيل محول الطباعة.

ملاحظة: يتم استدعاء هذه الطُرق الخاصة بالمحوّلات في سلسلة التعليمات الرئيسية لتطبيقك. في حال حذف تتوقّع أن يستغرق تنفيذ هذه الطرق في عملية التنفيذ قدرًا كبيرًا من يمكنك تنفيذها في سلسلة محادثات منفصلة. على سبيل المثال، يمكنك تلخيص تخطيط أو طباعة كتابة المستندات في كائنات AsyncTask منفصلة.

حساب معلومات المستندات المطبوعة

في إطار تنفيذ الفئة PrintDocumentAdapter، لا يجب أن يكون التطبيق قادرًا على تحديد نوع المستند الذي ينشئه وحساب إجمالي عدد صفحات مهمة الطباعة، مع تقديم معلومات حول حجم الصفحة المطبوعة. تنفيذ طريقة onLayout() في يقوم المحول بإجراء هذه العمليات الحسابية ويقدم معلومات حول الإخراج المتوقع مهمة طباعة في فئة PrintDocumentInfo، بما في ذلك عدد الصفحات ونوع المحتوى. يوضّح مثال الرمز التالي تنفيذًا أساسيًا لطريقة onLayout() لـ PrintDocumentAdapter:

Kotlin

override fun onLayout(
        oldAttributes: PrintAttributes?,
        newAttributes: PrintAttributes,
        cancellationSignal: CancellationSignal?,
        callback: LayoutResultCallback,
        extras: Bundle?
) {
    // Create a new PdfDocument with the requested page attributes
    pdfDocument = PrintedPdfDocument(activity, newAttributes)

    // Respond to cancellation request
    if (cancellationSignal?.isCanceled == true) {
        callback.onLayoutCancelled()
        return
    }

    // Compute the expected number of printed pages
    val pages = computePageCount(newAttributes)

    if (pages > 0) {
        // Return print information to print framework
        PrintDocumentInfo.Builder("print_output.pdf")
                .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                .setPageCount(pages)
                .build()
                .also { info ->
                    // Content layout reflow is complete
                    callback.onLayoutFinished(info, true)
                }
    } else {
        // Otherwise report an error to the print framework
        callback.onLayoutFailed("Page count calculation failed.")
    }
}

Java

@Override
public void onLayout(PrintAttributes oldAttributes,
                     PrintAttributes newAttributes,
                     CancellationSignal cancellationSignal,
                     LayoutResultCallback callback,
                     Bundle metadata) {
    // Create a new PdfDocument with the requested page attributes
    pdfDocument = new PrintedPdfDocument(getActivity(), newAttributes);

    // Respond to cancellation request
    if (cancellationSignal.isCanceled() ) {
        callback.onLayoutCancelled();
        return;
    }

    // Compute the expected number of printed pages
    int pages = computePageCount(newAttributes);

    if (pages > 0) {
        // Return print information to print framework
        PrintDocumentInfo info = new PrintDocumentInfo
                .Builder("print_output.pdf")
                .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                .setPageCount(pages)
                .build();
        // Content layout reflow is complete
        callback.onLayoutFinished(info, true);
    } else {
        // Otherwise report an error to the print framework
        callback.onLayoutFailed("Page count calculation failed.");
    }
}

يمكن أن يؤدي تنفيذ طريقة onLayout() هناك ثلاث نتائج: الإكمال أو الإلغاء أو الإخفاق في حالة ما إذا كان حساب لا يمكن إكمال التخطيط. يجب الإشارة إلى إحدى هذه النتائج عن طريق استدعاء الأعمدة للكائن PrintDocumentAdapter.LayoutResultCallback.

ملاحظة: المعلمة المنطقية تشير طريقة onLayoutFinished() إلى ما إذا كان محتوى التنسيق قد تغير بالفعل أم لا منذ آخر طلب. ويسمح ضبط هذه المَعلمة بشكلٍ صحيح لإطار عمل الطباعة بتجنُّب عن طريق استدعاء طريقة onWrite() بدون داعٍ، التخزين المؤقت للمستند المطبوع سابقًا بشكل أساسي وتحسين الأداء.

يتمثل العمل الرئيسي لـ "onLayout()" في حساب عدد الصفحات المتوقعة كمخرجات بناءً على سمات الطابعة. تعتمد طريقة حساب هذا العدد بشكل كبير على كيفية تخطيط التطبيق لصفحات الطباعة. يوضح مثال الرمز التالي عملية تنفيذ حيث يتم تسجيل عدد الصفحات الذي يحدده اتجاه الطباعة:

Kotlin

private fun computePageCount(printAttributes: PrintAttributes): Int {
    var itemsPerPage = 4 // default item count for portrait mode

    val pageSize = printAttributes.mediaSize
    if (!pageSize.isPortrait) {
        // Six items per page in landscape orientation
        itemsPerPage = 6
    }

    // Determine number of print items
    val printItemCount: Int = getPrintItemCount()

    return Math.ceil((printItemCount / itemsPerPage.toDouble())).toInt()
}

Java

private int computePageCount(PrintAttributes printAttributes) {
    int itemsPerPage = 4; // default item count for portrait mode

    MediaSize pageSize = printAttributes.getMediaSize();
    if (!pageSize.isPortrait()) {
        // Six items per page in landscape orientation
        itemsPerPage = 6;
    }

    // Determine number of print items
    int printItemCount = getPrintItemCount();

    return (int) Math.ceil(printItemCount / itemsPerPage);
}

كتابة ملف طباعة

عندما يحين وقت كتابة ناتج الطباعة في ملف، يستدعي إطار عمل طباعة Android طريقة onWrite() لفئة PrintDocumentAdapter في تطبيقك. تحدد معلمات الطريقة الصفحات التي يجب المكتوبة وملف الإخراج المراد استخدامه. يجب أن يعرض تنفيذ هذه الطريقة بعد ذلك كل طلب صفحة من المحتوى إلى ملف PDF متعدِّد الصفحات. عند اكتمال هذه العملية، لاستدعاء طريقة onWriteFinished() لكائن رد الاتصال.

ملاحظة: قد يستدعي إطار عمل الطباعة في Android الطريقة onWrite() مرة واحدة أو أكثر لكل الاتصال برقم onLayout(). لهذا السبب، من لتعيين المعلمة المنطقية onLayoutFinished() إلى false عندما لا يتم تغيير تنسيق محتوى الطباعة، تجنّب إجراء عمليات إعادة كتابة غير ضرورية لمستند الطباعة.

ملاحظة: المعلمة المنطقية تشير طريقة onLayoutFinished() إلى ما إذا كان محتوى التنسيق قد تغير بالفعل أم لا منذ آخر طلب. ويسمح ضبط هذه المَعلمة بشكلٍ صحيح لإطار عمل الطباعة بتجنُّب عن طريق استدعاء طريقة onLayout() بدون داعٍ، التخزين المؤقت للمستند المطبوع سابقًا بشكل أساسي وتحسين الأداء.

يوضح النموذج التالي الآليات الأساسية لهذه العملية باستخدام الفئة PrintedPdfDocument لإنشاء ملف PDF:

Kotlin

override fun onWrite(
        pageRanges: Array<out PageRange>,
        destination: ParcelFileDescriptor,
        cancellationSignal: CancellationSignal?,
        callback: WriteResultCallback
) {
    // Iterate over each page of the document,
    // check if it's in the output range.
    for (i in 0 until totalPages) {
        // Check to see if this page is in the output range.
        if (containsPage(pageRanges, i)) {
            // If so, add it to writtenPagesArray. writtenPagesArray.size()
            // is used to compute the next output page index.
            writtenPagesArray.append(writtenPagesArray.size(), i)
            pdfDocument?.startPage(i)?.also { page ->

                // check for cancellation
                if (cancellationSignal?.isCanceled == true) {
                    callback.onWriteCancelled()
                    pdfDocument?.close()
                    pdfDocument = null
                    return
                }

                // Draw page content for printing
                drawPage(page)

                // Rendering is complete, so page can be finalized.
                pdfDocument?.finishPage(page)
            }
        }
    }

    // Write PDF document to file
    try {
        pdfDocument?.writeTo(FileOutputStream(destination.fileDescriptor))
    } catch (e: IOException) {
        callback.onWriteFailed(e.toString())
        return
    } finally {
        pdfDocument?.close()
        pdfDocument = null
    }
    val writtenPages = computeWrittenPages()
    // Signal the print framework the document is complete
    callback.onWriteFinished(writtenPages)

    ...
}

Java

@Override
public void onWrite(final PageRange[] pageRanges,
                    final ParcelFileDescriptor destination,
                    final CancellationSignal cancellationSignal,
                    final WriteResultCallback callback) {
    // Iterate over each page of the document,
    // check if it's in the output range.
    for (int i = 0; i < totalPages; i++) {
        // Check to see if this page is in the output range.
        if (containsPage(pageRanges, i)) {
            // If so, add it to writtenPagesArray. writtenPagesArray.size()
            // is used to compute the next output page index.
            writtenPagesArray.append(writtenPagesArray.size(), i);
            PdfDocument.Page page = pdfDocument.startPage(i);

            // check for cancellation
            if (cancellationSignal.isCanceled()) {
                callback.onWriteCancelled();
                pdfDocument.close();
                pdfDocument = null;
                return;
            }

            // Draw page content for printing
            drawPage(page);

            // Rendering is complete, so page can be finalized.
            pdfDocument.finishPage(page);
        }
    }

    // Write PDF document to file
    try {
        pdfDocument.writeTo(new FileOutputStream(
                destination.getFileDescriptor()));
    } catch (IOException e) {
        callback.onWriteFailed(e.toString());
        return;
    } finally {
        pdfDocument.close();
        pdfDocument = null;
    }
    PageRange[] writtenPages = computeWrittenPages();
    // Signal the print framework the document is complete
    callback.onWriteFinished(writtenPages);

    ...
}

يفوّض هذا النموذج عرض محتوى صفحة PDF إلى drawPage() المستخدم، والتي ستتم مناقشتها في القسم التالي.

كما هو الحال مع التنسيق، يتم تنفيذ onWrite() الطريقة ثلاث نتائج: الإكمال أو الإلغاء أو الإخفاق في حالة لا يمكن كتابة المحتوى. يجب الإشارة إلى إحدى هذه النتائج عن طريق استدعاء الطريقة المناسبة لكائن PrintDocumentAdapter.WriteResultCallback.

ملاحظة: قد تستهلك عملية عرض مستند لطباعته موارد مكثفة. ضِمن لتجنّب حظر سلسلة محادثات واجهة المستخدم الرئيسية لتطبيقك، يجب مراعاة إجراء عمليات عرض الصفحة وكتابتها في سلسلة محادثات منفصلة، على سبيل المثال في AsyncTask. لمزيد من المعلومات حول التعامل مع سلاسل محادثات التنفيذ مثل المهام غير المتزامنة، راجع العمليات وThreads.

رسم محتوى صفحة بتنسيق PDF

عند طباعة طلبك، على طلبك إنشاء مستند PDF وإرساله إلى إطار عمل طباعة Android للطباعة. يمكنك استخدام أي مكتبة لإنشاء ملفات PDF لتنفيذ هذا الإجراء. الغرض. يعرض هذا الدرس كيفية استخدام صف PrintedPdfDocument. لإنشاء صفحات بتنسيق PDF من المحتوى الخاص بك

يستخدم الصف PrintedPdfDocument السمة Canvas. كائن لرسم عناصر على صفحة PDF، على غرار الرسم على تخطيط النشاط. يمكنك رسم العناصر في الصفحة المطبوعة باستخدام طرق الرسم Canvas. ما يلي: يوضح مثال التعليمة البرمجية كيفية رسم بعض العناصر البسيطة على صفحة مستند PDF باستخدام هذه الطرق:

Kotlin

private fun drawPage(page: PdfDocument.Page) {
    page.canvas.apply {

        // units are in points (1/72 of an inch)
        val titleBaseLine = 72f
        val leftMargin = 54f

        val paint = Paint()
        paint.color = Color.BLACK
        paint.textSize = 36f
        drawText("Test Title", leftMargin, titleBaseLine, paint)

        paint.textSize = 11f
        drawText("Test paragraph", leftMargin, titleBaseLine + 25, paint)

        paint.color = Color.BLUE
        drawRect(100f, 100f, 172f, 172f, paint)
    }
}

Java

private void drawPage(PdfDocument.Page page) {
    Canvas canvas = page.getCanvas();

    // units are in points (1/72 of an inch)
    int titleBaseLine = 72;
    int leftMargin = 54;

    Paint paint = new Paint();
    paint.setColor(Color.BLACK);
    paint.setTextSize(36);
    canvas.drawText("Test Title", leftMargin, titleBaseLine, paint);

    paint.setTextSize(11);
    canvas.drawText("Test paragraph", leftMargin, titleBaseLine + 25, paint);

    paint.setColor(Color.BLUE);
    canvas.drawRect(100, 100, 172, 172, paint);
}

عند استخدام Canvas للرسم على صفحة PDF، يتم تحديد العناصر في وهو ما يساوي 1/72 من البوصة. تأكَّد من استخدام وحدة القياس هذه لتحديد الحجم. من العناصر على الصفحة. لتحديد موضع العناصر المرسومة، يبدأ نظام الإحداثيات بـ 0,0 في الزاوية العلوية اليسرى من الصفحة.

ملاحظة: يتيح لك كائن Canvas وضع الطباعة. العناصر الموجودة على حافة مستند PDF، فإن العديد من الطابعات لا تستطيع الطباعة إلى حافة قطعة ورقية ورقية. تأكد من مراعاة الحواف غير القابلة للطباعة للصفحة عند في إنشاء مستند طباعة مع هذا الصف.