package PDF::API2::Resource::Font::SynFont;
use base 'PDF::API2::Resource::Font';
use strict;
use warnings;
our $VERSION = '2.043'; # VERSION
use Math::Trig;
use Unicode::UCD 'charinfo';
use PDF::API2::Util;
use PDF::API2::Basic::PDF::Utils;
=head1 NAME
PDF::API2::Resource::Font::SynFont - Module for creating synthetic Fonts.
=head1 SYNOPSIS
my $pdf = PDF::API2->new();
my $base_font = $pdf->font('Helvetica');
# Create a condensed synthetic font
my $condensed = $pdf->synthetic_font($base_font, hscale => 80);
# Compare the two fonts
my $text = $pdf->page->text();
$text->font($base_font, 18);
$text->distance(72, 720);
$text->text('Hello World!');
$text->font($condensed, 18);
$text->distance(0, -36);
$text->text('Hello World!');
$pdf->save('sample.pdf');
=head1 DESCRIPTION
This module allows you to create a custom font based on an existing font,
adjusting the scale, stroke thickness, angle, and other properties of each
glyph.
=head1 FONT OPTIONS
=head2 hscale
A percentage to condense (less than 100) or expand (greater than 100) the glyphs
horizontally.
=head2 angle
A number of degrees to lean the glyphs to the left (negative angle) or to the
right (positive angle).
=head2 bold
A stroke width, in thousandths of a text unit, to add to the glyph's outline,
creating a bold effect.
=head2 smallcaps
Set to true to replace lower-case characters with small versions of their
upper-case glyphs.
=head2 space
Additional space, in thousandths of a text unit, to add between glyphs.
=cut
sub new {
my ($class, $pdf, $font, %opts) = @_;
my $first = 1;
my $last = 255;
# Deprecated options
if (exists $opts{'-bold'}) {
$opts{'bold'} //= (delete $opts{'-bold'}) * 10;
}
if (exists $opts{'-caps'}) {
$opts{'smallcaps'} //= delete $opts{'-caps'};
}
if (exists $opts{'-oblique'}) {
$opts{'angle'} //= delete $opts{'-oblique'};
}
if (exists $opts{'-slant'}) {
$opts{'hscale'} //= (delete $opts{'-slant'}) * 100;
}
if (exists $opts{'-space'}) {
$opts{'space'} //= delete $opts{'-space'};
}
my $angle = $opts{'angle'} // 0;
my $bold = ($opts{'bold'} // 0);
my $hscale = ($opts{'hscale'} // 100) / 100;
my $space = $opts{'space'} // 0;
$font->encodeByName($opts{'-encode'}) if $opts{'-encode'};
$class = ref($class) if ref($class);
my $key = $opts{'name'} // 'Syn' . $font->name() . pdfkey();
my $self = $class->SUPER::new($pdf, $key);
$pdf->new_obj($self) unless $self->is_obj($pdf);
$self->{' font'} = $font;
$self->{' data'} = {
'type' => 'Type3',
'ascender' => $font->ascender(),
'capheight' => $font->capheight(),
'descender' => $font->descender(),
'iscore' => '0',
'isfixedpitch' => $font->isfixedpitch(),
'italicangle' => $font->italicangle() + $angle,
'missingwidth' => $font->missingwidth() * $hscale,
'underlineposition' => $font->underlineposition(),
'underlinethickness' => $font->underlinethickness(),
'xheight' => $font->xheight(),
'firstchar' => $first,
'lastchar' => $last,
'char' => [ '.notdef' ],
'uni' => [ 0 ],
'u2e' => { 0 => 0 },
'fontbbox' => '',
'wx' => { 'space' => '600' },
};
my $data = $self->data();
if (ref($font->fontbbox())) {
$data->{'fontbbox'} = [ @{$font->fontbbox()} ];
}
else {
$data->{'fontbbox'} = [ $font->fontbbox() ];
}
$data->{'fontbbox'}->[0] *= $hscale;
$data->{'fontbbox'}->[2] *= $hscale;
$self->{'Subtype'} = PDFName('Type3');
$self->{'FirstChar'} = PDFNum($first);
$self->{'LastChar'} = PDFNum($last);
$self->{'FontMatrix'} = PDFArray(map { PDFNum($_) } (0.001, 0, 0, 0.001, 0, 0));
$self->{'FontBBox'} = PDFArray(map { PDFNum($_) } $self->fontbbox());
my $procs = PDFDict();
$pdf->new_obj($procs);
$self->{'CharProcs'} = $procs;
$self->{'Resources'} = PDFDict();
$self->{'Resources'}->{'ProcSet'} = PDFArray(map { PDFName($_) }
qw(PDF Text ImageB ImageC ImageI));
my $xo = PDFDict();
$self->{'Resources'}->{'Font'} = $xo;
$self->{'Resources'}->{'Font'}->{'FSN'} = $font;
foreach my $w ($first .. $last) {
$data->{'char'}->[$w] = $font->glyphByEnc($w);
$data->{'uni'}->[$w] = uniByName($data->{'char'}->[$w]);
if (defined $data->{'uni'}->[$w]) {
$data->{'u2e'}->{$data->{'uni'}->[$w]} = $w;
}
}
if ($font->isa('PDF::API2::Resource::CIDFont')) {
$self->{'Encoding'} = PDFDict();
$self->{'Encoding'}->{'Type'} = PDFName('Encoding');
$self->{'Encoding'}->{'Differences'} = PDFArray();
foreach my $w ($first .. $last) {
my $char = $data->{'char'}->[$w];
if (defined $char and $char ne '.notdef') {
$self->{'Encoding'}->{'Differences'}->add_elements(PDFNum($w),
PDFName($char));
}
}
}
else {
$self->{'Encoding'} = $font->{'Encoding'};
}
my @widths;
foreach my $w ($first .. $last) {
if ($data->{'char'}->[$w] eq '.notdef') {
push @widths, $self->missingwidth();
next;
}
my $char = PDFDict();
my $uni = $data->{'uni'}->[$w];
my $wth = int($font->width(chr($uni)) * 1000 * $hscale + 2 * $space);
$procs->{$font->glyphByEnc($w)} = $char;
#$char->{'Filter'} = PDFArray(PDFName('FlateDecode'));
$char->{' stream'} = $wth . ' 0 ' . join(' ', map { int($_) } $self->fontbbox()) . " d1\n";
$char->{' stream'} .= "BT\n";
if ($angle) {
my @matrix = (1, 0, tan(deg2rad($angle)), 1, 0, 0);
$char->{' stream'} .= join(' ', @matrix) . " Tm\n";
}
$char->{' stream'} .= "2 Tr " . $bold . " w\n" if $bold;
my $ci = {};
if ($data->{'uni'}->[$w] ne '') {
$ci = charinfo($data->{'uni'}->[$w]);
}
if ($opts{'smallcaps'} and $ci->{'upper'}) {
$char->{' stream'} .= "/FSN 800 Tf\n";
$char->{' stream'} .= ($hscale * 110) . " Tz\n";
$char->{' stream'} .= " [ -$space ] TJ\n" if $space;
$wth = int($font->width(uc chr($uni)) * 800 * $hscale * 1.1 + 2 * $space);
$char->{' stream'} .= $font->text(uc chr($uni));
}
else {
$char->{' stream'} .= "/FSN 1000 Tf\n";
$char->{' stream'} .= ($hscale * 100) . " Tz\n" if $hscale != 1;
$char->{' stream'} .= " [ -$space ] TJ\n" if $space;
$char->{' stream'} .= $font->text(chr($uni));
}
$char->{' stream'} .= " Tj\nET\n";
push @widths, $wth;
$data->{'wx'}->{$font->glyphByEnc($w)} = $wth;
$pdf->new_obj($char);
}
$procs->{'.notdef'} = $procs->{$font->data->{'char'}->[32] // 0};
$self->{'Widths'} = PDFArray(map { PDFNum($_) } @widths);
$data->{'e2n'} = $data->{'char'};
$data->{'e2u'} = $data->{'uni'};
$data->{'u2c'} = {};
$data->{'u2e'} = {};
$data->{'u2n'} = {};
$data->{'n2c'} = {};
$data->{'n2e'} = {};
$data->{'n2u'} = {};
foreach my $n (reverse 0 .. 255) {
$data->{'n2c'}->{$data->{'char'}->[$n] // '.notdef'} //= $n;
$data->{'n2e'}->{$data->{'e2n'}->[$n] // '.notdef'} //= $n;
$data->{'n2u'}->{$data->{'e2n'}->[$n] // '.notdef'} //= $data->{'e2u'}->[$n];
$data->{'n2u'}->{$data->{'char'}->[$n] // '.notdef'} //= $data->{'uni'}->[$n];
if (defined $data->{'uni'}->[$n]) {
$data->{'u2c'}->{$data->{'uni'}->[$n]} //= $n;
}
if (defined $data->{'e2u'}->[$n]) {
$data->{'u2e'}->{$data->{'e2u'}->[$n]} //= $n;
my $value = ($data->{'e2n'}->[$n] // '.notdef');
$data->{'u2n'}->{$data->{'e2u'}->[$n]} //= $value;
}
if (defined $data->{'uni'}->[$n]) {
my $value = ($data->{'char'}->[$n] // '.notdef');
$data->{'u2n'}->{$data->{'uni'}->[$n]} //= $value;
}
}
return $self;
}
1;