array( 841.89, 1190.55 ), 'a4' => array( 595.28, 841.89 ), 'a5' => array( 420.94, 595.28 ), 'letter' => array( 612, 792 ), 'legal' => array( 612, 1008 ) ]; /** * Default page size * * @var array|string */ protected $arr_default_page_sizes = []; /** * Current page size * * @var array|string */ protected $arr_current_page_sizes = []; /** * Used for pages with non default sizes or orientations * * @var array */ protected $arr_page_sizes = []; /** * Dimensions of current page in points * * @var mixed */ protected $flt_width_points, $flt_height_points = 0.00; /** * Dimensions of current page in user units * * @var mixed */ protected $flt_current_width, $flt_current_height = 0.00; /** * Left Margin * * @var int */ protected $int_left_margin = 0; /** * Top Margin * * @var int */ protected $int_top_margin = 0; /** * Right Margin * * @var int */ protected $int_right_margin = 0; /** * Page break margin * * @var int */ protected $int_break_margin = 0; /** * Cell Margin * * @var float|int */ protected $int_cell_margin = 0; /** * Current position in user unit * * @var */ protected $flt_position_x, $flt_position_y = 0.00; /** * Height of last printed cell * * @var float */ protected $flt_last_cell_height = 0.00; /** * Line width in user units * * @var float */ protected $flt_line_width = 0.00; /** * The path containing fonts * * @var string */ protected $str_font_path = ''; /** * Array of core font names * * @var array */ protected $arr_core_fonts = [ 'courier', 'helvetica', 'times', 'symbol', 'zapfdingbats' ]; /** * Array of used fonts * * @var array */ protected $arr_fonts = []; /** * Array of font files * * @var array */ protected $arr_font_files = []; /** * Array of encoding differences * * @var array */ protected $arr_encoding_diffs = []; /** * Current font family * * @var string */ protected $str_current_font_family = ''; /** * Current font style * * @var string */ protected $str_current_font_style = ''; /** * Underline flag * * @var bool */ protected $bol_underline = false; /** * Array of current font info * * @var array */ protected $arr_current_font_info = []; /** * Current font size in points * * @var int */ protected $int_current_font_size = 12; /** * Current font size in user units * * @var int */ protected $int_font_size_user = 0; /** * The draw color * * @var string */ protected $str_draw_color = '0 G'; /** * The fill color * * @var string */ protected $str_fill_color = '0 g'; /** * The text color * * @var string */ protected $str_text_color = '0 g'; /** * Indicates whether fill and text colors are different * * @var bool */ protected $bol_fill_text_differ = false; /** * The word spacing * * @var int */ protected $int_word_spacing = 0; /** * Array of used images * * @var array */ protected $arr_images = []; /** * Array of links in pages * * @var array */ protected $arr_page_links = []; /** * Array of internal links * * @var array */ protected $arr_internal_links = []; /** * Automatic page breaking * * @var bool */ protected $bol_auto_page_break = false; /** * Threshold used to trigger page breaks * * @var float */ protected $flt_page_break_trigger = 0.00; /** * Flag set when processing header * * @var bool */ protected $bol_in_header = false; /** * Flag set when processing footer * * @var bool */ protected $bol_in_footer = false; /** * Zoom display mode * * @var mixed */ protected $mix_zoom_mode; /** * Layout display mode * * @var string */ protected $str_layout_mode = ''; /** * The title * * @var string */ protected $str_title = ''; /** * The subject * * @var string */ protected $str_subject = ''; /** * The author * * @var string */ protected $str_author = ''; /** * The keywords * * @var string */ protected $str_keywords = ''; /** * The creator * * @var string */ protected $str_creator = ''; /** * The alias for total number of pages * * @var string */ protected $str_alias_number_pages = ''; /** * The PDF version number * * @var string */ protected $str_pdf_version = '1.3'; /** * The font metric cache directory, if null no cache will be used * * @var string */ protected $cachePath = null; /** * PDF constructor. * * @param string $str_orientation * @param string $str_units * @param string $str_size */ public function __construct( $str_orientation = 'P', $str_units = 'mm', $str_size = 'A4' ) { $this->setFontPath( __DIR__ . '/font' ); // Scale factor switch ( $str_units ) { case 'pt': $this->flt_scale_factor = 1; break; case 'mm': case 'cm': $this->flt_scale_factor = 72 / 25.4; break; case 'in': $this->flt_scale_factor = 72; break; default: throw new WOE_FPDF_Exception( esc_html('Invalid unit specified: ' . $str_units), esc_html(WOE_FPDF_Exception::INVALID_UNIT) ); } $str_size = $this->getPageSize( $str_size ); $this->arr_default_page_sizes = $str_size; $this->arr_current_page_sizes = $str_size; // Page orientation switch ( $str_orientation ) { case 'portrait': case self::ORIENTATION_PORTRAIT: $this->str_default_orientation = 'P'; $this->flt_current_width = $str_size[0]; $this->flt_current_height = $str_size[1]; break; case 'landscape': case self::ORIENTATION_LANDSCAPE: $this->str_default_orientation = 'L'; $this->flt_current_width = $str_size[1]; $this->flt_current_height = $str_size[0]; break; default: throw new WOE_FPDF_Exception( esc_html('Invalid orientation: ' . $str_orientation), esc_html(WOE_FPDF_Exception::INVALID_ORIENTATION) ); break; } $this->str_current_orientation = $this->str_default_orientation; $this->flt_width_points = $this->flt_current_width * $this->flt_scale_factor; $this->flt_height_points = $this->flt_current_height * $this->flt_scale_factor; // Page margins (1 cm) $flt_margin = 28.35 / $this->flt_scale_factor; $this->setMargins( $flt_margin, $flt_margin ); // Interior cell margin (1 mm) $this->int_cell_margin = $flt_margin / 10; // Line width (0.2 mm) $this->flt_line_width = .567 / $this->flt_scale_factor; // Automatic page break $this->SetAutoPageBreak( true, 2 * $flt_margin ); // Default display mode $this->SetDisplayMode( 'default' ); // Enable compression $this->SetCompression( true ); } /** * @param $int_left * @param $int_top * @param null $int_right */ public function setMargins( $int_left, $int_top, $int_right = null ) { // Set left, top and right margins $this->int_left_margin = $int_left; $this->int_top_margin = $int_top; if ( null === $int_right ) { $int_right = $int_left; } $this->int_right_margin = $int_right; } /** * @param $int_margin */ public function SetLeftMargin( $int_margin ) { // Set left margin $this->int_left_margin = $int_margin; if ( $this->int_page > 0 && $this->flt_position_x < $int_margin ) { $this->flt_position_x = $int_margin; } } /** * @param $int_margin */ public function SetTopMargin( $int_margin ) { // Set top margin $this->int_top_margin = $int_margin; } /** * @param $int_margin */ public function SetRightMargin( $int_margin ) { // Set right margin $this->int_right_margin = $int_margin; } /** * @param $bol_auto * @param int $int_margin */ public function SetAutoPageBreak( $bol_auto, $int_margin = 0 ) { // Set auto page break mode and triggering margin $this->bol_auto_page_break = $bol_auto; $this->int_break_margin = $int_margin; $this->flt_page_break_trigger = $this->flt_current_height - $int_margin; } /** * Set display mode and zoom view * * @param string $zoomMode Set zoom mode: default, fullpage, fullwidth or real * @param string $layoutMode Set layout mode: default, single, continuous or two */ public function SetDisplayMode( $zoomMode = null, $layoutMode = null ) { // Validate zoom mode $zoomModes = [ 'default', 'fullpage', 'fullwidth', 'real' ]; if ( $zoomMode === null ) { $zoomMode = $zoomModes[0]; } if ( ! in_array( $zoomMode, $zoomModes ) ) { throw new WOE_FPDF_Exception( esc_html('Invalid zoom mode specified: `' . $zoomMode . '`'), esc_html(WOE_FPDF_Exception::INVALID_ZOOM_MODE) ); } // Valid layout mode $layoutModes = [ 'default', 'single', 'continuous', 'two' ]; if ( $layoutMode === null ) { $layoutMode = $layoutModes[0]; } if ( ! in_array( $layoutMode, $layoutModes ) ) { throw new WOE_FPDF_Exception( esc_html('Invalid layout mode specified: `' . $layoutMode . '`'), esc_html(WOE_FPDF_Exception::INVALID_LAYOUT_MODE) ); } // Set zoom and layout modes $this->mix_zoom_mode = $zoomMode; $this->str_layout_mode = $layoutMode; } /** * @param $bol_compression */ public function SetCompression( $bol_compression ) { // Set page compression if ( function_exists( 'gzcompress' ) ) { $this->bol_compress = $bol_compression; } else { $this->bol_compress = false; } } /** * @param $str_title * @param bool $bol_utf8 */ public function SetTitle( $str_title, $bol_utf8 = false ) { // Title of document if ( $bol_utf8 ) { $str_title = $this->UTF8toUTF16( $str_title ); } $this->str_title = $str_title; } /** * @param $str_subject * @param bool $bol_utf8 */ public function SetSubject( $str_subject, $bol_utf8 = false ) { // Subject of document if ( $bol_utf8 ) { $str_subject = $this->UTF8toUTF16( $str_subject ); } $this->str_subject = $str_subject; } /** * @param $str_author * @param bool $bol_utf8 */ public function SetAuthor( $str_author, $bol_utf8 = false ) { // Author of document if ( $bol_utf8 ) { $str_author = $this->UTF8toUTF16( $str_author ); } $this->str_author = $str_author; } /** * @param $str_keywords * @param bool $bol_utf8 */ public function SetKeywords( $str_keywords, $bol_utf8 = false ) { // Keywords of document if ( $bol_utf8 ) { $str_keywords = $this->UTF8toUTF16( $str_keywords ); } $this->str_keywords = $str_keywords; } /** * @param $str_creator * @param bool $bol_utf8 */ public function SetCreator( $str_creator, $bol_utf8 = false ) { // Creator of document if ( $bol_utf8 ) { $str_creator = $this->UTF8toUTF16( $str_creator ); } $this->str_creator = $str_creator; } /** * @param string $str_alias */ public function AliasNbPages( $str_alias = '{nb}' ) { // Define an alias for total number of pages $this->str_alias_number_pages = $str_alias; } /** * Inits the document */ public function Open() { // Begin document $this->int_state = self::DOCUMENT_STATE_INITIALIZED; } /** * Closes the document */ public function Close() { // Terminate document if ( $this->int_state == self::DOCUMENT_STATE_TERMINATED ) { return; } if ( $this->int_page == 0 ) { $this->AddPage(); } // Page footer $this->bol_in_footer = true; $this->Footer(); $this->bol_in_footer = false; // Close page $this->EndPage(); // Close document $this->EndDoc(); } /** * @param string $str_orientation * @param string $str_size */ public function AddPage( $str_orientation = '', $str_size = '' ) { // Start a new page if ( $this->int_state == self::DOCUMENT_STATE_NOT_INITIALIZED ) { $this->Open(); } $str_font_family = $this->str_current_font_family; $str_style = $this->str_current_font_style . ( $this->bol_underline ? 'U' : '' ); $int_font_size = $this->int_current_font_size; $flt_line_width = $this->flt_line_width; $str_draw_color = $this->str_draw_color; $str_fill_color = $this->str_fill_color; $str_text_color = $this->str_text_color; $bol_fill_text_differ = $this->bol_fill_text_differ; if ( $this->int_page > 0 ) { // Page footer $this->bol_in_footer = true; $this->Footer(); $this->bol_in_footer = false; // Close page $this->EndPage(); } // Start new page $this->BeginPage( $str_orientation, $str_size ); // Set line cap style to square $this->Out( '2 J' ); // Set line width $this->flt_line_width = $flt_line_width; $this->Out( sprintf( '%.2F w', $flt_line_width * $this->flt_scale_factor ) ); // Set font if ( $str_font_family ) { $this->SetFont( $str_font_family, $str_style, $int_font_size ); } // Set colors $this->str_draw_color = $str_draw_color; if ( $str_draw_color != '0 G' ) { $this->Out( $str_draw_color ); } $this->str_fill_color = $str_fill_color; if ( $str_fill_color != '0 g' ) { $this->Out( $str_fill_color ); } $this->str_text_color = $str_text_color; $this->bol_fill_text_differ = $bol_fill_text_differ; // Page header $this->bol_in_header = true; $this->Header(); $this->bol_in_header = false; // Restore line width if ( $this->flt_line_width != $flt_line_width ) { $this->flt_line_width = $flt_line_width; $this->Out( sprintf( '%.2F w', $flt_line_width * $this->flt_scale_factor ) ); } // Restore font if ( $str_font_family ) { $this->SetFont( $str_font_family, $str_style, $int_font_size ); } // Restore colors if ( $this->str_draw_color != $str_draw_color ) { $this->str_draw_color = $str_draw_color; $this->Out( $str_draw_color ); } if ( $this->str_fill_color != $str_fill_color ) { $this->str_fill_color = $str_fill_color; $this->Out( $str_fill_color ); } $this->str_text_color = $str_text_color; $this->bol_fill_text_differ = $bol_fill_text_differ; } /** * */ public function Header() { // To be implemented in your own inherited class } /** * */ public function Footer() { // To be implemented in your own inherited class } /** * @return int */ public function PageNo() { // Get current page number return $this->int_page; } /** * @param $int_red * @param null $int_green * @param null $int_blue */ public function SetDrawColor( $int_red, $int_green = null, $int_blue = null ) { // Set color for all stroking operations if ( ( $int_red == 0 && $int_green == 0 && $int_blue == 0 ) || $int_green === null ) { $this->str_draw_color = sprintf( '%.3F G', $int_red / 255 ); } else { $this->str_draw_color = sprintf( '%.3F %.3F %.3F RG', $int_red / 255, $int_green / 255, $int_blue / 255 ); } if ( $this->int_page > 0 ) { $this->Out( $this->str_draw_color ); } } /** * @param $int_red * @param null $int_green * @param null $int_blue */ public function SetFillColor( $int_red, $int_green = null, $int_blue = null ) { // Set color for all filling operations if ( ( $int_red == 0 && $int_green == 0 && $int_blue == 0 ) || $int_green === null ) { $this->str_fill_color = sprintf( '%.3F g', $int_red / 255 ); } else { $this->str_fill_color = sprintf( '%.3F %.3F %.3F rg', $int_red / 255, $int_green / 255, $int_blue / 255 ); } $this->bol_fill_text_differ = ( $this->str_fill_color != $this->str_text_color ); if ( $this->int_page > 0 ) { $this->Out( $this->str_fill_color ); } } /** * @param $int_red * @param null $int_green * @param null $int_blue */ public function SetTextColor( $int_red, $int_green = null, $int_blue = null ) { // Set color for text if ( ( $int_red == 0 && $int_green == 0 && $int_blue == 0 ) || $int_green === null ) { $this->str_text_color = sprintf( '%.3F g', $int_red / 255 ); } else { $this->str_text_color = sprintf( '%.3F %.3F %.3F rg', $int_red / 255, $int_green / 255, $int_blue / 255 ); } $this->bol_fill_text_differ = ( $this->str_fill_color != $this->str_text_color ); } /** * @param $str_text * * @return float|int */ public function GetStringWidth( $str_text ) { // Get width of a string in the current font $str_text = (string) $str_text; $arr_character_width = &$this->arr_current_font_info['cw']; $flt_width = 0; if ( $this->bol_uniform_subset ) { $str_unicode = $this->UTF8StringToArray( $str_text ); foreach ( $str_unicode as $str_char ) { if ( isset( $arr_character_width[ $str_char ] ) ) { $flt_width += ( ord( $arr_character_width[ 2 * $str_char ] ) << 8 ) + ord( $arr_character_width[ 2 * $str_char + 1 ] ); } else { if ( $str_char > 0 && $str_char < 128 && isset( $arr_character_width[ chr( $str_char ) ] ) ) { $flt_width += $arr_character_width[ chr( $str_char ) ]; } else { if ( isset( $this->arr_current_font_info['desc']['MissingWidth'] ) ) { $flt_width += $this->arr_current_font_info['desc']['MissingWidth']; } else { if ( isset( $this->arr_current_font_info['MissingWidth'] ) ) { $flt_width += $this->arr_current_font_info['MissingWidth']; } else { $flt_width += 500; } } } } } } else { $int_length = strlen( $str_text ); for ( $i = 0; $i < $int_length; $i ++ ) { $flt_width += $arr_character_width[ $str_text[ $i ] ]; } } return $flt_width * $this->int_font_size_user / 1000; } /** * @param $flt_width */ public function SetLineWidth( $flt_width ) { // Set line width $this->flt_line_width = $flt_width; if ( $this->int_page > 0 ) { $this->Out( sprintf( '%.2F w', $flt_width * $this->flt_scale_factor ) ); } } /** * @param $flt_x_1 * @param $flt_y_1 * @param $flt_x_2 * @param $flt_y_2 */ public function Line( $flt_x_1, $flt_y_1, $flt_x_2, $flt_y_2 ) { // Draw a line $this->Out( sprintf( '%.2F %.2F m %.2F %.2F l S', $flt_x_1 * $this->flt_scale_factor, ( $this->flt_current_height - $flt_y_1 ) * $this->flt_scale_factor, $flt_x_2 * $this->flt_scale_factor, ( $this->flt_current_height - $flt_y_2 ) * $this->flt_scale_factor ) ); } /** * @param $flt_x * @param $flt_y * @param $flt_width * @param $flt_height * @param string $str_style */ public function Rect( $flt_x, $flt_y, $flt_width, $flt_height, $str_style = '' ) { // Draw a rectangle if ( $str_style == 'F' ) { $op = 'f'; } elseif ( $str_style == 'FD' || $str_style == 'DF' ) { $op = 'B'; } else { $op = 'S'; } $this->Out( sprintf( '%.2F %.2F %.2F %.2F re %s', $flt_x * $this->flt_scale_factor, ( $this->flt_current_height - $flt_y ) * $this->flt_scale_factor, $flt_width * $this->flt_scale_factor, - $flt_height * $this->flt_scale_factor, $op ) ); } /** * Add a unicode font to the document * * @param string $fontFamily The Font-Family name to be used in setFont method * @param string $fontStyle Font-Style of the font to be used in setFont method (B, I, U) * @param string $fontFile The relative font filename used with the set font path */ public function AddFont( $fontFamily, $fontStyle = '', $fontFile = '' ) { // Add a TrueType, OpenType or Type1 font $fontFamily = strtolower( $fontFamily ); $fontStyle = strtoupper( $fontStyle ); if ( $fontStyle == 'IB' ) { $fontStyle = 'BI'; } if ( $fontFile == '' ) { $fontFile = str_replace( ' ', '', $fontFamily ) . strtolower( $fontStyle ); if ( file_exists( $fontFile . '.ttf' ) ) { $fontFile .= '.ttf'; } else { $fontFile .= '.php'; } } $fontExtension = pathinfo( $fontFile, PATHINFO_EXTENSION ); $isUnicode = ( strtolower( $fontExtension ) != 'php' ); $fontKey = $fontFamily . $fontStyle; if ( isset( $this->arr_fonts[ $fontKey ] ) ) { return; } if ( $isUnicode ) { $fontFile = $this->getFontPath() . $fontFile; $fontMetrics = $this->getFontMetricFiles( $fontFile, $fontKey ); $int_font_count = count( $this->arr_fonts ) + 1; $arr_numbers = range( 0, ( ! empty( $this->str_alias_number_pages ) ) ? 57 : 32 ); $this->arr_fonts[ $fontKey ] = array_merge( $fontMetrics, [ 'i' => $int_font_count, 'ttffile' => $fontFile, 'subset' => $arr_numbers, 'filename' => pathinfo( $fontFile, PATHINFO_FILENAME ), ] ); $this->arr_font_files[ $fontKey ] = [ 'length1' => $fontMetrics['originalsize'], 'type' => self::FONT_TRUETYPE, 'ttffile' => $fontFile, ]; $this->arr_font_files[ $fontFile ] = [ 'type' => self::FONT_TRUETYPE ]; unset( $fontMetrics ); } else { $arr_info = $this->LoadFont( $fontFile ); $arr_info['i'] = count( $this->arr_fonts ) + 1; if ( ! empty( $arr_info['diff'] ) ) { // Search existing encodings $int_key = array_search( $arr_info['diff'], $this->arr_encoding_diffs ); if ( ! $int_key ) { $int_key = count( $this->arr_encoding_diffs ) + 1; $this->arr_encoding_diffs[ $int_key ] = $arr_info['diff']; } $arr_info['diffn'] = $int_key; } if ( ! empty( $arr_info['file'] ) ) { // Embedded font if ( $arr_info['type'] == 'TrueType' ) { $this->arr_font_files[ $arr_info['file'] ] = array( 'length1' => $arr_info['originalsize'] ); } else { $this->arr_font_files[ $arr_info['file'] ] = array( 'length1' => $arr_info['size1'], 'length2' => $arr_info['size2'] ); } } $this->arr_fonts[ $fontKey ] = $arr_info; } } /** * @param $fontFile * @param $fontKey * * @return array */ public function getFontMetricFiles( $fontFile, $fontKey ) { $ext = strrchr( $fontFile, '.' ); $baseName = basename( $fontFile, $ext ); $cachePath = $this->getCachePath(); $metricFile = $cachePath . self::FILE_FONT_METRICS . $baseName . '.json'; $charWidthFile = $cachePath . self::FILE_CHARACTER_WIDTH . $baseName . '.dat'; $oFileSize = filesize( $fontFile ); if ( $cachePath !== null && file_exists( $metricFile ) && file_exists( $charWidthFile ) ) { $fopen = fopen( $metricFile, 'r' ); $fread = fread( $fopen, filesize( $metricFile ) + 1 ); fclose( $fopen ); $json = json_decode( $fread, true ); if ( json_last_error() === JSON_ERROR_NONE ) { $fopen = fopen( $charWidthFile, 'r' ); $fread = fread( $fopen, filesize( $charWidthFile ) + 1 ); fclose( $fopen ); $json['cw'] = $fread; if ( $oFileSize == $json['originalsize'] ) { return $json; } } } $ttf = new WOE_FPDF_TT_Font_File(); $ttf->getMetrics( $fontFile ); $name = preg_replace( '/[ ()]/', '', $ttf->getFullName() ); $flt_underline_pos = round( $ttf->getUnderlinePosition() ); $flt_underline_thickness = round( $ttf->getUnderlineThickness() ); $characterWidths = $ttf->getCharWidths(); // Generate metrics array $strMetricsData = [ 'name' => $name, 'type' => self::FONT_TRUETYPE, 'desc' => [ 'Ascent' => round( $ttf->getAscent() ), 'Descent' => round( $ttf->getDescent() ), 'CapHeight' => round( $ttf->getCapHeight() ), 'Flags' => $ttf->getFlags(), 'FontBBox' => '[' . round( $ttf->getBbox()[0] ) . ' ' . round( $ttf->getBbox()[1] ) . ' ' . round( $ttf->getBbox()[2] ) . ' ' . round( $ttf->getBbox()[3] ) . ']', 'ItalicAngle' => $ttf->getItalicAngle(), 'StemV' => round( $ttf->getStemV() ), 'MissingWidth' => round( $ttf->getDefaultWidth() ), ], 'flt_underline_pos' => $flt_underline_pos, 'flt_underline_thickness' => $flt_underline_thickness, 'ttffile' => str_replace( __DIR__ . "/", "", $metricFile ), 'originalsize' => $oFileSize, 'fontkey' => $fontKey, 'cw' => null, ]; $cachePath = $this->getCachePath(); if ( $cachePath !== null ) { // write metrics file $json = json_encode( $strMetricsData ); $fopen = fopen( $metricFile, 'w' ); fwrite( $fopen, $json ); fclose( $fopen ); // write char width file $fopen = fopen( $charWidthFile, 'w' ); fwrite( $fopen, $characterWidths ); fclose( $fopen ); // wp_delete_file char width 127 file $charWidth127file = $cachePath . self::FILE_CHARACTER_WIDTH . $baseName . '.json'; if ( file_exists( $charWidth127file ) ) { @wp_delete_file( $charWidth127file ); } } $strMetricsData['cw'] = $characterWidths; unset( $ttf ); return $strMetricsData; } /** * @param $str_family * @param string $str_style * @param int $int_size */ public function SetFont( $str_family, $str_style = '', $int_size = 0 ) { // Select a font; size given in points if ( $str_family == '' ) { $str_family = $this->str_current_font_family; } else { $str_family = strtolower( $str_family ); } $str_style = strtoupper( $str_style ); if ( strpos( $str_style, 'U' ) !== false ) { $this->bol_underline = true; $str_style = str_replace( 'U', '', $str_style ); } else { $this->bol_underline = false; } if ( $str_style == 'IB' ) { $str_style = 'BI'; } if ( $int_size == 0 ) { $int_size = $this->int_current_font_size; } // Test if font is already selected if ( $this->str_current_font_family == $str_family && $this->str_current_font_style == $str_style && $this->int_current_font_size == $int_size ) { return; } // Test if font is already loaded $str_font_key = $str_family . $str_style; if ( ! isset( $this->arr_fonts[ $str_font_key ] ) ) { // Test if one of the core fonts if ( $str_family == 'arial' ) { $str_family = 'helvetica'; } if ( ! in_array( $str_family, $this->arr_core_fonts ) ) { throw new WOE_FPDF_Exception( esc_html('Undefined font: ' . $str_family . ' ' . $str_style), esc_html(WOE_FPDF_Exception::UNDEFINED_FONT) ); } if ( $str_family == 'symbol' || $str_family == 'zapfdingbats' ) { $str_style = ''; } $str_font_key = $str_family . $str_style; if ( ! isset( $this->arr_fonts[ $str_font_key ] ) ) { $this->AddFont( $str_family, $str_style ); } } // Select it $this->str_current_font_family = $str_family; $this->str_current_font_style = $str_style; $this->int_current_font_size = $int_size; $this->int_font_size_user = $int_size / $this->flt_scale_factor; $this->arr_current_font_info = &$this->arr_fonts[ $str_font_key ]; if ( $this->arr_fonts[ $str_font_key ]['type'] == self::FONT_TRUETYPE ) { $this->bol_uniform_subset = true; } else { $this->bol_uniform_subset = false; } if ( $this->int_page > 0 ) { $this->Out( sprintf( 'BT /F%d %.2F Tf ET', $this->arr_current_font_info['i'], $this->int_current_font_size ) ); } } /** * @param $int_size */ public function SetFontSize( $int_size ) { // Set font size in points if ( $this->int_current_font_size == $int_size ) { return; } $this->int_current_font_size = $int_size; $this->int_font_size_user = $int_size / $this->flt_scale_factor; if ( $this->int_page > 0 ) { $this->Out( sprintf( 'BT /F%d %.2F Tf ET', $this->arr_current_font_info['i'], $this->int_current_font_size ) ); } } /** * @return int */ public function AddLink() { // Create a new internal link $int_count = count( $this->arr_internal_links ) + 1; $this->arr_internal_links[ $int_count ] = array( 0, 0 ); return $int_count; } /** * @param $mix_link_key * @param int $int_y * @param int $int_page */ public function SetLink( $mix_link_key, $int_y = 0, $int_page = - 1 ) { // Set destination of internal link if ( $int_y == - 1 ) { $int_y = $this->flt_position_y; } if ( $int_page == - 1 ) { $int_page = $this->int_page; } $this->arr_internal_links[ $mix_link_key ] = array( $int_page, $int_y ); } /** * @param $flt_x * @param $flt_y * @param $flt_width * @param $flt_height * @param $mix_link_key */ public function Link( $flt_x, $flt_y, $flt_width, $flt_height, $mix_link_key ) { // Put a link on the page $this->arr_page_links[ $this->int_page ][] = array( $flt_x * $this->flt_scale_factor, $this->flt_height_points - $flt_y * $this->flt_scale_factor, $flt_width * $this->flt_scale_factor, $flt_height * $this->flt_scale_factor, $mix_link_key ); } /** * @param $flt_x * @param $flt_y * @param $str_text */ public function Text( $flt_x, $flt_y, $str_text ) { // Output a string if ( $this->bol_uniform_subset ) { $str_text_2 = '(' . $this->EscapeString( $this->UTF8ToUTF16BE( $str_text, false ) ) . ')'; foreach ( $this->UTF8StringToArray( $str_text ) as $uni ) { $this->arr_current_font_info['subset'][ $uni ] = $uni; } } else { $str_text_2 = '(' . $this->EscapeString( $str_text ) . ')'; } $str_output = sprintf( 'BT %.2F %.2F Td %s Tj ET', $flt_x * $this->flt_scale_factor, ( $this->flt_current_height - $flt_y ) * $this->flt_scale_factor, $str_text_2 ); if ( $this->bol_underline && $str_text != '' ) { $str_output .= ' ' . $this->DoUnderline( $flt_x, $flt_y, $str_text ); } if ( $this->bol_fill_text_differ ) { $str_output = 'q ' . $this->str_text_color . ' ' . $str_output . ' Q'; } $this->Out( $str_output ); } /** * @return bool */ public function AcceptPageBreak() { // Accept automatic page break or not return $this->bol_auto_page_break; } /** * @param float $flt_width * @param float $flt_height * @param string $str_text * @param int $int_border * @param int $int_line_number * @param string $str_alignment * @param bool $bol_fill * @param string $str_link */ public function Cell( $flt_width, $flt_height = 0, $str_text = '', $int_border = 0, $int_line_number = 0, $str_alignment = '', $bol_fill = false, $str_link = '' ) { // Output a cell $flt_scale = $this->flt_scale_factor; if ( $this->flt_position_y + $flt_height > $this->flt_page_break_trigger && ! $this->bol_in_header && ! $this->bol_in_footer && $this->AcceptPageBreak() ) { // Automatic page break $flt_position_x = $this->flt_position_x; $int_word_spacing = $this->int_word_spacing; if ( $int_word_spacing > 0 ) { $this->int_word_spacing = 0; $this->Out( '0 Tw' ); } $this->AddPage( $this->str_current_orientation, $this->arr_current_page_sizes ); $this->flt_position_x = $flt_position_x; if ( $int_word_spacing > 0 ) { $this->int_word_spacing = $int_word_spacing; $this->Out( sprintf( '%.3F Tw', $int_word_spacing * $flt_scale ) ); } } if ( $flt_width == 0 ) { $flt_width = $this->flt_current_width - $this->int_right_margin - $this->flt_position_x; } $str_output = ''; if ( $bol_fill || $int_border == 1 ) { if ( $bol_fill ) { $str_operation = ( $int_border == 1 ) ? 'B' : 'f'; } else { $str_operation = 'S'; } $str_output = sprintf( '%.2F %.2F %.2F %.2F re %s ', $this->flt_position_x * $flt_scale, ( $this->flt_current_height - $this->flt_position_y ) * $flt_scale, $flt_width * $flt_scale, - $flt_height * $flt_scale, $str_operation ); } if ( is_string( $int_border ) ) { $flt_position_x = $this->flt_position_x; $flt_position_y = $this->flt_position_y; if ( strpos( $int_border, 'L' ) !== false ) { $str_output .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $flt_position_x * $flt_scale, ( $this->flt_current_height - $flt_position_y ) * $flt_scale, $flt_position_x * $flt_scale, ( $this->flt_current_height - ( $flt_position_y + $flt_height ) ) * $flt_scale ); } if ( strpos( $int_border, 'T' ) !== false ) { $str_output .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $flt_position_x * $flt_scale, ( $this->flt_current_height - $flt_position_y ) * $flt_scale, ( $flt_position_x + $flt_width ) * $flt_scale, ( $this->flt_current_height - $flt_position_y ) * $flt_scale ); } if ( strpos( $int_border, 'R' ) !== false ) { $str_output .= sprintf( '%.2F %.2F m %.2F %.2F l S ', ( $flt_position_x + $flt_width ) * $flt_scale, ( $this->flt_current_height - $flt_position_y ) * $flt_scale, ( $flt_position_x + $flt_width ) * $flt_scale, ( $this->flt_current_height - ( $flt_position_y + $flt_height ) ) * $flt_scale ); } if ( strpos( $int_border, 'B' ) !== false ) { $str_output .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $flt_position_x * $flt_scale, ( $this->flt_current_height - ( $flt_position_y + $flt_height ) ) * $flt_scale, ( $flt_position_x + $flt_width ) * $flt_scale, ( $this->flt_current_height - ( $flt_position_y + $flt_height ) ) * $flt_scale ); } } if ( $str_text !== '' ) { if ( $str_alignment == 'R' ) { $flt_dimensions = $flt_width - $this->int_cell_margin - $this->GetStringWidth( $str_text ); } elseif ( $str_alignment == 'C' ) { $flt_dimensions = ( $flt_width - $this->GetStringWidth( $str_text ) ) / 2; } else { $flt_dimensions = $this->int_cell_margin; } if ( $this->bol_fill_text_differ ) { $str_output .= 'q ' . $this->str_text_color . ' '; } // If multibyte, Tw has no effect - do word spacing using an adjustment before each space if ( $this->int_word_spacing && $this->bol_uniform_subset ) { foreach ( $this->UTF8StringToArray( $str_text ) as $mix_unicode ) { $this->arr_current_font_info['subset'][ $mix_unicode ] = $mix_unicode; } $str_space = $this->EscapeString( $this->UTF8ToUTF16BE( ' ', false ) ); $str_output .= sprintf( 'BT 0 Tw %.2F %.2F Td [', ( $this->flt_position_x + $flt_dimensions ) * $flt_scale, ( $this->flt_current_height - ( $this->flt_position_y + .5 * $flt_height + .3 * $this->int_font_size_user ) ) * $flt_scale ); $arr_bits = explode( ' ', $str_text ); $int_bit_count = count( $arr_bits ); for ( $i = 0; $i < $int_bit_count; $i ++ ) { $str_converted_text = $arr_bits[ $i ]; $str_converted_text = '(' . $this->EscapeString( $this->UTF8ToUTF16BE( $str_converted_text, false ) ) . ')'; $str_output .= sprintf( '%s ', $str_converted_text ); if ( ( $i + 1 ) < $int_bit_count ) { $flt_adjustment = - ( $this->int_word_spacing * $this->flt_scale_factor ) * 1000 / $this->int_current_font_size; $str_output .= sprintf( '%d(%s) ', $flt_adjustment, $str_space ); } } $str_output .= '] TJ'; $str_output .= ' ET'; } else { if ( $this->bol_uniform_subset ) { $str_text_2 = '(' . $this->EscapeString( $this->UTF8ToUTF16BE( $str_text, false ) ) . ')'; foreach ( $this->UTF8StringToArray( $str_text ) as $mix_unicode ) { $this->arr_current_font_info['subset'][ $mix_unicode ] = $mix_unicode; } } else { $str_text_2 = '(' . str_replace( ')', '\\)', str_replace( '(', '\\(', str_replace( '\\', '\\\\', $str_text ) ) ) . ')'; } $str_output .= sprintf( 'BT %.2F %.2F Td %s Tj ET', ( $this->flt_position_x + $flt_dimensions ) * $flt_scale, ( $this->flt_current_height - ( $this->flt_position_y + .5 * $flt_height + .3 * $this->int_font_size_user ) ) * $flt_scale, $str_text_2 ); } if ( $this->bol_underline ) { $str_output .= ' ' . $this->DoUnderline( $this->flt_position_x + $flt_dimensions, $this->flt_position_y + .5 * $flt_height + .3 * $this->int_font_size_user, $str_text ); } if ( $this->bol_fill_text_differ ) { $str_output .= ' Q'; } if ( $str_link ) { $this->Link( $this->flt_position_x + $flt_dimensions, $this->flt_position_y + .5 * $flt_height - .5 * $this->int_font_size_user, $this->GetStringWidth( $str_text ), $this->int_font_size_user, $str_link ); } } if ( $str_output ) { $this->Out( $str_output ); } $this->flt_last_cell_height = $flt_height; if ( $int_line_number > 0 ) { // Go to next line $this->flt_position_y += $flt_height; if ( $int_line_number == 1 ) { $this->flt_position_x = $this->int_left_margin; } } else { $this->flt_position_x += $flt_width; } } /** * Output text with automatic or explicit line breaks * * @param float $width * @param float $height * @param string $text * @param int|string $border Indicates if borders must be drawn around the cell block. * The value can be either a number: 0 - no border, 1 - frame * or a string containing some or all of the following characters (in any order): L - left, 'T' - top, 'R' - right, 'B' - bottom * @param string $horizontal_alignment * @param string $vertical_alignment * @param bool $bol_fill * @param int $lines_limit * * @return string */ public function MultiCell( $width, $height, $text, $border = 0, $horizontal_alignment = 'J', $vertical_alignment = 'T', $bol_fill = false, $lines_limit = 0 ) { $row_height = 5; // was required for calculate char width // $arr_character_width = &$this->arr_current_font_info['cw']; /* is it really necessary? if ( $width == 0 ) { $width = $this->flt_current_width - $this->int_right_margin - $this->flt_position_x; } */ // define max width available for text $available_width = ( $width - 2 * $this->int_cell_margin ); // sanitize text $text = str_replace( "\r", '', $text ); // remove last char if it's line break if ( $this->bol_uniform_subset ) { $int_length = mb_strlen( $text, 'utf-8' ); while ( $int_length > 0 && mb_substr( $text, $int_length - 1, 1, 'utf-8' ) == "\n" ) { $int_length --; } } else { $int_length = strlen( $text ); if ( $int_length > 0 && $text[ $int_length - 1 ] == "\n" ) { $int_length --; } } $adjusted_border = ''; $border_between_cells = ''; if ( $border ) { if ( $border === 1 ) { $border = 'LTRB'; $adjusted_border = 'LRT'; $border_between_cells = 'LR'; } else { if ( strpos( $border, 'L' ) !== false ) { $border_between_cells .= 'L'; } if ( strpos( $border, 'R' ) !== false ) { $border_between_cells .= 'R'; } $adjusted_border = $border_between_cells; if ( strpos( $border, 'T' ) !== false ) { $adjusted_border = $border_between_cells . 'T'; } } } $cellCallback = function ( $text, $from, $to, $adjusted_border ) use ( $width, $row_height, $horizontal_alignment, $bol_fill ) { if ( $this->bol_uniform_subset ) { $this->Cell( $width, $row_height, mb_substr( $text, $from, $to - $from, 'UTF-8' ), $adjusted_border, 2, $horizontal_alignment, $bol_fill ); } else { $this->Cell( $width, $row_height, substr( $text, $from, $to - $from ), $adjusted_border, 2, $horizontal_alignment, $bol_fill ); } }; $simple_draw_queue = array(); $last_space_char_pos = - 1; $current_char_pos = 0; $last_line_break_pos = 0; $current_line_width = 0; $space_counter = 0; $line_count = 1; $width_until_last_space = 0; while ( $current_char_pos < $int_length ) { // Get next character if ( $this->bol_uniform_subset ) { $current_char = mb_substr( $text, $current_char_pos, 1, 'UTF-8' ); } else { $current_char = $text[ $current_char_pos ]; } if ( $current_char == "\n" ) { // Explicit line break if ( $this->int_word_spacing > 0 ) { $this->int_word_spacing = 0; $simple_draw_queue[] = array( array( $this, 'Out' ), array( '0 Tw' ) ); } $simple_draw_queue[] = array( $cellCallback, array( $text, $last_line_break_pos, $current_char_pos, $adjusted_border ) ); $current_char_pos ++; $last_space_char_pos = - 1; $last_line_break_pos = $current_char_pos; $current_line_width = 0; $space_counter = 0; $line_count ++; if ( $border && $line_count == 2 ) { $adjusted_border = $border_between_cells; } if ( $lines_limit && $line_count > $lines_limit ) { return substr( $text, $current_char_pos ); } continue; } if ( $current_char == ' ' ) { $last_space_char_pos = $current_char_pos; $width_until_last_space = $current_line_width; $space_counter ++; } // GetStringWidth() can calculate depends on $this->bol_uniform_subset $current_line_width += $this->GetStringWidth( $current_char ); /* if ( $this->bol_uniform_subset ) { $current_line_width += $this->GetStringWidth( $str_character ); } else { $current_line_width += $arr_character_width[ $str_character ] * $this->int_font_size_user / 1000; } */ if ( $current_line_width > $available_width ) { // Automatic line break if ( $last_space_char_pos == - 1 ) { if ( $current_char_pos == $last_line_break_pos ) { $current_char_pos ++; } if ( $this->int_word_spacing > 0 ) { $this->int_word_spacing = 0; $simple_draw_queue[] = array( array( $this, 'Out' ), array( '0 Tw' ) ); } $simple_draw_queue[] = array( $cellCallback, array( $text, $last_line_break_pos, $current_char_pos, $adjusted_border ) ); } else { if ( $horizontal_alignment == 'J' ) { $this->int_word_spacing = ( $space_counter > 1 ) ? ( $available_width - $width_until_last_space ) / ( $space_counter - 1 ) : 0; $simple_draw_queue[] = array( array( $this, 'Out' ), array( sprintf( '%.3F Tw', $this->int_word_spacing * $this->flt_scale_factor ) ) ); } $simple_draw_queue[] = array( $cellCallback, array( $text, $last_line_break_pos, $last_space_char_pos, $adjusted_border ) ); $current_char_pos = $last_space_char_pos + 1; } $last_space_char_pos = - 1; $last_line_break_pos = $current_char_pos; $current_line_width = 0; $space_counter = 0; $line_count ++; if ( $border && $line_count == 2 ) { $adjusted_border = $border_between_cells; } if ( $lines_limit && $line_count > $lines_limit ) { if ( $this->int_word_spacing > 0 ) { $this->int_word_spacing = 0; $simple_draw_queue[] = array( array( $this, 'Out' ), array( '0 Tw' ) ); } return substr( $text, $current_char_pos ); } } else { $current_char_pos ++; } } // Last chunk if ( $this->int_word_spacing > 0 ) { $this->int_word_spacing = 0; $simple_draw_queue[] = array( array( $this, 'Out' ), array( '0 Tw' ) ); } if ( $border && strpos( $border, 'B' ) !== false ) { $adjusted_border .= 'B'; } $simple_draw_queue[] = array( $cellCallback, array( $text, $last_line_break_pos, $current_char_pos, $adjusted_border ) ); // install y pos depends on vertical align if ( 'C' === $vertical_alignment ) { $this->flt_position_y += ( $height - $row_height * $line_count ) / 2; } elseif ( 'B' === $vertical_alignment ) { $this->flt_position_y += $height - $row_height * $line_count; } else { // do nothing for top vertical align } foreach ( $simple_draw_queue as $item ) { if ( isset( $item[0] ) && is_callable( $item[0] ) ) { $func = $item[0]; $args = isset( $item[1] ) ? $item[1] : array(); call_user_func_array( $func, $args ); } } $this->flt_position_x = $this->int_left_margin; return ''; } /** * @param float $flt_height * @param string $str_text * @param string $str_link */ public function Write( $flt_height, $str_text, $str_link = '' ) { // Output text in flowing mode $arr_character_widths = &$this->arr_current_font_info['cw']; $flt_width = $this->flt_current_width - $this->int_right_margin - $this->flt_position_x; $flt_max_width = ( $flt_width - 2 * $this->int_cell_margin ); $str_text = str_replace( "\r", '', $str_text ); if ( $this->bol_uniform_subset ) { $int_length = mb_strlen( $str_text, 'UTF-8' ); if ( $int_length == 1 && $str_text == " " ) { $this->flt_position_x += $this->GetStringWidth( $str_text ); return; } } else { $int_length = strlen( $str_text ); } $int_sep = - 1; $int_i = 0; $int_j = 0; $flt_string_width = 0; $int_line_count = 1; while ( $int_i < $int_length ) { // Get next character if ( $this->bol_uniform_subset ) { $str_character = mb_substr( $str_text, $int_i, 1, 'UTF-8' ); } else { $str_character = $str_text[ $int_i ]; } if ( $str_character == "\n" ) { // Explicit line break if ( $this->bol_uniform_subset ) { $this->Cell( $flt_width, $flt_height, mb_substr( $str_text, $int_j, $int_i - $int_j, 'UTF-8' ), 0, 2, '', 0, $str_link ); } else { $this->Cell( $flt_width, $flt_height, substr( $str_text, $int_j, $int_i - $int_j ), 0, 2, '', 0, $str_link ); } $int_i ++; $int_sep = - 1; $int_j = $int_i; $flt_string_width = 0; if ( $int_line_count == 1 ) { $this->flt_position_x = $this->int_left_margin; $flt_width = $this->flt_current_width - $this->int_right_margin - $this->flt_position_x; $flt_max_width = ( $flt_width - 2 * $this->int_cell_margin ); } $int_line_count ++; continue; } if ( $str_character == ' ' ) { $int_sep = $int_i; } if ( $this->bol_uniform_subset ) { $flt_string_width += $this->GetStringWidth( $str_character ); } else { $flt_string_width += $arr_character_widths[ $str_character ] * $this->int_font_size_user / 1000; } if ( $flt_string_width > $flt_max_width ) { // Automatic line break if ( $int_sep == - 1 ) { if ( $this->flt_position_x > $this->int_left_margin ) { // Move to next line $this->flt_position_x = $this->int_left_margin; $this->flt_position_y += $flt_height; $flt_width = $this->flt_current_width - $this->int_right_margin - $this->flt_position_x; $flt_max_width = ( $flt_width - 2 * $this->int_cell_margin ); $int_i ++; $int_line_count ++; continue; } if ( $int_i == $int_j ) { $int_i ++; } if ( $this->bol_uniform_subset ) { $this->Cell( $flt_width, $flt_height, mb_substr( $str_text, $int_j, $int_i - $int_j, 'UTF-8' ), 0, 2, '', 0, $str_link ); } else { $this->Cell( $flt_width, $flt_height, substr( $str_text, $int_j, $int_i - $int_j ), 0, 2, '', 0, $str_link ); } } else { if ( $this->bol_uniform_subset ) { $this->Cell( $flt_width, $flt_height, mb_substr( $str_text, $int_j, $int_sep - $int_j, 'UTF-8' ), 0, 2, '', 0, $str_link ); } else { $this->Cell( $flt_width, $flt_height, substr( $str_text, $int_j, $int_sep - $int_j ), 0, 2, '', 0, $str_link ); } $int_i = $int_sep + 1; } $int_sep = - 1; $int_j = $int_i; $flt_string_width = 0; if ( $int_line_count == 1 ) { $this->flt_position_x = $this->int_left_margin; $flt_width = $this->flt_current_width - $this->int_right_margin - $this->flt_position_x; $flt_max_width = ( $flt_width - 2 * $this->int_cell_margin ); } $int_line_count ++; } else { $int_i ++; } } // Last chunk if ( $int_i != $int_j ) { if ( $this->bol_uniform_subset ) { $this->Cell( $flt_string_width, $flt_height, mb_substr( $str_text, $int_j, $int_i - $int_j, 'UTF-8' ), 0, 0, '', 0, $str_link ); } else { $this->Cell( $flt_string_width, $flt_height, substr( $str_text, $int_j ), 0, 0, '', 0, $str_link ); } } } /** * @param float|null $flt_height */ public function Ln( $flt_height = null ) { // Line feed; default value is last cell height $this->flt_position_x = $this->int_left_margin; if ( $flt_height === null ) { $this->flt_position_y += $this->flt_last_cell_height; } else { $this->flt_position_y += $flt_height; } } /** * @param $str_file * @param float|null $flt_x * @param float|null $flt_y * @param int $int_width * @param int $int_height * @param string $str_type * @param string $str_link */ public function Image( $str_file, $flt_x = null, $flt_y = null, $int_width = 0, $int_height = 0, $str_type = '', $str_link = '' ) { // Put an image on the page if ( ! isset( $this->arr_images[ $str_file ] ) ) { // First use of this image, get info if ( $str_type == '' ) { $int_position = strrpos( $str_file, '.' ); if ( ! $int_position ) { throw new WOE_FPDF_Exception( esc_html('Image file has no extension and no type was specified: ' . $str_file), esc_html(WOE_FPDF_Exception::INVALID_IMAGE) ); } $str_type = substr( $str_file, $int_position + 1 ); } $str_type = strtolower( $str_type ); if ( $str_type == 'jpeg' ) { $str_type = 'jpg'; } $str_method = '_parse' . $str_type; if ( ! method_exists( $this, $str_method ) ) { throw new WOE_FPDF_Exception( esc_html('Unsupported image type: `' . $str_type . '`'), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } $arr_image_info = $this->$str_method( $str_file ); $arr_image_info['i'] = count( $this->arr_images ) + 1; $this->arr_images[ $str_file ] = $arr_image_info; } else { $arr_image_info = $this->arr_images[ $str_file ]; } // Automatic width and height calculation if needed if ( $int_width == 0 && $int_height == 0 ) { // Put image at 96 dpi $int_width = - 96; $int_height = - 96; } if ( $int_width < 0 ) { $int_width = - $arr_image_info['w'] * 72 / $int_width / $this->flt_scale_factor; } if ( $int_height < 0 ) { $int_height = - $arr_image_info['h'] * 72 / $int_height / $this->flt_scale_factor; } if ( $int_width == 0 ) { $int_width = $int_height * $arr_image_info['w'] / $arr_image_info['h']; } if ( $int_height == 0 ) { $int_height = $int_width * $arr_image_info['h'] / $arr_image_info['w']; } // Flowing mode if ( $flt_y === null ) { if ( $this->flt_position_y + $int_height > $this->flt_page_break_trigger && ! $this->bol_in_header && ! $this->bol_in_footer && $this->AcceptPageBreak() ) { // Automatic page break $flt_new_x_pos = $this->flt_position_x; $this->AddPage( $this->str_current_orientation, $this->arr_current_page_sizes ); $this->flt_position_x = $flt_new_x_pos; } $flt_y = $this->flt_position_y; $this->flt_position_y += $int_height; } if ( $flt_x === null ) { $flt_x = $this->flt_position_x; } $this->Out( sprintf( 'q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q', $int_width * $this->flt_scale_factor, $int_height * $this->flt_scale_factor, $flt_x * $this->flt_scale_factor, ( $this->flt_current_height - ( $flt_y + $int_height ) ) * $this->flt_scale_factor, $arr_image_info['i'] ) ); if ( $str_link ) { $this->Link( $flt_x, $flt_y, $int_width, $int_height, $str_link ); } } /** * @return mixed */ public function GetX() { // Get x position return $this->flt_position_x; } /** * @param $flt_position_x */ public function SetX( $flt_position_x ) { // Set x position if ( $flt_position_x >= 0 ) { $this->flt_position_x = $flt_position_x; } else { $this->flt_position_x = $this->flt_current_width + $flt_position_x; } } /** * @return float */ public function GetY() { // Get y position return $this->flt_position_y; } /** * @param $flt_position_y */ public function SetY( $flt_position_y ) { // Set y position and reset x $this->flt_position_x = $this->int_left_margin; if ( $flt_position_y >= 0 ) { $this->flt_position_y = $flt_position_y; } else { $this->flt_position_y = $this->flt_current_height + $flt_position_y; } } /** * @param $x * @param $y */ public function SetXY( $x, $y ) { // Set x and y positions $this->SetY( $y ); $this->SetX( $x ); } /** * @return string */ public function output() { // Output PDF to some destination if ( $this->int_state < self::DOCUMENT_STATE_TERMINATED ) { $this->Close(); } return $this->str_buffer; } /** * Set the font directory where font should be loaded front * * @param $fontPath string The font directory * * @return string */ public function setFontPath( $fontPath ) { if ( ! file_exists( $fontPath ) || ! is_dir( $fontPath ) ) { throw new WOE_FPDF_Exception( esc_html('Font path does not exist `' . $fontPath . '`'), esc_html(WOE_FPDF_Exception::INVALID_FONT_PATH) ); } $this->str_font_path = realpath( $fontPath ) . '/'; return $this->str_font_path; } /** * @return string */ protected function getFontPath() { return realpath( $this->str_font_path ) . '/'; } /** * */ protected function checkOutput() { if ( PHP_SAPI !== 'cli' ) { if ( headers_sent( $str_file, $int_line ) ) { throw new WOE_FPDF_Exception( esc_html("Some data has already been output, can't send PDF file, output started at " . $str_file . ":" . $int_line), esc_html(WOE_FPDF_Exception::HEADER_ALREADY_SENT) ); } } if ( ob_get_length() ) { // The output buffer is not empty if ( ! preg_match( '/^(\xEF\xBB\xBF)?\s*$/', ob_get_contents() ) ) { throw new WOE_FPDF_Exception( esc_html('Some data has already been output, can\'t send PDF file'), esc_html(WOE_FPDF_Exception::HEADER_ALREADY_SENT) ); } // It contains only a UTF-8 BOM and/or whitespace, let's clean it ob_clean(); } } /** * @param $mix_size * * @return array|string */ private function getPageSize( $mix_size ) { if ( is_string( $mix_size ) ) { $mix_size = strtolower( $mix_size ); if ( ! isset( $this->arr_standard_page_sizes[ $mix_size ] ) ) { throw new WOE_FPDF_Exception( esc_html('Invalid page size: ' . $mix_size), esc_html(WOE_FPDF_Exception::INVALID_PAGE_SIZE) ); } $a = $this->arr_standard_page_sizes[ $mix_size ]; return array( $a[0] / $this->flt_scale_factor, $a[1] / $this->flt_scale_factor ); } else { if ( $mix_size[0] > $mix_size[1] ) { return array( $mix_size[1], $mix_size[0] ); } else { return $mix_size; } } } /** * @param $str_orientation * @param $mix_size */ private function BeginPage( $str_orientation, $mix_size ) { $this->int_page ++; $this->arr_pages[ $this->int_page ] = ''; $this->int_state = self::DOCUMENT_STATE_CREATING; $this->flt_position_x = $this->int_left_margin; $this->flt_position_y = $this->int_top_margin; $this->str_current_font_family = ''; // Check page size and orientation if ( $str_orientation == '' ) { $str_orientation = $this->str_default_orientation; } else { $str_orientation = strtoupper( $str_orientation[0] ); } if ( $mix_size == '' ) { $mix_size = $this->arr_default_page_sizes; } else { $mix_size = $this->getPageSize( $mix_size ); } if ( $str_orientation != $this->str_current_orientation || $mix_size[0] != $this->arr_current_page_sizes[0] || $mix_size[1] != $this->arr_current_page_sizes[1] ) { // New size or orientation if ( $str_orientation == self::ORIENTATION_PORTRAIT ) { $this->flt_current_width = $mix_size[0]; $this->flt_current_height = $mix_size[1]; } else { // landscape $this->flt_current_width = $mix_size[1]; $this->flt_current_height = $mix_size[0]; } $this->flt_width_points = $this->flt_current_width * $this->flt_scale_factor; $this->flt_height_points = $this->flt_current_height * $this->flt_scale_factor; $this->flt_page_break_trigger = $this->flt_current_height - $this->int_break_margin; $this->str_current_orientation = $str_orientation; $this->arr_current_page_sizes = $mix_size; } if ( $str_orientation != $this->str_default_orientation || $mix_size[0] != $this->arr_default_page_sizes[0] || $mix_size[1] != $this->arr_default_page_sizes[1] ) { $this->arr_page_sizes[ $this->int_page ] = array( $this->flt_width_points, $this->flt_height_points ); } } /** * */ private function EndPage() { $this->int_state = self::DOCUMENT_STATE_INITIALIZED; } /** * @param $str_font * * @return array */ private function LoadFont( $str_font ) { // Load a font definition file from the font directory include( $this->str_font_path . $str_font ); $arr_defined_vars = get_defined_vars(); if ( ! isset( $arr_defined_vars['name'] ) ) { throw new WOE_FPDF_Exception( esc_html('Could not include font definition file'), esc_html(WOE_FPDF_Exception::INVALID_FONT_FILE) ); } return $arr_defined_vars; } /** * @param $str_text * * @return mixed */ protected function EscapeString( $str_text ) { // Escape special characters in strings $str_text = str_replace( '\\', '\\\\', $str_text ); $str_text = str_replace( '(', '\\(', $str_text ); $str_text = str_replace( ')', '\\)', $str_text ); $str_text = str_replace( "\r", '\\r', $str_text ); return $str_text; } /** * @param $s * * @return string */ protected function TextString( $s ) { // Format a text string return '(' . $this->EscapeString( $s ) . ')'; } /** * @param $str_text * * @return string */ private function UTF8toUTF16( $str_text ) { // Convert UTF-8 to UTF-16BE with BOM $str_res = "\xFE\xFF"; $int_length = strlen( $str_text ); $i = 0; while ( $i < $int_length ) { $int_character_1 = ord( $str_text[ $i ++ ] ); if ( $int_character_1 >= 224 ) { // 3-byte character $int_character_2 = ord( $str_text[ $i ++ ] ); $int_character_3 = ord( $str_text[ $i ++ ] ); $str_res .= chr( ( ( $int_character_1 & 0x0F ) << 4 ) + ( ( $int_character_2 & 0x3C ) >> 2 ) ); $str_res .= chr( ( ( $int_character_2 & 0x03 ) << 6 ) + ( $int_character_3 & 0x3F ) ); } elseif ( $int_character_1 >= 192 ) { // 2-byte character $int_character_2 = ord( $str_text[ $i ++ ] ); $str_res .= chr( ( $int_character_1 & 0x1C ) >> 2 ); $str_res .= chr( ( ( $int_character_1 & 0x03 ) << 6 ) + ( $int_character_2 & 0x3F ) ); } else { // Single-byte character $str_res .= "\0" . chr( $int_character_1 ); } } return $str_res; } private function DoUnderline( $flt_x, $flt_y, $str_text ) { // Underline text $flt_underline_position = $this->arr_current_font_info['up']; $flt_underline_thickness = $this->arr_current_font_info['ut']; $flt_width = $this->GetStringWidth( $str_text ) + $this->int_word_spacing * substr_count( $str_text, ' ' ); return sprintf( '%.2F %.2F %.2F %.2F re f', $flt_x * $this->flt_scale_factor, ( $this->flt_current_height - ( $flt_y - $flt_underline_position / 1000 * $this->int_font_size_user ) ) * $this->flt_scale_factor, $flt_width * $this->flt_scale_factor, - $flt_underline_thickness / 1000 * $this->int_current_font_size ); } /** * @param $str_file * * @return array */ private function _parsejpg( $str_file ) { // Extract info from a JPEG file $arr_size_data = getimagesize( $str_file ); if ( ! $arr_size_data ) { throw new WOE_FPDF_Exception( esc_html('Missing or incorrect image file: ' . $str_file), esc_html(WOE_FPDF_Exception::INVALID_IMAGE) ); } if ( $arr_size_data[2] != 2 ) { throw new WOE_FPDF_Exception( 'Not a JPEG file: ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } if ( ! isset( $arr_size_data['channels'] ) || $arr_size_data['channels'] == 3 ) { $str_color_space = 'DeviceRGB'; } elseif ( $arr_size_data['channels'] == 4 ) { $str_color_space = 'DeviceCMYK'; } else { $str_color_space = 'DeviceGray'; } $int_bits_per_component = isset( $arr_size_data['bits'] ) ? $arr_size_data['bits'] : 8; $str_data = file_get_contents( $str_file ); return array( 'w' => $arr_size_data[0], 'h' => $arr_size_data[1], 'cs' => $str_color_space, 'bpc' => $int_bits_per_component, 'f' => 'DCTDecode', 'data' => $str_data ); } /** * @param $str_file * * @return array */ private function _parsewebp($str_file) { $objImage = imagecreatefromwebp($str_file); $strTmp = tempnam('.', 'png'); if (!$strTmp) { throw new WOE_FPDF_Exception( esc_html('Unable to create a temporary file'), esc_html(WOE_FPDF_Exception::IMAGE_NOT_WRITABLE)); } if (!imagepng($objImage, $strTmp)) { throw new WOE_FPDF_Exception( esc_html('Error while saving to temporary file'), esc_html(WOE_FPDF_Exception::IMAGE_NOT_WRITABLE)); } imagedestroy($objImage); $arrInfo = $this->_parsepng($strTmp); wp_delete_file($strTmp); return $arrInfo; } private function _parseavif($str_file) { $arrInfo = array(); if (function_exists('imagecreatefromavif')) { $objImage = imagecreatefromavif($str_file); $strTmp = tempnam('.', 'png'); if (!$strTmp) { throw new WOE_FPDF_Exception( esc_html('Unable to create a temporary file'), esc_html(WOE_FPDF_Exception::IMAGE_NOT_WRITABLE) ); } if (!imagepng($objImage, $strTmp)) { throw new WOE_FPDF_Exception( esc_html('Error while saving to temporary file'), esc_html(WOE_FPDF_Exception::IMAGE_NOT_WRITABLE) ); } imagedestroy($objImage); $arrInfo = $this->_parsepng($strTmp); wp_delete_file($strTmp); } return $arrInfo; } /** * @param $str_file * * @return array */ private function _parsepng( $str_file ) { // Extract info from a PNG file $ptr_file = fopen( $str_file, 'rb' ); if ( ! $ptr_file ) { throw new WOE_FPDF_Exception( esc_html('Can\'t open image file: ' . $str_file), esc_html(WOE_FPDF_Exception::INVALID_IMAGE) ); } $arr_info = $this->_parsepngstream( $ptr_file, $str_file ); fclose( $ptr_file ); return $arr_info; } /** * @param $ptr_file * @param $str_file * * @return array */ private function _parsepngstream( $ptr_file, $str_file ) { // Check signature if ( $this->_readstream( $ptr_file, 8 ) != chr( 137 ) . 'PNG' . chr( 13 ) . chr( 10 ) . chr( 26 ) . chr( 10 ) ) { throw new WOE_FPDF_Exception( esc_html('Not a PNG file: ' . $str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } // Read header chunk $this->_readstream( $ptr_file, 4 ); if ( $this->_readstream( $ptr_file, 4 ) != 'IHDR' ) { throw new WOE_FPDF_Exception( esc_html('Incorrect PNG file: ' . $str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } $int_width = $this->_readint( $ptr_file ); $int_height = $this->_readint( $ptr_file ); $int_bits_per_component = ord( $this->_readstream( $ptr_file, 1 ) ); if ( $int_bits_per_component > 8 ) { throw new WOE_FPDF_Exception( esc_html('16-bit depth not supported: ' . $str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } $int_color_channels = ord( $this->_readstream( $ptr_file, 1 ) ); if ( $int_color_channels == 0 || $int_color_channels == 4 ) { $str_color_space = 'DeviceGray'; } elseif ( $int_color_channels == 2 || $int_color_channels == 6 ) { $str_color_space = 'DeviceRGB'; } elseif ( $int_color_channels == 3 ) { $str_color_space = 'Indexed'; } else { throw new WOE_FPDF_Exception( 'Unknown color type: ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } if ( ord( $this->_readstream( $ptr_file, 1 ) ) != 0 ) { throw new WOE_FPDF_Exception( 'Unknown compression method: ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } if ( ord( $this->_readstream( $ptr_file, 1 ) ) != 0 ) { throw new WOE_FPDF_Exception( 'Unknown filter method: ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } if ( ord( $this->_readstream( $ptr_file, 1 ) ) != 0 ) { throw new WOE_FPDF_Exception( 'Interlacing not supported: ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } $this->_readstream( $ptr_file, 4 ); $str_predictor = '/Predictor 15 /Colors ' . ( $str_color_space == 'DeviceRGB' ? 3 : 1 ) . ' /BitsPerComponent ' . $int_bits_per_component . ' /Columns ' . $int_width; // Scan chunks looking for palette, transparency and image data $str_palette = ''; $arr_transparency_data = ''; $str_data = ''; do { $int_line = $this->_readint( $ptr_file ); $str_type = $this->_readstream( $ptr_file, 4 ); if ( $str_type == 'PLTE' ) { // Read palette $str_palette = $this->_readstream( $ptr_file, $int_line ); $this->_readstream( $ptr_file, 4 ); } elseif ( $str_type == 'tRNS' ) { // Read transparency info $str_transparency = $this->_readstream( $ptr_file, $int_line ); if ( $int_color_channels == 0 ) { $arr_transparency_data = array( ord( substr( $str_transparency, 1, 1 ) ) ); } elseif ( $int_color_channels == 2 ) { $arr_transparency_data = array( ord( substr( $str_transparency, 1, 1 ) ), ord( substr( $str_transparency, 3, 1 ) ), ord( substr( $str_transparency, 5, 1 ) ) ); } else { $int_pos = strpos( $str_transparency, chr( 0 ) ); if ( $int_pos !== false ) { $arr_transparency_data = array( $int_pos ); } } $this->_readstream( $ptr_file, 4 ); } elseif ( $str_type == 'IDAT' ) { // Read image data block $str_data .= $this->_readstream( $ptr_file, $int_line ); $this->_readstream( $ptr_file, 4 ); } elseif ( $str_type == 'IEND' ) { break; } else { $this->_readstream( $ptr_file, $int_line + 4 ); } } while ( $int_line ); if ( $str_color_space == 'Indexed' && empty( $str_palette ) ) { throw new WOE_FPDF_Exception( 'Missing palette in ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::UNSUPPORTED_IMAGE) ); } $arr_info = array( 'w' => $int_width, 'h' => $int_height, 'cs' => $str_color_space, 'bpc' => $int_bits_per_component, 'f' => 'FlateDecode', 'dp' => $str_predictor, 'pal' => $str_palette, 'trns' => $arr_transparency_data ); if ( $int_color_channels >= 4 ) { // Extract alpha channel if ( ! function_exists( 'gzuncompress' ) ) { throw new WOE_FPDF_Exception( 'Zlib not available, can\'t handle alpha channel: ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::EXTENSION_NOT_AVAILABLE) ); } $str_data = gzuncompress( $str_data ); $str_color = ''; $str_alpha = ''; if ( $int_color_channels == 4 ) { // Gray image $int_length = 2 * $int_width; for ( $i = 0; $i < $int_height; $i ++ ) { $int_pos = ( 1 + $int_length ) * $i; $str_color .= $str_data[ $int_pos ]; $str_alpha .= $str_data[ $int_pos ]; $str_line = substr( $str_data, $int_pos + 1, $int_length ); $str_color .= preg_replace( '/(.)./s', '$1', $str_line ); $str_alpha .= preg_replace( '/.(.)/s', '$1', $str_line ); } } else { // RGB image $int_length = 4 * $int_width; for ( $i = 0; $i < $int_height; $i ++ ) { $int_pos = ( 1 + $int_length ) * $i; $str_color .= $str_data[ $int_pos ]; $str_alpha .= $str_data[ $int_pos ]; $str_line = substr( $str_data, $int_pos + 1, $int_length ); $str_color .= preg_replace( '/(.{3})./s', '$1', $str_line ); $str_alpha .= preg_replace( '/.{3}(.)/s', '$1', $str_line ); } } unset( $str_data ); $str_data = gzcompress( $str_color ); $arr_info['smask'] = gzcompress( $str_alpha ); if ( $this->str_pdf_version < '1.4' ) { $this->str_pdf_version = '1.4'; } } $arr_info['data'] = $str_data; return $arr_info; } /** * @param $ptr_file * @param $int_bytes * * @return string */ private function _readstream( $ptr_file, $int_bytes ) { // Read n bytes from stream $str_result = ''; while ( $int_bytes > 0 && ! feof( $ptr_file ) ) { $str_data = fread( $ptr_file, $int_bytes ); if ( $str_data === false ) { throw new WOE_FPDF_Exception( 'Error while reading stream', esc_html(WOE_FPDF_Exception::INVALID_STREAM) ); } $int_bytes -= strlen( $str_data ); $str_result .= $str_data; } if ( $int_bytes > 0 ) { throw new WOE_FPDF_Exception( 'Unexpected end of stream', esc_html(WOE_FPDF_Exception::INVALID_STREAM) ); } return $str_result; } /** * @param $ptr_file * * @return mixed */ private function _readint( $ptr_file ) { // Read a 4-byte integer from stream $arr_data = unpack( 'Ni', $this->_readstream( $ptr_file, 4 ) ); return $arr_data['i']; } /** * @param $str_file * * @return array */ private function _parsegif( $str_file ) { // Extract info from a GIF file (via PNG conversion) if ( ! function_exists( 'imagepng' ) ) { throw new WOE_FPDF_Exception( 'GD extension is required for GIF support', esc_html(WOE_FPDF_Exception::EXTENSION_NOT_AVAILABLE) ); } if ( ! function_exists( 'imagecreatefromgif' ) ) { throw new WOE_FPDF_Exception( 'GD has no GIF read support', esc_html(WOE_FPDF_Exception::EXTENSION_NOT_AVAILABLE) ); } $obj_image = imagecreatefromgif( $str_file ); if ( ! $obj_image ) { throw new WOE_FPDF_Exception( 'Missing or incorrect image file: ' . esc_html($str_file), esc_html(WOE_FPDF_Exception::INVALID_IMAGE) ); } imageinterlace( $obj_image, 0 ); $ptr_file = @fopen( 'php://temp', 'rb+' ); if ( $ptr_file ) { // Perform conversion in memory ob_start(); imagepng( $obj_image ); $str_data = ob_get_clean(); imagedestroy( $obj_image ); fwrite( $ptr_file, $str_data ); rewind( $ptr_file ); $arr_info = $this->_parsepngstream( $ptr_file, $str_file ); fclose( $ptr_file ); } else { // Use temporary file $str_tmp = tempnam( '.', 'gif' ); if ( ! $str_tmp ) { throw new WOE_FPDF_Exception( 'Unable to create a temporary file', esc_html(WOE_FPDF_Exception::IMAGE_NOT_WRITABLE) ); } if ( ! imagepng( $obj_image, $str_tmp ) ) { throw new WOE_FPDF_Exception( 'Error while saving to temporary file', esc_html(WOE_FPDF_Exception::IMAGE_NOT_WRITABLE) ); } imagedestroy( $obj_image ); $arr_info = $this->_parsepng( $str_tmp ); wp_delete_file( $str_tmp ); } return $arr_info; } /** * Creates a new object */ protected function NewObject() { $this->int_current_object ++; $this->arr_offsets[ $this->int_current_object ] = strlen( $this->str_buffer ); $this->Out( $this->int_current_object . ' 0 obj' ); } /** * @param $str_data */ protected function PutStream( $str_data ) { $this->Out( 'stream' ); $this->Out( $str_data ); $this->Out( 'endstream' ); } /** * @param $str_data */ protected function Out( $str_data ) { // Add a line to the document if ( $this->int_state == self::DOCUMENT_STATE_CREATING ) { $this->arr_pages[ $this->int_page ] .= $str_data . "\n"; } else { $this->str_buffer .= $str_data . "\n"; } } /** * Puts the pages into the document */ private function PutPages() { $int_page = $this->int_page; if ( ! empty( $this->str_alias_number_pages ) ) { // Replace number of pages in fonts using subsets $str_alias = $this->UTF8ToUTF16BE( $this->str_alias_number_pages, false ); $str_replace = $this->UTF8ToUTF16BE( "$int_page", false ); for ( $n = 1; $n <= $int_page; $n ++ ) { $this->arr_pages[ $n ] = str_replace( $str_alias, $str_replace, $this->arr_pages[ $n ] ); } // Now repeat for no pages in non-subset fonts for ( $n = 1; $n <= $int_page; $n ++ ) { $this->arr_pages[ $n ] = str_replace( $this->str_alias_number_pages, $int_page, $this->arr_pages[ $n ] ); } } if ( $this->str_default_orientation == self::ORIENTATION_PORTRAIT ) { $flt_page_width = $this->arr_default_page_sizes[0] * $this->flt_scale_factor; $flt_page_height = $this->arr_default_page_sizes[1] * $this->flt_scale_factor; } else { $flt_page_width = $this->arr_default_page_sizes[1] * $this->flt_scale_factor; $flt_page_height = $this->arr_default_page_sizes[0] * $this->flt_scale_factor; } $str_filter = ( $this->bol_compress ) ? '/Filter /FlateDecode ' : ''; for ( $n = 1; $n <= $int_page; $n ++ ) { // Page $this->NewObject(); $this->Out( '<Out( '/Parent 1 0 R' ); if ( isset( $this->arr_page_sizes[ $n ] ) ) { $this->Out( sprintf( '/MediaBox [0 0 %.2F %.2F]', $this->arr_page_sizes[ $n ][0], $this->arr_page_sizes[ $n ][1] ) ); } $this->Out( '/Resources 2 0 R' ); if ( isset( $this->arr_page_links[ $n ] ) ) { // Links $str_annotations = '/Annots ['; foreach ( $this->arr_page_links[ $n ] as $arr_page_link ) { $str_rectangle = sprintf( '%.2F %.2F %.2F %.2F', $arr_page_link[0], $arr_page_link[1], $arr_page_link[0] + $arr_page_link[2], $arr_page_link[1] - $arr_page_link[3] ); $str_annotations .= '<TextString( $arr_page_link[4] ) . '>>>>'; } else { $arr_link = $this->arr_internal_links[ $arr_page_link[4] ]; $flt_height = isset( $this->arr_page_sizes[ $arr_link[0] ] ) ? $this->arr_page_sizes[ $arr_link[0] ][1] : $flt_page_height; $str_annotations .= sprintf( '/Dest [%d 0 R /XYZ 0 %.2F null]>>', 1 + 2 * $arr_link[0], $flt_height - $arr_link[1] * $this->flt_scale_factor ); } } $this->Out( $str_annotations . ']' ); } if ( $this->str_pdf_version > '1.3' ) { $this->Out( '/Group <>' ); } $this->Out( '/Contents ' . ( $this->int_current_object + 1 ) . ' 0 R>>' ); $this->Out( 'endobj' ); // Page content $str_page_content = ( $this->bol_compress ) ? gzcompress( $this->arr_pages[ $n ] ) : $this->arr_pages[ $n ]; $this->NewObject(); $this->Out( '<<' . $str_filter . '/Length ' . strlen( $str_page_content ) . '>>' ); $this->PutStream( $str_page_content ); $this->Out( 'endobj' ); } // Pages root $this->arr_offsets[1] = strlen( $this->str_buffer ); $this->Out( '1 0 obj' ); $this->Out( '<Out( $str_kids . ']' ); $this->Out( '/Count ' . $int_page ); $this->Out( sprintf( '/MediaBox [0 0 %.2F %.2F]', $flt_page_width, $flt_page_height ) ); $this->Out( '>>' ); $this->Out( 'endobj' ); } /** * */ public function PutFonts() { $int_current_object = $this->int_current_object; foreach ( $this->arr_encoding_diffs as $str_diff ) { // Encodings $this->NewObject(); $this->Out( '<>' ); $this->Out( 'endobj' ); } foreach ( $this->arr_font_files as $str_file => $arr_info ) { if ( ! isset( $arr_info['type'] ) || $arr_info['type'] != self::FONT_TRUETYPE ) { // Font file embedding $this->NewObject(); $this->arr_font_files[ $str_file ]['n'] = $this->int_current_object; $str_font = $this->readFontFile( $this->getFontPath() . $str_file ); $bol_compressed_file = ( substr( $str_file, - 2 ) == '.z' ); if ( ! $bol_compressed_file && isset( $arr_info['length2'] ) ) { $bol_header = ( ord( $str_font[0] ) == 128 ); if ( $bol_header ) { // Strip first binary header $str_font = substr( $str_font, 6 ); } if ( $bol_header && ord( $str_font[ $arr_info['length1'] ] ) == 128 ) { // Strip second binary header $str_font = substr( $str_font, 0, $arr_info['length1'] ) . substr( $str_font, $arr_info['length1'] + 6 ); } } $this->Out( '<Out( '/Filter /FlateDecode' ); } $this->Out( '/Length1 ' . $arr_info['length1'] ); if ( isset( $arr_info['length2'] ) ) { $this->Out( '/Length2 ' . $arr_info['length2'] . ' /Length3 0' ); } $this->Out( '>>' ); $this->PutStream( $str_font ); $this->Out( 'endobj' ); } } foreach ( $this->arr_fonts as $str_key => $arr_font_data ) { // Font objects //$this->fonts[$k]['n']=$this->n+1; $str_type = $arr_font_data['type']; $str_name = $arr_font_data['name']; if ( $str_type == 'Core' ) { // Standard font $this->arr_fonts[ $str_key ]['n'] = $this->int_current_object + 1; $this->NewObject(); $this->Out( '<Out( '/BaseFont /' . $str_name ); $this->Out( '/Subtype /Type1' ); if ( $str_name != 'Symbol' && $str_name != 'ZapfDingbats' ) { $this->Out( '/Encoding /WinAnsiEncoding' ); } $this->Out( '>>' ); $this->Out( 'endobj' ); } elseif ( $str_type == 'Type1' || $str_type == 'TrueType' ) { // Additional Type1 or TrueType font $this->arr_fonts[ $str_key ]['n'] = $this->int_current_object + 1; $this->NewObject(); $this->Out( '<Out( '/BaseFont /' . $str_name ); $this->Out( '/Subtype /' . $str_type ); $this->Out( '/FirstChar 32 /LastChar 255' ); $this->Out( '/Widths ' . ( $this->int_current_object + 1 ) . ' 0 R' ); $this->Out( '/FontDescriptor ' . ( $this->int_current_object + 2 ) . ' 0 R' ); if ( $arr_font_data['enc'] ) { if ( isset( $arr_font_data['diff'] ) ) { $this->Out( '/Encoding ' . ( $int_current_object + $arr_font_data['diff'] ) . ' 0 R' ); } else { $this->Out( '/Encoding /WinAnsiEncoding' ); } } $this->Out( '>>' ); $this->Out( 'endobj' ); // Widths $this->NewObject(); $int_character_width =& $arr_font_data['cw']; $str_data = '['; for ( $i = 32; $i <= 255; $i ++ ) { $str_data .= $int_character_width[ chr( $i ) ] . ' '; } $this->Out( $str_data . ']' ); $this->Out( 'endobj' ); // Descriptor $this->NewObject(); $str_data = '< $str_value ) { $str_data .= ' /' . $str_inner_key . ' ' . $str_value; } $str_file = $arr_font_data['file']; if ( $str_file ) { $str_data .= ' /FontFile' . ( $str_type == 'Type1' ? '' : '2' ) . ' ' . $this->arr_font_files[ $str_file ]['n'] . ' 0 R'; } $this->Out( $str_data . '>>' ); $this->Out( 'endobj' ); } // TrueType embedded SUBSETS or FULL else { if ( $str_type === self::FONT_TRUETYPE ) { $this->arr_fonts[ $str_key ]['n'] = $this->int_current_object + 1; $obj_ttf = new WOE_FPDF_TT_Font_File(); $str_font_name = 'MPDFAA' . '+' . $arr_font_data['name']; $str_subset = $arr_font_data['subset']; unset( $str_subset[0] ); $str_ttf_font_stream = $obj_ttf->makeSubset( $arr_font_data['ttffile'], $str_subset ); $int_ttf_font_size = strlen( $str_ttf_font_stream ); $str_font_stream_compressed = gzcompress( $str_ttf_font_stream ); $str_code_to_glyph = $obj_ttf->getCodeToGlyph(); unset( $str_code_to_glyph[0] ); // Type0 Font // A composite font - a font composed of other fonts, organized hierarchically $this->NewObject(); $this->Out( '<Out( '/Subtype /Type0' ); $this->Out( '/BaseFont /' . $str_font_name . '' ); $this->Out( '/Encoding /Identity-H' ); $this->Out( '/DescendantFonts [' . ( $this->int_current_object + 1 ) . ' 0 R]' ); $this->Out( '/ToUnicode ' . ( $this->int_current_object + 2 ) . ' 0 R' ); $this->Out( '>>' ); $this->Out( 'endobj' ); // CIDFontType2 // A CIDFont whose glyph descriptions are based on TrueType font technology $this->NewObject(); $this->Out( '<Out( '/Subtype /CIDFontType2' ); $this->Out( '/BaseFont /' . $str_font_name . '' ); $this->Out( '/CIDSystemInfo ' . ( $this->int_current_object + 2 ) . ' 0 R' ); $this->Out( '/FontDescriptor ' . ( $this->int_current_object + 3 ) . ' 0 R' ); if ( isset( $arr_font_data['desc']['MissingWidth'] ) ) { $this->Out( '/DW ' . $arr_font_data['desc']['MissingWidth'] . '' ); } $this->PutTTFontWidths( $arr_font_data, $obj_ttf->getMaxUni() ); $this->Out( '/CIDToGIDMap ' . ( $this->int_current_object + 4 ) . ' 0 R' ); $this->Out( '>>' ); $this->Out( 'endobj' ); // ToUnicode $this->NewObject(); $str_to_unicode = "/CIDInit /ProcSet findresource begin\n"; $str_to_unicode .= "12 dict begin\n"; $str_to_unicode .= "begincmap\n"; $str_to_unicode .= "/CIDSystemInfo\n"; $str_to_unicode .= "<Out( '<>' ); $this->PutStream( $str_to_unicode ); $this->Out( 'endobj' ); // CIDSystemInfo dictionary $this->NewObject(); $this->Out( '<Out( '/Ordering (UCS)' ); $this->Out( '/Supplement 0' ); $this->Out( '>>' ); $this->Out( 'endobj' ); // Font descriptor $this->NewObject(); $this->Out( '<Out( '/FontName /' . $str_font_name ); foreach ( $arr_font_data['desc'] as $str_inner_inner_key => $str_value ) { if ( $str_inner_inner_key == 'Flags' ) { $str_value = $str_value | 4; $str_value = $str_value & ~32; } // SYMBOLIC font flag $this->Out( ' /' . $str_inner_inner_key . ' ' . $str_value ); } $this->Out( '/FontFile2 ' . ( $this->int_current_object + 2 ) . ' 0 R' ); $this->Out( '>>' ); $this->Out( 'endobj' ); // Embed CIDToGIDMap // A specification of the mapping from CIDs to glyph indices $str_cid_to_gid_map = str_pad( '', 256 * 256 * 2, "\x00" ); foreach ( $str_code_to_glyph as $cc => $str_clyph ) { $str_cid_to_gid_map[ $cc * 2 ] = chr( $str_clyph >> 8 ); $str_cid_to_gid_map[ $cc * 2 + 1 ] = chr( $str_clyph & 0xFF ); } $str_cid_to_gid_map = gzcompress( $str_cid_to_gid_map ); $this->NewObject(); $this->Out( '<Out( '/Filter /FlateDecode' ); $this->Out( '>>' ); $this->PutStream( $str_cid_to_gid_map ); $this->Out( 'endobj' ); //Font file $this->NewObject(); $this->Out( '<Out( '/Filter /FlateDecode' ); $this->Out( '/Length1 ' . $int_ttf_font_size ); $this->Out( '>>' ); $this->PutStream( $str_font_stream_compressed ); $this->Out( 'endobj' ); unset( $obj_ttf ); } else { // Allow for additional types $this->arr_fonts[ $str_key ]['n'] = $this->int_current_object + 1; $str_method = '_put' . strtolower( $str_type ); if ( ! method_exists( $this, $str_method ) ) { throw new WOE_FPDF_Exception( 'Unsupported font type: ' . esc_html($str_type), esc_html(WOE_FPDF_Exception::UNSUPPORTED_FONT) ); } $this->$str_method( $arr_font_data ); } } } } protected function PutTTFontWidths( $font, $maxUni ) { $cachePath = $this->getCachePath(); $cacheFile = $cachePath . self::FILE_CHARACTER_WIDTH . $font['filename'] . '.json'; $rangeid = 0; $range = array(); $prevcid = - 2; $prevwidth = - 1; $interval = false; $startcid = 1; if ( $cachePath !== null && file_exists( $cacheFile ) ) { $fopen = fopen( $cacheFile, 'r' ); $fread = fread( $fopen, filesize( $cacheFile ) ); fclose( $fopen ); $json = json_decode( $fread, true ); if ( json_last_error() !== JSON_ERROR_NONE ) { extract( $json ); $startcid = 128; } } $cwlen = $maxUni + 1; // for each character for ( $cid = $startcid; $cid < $cwlen; $cid ++ ) { if( !isset($font['cw'][ $cid * 2 ]) ) { continue; } if ( $font['cw'][ $cid * 2 ] == "\00" && $font['cw'][ $cid * 2 + 1 ] == "\00" ) { continue; } $width = ( ord( $font['cw'][ $cid * 2 ] ) << 8 ) + ord( $font['cw'][ $cid * 2 + 1 ] ); if ( $width == 65535 ) { $width = 0; } if ( $cid > 255 && ( ! isset( $font['subset'][ $cid ] ) || ! $font['subset'][ $cid ] ) ) { continue; } if ( ! isset( $font['dw'] ) || ( isset( $font['dw'] ) && $width != $font['dw'] ) ) { if ( $cid == ( $prevcid + 1 ) ) { if ( $width == $prevwidth ) { if ( $width == $range[ $rangeid ][0] ) { $range[ $rangeid ][] = $width; } else { array_pop( $range[ $rangeid ] ); // new range $rangeid = $prevcid; $range[ $rangeid ] = array(); $range[ $rangeid ][] = $prevwidth; $range[ $rangeid ][] = $width; } $interval = true; $range[ $rangeid ]['interval'] = true; } else { if ( $interval ) { // new range $rangeid = $cid; $range[ $rangeid ] = array(); $range[ $rangeid ][] = $width; } else { $range[ $rangeid ][] = $width; } $interval = false; } } else { $rangeid = $cid; $range[ $rangeid ] = array(); $range[ $rangeid ][] = $width; $interval = false; } $prevcid = $cid; $prevwidth = $width; } } // write file if ( $cachePath !== null && ! file_exists( $cacheFile ) ) { $fh = fopen( $cacheFile, 'wb' ); $cw127 = [ 'rangeid' => $rangeid, 'prevcid' => $prevcid, 'prevwidth' => $prevwidth, 'interval' => (bool) $interval, 'range' => $range, ]; fwrite( $fh, json_encode( $cw127 ) ); fclose( $fh ); } $prevk = - 1; $nextk = - 1; $prevint = false; foreach ( $range as $k => $ws ) { $cws = count( $ws ); if ( ( $k == $nextk ) AND ( ! $prevint ) AND ( ( ! isset( $ws['interval'] ) ) OR ( $cws < 4 ) ) ) { if ( isset( $range[ $k ]['interval'] ) ) { unset( $range[ $k ]['interval'] ); } $range[ $prevk ] = array_merge( $range[ $prevk ], $range[ $k ] ); unset( $range[ $k ] ); } else { $prevk = $k; } $nextk = $k + $cws; if ( isset( $ws['interval'] ) ) { if ( $cws > 3 ) { $prevint = true; } else { $prevint = false; } unset( $range[ $k ]['interval'] ); -- $nextk; } else { $prevint = false; } } $w = ''; foreach ( $range as $k => $ws ) { if ( count( array_count_values( $ws ) ) == 1 ) { $w .= ' ' . $k . ' ' . ( $k + count( $ws ) - 1 ) . ' ' . $ws[0]; } else { $w .= ' ' . $k . ' [ ' . implode( ' ', $ws ) . ' ]' . "\n"; } } $this->Out( '/W [' . $w . ' ]' ); } /** * */ private function PutImages() { foreach ( array_keys( $this->arr_images ) as $str_file ) { $this->PutImage( $this->arr_images[ $str_file ] ); unset( $this->arr_images[ $str_file ]['data'] ); unset( $this->arr_images[ $str_file ]['smask'] ); } } /** * @param $arr_info */ public function PutImage( &$arr_info ) { $this->NewObject(); $arr_info['n'] = $this->int_current_object; $this->Out( '<Out( '/Subtype /Image' ); $this->Out( '/Width ' . $arr_info['w'] ); $this->Out( '/Height ' . $arr_info['h'] ); if ( $arr_info['cs'] == 'Indexed' ) { $this->Out( '/ColorSpace [/Indexed /DeviceRGB ' . ( strlen( $arr_info['pal'] ) / 3 - 1 ) . ' ' . ( $this->int_current_object + 1 ) . ' 0 R]' ); } else { $this->Out( '/ColorSpace /' . $arr_info['cs'] ); if ( $arr_info['cs'] == 'DeviceCMYK' ) { $this->Out( '/Decode [1 0 1 0 1 0 1 0]' ); } } $this->Out( '/BitsPerComponent ' . $arr_info['bpc'] ); if ( isset( $arr_info['f'] ) ) { $this->Out( '/Filter /' . $arr_info['f'] ); } if ( isset( $arr_info['dp'] ) ) { $this->Out( '/DecodeParms <<' . $arr_info['dp'] . '>>' ); } if ( isset( $arr_info['trns'] ) && is_array( $arr_info['trns'] ) ) { $trns = ''; for ( $i = 0; $i < count( $arr_info['trns'] ); $i ++ ) { $trns .= $arr_info['trns'][ $i ] . ' ' . $arr_info['trns'][ $i ] . ' '; } $this->Out( '/Mask [' . $trns . ']' ); } if ( isset( $arr_info['smask'] ) ) { $this->Out( '/SMask ' . ( $this->int_current_object + 1 ) . ' 0 R' ); } $this->Out( '/Length ' . strlen( $arr_info['data'] ) . '>>' ); $this->PutStream( $arr_info['data'] ); $this->Out( 'endobj' ); // Soft mask if ( isset( $arr_info['smask'] ) ) { $str_dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' . $arr_info['w']; $arr_smask = array( 'w' => $arr_info['w'], 'h' => $arr_info['h'], 'cs' => 'DeviceGray', 'bpc' => 8, 'f' => $arr_info['f'], 'dp' => $str_dp, 'data' => $arr_info['smask'] ); $this->PutImage( $arr_smask ); } // Palette if ( $arr_info['cs'] == 'Indexed' ) { $str_filter = ( $this->bol_compress ) ? '/Filter /FlateDecode ' : ''; $str_pal = ( $this->bol_compress ) ? gzcompress( $arr_info['pal'] ) : $arr_info['pal']; $this->NewObject(); $this->Out( '<<' . $str_filter . '/Length ' . strlen( $str_pal ) . '>>' ); $this->PutStream( $str_pal ); $this->Out( 'endobj' ); } } /** * */ public function PutXObjectDict() { foreach ( $this->arr_images as $arr_image ) { $this->Out( '/I' . $arr_image['i'] . ' ' . $arr_image['n'] . ' 0 R' ); } } /** * */ public function PutResourceDict() { $this->Out( '/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]' ); $this->Out( '/Font <<' ); foreach ( $this->arr_fonts as $arr_font ) { $this->Out( '/F' . $arr_font['i'] . ' ' . $arr_font['n'] . ' 0 R' ); } $this->Out( '>>' ); $this->Out( '/XObject <<' ); $this->PutXObjectDict(); $this->Out( '>>' ); } /** * */ public function PutResources() { $this->PutFonts(); $this->PutImages(); // Resource dictionary $this->arr_offsets[2] = strlen( $this->str_buffer ); $this->Out( '2 0 obj' ); $this->Out( '<<' ); $this->PutResourceDict(); $this->Out( '>>' ); $this->Out( 'endobj' ); } /** * */ public function PutInfo() { $this->Out( '/Producer ' . $this->TextString( 'tFPDF ' . $this->str_pdf_version ) ); if ( ! empty( $this->str_title ) ) { $this->Out( '/Title ' . $this->TextString( $this->str_title ) ); } if ( ! empty( $this->str_subject ) ) { $this->Out( '/Subject ' . $this->TextString( $this->str_subject ) ); } if ( ! empty( $this->str_author ) ) { $this->Out( '/Author ' . $this->TextString( $this->str_author ) ); } if ( ! empty( $this->str_keywords ) ) { $this->Out( '/Keywords ' . $this->TextString( $this->str_keywords ) ); } if ( ! empty( $this->str_creator ) ) { $this->Out( '/Creator ' . $this->TextString( $this->str_creator ) ); } $this->Out( '/CreationDate ' . $this->TextString( 'D:' . @gmdate( 'YmdHis' ) ) ); } /** * */ public function PutCatalog() { $this->Out( '/Type /Catalog' ); $this->Out( '/Pages 1 0 R' ); if ( $this->mix_zoom_mode == 'fullpage' ) { $this->Out( '/OpenAction [3 0 R /Fit]' ); } elseif ( $this->mix_zoom_mode == 'fullwidth' ) { $this->Out( '/OpenAction [3 0 R /FitH null]' ); } elseif ( $this->mix_zoom_mode == 'real' ) { $this->Out( '/OpenAction [3 0 R /XYZ null null 1]' ); } elseif ( ! is_string( $this->mix_zoom_mode ) ) { $this->Out( '/OpenAction [3 0 R /XYZ null null ' . sprintf( '%.2F', $this->mix_zoom_mode / 100 ) . ']' ); } if ( $this->str_layout_mode == 'single' ) { $this->Out( '/PageLayout /SinglePage' ); } elseif ( $this->str_layout_mode == 'continuous' ) { $this->Out( '/PageLayout /OneColumn' ); } elseif ( $this->str_layout_mode == 'two' ) { $this->Out( '/PageLayout /TwoColumnLeft' ); } } /** * */ public function PutHeader() { $this->Out( '%PDF-' . $this->str_pdf_version ); } /** * */ public function PutTrailer() { $this->Out( '/Size ' . ( $this->int_current_object + 1 ) ); $this->Out( '/Root ' . $this->int_current_object . ' 0 R' ); $this->Out( '/Info ' . ( $this->int_current_object - 1 ) . ' 0 R' ); } /** * */ public function EndDoc() { $this->PutHeader(); $this->PutPages(); $this->PutResources(); // Info $this->NewObject(); $this->Out( '<<' ); $this->PutInfo(); $this->Out( '>>' ); $this->Out( 'endobj' ); // Catalog $this->NewObject(); $this->Out( '<<' ); $this->PutCatalog(); $this->Out( '>>' ); $this->Out( 'endobj' ); // Cross-ref $int_buffer_size = strlen( $this->str_buffer ); $this->Out( 'xref' ); $this->Out( '0 ' . ( $this->int_current_object + 1 ) ); $this->Out( '0000000000 65535 f ' ); for ( $i = 1; $i <= $this->int_current_object; $i ++ ) { $this->Out( sprintf( '%010d 00000 n ', $this->arr_offsets[ $i ] ) ); } // Trailer $this->Out( 'trailer' ); $this->Out( '<<' ); $this->PutTrailer(); $this->Out( '>>' ); $this->Out( 'startxref' ); $this->Out( $int_buffer_size ); $this->Out( '%%EOF' ); $this->int_state = self::DOCUMENT_STATE_TERMINATED; } /** * Converts UTF-8 strings to UTF16-BE * * @param $str_input * @param bool $bol_set_byte_order_mark * * @return string */ public function UTF8ToUTF16BE( $str_input, $bol_set_byte_order_mark = true ) { $str_output = ""; if ( $bol_set_byte_order_mark ) { $str_output .= "\xFE\xFF"; } $str_output .= mb_convert_encoding( $str_input, 'UTF-16BE', 'UTF-8' ); return $str_output; } /** * Converts UTF-8 strings to codepoints array * * @param $str_input * * @return array */ public function UTF8StringToArray( $str_input ) { $arr_output = array(); $int_string_length = strlen( $str_input ); for ( $i = 0; $i < $int_string_length; $i ++ ) { $uni = - 1; $int_char = ord( $str_input[ $i ] ); if ( $int_char <= 0x7F ) { $uni = $int_char; } elseif ( $int_char >= 0xC2 ) { if ( ( $int_char <= 0xDF ) && ( $i < $int_string_length - 1 ) ) { $uni = ( $int_char & 0x1F ) << 6 | ( ord( $str_input[ ++ $i ] ) & 0x3F ); } elseif ( ( $int_char <= 0xEF ) && ( $i < $int_string_length - 2 ) ) { $uni = ( $int_char & 0x0F ) << 12 | ( ord( $str_input[ ++ $i ] ) & 0x3F ) << 6 | ( ord( $str_input[ ++ $i ] ) & 0x3F ); } elseif ( ( $int_char <= 0xF4 ) && ( $i < $int_string_length - 3 ) ) { $uni = ( $int_char & 0x0F ) << 18 | ( ord( $str_input[ ++ $i ] ) & 0x3F ) << 12 | ( ord( $str_input[ ++ $i ] ) & 0x3F ) << 6 | ( ord( $str_input[ ++ $i ] ) & 0x3F ); } } if ( $uni >= 0 ) { $arr_output[] = $uni; } } return $arr_output; } /** * With this method you can set the cache path * * @param string $cachePath The cache folder * * @return string The newly set cache folder */ public function setCachePath( $cachePath = null ) { if ( ! file_exists( $cachePath ) ) { // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_mkdir @mkdir( $cachePath, 0775, true ); } // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_is_writable if ( ! file_exists( $cachePath ) || ! is_dir( $cachePath ) || ! is_writable( $cachePath ) ) { throw new WOE_FPDF_Exception( 'Could not write to cache folder: ' . esc_html($cachePath), esc_html(WOE_FPDF_Exception::INVALID_CACHE_FOLDER) ); } $this->cachePath = realpath( $cachePath ) . '/'; return $cachePath; } /** * Get the currently set cache folder * * @return string The cache folder, may be null */ public function getCachePath() { return $this->cachePath; } /** * Clear all cached files in the cache folder * */ public function clearCache() { $cachePath = $this->getCachePath(); if ( $cachePath !== null ) { $cacheFiles = glob( $cachePath . '*.*' ); foreach ( $cacheFiles as $cacheFile ) { @wp_delete_file( $cacheFile ); } } } /** * Calculates the expected line height for a multi cell, * useful if you want to know how much space a cell is going to use * * @param string $text The text to calculate the line height from * @param int $cellWidth The width of the multicell * @param int $lineHeight The line height of the multicell * * @return int The calculated line height */ public function calculateHeight( $text, $cellWidth = 80, $lineHeight = 3 ) { $explode = explode( "\r", $text ); array_walk( $explode, 'trim' ); $lines = []; foreach ( $explode as $split ) { $sub = $split; $char = 1; while ( $char <= strlen( $sub ) ) { $substr = substr( $sub, 0, $char ); if ( $this->getStringWidth( $substr ) >= $cellWidth - 1 ) { $pos = strrpos( $substr, " " ); $lines[] = substr( $sub, 0, ( $pos !== false ? $pos : $char ) ) . ( $pos === false ? '-' : '' ); if ( $pos !== false ) { //if $pos returns FALSE, substr has no whitespace, so split word on current position $char = $pos + 1; $len = $char; } $sub = ltrim( substr( $sub, $char ) ); $char = 0; } $char ++; } if ( ! empty( $sub ) ) { $lines[] = $sub; } } return (int) count( $lines ) * $lineHeight; } }