File Coverage

blib/lib/Text/Layout/PDFAPI2.pm
Criterion Covered Total %
statement 142 354 40.1
branch 34 132 25.7
condition 30 169 17.7
subroutine 13 22 59.0
pod 1 10 10.0
total 220 687 32.0


line stmt bran cond sub pod time code
1             #! perl
2              
3 4     4   307428 use strict;
  4         19  
  4         123  
4 4     4   29 use warnings;
  4         12  
  4         102  
5 4     4   21 use utf8;
  4         7  
  4         22  
6              
7             package Text::Layout::PDFAPI2;
8              
9 4     4   576 use parent 'Text::Layout';
  4         358  
  4         30  
10 4     4   241 use Carp;
  4         9  
  4         250  
11 4     4   27 use List::Util qw(max);
  4         8  
  4         16661  
12              
13             my $hb;
14             my $fc;
15              
16             #### API
17             sub new {
18 2     2 1 15 my ( $pkg, @data ) = @_;
19 2 50 33     32 unless ( @data == 1 && ref($data[0]) =~ /^PDF::(API2|Builder)\b/ ) {
20 0         0 croak("Usage: Text::Layout::PDFAPI2->new(\$pdf)");
21             }
22 2         18 my $self = $pkg->SUPER::new;
23 2         14 $self->{_context} = $data[0];
24 2 50 33     13 if ( !$fc || $fc->{__PDF__} ne $data[0] ) {
25             # Init cache.
26 2         6 $fc = { __PDF__ => $data[0] };
27 2         13 Text::Layout::FontConfig->reset;
28             }
29 2         8 $self;
30             }
31              
32             # Creates a (singleton) HarfBuzz::Shaper object.
33             sub _hb_init {
34 0 0   0   0 return $hb if defined $hb;
35 0         0 $hb = 0;
36 0         0 eval {
37 0         0 require HarfBuzz::Shaper;
38 0         0 $hb = HarfBuzz::Shaper->new;
39             };
40 0         0 return $hb;
41             }
42              
43             # Verify if a font needs shaping, and we can do that.
44             sub _hb_font_check {
45 4     4   10 my ( $f ) = @_;
46 4 100       36 return $f->{_hb_checked} if defined $f->{_hb_checked};
47              
48 1 50       7 if ( $f->get_shaping ) {
49 0         0 my $fn = $f->to_string;
50 0 0       0 if ( $f->{font}->can("fontfilename") ) {
51 0 0       0 if ( _hb_init() ) {
52             # warn("Font $fn will use shaping.\n");
53 0         0 return $f->{_hb_checked} = 1;
54             }
55 0         0 carp("Font $fn: Requires shaping but HarfBuzz cannot be loaded.");
56             }
57             else {
58 0         0 carp("Font $fn: Shaping not supported");
59             }
60             }
61             else {
62             # warn("Font ", $f->to_string, " does not need shaping.\n");
63             }
64 1         9 return $f->{_hb_checked} = 0;
65             }
66              
67             #### API
68             sub render {
69 1     1 0 3 my ( $self, $x, $y, $text, $fp ) = @_;
70              
71 1         3 $self->{_lastx} = $x;
72 1         3 $self->{_lasty} = $y;
73              
74 1         4 my @bb = $self->get_pixel_bbox;
75 1         3 my $bl = $bb[0];
76 1   50     6 my $align = $self->{_alignment} // 0;
77 1 50       4 if ( $self->{_width} ) {
78 0         0 my $w = $bb[3];
79 0 0       0 if ( $w < $self->{_width} ) {
80 0 0       0 if ( $align eq "right" ) {
    0          
81 0         0 $x += $self->{_width} - $w;
82             }
83             elsif ( $align eq "center" ) {
84 0         0 $x += ( $self->{_width} - $w ) / 2;
85             }
86             else {
87 0         0 $x += $bb[1];
88             }
89             }
90             }
91 1         3 my $upem = 1000;
92              
93 1         2 foreach my $fragment ( @{ $self->{_content} } ) {
  1         3  
94 1 50       6 next unless length($fragment->{text});
95 1         1 my $x0 = $x;
96 1         2 my $y0 = $y;
97 1         3 my $f = $fragment->{font};
98 1         4 my $font = $f->get_font($self);
99 1 50       4 unless ( $font ) {
100 0         0 carp("Can't happen?");
101 0         0 $f = $self->{_currentfont};
102 0         0 $font = $f->getfont($self);
103             }
104 1         15 $text->strokecolor( $fragment->{color} );
105 1         262 $text->fillcolor( $fragment->{color} );
106 1   33     480 $text->font( $font, $fragment->{size} || $self->{_currentsize} );
107              
108 1 50       256 if ( _hb_font_check($f) ) {
109 0         0 $hb->set_font( $font->fontfilename );
110 0   0     0 $hb->set_size( $fragment->{size} || $self->{_currentsize} );
111 0         0 $hb->set_text( $fragment->{text} );
112 0 0       0 $hb->set_direction( $f->{direction} ) if $f->{direction};
113 0 0       0 $hb->set_language( $f->{language} ) if $f->{language};
114 0         0 my $info = $hb->shaper($fp);
115 0         0 my $y = $y - $fragment->{base} - $bl;
116 0   0     0 my $sz = $fragment->{size} || $self->{_currentsize};
117 0         0 my $w = 0;
118 0         0 $w += $_->{ax} for @$info;
119              
120 0 0       0 if ( $fragment->{bgcolor} ) {
121 0         0 my $y = $y0;
122 0         0 my $h = -$sz*($f->get_ascender-$f->get_descender)/$upem;
123 0         0 my $x = $x0;
124 0         0 $text->add(PDF::API2::Content::_save());
125              
126 0         0 $text->add($text->_fillcolor($fragment->{bgcolor}));
127 0         0 $text->add($text->_strokecolor($fragment->{bgcolor}));
128 0         0 $text->add(PDF::API2::Content::_linewidth(2));
129 0         0 $text->add(PDF::API2::Content::_move($x, $y));
130 0         0 $text->add(PDF::API2::Content::_line($x+$w, $y));
131 0         0 $text->add(PDF::API2::Content::_line($x+$w, $y+$h));
132 0         0 $text->add(PDF::API2::Content::_line($x, $y+$h));
133 0         0 $text->add('h'); # close
134 0         0 $text->add('B'); # fillstroke
135 0         0 $text->add(PDF::API2::Content::_restore());
136             }
137              
138 0         0 foreach my $g ( @$info ) {
139 0         0 $text->translate( $x + $g->{dx}, $y - $g->{dy} );
140 0         0 $text->glyph_by_CId( $g->{g} );
141 0         0 $x += $g->{ax};
142 0         0 $y += $g->{ay};
143             }
144             }
145             else {
146             printf("%.2f %.2f %.2f \"%s\" %s\n",
147             $x, $y-$fragment->{base}-$bl,
148             $font->width($fragment->{text}) * ($fragment->{size} || $self->{_currentsize}),
149             $fragment->{text},
150             join(" ", $fragment->{font}->{family},
151             $fragment->{font}->{style},
152             $fragment->{font}->{weight},
153             $fragment->{size} || $self->{_currentsize},
154             $fragment->{color},
155             $fragment->{underline}||'""', $fragment->{underline_color}||'""',
156 1         2 $fragment->{strikethrough}||'""', $fragment->{strikethrough_color}||'""',
157             ),
158             ) if 0;
159 1         4 my $t = $fragment->{text};
160 1 50       4 if ( $t ne "" ) {
161              
162             # See ChordPro issue 240.
163 1 50 33     4 if ( $font->issymbol && $font->is_standard ) {
164             # This enables byte access to these symbol fonts.
165 0         0 utf8::downgrade( $t, 1 );
166             }
167              
168 1         10 my $y = $y-$fragment->{base}-$bl;
169 1   33     5 my $sz = $fragment->{size} || $self->{_currentsize};
170 1         4 my $w = $font->width($t) * $sz;
171              
172 1 50       381 if ( $fragment->{bgcolor} ) {
173 0         0 my ( $d0, $a0 );
174 0         0 $d0 = $f->get_descender * $sz / $upem;
175 0         0 $a0 = $f->get_ascender * $sz / $upem;
176 0         0 my $y = $y0;
177 0         0 my $h = -($a0-$d0);
178 0         0 my $x = $x0;
179 0         0 $text->add(PDF::API2::Content::_save());
180              
181 0         0 $text->add($text->_fillcolor($fragment->{bgcolor}));
182 0         0 $text->add($text->_strokecolor($fragment->{bgcolor}));
183 0         0 $text->add(PDF::API2::Content::_linewidth(2));
184 0         0 $text->add(PDF::API2::Content::_move($x, $y));
185 0         0 $text->add(PDF::API2::Content::_line($x+$w, $y));
186 0         0 $text->add(PDF::API2::Content::_line($x+$w, $y+$h));
187 0         0 $text->add(PDF::API2::Content::_line($x, $y+$h));
188 0         0 $text->add('h'); # close
189 0         0 $text->add('B'); # fillstroke
190 0         0 $text->add(PDF::API2::Content::_restore());
191             }
192              
193 1         10 $text->translate( $x, $y );
194 1         552 $text->text($t);
195 1         559 $x += $w;
196             }
197             }
198              
199 1 50       4 next unless $x > $x0;
200              
201 1         4 my $dw = 1000;
202 1         7 my $xh = $font->xheight;
203              
204 1         7 my @strikes;
205 1 50 33     6 if ( $fragment->{underline} && $fragment->{underline} ne 'none' ) {
206 0   0     0 my $sz = $fragment->{size} || $self->{_currentsize};
207             my $d = -( $f->{underline_position}
208 0   0     0 || $font->underlineposition ) * $sz/$dw;
209             my $h = ( $f->{underline_thickness}
210 0   0     0 || $font->underlinethickness ) * $sz/$dw;
211 0   0     0 my $col = $fragment->{underline_color} // $fragment->{color};
212 0 0       0 if ( $fragment->{underline} eq 'double' ) {
213 0         0 push( @strikes, [ $d-0.125*$h, $h * 0.75, $col ],
214             [ $d+1.125*$h, $h * 0.75, $col ] );
215             }
216             else {
217 0         0 push( @strikes, [ $d+$h/2, $h, $col ] );
218             }
219             }
220              
221 1 50       4 if ( $fragment->{strikethrough} ) {
222 0   0     0 my $sz = $fragment->{size} || $self->{_currentsize};
223             my $d = -( $f->{strikeline_position}
224             ? $f->{strikeline_position}
225 0 0       0 : 0.6*$xh ) * $sz/$dw;
226             my $h = ( $f->{strikeline_thickness}
227             || $f->{underline_thickness}
228 0   0     0 || $font->underlinethickness ) * $sz/$dw;
229 0   0     0 my $col = $fragment->{strikethrough_color} // $fragment->{color};
230 0         0 push( @strikes, [ $d+$h/2, $h, $col ] );
231             }
232              
233 1 50 33     5 if ( $fragment->{overline} && $fragment->{overline} ne 'none' ) {
234 0   0     0 my $sz = $fragment->{size} || $self->{_currentsize};
235             my $h = ( $f->{overline_thickness}
236             || $f->{underline_thickness}
237 0   0     0 || $font->underlinethickness ) * $sz/$dw;
238             my $d = -( $f->{overline_position}
239 0 0       0 ? $f->{overline_position} * $sz/$dw
240             : $xh*$sz/$dw + 2*$h );
241 0   0     0 my $col = $fragment->{overline_color} // $fragment->{color};
242 0 0       0 if ( $fragment->{overline} eq 'double' ) {
243 0         0 push( @strikes, [ $d-0.125*$h, $h * 0.75, $col ],
244             [ $d+1.125*$h, $h * 0.75, $col ] );
245             }
246             else {
247 0         0 push( @strikes, [ $d+$h/2, $h, $col ] );
248             }
249             }
250 1         3 for ( @strikes ) {
251              
252             # Mostly copied from PDF::API2::Content::_text_underline.
253 0         0 $text->add_post(PDF::API2::Content::_save());
254              
255 0         0 $text->add_post($text->_strokecolor($_->[2]));
256 0         0 $text->add_post(PDF::API2::Content::_linewidth($_->[1]));
257 0         0 $text->add_post(PDF::API2::Content::_move($x0, $y0-$fragment->{base}-$bl-$_->[0]));
258 0         0 $text->add_post(PDF::API2::Content::_line($x, $y0-$fragment->{base}-$bl-$_->[0]));
259 0         0 $text->add_post(PDF::API2::Content::_stroke());
260 0         0 $text->add_post(PDF::API2::Content::_restore());
261             }
262              
263 1 50       154 if ( $fragment->{href} ) {
264 0   0     0 my $sz = $fragment->{size} || $self->{_currentsize};
265 0         0 my $ann = $text->{' apipage'}->annotation;
266             $ann->url( $fragment->{href},
267             # -border => [ 0, 0, 1 ],
268 0         0 -rect => [ $x0, $y0, #-$fragment->{base}-$bl,
269             $x, $y0 - $sz ]
270             );
271             }
272             }
273             }
274              
275             #### API
276             sub bbox {
277 3     3 0 7 my ( $self, $all ) = @_;
278              
279 3         10 my ( $bl, $x, $y, $w, $h ) = (0) x 4;
280 3         6 my ( $d, $a ) = (0) x 2;
281 3         9 my ( $xMin, $xMax, $yMin, $yMax );
282 3         0 my $dir;
283              
284 3         4 foreach ( @{ $self->{_content} } ) {
  3         8  
285 3         8 my $f = $_->{font};
286 3         9 my $font = $f->get_font($self);
287 3 50       9 unless ( $font ) {
288 0         0 carp("Can't happen?");
289 0         0 $f = $self->{_currentfont};
290 0         0 $font = $f->getfont($self);
291             }
292 3         5 my $upem = 1000; # as delivered by PDF::API2
293 3         7 my $size = $_->{size};
294 3         5 my $base = $_->{base};
295 3   50     13 my $mydir = $f->{direction} || 'ltr';
296              
297             # Width and inkbox, if requested.
298 3 50 66     7 if ( _hb_font_check( $f ) ) {
    50          
299 0         0 $hb->set_font( $font->fontfilename );
300 0         0 $hb->set_size($size);
301 0 0       0 $hb->set_language( $f->{language} ) if $f->{language};
302 0 0       0 $hb->set_direction( $f->{direction} ) if $f->{direction};
303 0         0 $hb->set_text( $_->{text} );
304 0         0 my $info = $hb->shaper;
305 0         0 $mydir = $hb->get_direction;
306             # warn("mydir $mydir\n");
307              
308 0 0       0 if ( $all ) {
309 0         0 my $ext = $hb->get_extents;
310 0         0 foreach my $g ( @$info ) {
311 0         0 my $e = shift(@$ext);
312             printf STDERR ( "G %3d %6.2f %6.2f %6.2f %6.2f %6.2f\n",
313             $g->{g}, $g->{ax},
314 0         0 @$e{ qw( x_bearing y_bearing width height ) } ) if 0;
315             # It is easier to work with the baseline oriented box.
316 0         0 $e->{xMin} = $e->{x_bearing};
317 0         0 $e->{yMin} = $e->{y_bearing} + $e->{height} - $base;
318 0         0 $e->{xMax} = $e->{x_bearing} + $e->{width};
319 0         0 $e->{yMax} = $e->{y_bearing} - $base;
320              
321 0 0 0     0 $xMin //= $w + $e->{xMin} if $e->{width};
322             $yMin = $e->{yMin}
323 0 0 0     0 if !defined($yMin) || $e->{yMin} < $yMin;
324             $yMax = $e->{yMax}
325 0 0 0     0 if !defined($yMax) || $e->{yMax} > $yMax;
326 0         0 $xMax = $w + $e->{xMax};
327 0         0 $w += $g->{ax};
328             }
329             }
330             else {
331 0         0 foreach my $g ( @$info ) {
332 0         0 $w += $g->{ax};
333             }
334             }
335             }
336             elsif ( $all && $font->can("extents") ) {
337 0         0 my $e = $font->extents( $_->{text}, $size );
338 0 0 0     0 printf STDERR ("(%.2f,%.2f)(%.2f,%.2f) -> ",
      0        
      0        
      0        
      0        
339             $xMin//0, $yMin//0, $xMax//0, $yMax//0 ) if $all && 0;
340 0 0       0 $xMax = $w + $e->{xMax} if $all;
341 0         0 $w += $e->{wx};
342             # warn("W \"", $_->{text}, "\" $w, ", $e->{width}, "\n");
343 0 0       0 if ( $all ) {
344 0         0 $_ -= $base for $e->{yMin}, $e->{yMax};
345             # Baseline oriented box.
346 0   0     0 $xMin //= $e->{xMin};
347             $yMin = $e->{yMin}
348 0 0 0     0 if !defined($yMin) || $e->{yMin} < $yMin;
349             $yMax = $e->{yMax}
350 0 0 0     0 if !defined($yMax) || $e->{yMax} > $yMax;
351 0         0 printf STDERR ("(%.2f,%.2f)(%.2f,%.2f)\n",
352             $xMin//0, $yMin//0, $xMax//0, $yMax//0 ) if 0;
353             }
354             }
355             else {
356 3         15 $w += $font->width( $_->{text} ) * $size;
357             }
358              
359             # We have width. Now the rest of the layoutbox.
360 3         992 my ( $d0, $a0 );
361 3         10 $d0 = $f->get_descender * $size / $upem - $base;
362 3         8 $a0 = $f->get_ascender * $size / $upem - $base;
363             # Keep track of biggest decender/ascender.
364 3 50       11 $d = $d0 if $d0 < $d;
365 3 50       7 $a = $a0 if $a0 > $a;
366              
367             # Direction.
368 3   33     14 $dir //= $mydir;
369 3 50       11 $dir = 0 unless $dir eq $mydir; # mix
370             }
371 3         4 $bl = $a;
372 3         6 $h = $a - $d;
373              
374 3         5 my $align = $self->{_alignment};
375             # warn("ALIGN: ", $align//"","\n");
376 3 0 33     11 if ( $self->{_width} && $dir && $w < $self->{_width} ) {
      33        
377 0 0 0     0 if ( $dir eq 'rtl' && (!$align || $align eq "left") ) {
      0        
378 0         0 $align = "right";
379             # warn("ALIGN: set to $align\n");
380             }
381             }
382 3 0 33     9 if ( $self->{_width} && $align && $w < $self->{_width} ) {
      33        
383             # warn("ALIGNING...\n");
384 0 0       0 if ( $align eq "right" ) {
    0          
385             # warn("ALIGNING: to $align\n");
386 0         0 $x += my $d = $self->{_width} - $w;
387 0 0       0 $xMin += $d if defined $xMin;
388 0 0       0 $xMax += $d if defined $xMax;
389             }
390             elsif ( $align eq "center" ) {
391             # warn("ALIGNING: to $align\n");
392 0         0 $x += my $d = ( $self->{_width} - $w ) / 2;
393 0 0       0 $xMin += $d if defined $xMin;
394 0 0       0 $xMax += $d if defined $xMax;
395             }
396             }
397              
398 3 50       31 [ $bl, $x, $y-$h, $w, $h,
399             defined $xMin ? ( $xMin, $yMin-$bl, $xMax-$xMin, $yMax-$yMin ) : ()];
400             }
401              
402             #### API
403             sub load_font {
404 2     2 0 5 my ( $self, $font, $fd ) = @_;
405              
406 2 50       9 if ( my $f = $fc->{$font} ) {
407             # warn("Loaded font $font (cached)\n");
408 0   0     0 $fd->{ascender} //= $f->ascender;
409 0   0     0 $fd->{descender} //= $f->descender;
410 0         0 return $f;
411             }
412 2         4 my $ff;
413 2 50       8 if ( $font =~ /\.[ot]tf$/ ) {
414 0         0 eval {
415             $ff = $self->{_context}->ttfont( $font,
416             -dokern => 1,
417             $fd->{nosubset}
418 0 0       0 ? ( -nosubset => 1 )
419             : (),
420             );
421             };
422             }
423             else {
424 2         4 eval {
425 2         31 $ff = $self->{_context}->corefont( $font, -dokern => 1 );
426             };
427             }
428              
429 2 50       75930 croak( "Cannot load font: ", $font, "\n", $@ ) unless $ff;
430             # warn("Loaded font: $font\n");
431 2         9 $self->{font} = $ff;
432 2   33     25 $fd->{ascender} //= $ff->ascender;
433 2   33     35 $fd->{descender} //= $ff->descender;
434 2         20 $fc->{$font} = $ff;
435 2         17 return $ff;
436             }
437              
438             sub xheight {
439 0     0 0 0 $_[0]->data->{xheight};
440             }
441              
442             ################ Extensions to PDF::API2 ################
443              
444             sub PDF::API2::Content::glyph_by_CId {
445 0     0 0 0 my ( $self, $cid ) = @_;
446 0         0 $self->add( sprintf("<%04x> Tj", $cid ) );
447 0         0 $self->{' font'}->fontfile->subsetByCId($cid);
448             }
449              
450             # HarfBuzz requires a TT/OT font. Define the fontfilename method only
451             # for classes that HarfBuzz can deal with.
452             sub PDF::API2::Resource::CIDFont::TrueType::fontfilename {
453 0     0 0 0 my ( $self ) = @_;
454 0         0 $self->fontfile->{' font'}->{' fname'};
455             }
456              
457             # Add extents calculation for CIDfonts.
458             # Note: Origin is x=0 at the baseline.
459             sub PDF::API2::Resource::CIDFont::extents {
460 1     1 0 393951 my ( $self, $text, $size ) = @_;
461 1   50     5 $size //= 1;
462 1         7 my $e = $self->extents_cid( $self->cidsByStr($text), $size );
463 1         5 return $e;
464             }
465              
466             sub PDF::API2::Resource::CIDFont::extents_cid {
467 1     1 0 221 my ( $self, $text, $size ) = @_;
468 1         3 my $width = 0;
469 1         2 my ( $xMin, $xMax, $yMin, $yMax, $bl );
470              
471 1         4 my $upem = $self->data->{upem};
472 1         6 my $glyphs = $self->fontobj->{loca}->read->{glyphs};
473 1         30 $bl = $self->ascender;
474 1         7 my $lastglyph = 0;
475 1         2 my $lastwidth;
476              
477             # Fun ahead! Widths are in 1000 and xMin and such in upem.
478             # Scale to 1000ths.
479 1         3 my $scale = 1000 / $upem;
480              
481 1         4 foreach my $n (unpack('n*', $text)) {
482 19         54 $width += $lastwidth = $self->wxByCId($n);
483 19 50 33     779 if ($self->{'-dokern'} and $self->haveKernPairs()) {
484 0 0       0 if ($self->kernPairCid($lastglyph, $n)) {
485 0         0 $width -= $self->kernPairCid($lastglyph, $n);
486             }
487             }
488 19         23 $lastglyph = $n;
489 19         32 my $ex = $glyphs->[$n];
490 19 50 33     63 unless ( defined $ex && %$ex ) {
491 0         0 warn("Missing glyph: $n\n");
492 0         0 next;
493             }
494 19         51 $ex->read;
495              
496 19         3709 my $e;
497             # Copy while scaling.
498 19         86 $e->{$_} = $ex->{$_} * $scale for qw( xMin yMin xMax yMax );
499              
500             printf STDERR ( "G %3d %6.2f %6.2f %6.2f %6.2f %6.2f\n",
501             $n, $lastwidth,
502 19         26 @$e{ qw( xMin yMin xMax yMax ) } ) if 0;
503              
504 19   66     39 $xMin //= ($width - $lastwidth) + $e->{xMin};
505 19 100 100     71 $yMin = $e->{yMin} if !defined($yMin) || $e->{yMin} < $yMin;
506 19 100 66     58 $yMax = $e->{yMax} if !defined($yMax) || $e->{yMax} > $yMax;
507 19         76 $xMax = ($width - $lastwidth) + $e->{xMax};
508             }
509              
510 1 50       6 if ( defined $lastwidth ) {
511             # $xMax += ($width - $lastwidth);
512             }
513             else {
514 0         0 $xMin = $yMin = $xMax = $yMax = 0;
515 0         0 $width = $self->missingwidth;
516             }
517 1   50     10 $_ = ($_//0)*$size/1000 for $xMin, $xMax, $yMin, $yMax, $bl;
518 1   50     5 $_ = ($_//0)*$size/1000 for $width;
519              
520 1         21 return { x => $xMin,
521             y => $yMin,
522             width => $xMax - $xMin,
523             height => $yMax - $yMin,
524             # These are for convenience
525             xMin => $xMin,
526             yMin => $yMin,
527             xMax => $xMax,
528             yMax => $yMax,
529             wx => $width,
530             bl => $bl,
531             };
532             }
533              
534             ################ Extensions to PDF::Builder ################
535              
536             sub PDF::Builder::Content::glyph_by_CId {
537 0     0     my ( $self, $cid ) = @_;
538 0           $self->add( sprintf("<%04x> Tj", $cid ) );
539 0           $self->{' font'}->fontfile->subsetByCId($cid);
540             }
541              
542             # HarfBuzz requires a TT/OT font. Define the fontfilename method only
543             # for classes that HarfBuzz can deal with.
544             sub PDF::Builder::Resource::CIDFont::TrueType::fontfilename {
545 0     0     my ( $self ) = @_;
546 0           $self->fontfile->{' font'}->{' fname'};
547             }
548              
549             ################ For debugging/convenience ################
550              
551             # Shows the bounding box of the last piece of text that was rendered.
552             sub showbb {
553 0     0 0   my ( $self, $gfx, $x, $y, $col ) = @_;
554 0   0       $x //= $self->{_lastx};
555 0   0       $y //= $self->{_lasty};
556 0   0       $col ||= "magenta";
557              
558 0           my ( $ink, $bb ) = $self->get_pixel_extents;
559 0           my $bl = $bb->{bl};
560             # Bounding box, top-left coordinates.
561             printf( "Ink: %6.2f %6.2f %6.2f %6.2f\n",
562 0           @$ink{qw( x y width height )} );
563             printf( "Layout: %6.2f %6.2f %6.2f %6.2f BL %.2f\n",
564 0           @$bb{qw( x y width height )}, $bl );
565              
566             # NOTE: Some fonts include natural spacing in the bounding box.
567             # NOTE: Some fonts exclude accents on capitals from the bounding box.
568              
569 0           $gfx->save;
570 0           $gfx->translate( $x, $y );
571              
572             # Show origin.
573 0           _showloc($gfx);
574              
575             # Show baseline.
576 0           _line( $gfx, $bb->{x}, -$bl, $bb->{width}, 0, $col );
577 0           $gfx->restore;
578              
579             # Show layout box.
580 0           $gfx->save;
581 0           $gfx->linewidth( 0.25 );
582 0           $gfx->strokecolor($col);
583 0           $gfx->translate( $x, $y );
584 0           for my $e ( $bb ) {
585 0           $gfx->rect( @$e{ qw( x y width height ) } );
586 0           $gfx->stroke;
587             }
588 0           $gfx->restore;
589              
590             # Show ink box.
591 0           $gfx->save;
592 0           $gfx->linewidth( 0.25 );
593 0           $gfx->strokecolor("cyan");
594 0           $gfx->translate( $x, $y );
595 0           for my $e ( $ink ) {
596 0           $gfx->rect( @$e{ qw( x y width height ) } );
597 0           $gfx->stroke;
598             }
599 0           $gfx->restore;
600             }
601              
602             sub _showloc {
603 0     0     my ( $gfx, $x, $y, $d, $col ) = @_;
604 0   0       $x ||= 0; $y ||= 0; $d ||= 50; $col ||= "blue";
  0   0        
  0   0        
  0   0        
605              
606 0           _line( $gfx, $x-$d, $y, 2*$d, 0, $col );
607 0           _line( $gfx, $x, $y-$d, 0, 2*$d, $col );
608             }
609              
610             sub _line {
611 0     0     my ( $gfx, $x, $y, $w, $h, $col, $lw ) = @_;
612 0   0       $col ||= "black";
613 0   0       $lw ||= 0.5;
614              
615 0           $gfx->save;
616 0           $gfx->move( $x, $y );
617 0           $gfx->line( $x+$w, $y+$h );
618 0           $gfx->linewidth($lw);
619 0           $gfx->strokecolor($col);
620 0           $gfx->stroke;
621 0           $gfx->restore;
622             }
623              
624             1;