>'; $kids = array(); foreach ( $pages as $n => $_ ) { $kids[] = ( 5 + $n * 2 ) . ' 0 R'; } $objects[2] = '<< /Type /Pages /Kids [' . implode( ' ', $kids ) . '] /Count ' . count( $pages ) . ' >>'; $objects[3] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>'; $objects[4] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>'; foreach ( $pages as $n => $page_rows ) { $page_id = 5 + $n * 2; $stream_id = $page_id + 1; $stream = self::page_stream( $page_rows, $frequency, $host, $n + 1, count( $pages ) ); $objects[ $page_id ] = '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 3 0 R /F2 4 0 R >> >> /Contents ' . $stream_id . ' 0 R >>'; $objects[ $stream_id ] = '<< /Length ' . strlen( $stream ) . " >>\nstream\n" . $stream . "\nendstream"; } ksort( $objects, SORT_NUMERIC ); $pdf = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n"; $offsets = array( 0 ); foreach ( $objects as $id => $object ) { $offsets[ $id ] = strlen( $pdf ); $pdf .= $id . " 0 obj\n" . $object . "\nendobj\n"; } $xref = strlen( $pdf ); $pdf .= "xref\n0 " . ( count( $objects ) + 1 ) . "\n0000000000 65535 f \n"; foreach ( $objects as $id => $_ ) { $pdf .= sprintf( '%010d 00000 n ', $offsets[ $id ] ) . "\n"; } $pdf .= "trailer\n<< /Size " . ( count( $objects ) + 1 ) . " /Root 1 0 R >>\nstartxref\n" . $xref . "\n%%EOF\n"; return $pdf; } private static function page_stream( array $lines, $frequency, $host, $page, $pages ) { $title = strtoupper( self::ascii( $frequency ) ) . ' ACTIVITY REPORT'; $site = self::ascii( $host ); $out = array( '0.035 0.42 0.46 rg 0 742 595 100 re f', '1 1 1 rg', self::text( 42, 794, 19, 'F2', 'AI-O / ' . $title ), self::text( 42, 765, 10, 'F1', $site ), '0.86 0.92 0.94 RG 42 726 m 553 726 l S', ); $y = 705; foreach ( $lines as $line ) { if ( '' === $line ) { $y -= 10; continue; } $heading = in_array( $line, array( 'AI bot / fetch requests', 'Top AI bots / fetchers (requests, last seen UTC)', 'Most-fetched pages (bot requests)', 'AI referral sources (visits)', 'Guidance files (requests)' ), true ); $out[] = $heading ? '0.035 0.42 0.46 rg' : '0.17 0.23 0.27 rg'; $out[] = self::text( 44, $y, $heading ? 12 : 10, $heading ? 'F2' : 'F1', $line ); $y -= $heading ? 23 : 16; } $out[] = '0.75 0.79 0.82 RG 42 62 m 553 62 l S'; $out[] = '0.33 0.39 0.42 rg'; $out[] = self::text( 44, 43, 9, 'F1', 'Observed AI requests and referrals - not unique users or guaranteed citations.' ); $out[] = self::text( 487, 43, 9, 'F1', $page . ' / ' . $pages ); return implode( "\n", $out ) . "\n"; } private static function text( $x, $y, $size, $font, $value ) { $safe = str_replace( array( '\\', '(', ')' ), array( '\\\\', '\\(', '\\)' ), (string) $value ); return sprintf( 'BT /%s %d Tf 1 0 0 1 %d %d Tm (%s) Tj ET', $font, $size, $x, $y, $safe ); } private static function ascii( $value ) { $value = strtr( (string) $value, array( '—' => '-', '–' => '-', '→' => '>', '’' => "'", '“' => '"', '”' => '"', '•' => '-', ) ); if ( function_exists( 'iconv' ) ) { $converted = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $value ); if ( false !== $converted ) { $value = $converted; } } return (string) preg_replace( '/[^\x20-\x7e]/', '', $value ); } }