#!/usr/bin/perl # # Generate a patch to add M+ fbcon fonts to Linux(-libre) # # Copyright (C) 2019 Patrick McDermott # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use strict; use warnings; no indirect; no autovivification; use English qw(-no_match_vars); use Font::FreeType; use List::Util qw(max); use Text::Diff; my @FONTS = ( # face weight w h [qw(1mn medium 10 14)], [qw(1mn regular 12 17)], [qw(1mn regular 14 19)], [qw(1mn regular 16 22)], ); my $PATCH = 'patches/01_add-mplus-fonts.patch'; sub header { my ($mplus_dir) = @_; my $ft; my $face; my $version; my $header; $ft = Font::FreeType->new(); $face = $ft->face("$mplus_dir/mplus-1m-regular.ttf"); foreach my $entry (@{$face->namedinfos()}) { if ($entry->platform_id() != 1 or $entry->encoding_id() != 0) { next; } if ($entry->name_id() != 5) { next; } $version = $entry->string(); $version =~ s{\AVersion[ \t]1[.]}{}msxa; } ($header = <<" EOF") =~ s{^\t\t}{}msxag; Author: Patrick McDermott Subject: Add M+ fonts Generated from M+ version $version by $PROGRAM_NAME EOF return $header; } sub extract { my ($fname) = @_; return `tar --wildcards --no-wildcards-match-slash -xJOf \\ linux-libre-*.orig.tar.xz 'linux-*/$fname'`; } sub make_patch { my ($fname, $old, $new) = @_; return "diff -Naur a/$fname b/$fname\n--- a/$fname\n+++ b/$fname\n" . diff(\$old, \$new); } sub patch_font_h { my $old; my $new; my $i; my $inject; my $face; my $weight; my $width; my $height; $new = $old = extract('include/linux/font.h'); # Inject "#define"s. ($i) = $old =~ m{.*^[#]\s*define\s+[A-Za-x0-9_]+_IDX\s+(\d+)\s*$}msxa; $inject = ''; foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $inject .= sprintf("#define MPLUS_%s_%s_%dx%d_IDX\t%d\n", uc($face), uc($weight), $width, $height, ++$i); } $new =~ s{\A(.*^[#]\s*define\s+[A-Za-z0-9_]+_IDX\s+\d+\s*$)} {$1$inject}msxa; # Inject "struct font_desc" declarations. $inject = ''; foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $inject .= sprintf(",\n\t\t\tfont_mplus_%s_%s_%dx%d", $face, $weight, $width, $height); } $new =~ s{(extern\sconst\sstruct\sfont_desc[^;]+);}{$1$inject;}msxa; return make_patch('include/linux/font.h', $old, $new); } sub patch_kconfig { my $old; my $new; my $inject; my $face; my $weight; my $width; my $height; $new = $old = extract('lib/fonts/Kconfig'); $inject = ''; foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $inject .= sprintf("config FONT_MPLUS_%s_%s_%dx%d\n", uc($face), uc($weight), $width, $height); $inject .= sprintf("\tbool \"M+ %s %s %dx%d font " . "(not supported by all drivers)\" if FONTS\n", $face, $weight, $width, $height); $inject .= "\tdepends on FRAMEBUFFER_CONSOLE\n\thelp\n"; $inject .= "\t This is a sans-serif font with a " . "sophisticated and relaxed design that\n\t balances " . "natural letterform and high legibility.\n\n"; } $new =~ s{ ^ ( [ \t]* config [ \t]+ FONT_AUTOSELECT [ \t]* ) $ } {$inject$1}msxa; $inject = ''; foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $inject .= sprintf("\tdepends on !FONT_MPLUS_%s_%s_%dx%d\n", uc($face), uc($weight), $width, $height); } $new =~ s{ ^ ( [ \t]* select [ \t]+ FONT_[A-Za-z0-9_]+ [ \t]* ) $ } {$inject$1}msxa; return make_patch('lib/fonts/Kconfig', $old, $new); } sub patch_makefile { my $old; my $new; my $max_var_len; my $face; my $weight; my $width; my $height; my $len; my $inject; $new = $old = extract('lib/fonts/Makefile'); $max_var_len = 0; foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $len = length( sprintf('font-objs-$(CONFIG_FONT_MPLUS_%s_%s_%dx%d)', $face, $weight, $width, $height)); $max_var_len = max($max_var_len, $len); } $inject = ''; foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $len = length( sprintf('font-objs-$(CONFIG_FONT_MPLUS_%s_%s_%dx%d)', $face, $weight, $width, $height)); $len = $max_var_len - $len; $inject .= sprintf("\n" . 'font-objs-$(CONFIG_FONT_MPLUS_%s_%s_%dx%d)%' . $len . 's += font_mplus_%s_%s_%dx%d.o', uc($face), uc($weight), $width, $height, '', $face, $weight, $width, $height); } $new =~ s{ \A ( .* ^ font-objs-\$[(]CONFIG_FONT_[A-Za-z0-9_]+[)] [ \t]* [+]= [ \t]* font_[a-z0-9_]+[.]o [ \t]* $ ) }{$1$inject}msxa; return make_patch('lib/fonts/Makefile', $old, $new); } sub patch_fonts_c { my $old; my $new; my $inject; my $face; my $weight; my $width; my $height; $new = $old = extract('lib/fonts/fonts.c'); $inject = ''; foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $inject .= sprintf("#ifdef CONFIG_FONT_MPLUS_%s_%s_%dx%d\n" . "#undef NO_FONTS\n &font_mplus_%s_%s_%dx%d,\n" . "#endif\n", uc($face), uc($weight), $width, $height, $face, $weight, $width, $height); } $new =~ s< ( \s* static \s* const \s* struct \s* font_desc \s* [*] \s* fonts\[\] \s* = \s* { [^}]+ ) } ><$1$inject}>msxa; return make_patch('lib/fonts/fonts.c', $old, $new); } sub conv_fonts { my ($mplus_dir, $bdf2fbcon) = @_; my %patch; my $face; my $weight; my $width; my $height; my $font_name; my $new; %patch = (); foreach my $font (@FONTS) { ($face, $weight, $width, $height) = @$font; $font_name = "mplus-$face-$weight"; $new = `otf2bdf -p $width -r 72 $mplus_dir/$font_name.ttf | \\ $bdf2fbcon -o - -n $font_name -p 2 -`; $font_name .= "-${width}x${height}"; $patch{$font_name} = make_patch("lib/fonts/font_mplus_${face}" . "_${weight}_${width}x${height}.c", '', $new); } return join('', @patch{sort(keys(%patch))}); } sub main { my $mplus_dir; my $bdf2fbcon; my $patch; my $fh; if (scalar(@ARGV) != 2) { STDERR->printf("Usage: %s \n", $PROGRAM_NAME); return 1; } ($mplus_dir, $bdf2fbcon) = @ARGV; $patch = patch_font_h(); $patch .= patch_kconfig(); $patch .= patch_makefile(); $patch .= conv_fonts($mplus_dir, $bdf2fbcon); $patch .= patch_fonts_c(); open($fh, '>', "$PATCH~~") or die("Error opening $PATCH~~: $OS_ERROR"); $fh->print($patch); close($fh) or die("Error closing $PATCH~~: $OS_ERROR"); open($fh, '>', "$PATCH~") or die("Error opening $PATCH~: $OS_ERROR"); $fh->print(header($mplus_dir) . "---\n" . `diffstat $PATCH~~` . "\n" . $patch); close($fh) or die("Error closing $PATCH~: $OS_ERROR"); unlink("$PATCH~~") or die("Error unlinking $PATCH~~"); rename("$PATCH~", "$PATCH") or die("Error renaming $PATCH~: $OS_ERROR"); return 0; } exit(main());