@@ -557,21 +557,6 @@ def _multiline_check(self, text: AnyStr) -> bool:
557
557
558
558
return split_character in text
559
559
560
- def _multiline_split (self , text : AnyStr ) -> list [AnyStr ]:
561
- return text .split ("\n " if isinstance (text , str ) else b"\n " )
562
-
563
- def _multiline_spacing (
564
- self ,
565
- font : ImageFont .ImageFont | ImageFont .FreeTypeFont | ImageFont .TransposedFont ,
566
- spacing : float ,
567
- stroke_width : float ,
568
- ) -> float :
569
- return (
570
- self .textbbox ((0 , 0 ), "A" , font , stroke_width = stroke_width )[3 ]
571
- + stroke_width
572
- + spacing
573
- )
574
-
575
560
def text (
576
561
self ,
577
562
xy : tuple [float , float ],
@@ -699,29 +684,30 @@ def draw_text(ink: int, stroke_width: float = 0) -> None:
699
684
# Only draw normal text
700
685
draw_text (ink )
701
686
702
- def multiline_text (
687
+ def _prepare_multiline_text (
703
688
self ,
704
689
xy : tuple [float , float ],
705
690
text : AnyStr ,
706
- fill : _Ink | None = None ,
707
691
font : (
708
692
ImageFont .ImageFont
709
693
| ImageFont .FreeTypeFont
710
694
| ImageFont .TransposedFont
711
695
| None
712
- ) = None ,
713
- anchor : str | None = None ,
714
- spacing : float = 4 ,
715
- align : str = "left" ,
716
- direction : str | None = None ,
717
- features : list [str ] | None = None ,
718
- language : str | None = None ,
719
- stroke_width : float = 0 ,
720
- stroke_fill : _Ink | None = None ,
721
- embedded_color : bool = False ,
722
- * ,
723
- font_size : float | None = None ,
724
- ) -> None :
696
+ ),
697
+ anchor : str | None ,
698
+ spacing : float ,
699
+ align : str ,
700
+ direction : str | None ,
701
+ features : list [str ] | None ,
702
+ language : str | None ,
703
+ stroke_width : float ,
704
+ embedded_color : bool ,
705
+ font_size : float | None ,
706
+ ) -> tuple [
707
+ ImageFont .ImageFont | ImageFont .FreeTypeFont | ImageFont .TransposedFont ,
708
+ str ,
709
+ list [tuple [tuple [float , float ], AnyStr ]],
710
+ ]:
725
711
if direction == "ttb" :
726
712
msg = "ttb direction is unsupported for multiline text"
727
713
raise ValueError (msg )
@@ -740,11 +726,21 @@ def multiline_text(
740
726
741
727
widths = []
742
728
max_width : float = 0
743
- lines = self ._multiline_split (text )
744
- line_spacing = self ._multiline_spacing (font , spacing , stroke_width )
729
+ lines = text .split ("\n " if isinstance (text , str ) else b"\n " )
730
+ line_spacing = (
731
+ self .textbbox ((0 , 0 ), "A" , font , stroke_width = stroke_width )[3 ]
732
+ + stroke_width
733
+ + spacing
734
+ )
735
+
745
736
for line in lines :
746
737
line_width = self .textlength (
747
- line , font , direction = direction , features = features , language = language
738
+ line ,
739
+ font ,
740
+ direction = direction ,
741
+ features = features ,
742
+ language = language ,
743
+ embedded_color = embedded_color ,
748
744
)
749
745
widths .append (line_width )
750
746
max_width = max (max_width , line_width )
@@ -755,6 +751,7 @@ def multiline_text(
755
751
elif anchor [1 ] == "d" :
756
752
top -= (len (lines ) - 1 ) * line_spacing
757
753
754
+ parts = []
758
755
for idx , line in enumerate (lines ):
759
756
left = xy [0 ]
760
757
width_difference = max_width - widths [idx ]
@@ -766,18 +763,81 @@ def multiline_text(
766
763
left -= width_difference
767
764
768
765
# then align by align parameter
769
- if align == "left" :
766
+ if align in ( "left" , "justify" ) :
770
767
pass
771
768
elif align == "center" :
772
769
left += width_difference / 2.0
773
770
elif align == "right" :
774
771
left += width_difference
775
772
else :
776
- msg = 'align must be "left", "center" or "right "'
773
+ msg = 'align must be "left", "center", "right" or "justify "'
777
774
raise ValueError (msg )
778
775
776
+ if align == "justify" and width_difference != 0 :
777
+ words = line .split (" " if isinstance (text , str ) else b" " )
778
+ word_widths = [
779
+ self .textlength (
780
+ word ,
781
+ font ,
782
+ direction = direction ,
783
+ features = features ,
784
+ language = language ,
785
+ embedded_color = embedded_color ,
786
+ )
787
+ for word in words
788
+ ]
789
+ width_difference = max_width - sum (word_widths )
790
+ for i , word in enumerate (words ):
791
+ parts .append (((left , top ), word ))
792
+ left += word_widths [i ] + width_difference / (len (words ) - 1 )
793
+ else :
794
+ parts .append (((left , top ), line ))
795
+
796
+ top += line_spacing
797
+
798
+ return font , anchor , parts
799
+
800
+ def multiline_text (
801
+ self ,
802
+ xy : tuple [float , float ],
803
+ text : AnyStr ,
804
+ fill : _Ink | None = None ,
805
+ font : (
806
+ ImageFont .ImageFont
807
+ | ImageFont .FreeTypeFont
808
+ | ImageFont .TransposedFont
809
+ | None
810
+ ) = None ,
811
+ anchor : str | None = None ,
812
+ spacing : float = 4 ,
813
+ align : str = "left" ,
814
+ direction : str | None = None ,
815
+ features : list [str ] | None = None ,
816
+ language : str | None = None ,
817
+ stroke_width : float = 0 ,
818
+ stroke_fill : _Ink | None = None ,
819
+ embedded_color : bool = False ,
820
+ * ,
821
+ font_size : float | None = None ,
822
+ ) -> None :
823
+ font , anchor , lines = self ._prepare_multiline_text (
824
+ xy ,
825
+ text ,
826
+ font ,
827
+ anchor ,
828
+ spacing ,
829
+ align ,
830
+ direction ,
831
+ features ,
832
+ language ,
833
+ stroke_width ,
834
+ embedded_color ,
835
+ font_size ,
836
+ )
837
+
838
+ for xy , line in lines :
779
839
self .text (
780
- ( left , top ) ,
840
+ xy ,
781
841
line ,
782
842
fill ,
783
843
font ,
@@ -789,7 +849,6 @@ def multiline_text(
789
849
stroke_fill = stroke_fill ,
790
850
embedded_color = embedded_color ,
791
851
)
792
- top += line_spacing
793
852
794
853
def textlength (
795
854
self ,
@@ -891,69 +950,26 @@ def multiline_textbbox(
891
950
* ,
892
951
font_size : float | None = None ,
893
952
) -> tuple [float , float , float , float ]:
894
- if direction == "ttb" :
895
- msg = "ttb direction is unsupported for multiline text"
896
- raise ValueError (msg )
897
-
898
- if anchor is None :
899
- anchor = "la"
900
- elif len (anchor ) != 2 :
901
- msg = "anchor must be a 2 character string"
902
- raise ValueError (msg )
903
- elif anchor [1 ] in "tb" :
904
- msg = "anchor not supported for multiline text"
905
- raise ValueError (msg )
906
-
907
- if font is None :
908
- font = self ._getfont (font_size )
909
-
910
- widths = []
911
- max_width : float = 0
912
- lines = self ._multiline_split (text )
913
- line_spacing = self ._multiline_spacing (font , spacing , stroke_width )
914
- for line in lines :
915
- line_width = self .textlength (
916
- line ,
917
- font ,
918
- direction = direction ,
919
- features = features ,
920
- language = language ,
921
- embedded_color = embedded_color ,
922
- )
923
- widths .append (line_width )
924
- max_width = max (max_width , line_width )
925
-
926
- top = xy [1 ]
927
- if anchor [1 ] == "m" :
928
- top -= (len (lines ) - 1 ) * line_spacing / 2.0
929
- elif anchor [1 ] == "d" :
930
- top -= (len (lines ) - 1 ) * line_spacing
953
+ font , anchor , lines = self ._prepare_multiline_text (
954
+ xy ,
955
+ text ,
956
+ font ,
957
+ anchor ,
958
+ spacing ,
959
+ align ,
960
+ direction ,
961
+ features ,
962
+ language ,
963
+ stroke_width ,
964
+ embedded_color ,
965
+ font_size ,
966
+ )
931
967
932
968
bbox : tuple [float , float , float , float ] | None = None
933
969
934
- for idx , line in enumerate (lines ):
935
- left = xy [0 ]
936
- width_difference = max_width - widths [idx ]
937
-
938
- # first align left by anchor
939
- if anchor [0 ] == "m" :
940
- left -= width_difference / 2.0
941
- elif anchor [0 ] == "r" :
942
- left -= width_difference
943
-
944
- # then align by align parameter
945
- if align == "left" :
946
- pass
947
- elif align == "center" :
948
- left += width_difference / 2.0
949
- elif align == "right" :
950
- left += width_difference
951
- else :
952
- msg = 'align must be "left", "center" or "right"'
953
- raise ValueError (msg )
954
-
970
+ for xy , line in lines :
955
971
bbox_line = self .textbbox (
956
- ( left , top ) ,
972
+ xy ,
957
973
line ,
958
974
font ,
959
975
anchor ,
@@ -973,8 +989,6 @@ def multiline_textbbox(
973
989
max (bbox [3 ], bbox_line [3 ]),
974
990
)
975
991
976
- top += line_spacing
977
-
978
992
if bbox is None :
979
993
return xy [0 ], xy [1 ], xy [0 ], xy [1 ]
980
994
return bbox
0 commit comments