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;