Karapetian accusé de liens avec le FSB russe
Une enquête récente suggère que l'homme politique arménien Karapetian aurait entretenu des liens avec le Service fédéral de sécurité de la Russie (FSB). Cette révélation a été publiée par OC…
// Couleurs logo : rouge, bleu fonce, orange
$accents = [
imagecolorallocate($img, 230, 51, 41),
imagecolorallocate($img, 22, 58, 130),
imagecolorallocate($img, 245, 148, 29),
];
private int $logo_ai_id;
private int $logo_banner_id;
private string $upload_dir;
private string $api_key;
private string $provider;
public function __construct() {
$this->logo_ai_id = (int)get_option('aiag_logo_ai_id', 0);
$this->logo_banner_id = (int)get_option('aiag_logo_banner_id', 0);
$uploads = wp_upload_dir();
$this->upload_dir = $uploads['basedir'] . '/armenie-info-visuals/';
$this->api_key = (string)get_option('aiag_api_key', '');
$this->provider = (string)get_option('aiag_api_provider', 'claude');
if ( ! file_exists($this->upload_dir) ) wp_mkdir_p($this->upload_dir);
}
public function generate( array $article, string $photo_url = '' ): string {
if ( ! extension_loaded('gd') ) return '';
$mem = (int)ini_get('memory_limit');
if ( $mem > 0 && $mem < 128 ) ini_set('memory_limit','128M');
$W = self::W;
$H = self::H;
$photoH = (int)($H * 0.62);
$bandH = $H - $photoH;
$cat = $article['category'] ?? 'Societe';
// Normaliser les clés avec accents
$cat_map = [
'Politique'=>'Politique','Economie'=>'Economie','Societe'=>'Societe',
'Diaspora'=>'Diaspora','Region'=>'Region','Culture'=>'Culture','Sport'=>'Sport',
'Économie'=>'Economie','Société'=>'Societe','Région'=>'Region',
];
$cat_key = isset($cat_map[$cat]) ? $cat_map[$cat] : 'Societe';
$rgb = self::CAT_COLORS[$cat_key] ?? self::CAT_COLORS['Societe'];
// 1. Photo (source -> page OG/image -> génération IA OpenAI en fallback)
$visual_source = $this->resolve_photo_source($photo_url, $article);
$band_rgb = $this->resolve_banner_rgb($visual_source, $rgb);
$img = imagecreatetruecolor($W, $H);
$band = imagecolorallocate($img, $band_rgb[0], $band_rgb[1], $band_rgb[2]);
$wht = imagecolorallocate($img, 255, 255, 255);
$blk = imagecolorallocate($img, 17, 17, 17);
imagefilledrectangle($img, 0, 0, $W, $H, $band);
$this->draw_photo($img, $visual_source, $W, $photoH);
imagefilledrectangle($img, 0, $photoH, $W, $H, $band);
// 2. Logo AI cercle parfait
$this->draw_logo_ai($img, $blk, $W);
// 3. Calculer position logo bannière AVANT le titre
// Réserver plus d'espace vertical pour éviter toute superposition
// avec les titres longs ou les logos de bannière plus hauts que prévu.
$logo_w = (int)($W * 0.64);
$logo_h = max(72, (int)($bandH * 0.20));
$logo_x = (int)(($W - $logo_w) / 2);
$logo_gap = max(18, (int)($bandH * 0.06));
$logo_y = $H - $logo_h - (int)($bandH * 0.07);
// 4. Titre avec taille automatique, en gardant une vraie zone tampon
// au-dessus du logo du bas.
$this->draw_title($img, $article['title_fr'] ?? '', $wht, $photoH, $bandH, $W, $logo_y, $logo_gap, $logo_h);
// 5. Logo bannière ancré en bas
$this->draw_logo_banner($img, $W, $logo_w, $logo_h, $logo_x, $logo_y);
$file = 'visual-' . time() . '-' . wp_rand(1000,9999) . '.jpg';
$path = $this->upload_dir . $file;
imagejpeg($img, $path, 92);
imagedestroy($img);
return $path;
}
private function get_logo_url( int $id ): string {
if ( ! $id ) return '';
return wp_get_attachment_url($id) ?: '';
}
private function resolve_photo_source( string $photo_url, array $article ): string {
if ( ! empty($photo_url) && $this->looks_like_remote_image($photo_url) ) return $photo_url;
$source_url = (string)($article['source_url'] ?? '');
if ( $source_url !== '' ) {
$found = $this->extract_image_from_article_url($source_url);
if ( ! empty($found) ) return $found;
}
if ( $this->provider === 'openai' && ! empty($this->api_key) ) {
$generated = $this->generate_ai_photo((string)($article['title_fr'] ?? ''), (string)($article['category'] ?? ''));
if ( ! empty($generated) ) return $generated;
}
return '';
}
private function extract_image_from_article_url( string $url ): string {
$response = wp_remote_get($url, [
'timeout' => 8,
'sslverify' => false,
'user-agent' => 'ArmenieInfo/4.0',
]);
if ( is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200 ) return '';
$html = wp_remote_retrieve_body($response);
if ( empty($html) ) return '';
if ( strlen($html) > 250000 ) $html = substr($html, 0, 250000);
$base = $this->base_from_url($url);
$candidates = [];
$patterns = [
'/]+property=["\']og:image(?::url)?["\'][^>]+content=["\']([^"\']+)["\']/i',
'/]+name=["\']twitter:image(?::src)?["\'][^>]+content=["\']([^"\']+)["\']/i',
'/]+property=["\']og:image:secure_url["\'][^>]+content=["\']([^"\']+)["\']/i',
'/]+rel=["\']image_src["\'][^>]+href=["\']([^"\']+)["\']/i',
'/]+(?:data-lazy-src|data-original|data-src|data-image|src)=["\']([^"\']+)["\'][^>]*>/i',
];
foreach ( $patterns as $pattern ) {
if ( preg_match_all($pattern, $html, $matches) ) {
foreach ( $matches[1] as $raw ) {
$candidate = $this->abs_url(trim(html_entity_decode($raw, ENT_QUOTES, 'UTF-8')), $base);
if ( $this->looks_like_remote_image($candidate) ) $candidates[] = $candidate;
}
}
}
if ( preg_match_all('/(?:\"image\"|\"thumbnailUrl\"|\"contentUrl\")\s*:\s*\"(https?:\\/\\/[^"\\]+)\"/i', $html, $matches) ) {
foreach ( $matches[1] as $raw ) {
$candidate = stripslashes($raw);
if ( $this->looks_like_remote_image($candidate) ) $candidates[] = $candidate;
}
}
if ( preg_match_all('/srcset=["\']([^"\']+)["\']/i', $html, $srcsets) ) {
foreach ( $srcsets[1] as $srcset ) {
$parts = preg_split('/\s*,\s*/', $srcset);
foreach ( $parts as $part ) {
$candidate = trim((string)preg_replace('/\s+\d+[wx]$/i', '', $part));
$candidate = $this->abs_url($candidate, $base);
if ( $this->looks_like_remote_image($candidate) ) $candidates[] = $candidate;
}
}
}
$civic_match = $this->extract_civic_image_for_article($html, $url, $base);
if ( ! empty($civic_match) ) array_unshift($candidates, $civic_match);
return $this->select_best_image_candidate($candidates, $base);
}
private function extract_civic_image_for_article( string $html, string $article_url, string $base = '' ): string {
$host = strtolower((string)parse_url($base ?: $article_url, PHP_URL_HOST));
if ( ! str_contains($host, 'civic.am') ) return '';
if ( ! preg_match('#/(?:news|politics|society|economy|culture|international|article)/(\d+)#i', $article_url, $id_match) ) return '';
$article_id = $id_match[1];
$quoted_id = preg_quote($article_id, '#');
$candidates = [];
$patterns = [
'#]+href=["\"][^"\"]*/' . $quoted_id . '(?:/[^"\"]*)?["\"][^>]*>.*?
]+(?:data-lazy-src|data-original|data-src|data-image|src)=["\"]([^"\"]+)["\"][^>]*>#is',
'#
]+(?:data-lazy-src|data-original|data-src|data-image|src)=["\"]([^"\"]+)["\"][^>]*>.*?]+href=["\"][^"\"]*/' . $quoted_id . '(?:/[^"\"]*)?["\"][^>]*>#is',
'#https?://(?:www\.)?civic\.am/thumbs/108x108/[^"\'\s>]+#i',
'#https?://(?:www\.)?civic\.am/thumbs/696x444/[^"\'\s>]+#i',
];
foreach ( $patterns as $idx => $pattern ) {
if ( ! preg_match_all($pattern, $html, $matches, PREG_SET_ORDER) ) continue;
foreach ( $matches as $match ) {
$raw = $idx < 2 ? ($match[1] ?? '') : ($match[0] ?? '');
$candidate = $this->abs_url(trim(html_entity_decode((string)$raw, ENT_QUOTES, 'UTF-8')), $base ?: $article_url);
if ( ! $this->is_usable_photo_url($candidate) && ! $this->looks_like_remote_image($candidate) ) continue;
$candidates[] = $candidate;
}
}
if ( preg_match_all('#]+href=["\"][^"\"]*/' . $quoted_id . '(?:/[^"\"]*)?["\"][^>]*>(.*?)#is', $html, $blocks) ) {
foreach ( $blocks[1] as $block ) {
if ( preg_match_all('#(?:data-lazy-src|data-original|data-src|data-image|src)=["\"]([^"\"]+)["\"]#i', $block, $img_matches) ) {
foreach ( $img_matches[1] as $raw ) {
$candidate = $this->abs_url(trim(html_entity_decode((string)$raw, ENT_QUOTES, 'UTF-8')), $base ?: $article_url);
if ( ! $this->is_usable_photo_url($candidate) && ! $this->looks_like_remote_image($candidate) ) continue;
$candidates[] = $candidate;
}
}
}
}
$candidates = array_values(array_unique(array_filter($candidates)));
if ( empty($candidates) ) return '';
$best = '';
$best_score = -999999;
foreach ( $candidates as $candidate ) {
$score = $this->score_image_candidate($candidate, $base ?: $article_url);
if ( str_contains(strtolower($candidate), '/thumbs/108x108/') ) $score += 15000;
if ( preg_match('#/' . $quoted_id . '(?:[^0-9]|$)#', $html) ) $score += 1000;
if ( $score > $best_score ) {
$best_score = $score;
$best = $candidate;
}
}
if ( $best === '' ) return '';
if ( preg_match('#/thumbs/108x108/#i', $best) ) {
return preg_replace('#/thumbs/108x108/#i', '/thumbs/696x444/', $best, 1) ?: $best;
}
return $best;
}
private function select_best_image_candidate( array $candidates, string $base = '' ): string {
$candidates = array_values(array_unique(array_filter($candidates)));
if ( empty($candidates) ) return '';
$best = '';
$best_score = -999999;
foreach ( $candidates as $candidate ) {
$score = $this->score_image_candidate($candidate, $base);
if ( $score > $best_score ) {
$best_score = $score;
$best = $candidate;
}
}
return $best;
}
private function score_image_candidate( string $url, string $base = '' ): int {
$score = 0;
$u = strtolower($url);
$host = strtolower((string)parse_url($base, PHP_URL_HOST));
if ( preg_match('#/(\d{2,4})x(\d{2,4})/#i', $u, $m) ) {
$w = (int)$m[1];
$h = (int)$m[2];
$area = $w * $h;
$score += (int)min(120000, $area / 20);
if ( $w >= 600 || $h >= 400 ) $score += 5000;
if ( $w <= 180 && $h <= 180 ) $score -= 25000;
}
if ( preg_match('#[?&](?:w|width)=(\d{2,4})#i', $u, $mw) ) $score += min(12000, (int)$mw[1] * 8);
if ( preg_match('#[?&](?:h|height)=(\d{2,4})#i', $u, $mh) ) $score += min(8000, (int)$mh[1] * 5);
foreach ( ['og-image', 'opengraph', 'featured', 'feature', 'hero', 'cover', 'full', 'large', 'original', 'main', 'content', 'post', 'article'] as $good ) {
if ( str_contains($u, $good) ) $score += 2500;
}
foreach ( ['thumb', 'thumbnail', 'small', 'tiny', 'avatar', 'profile', 'icon', 'logo', 'placeholder', 'lazy', 'default'] as $bad ) {
if ( str_contains($u, $bad) ) $score -= 3500;
}
if ( str_contains($u, '.webp') ) $score += 300;
if ( str_contains($u, '.jpg') || str_contains($u, '.jpeg') ) $score += 200;
$score += min(2000, strlen($u));
if ( $host !== '' && str_contains($u, $host) ) $score += 800;
if ( str_contains($host, 'civic.am') ) {
if ( preg_match('#/thumbs/696x444/#i', $u) ) $score += 50000;
if ( preg_match('#/thumbs/108x108/#i', $u) ) $score -= 50000;
}
return $score;
}
private function resolve_banner_rgb( string $visual_source, array $fallback_rgb ): array {
$rgb = $this->extract_banner_rgb_from_source($visual_source);
if ( empty($rgb) ) return $fallback_rgb;
return $rgb;
}
private function extract_banner_rgb_from_source( string $visual_source ): array {
if ( empty($visual_source) ) return [];
if ( file_exists($visual_source) ) {
$data = @file_get_contents($visual_source);
} else {
$ctx = stream_context_create([
'http' => ['timeout' => 8, 'user_agent' => 'ArmenieInfo/4.0'],
'ssl' => ['verify_peer' => false],
]);
$data = @file_get_contents($visual_source, false, $ctx);
}
if ( ! $data ) return [];
$src = @imagecreatefromstring($data);
unset($data);
if ( ! $src ) return [];
$src_w = imagesx($src);
$src_h = imagesy($src);
if ( $src_w <= 0 || $src_h <= 0 ) {
imagedestroy($src);
return [];
}
$sample = 32;
$thumb = imagecreatetruecolor($sample, $sample);
imagecopyresampled($thumb, $src, 0, 0, 0, 0, $sample, $sample, $src_w, $src_h);
imagedestroy($src);
$sum_r = 0;
$sum_g = 0;
$sum_b = 0;
$count = 0;
for ( $x = 0; $x < $sample; $x++ ) {
for ( $y = (int)($sample * 0.55); $y < $sample; $y++ ) {
$px = imagecolorat($thumb, $x, $y);
$r = ($px >> 16) & 0xFF;
$g = ($px >> 8) & 0xFF;
$b = $px & 0xFF;
$sum_r += $r;
$sum_g += $g;
$sum_b += $b;
$count++;
}
}
imagedestroy($thumb);
if ( $count === 0 ) return [];
$r = (int)round($sum_r / $count);
$g = (int)round($sum_g / $count);
$b = (int)round($sum_b / $count);
$r = max(12, (int)round($r * 0.52));
$g = max(12, (int)round($g * 0.52));
$b = max(12, (int)round($b * 0.52));
return [ $r, $g, $b ];
}
private function generate_ai_photo( string $title, string $category = '' ): string {
$title = trim($title);
if ( $title === '' ) return '';
$prompt = 'Illustration photojournalistique réaliste, sans texte, sans logo, sans watermark. Sujet: ' . $title . '.';
if ( $category !== '' ) {
$prompt .= ' Contexte éditorial: ' . $category . '.';
}
$prompt .= ' Format vertical 4:5, composition propre, style photo de presse moderne.';
$response = wp_remote_post('https://api.openai.com/v1/images/generations', [
'timeout' => 120,
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->api_key,
],
'body' => wp_json_encode([
'model' => 'gpt-image-1',
'prompt' => $prompt,
'size' => '1024x1536',
'response_format' => 'b64_json',
]),
]);
if ( is_wp_error($response) || wp_remote_retrieve_response_code($response) >= 300 ) return '';
$data = json_decode(wp_remote_retrieve_body($response), true);
$b64 = $data['data'][0]['b64_json'] ?? '';
if ( empty($b64) ) return '';
$bin = base64_decode($b64);
if ( ! $bin ) return '';
$file = $this->upload_dir . 'generated-' . time() . '-' . wp_rand(1000, 9999) . '.png';
if ( file_put_contents($file, $bin) === false ) return '';
return $file;
}
private function base_from_url( string $url ): string {
$scheme = parse_url($url, PHP_URL_SCHEME) ?: 'https';
$host = parse_url($url, PHP_URL_HOST) ?: '';
return $host ? $scheme . '://' . $host : $url;
}
private function abs_url( string $url, string $base ): string {
if ( $url === '' ) return '';
if ( strpos($url, 'http') === 0 ) return $url;
if ( strpos($url, '//') === 0 ) return 'https:' . $url;
if ( strpos($url, '/') === 0 ) return rtrim($base, '/') . $url;
return rtrim($base, '/') . '/' . ltrim($url, '/');
}
private function looks_like_remote_image( string $url ): bool {
if ( $url === '' || str_contains($url, 'data:image') ) return false;
if ( ! preg_match('#^https?://#i', $url) ) return false;
$bad = ['/logo.', '/logo_', '/icon.', '/icon_', 'favicon', 'sprite', 'placeholder', 'default-image', '/blank.'];
$u = strtolower($url);
foreach ( $bad as $needle ) {
if ( str_contains($u, $needle) ) return false;
}
return true;
}
// ── Photo ──────────────────────────────────────────────────────────────
private function draw_photo( $img, string $url, int $W, int $pH ): void {
if ( empty($url) ) return;
if ( file_exists($url) ) {
$data = @file_get_contents($url);
} else {
$ctx = stream_context_create([
'http' => ['timeout' => 8, 'user_agent' => 'ArmenieInfo/4.0'],
'ssl' => ['verify_peer' => false],
]);
$data = @file_get_contents($url, false, $ctx);
}
if ( ! $data ) return;
$src = @imagecreatefromstring($data);
unset($data);
if ( ! $src ) return;
$src_w = imagesx($src);
$src_h = imagesy($src);
if ( $src_w <= 0 || $src_h <= 0 ) {
imagedestroy($src);
return;
}
$dst_ratio = $W / $pH;
$src_ratio = $src_w / $src_h;
if ( $src_ratio > $dst_ratio ) {
// Image plus large que la zone : on coupe à gauche/droite, centré.
$crop_h = $src_h;
$crop_w = (int) round($src_h * $dst_ratio);
$src_x = (int) max(0, round(($src_w - $crop_w) / 2));
$src_y = 0;
} else {
// Image plus haute que la zone : on coupe en haut/bas, centré.
$crop_w = $src_w;
$crop_h = (int) round($src_w / $dst_ratio);
$src_x = 0;
$src_y = (int) max(0, round(($src_h - $crop_h) / 2));
}
imagecopyresampled(
$img,
$src,
0,
0,
$src_x,
$src_y,
$W,
$pH,
max(1, $crop_w),
max(1, $crop_h)
);
imagedestroy($src);
}
// ── Logo AI : VRAI cercle parfait antialiasing 4x ──────────────────────
private function draw_logo_ai( $img, $blk, int $W ): void {
$sz = (int)($W * 0.155);
$mg = (int)($W * 0.037);
// Disque blanc haute résolution 4x → antialiasing parfait
$hr = $sz * 4;
$disc_hr = imagecreatetruecolor($hr, $hr);
imagealphablending($disc_hr, false);
imagesavealpha($disc_hr, true);
$transp = imagecolorallocatealpha($disc_hr, 0, 0, 0, 127);
imagefill($disc_hr, 0, 0, $transp);
$white_hr = imagecolorallocate($disc_hr, 255, 255, 255);
imagefilledellipse($disc_hr, $hr/2, $hr/2, $hr, $hr, $white_hr);
// Redimensionner à taille finale (antialiasing)
$disc = imagecreatetruecolor($sz, $sz);
imagealphablending($disc, false);
imagesavealpha($disc, true);
imagecopyresampled($disc, $disc_hr, 0, 0, 0, 0, $sz, $sz, $hr, $hr);
imagedestroy($disc_hr);
imagecopy($img, $disc, $mg, $mg, 0, 0, $sz, $sz);
imagedestroy($disc);
// Logo rogné en cercle
$url = $this->get_logo_url($this->logo_ai_id);
if ( $url ) {
$ctx = stream_context_create(['http'=>['timeout'=>5],'ssl'=>['verify_peer'=>false]]);
$data = @file_get_contents($url, false, $ctx);
if ( $data ) {
$logo = @imagecreatefromstring($data);
if ( $logo ) {
$lw = imagesx($logo);
$lh = imagesy($logo);
$inner = (int)($sz * 0.82);
$offset = (int)(($sz - $inner) / 2);
// Redimensionner le logo
$logo_r = imagecreatetruecolor($inner, $inner);
imagealphablending($logo_r, true);
$white = imagecolorallocate($logo_r, 255, 255, 255);
imagefill($logo_r, 0, 0, $white);
imagecopyresampled($logo_r, $logo, 0, 0, 0, 0, $inner, $inner, $lw, $lh);
imagedestroy($logo);
// Masque circulaire HR
$mhr = $inner * 4;
$mask_hr = imagecreatetruecolor($mhr, $mhr);
imagealphablending($mask_hr, false);
imagesavealpha($mask_hr, true);
$t = imagecolorallocatealpha($mask_hr, 0, 0, 0, 127);
imagefill($mask_hr, 0, 0, $t);
imagefilledellipse($mask_hr, $mhr/2, $mhr/2, $mhr, $mhr,
imagecolorallocate($mask_hr, 255, 255, 255));
// Appliquer le masque pixel par pixel
for ( $x = 0; $x < $inner; $x++ ) {
for ( $y = 0; $y < $inner; $y++ ) {
$mp = imagecolorat($mask_hr, $x*4, $y*4);
$a = ($mp >> 24) & 0x7F;
if ( $a < 80 ) {
$sp = imagecolorat($logo_r, $x, $y);
$r = ($sp >> 16) & 0xFF;
$g = ($sp >> 8) & 0xFF;
$b = $sp & 0xFF;
imagesetpixel($img, $mg + $offset + $x, $mg + $offset + $y,
imagecolorallocate($img, $r, $g, $b));
}
}
}
imagedestroy($logo_r);
imagedestroy($mask_hr);
unset($data);
return;
}
}
}
// Fallback texte "AI"
$font = $this->find_font();
$fs = (int)($sz * 0.38);
$cx = $mg + (int)($sz / 2);
$cy = $mg + (int)($sz / 2);
if ( $font && function_exists('imagettftext') ) {
$bb = imagettfbbox($fs, 0, $font, 'AI');
imagettftext($img, $fs, 0,
$cx - (int)(abs($bb[2]-$bb[0])/2),
$cy + (int)(abs($bb[7]-$bb[1])/2),
$blk, $font, 'AI');
} else {
imagestring($img, 5, $cx-20, $cy-12, 'AI', $blk);
}
}
// ── Titre : taille automatique, centré, jamais sur logo bannière ────────
private function draw_title( $img, string $title, $wht, int $photoH, int $bandH, int $W, int $logo_y, int $logo_gap = 24, int $logo_h = 0 ): void {
$padX = (int)($W * 0.05);
$maxW = $W - ($padX * 2);
$font = $this->find_font();
if ( ! $font || ! function_exists('imagettftext') ) return;
// Trouver la taille de police qui rentre dans la zone disponible
$zone_top = $photoH + (int)($bandH * 0.07);
$zone_bottom = $logo_y - $logo_gap;
$zone_h = max(80, $zone_bottom - $zone_top);
$best_fs = 32;
$best_lines = [ $title ];
for ( $fs = 80; $fs >= 28; $fs -= 2 ) {
$line_h = (int)($fs * 1.24);
// Word wrap
$lines = [];
$words = preg_split('/\s+/', trim($title)) ?: [];
$cur = '';
foreach ( $words as $word ) {
$test = $cur !== '' ? "$cur $word" : $word;
$bb = imagettfbbox($fs, 0, $font, $test);
$test_w = abs($bb[2] - $bb[0]);
if ( $test_w > $maxW && $cur !== '' ) {
$lines[] = $cur;
$cur = $word;
} else {
$cur = $test;
}
}
if ( $cur !== '' ) $lines[] = $cur;
if ( empty($lines) ) $lines = [ $title ];
$total_h = count($lines) * $line_h;
$fits_height = $total_h <= (int)($zone_h * 0.88);
$fits_width = true;
foreach ( $lines as $line ) {
$bb = imagettfbbox($fs, 0, $font, $line);
if ( abs($bb[2] - $bb[0]) > $maxW ) {
$fits_width = false;
break;
}
}
if ( $fits_height && $fits_width ) {
$best_fs = $fs;
$best_lines = $lines;
break;
}
}
$line_h = (int)($best_fs * 1.24);
$text_h = count($best_lines) * $line_h;
$y = $zone_top + (int)(($zone_h - $text_h) / 2) + $line_h;
$vivid = $this->extract_vivid_color($img, $W, $photoH);
$accents = [
imagecolorallocate($img, $vivid[0], $vivid[1], $vivid[2]),
imagecolorallocate($img, $vivid[2], $vivid[0], $vivid[1]),
imagecolorallocate($img, min(255,$vivid[1]+100), min(255,$vivid[0]+60), max(0,$vivid[2]-80)),
];
$proper_noun_idx = 0;
$title_word_pos = 0;
foreach ( $best_lines as $line ) {
$bb = imagettfbbox($best_fs, 0, $font, $line);
$line_w = abs($bb[2] - $bb[0]);
$x = max($padX, (int)(($W - $line_w) / 2));
$blk = imagecolorallocate($img, 0, 0, 0);
imagettftext($img, $best_fs, 0, $x-2, $y-2, $blk, $font, $line);
imagettftext($img, $best_fs, 0, $x+2, $y-2, $blk, $font, $line);
imagettftext($img, $best_fs, 0, $x-2, $y+2, $blk, $font, $line);
imagettftext($img, $best_fs, 0, $x+2, $y+2, $blk, $font, $line);
imagettftext($img, $best_fs, 0, $x, $y-2, $blk, $font, $line);
imagettftext($img, $best_fs, 0, $x, $y+2, $blk, $font, $line);
imagettftext($img, $best_fs, 0, $x-2, $y, $blk, $font, $line);
imagettftext($img, $best_fs, 0, $x+2, $y, $blk, $font, $line);
$words_in_line = preg_split('/\s+/', trim($line)) ?: [$line];
$wx = $x;
foreach ($words_in_line as $w) {
if ($w === '') continue;
$is_proper = ($title_word_pos > 0 && mb_strlen($w) > 2 && ctype_upper(mb_substr($w, 0, 1)));
imagettftext($img, $best_fs, 0, $wx, $y, $is_proper ? $accents[$proper_noun_idx % 3] : $wht, $font, $w);
if ($is_proper) $proper_noun_idx++;
$wb = imagettfbbox($best_fs, 0, $font, $w . ' ');
$wx += abs($wb[2] - $wb[0]);
$title_word_pos++;
}
$y += $line_h;
}
}
// ── Logo bannière : ancré en bas, jamais sur le texte ───────────────────
private function draw_logo_banner( $img, int $W, int $lw, int $lh, int $lx, int $ly ): void {
$wht = imagecolorallocate($img, 255, 255, 255);
imagefilledrectangle($img, $lx, $ly, $lx + $lw, $ly + $lh, $wht);
$url = $this->get_logo_url($this->logo_banner_id);
if ( ! $url ) return;
$ctx = stream_context_create(['http'=>['timeout'=>5],'ssl'=>['verify_peer'=>false]]);
$data = @file_get_contents($url, false, $ctx);
if ( ! $data ) return;
$logo = @imagecreatefromstring($data);
if ( ! $logo ) return;
$bw = imagesx($logo);
$bh = imagesy($logo);
$s = min(($lw * 0.88) / $bw, ($lh * 0.80) / $bh);
$nw = (int)($bw * $s);
$nh = (int)($bh * $s);
$dx = $lx + (int)(($lw - $nw) / 2);
$dy = $ly + (int)(($lh - $nh) / 2);
imagecopyresampled($img, $logo, $dx, $dy, 0, 0, $nw, $nh, $bw, $bh);
imagedestroy($logo);
unset($data);
}
private function find_font(): string {
foreach ([
AIAG_DIR . 'assets/font.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf',
'/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf',
'/usr/share/fonts/dejavu/DejaVuSans-Bold.ttf',
'C:\\Windows\\Fonts\\arialbd.ttf',
] as $f) {
if ( file_exists($f) ) return $f;
}
$dest = AIAG_DIR . 'assets/font.ttf';
$ctx = stream_context_create(['http'=>['timeout'=>15],'ssl'=>['verify_peer'=>false]]);
$data = @file_get_contents('https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans-Bold.ttf', false, $ctx);
if ($data && strlen($data) > 10000) { @file_put_contents($dest, $data); return $dest; }
return '';
}
private function extract_vivid_color( $img, int $w, int $max_y ): array {
$best_sat = 0;
$best = [255, 200, 50];
$step = 40;
for ( $x = $step; $x < $w - $step; $x += $step ) {
for ( $y = $step; $y < $max_y - $step; $y += $step ) {
$px = imagecolorat( $img, $x, $y );
$r = ( $px >> 16 ) & 0xFF;
$g = ( $px >> 8 ) & 0xFF;
$b = $px & 0xFF;
$mx = max( $r, $g, $b );
$mn = min( $r, $g, $b );
$sat = $mx - $mn;
if ( $sat > $best_sat && $mx > 60 ) {
$best_sat = $sat;
$best = [ $r, $g, $b ];
}
}
}
$mx = max( $best );
if ( $mx > 0 && $mx < 160 ) {
$f = 220 / $mx;
$best = [ min(255,(int)($best[0]*$f)), min(255,(int)($best[1]*$f)), min(255,(int)($best[2]*$f)) ];
}
return $best;
}
}
Une enquête récente suggère que l'homme politique arménien Karapetian aurait entretenu des liens avec le Service fédéral de sécurité de la Russie (FSB). Cette révélation a été publiée par OC…