#!/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());