22import regsub
33import string
44import sys
5+ from types import StringType
56
67
78AS_IS = None
@@ -12,66 +13,94 @@ class NullFormatter:
1213 def __init__ (self ): pass
1314 def end_paragraph (self , blankline ): pass
1415 def add_line_break (self ): pass
15- def add_hor_rule (self ): pass
16+ def add_hor_rule (self , abswidth = None , percentwidth = 1.0 ,
17+ height = None , align = None ): pass
1618 def add_label_data (self , format , counter ): pass
1719 def add_flowing_data (self , data ): pass
1820 def add_literal_data (self , data ): pass
1921 def flush_softspace (self ): pass
22+ def push_alignment (self , align ): pass
23+ def pop_alignment (self ): pass
2024 def push_font (self , x ): pass
2125 def pop_font (self ): pass
2226 def push_margin (self , margin ): pass
2327 def pop_margin (self ): pass
2428 def set_spacing (self , spacing ): pass
25- def push_style (self , style ): pass
26- def pop_style (self ): pass
29+ def push_style (self , * styles ): pass
30+ def pop_style (self , n = 1 ): pass
31+ def assert_line_data (self , flag = 1 ): pass
2732
2833
2934class AbstractFormatter :
3035
3136 def __init__ (self , writer ):
3237 self .writer = writer # Output device
38+ self .align = None # Current alignment
39+ self .align_stack = [] # Alignment stack
3340 self .font_stack = [] # Font state
3441 self .margin_stack = [] # Margin state
3542 self .spacing = None # Vertical spacing state
3643 self .style_stack = [] # Other state, e.g. color
3744 self .nospace = 1 # Should leading space be suppressed
3845 self .softspace = 0 # Should a space be inserted
46+ self .para_end = 1 # Just ended a paragraph
47+ self .parskip = 0 # Skipped space between paragraphs?
48+ self .hard_break = 1 # Have a hard break
49+ self .have_label = 0
3950
4051 def end_paragraph (self , blankline ):
41- if not self .nospace :
42- self .writer .send_paragraph (blankline )
43- self .nospace = 1
52+ if not self .hard_break :
53+ self .writer .send_line_break ()
54+ self .have_label = 0
55+ if self .parskip < blankline and not self .have_label :
56+ self .writer .send_paragraph (blankline - self .parskip )
57+ self .parskip = blankline
58+ self .have_label = 0
59+ self .hard_break = self .nospace = self .para_end = 1
4460 self .softspace = 0
4561
4662 def add_line_break (self ):
47- self .writer .send_line_break ()
48- self .nospace = 1
63+ if not (self .hard_break or self .para_end ):
64+ self .writer .send_line_break ()
65+ self .have_label = self .parskip = 0
66+ self .hard_break = self .nospace = 1
4967 self .softspace = 0
5068
51- def add_hor_rule (self ):
52- self .writer .send_hor_rule ()
53- self .nospace = 1
54- self .softspace = 0
55-
56- def add_label_data (self , format , counter ):
57- data = self .format_counter (format , counter )
58- self .writer .send_label_data (data )
69+ def add_hor_rule (self , * args , ** kw ):
70+ if not self .hard_break :
71+ self .writer .send_line_break ()
72+ apply (self .writer .send_hor_rule , args , kw )
73+ self .hard_break = self .nospace = 1
74+ self .have_label = self .para_end = self .softspace = self .parskip = 0
75+
76+ def add_label_data (self , format , counter , blankline = None ):
77+ if self .have_label or not self .hard_break :
78+ self .writer .send_line_break ()
79+ if not self .para_end :
80+ self .writer .send_paragraph ((blankline and 1 ) or 0 )
81+ if type (format ) is StringType :
82+ self .writer .send_label_data (self .format_counter (format , counter ))
83+ else :
84+ self .writer .send_label_data (format )
85+ self .nospace = self .have_label = self .hard_break = self .para_end = 1
86+ self .softspace = self .parskip = 0
5987
6088 def format_counter (self , format , counter ):
61- if counter <= 0 :
62- return format
6389 label = ''
6490 for c in format :
6591 try :
6692 if c == '1' :
67- c = '%d' % counter
93+ label = label + ( '%d' % counter )
6894 elif c in 'aA' :
69- c = self .format_letter (c , counter )
95+ if counter > 0 :
96+ label = label + self .format_letter (c , counter )
7097 elif c in 'iI' :
71- c = self .format_roman (c , counter )
98+ if counter > 0 :
99+ label = label + self .format_roman (c , counter )
100+ else :
101+ label = label + c
72102 except :
73- pass
74- label = label + c
103+ label = label + c
75104 return label
76105
77106 def format_letter (self , case , counter ):
@@ -85,57 +114,88 @@ def format_letter(self, case, counter):
85114 def format_roman (self , case , counter ):
86115 ones = ['i' , 'x' , 'c' , 'm' ]
87116 fives = ['v' , 'l' , 'd' ]
88- label = ''
89- index = 0
117+ label , index = '' , 0
90118 # This will die of IndexError when counter is too big
91119 while counter > 0 :
92120 counter , x = divmod (counter , 10 )
93121 if x == 9 :
94- s = ones [index ] + ones [index + 1 ]
122+ label = ones [index ] + ones [index + 1 ] + label
95123 elif x == 4 :
96- s = ones [index ] + fives [index ]
124+ label = ones [index ] + fives [index ] + label
97125 else :
98126 if x >= 5 :
99127 s = fives [index ]
100128 x = x - 5
101129 else :
102130 s = ''
103131 s = s + ones [index ]* x
104- label = s + label
132+ label = s + label
105133 index = index + 1
106- if case == 'I' : label = string .upper (label )
134+ if case == 'I' :
135+ return string .upper (label )
107136 return label
108137
109- def add_flowing_data (self , data ):
138+ def add_flowing_data (self , data ,
139+ # These are only here to load them into locals:
140+ whitespace = string .whitespace ,
141+ join = string .join , split = string .split ):
110142 if not data : return
111143 # The following looks a bit convoluted but is a great improvement over
112144 # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
113- prespace = data [0 ] in string .whitespace
114- postspace = data [- 1 ] in string .whitespace
115- data = string .join (string .split (data ))
116- if self .nospace and prespace :
117- if not data : return
118- prespace = 0
119- elif self .softspace :
120- prespace = 1
121- self .nospace = self .softspace = 0
122- if postspace :
123- self .softspace = 1
124- if prespace : data = ' ' + data
145+ prespace = data [:1 ] in whitespace
146+ postspace = data [- 1 :] in whitespace
147+ data = join (split (data ))
148+ if self .nospace and not data :
149+ return
150+ elif prespace or self .softspace :
151+ if not data :
152+ if not self .nospace :
153+ self .softspace = 1
154+ self .parskip = 0
155+ return
156+ if not self .nospace :
157+ data = ' ' + data
158+ self .hard_break = self .nospace = self .para_end = \
159+ self .parskip = self .have_label = 0
160+ self .softspace = postspace
125161 self .writer .send_flowing_data (data )
126162
127163 def add_literal_data (self , data ):
128- if self .softspace and data [:1 ] != '\n ' :
129- data = ' ' + data
130- self .nospace = self .softspace = 0
164+ if not data : return
165+ # Caller is expected to cause flush_softspace() if needed.
166+ self .hard_break = data [- 1 :] == '\n '
167+ self .nospace = self .para_end = self .softspace = \
168+ self .parskip = self .have_label = 0
131169 self .writer .send_literal_data (data )
132170
133171 def flush_softspace (self ):
134172 if self .softspace :
135- self .nospace = self .softspace = 0
173+ self .hard_break = self .nospace = self .para_end = self .parskip = \
174+ self .have_label = self .softspace = 0
136175 self .writer .send_flowing_data (' ' )
137176
177+ def push_alignment (self , align ):
178+ if align and align != self .align :
179+ self .writer .new_alignment (align )
180+ self .align = align
181+ self .align_stack .append (align )
182+ else :
183+ self .align_stack .append (self .align )
184+
185+ def pop_alignment (self ):
186+ if self .align_stack :
187+ del self .align_stack [- 1 ]
188+ if self .align_stack :
189+ self .align = align = self .align_stack [- 1 ]
190+ self .writer .new_alignment (align )
191+ else :
192+ self .align = None
193+ self .writer .new_alignment (None )
194+
138195 def push_font (self , (size , i , b , tt )):
196+ if self .softspace :
197+ self .hard_break = self .nospace = self .para_end = self .softspace = 0
198+ self .writer .send_flowing_data (' ' )
139199 if self .font_stack :
140200 csize , ci , cb , ctt = self .font_stack [- 1 ]
141201 if size is AS_IS : size = csize
@@ -147,6 +207,9 @@ def push_font(self, (size, i, b, tt)):
147207 self .writer .new_font (font )
148208
149209 def pop_font (self ):
210+ if self .softspace :
211+ self .hard_break = self .nospace = self .para_end = self .softspace = 0
212+ self .writer .send_flowing_data (' ' )
150213 if self .font_stack :
151214 del self .font_stack [- 1 ]
152215 if self .font_stack :
@@ -157,36 +220,69 @@ def pop_font(self):
157220
158221 def push_margin (self , margin ):
159222 self .margin_stack .append (margin )
160- self .writer .new_margin (margin , len (self .margin_stack ))
223+ fstack = filter (None , self .margin_stack )
224+ if not margin and fstack :
225+ margin = fstack [- 1 ]
226+ self .writer .new_margin (margin , len (fstack ))
161227
162228 def pop_margin (self ):
163229 if self .margin_stack :
164230 del self .margin_stack [- 1 ]
165- if self .margin_stack :
166- margin = self .margin_stack [- 1 ]
231+ fstack = filter (None , self .margin_stack )
232+ if fstack :
233+ margin = fstack [- 1 ]
167234 else :
168235 margin = None
169- self .writer .new_margin (margin , len (self . margin_stack ))
236+ self .writer .new_margin (margin , len (fstack ))
170237
171238 def set_spacing (self , spacing ):
172239 self .spacing = spacing
173240 self .writer .new_spacing (spacing )
174241
175- def push_style (self , style ):
176- self .style_stack .append (style )
242+ def push_style (self , * styles ):
243+ if self .softspace :
244+ self .hard_break = self .nospace = self .para_end = self .softspace = 0
245+ self .writer .send_flowing_data (' ' )
246+ for style in styles :
247+ self .style_stack .append (style )
177248 self .writer .new_styles (tuple (self .style_stack ))
178249
179- def pop_style (self ):
180- if self .style_stack :
181- del self .style_stack [- 1 ]
250+ def pop_style (self , n = 1 ):
251+ if self .softspace :
252+ self .hard_break = self .nospace = self .para_end = self .softspace = 0
253+ self .writer .send_flowing_data (' ' )
254+ del self .style_stack [- n :]
182255 self .writer .new_styles (tuple (self .style_stack ))
183256
257+ def assert_line_data (self , flag = 1 ):
258+ self .nospace = self .hard_break = not flag
259+ self .para_end = self .have_label = 0
260+
261+
262+ class NullWriter :
263+ """Minimal writer interface to use in testing.
264+ """
265+ def new_alignment (self , align ): pass
266+ def new_font (self , font ): pass
267+ def new_margin (self , margin , level ): pass
268+ def new_spacing (self , spacing ): pass
269+ def new_styles (self , styles ): pass
270+ def send_paragraph (self , blankline ): pass
271+ def send_line_break (self ): pass
272+ def send_hor_rule (self , * args , ** kw ): pass
273+ def send_label_data (self , data ): pass
274+ def send_flowing_data (self , data ): pass
275+ def send_literal_data (self , data ): pass
276+
184277
185278class AbstractWriter :
186279
187280 def __init__ (self ):
188281 pass
189282
283+ def new_alignment (self , align ):
284+ print "new_alignment(%s)" % `align`
285+
190286 def new_font (self , font ):
191287 print "new_font(%s)" % `font`
192288
@@ -205,7 +301,7 @@ def send_paragraph(self, blankline):
205301 def send_line_break (self ):
206302 print "send_line_break()"
207303
208- def send_hor_rule (self ):
304+ def send_hor_rule (self , * args , ** kw ):
209305 print "send_hor_rule()"
210306
211307 def send_label_data (self , data ):
@@ -240,7 +336,7 @@ def send_line_break(self):
240336 self .col = 0
241337 self .atbreak = 0
242338
243- def send_hor_rule (self ):
339+ def send_hor_rule (self , * args , ** kw ):
244340 self .file .write ('\n ' )
245341 self .file .write ('-' * self .maxcol )
246342 self .file .write ('\n ' )
0 commit comments