File Coverage

blib/lib/PDF/Make/Builder/Font.pm
Criterion Covered Total %
statement 75 78 96.1
branch 22 28 78.5
condition 9 15 60.0
subroutine 16 16 100.0
pod 8 8 100.0
total 130 145 89.6


line stmt bran cond sub pod time code
1             package PDF::Make::Builder::Font;
2 42     42   212 use strict;
  42         54  
  42         1212  
3 42     42   179 use warnings;
  42         52  
  42         1460  
4 42     42   148 use Object::Proto;
  42         46  
  42         1905  
5 42     42   199 use PDF::Make::Page qw(:fonts);
  42         57  
  42         5477  
6 42     42   15950 use PDF::Make::Font ();
  42         108  
  42         1611  
7              
8             BEGIN {
9 42     42   1834 Object::Proto::define('PDF::Make::Builder::Font',
10             'colour:Str:default(#000)',
11             'size:Num:default(9)',
12             'family:Str:default(Helvetica)',
13             'bold:Bool:default(0)',
14             'italic:Bool:default(0)',
15             'line_height:Num',
16             'loaded:HashRef:default({})',
17             );
18 42         39089 Object::Proto::import_accessors('PDF::Make::Builder::Font');
19             }
20              
21             # Standard 14 font family mapping: family => { variant => page constant }
22             my %STD14 = (
23             Times => {
24             normal => TIMES_ROMAN,
25             bold => TIMES_BOLD,
26             italic => TIMES_ITALIC,
27             bolditalic => TIMES_BOLDITALIC,
28             },
29             Helvetica => {
30             normal => HELVETICA,
31             bold => HELVETICA_BOLD,
32             italic => HELVETICA_OBLIQUE,
33             bolditalic => HELVETICA_BOLDOBLIQUE,
34             },
35             Courier => {
36             normal => COURIER,
37             bold => COURIER_BOLD,
38             italic => COURIER_OBLIQUE,
39             bolditalic => COURIER_BOLDOBLIQUE,
40             },
41             Symbol => { normal => SYMBOL },
42             ZapfDingbats => { normal => ZAPFDINGBATS },
43             );
44              
45             # Map family+variant to PDF BaseFont name for XS Font
46             my %BASEFONT = (
47             'Times_normal' => 'Times-Roman',
48             'Times_bold' => 'Times-Bold',
49             'Times_italic' => 'Times-Italic',
50             'Times_bolditalic' => 'Times-BoldItalic',
51             'Helvetica_normal' => 'Helvetica',
52             'Helvetica_bold' => 'Helvetica-Bold',
53             'Helvetica_italic' => 'Helvetica-Oblique',
54             'Helvetica_bolditalic' => 'Helvetica-BoldOblique',
55             'Courier_normal' => 'Courier',
56             'Courier_bold' => 'Courier-Bold',
57             'Courier_italic' => 'Courier-Oblique',
58             'Courier_bolditalic' => 'Courier-BoldOblique',
59             'Symbol_normal' => 'Symbol',
60             'ZapfDingbats_normal' => 'ZapfDingbats',
61             );
62              
63             # Cache for PDF::Make::Font XS objects (exact per-glyph metrics)
64             my %_xs_font_cache;
65              
66             sub _xs_font {
67 15490     15490   18290 my ($self, $variant) = @_;
68 15490   33     30015 $variant //= $self->_default_variant;
69 15490         17881 my $fam = family $self;
70 15490         17148 my $key = "${fam}_${variant}";
71 15490 100       27964 return $_xs_font_cache{$key} if $_xs_font_cache{$key};
72 34         78 my $basefont = $BASEFONT{$key};
73 34 50       103 return undef unless $basefont;
74 34         1783 $_xs_font_cache{$key} = PDF::Make::Font->standard14($basefont);
75 34         106 return $_xs_font_cache{$key};
76             }
77              
78             sub _default_variant {
79 15902     15902   18602 my ($self) = @_;
80 15902 100 100     24202 return 'bolditalic' if bold($self) && italic($self);
81 15898 100       21261 return 'bold' if bold($self);
82 15884 100       20967 return 'italic' if italic($self);
83 15875         32654 return 'normal';
84             }
85              
86             sub effective_line_height {
87 402     402 1 507 my ($self) = @_;
88 402         455 my $lh = line_height $self;
89 402 100 66     1220 return defined $lh && $lh > 0 ? $lh : size $self;
90             }
91              
92             sub measure_text {
93 15476     15476 1 19638 my ($self, $text) = @_;
94 15476         19101 my $xs = $self->_xs_font;
95 15476 50       120484 return $xs->string_width($text, size $self) if $xs;
96             # Fallback for unknown fonts
97 0         0 return length($text) * 0.52 * (size $self);
98             }
99              
100             sub measure_word {
101 5     5 1 9 my ($self, $word) = @_;
102 5         12 my $xs = $self->_xs_font;
103 5 50       26 return $xs->string_width($word, size $self) if $xs;
104 0         0 return length($word) * 0.52 * (size $self);
105             }
106              
107             sub space_width {
108 9     9 1 153667 my ($self) = @_;
109 9         27 my $xs = $self->_xs_font;
110 9 50       112 return $xs->string_width(' ', size $self) if $xs;
111 0         0 return 0.28 * (size $self);
112             }
113              
114             sub hex_to_rgb {
115 505     505 1 2612 my ($self, $hex) = @_;
116 505         1395 $hex =~ s/^#//;
117 505         643 my ($r, $g, $b);
118 505 100       806 if (length($hex) == 3) {
    100          
119 448         928 ($r, $g, $b) = map { hex($_.$_) / 255.0 } split //, $hex;
  1344         2484  
120             } elsif (length($hex) == 6) {
121 54         142 $r = hex(substr($hex, 0, 2)) / 255.0;
122 54         103 $g = hex(substr($hex, 2, 2)) / 255.0;
123 54         99 $b = hex(substr($hex, 4, 2)) / 255.0;
124             } else {
125 3         11 return (0, 0, 0);
126             }
127 502         1062 return ($r, $g, $b);
128             }
129              
130             sub resource_name {
131 12     12 1 14 my ($self, $variant) = @_;
132 12   33     38 $variant //= $self->_default_variant;
133 12         13 my $fam = family $self;
134 12         22 return "F_${fam}_${variant}";
135             }
136              
137             sub ensure_loaded {
138 426     426 1 630 my ($self, $xs_page, $variant) = @_;
139 426   66     1066 $variant //= $self->_default_variant;
140 426         511 my $fam = family $self;
141 426         583 my $key = "${fam}_${variant}";
142 426         524 my $res_name = "F_${key}";
143              
144             # Cache is per-page (keyed by page pointer address) to handle overflow
145 426         864 my $page_id = "$xs_page"; # stringified ref = unique per page
146 426         501 my $ld = loaded $self;
147 426 100       1028 return $res_name if $ld->{"${key}_${page_id}"};
148              
149 182         267 my $family_map = $STD14{$fam};
150 182 50       300 die "PDF::Make::Builder::Font: unknown font family '$fam'" unless $family_map;
151 182         289 my $font_id = $family_map->{$variant};
152 182 50       316 die "PDF::Make::Builder::Font: unknown variant '$variant' for '$fam'" unless defined $font_id;
153              
154 182         1840 $xs_page->add_std14_font($res_name, $font_id);
155 182         519 $ld->{"${key}_${page_id}"} = 1;
156 182         240 loaded $self, $ld;
157 182         495 return $res_name;
158             }
159              
160 1     1 1 522 sub families { return keys %STD14 }
161              
162             1;
163              
164             __END__