[CakePHP] Sử dụng thư viện mPDF (p2)

Trong bài trước tôi đã giới thiệu cách thiết lập và sử dụng vài chức năng trong mPDF với CakePHP. Bài này tôi sẽ tiếp tục trình bày về một vài chức năng hữu ích khác như cách set header, footer cho các trang PDF, bảo vệ file, đánh số trang, canh lề ...

Headers & Footers

Khi bạn sử dụng mPDF, bạn có nhiều cách khác nhau để thiết lập headers và footers cho các trang PDF của bạn. Từ phiên bản 6.0 thì đã có các phương thức mới được thêm vào, đó là sử dụng HTML ngay trong chúng mà không phải thông qua các biến của mPDF nữa. Do đó, từ bản 6 thì các phương thức Non-HTML đã bị deprecated, tuy rằng chúng vẫn còn được hỗ trợ. Dưới đây là các cách bạn thao tác với headers & footers :

  • RUNTIME : bạn dùng cách này khi muốn set headers và footers cho tất cả các trang có trong PDF sẽ xuất ra.

  • NAMED : có nghĩa là bạn định nghĩa ra các headers, footers ở bất kì thời điểm nào (trước khi sử dụng) và đặt tên cho chúng. Sau đó chúng có thể được dùng và sử dụng lại bất kì khi nào qua tên đã định nghĩa. Bạn có thể dùng mã PHP hoặc các thẻ HTML custom.

  • Non-HTML : headers/footers của bạn là một chuỗi text và không có HTML định nghĩa style. Việc thay đổi style cho chuỗi này sẽ được làm qua các biến của mPDF.

  • HTML : đây là cách mới để nghĩa headers/footers với mã HTML chuẩn. Chúng chỉ có thể được định nghĩa ngoài khối thẻ HTML (ngoại trừ thẻ <body>).

    Chú ý : Với phương thức mới, chấp nhận các thẻ HTML trong headers/footers nên cũng đồng nghĩa sẽ tốn thời gian cũng như memory khi xử lý. Do đó, với các PDF lớn thì các bạn nên chú ý.

Phiên bản mà tôi dùng trong phần trước là 5.7.4 nên trong bài này tôi cũng chỉ đề cập đến những phương thức cũ hơn so với bản mới nhất là 6.0.

Runtime : SetHeader() và SetFooter()

Như tôi đã nói ở trên, đây là hai phương thức cho phép thiết lập headers và footers cho toàn bộ các trang có trong PDF của bạn.

1) SetHeader([ mixed $header [, string $side [, boolean $write ]]])

  • header : Tham số này sẽ là nội dung header, nó có thể là một chuỗi hoặc một mảng. Nếu một chuỗi trống, gía trị NULL hay mảng trống thì sẽ không có header. Mặc định là một mảng array() trống.

  • chuỗi : Một giá trị chuỗi đơn giản truyền vào thì header sẽ căn phải, với PDF 2 mặt thì trang chẵn sẽ căn trái. Còn nếu một chuỗi có định dạng như sau "tên tài liệu|{PAGE_NO}|tiêu đề" thì lần lượt các chuỗi con trong nó sẽ được căn trái, giữa, phải. Nhưng với trang chẵn của PDF 2 mặt thì sẽ là căn phải, giữa, trái.

  • mảng : Sẽ có 2 khả năng. Một là các trang chẵn lẻ sẽ có thông tin header giống nhau, khi đó thì tham số $side sẽ có giá trị trống. Khả năng thứ 2, bạn có thể thiết lập cho từng trang chẵn lẻ có header như nào. Bạn có thể thiết lập các thông số sau :

    • content: chuỗi
    • font-size: kiểu float
    • font-style: nhận vào chuỗi B|I|BI|BLANK
    • font-family: tất cả loại font-family
    • color: nhận chuỗi CSS dạng mã mầu #RRGGBB
    • line: 0 hoặc 1, để không hoặc có vẽ một đường bên dưới header
  • side : mặc định là trống để thiết lập cho tất cả các trang. Ngoài ra, nó có thể nhận O hoặc E, tức là trang lẻ hoặc chẵn.

  • write : tham số này nếu đặt true thì sẽ thiết lập ngay header vào trang hiện tại. Thường bạn sẽ dùng khi mà bạn đang thiết lập header sau khi một trang PDF mới được thêm vào.

Hãy xem đoạn code bên dưới

$header = array (
  'odd' => array (
    'L' => array (
      'content' => 'left header',
      'font-size' => 8,
      'font-style' => '',
      'font-family' => 'serif',
      'color'=>'#000000'
    ),
    'C' => array (
      'content' => 'bold center header here',
      'font-size' => 10,
      'font-style' => 'B',
      'font-family' => 'serif',
      'color'=>'#000000'
    ),
    'R' => array (
      'content' => 'right header, italic',
      'font-size' => 8,
      'font-style' => 'i',
      'font-family' => 'serif',
      'color'=>'#000000'
    ),
    'line' => 1, // kẻ đường bên dưới header
  ),
  'even' => array ()
);

Giờ hãy add phương thức cùng đoạn code trên vào PostsController.php và chạy thử (đồng thời hãy thêm sốP lượng post vào DB khoảng 60 bản ghi để xem được kết quả trên nhiều trang):

$this->Mpdf->SetHeader($header);

Kết quả header sẽ như bên dưới cho tất cả các trang :

header1.png

Tiếp đến là khả năng thứ 2, hãy dùng đoạn code khác :

	$oddHeader = array (
	    'L' => array (
	      'content' => 'odd left',
	      'font-size' => 12,
	      'font-style' => 'BI',
	      'font-family' => 'serif',
	      'color'=>'#000000'
	    ),
	    'C' => array (
	      'content' => 'center odd',
	      'font-size' => 8,
	      'font-style' => 'i',
	      'font-family' => 'serif',
	      'color'=>'#000000'
	    ),
	    'R' => array (
	      'content' => 'right odd',
	      'font-size' => 12,
	      'font-style' => 'B',
	      'font-family' => 'serif',
	      'color'=>'#000000'
	    ),
	    'line' => 1, // line below header
	);

	$evenHeader = array (
	    'L' => array (
	      'content' => 'left even header',
	      'font-size' => 8,
	      'font-style' => '',
	      'font-family' => 'serif',
	      'color'=>'#000000'
	    ),
	    'C' => array (
	      'content' => 'bold center even header here',
	      'font-size' => 10,
	      'font-style' => 'B',
	      'font-family' => 'serif',
	      'color'=>'#000000'
	    ),
	    'R' => array (
	      'content' => 'right even header, italic',
	      'font-size' => 8,
	      'font-style' => 'i',
	      'font-family' => 'serif',
	      'color'=>'#000000'
	    ),
	    'line' => 0, // no line below header
	);
	$this->Mpdf->mirrorMargins = 1; // dòng này sẽ thiết lập PDF ở chế độ 2 mặt, nếu không thêm vào thì chỉ có một header cho tất cả các trang
	$this->Mpdf->SetHeader($oddHeader, "O");
	$this->Mpdf->SetHeader($evenHeader, "E");

Trang lẻ

header2.png

Trang chẵn

header3.png

Bạn hãy thử thay đổi một vào thiết lập để xem ảnh hưởng của chúng đến header như là màu sắc chẳng hạn.

2) SetFooter([ mixed $header [, string $side]])

Bạn có thể thấy phương thức này tương tự như SetHeader(), chỉ khác ở chỗ nó không có tham số write. Các phần thiết lập còn lại hoàn toàn giống nên tôi sẽ không trình bày về phần này chi tiết. Hãy thử thêm đoạn code tương tự cho phần footer và chạy thử xem 😃

Footer trang lẻ tôi đánh số và để màu trắng

footer1.png

Footer trang chẵn tôi bỏ đánh số và hiển thị màu vàng

footer2.png

Bảo vệ file PDF

Trong thực tế chỉ add watermark vào thôi có lẽ chưa đủ, vì nếu có ai đó muốn ăn cắp nội dung PDF của bạn thì họ hoàn toàn có thể. Do đó, mPDF cũng có cung cấp một số giải pháp hữu hiệu thông qua phương thức SetProtection().

SetProtection ( array $permissions [, string $user_password [, string $owner_password [, integer $length ]]])

Phương thức này cho phép bạn thiết lập quyền truy cập sử dụng PDF sẽ được tạo ra trong chương trình, bao gồm mã hoá, đặt mật khẩu đối với end user và chủ sở hữu.

Chú ý : Mặc định mPDF không hề mã hoá cũng như không giới hạn quyền truy cập, sử dụng PDF như sao chép nội dung, in ấn và chỉnh sửa.
  • permissions : Tham số này nhận vào một mảng chỉ rõ quyền được gán cho người dùng cuối. Nếu là mảng trống thì user hoàn toàn không có quyền với tài liệu PDF. Các giá trị bạn có thể thiết lập :

    • print : cho phép in file PDF
    • modify : user được chỉnh sửa nội dung file PDF bằng các thao tác như 'fill-forms', 'extract' và 'assemble'
    • copy : sao chép hoặc là extract text và hình ảnh từ tài liệu PDF
    • annot-forms : cho phép người dùng thêm hay sửa các note, comment; hoặc điền vào các form có trong PDF. Ngoài ra, nếu quyền 'modify' cũng được thiết lập thì user có thể tạo hoặc sửa các form.
    • fill-forms : Nếu được thiết lập thì dù 'annot-forms' không được thiết lập đi chăng nữa, user vẫn có quyền điền vào các form có trong PDF.
    • extract : cho phép user trích xuất các text hay hình ảnh
    • assemble : cho phép chèn, quay hay thậm chí xoá các trang PDF, rồi có thể tạo bookmark hay ảnh thumbnail (dù bạn có thiết lập quyền 'modify' hay không)
    • print-highres : cho phép thiết lập độ phân giải cao khi in ấn, vì bình thường nếu không thiết lập quyền này sẽ bị hạn chế về độ phân giải khi in.
  • user_password : chỉ định mật khẩu mới cho phép user mở file PDF. Nếu để trống hoặc bỏ qua thì không cần mật khẩu vẫn sẽ mở được file.

  • owner_password : chỉ định mật khẩu để có đầy đủ hết các quyền đối với file. Nếu bạn bỏ qua thì một mật khẩu mặc định được thiết lập tự động bởi mPDF.

  • length : chỉ định độ dài bit dùng cho việc mã hoá. Bạn có thể set là 40 hoặc 128, mặc định là 40. Nhưng nếu bạn thiết lập một trong 4 quyền cuối cùng ở bên trên thì mặc định sẽ là 128, dù có đặt là 40 đi chăng nữa.

Hãy thêm đoạn code dưới vào và chạy thử chương trình,r ồi mở file PDF xem kết quả sẽ như nào :

/*
 * Mã hoá file và không cấp bất cứ quyền nào cho user như là sao chép hay in ấn ...
 * Nhưng user vẫn có thể mở file mà không cần mật khẩu
 * Owner sẽ không thể truy cập với full quyền vì không thiết lập owner_password
 */
$this->Mpdf->SetProtection(array());
Chú ý : Nếu bạn đang dùng ứng dụng xem PDF trên Ubuntu như Document Viewer thì bạn thấy bạn vẫn có quyền copy, in ấn ... bởi vì những thiết lập này được xây dựng cho Adobe nên hiện nó chỉ có hiệu lực nếu bạn đang dùng Adobe Reader. Do đó, giải pháp tốt nhất có lẽ là đặt mật khẩu bảo vệ.

Hãy thử thay thế và chạy đoạn code sau. Khi bạn mở file với trình đọc bất kì, bạn sẽ cần có mật khẩu.

/*
 * Mã hoá file và không cấp bất cứ quyền nào cho user trừ việc in PDF
 * Khi bạn mở file với trình đọc bất kì, bạn sẽ cần có mật khẩu.
 * Bạn sẽ có full quyền khi cung cấp được mật khẩu của owner
 */
$this->Mpdf->SetProtection(array('print'), 'pass2open', 'pass2havefullaccess');

Thay đổi ngôn ngữ

Bình thường mặc định sẽ hoàn toàn hiển thị được kí tự tiếng Anh, nhưng nếu bạn làm cho các khách hàng nước ngoài có kí tự đặc biệt như là Nhật, Hàn, Trung Quốc thì sẽ không thể hiển thị được hoặc hiển thị thành những kí tự lạ. mPDF hỗ trợ rất nhiều ngôn ngữ ngoài các ngôn ngữ trên còn có rất nhiều thứ tiếng đặc thù khác nữa, như là Ả rập, Thái và cả Việt Nam ... Trong bài này tôi sẽ trình bày cách config để file PDF có thể hiển thị tốt tiếng Nhật - ngôn ngữ của khách hàng hiện tại. Hãy thay đổi vài text sang chuỗi có chứa kí tự tiếng Nhật :

// phần watermark
$this->Mpdf->SetWatermarkText("test 日本語", 0.2);
...
// phần header
'content' => 'odd left 日本語',

Nếu bạn chạy thử bây giờ thì các kí tự tiếng Nhật trên sẽ hiển thị trống. Bạn cần phải thay đổi một vài chỗ như sau, đầu tiên cần set useAdobeCJKtrue trong config.php của thư mục mpdf trong Vendor.

$this->useAdobeCJK = true;

Thứ hai, bạn cần thay đổi một chút trong file app/View/Layouts/default.ctp, để useAdobeCJK hoạt động được nó cần biết ngôn ngữ nào sử dụng thông qua thuộc tính lang, nên hãy thêm vào thẻ body như sau:

<body lang='ja'>

Nhưng những thiết lập ở trên lại không apply được cho watermark text nên nó vẫn không hiển thị được tiếng Nhật, bạn phải set thông qua biến watermark_font

$this->Mpdf->watermark_font = 'sjis';

Page size, orientation & margin

Việc thay đổi kích cỡ cũng như chiều quay của PDF trong mPDF rất đơn giản. Nếu bạn để ý thì trong thiết lập của hàm init() của MpdfComponent đã có thông số này, kích cỡ cho phép cài đặt là từ A0-A10 và nếu có hậu tố -L thì các trang PDF xuất ra sẽ theo chiều ngang.

// nếu bạn thêm -L vào sau A4 thì trang PDF xuất ra sẽ theo chiều ngang
'format' => 'A4'

Việc thiết lập margin cho PDF cũng diễn ra khi khởi tạo, những giá trị mặc định bạn có thể set trong MpdfComponent.php

    // canh lề, giá trị nhận vào với đơn vị mm
    'margin_left' => 15,
    'margin_right' => 15,
    'margin_top' => 16,
    'margin_bottom' => 16,
    'margin_header' => 9,
    'margin_footer' => 9

Nhưng bạn cũng có thể cho phép user ghi đè những thông số này, bằng cách truyền vào những giá trị khác khi gọi đến init(). Các giá trị truyền vào có thể lấy từ input của user nhưng để đơn giản, tôi sẽ fix trực tiếp trong code.

    $this->Mpdf->init(array('margin_left' => 20,
	    'margin_right' => 20,
	    'margin_top' => 25,
	    'margin_bottom' => 25,
	    'margin_header' => 15,
	    'margin_footer' => 15)
    	);

Bạn hãy chạy thử thì sẽ thấy kết quả file PDF với nhưng thông số căn lề mới đã được áp dụng.

margin.png

code on github


All Rights Reserved