blob: 9f15ff224a763035c79722c7294bb7cd1a311476 [file] [log] [blame]
rjw6c1fd8f2022-11-30 14:33:01 +08001package Spreadsheet::WriteExcel::Format;
2
3###############################################################################
4#
5# Format - A class for defining Excel formatting.
6#
7#
8# Used in conjunction with Spreadsheet::WriteExcel
9#
10# Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
11#
12# Documentation after __END__
13#
14
15use Exporter;
16use strict;
17use Carp;
18
19
20
21
22
23
24use vars qw($AUTOLOAD $VERSION @ISA);
25@ISA = qw(Exporter);
26
27$VERSION = '2.37';
28
29###############################################################################
30#
31# new()
32#
33# Constructor
34#
35sub new {
36
37 my $class = shift;
38
39 my $self = {
40 _xf_index => shift || 0,
41
42 _type => 0,
43 _font_index => 0,
44 _font => 'Arial',
45 _size => 10,
46 _bold => 0x0190,
47 _italic => 0,
48 _color => 0x7FFF,
49 _underline => 0,
50 _font_strikeout => 0,
51 _font_outline => 0,
52 _font_shadow => 0,
53 _font_script => 0,
54 _font_family => 0,
55 _font_charset => 0,
56 _font_encoding => 0,
57
58 _num_format => 0,
59 _num_format_enc => 0,
60
61 _hidden => 0,
62 _locked => 1,
63
64 _text_h_align => 0,
65 _text_wrap => 0,
66 _text_v_align => 2,
67 _text_justlast => 0,
68 _rotation => 0,
69
70 _fg_color => 0x40,
71 _bg_color => 0x41,
72
73 _pattern => 0,
74
75 _bottom => 0,
76 _top => 0,
77 _left => 0,
78 _right => 0,
79
80 _bottom_color => 0x40,
81 _top_color => 0x40,
82 _left_color => 0x40,
83 _right_color => 0x40,
84
85 _indent => 0,
86 _shrink => 0,
87 _merge_range => 0,
88 _reading_order => 0,
89
90 _diag_type => 0,
91 _diag_color => 0x40,
92 _diag_border => 0,
93
94 _font_only => 0,
95
96 # Temp code to prevent merged formats in non-merged cells.
97 _used_merge => 0,
98
99 };
100
101 bless $self, $class;
102
103 # Set properties passed to Workbook::add_format()
104 $self->set_format_properties(@_) if @_;
105
106 return $self;
107}
108
109
110###############################################################################
111#
112# copy($format)
113#
114# Copy the attributes of another Spreadsheet::WriteExcel::Format object.
115#
116sub copy {
117 my $self = shift;
118 my $other = $_[0];
119
120 return unless defined $other;
121 return unless (ref($self) eq ref($other));
122
123 # Store the properties that we don't want overwritten.
124 my $xf = $self->{_xf_index};
125 my $merge_range = $self->{_merge_range};
126 my $used_merge = $self->{_used_merge};
127
128 %$self = %$other; # Copy properties
129
130 # Restore saved properties.
131 $self->{_xf_index} = $xf;
132 $self->{_merge_range} = $merge_range;
133 $self->{_used_merge} = $used_merge;
134}
135
136
137###############################################################################
138#
139# get_xf($style)
140#
141# Generate an Excel BIFF XF record.
142#
143sub get_xf {
144
145 use integer; # Avoid << shift bug in Perl 5.6.0 on HP-UX
146
147 my $self = shift;
148
149 my $record; # Record identifier
150 my $length; # Number of bytes to follow
151
152 my $ifnt; # Index to FONT record
153 my $ifmt; # Index to FORMAT record
154 my $style; # Style and other options
155 my $align; # Alignment
156 my $indent; #
157 my $icv; # fg and bg pattern colors
158 my $border1; # Border line options
159 my $border2; # Border line options
160 my $border3; # Border line options
161
162
163 # Set the type of the XF record and some of the attributes.
164 if ($self->{_type} == 0xFFF5) {
165 $style = 0xFFF5;
166 }
167 else {
168 $style = $self->{_locked};
169 $style |= $self->{_hidden} << 1;
170 }
171
172
173 # Flags to indicate if attributes have been set.
174 my $atr_num = ($self->{_num_format} != 0);
175
176 my $atr_fnt = ($self->{_font_index} != 0);
177
178 my $atr_alc = ($self->{_text_h_align} != 0 ||
179 $self->{_text_v_align} != 2 ||
180 $self->{_shrink} != 0 ||
181 $self->{_merge_range} != 0 ||
182 $self->{_text_wrap} != 0 ||
183 $self->{_indent} != 0) ? 1 : 0;
184
185 my $atr_bdr = ($self->{_bottom} != 0 ||
186 $self->{_top} != 0 ||
187 $self->{_left} != 0 ||
188 $self->{_right} != 0 ||
189 $self->{_diag_type} != 0) ? 1: 0;
190
191 my $atr_pat = ($self->{_fg_color} != 0x40 ||
192 $self->{_bg_color} != 0x41 ||
193 $self->{_pattern} != 0x00) ? 1 : 0;
194
195 my $atr_prot = ($self->{_hidden} != 0 ||
196 $self->{_locked} != 1) ? 1 : 0;
197
198
199 # Set attribute changed flags for the style formats.
200 if ($self->{_xf_index} != 0 and $self->{_type} == 0xFFF5) {
201
202 if ($self->{_xf_index} >= 16) {
203 $atr_num = 0;
204 $atr_fnt = 1;
205 }
206 else {
207 $atr_num = 1;
208 $atr_fnt = 0;
209 }
210
211 $atr_alc = 1;
212 $atr_bdr = 1;
213 $atr_pat = 1;
214 $atr_prot = 1;
215 }
216
217
218 # Set a default diagonal border style if none was specified.
219 $self->{_diag_border} = 1 if !$self->{_diag_border} and $self->{_diag_type};
220
221
222 # Reset the default colours for the non-font properties
223 $self->{_fg_color} = 0x40 if $self->{_fg_color} == 0x7FFF;
224 $self->{_bg_color} = 0x41 if $self->{_bg_color} == 0x7FFF;
225 $self->{_bottom_color} = 0x40 if $self->{_bottom_color} == 0x7FFF;
226 $self->{_top_color} = 0x40 if $self->{_top_color} == 0x7FFF;
227 $self->{_left_color} = 0x40 if $self->{_left_color} == 0x7FFF;
228 $self->{_right_color} = 0x40 if $self->{_right_color} == 0x7FFF;
229 $self->{_diag_color} = 0x40 if $self->{_diag_color} == 0x7FFF;
230
231
232 # Zero the default border colour if the border has not been set.
233 $self->{_bottom_color} = 0 if $self->{_bottom} == 0;
234 $self->{_top_color} = 0 if $self->{_top} == 0;
235 $self->{_right_color} = 0 if $self->{_right} == 0;
236 $self->{_left_color} = 0 if $self->{_left} == 0;
237 $self->{_diag_color} = 0 if $self->{_diag_type} == 0;
238
239
240 # The following 2 logical statements take care of special cases in relation
241 # to cell colours and patterns:
242 # 1. For a solid fill (_pattern == 1) Excel reverses the role of foreground
243 # and background colours.
244 # 2. If the user specifies a foreground or background colour without a
245 # pattern they probably wanted a solid fill, so we fill in the defaults.
246 #
247 if ($self->{_pattern} <= 0x01 and
248 $self->{_bg_color} != 0x41 and
249 $self->{_fg_color} == 0x40 )
250 {
251 $self->{_fg_color} = $self->{_bg_color};
252 $self->{_bg_color} = 0x40;
253 $self->{_pattern} = 1;
254 }
255
256 if ($self->{_pattern} <= 0x01 and
257 $self->{_bg_color} == 0x41 and
258 $self->{_fg_color} != 0x40 )
259 {
260 $self->{_bg_color} = 0x40;
261 $self->{_pattern} = 1;
262 }
263
264
265 # Set default alignment if indent is set.
266 $self->{_text_h_align} = 1 if $self->{_indent} and
267 $self->{_text_h_align} == 0;
268
269
270 $record = 0x00E0;
271 $length = 0x0014;
272
273 $ifnt = $self->{_font_index};
274 $ifmt = $self->{_num_format};
275
276
277 $align = $self->{_text_h_align};
278 $align |= $self->{_text_wrap} << 3;
279 $align |= $self->{_text_v_align} << 4;
280 $align |= $self->{_text_justlast} << 7;
281 $align |= $self->{_rotation} << 8;
282
283
284
285 $indent = $self->{_indent};
286 $indent |= $self->{_shrink} << 4;
287 $indent |= $self->{_merge_range} << 5;
288 $indent |= $self->{_reading_order} << 6;
289 $indent |= $atr_num << 10;
290 $indent |= $atr_fnt << 11;
291 $indent |= $atr_alc << 12;
292 $indent |= $atr_bdr << 13;
293 $indent |= $atr_pat << 14;
294 $indent |= $atr_prot << 15;
295
296
297 $border1 = $self->{_left};
298 $border1 |= $self->{_right} << 4;
299 $border1 |= $self->{_top} << 8;
300 $border1 |= $self->{_bottom} << 12;
301
302 $border2 = $self->{_left_color};
303 $border2 |= $self->{_right_color} << 7;
304 $border2 |= $self->{_diag_type} << 14;
305
306
307 $border3 = $self->{_top_color};
308 $border3 |= $self->{_bottom_color} << 7;
309 $border3 |= $self->{_diag_color} << 14;
310 $border3 |= $self->{_diag_border} << 21;
311 $border3 |= $self->{_pattern} << 26;
312
313 $icv = $self->{_fg_color};
314 $icv |= $self->{_bg_color} << 7;
315
316
317
318 my $header = pack("vv", $record, $length);
319 my $data = pack("vvvvvvvVv", $ifnt, $ifmt, $style,
320 $align, $indent,
321 $border1, $border2, $border3,
322 $icv);
323
324 return($header . $data);
325}
326
327
328###############################################################################
329#
330# Note to porters. The majority of the set_property() methods are created
331# dynamically via Perl' AUTOLOAD sub, see below. You may prefer/have to specify
332# them explicitly in other implementation languages.
333#
334
335
336###############################################################################
337#
338# get_font()
339#
340# Generate an Excel BIFF FONT record.
341#
342sub get_font {
343
344 my $self = shift;
345
346 my $record; # Record identifier
347 my $length; # Record length
348
349 my $dyHeight; # Height of font (1/20 of a point)
350 my $grbit; # Font attributes
351 my $icv; # Index to color palette
352 my $bls; # Bold style
353 my $sss; # Superscript/subscript
354 my $uls; # Underline
355 my $bFamily; # Font family
356 my $bCharSet; # Character set
357 my $reserved; # Reserved
358 my $cch; # Length of font name
359 my $rgch; # Font name
360 my $encoding; # Font name character encoding
361
362
363 $dyHeight = $self->{_size} * 20;
364 $icv = $self->{_color};
365 $bls = $self->{_bold};
366 $sss = $self->{_font_script};
367 $uls = $self->{_underline};
368 $bFamily = $self->{_font_family};
369 $bCharSet = $self->{_font_charset};
370 $rgch = $self->{_font};
371 $encoding = $self->{_font_encoding};
372
373 # Handle utf8 strings in perl 5.8.
374 if ($] >= 5.008) {
375 require Encode;
376
377 if (Encode::is_utf8($rgch)) {
378 $rgch = Encode::encode("UTF-16BE", $rgch);
379 $encoding = 1;
380 }
381 }
382
383 $cch = length $rgch;
384
385 # Handle Unicode font names.
386 if ($encoding == 1) {
387 croak "Uneven number of bytes in Unicode font name" if $cch % 2;
388 $cch /= 2 if $encoding;
389 $rgch = pack 'v*', unpack 'n*', $rgch;
390 }
391
392 $record = 0x31;
393 $length = 0x10 + length $rgch;
394 $reserved = 0x00;
395
396 $grbit = 0x00;
397 $grbit |= 0x02 if $self->{_italic};
398 $grbit |= 0x08 if $self->{_font_strikeout};
399 $grbit |= 0x10 if $self->{_font_outline};
400 $grbit |= 0x20 if $self->{_font_shadow};
401
402
403 my $header = pack("vv", $record, $length);
404 my $data = pack("vvvvvCCCCCC", $dyHeight, $grbit, $icv, $bls,
405 $sss, $uls, $bFamily,
406 $bCharSet, $reserved, $cch, $encoding);
407
408 return($header . $data . $rgch);
409}
410
411###############################################################################
412#
413# get_font_key()
414#
415# Returns a unique hash key for a font. Used by Workbook->_store_all_fonts()
416#
417sub get_font_key {
418
419 my $self = shift;
420
421 # The following elements are arranged to increase the probability of
422 # generating a unique key. Elements that hold a large range of numbers
423 # e.g. _color are placed between two binary elements such as _italic
424 #
425 my $key = "$self->{_font}$self->{_size}";
426 $key .= "$self->{_font_script}$self->{_underline}";
427 $key .= "$self->{_font_strikeout}$self->{_bold}$self->{_font_outline}";
428 $key .= "$self->{_font_family}$self->{_font_charset}";
429 $key .= "$self->{_font_shadow}$self->{_color}$self->{_italic}";
430 $key .= "$self->{_font_encoding}";
431 $key =~ s/ /_/g; # Convert the key to a single word
432
433 return $key;
434}
435
436
437###############################################################################
438#
439# get_xf_index()
440#
441# Returns the used by Worksheet->_XF()
442#
443sub get_xf_index {
444 my $self = shift;
445
446 return $self->{_xf_index};
447}
448
449
450###############################################################################
451#
452# _get_color()
453#
454# Used in conjunction with the set_xxx_color methods to convert a color
455# string into a number. Color range is 0..63 but we will restrict it
456# to 8..63 to comply with Gnumeric. Colors 0..7 are repeated in 8..15.
457#
458sub _get_color {
459
460 my %colors = (
461 aqua => 0x0F,
462 cyan => 0x0F,
463 black => 0x08,
464 blue => 0x0C,
465 brown => 0x10,
466 magenta => 0x0E,
467 fuchsia => 0x0E,
468 gray => 0x17,
469 grey => 0x17,
470 green => 0x11,
471 lime => 0x0B,
472 navy => 0x12,
473 orange => 0x35,
474 pink => 0x21,
475 purple => 0x14,
476 red => 0x0A,
477 silver => 0x16,
478 white => 0x09,
479 yellow => 0x0D,
480 );
481
482 # Return the default color, 0x7FFF, if undef,
483 return 0x7FFF unless defined $_[0];
484
485 # or the color string converted to an integer,
486 return $colors{lc($_[0])} if exists $colors{lc($_[0])};
487
488 # or the default color if string is unrecognised,
489 return 0x7FFF if ($_[0] =~ m/\D/);
490
491 # or an index < 8 mapped into the correct range,
492 return $_[0] + 8 if $_[0] < 8;
493
494 # or the default color if arg is outside range,
495 return 0x7FFF if $_[0] > 63;
496
497 # or an integer in the valid range
498 return $_[0];
499}
500
501
502###############################################################################
503#
504# set_type()
505#
506# Set the XF object type as 0 = cell XF or 0xFFF5 = style XF.
507#
508sub set_type {
509
510 my $self = shift;
511 my $type = $_[0];
512
513 if (defined $_[0] and $_[0] eq 0) {
514 $self->{_type} = 0x0000;
515 }
516 else {
517 $self->{_type} = 0xFFF5;
518 }
519}
520
521
522###############################################################################
523#
524# set_align()
525#
526# Set cell alignment.
527#
528sub set_align {
529
530 my $self = shift;
531 my $location = $_[0];
532
533 return if not defined $location; # No default
534 return if $location =~ m/\d/; # Ignore numbers
535
536 $location = lc($location);
537
538 $self->set_text_h_align(1) if ($location eq 'left');
539 $self->set_text_h_align(2) if ($location eq 'centre');
540 $self->set_text_h_align(2) if ($location eq 'center');
541 $self->set_text_h_align(3) if ($location eq 'right');
542 $self->set_text_h_align(4) if ($location eq 'fill');
543 $self->set_text_h_align(5) if ($location eq 'justify');
544 $self->set_text_h_align(6) if ($location eq 'center_across');
545 $self->set_text_h_align(6) if ($location eq 'centre_across');
546 $self->set_text_h_align(6) if ($location eq 'merge'); # S:WE name
547 $self->set_text_h_align(7) if ($location eq 'distributed');
548 $self->set_text_h_align(7) if ($location eq 'equal_space'); # ParseExcel
549
550
551 $self->set_text_v_align(0) if ($location eq 'top');
552 $self->set_text_v_align(1) if ($location eq 'vcentre');
553 $self->set_text_v_align(1) if ($location eq 'vcenter');
554 $self->set_text_v_align(2) if ($location eq 'bottom');
555 $self->set_text_v_align(3) if ($location eq 'vjustify');
556 $self->set_text_v_align(4) if ($location eq 'vdistributed');
557 $self->set_text_v_align(4) if ($location eq 'vequal_space'); # ParseExcel
558}
559
560
561###############################################################################
562#
563# set_valign()
564#
565# Set vertical cell alignment. This is required by the set_format_properties()
566# method to differentiate between the vertical and horizontal properties.
567#
568sub set_valign {
569
570 my $self = shift;
571 $self->set_align(@_);
572}
573
574
575###############################################################################
576#
577# set_center_across()
578#
579# Implements the Excel5 style "merge".
580#
581sub set_center_across {
582
583 my $self = shift;
584
585 $self->set_text_h_align(6);
586}
587
588
589###############################################################################
590#
591# set_merge()
592#
593# This was the way to implement a merge in Excel5. However it should have been
594# called "center_across" and not "merge".
595# This is now deprecated. Use set_center_across() or better merge_range().
596#
597#
598sub set_merge {
599
600 my $self = shift;
601
602 $self->set_text_h_align(6);
603}
604
605
606###############################################################################
607#
608# set_bold()
609#
610# Bold has a range 0x64..0x3E8.
611# 0x190 is normal. 0x2BC is bold. So is an excessive use of AUTOLOAD.
612#
613sub set_bold {
614
615 my $self = shift;
616 my $weight = $_[0];
617
618 $weight = 0x2BC if not defined $weight; # Bold text
619 $weight = 0x2BC if $weight == 1; # Bold text
620 $weight = 0x190 if $weight == 0; # Normal text
621 $weight = 0x190 if $weight < 0x064; # Lower bound
622 $weight = 0x190 if $weight > 0x3E8; # Upper bound
623
624 $self->{_bold} = $weight;
625}
626
627
628###############################################################################
629#
630# set_border($style)
631#
632# Set cells borders to the same style
633#
634sub set_border {
635
636 my $self = shift;
637 my $style = $_[0];
638
639 $self->set_bottom($style);
640 $self->set_top($style);
641 $self->set_left($style);
642 $self->set_right($style);
643}
644
645
646###############################################################################
647#
648# set_border_color($color)
649#
650# Set cells border to the same color
651#
652sub set_border_color {
653
654 my $self = shift;
655 my $color = $_[0];
656
657 $self->set_bottom_color($color);
658 $self->set_top_color($color);
659 $self->set_left_color($color);
660 $self->set_right_color($color);
661}
662
663
664###############################################################################
665#
666# set_rotation($angle)
667#
668# Set the rotation angle of the text. An alignment property.
669#
670sub set_rotation {
671
672 my $self = shift;
673 my $rotation = $_[0];
674
675 # Argument should be a number
676 return if $rotation !~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/;
677
678 # The arg type can be a double but the Excel dialog only allows integers.
679 $rotation = int $rotation;
680
681 if ($rotation == 270) {
682 $rotation = 255;
683 }
684 elsif ($rotation >= -90 or $rotation <= 90) {
685 $rotation = -$rotation +90 if $rotation < 0;
686 }
687 else {
688 carp "Rotation $rotation outside range: -90 <= angle <= 90";
689 $rotation = 0;
690 }
691
692 $self->{_rotation} = $rotation;
693}
694
695
696###############################################################################
697#
698# set_format_properties()
699#
700# Convert hashes of properties to method calls.
701#
702sub set_format_properties {
703
704 my $self = shift;
705
706 my %properties = @_; # Merge multiple hashes into one
707
708 while (my($key, $value) = each(%properties)) {
709
710 # Strip leading "-" from Tk style properties e.g. -color => 'red'.
711 $key =~ s/^-//;
712
713 # Create a sub to set the property.
714 my $sub = \&{"set_$key"};
715 $sub->($self, $value);
716 }
717}
718
719# Renamed rarely used set_properties() to set_format_properties() to avoid
720# confusion with Workbook method of the same name. The following acts as an
721# alias for any code that uses the old name.
722*set_properties = *set_format_properties;
723
724
725###############################################################################
726#
727# AUTOLOAD. Deus ex machina.
728#
729# Dynamically create set methods that aren't already defined.
730#
731sub AUTOLOAD {
732
733 my $self = shift;
734
735 # Ignore calls to DESTROY
736 return if $AUTOLOAD =~ /::DESTROY$/;
737
738 # Check for a valid method names, i.e. "set_xxx_yyy".
739 $AUTOLOAD =~ /.*::set(\w+)/ or die "Unknown method: $AUTOLOAD\n";
740
741 # Match the attribute, i.e. "_xxx_yyy".
742 my $attribute = $1;
743
744 # Check that the attribute exists
745 exists $self->{$attribute} or die "Unknown method: $AUTOLOAD\n";
746
747 # The attribute value
748 my $value;
749
750
751 # There are two types of set methods: set_property() and
752 # set_property_color(). When a method is AUTOLOADED we store a new anonymous
753 # sub in the appropriate slot in the symbol table. The speeds up subsequent
754 # calls to the same method.
755 #
756 no strict 'refs'; # To allow symbol table hackery
757
758 if ($AUTOLOAD =~ /.*::set\w+color$/) {
759 # For "set_property_color" methods
760 $value = _get_color($_[0]);
761
762 *{$AUTOLOAD} = sub {
763 my $self = shift;
764
765 $self->{$attribute} = _get_color($_[0]);
766 };
767 }
768 else {
769
770 $value = $_[0];
771 $value = 1 if not defined $value; # The default value is always 1
772
773 *{$AUTOLOAD} = sub {
774 my $self = shift;
775 my $value = shift;
776
777 $value = 1 if not defined $value;
778 $self->{$attribute} = $value;
779 };
780 }
781
782
783 $self->{$attribute} = $value;
784}
785
786
7871;
788
789
790__END__
791
792
793=head1 NAME
794
795Format - A class for defining Excel formatting.
796
797=head1 SYNOPSIS
798
799See the documentation for Spreadsheet::WriteExcel
800
801=head1 DESCRIPTION
802
803This module is used in conjunction with Spreadsheet::WriteExcel.
804
805=head1 AUTHOR
806
807John McNamara jmcnamara@cpan.org
808
809=head1 COPYRIGHT
810
811© MM-MMX, John McNamara.
812
813All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.