* * Tycho Veltmeijer (versions 1.30+) * * License: LGPL * *******************************************************************************/ define( 'tFPDF_VERSION', '1.32' ); class tFPDF { protected $unifontSubset; protected $page; // current page number protected $n; // current object number protected $offsets; // array of object offsets protected $buffer; // buffer holding in-memory PDF protected $pages; // array containing pages protected $state; // current document state protected $compress; // compression flag protected $k; // scale factor (number of points in user unit) protected $DefOrientation; // default orientation protected $CurOrientation; // current orientation protected $StdPageSizes; // standard page sizes protected $DefPageSize; // default page size protected $CurPageSize; // current page size protected $CurRotation; // current page rotation protected $PageInfo; // page-related data protected $wPt, $hPt; // dimensions of current page in points protected $w, $h; // dimensions of current page in user unit protected $lMargin; // left margin protected $tMargin; // top margin protected $rMargin; // right margin protected $bMargin; // page break margin protected $cMargin; // cell margin protected $x, $y; // current position in user unit protected $lasth; // height of last printed cell protected $LineWidth; // line width in user unit protected $fontpath; // path containing fonts protected $CoreFonts; // array of core font names protected $fonts; // array of used fonts protected $FontFiles; // array of font files protected $encodings; // array of encodings protected $cmaps; // array of ToUnicode CMaps protected $FontFamily; // current font family protected $FontStyle; // current font style protected $underline; // underlining flag protected $CurrentFont; // current font info protected $FontSizePt; // current font size in points protected $FontSize; // current font size in user unit protected $DrawColor; // commands for drawing color protected $FillColor; // commands for filling color protected $TextColor; // commands for text color protected $ColorFlag; // indicates whether fill and text colors are different protected $WithAlpha; // indicates whether alpha channel is used protected $ws; // word spacing protected $images; // array of used images protected $PageLinks; // array of links in pages protected $links; // array of internal links protected $AutoPageBreak; // automatic page breaking protected $PageBreakTrigger; // threshold used to trigger page breaks protected $InHeader; // flag set when processing header protected $InFooter; // flag set when processing footer protected $AliasNbPages; // alias for total number of pages protected $ZoomMode; // zoom display mode protected $LayoutMode; // layout display mode protected $metadata; // document properties protected $PDFVersion; // PDF version number /******************************************************************************* * Public methods * *******************************************************************************/ function __construct( $orientation = 'P', $unit = 'mm', $size = 'A4' ) { // Some checks $this->_dochecks(); // Initialization of properties $this->state = 0; $this->page = 0; $this->n = 2; $this->buffer = ''; $this->pages = []; $this->PageInfo = []; $this->fonts = []; $this->FontFiles = []; $this->encodings = []; $this->cmaps = []; $this->images = []; $this->links = []; $this->InHeader = false; $this->InFooter = false; $this->lasth = 0; $this->FontFamily = ''; $this->FontStyle = ''; $this->FontSizePt = 12; $this->underline = false; $this->DrawColor = '0 G'; $this->FillColor = '0 g'; $this->TextColor = '0 g'; $this->ColorFlag = false; $this->WithAlpha = false; $this->ws = 0; // Font path if ( defined( 'FPDF_FONTPATH' ) ) { $this->fontpath = FPDF_FONTPATH; if ( substr( $this->fontpath, -1 ) != '/' && substr( $this->fontpath, -1 ) != '\\' ) { $this->fontpath .= '/'; } } elseif ( is_dir( dirname( __FILE__ ) . '/font' ) ) { $this->fontpath = dirname( __FILE__ ) . '/font/'; } else { $this->fontpath = ''; } // Core fonts $this->CoreFonts = [ 'courier', 'helvetica', 'times', 'symbol', 'zapfdingbats' ]; // Scale factor if ( $unit == 'pt' ) { $this->k = 1; } elseif ( $unit == 'mm' ) { $this->k = 72 / 25.4; } elseif ( $unit == 'cm' ) { $this->k = 72 / 2.54; } elseif ( $unit == 'in' ) { $this->k = 72; } else { $this->Error( 'Incorrect unit: ' . $unit ); } // Page sizes $this->StdPageSizes = [ 'a3' => [ 841.89, 1190.55 ], 'a4' => [ 595.28, 841.89 ], 'a5' => [ 420.94, 595.28 ], 'letter' => [ 612, 792 ], 'legal' => [ 612, 1008 ] ]; $size = $this->_getpagesize( $size ); $this->DefPageSize = $size; $this->CurPageSize = $size; // Page orientation $orientation = strtolower( $orientation ); if ( $orientation == 'p' || $orientation == 'portrait' ) { $this->DefOrientation = 'P'; $this->w = $size[0]; $this->h = $size[1]; } elseif ( $orientation == 'l' || $orientation == 'landscape' ) { $this->DefOrientation = 'L'; $this->w = $size[1]; $this->h = $size[0]; } else { $this->Error( 'Incorrect orientation: ' . $orientation ); } $this->CurOrientation = $this->DefOrientation; $this->wPt = $this->w * $this->k; $this->hPt = $this->h * $this->k; // Page rotation $this->CurRotation = 0; // Page margins (1 cm) $margin = 28.35 / $this->k; $this->SetMargins( $margin, $margin ); // Interior cell margin (1 mm) $this->cMargin = $margin / 10; // Line width (0.2 mm) $this->LineWidth = .567 / $this->k; // Automatic page break $this->SetAutoPageBreak( true, 2 * $margin ); // Default display mode $this->SetDisplayMode( 'default' ); // Enable compression $this->SetCompression( true ); // Set default PDF version number $this->PDFVersion = '1.3'; } function SetMargins( $left, $top, $right = null ) { // Set left, top and right margins $this->lMargin = $left; $this->tMargin = $top; if ( $right === null ) { $right = $left; } $this->rMargin = $right; } function SetLeftMargin( $margin ) { // Set left margin $this->lMargin = $margin; if ( $this->page > 0 && $this->x < $margin ) { $this->x = $margin; } } function SetTopMargin( $margin ) { // Set top margin $this->tMargin = $margin; } function SetRightMargin( $margin ) { // Set right margin $this->rMargin = $margin; } function SetAutoPageBreak( $auto, $margin = 0 ) { // Set auto page break mode and triggering margin $this->AutoPageBreak = $auto; $this->bMargin = $margin; $this->PageBreakTrigger = $this->h - $margin; } function SetDisplayMode( $zoom, $layout = 'default' ) { // Set display mode in viewer if ( $zoom == 'fullpage' || $zoom == 'fullwidth' || $zoom == 'real' || $zoom == 'default' || !is_string( $zoom ) ) { $this->ZoomMode = $zoom; } else { $this->Error( 'Incorrect zoom display mode: ' . $zoom ); } if ( $layout == 'single' || $layout == 'continuous' || $layout == 'two' || $layout == 'default' ) { $this->LayoutMode = $layout; } else { $this->Error( 'Incorrect layout display mode: ' . $layout ); } } function SetCompression( $compress ) { // Set page compression if ( function_exists( 'gzcompress' ) ) { $this->compress = $compress; } else { $this->compress = false; } } function SetTitle( $title, $isUTF8 = false ) { // Title of document $this->metadata['Title'] = $isUTF8 ? $title : utf8_encode( $title ); } function SetAuthor( $author, $isUTF8 = false ) { // Author of document $this->metadata['Author'] = $isUTF8 ? $author : utf8_encode( $author ); } function SetSubject( $subject, $isUTF8 = false ) { // Subject of document $this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode( $subject ); } function SetKeywords( $keywords, $isUTF8 = false ) { // Keywords of document $this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode( $keywords ); } function SetCreator( $creator, $isUTF8 = false ) { // Creator of document $this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode( $creator ); } function AliasNbPages( $alias = '{nb}' ) { // Define an alias for total number of pages $this->AliasNbPages = $alias; } function Error( $msg ) { // Fatal error throw new \Exception( 'FPDF error: ' . $msg ); } function Close() { // Terminate document if ( $this->state == 3 ) { return; } if ( $this->page == 0 ) { $this->AddPage(); } // Page footer $this->InFooter = true; $this->Footer(); $this->InFooter = false; // Close page $this->_endpage(); // Close document $this->_enddoc(); } function AddPage( $orientation = '', $size = '', $rotation = 0 ) { // Start a new page if ( $this->state == 3 ) { $this->Error( 'The document is closed' ); } $family = $this->FontFamily; $style = $this->FontStyle . ( $this->underline ? 'U' : '' ); $fontsize = $this->FontSizePt; $lw = $this->LineWidth; $dc = $this->DrawColor; $fc = $this->FillColor; $tc = $this->TextColor; $cf = $this->ColorFlag; if ( $this->page > 0 ) { // Page footer $this->InFooter = true; $this->Footer(); $this->InFooter = false; // Close page $this->_endpage(); } // Start new page $this->_beginpage( $orientation, $size, $rotation ); // Set line cap style to square $this->_out( '2 J' ); // Set line width $this->LineWidth = $lw; $this->_out( sprintf( '%.2F w', $lw * $this->k ) ); // Set font if ( $family ) { $this->SetFont( $family, $style, $fontsize ); } // Set colors $this->DrawColor = $dc; if ( $dc != '0 G' ) { $this->_out( $dc ); } $this->FillColor = $fc; if ( $fc != '0 g' ) { $this->_out( $fc ); } $this->TextColor = $tc; $this->ColorFlag = $cf; // Page header $this->InHeader = true; $this->Header(); $this->InHeader = false; // Restore line width if ( $this->LineWidth != $lw ) { $this->LineWidth = $lw; $this->_out( sprintf( '%.2F w', $lw * $this->k ) ); } // Restore font if ( $family ) { $this->SetFont( $family, $style, $fontsize ); } // Restore colors if ( $this->DrawColor != $dc ) { $this->DrawColor = $dc; $this->_out( $dc ); } if ( $this->FillColor != $fc ) { $this->FillColor = $fc; $this->_out( $fc ); } $this->TextColor = $tc; $this->ColorFlag = $cf; } function Header() { // To be implemented in your own inherited class } function Footer() { // To be implemented in your own inherited class } function PageNo() { // Get current page number return $this->page; } function SetDrawColor( $r, $g = null, $b = null ) { // Set color for all stroking operations if ( ( $r == 0 && $g == 0 && $b == 0 ) || $g === null ) { $this->DrawColor = sprintf( '%.3F G', $r / 255 ); } else { $this->DrawColor = sprintf( '%.3F %.3F %.3F RG', $r / 255, $g / 255, $b / 255 ); } if ( $this->page > 0 ) { $this->_out( $this->DrawColor ); } } function SetFillColor( $r, $g = null, $b = null ) { // Set color for all filling operations if ( ( $r == 0 && $g == 0 && $b == 0 ) || $g === null ) { $this->FillColor = sprintf( '%.3F g', $r / 255 ); } else { $this->FillColor = sprintf( '%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255 ); } $this->ColorFlag = ( $this->FillColor != $this->TextColor ); if ( $this->page > 0 ) { $this->_out( $this->FillColor ); } } function SetTextColor( $r, $g = null, $b = null ) { // Set color for text if ( ( $r == 0 && $g == 0 && $b == 0 ) || $g === null ) { $this->TextColor = sprintf( '%.3F g', $r / 255 ); } else { $this->TextColor = sprintf( '%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255 ); } $this->ColorFlag = ( $this->FillColor != $this->TextColor ); } function GetStringWidth( $s ) { // Get width of a string in the current font $s = (string) $s; $cw = &$this->CurrentFont['cw']; $w = 0; if ( $this->unifontSubset ) { $unicode = $this->UTF8StringToArray( $s ); foreach ( $unicode as $char ) { if ( isset( $cw[2 * $char] ) ) {$w += ( ord( $cw[2 * $char] ) << 8 ) + ord( $cw[2 * $char + 1] );} else if ( $char > 0 && $char < 128 && isset( $cw[chr( $char )] ) ) {$w += $cw[chr( $char )];} else if ( isset( $this->CurrentFont['desc']['MissingWidth'] ) ) {$w += $this->CurrentFont['desc']['MissingWidth'];} else if ( isset( $this->CurrentFont['MissingWidth'] ) ) {$w += $this->CurrentFont['MissingWidth'];} else { $w += 500;} } } else { $l = strlen( $s ); for ( $i = 0; $i < $l; $i++ ) { $w += $cw[$s[$i]]; } } return $w * $this->FontSize / 1000; } function SetLineWidth( $width ) { // Set line width $this->LineWidth = $width; if ( $this->page > 0 ) { $this->_out( sprintf( '%.2F w', $width * $this->k ) ); } } function Line( $x1, $y1, $x2, $y2 ) { // Draw a line $this->_out( sprintf( '%.2F %.2F m %.2F %.2F l S', $x1 * $this->k, ( $this->h - $y1 ) * $this->k, $x2 * $this->k, ( $this->h - $y2 ) * $this->k ) ); } function Rect( $x, $y, $w, $h, $style = '' ) { // Draw a rectangle if ( $style == 'F' ) { $op = 'f'; } elseif ( $style == 'FD' || $style == 'DF' ) { $op = 'B'; } else { $op = 'S'; } $this->_out( sprintf( '%.2F %.2F %.2F %.2F re %s', $x * $this->k, ( $this->h - $y ) * $this->k, $w * $this->k, -$h * $this->k, $op ) ); } function AddFont( $family, $style = '', $file = '', $uni = false ) { // Add a TrueType, OpenType or Type1 font $family = strtolower( $family ); $style = strtoupper( $style ); if ( $style == 'IB' ) { $style = 'BI'; } if ( $file == '' ) { if ( $uni ) { $file = str_replace( ' ', '', $family ) . strtolower( $style ) . '.ttf'; } else { $file = str_replace( ' ', '', $family ) . strtolower( $style ) . '.php'; } } $fontkey = $family . $style; if ( isset( $this->fonts[$fontkey] ) ) { return; } if ( $uni ) { if ( defined( "_SYSTEM_TTFONTS" ) && file_exists( _SYSTEM_TTFONTS . $file ) ) {$ttffilename = _SYSTEM_TTFONTS . $file;} else { $ttffilename = $this->fontpath . 'unifont/' . $file;} $unifilename = $this->fontpath . 'unifont/' . strtolower( substr( $file, 0, ( strpos( $file, '.' ) ) ) ); $name = ''; $originalsize = 0; $ttfstat = stat( $ttffilename ); if ( file_exists( $unifilename . '.mtx.php' ) ) { include $unifilename . '.mtx.php'; } if ( !isset( $type ) || !isset( $name ) || $originalsize != $ttfstat['size'] ) { $ttffile = $ttffilename; require_once $this->fontpath . 'unifont/ttfonts.php'; $ttf = new \Etn\Utils\Font\Unifont\TTFontFile(); $ttf->getMetrics( $ttffile ); $cw = $ttf->charWidths; $name = preg_replace( '/[ ()]/', '', $ttf->fullName ); $desc = [ 'Ascent' => round( $ttf->ascent ), 'Descent' => round( $ttf->descent ), 'CapHeight' => round( $ttf->capHeight ), 'Flags' => $ttf->flags, 'FontBBox' => '[' . round( $ttf->bbox[0] ) . " " . round( $ttf->bbox[1] ) . " " . round( $ttf->bbox[2] ) . " " . round( $ttf->bbox[3] ) . ']', 'ItalicAngle' => $ttf->italicAngle, 'StemV' => round( $ttf->stemV ), 'MissingWidth' => round( $ttf->defaultWidth ) ]; $up = round( $ttf->underlinePosition ); $ut = round( $ttf->underlineThickness ); $originalsize = $ttfstat['size'] + 0; $type = 'TTF'; // Generate metrics .php file $s = '"; if ( is_writable( dirname( $this->fontpath . 'unifont/' . 'x' ) ) ) { $fh = fopen( $unifilename . '.mtx.php', "w" ); fwrite( $fh, $s, strlen( $s ) ); fclose( $fh ); $fh = fopen( $unifilename . '.cw.dat', "wb" ); fwrite( $fh, $cw, strlen( $cw ) ); fclose( $fh ); @unlink( $unifilename . '.cw127.php' ); } unset( $ttf ); } else { $cw = @file_get_contents( $unifilename . '.cw.dat' ); } $i = count( $this->fonts ) + 1; if ( !empty( $this->AliasNbPages ) ) { $sbarr = range( 0, 57 ); } else { $sbarr = range( 0, 32 ); } $this->fonts[$fontkey] = [ 'i' => $i, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'ttffile' => $ttffile, 'fontkey' => $fontkey, 'subset' => $sbarr, 'unifilename' => $unifilename ]; $this->FontFiles[$fontkey] = [ 'length1' => $originalsize, 'type' => "TTF", 'ttffile' => $ttffile ]; $this->FontFiles[$file] = [ 'type' => "TTF" ]; unset( $cw ); } else { $info = $this->_loadfont( $file ); $info['i'] = count( $this->fonts ) + 1; if ( !empty( $info['file'] ) ) { // Embedded font if ( $info['type'] == 'TrueType' ) { $this->FontFiles[$info['file']] = [ 'length1' => $info['originalsize'] ]; } else { $this->FontFiles[$info['file']] = [ 'length1' => $info['size1'], 'length2' => $info['size2'] ]; } } $this->fonts[$fontkey] = $info; } } function SetFont( $family, $style = '', $size = 0 ) { // Select a font; size given in points if ( $family == '' ) { $family = $this->FontFamily; } else { $family = strtolower( $family ); } $style = strtoupper( $style ); if ( strpos( $style, 'U' ) !== false ) { $this->underline = true; $style = str_replace( 'U', '', $style ); } else { $this->underline = false; } if ( $style == 'IB' ) { $style = 'BI'; } if ( $size == 0 ) { $size = $this->FontSizePt; } // Test if font is already selected if ( $this->FontFamily == $family && $this->FontStyle == $style && $this->FontSizePt == $size ) { return; } // Test if font is already loaded $fontkey = $family . $style; if ( !isset( $this->fonts[$fontkey] ) ) { // Test if one of the core fonts if ( $family == 'arial' ) { $family = 'helvetica'; } if ( in_array( $family, $this->CoreFonts ) ) { if ( $family == 'symbol' || $family == 'zapfdingbats' ) { $style = ''; } $fontkey = $family . $style; if ( !isset( $this->fonts[$fontkey] ) ) { $this->AddFont( $family, $style ); } } else { $this->Error( 'Undefined font: ' . $family . ' ' . $style ); } } // Select it $this->FontFamily = $family; $this->FontStyle = $style; $this->FontSizePt = $size; $this->FontSize = $size / $this->k; $this->CurrentFont = &$this->fonts[$fontkey]; if ( $this->fonts[$fontkey]['type'] == 'TTF' ) {$this->unifontSubset = true;} else { $this->unifontSubset = false;} if ( $this->page > 0 ) { $this->_out( sprintf( 'BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt ) ); } } function SetFontSize( $size ) { // Set font size in points if ( $this->FontSizePt == $size ) { return; } $this->FontSizePt = $size; $this->FontSize = $size / $this->k; if ( $this->page > 0 ) { $this->_out( sprintf( 'BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt ) ); } } function AddLink() { // Create a new internal link $n = count( $this->links ) + 1; $this->links[$n] = [ 0, 0 ]; return $n; } function SetLink( $link, $y = 0, $page = -1 ) { // Set destination of internal link if ( $y == -1 ) { $y = $this->y; } if ( $page == -1 ) { $page = $this->page; } $this->links[$link] = [ $page, $y ]; } function Link( $x, $y, $w, $h, $link ) { // Put a link on the page $this->PageLinks[$this->page][] = [ $x * $this->k, $this->hPt - $y * $this->k, $w * $this->k, $h * $this->k, $link ]; } function Text( $x, $y, $txt ) { // Output a string $txt = (string) $txt; if ( !isset( $this->CurrentFont ) ) { $this->Error( 'No font has been set' ); } if ( $this->unifontSubset ) { $txt2 = '(' . $this->_escape( $this->UTF8ToUTF16BE( $txt, false ) ) . ')'; foreach ( $this->UTF8StringToArray( $txt ) as $uni ) { $this->CurrentFont['subset'][$uni] = $uni; } } else { $txt2 = '(' . $this->_escape( $txt ) . ')'; } $s = sprintf( 'BT %.2F %.2F Td %s Tj ET', $x * $this->k, ( $this->h - $y ) * $this->k, $txt2 ); if ( $this->underline && $txt != '' ) { $s .= ' ' . $this->_dounderline( $x, $y, $txt ); } if ( $this->ColorFlag ) { $s = 'q ' . $this->TextColor . ' ' . $s . ' Q'; } $this->_out( $s ); } function AcceptPageBreak() { // Accept automatic page break or not return $this->AutoPageBreak; } function Cell( $w, $h = 0, $txt = '', $border = 0, $ln = 0, $align = '', $fill = false, $link = '' ) { // Output a cell $txt = (string) $txt; $k = $this->k; if ( $this->y + $h > $this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak() ) { // Automatic page break $x = $this->x; $ws = $this->ws; if ( $ws > 0 ) { $this->ws = 0; $this->_out( '0 Tw' ); } $this->AddPage( $this->CurOrientation, $this->CurPageSize, $this->CurRotation ); $this->x = $x; if ( $ws > 0 ) { $this->ws = $ws; $this->_out( sprintf( '%.3F Tw', $ws * $k ) ); } } if ( $w == 0 ) { $w = $this->w - $this->rMargin - $this->x; } $s = ''; if ( $fill || $border == 1 ) { if ( $fill ) { $op = ( $border == 1 ) ? 'B' : 'f'; } else { $op = 'S'; } $s = sprintf( '%.2F %.2F %.2F %.2F re %s ', $this->x * $k, ( $this->h - $this->y ) * $k, $w * $k, -$h * $k, $op ); } if ( is_string( $border ) ) { $x = $this->x; $y = $this->y; if ( strpos( $border, 'L' ) !== false ) { $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $x * $k, ( $this->h - $y ) * $k, $x * $k, ( $this->h - ( $y + $h ) ) * $k ); } if ( strpos( $border, 'T' ) !== false ) { $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $x * $k, ( $this->h - $y ) * $k, ( $x + $w ) * $k, ( $this->h - $y ) * $k ); } if ( strpos( $border, 'R' ) !== false ) { $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', ( $x + $w ) * $k, ( $this->h - $y ) * $k, ( $x + $w ) * $k, ( $this->h - ( $y + $h ) ) * $k ); } if ( strpos( $border, 'B' ) !== false ) { $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $x * $k, ( $this->h - ( $y + $h ) ) * $k, ( $x + $w ) * $k, ( $this->h - ( $y + $h ) ) * $k ); } } if ( $txt !== '' ) { if ( !isset( $this->CurrentFont ) ) { $this->Error( 'No font has been set' ); } if ( $align == 'R' ) { $dx = $w - $this->cMargin - $this->GetStringWidth( $txt ); } elseif ( $align == 'C' ) { $dx = ( $w - $this->GetStringWidth( $txt ) ) / 2; } else { $dx = $this->cMargin; } if ( $this->ColorFlag ) { $s .= 'q ' . $this->TextColor . ' '; } // If multibyte, Tw has no effect - do word spacing using an adjustment before each space if ( $this->ws && $this->unifontSubset ) { foreach ( $this->UTF8StringToArray( $txt ) as $uni ) { $this->CurrentFont['subset'][$uni] = $uni; } $space = $this->_escape( $this->UTF8ToUTF16BE( ' ', false ) ); $s .= sprintf( 'BT 0 Tw %.2F %.2F Td [', ( $this->x + $dx ) * $k, ( $this->h - ( $this->y + .5 * $h + .3 * $this->FontSize ) ) * $k ); $t = explode( ' ', $txt ); $numt = count( $t ); for ( $i = 0; $i < $numt; $i++ ) { $tx = $t[$i]; $tx = '(' . $this->_escape( $this->UTF8ToUTF16BE( $tx, false ) ) . ')'; $s .= sprintf( '%s ', $tx ); if ( ( $i + 1 ) < $numt ) { $adj = -( $this->ws * $this->k ) * 1000 / $this->FontSizePt; $s .= sprintf( '%d(%s) ', $adj, $space ); } } $s .= '] TJ'; $s .= ' ET'; } else { if ( $this->unifontSubset ) { $txt2 = '(' . $this->_escape( $this->UTF8ToUTF16BE( $txt, false ) ) . ')'; foreach ( $this->UTF8StringToArray( $txt ) as $uni ) { $this->CurrentFont['subset'][$uni] = $uni; } } else { $txt2 = '(' . $this->_escape( $txt ) . ')'; } $s .= sprintf( 'BT %.2F %.2F Td %s Tj ET', ( $this->x + $dx ) * $k, ( $this->h - ( $this->y + .5 * $h + .3 * $this->FontSize ) ) * $k, $txt2 ); } if ( $this->underline ) { $s .= ' ' . $this->_dounderline( $this->x + $dx, $this->y + .5 * $h + .3 * $this->FontSize, $txt ); } if ( $this->ColorFlag ) { $s .= ' Q'; } if ( $link ) { $this->Link( $this->x + $dx, $this->y + .5 * $h - .5 * $this->FontSize, $this->GetStringWidth( $txt ), $this->FontSize, $link ); } } if ( $s ) { $this->_out( $s ); } $this->lasth = $h; if ( $ln > 0 ) { // Go to next line $this->y += $h; if ( $ln == 1 ) { $this->x = $this->lMargin; } } else { $this->x += $w; } } function MultiCell( $w, $h, $txt, $border = 0, $align = 'J', $fill = false ) { // Output text with automatic or explicit line breaks if ( !isset( $this->CurrentFont ) ) { $this->Error( 'No font has been set' ); } $cw = &$this->CurrentFont['cw']; if ( $w == 0 ) { $w = $this->w - $this->rMargin - $this->x; } $wmax = ( $w - 2 * $this->cMargin ); //$wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; $s = str_replace( "\r", '', (string) $txt ); if ( $this->unifontSubset ) { $nb = mb_strlen( $s, 'utf-8' ); while ( $nb > 0 && mb_substr( $s, $nb - 1, 1, 'utf-8' ) == "\n" ) { $nb--; } } else { $nb = strlen( $s ); if ( $nb > 0 && $s[$nb - 1] == "\n" ) { $nb--; } } $b = 0; if ( $border ) { if ( $border == 1 ) { $border = 'LTRB'; $b = 'LRT'; $b2 = 'LR'; } else { $b2 = ''; if ( strpos( $border, 'L' ) !== false ) { $b2 .= 'L'; } if ( strpos( $border, 'R' ) !== false ) { $b2 .= 'R'; } $b = ( strpos( $border, 'T' ) !== false ) ? $b2 . 'T' : $b2; } } $sep = -1; $i = 0; $j = 0; $l = 0; $ns = 0; $nl = 1; while ( $i < $nb ) { // Get next character if ( $this->unifontSubset ) { $c = mb_substr( $s, $i, 1, 'UTF-8' ); } else { $c = $s[$i]; } if ( $c == "\n" ) { // Explicit line break if ( $this->ws > 0 ) { $this->ws = 0; $this->_out( '0 Tw' ); } if ( $this->unifontSubset ) { $this->Cell( $w, $h, mb_substr( $s, $j, $i - $j, 'UTF-8' ), $b, 2, $align, $fill ); } else { $this->Cell( $w, $h, substr( $s, $j, $i - $j ), $b, 2, $align, $fill ); } $i++; $sep = -1; $j = $i; $l = 0; $ns = 0; $nl++; if ( $border && $nl == 2 ) { $b = $b2; } continue; } if ( $c == ' ' ) { $sep = $i; $ls = $l; $ns++; } if ( $this->unifontSubset ) {$l += $this->GetStringWidth( $c );} else { $l += $cw[$c] * $this->FontSize / 1000;} if ( $l > $wmax ) { // Automatic line break if ( $sep == -1 ) { if ( $i == $j ) { $i++; } if ( $this->ws > 0 ) { $this->ws = 0; $this->_out( '0 Tw' ); } if ( $this->unifontSubset ) { $this->Cell( $w, $h, mb_substr( $s, $j, $i - $j, 'UTF-8' ), $b, 2, $align, $fill ); } else { $this->Cell( $w, $h, substr( $s, $j, $i - $j ), $b, 2, $align, $fill ); } } else { if ( $align == 'J' ) { $this->ws = ( $ns > 1 ) ? ( $wmax - $ls ) / ( $ns - 1 ) : 0; $this->_out( sprintf( '%.3F Tw', $this->ws * $this->k ) ); } if ( $this->unifontSubset ) { $this->Cell( $w, $h, mb_substr( $s, $j, $sep - $j, 'UTF-8' ), $b, 2, $align, $fill ); } else { $this->Cell( $w, $h, substr( $s, $j, $sep - $j ), $b, 2, $align, $fill ); } $i = $sep + 1; } $sep = -1; $j = $i; $l = 0; $ns = 0; $nl++; if ( $border && $nl == 2 ) { $b = $b2; } } else { $i++; } } // Last chunk if ( $this->ws > 0 ) { $this->ws = 0; $this->_out( '0 Tw' ); } if ( $border && strpos( $border, 'B' ) !== false ) { $b .= 'B'; } if ( $this->unifontSubset ) { $this->Cell( $w, $h, mb_substr( $s, $j, $i - $j, 'UTF-8' ), $b, 2, $align, $fill ); } else { $this->Cell( $w, $h, substr( $s, $j, $i - $j ), $b, 2, $align, $fill ); } $this->x = $this->lMargin; } function Write( $h, $txt, $link = '' ) { // Output text in flowing mode if ( !isset( $this->CurrentFont ) ) { $this->Error( 'No font has been set' ); } $cw = &$this->CurrentFont['cw']; $w = $this->w - $this->rMargin - $this->x; $wmax = ( $w - 2 * $this->cMargin ); $s = str_replace( "\r", '', (string) $txt ); if ( $this->unifontSubset ) { $nb = mb_strlen( $s, 'UTF-8' ); if ( $nb == 1 && $s == " " ) { $this->x += $this->GetStringWidth( $s ); return; } } else { $nb = strlen( $s ); } $sep = -1; $i = 0; $j = 0; $l = 0; $nl = 1; while ( $i < $nb ) { // Get next character if ( $this->unifontSubset ) { $c = mb_substr( $s, $i, 1, 'UTF-8' ); } else { $c = $s[$i]; } if ( $c == "\n" ) { // Explicit line break if ( $this->unifontSubset ) { $this->Cell( $w, $h, mb_substr( $s, $j, $i - $j, 'UTF-8' ), 0, 2, '', false, $link ); } else { $this->Cell( $w, $h, substr( $s, $j, $i - $j ), 0, 2, '', false, $link ); } $i++; $sep = -1; $j = $i; $l = 0; if ( $nl == 1 ) { $this->x = $this->lMargin; $w = $this->w - $this->rMargin - $this->x; $wmax = ( $w - 2 * $this->cMargin ); } $nl++; continue; } if ( $c == ' ' ) { $sep = $i; } if ( $this->unifontSubset ) {$l += $this->GetStringWidth( $c );} else { $l += $cw[$c] * $this->FontSize / 1000;} if ( $l > $wmax ) { // Automatic line break if ( $sep == -1 ) { if ( $this->x > $this->lMargin ) { // Move to next line $this->x = $this->lMargin; $this->y += $h; $w = $this->w - $this->rMargin - $this->x; $wmax = ( $w - 2 * $this->cMargin ); $i++; $nl++; continue; } if ( $i == $j ) { $i++; } if ( $this->unifontSubset ) { $this->Cell( $w, $h, mb_substr( $s, $j, $i - $j, 'UTF-8' ), 0, 2, '', false, $link ); } else { $this->Cell( $w, $h, substr( $s, $j, $i - $j ), 0, 2, '', false, $link ); } } else { if ( $this->unifontSubset ) { $this->Cell( $w, $h, mb_substr( $s, $j, $sep - $j, 'UTF-8' ), 0, 2, '', false, $link ); } else { $this->Cell( $w, $h, substr( $s, $j, $sep - $j ), 0, 2, '', false, $link ); } $i = $sep + 1; } $sep = -1; $j = $i; $l = 0; if ( $nl == 1 ) { $this->x = $this->lMargin; $w = $this->w - $this->rMargin - $this->x; $wmax = ( $w - 2 * $this->cMargin ); } $nl++; } else { $i++; } } // Last chunk if ( $i != $j ) { if ( $this->unifontSubset ) { $this->Cell( $l, $h, mb_substr( $s, $j, $i - $j, 'UTF-8' ), 0, 0, '', false, $link ); } else { $this->Cell( $l, $h, substr( $s, $j ), 0, 0, '', false, $link ); } } } function Ln( $h = null ) { // Line feed; default value is the last cell height $this->x = $this->lMargin; if ( $h === null ) { $this->y += $this->lasth; } else { $this->y += $h; } } function Image( $file, $x = null, $y = null, $w = 0, $h = 0, $type = '', $link = '' ) { // Put an image on the page if ( $file == '' ) { $this->Error( 'Image file name is empty' ); } if ( !isset( $this->images[$file] ) ) { // First use of this image, get info if ( $type == '' ) { $pos = strrpos( $file, '.' ); if ( !$pos ) { $this->Error( 'Image file has no extension and no type was specified: ' . $file ); } $type = substr( $file, $pos + 1 ); } $type = strtolower( $type ); if ( $type == 'jpeg' ) { $type = 'jpg'; } $mtd = '_parse' . $type; if ( !method_exists( $this, $mtd ) ) { $this->Error( 'Unsupported image type: ' . $type ); } $info = $this->$mtd( $file ); $info['i'] = count( $this->images ) + 1; $this->images[$file] = $info; } else { $info = $this->images[$file]; } // Automatic width and height calculation if needed if ( $w == 0 && $h == 0 ) { // Put image at 96 dpi $w = -96; $h = -96; } if ( $w < 0 ) { $w = -$info['w'] * 72 / $w / $this->k; } if ( $h < 0 ) { $h = -$info['h'] * 72 / $h / $this->k; } if ( $w == 0 ) { $w = $h * $info['w'] / $info['h']; } if ( $h == 0 ) { $h = $w * $info['h'] / $info['w']; } // Flowing mode if ( $y === null ) { if ( $this->y + $h > $this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak() ) { // Automatic page break $x2 = $this->x; $this->AddPage( $this->CurOrientation, $this->CurPageSize, $this->CurRotation ); $this->x = $x2; } $y = $this->y; $this->y += $h; } if ( $x === null ) { $x = $this->x; } $this->_out( sprintf( 'q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q', $w * $this->k, $h * $this->k, $x * $this->k, ( $this->h - ( $y + $h ) ) * $this->k, $info['i'] ) ); if ( $link ) { $this->Link( $x, $y, $w, $h, $link ); } } function GetPageWidth() { // Get current page width return $this->w; } function GetPageHeight() { // Get current page height return $this->h; } function GetX() { // Get x position return $this->x; } function SetX( $x ) { // Set x position if ( $x >= 0 ) { $this->x = $x; } else { $this->x = $this->w + $x; } } function GetY() { // Get y position return $this->y; } function SetY( $y, $resetX = true ) { // Set y position and optionally reset x if ( $y >= 0 ) { $this->y = $y; } else { $this->y = $this->h + $y; } if ( $resetX ) { $this->x = $this->lMargin; } } function SetXY( $x, $y ) { // Set x and y positions $this->SetX( $x ); $this->SetY( $y, false ); } function Output( $dest = '', $name = '', $isUTF8 = false ) { // Output PDF to some destination $this->Close(); if ( strlen( $name ) == 1 && strlen( $dest ) != 1 ) { // Fix parameter order $tmp = $dest; $dest = $name; $name = $tmp; } if ( $dest == '' ) { $dest = 'I'; } if ( $name == '' ) { $name = 'doc.pdf'; } switch ( strtoupper( $dest ) ) { case 'I': // Send to standard output $this->_checkoutput(); if ( PHP_SAPI != 'cli' ) { // We send to a browser header( 'Content-Type: application/pdf' ); header( 'Content-Disposition: inline; ' . $this->_httpencode( 'filename', $name, $isUTF8 ) ); header( 'Cache-Control: private, max-age=0, must-revalidate' ); header( 'Pragma: public' ); } echo Helper::render( $this->buffer ); break; case 'D': // Download file $this->_checkoutput(); header( 'Content-Type: application/x-download' ); header( 'Content-Disposition: attachment; ' . $this->_httpencode( 'filename', $name, $isUTF8 ) ); header( 'Cache-Control: private, max-age=0, must-revalidate' ); header( 'Pragma: public' ); echo Helper::render( $this->buffer ); break; case 'F': // Save to local file if ( !file_put_contents( $name, $this->buffer ) ) { $this->Error( 'Unable to create output file: ' . $name ); } break; case 'S': // Return as a string return $this->buffer; default: $this->Error( 'Incorrect output destination: ' . $dest ); } return ''; } /******************************************************************************* * Protected methods * *******************************************************************************/ protected function _dochecks() { // Check availability of mbstring if ( !function_exists( 'mb_strlen' ) ) { $this->Error( 'mbstring extension is not available' ); } // Check mbstring overloading if ( ini_get( 'mbstring.func_overload' ) & 2 ) { $this->Error( 'mbstring overloading must be disabled' ); } } protected function _checkoutput() { if ( PHP_SAPI != 'cli' ) { if ( headers_sent( $file, $line ) ) { $this->Error( "Some data has already been output, can't send PDF file (output started at $file:$line)" ); } } if ( ob_get_length() ) { // The output buffer is not empty if ( preg_match( '/^(\xEF\xBB\xBF)?\s*$/', ob_get_contents() ) ) { // It contains only a UTF-8 BOM and/or whitespace, let's clean it ob_clean(); } else { $this->Error( "Some data has already been output, can't send PDF file" ); } } } protected function _getpagesize( $size ) { if ( is_string( $size ) ) { $size = strtolower( $size ); if ( !isset( $this->StdPageSizes[$size] ) ) { $this->Error( 'Unknown page size: ' . $size ); } $a = $this->StdPageSizes[$size]; return [ $a[0] / $this->k, $a[1] / $this->k ]; } else { if ( $size[0] > $size[1] ) { return [ $size[1], $size[0] ]; } else { return $size; } } } protected function _beginpage( $orientation, $size, $rotation ) { $this->page++; $this->pages[$this->page] = ''; $this->state = 2; $this->x = $this->lMargin; $this->y = $this->tMargin; $this->FontFamily = ''; // Check page size and orientation if ( $orientation == '' ) { $orientation = $this->DefOrientation; } else { $orientation = strtoupper( $orientation[0] ); } if ( $size == '' ) { $size = $this->DefPageSize; } else { $size = $this->_getpagesize( $size ); } if ( $orientation != $this->CurOrientation || $size[0] != $this->CurPageSize[0] || $size[1] != $this->CurPageSize[1] ) { // New size or orientation if ( $orientation == 'P' ) { $this->w = $size[0]; $this->h = $size[1]; } else { $this->w = $size[1]; $this->h = $size[0]; } $this->wPt = $this->w * $this->k; $this->hPt = $this->h * $this->k; $this->PageBreakTrigger = $this->h - $this->bMargin; $this->CurOrientation = $orientation; $this->CurPageSize = $size; } if ( $orientation != $this->DefOrientation || $size[0] != $this->DefPageSize[0] || $size[1] != $this->DefPageSize[1] ) { $this->PageInfo[$this->page]['size'] = [ $this->wPt, $this->hPt ]; } if ( $rotation != 0 ) { if ( $rotation % 90 != 0 ) { $this->Error( 'Incorrect rotation value: ' . $rotation ); } $this->CurRotation = $rotation; $this->PageInfo[$this->page]['rotation'] = $rotation; } } protected function _endpage() { $this->state = 1; } protected function _loadfont( $font ) { // Load a font definition file from the font directory if ( strpos( $font, '/' ) !== false || strpos( $font, "\\" ) !== false ) { $this->Error( 'Incorrect font definition file name: ' . $font ); } include $this->fontpath . $font; if ( !isset( $name ) ) { $this->Error( 'Could not include font definition file' ); } if ( isset( $enc ) ) { $enc = strtolower( $enc ); } if ( !isset( $subsetted ) ) { $subsetted = false; } return get_defined_vars(); } protected function _isascii( $s ) { // Test if string is ASCII $nb = strlen( $s ); for ( $i = 0; $i < $nb; $i++ ) { if ( ord( $s[$i] ) > 127 ) { return false; } } return true; } protected function _httpencode( $param, $value, $isUTF8 ) { // Encode HTTP header field parameter if ( $this->_isascii( $value ) ) { return $param . '="' . $value . '"'; } if ( !$isUTF8 ) { $value = utf8_encode( $value ); } if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) { return $param . '="' . rawurlencode( $value ) . '"'; } else { return $param . "*=UTF-8''" . rawurlencode( $value ); } } protected function _UTF8toUTF16( $s ) { // Convert UTF-8 to UTF-16BE with BOM $res = "\xFE\xFF"; $nb = strlen( $s ); $i = 0; while ( $i < $nb ) { $c1 = ord( $s[$i++] ); if ( $c1 >= 224 ) { // 3-byte character $c2 = ord( $s[$i++] ); $c3 = ord( $s[$i++] ); $res .= chr( ( ( $c1 & 0x0F ) << 4 ) + ( ( $c2 & 0x3C ) >> 2 ) ); $res .= chr( ( ( $c2 & 0x03 ) << 6 ) + ( $c3 & 0x3F ) ); } elseif ( $c1 >= 192 ) { // 2-byte character $c2 = ord( $s[$i++] ); $res .= chr( ( $c1 & 0x1C ) >> 2 ); $res .= chr( ( ( $c1 & 0x03 ) << 6 ) + ( $c2 & 0x3F ) ); } else { // Single-byte character $res .= "\0" . chr( $c1 ); } } return $res; } protected function _escape( $s ) { // Escape special characters if ( strpos( $s, '(' ) !== false || strpos( $s, ')' ) !== false || strpos( $s, '\\' ) !== false || strpos( $s, "\r" ) !== false ) { return str_replace( [ '\\', '(', ')', "\r" ], [ '\\\\', '\\(', '\\)', '\\r' ], $s ); } else { return $s; } } protected function _textstring( $s ) { // Format a text string if ( !$this->_isascii( $s ) ) { $s = $this->_UTF8toUTF16( $s ); } return '(' . $this->_escape( $s ) . ')'; } protected function _dounderline( $x, $y, $txt ) { // Underline text $up = $this->CurrentFont['up']; $ut = $this->CurrentFont['ut']; $w = $this->GetStringWidth( $txt ) + $this->ws * substr_count( $txt, ' ' ); return sprintf( '%.2F %.2F %.2F %.2F re f', $x * $this->k, ( $this->h - ( $y - $up / 1000 * $this->FontSize ) ) * $this->k, $w * $this->k, -$ut / 1000 * $this->FontSizePt ); } protected function _parsejpg( $file ) { // Extract info from a JPEG file $a = getimagesize( $file ); if ( !$a ) { $this->Error( 'Missing or incorrect image file: ' . $file ); } if ( $a[2] != 2 ) { $this->Error( 'Not a JPEG file: ' . $file ); } if ( !isset( $a['channels'] ) || $a['channels'] == 3 ) { $colspace = 'DeviceRGB'; } elseif ( $a['channels'] == 4 ) { $colspace = 'DeviceCMYK'; } else { $colspace = 'DeviceGray'; } $bpc = isset( $a['bits'] ) ? $a['bits'] : 8; $data = file_get_contents( $file ); return [ 'w' => $a[0], 'h' => $a[1], 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data ]; } protected function _parsepng( $file ) { // Extract info from a PNG file $f = fopen( $file, 'rb' ); if ( !$f ) { $this->Error( 'Can\'t open image file: ' . $file ); } $info = $this->_parsepngstream( $f, $file ); fclose( $f ); return $info; } protected function _parsepngstream( $f, $file ) { // Check signature if ( $this->_readstream( $f, 8 ) != chr( 137 ) . 'PNG' . chr( 13 ) . chr( 10 ) . chr( 26 ) . chr( 10 ) ) { $this->Error( 'Not a PNG file: ' . $file ); } // Read header chunk $this->_readstream( $f, 4 ); if ( $this->_readstream( $f, 4 ) != 'IHDR' ) { $this->Error( 'Incorrect PNG file: ' . $file ); } $w = $this->_readint( $f ); $h = $this->_readint( $f ); $bpc = ord( $this->_readstream( $f, 1 ) ); if ( $bpc > 8 ) { $this->Error( '16-bit depth not supported: ' . $file ); } $ct = ord( $this->_readstream( $f, 1 ) ); if ( $ct == 0 || $ct == 4 ) { $colspace = 'DeviceGray'; } elseif ( $ct == 2 || $ct == 6 ) { $colspace = 'DeviceRGB'; } elseif ( $ct == 3 ) { $colspace = 'Indexed'; } else { $this->Error( 'Unknown color type: ' . $file ); } if ( ord( $this->_readstream( $f, 1 ) ) != 0 ) { $this->Error( 'Unknown compression method: ' . $file ); } if ( ord( $this->_readstream( $f, 1 ) ) != 0 ) { $this->Error( 'Unknown filter method: ' . $file ); } if ( ord( $this->_readstream( $f, 1 ) ) != 0 ) { $this->Error( 'Interlacing not supported: ' . $file ); } $this->_readstream( $f, 4 ); $dp = '/Predictor 15 /Colors ' . ( $colspace == 'DeviceRGB' ? 3 : 1 ) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; // Scan chunks looking for palette, transparency and image data $pal = ''; $trns = ''; $data = ''; do { $n = $this->_readint( $f ); $type = $this->_readstream( $f, 4 ); if ( $type == 'PLTE' ) { // Read palette $pal = $this->_readstream( $f, $n ); $this->_readstream( $f, 4 ); } elseif ( $type == 'tRNS' ) { // Read transparency info $t = $this->_readstream( $f, $n ); if ( $ct == 0 ) { $trns = [ ord( substr( $t, 1, 1 ) ) ]; } elseif ( $ct == 2 ) { $trns = [ ord( substr( $t, 1, 1 ) ), ord( substr( $t, 3, 1 ) ), ord( substr( $t, 5, 1 ) ) ]; } else { $pos = strpos( $t, chr( 0 ) ); if ( $pos !== false ) { $trns = [ $pos ]; } } $this->_readstream( $f, 4 ); } elseif ( $type == 'IDAT' ) { // Read image data block $data .= $this->_readstream( $f, $n ); $this->_readstream( $f, 4 ); } elseif ( $type == 'IEND' ) { break; } else { $this->_readstream( $f, $n + 4 ); } } while ( $n ); if ( $colspace == 'Indexed' && empty( $pal ) ) { $this->Error( 'Missing palette in ' . $file ); } $info = [ 'w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'dp' => $dp, 'pal' => $pal, 'trns' => $trns ]; if ( $ct >= 4 ) { // Extract alpha channel if ( !function_exists( 'gzuncompress' ) ) { $this->Error( 'Zlib not available, can\'t handle alpha channel: ' . $file ); } $data = gzuncompress( $data ); $color = ''; $alpha = ''; if ( $ct == 4 ) { // Gray image $len = 2 * $w; for ( $i = 0; $i < $h; $i++ ) { $pos = ( 1 + $len ) * $i; $color .= $data[$pos]; $alpha .= $data[$pos]; $line = substr( $data, $pos + 1, $len ); $color .= preg_replace( '/(.)./s', '$1', $line ); $alpha .= preg_replace( '/.(.)/s', '$1', $line ); } } else { // RGB image $len = 4 * $w; for ( $i = 0; $i < $h; $i++ ) { $pos = ( 1 + $len ) * $i; $color .= $data[$pos]; $alpha .= $data[$pos]; $line = substr( $data, $pos + 1, $len ); $color .= preg_replace( '/(.{3})./s', '$1', $line ); $alpha .= preg_replace( '/.{3}(.)/s', '$1', $line ); } } unset( $data ); $data = gzcompress( $color ); $info['smask'] = gzcompress( $alpha ); $this->WithAlpha = true; if ( $this->PDFVersion < '1.4' ) { $this->PDFVersion = '1.4'; } } $info['data'] = $data; return $info; } protected function _readstream( $f, $n ) { // Read n bytes from stream $res = ''; while ( $n > 0 && !feof( $f ) ) { $s = fread( $f, $n ); if ( $s === false ) { $this->Error( 'Error while reading stream' ); } $n -= strlen( $s ); $res .= $s; } if ( $n > 0 ) { $this->Error( 'Unexpected end of stream' ); } return $res; } protected function _readint( $f ) { // Read a 4-byte integer from stream $a = unpack( 'Ni', $this->_readstream( $f, 4 ) ); return $a['i']; } protected function _parsegif( $file ) { // Extract info from a GIF file (via PNG conversion) if ( !function_exists( 'imagepng' ) ) { $this->Error( 'GD extension is required for GIF support' ); } if ( !function_exists( 'imagecreatefromgif' ) ) { $this->Error( 'GD has no GIF read support' ); } $im = imagecreatefromgif( $file ); if ( !$im ) { $this->Error( 'Missing or incorrect image file: ' . $file ); } imageinterlace( $im, 0 ); ob_start(); imagepng( $im ); $data = ob_get_clean(); imagedestroy( $im ); $f = fopen( 'php://temp', 'rb+' ); if ( !$f ) { $this->Error( 'Unable to create memory stream' ); } fwrite( $f, $data ); rewind( $f ); $info = $this->_parsepngstream( $f, $file ); fclose( $f ); return $info; } protected function _out( $s ) { // Add a line to the document if ( $this->state == 2 ) { $this->pages[$this->page] .= $s . "\n"; } elseif ( $this->state == 1 ) { $this->_put( $s ); } elseif ( $this->state == 0 ) { $this->Error( 'No page has been added yet' ); } elseif ( $this->state == 3 ) { $this->Error( 'The document is closed' ); } } protected function _put( $s ) { $this->buffer .= $s . "\n"; } protected function _getoffset() { return strlen( $this->buffer ); } protected function _newobj( $n = null ) { // Begin a new object if ( $n === null ) { $n = ++$this->n; } $this->offsets[$n] = $this->_getoffset(); $this->_put( $n . ' 0 obj' ); } protected function _putstream( $data ) { $this->_put( 'stream' ); $this->_put( $data ); $this->_put( 'endstream' ); } protected function _putstreamobject( $data ) { if ( $this->compress ) { $entries = '/Filter /FlateDecode '; $data = gzcompress( $data ); } else { $entries = ''; } $entries .= '/Length ' . strlen( $data ); $this->_newobj(); $this->_put( '<<' . $entries . '>>' ); $this->_putstream( $data ); $this->_put( 'endobj' ); } protected function _putpage( $n ) { $this->_newobj(); $this->_put( '<_put( '/Parent 1 0 R' ); if ( isset( $this->PageInfo[$n]['size'] ) ) { $this->_put( sprintf( '/MediaBox [0 0 %.2F %.2F]', $this->PageInfo[$n]['size'][0], $this->PageInfo[$n]['size'][1] ) ); } if ( isset( $this->PageInfo[$n]['rotation'] ) ) { $this->_put( '/Rotate ' . $this->PageInfo[$n]['rotation'] ); } $this->_put( '/Resources 2 0 R' ); if ( isset( $this->PageLinks[$n] ) ) { // Links $annots = '/Annots ['; foreach ( $this->PageLinks[$n] as $pl ) { $rect = sprintf( '%.2F %.2F %.2F %.2F', $pl[0], $pl[1], $pl[0] + $pl[2], $pl[1] - $pl[3] ); $annots .= '<_textstring( $pl[4] ) . '>>>>'; } else { $l = $this->links[$pl[4]]; if ( isset( $this->PageInfo[$l[0]]['size'] ) ) { $h = $this->PageInfo[$l[0]]['size'][1]; } else { $h = ( $this->DefOrientation == 'P' ) ? $this->DefPageSize[1] * $this->k : $this->DefPageSize[0] * $this->k; } $annots .= sprintf( '/Dest [%d 0 R /XYZ 0 %.2F null]>>', $this->PageInfo[$l[0]]['n'], $h - $l[1] * $this->k ); } } $this->_put( $annots . ']' ); } if ( $this->WithAlpha ) { $this->_put( '/Group <>' ); } $this->_put( '/Contents ' . ( $this->n + 1 ) . ' 0 R>>' ); $this->_put( 'endobj' ); // Page content if ( !empty( $this->AliasNbPages ) ) { $alias = $this->UTF8ToUTF16BE( $this->AliasNbPages, false ); $r = $this->UTF8ToUTF16BE( $this->page, false ); $this->pages[$n] = str_replace( $alias, $r, $this->pages[$n] ); // Now repeat for no pages in non-subset fonts $this->pages[$n] = str_replace( $this->AliasNbPages, $this->page, $this->pages[$n] ); } $this->_putstreamobject( $this->pages[$n] ); } protected function _putpages() { $nb = $this->page; for ( $n = 1; $n <= $nb; $n++ ) { $this->PageInfo[$n]['n'] = $this->n + 1 + 2 * ( $n - 1 ); } for ( $n = 1; $n <= $nb; $n++ ) { $this->_putpage( $n ); } // Pages root $this->_newobj( 1 ); $this->_put( '<PageInfo[$n]['n'] . ' 0 R '; } $this->_put( $kids . ']' ); $this->_put( '/Count ' . $nb ); if ( $this->DefOrientation == 'P' ) { $w = $this->DefPageSize[0]; $h = $this->DefPageSize[1]; } else { $w = $this->DefPageSize[1]; $h = $this->DefPageSize[0]; } $this->_put( sprintf( '/MediaBox [0 0 %.2F %.2F]', $w * $this->k, $h * $this->k ) ); $this->_put( '>>' ); $this->_put( 'endobj' ); } protected function _putfonts() { foreach ( $this->FontFiles as $file => $info ) { if ( !isset( $info['type'] ) || $info['type'] != 'TTF' ) { // Font file embedding $this->_newobj(); $this->FontFiles[$file]['n'] = $this->n; $font = file_get_contents( $this->fontpath . $file, true ); if ( !$font ) { $this->Error( 'Font file not found: ' . $file ); } $compressed = ( substr( $file, -2 ) == '.z' ); if ( !$compressed && isset( $info['length2'] ) ) { $font = substr( $font, 6, $info['length1'] ) . substr( $font, 6 + $info['length1'] + 6, $info['length2'] ); } $this->_put( '<_put( '/Filter /FlateDecode' ); } $this->_put( '/Length1 ' . $info['length1'] ); if ( isset( $info['length2'] ) ) { $this->_put( '/Length2 ' . $info['length2'] . ' /Length3 0' ); } $this->_put( '>>' ); $this->_putstream( $font ); $this->_put( 'endobj' ); } } foreach ( $this->fonts as $k => $font ) { // Encoding if ( isset( $font['diff'] ) ) { if ( !isset( $this->encodings[$font['enc']] ) ) { $this->_newobj(); $this->_put( '<>' ); $this->_put( 'endobj' ); $this->encodings[$font['enc']] = $this->n; } } // ToUnicode CMap if ( isset( $font['uv'] ) ) { if ( isset( $font['enc'] ) ) { $cmapkey = $font['enc']; } else { $cmapkey = $font['name']; } if ( !isset( $this->cmaps[$cmapkey] ) ) { $cmap = $this->_tounicodecmap( $font['uv'] ); $this->_putstreamobject( $cmap ); $this->cmaps[$cmapkey] = $this->n; } } // Font object $type = $font['type']; $name = $font['name']; if ( $type == 'Core' ) { // Core font $this->fonts[$k]['n'] = $this->n + 1; $this->_newobj(); $this->_put( '<_put( '/BaseFont /' . $name ); $this->_put( '/Subtype /Type1' ); if ( $name != 'Symbol' && $name != 'ZapfDingbats' ) { $this->_put( '/Encoding /WinAnsiEncoding' ); } if ( isset( $font['uv'] ) ) { $this->_put( '/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R' ); } $this->_put( '>>' ); $this->_put( 'endobj' ); } elseif ( $type == 'Type1' || $type == 'TrueType' ) { // Additional Type1 or TrueType/OpenType font if ( isset( $font['subsetted'] ) && $font['subsetted'] ) { $name = 'AAAAAA+' . $name; } $this->fonts[$k]['n'] = $this->n + 1; $this->_newobj(); $this->_put( '<_put( '/BaseFont /' . $name ); $this->_put( '/Subtype /' . $type ); $this->_put( '/FirstChar 32 /LastChar 255' ); $this->_put( '/Widths ' . ( $this->n + 1 ) . ' 0 R' ); $this->_put( '/FontDescriptor ' . ( $this->n + 2 ) . ' 0 R' ); if ( $font['enc'] ) { if ( isset( $font['diff'] ) ) { $this->_put( '/Encoding ' . $this->encodings[$font['enc']] . ' 0 R' ); } else { $this->_put( '/Encoding /WinAnsiEncoding' ); } } if ( isset( $font['uv'] ) ) { $this->_put( '/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R' ); } $this->_put( '>>' ); $this->_put( 'endobj' ); // Widths $this->_newobj(); $cw = &$font['cw']; $s = '['; for ( $i = 32; $i <= 255; $i++ ) { $s .= $cw[chr( $i )] . ' '; } $this->_put( $s . ']' ); $this->_put( 'endobj' ); // Descriptor $this->_newobj(); $s = '< $v ) { $s .= ' /' . $k . ' ' . $v; } if ( !empty( $font['file'] ) ) { $s .= ' /FontFile' . ( $type == 'Type1' ? '' : '2' ) . ' ' . $this->FontFiles[$font['file']]['n'] . ' 0 R'; } $this->_put( $s . '>>' ); $this->_put( 'endobj' ); } // TrueType embedded SUBSETS or FULL elseif ( $type == 'TTF' ) { $this->fonts[$k]['n'] = $this->n + 1; require_once $this->fontpath . 'unifont/ttfonts.php'; $ttf = new \Etn\Utils\Font\Unifont\TTFontFile(); $fontname = 'MPDFAA' . '+' . $font['name']; $subset = $font['subset']; unset( $subset[0] ); $font['ttffile'] = \Wpeventin::utils_dir() . "font/unifont/DejaVuSansCondensed.ttf"; $ttfontstream = $ttf->makeSubset( $font['ttffile'], $subset ); $ttfontsize = strlen( $ttfontstream ); $fontstream = gzcompress( $ttfontstream ); $codeToGlyph = $ttf->codeToGlyph; unset( $codeToGlyph[0] ); // Type0 Font // A composite font - a font composed of other fonts, organized hierarchically $this->_newobj(); $this->_put( '<_put( '/Subtype /Type0' ); $this->_put( '/BaseFont /' . $fontname . '' ); $this->_put( '/Encoding /Identity-H' ); $this->_put( '/DescendantFonts [' . ( $this->n + 1 ) . ' 0 R]' ); $this->_put( '/ToUnicode ' . ( $this->n + 2 ) . ' 0 R' ); $this->_put( '>>' ); $this->_put( 'endobj' ); // CIDFontType2 // A CIDFont whose glyph descriptions are based on TrueType font technology $this->_newobj(); $this->_put( '<_put( '/Subtype /CIDFontType2' ); $this->_put( '/BaseFont /' . $fontname . '' ); $this->_put( '/CIDSystemInfo ' . ( $this->n + 2 ) . ' 0 R' ); $this->_put( '/FontDescriptor ' . ( $this->n + 3 ) . ' 0 R' ); if ( isset( $font['desc']['MissingWidth'] ) ) { $this->_out( '/DW ' . $font['desc']['MissingWidth'] . '' ); } $this->_putTTfontwidths( $font, $ttf->maxUni ); $this->_put( '/CIDToGIDMap ' . ( $this->n + 4 ) . ' 0 R' ); $this->_put( '>>' ); $this->_put( 'endobj' ); // ToUnicode $this->_newobj(); $toUni = "/CIDInit /ProcSet findresource begin\n"; $toUni .= "12 dict begin\n"; $toUni .= "begincmap\n"; $toUni .= "/CIDSystemInfo\n"; $toUni .= "<_put( '<>' ); $this->_putstream( $toUni ); $this->_put( 'endobj' ); // CIDSystemInfo dictionary $this->_newobj(); $this->_put( '<_put( '/Ordering (UCS)' ); $this->_put( '/Supplement 0' ); $this->_put( '>>' ); $this->_put( 'endobj' ); // Font descriptor $this->_newobj(); $this->_put( '<_put( '/FontName /' . $fontname ); foreach ( $font['desc'] as $kd => $v ) { if ( $kd == 'Flags' ) {$v = $v | 4; $v = $v & ~32;} // SYMBOLIC font flag $this->_out( ' /' . $kd . ' ' . $v ); } $this->_put( '/FontFile2 ' . ( $this->n + 2 ) . ' 0 R' ); $this->_put( '>>' ); $this->_put( 'endobj' ); // Embed CIDToGIDMap // A specification of the mapping from CIDs to glyph indices $cidtogidmap = ''; $cidtogidmap = str_pad( '', 256 * 256 * 2, "\x00" ); foreach ( $codeToGlyph as $cc => $glyph ) { $cidtogidmap[$cc * 2] = chr( $glyph >> 8 ); $cidtogidmap[$cc * 2 + 1] = chr( $glyph & 0xFF ); } $cidtogidmap = gzcompress( $cidtogidmap ); $this->_newobj(); $this->_put( '<_put( '/Filter /FlateDecode' ); $this->_put( '>>' ); $this->_putstream( $cidtogidmap ); $this->_put( 'endobj' ); //Font file $this->_newobj(); $this->_put( '<_put( '/Filter /FlateDecode' ); $this->_put( '/Length1 ' . $ttfontsize ); $this->_put( '>>' ); $this->_putstream( $fontstream ); $this->_put( 'endobj' ); unset( $ttf ); } else { // Allow for additional types $this->fonts[$k]['n'] = $this->n + 1; $mtd = '_put' . strtolower( $type ); if ( !method_exists( $this, $mtd ) ) { $this->Error( 'Unsupported font type: ' . $type ); } $this->$mtd( $font ); } } } protected function _putTTfontwidths( &$font, $maxUni ) { if ( file_exists( $font['unifilename'] . '.cw127.php' ) ) { include $font['unifilename'] . '.cw127.php'; $startcid = 128; } else { $rangeid = 0; $range = []; $prevcid = -2; $prevwidth = -1; $interval = false; $startcid = 1; } $cwlen = $maxUni + 1; // for each character for ( $cid = $startcid; $cid < $cwlen; $cid++ ) { if ( $cid == 128 && ( !file_exists( $font['unifilename'] . '.cw127.php' ) ) ) { if ( is_writable( dirname( $this->fontpath . 'unifont/x' ) ) ) { $fh = fopen( $font['unifilename'] . '.cw127.php', "wb" ); $cw127 = '"; fwrite( $fh, $cw127, strlen( $cw127 ) ); fclose( $fh ); } } if ( ( !isset( $font['cw'][$cid * 2] ) || !isset( $font['cw'][$cid * 2 + 1] ) ) || ( $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] = []; $range[$rangeid][] = $prevwidth; $range[$rangeid][] = $width; } $interval = true; $range[$rangeid]['interval'] = true; } else { if ( $interval ) { // new range $rangeid = $cid; $range[$rangeid] = []; $range[$rangeid][] = $width; } else { $range[$rangeid][] = $width;} $interval = false; } } else { $rangeid = $cid; $range[$rangeid] = []; $range[$rangeid][] = $width; $interval = false; } $prevcid = $cid; $prevwidth = $width; } } $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 . ' ]' ); } protected function _tounicodecmap( $uv ) { $ranges = ''; $nbr = 0; $chars = ''; $nbc = 0; foreach ( $uv as $c => $v ) { if ( is_array( $v ) ) { $ranges .= sprintf( "<%02X> <%02X> <%04X>\n", $c, $c + $v[1] - 1, $v[0] ); $nbr++; } else { $chars .= sprintf( "<%02X> <%04X>\n", $c, $v ); $nbc++; } } $s = "/CIDInit /ProcSet findresource begin\n"; $s .= "12 dict begin\n"; $s .= "begincmap\n"; $s .= "/CIDSystemInfo\n"; $s .= "< 0 ) { $s .= "$nbr beginbfrange\n"; $s .= $ranges; $s .= "endbfrange\n"; } if ( $nbc > 0 ) { $s .= "$nbc beginbfchar\n"; $s .= $chars; $s .= "endbfchar\n"; } $s .= "endcmap\n"; $s .= "CMapName currentdict /CMap defineresource pop\n"; $s .= "end\n"; $s .= "end"; return $s; } protected function _putimages() { foreach ( array_keys( $this->images ) as $file ) { $this->_putimage( $this->images[$file] ); unset( $this->images[$file]['data'] ); unset( $this->images[$file]['smask'] ); } } protected function _putimage( &$info ) { $this->_newobj(); $info['n'] = $this->n; $this->_put( '<_put( '/Subtype /Image' ); $this->_put( '/Width ' . $info['w'] ); $this->_put( '/Height ' . $info['h'] ); if ( $info['cs'] == 'Indexed' ) { $this->_put( '/ColorSpace [/Indexed /DeviceRGB ' . ( strlen( $info['pal'] ) / 3 - 1 ) . ' ' . ( $this->n + 1 ) . ' 0 R]' ); } else { $this->_put( '/ColorSpace /' . $info['cs'] ); if ( $info['cs'] == 'DeviceCMYK' ) { $this->_put( '/Decode [1 0 1 0 1 0 1 0]' ); } } $this->_put( '/BitsPerComponent ' . $info['bpc'] ); if ( isset( $info['f'] ) ) { $this->_put( '/Filter /' . $info['f'] ); } if ( isset( $info['dp'] ) ) { $this->_put( '/DecodeParms <<' . $info['dp'] . '>>' ); } if ( isset( $info['trns'] ) && is_array( $info['trns'] ) ) { $trns = ''; for ( $i = 0; $i < count( $info['trns'] ); $i++ ) { $trns .= $info['trns'][$i] . ' ' . $info['trns'][$i] . ' '; } $this->_put( '/Mask [' . $trns . ']' ); } if ( isset( $info['smask'] ) ) { $this->_put( '/SMask ' . ( $this->n + 1 ) . ' 0 R' ); } $this->_put( '/Length ' . strlen( $info['data'] ) . '>>' ); $this->_putstream( $info['data'] ); $this->_put( 'endobj' ); // Soft mask if ( isset( $info['smask'] ) ) { $dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' . $info['w']; $smask = [ 'w' => $info['w'], 'h' => $info['h'], 'cs' => 'DeviceGray', 'bpc' => 8, 'f' => $info['f'], 'dp' => $dp, 'data' => $info['smask'] ]; $this->_putimage( $smask ); } // Palette if ( $info['cs'] == 'Indexed' ) { $this->_putstreamobject( $info['pal'] ); } } protected function _putxobjectdict() { foreach ( $this->images as $image ) { $this->_put( '/I' . $image['i'] . ' ' . $image['n'] . ' 0 R' ); } } protected function _putresourcedict() { $this->_put( '/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]' ); $this->_put( '/Font <<' ); foreach ( $this->fonts as $font ) { $this->_put( '/F' . $font['i'] . ' ' . $font['n'] . ' 0 R' ); } $this->_put( '>>' ); $this->_put( '/XObject <<' ); $this->_putxobjectdict(); $this->_put( '>>' ); } protected function _putresources() { $this->_putfonts(); $this->_putimages(); // Resource dictionary $this->_newobj( 2 ); $this->_put( '<<' ); $this->_putresourcedict(); $this->_put( '>>' ); $this->_put( 'endobj' ); } protected function _putinfo() { $this->metadata['Producer'] = 'tFPDF ' . tFPDF_VERSION; $this->metadata['CreationDate'] = 'D:' . @date( 'YmdHis' ); foreach ( $this->metadata as $key => $value ) { $this->_put( '/' . $key . ' ' . $this->_textstring( $value ) ); } } protected function _putcatalog() { $n = $this->PageInfo[1]['n']; $this->_put( '/Type /Catalog' ); $this->_put( '/Pages 1 0 R' ); if ( $this->ZoomMode == 'fullpage' ) { $this->_put( '/OpenAction [' . $n . ' 0 R /Fit]' ); } elseif ( $this->ZoomMode == 'fullwidth' ) { $this->_put( '/OpenAction [' . $n . ' 0 R /FitH null]' ); } elseif ( $this->ZoomMode == 'real' ) { $this->_put( '/OpenAction [' . $n . ' 0 R /XYZ null null 1]' ); } elseif ( !is_string( $this->ZoomMode ) ) { $this->_put( '/OpenAction [' . $n . ' 0 R /XYZ null null ' . sprintf( '%.2F', $this->ZoomMode / 100 ) . ']' ); } if ( $this->LayoutMode == 'single' ) { $this->_put( '/PageLayout /SinglePage' ); } elseif ( $this->LayoutMode == 'continuous' ) { $this->_put( '/PageLayout /OneColumn' ); } elseif ( $this->LayoutMode == 'two' ) { $this->_put( '/PageLayout /TwoColumnLeft' ); } } protected function _putheader() { $this->_put( '%PDF-' . $this->PDFVersion ); } protected function _puttrailer() { $this->_put( '/Size ' . ( $this->n + 1 ) ); $this->_put( '/Root ' . $this->n . ' 0 R' ); $this->_put( '/Info ' . ( $this->n - 1 ) . ' 0 R' ); } protected function _enddoc() { $this->_putheader(); $this->_putpages(); $this->_putresources(); // Info $this->_newobj(); $this->_put( '<<' ); $this->_putinfo(); $this->_put( '>>' ); $this->_put( 'endobj' ); // Catalog $this->_newobj(); $this->_put( '<<' ); $this->_putcatalog(); $this->_put( '>>' ); $this->_put( 'endobj' ); // Cross-ref $offset = $this->_getoffset(); $this->_put( 'xref' ); $this->_put( '0 ' . ( $this->n + 1 ) ); $this->_put( '0000000000 65535 f ' ); for ( $i = 1; $i <= $this->n; $i++ ) { $this->_put( sprintf( '%010d 00000 n ', $this->offsets[$i] ) ); } // Trailer $this->_put( 'trailer' ); $this->_put( '<<' ); $this->_puttrailer(); $this->_put( '>>' ); $this->_put( 'startxref' ); $this->_put( $offset ); $this->_put( '%%EOF' ); $this->state = 3; } // ********* NEW FUNCTIONS ********* // Converts UTF-8 strings to UTF16-BE. protected function UTF8ToUTF16BE( $str, $setbom = true ) { $outstr = ""; if ( $setbom ) { $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM) } $outstr .= mb_convert_encoding( $str, 'UTF-16BE', 'UTF-8' ); return $outstr; } // Converts UTF-8 strings to codepoints array protected function UTF8StringToArray( $str ) { $out = []; $len = strlen( $str ); for ( $i = 0; $i < $len; $i++ ) { $uni = -1; $h = ord( $str[$i] ); if ( $h <= 0x7F ) { $uni = $h; } elseif ( $h >= 0xC2 ) { if ( ( $h <= 0xDF ) && ( $i < $len - 1 ) ) { $uni = ( $h & 0x1F ) << 6 | ( ord( $str[++$i] ) & 0x3F ); } elseif ( ( $h <= 0xEF ) && ( $i < $len - 2 ) ) { $uni = ( $h & 0x0F ) << 12 | ( ord( $str[++$i] ) & 0x3F ) << 6 | ( ord( $str[++$i] ) & 0x3F ); } elseif ( ( $h <= 0xF4 ) && ( $i < $len - 3 ) ) { $uni = ( $h & 0x0F ) << 18 | ( ord( $str[++$i] ) & 0x3F ) << 12 | ( ord( $str[++$i] ) & 0x3F ) << 6 | ( ord( $str[++$i] ) & 0x3F ); } } if ( $uni >= 0 ) { $out[] = $uni; } } return $out; } } ?>