34
35
36:- module(html_text,
37 [ html_text/1, 38 html_text/2 39 ]). 40:- use_module(library(debug),[debug/3]). 41:- autoload(library(ansi_term),[ansi_format/3]). 42:- autoload(library(apply),[foldl/4,maplist/3,maplist/2]). 43:- autoload(library(error),[must_be/2]). 44:- autoload(library(lists),
45 [ append/3, list_to_set/2, reverse/2, delete/3, sum_list/2,
46 nth1/3, max_list/2
47 ]). 48:- autoload(library(option),[select_option/4,merge_options/3,option/3]). 49:- autoload(library(sgml),[xml_is_dom/1,load_html/3]). 50:- autoload(library(lynx/format),[format_paragraph/2,trim_line/2]). 51:- autoload(library(lynx/html_style),
52 [ element_css/3, css_block_options/5, css_inline_options/3,
53 attrs_classes/2, style_css_attrs/2
54 ]). 55
56:- predicate_options(html_text/2, 2,
57 [ margin_left(integer),
58 margin_right(integer),
59 width(integer),
60 text_align(oneof([justify, left]))
61 ]). 62
70
84
85html_text(Input) :-
86 html_text(Input, []).
87
88html_text(Input, Options) :-
89 ( xml_is_dom(Input)
90 -> DOM = Input
91 ; load_html(Input, DOM, Options)
92 ),
93 default_state(State0),
94 state_options(Options, State0, State),
95 init_nl,
96 format_dom(DOM, State).
97
98state_options([], State, State).
99state_options([H|T], State0, State) :-
100 H =.. [Key,Value],
101 ( fmt_option(Key, Type, _Default)
102 -> must_be(Type, Value),
103 State1 = State0.put(Key,Value)
104 ; State1 = State0
105 ),
106 state_options(T, State1, State).
107
108fmt_option(margin_left, integer, 0).
109fmt_option(margin_right, integer, 0).
110fmt_option(text_align, oneof([justify, left]), justify).
111fmt_option(width, between(10,1000), 72).
112
113default_state(State) :-
114 findall(Key-Value, fmt_option(Key, _, Value), Pairs),
115 dict_pairs(Dict, _, Pairs),
116 State = Dict.put(_{ style:[], list:[]}).
117
121
122format_dom([], _) :-
123 !.
124format_dom([H|T], State) :-
125 format_dom(H, State),
126 !,
127 format_dom(T, State).
128format_dom(Content, State) :-
129 Content = [H0|_],
130 \+ is_block_element(H0),
131 !,
132 ( append(Inline, [H|T], Content),
133 is_block_element(H)
134 -> true
135 ; Inline = Content
136 ),
137 format_dom(element(p, [], Inline), State),
138 format_dom([H|T], State).
139format_dom(element(html, _, Content), State) :-
140 !,
141 format_dom(Content, State).
142format_dom(element(head, _, _), _) :-
143 !.
144format_dom(element(body, _, Content), State) :-
145 !,
146 format_dom(Content, State).
147format_dom(element(E, Attrs, Content), State) :-
148 !,
149 ( format_element(E, Attrs, Content, State)
150 -> true
151 ; debug(format(html), 'Skipped block element ~q', [E])
152 ).
153
154format_element(pre, Attrs, [Content], State) :-
155 !,
156 block_element(pre, Attrs, Top-Bottom, BlockAttrs, Style),
157 update_style(Style, State, State1),
158 ask_nl(Top),
159 emit_code(Content, BlockAttrs, State1),
160 ask_nl(Bottom).
161format_element(table, Attrs, Content, State) :-
162 !,
163 block_element(table, Attrs, Top-Bottom, BlockAttrs, Style),
164 update_style(Style, State, State1),
165 state_par_properties(State1, BlockAttrs, BlockOptions),
166 ask_nl(Top),
167 emit_nl,
168 format_table(Content, Attrs, BlockOptions, State1),
169 ask_nl(Bottom).
170format_element(hr, Attrs, _, State) :-
171 !,
172 block_element(hr, Attrs, Top-Bottom, BlockAttrs, Style),
173 update_style(Style, State, State1),
174 state_par_properties(State1, BlockAttrs, BlockOptions),
175 ask_nl(Top),
176 emit_nl,
177 emit_hr(Attrs, BlockOptions, State1),
178 ask_nl(Bottom).
179format_element(Elem, Attrs, Content, State) :-
180 block_element(Elem, Attrs, Top-Bottom, BlockAttrs, Style),
181 !,
182 update_style(Style, State, State1),
183 block_words(Content, SubBlocks, Words, State1),
184 ( Words == []
185 -> true
186 ; ask_nl(Top),
187 emit_block(Words, BlockAttrs, State1),
188 ask_nl(Bottom)
189 ),
190 ( SubBlocks \== []
191 -> update_state_par_properties(BlockAttrs, State1, State2),
192 format_dom(SubBlocks, State2)
193 ; true
194 ).
195format_element(Elem, Attrs, Content, State) :-
196 list_element(Elem, Attrs, Top-Bottom, State, State1),
197 !,
198 open_list(Elem, State1, State2),
199 ask_nl(Top),
200 format_list(Content, Elem, 1, State2),
201 ask_nl(Bottom).
202format_element(Elem, Attrs, Content, State) :-
203 format_list_element(element(Elem, Attrs, Content), none, 0, State).
204
208
209block_element(El, Attrs, Margins, ParOptions, Style) :-
210 block_element(El, Margins0, ParOptions0, Style0),
211 ( nonvar(Attrs),
212 element_css(El, Attrs, CSS)
213 -> css_block_options(CSS, Margins0, Margins, ParOptions, Style1),
214 append(Style1, Style0, Style2),
215 list_to_set(Style2, Style)
216 ; Margins = Margins0,
217 ParOptions = ParOptions0,
218 Style = Style0
219 ).
220
221block_element(p, 1-2, [], []).
222block_element(div, 1-1, [], []).
223block_element(hr, 1-1, [], []).
224block_element(h1, 2-2, [], [bold]).
225block_element(h2, 2-2, [], [bold]).
226block_element(h3, 2-2, [], [bold]).
227block_element(h4, 2-2, [], [bold]).
228block_element(pre, 2-2, [], []).
229block_element(blockquote, 2-2, [margin_left(4), margin_right(4)], []).
230block_element(table, 2-2, [], []).
231
232list_element(ul, _, Margins, State0, State) :-
233 margins(4, 4, State0, State),
234 list_level_margins(State, Margins).
235list_element(ol, _, Margins, State0, State) :-
236 margins(4, 4, State0, State),
237 list_level_margins(State, Margins).
238list_element(dl, _, 2-2, State, State).
239
240list_element(ul).
241list_element(ol).
242list_element(dl).
243
244list_level_margins(State, 2-2) :-
245 nonvar(State),
246 block_list_context(State.get(list)),
247 !.
248list_level_margins(_, 0-0).
249
257
258block_list_context([]).
259block_list_context([dl|_]).
260
261format_list([], _, _, _).
262format_list([H|T], Type, Nth, State) :-
263 format_list_element(H, Type, Nth, State),
264 ( T == []
265 -> true
266 ; Nth1 is Nth + 1,
267 format_list(T, Type, Nth1, State)
268 ).
269
270format_list_element(element(LE, Attrs, Content), Type, Nth, State) :-
271 setup_list_element(LE, Attrs, Type, Nth, ListParProps, State, State1),
272 block_words(Content, Blocks, Words, State1),
273 emit_block(Words, ListParProps, State1),
274 ( Blocks \== []
275 -> ask_nl(2), 276 update_state_par_properties(ListParProps, State1, State2),
277 format_dom(Blocks, State2)
278 ; true
279 ).
280
281setup_list_element(li, _Attrs, _Type, Nth, ListParProps, State, State) :-
282 list_par_properties(State.list, Nth, ListParProps).
283setup_list_element(dt, _Attrs, _Type, _Nth, [], State, State2) :-
284 margins(0, 0, State, State1),
285 update_style([bold], State1, State2).
286setup_list_element(dd, _Attrs, _Type, _Nth, [], State, State1) :-
287 margins(4, 0, State, State1).
288
289list_item_element(li).
290list_item_element(dt).
291list_item_element(dd).
292
293list_par_properties([ul|_More], _, [bullet('\u2022')]).
294list_par_properties([ol|_More], N, [bullet(N)]).
295
296
300
301block_words(Content, RC, Words, State) :-
302 phrase(bwords(Content, RC, State), Words0),
303 join_whitespace(Words0, Words1),
304 trim_line(Words1, Words).
305
306bwords([], [], _) -->
307 !.
308bwords([H|T], Rest, _State) -->
309 { var(Rest),
310 is_block_element(H),
311 !,
312 Rest = [H|T]
313 }.
314bwords([H|T], Rest, State) -->
315 !,
316 bwordsel(H, State),
317 bwords(T, Rest, State).
318
319is_block_element(element(E,_,_)) :-
320 ( block_element(E, _, _, _)
321 ; list_element(E)
322 ; list_item_element(E)
323 ),
324 debug(format(html), 'Found block ~q', [E]),
325 !.
326
327bwordsel(element(Elem, Attrs, Content), State) -->
328 { styled_inline(Elem, Attrs, Margins, Style),
329 !,
330 update_style(Style, State, State1)
331 },
332 left_margin(Margins),
333 bwords(Content, [], State1),
334 right_margin(Margins).
335bwordsel(element(br, _, _), _State) -->
336 [br([])].
337bwordsel(CDATA, State) -->
338 { atomic(CDATA),
339 !,
340 split_string(CDATA, " \n\t\r", "", Words)
341 },
342 words(Words, State).
343bwordsel(element(Elem, _Attrs, _Content), _State) -->
344 { debug(format(html), 'Skipped inline element ~q', [Elem]) }.
345
346left_margin(0-_) --> !.
347left_margin(N-_) --> [b(N,_)].
348
349right_margin(_-0) --> !.
350right_margin(_-N) --> [b(N,_)].
351
352styled_inline(El, Attrs, Margins, Style) :-
353 styled_inline(El, Style0),
354 ( nonvar(Attrs),
355 element_css(El, Attrs, CSS)
356 -> css_inline_options(CSS, Margins, Style1),
357 append(Style1, Style0, Style2),
358 list_to_set(Style2, Style)
359 ; Style = Style0
360 ).
361
362styled_inline(b, [bold]).
363styled_inline(strong, [bold]).
364styled_inline(em, [bold]).
365styled_inline(span, []).
366styled_inline(i, [underline]).
367styled_inline(a, [underline]).
368styled_inline(var, []).
369styled_inline(code, []).
370
375
376words([], _) --> [].
377words([""|T0], State) -->
378 !,
379 { skip_leading_spaces(T0, T) },
380 space,
381 words(T, State).
382words([H|T], State) -->
383 word(H, State),
384 ( {T==[]}
385 -> []
386 ; { skip_leading_spaces(T, T1) },
387 space,
388 words(T1, State)
389 ).
390
391skip_leading_spaces([""|T0], T) :-
392 !,
393 skip_leading_spaces(T0, T).
394skip_leading_spaces(L, L).
395
396word(W, State) -->
397 { string_length(W, Len),
398 ( Style = State.get(style)
399 -> true
400 ; Style = []
401 )
402 },
403 [w(W, Len, Style)].
404
405space -->
406 [b(1,_)].
407
411
412join_whitespace([], []).
413join_whitespace([H0|T0], [H|T]) :-
414 join_whitespace(H0, H, T0, T1),
415 !,
416 join_whitespace(T1, T).
417join_whitespace([H|T0], [H|T]) :-
418 join_whitespace(T0, T).
419
420join_whitespace(b(Len0,_), b(Len,_), T0, T) :-
421 take_whitespace(T0, T, Len0, Len).
422
423take_whitespace([b(Len1,_)|T0], T, Len0, Len) :-
424 !,
425 Len2 is max(Len1,Len0),
426 take_whitespace(T0, T, Len2, Len).
427take_whitespace(L, L, Len, Len).
428
429
430 433
437
438update_style([], State, State) :-
439 !.
440update_style(Extra, State0, State) :-
441 ( get_dict(style, State0, Style0, State, Style)
442 -> add_style(Extra, Style0, Style)
443 ; add_style(Extra, [], Style),
444 put_dict(style, State0, Style, State)
445 ).
446
447add_style(Extra, Style0, Style) :-
448 reverse(Extra, RevExtra),
449 foldl(add1_style, RevExtra, Style0, Style).
450
454
455add1_style(New, Style0, Style) :-
456 ( style_overrides(New, Add, Overrides)
457 -> delete_all(Overrides, Style0, Style1),
458 append(Add, Style1, Style)
459 ; Style = [New|Style0]
460 ).
461
462delete_all([], List, List).
463delete_all([H|T], List0, List) :-
464 delete(List0, H, List1),
465 delete_all(T, List1, List).
466
467style_overrides(normal, [], [bold]).
468style_overrides(fg(C), [fg(C)], [fg(_), hfg(_)]).
469style_overrides(bg(C), [bg(C)], [bg(_), hbg(_)]).
470style_overrides(underline(false), [], [underline]).
471
472margins(Left, Right, State0, State) :-
473 _{ margin_left:ML0, margin_right:MR0 } >:< State0,
474 ML is ML0 + Left,
475 MR is MR0 + Right,
476 State = State0.put(_{margin_left:ML, margin_right:MR}).
477
478open_list(Type, State0, State) :-
479 get_dict(list, State0, Lists, State, [Type|Lists]).
480
481update_state_par_properties([], State, State).
482update_state_par_properties([H|T], State0, State) :-
483 H =.. [ Key, Value ],
484 State1 = State0.put(Key,Value),
485 update_state_par_properties(T, State1, State).
486
491
492state_par_properties(State, Props) :-
493 Props0 = [ margin_left(LM),
494 margin_right(RM),
495 text_align(TA),
496 width(W),
497 pad(Pad)
498 ],
499 _{margin_left:LM, margin_right:RM, text_align:TA, width:W,
500 pad:Pad} >:< State,
501 filled_par_props(Props0, Props).
502
503filled_par_props([], []).
504filled_par_props([H|T0], [H|T]) :-
505 arg(1, H, A),
506 nonvar(A),
507 !,
508 filled_par_props(T0, T).
509filled_par_props([_|T0], T) :-
510 filled_par_props(T0, T).
511
512
513state_par_properties(State, Options, BlockOptions) :-
514 state_par_properties(State, Options0),
515 foldl(merge_par_option, Options, Options0, BlockOptions).
516
517merge_par_option(margin_left(ML0), Options0, [margin_left(ML)|Options1]) :-
518 !,
519 select_option(margin_left(ML1), Options0, Options1, 0),
520 ML is ML0+ML1.
521merge_par_option(margin_right(MR0), Options0, [margin_right(MR)|Options1]) :-
522 !,
523 select_option(margin_right(MR1), Options0, Options1, 0),
524 MR is MR0+MR1.
525merge_par_option(Opt, Options0, Options) :-
526 merge_options([Opt], Options0, Options).
527
533
534emit_block([], _, _) :-
535 !.
536emit_block(Words, Options, State) :-
537 state_par_properties(State, Options, BlockOptions),
538 use_current_position(BlockOptions, BlockOptions1),
539 ask_nl(1),
540 emit_nl,
541 format_paragraph(Words, BlockOptions1),
542 ask_nl(1).
543
544use_current_position(Options0, Options) :-
545 nb_current(nl_pending, start),
546 line_position(current_output, Pos),
547 Pos > 0,
548 !,
549 Hang is -Pos,
550 Options = [hang(Hang)|Options0].
551use_current_position(Options, Options).
552
553
559
560init_nl :-
561 nb_setval(nl_pending, start).
562
563init_nl(Old) :-
564 ( nb_current(nl_pending, Old)
565 -> true
566 ; Old = []
567 ),
568 nb_setval(nl_pending, start).
569exit_nl(Old) :-
570 nb_setval(nl_pending, Old).
571
572ask_nl(N) :-
573 ( nb_current(nl_pending, N0)
574 -> ( N0 == start
575 -> true
576 ; integer(N0)
577 -> N1 is max(N0, N),
578 nb_setval(nl_pending, N1)
579 ; nb_setval(nl_pending, N)
580 )
581 ; nb_setval(nl_pending, N)
582 ).
583
584emit_nl :-
585 ( nb_current(nl_pending, N),
586 integer(N)
587 -> forall(between(1,N,_), nl)
588 ; true
589 ),
590 nb_setval(nl_pending, 0).
591
592
593 596
598
599emit_code(Content, BlockAttrs, State) :-
600 Style = State.style,
601 split_string(Content, "\n", "", Lines),
602 option(margin_left(LM0), BlockAttrs, 4),
603 LM is LM0+State.margin_left,
604 ask_nl(1),
605 emit_nl,
606 emit_code_lines(Lines, 1, LM, Style),
607 ask_nl(1).
608
609emit_code_lines([], _, _, _).
610emit_code_lines([H|T], LineNo, LM, Style) :-
611 emit_code_line(H, LineNo, LM, Style),
612 LineNo1 is LineNo + 1,
613 emit_code_lines(T, LineNo1, LM, Style).
614
615emit_code_line(Line, _LineNo, LM, Style) :-
616 emit_nl,
617 emit_indent(LM),
618 ( Style == []
619 -> write(Line)
620 ; ansi_format(Style, '~s', [Line])
621 ),
622 ask_nl(1).
623
624emit_indent(N) :-
625 forall(between(1, N, _),
626 put_char(' ')).
627
628
629 632
634
635format_table(Content, Attrs, BlockAttrs, State) :-
636 tty_state(TTY),
637 option(margin_left(ML), BlockAttrs, 0),
638 option(margin_right(MR), BlockAttrs, 0),
639 MaxTableWidth is State.width - ML - MR,
640 table_cell_state(Attrs, State, CellState0),
641 CellState = CellState0.put(tty, TTY),
642 phrase(rows(Content), Rows),
643 columns(Rows, Columns),
644 maplist(auto_column_width(CellState), Columns, Widths),
645 column_widths(Widths, MaxTableWidth, ColWidths),
646 maplist(format_row(ColWidths, CellState, ML), Rows).
647
648tty_state(TTY) :-
649 stream_property(current_output, tty(true)),
650 !,
651 TTY = true.
652tty_state(false).
653
654
659
660column_widths(Widths, MaxTableWidth, Widths) :-
661 sum_list(Widths, AutoWidth),
662 AutoWidth =< MaxTableWidth,
663 !.
664column_widths(AutoWidths, MaxTableWidth, Widths) :-
665 sort(0, >=, AutoWidths, Sorted),
666 append(Wrapped, Keep, Sorted),
667 sum_list(Keep, KeepWidth),
668 KeepWidth < MaxTableWidth/2,
669 length(Wrapped, NWrapped),
670 WideWidth is round((MaxTableWidth-KeepWidth)/NWrapped),
671 ( [KeepW|_] = Keep
672 -> true
673 ; KeepW = 0
674 ),
675 !,
676 maplist(truncate_column(KeepW,WideWidth), AutoWidths, Widths).
677
678truncate_column(Keep, WideWidth, AutoWidth, Width) :-
679 ( AutoWidth =< Keep
680 -> Width = AutoWidth
681 ; Width = WideWidth
682 ).
683
684table_cell_state(Attrs, State, CellState) :-
685 ( element_css(table, Attrs, CSS)
686 -> true
687 ; CSS = []
688 ),
689 option(padding_left(PL), CSS, 1),
690 option(padding_right(PR), CSS, 1),
691 CellState = State.put(_{margin_left:PL, margin_right:PR}).
692
693
695
696rows([]) --> [].
697rows([H|T]) --> rows(H), rows(T).
698rows([element(tbody,_,Content)|T]) --> rows(Content), rows(T).
699rows([element(tr,Attrs,Columns)|T]) --> [row(Columns, Attrs)], rows(T).
700
705
706columns(Rows, Columns) :-
707 columns(Rows, 1, Columns).
708
709columns(Rows, I, Columns) :-
710 maplist(row_column(I, Found), Rows, H),
711 ( Found == true
712 -> Columns = [H|T],
713 I2 is I + 1,
714 columns(Rows, I2, T)
715 ; Columns = []
716 ).
717
718row_column(I, Found, row(Columns, _Attrs), Cell) :-
719 ( nth1(I, Columns, Cell)
720 -> Found = true
721 ; Cell = element(td,[],[])
722 ).
723
724auto_column_width(State, Col, Width) :-
725 maplist(auto_cell_width(State), Col, Widths),
726 max_list(Widths, Width).
727
728auto_cell_width(State, Cell, Width) :-
729 cell_colspan(Cell, 1),
730 !,
731 format_cell_to_string(Cell, 1_000, State, String),
732 write_size(String, Width0, _Height, []),
733 Width is Width0 + State.margin_right.
734auto_cell_width(_, _, 0).
735
739
740format_row(ColWidths, State, MarginLeft, Row) :-
741 hrule(Row, ColWidths, MarginLeft),
742 format_cells(ColWidths, CWSpanned, 1, Row, State, Cells),
743 format_row_lines(1, CWSpanned, Cells, MarginLeft).
744
745hrule(row(_, Attrs), ColWidths, MarginLeft) :-
746 attrs_classes(Attrs, Classes),
747 memberchk(hline, Classes),
748 !,
749 sum_list(ColWidths, RuleLen),
750 format('~N~t~*|~`-t~*+', [MarginLeft, RuleLen]).
751hrule(_, _, _).
752
753format_row_lines(LineNo, Widths, Cells, MarginLeft) :-
754 nth_row_line(Widths, 1, LineNo, Cells, CellLines, Found),
755 ( Found == true
756 -> emit_nl,
757 emit_indent(MarginLeft),
758 maplist(emit_cell_line, CellLines),
759 ask_nl(1),
760 LineNo1 is LineNo + 1,
761 format_row_lines(LineNo1, Widths, Cells, MarginLeft)
762 ; true
763 ).
764
765emit_cell_line(Line-Pad) :-
766 write(Line),
767 forall(between(1,Pad,_), put_char(' ')).
768
769nth_row_line([], _, _, _, [], _).
770nth_row_line([ColW|CWT], CellNo, LineNo, Cells, [CellLine-Pad|ColLines],
771 Found) :-
772 nth1(CellNo, Cells, CellLines),
773 ( nth1(LineNo, CellLines, CellLine)
774 -> Found = true,
775 Pad = 0
776 ; CellLine = '', Pad = ColW
777 ),
778 CellNo1 is CellNo + 1,
779 nth_row_line(CWT, CellNo1, LineNo, Cells, ColLines, Found).
780
781
787
788format_cells([], [], _, _, _, []) :- !.
789format_cells(CWidths, [HW|TW], Column, Row, State, [HC|TC]) :-
790 Row = row(Columns, _Attrs),
791 nth1(Column, Columns, Cell),
792 cell_colspan(Cell, CWidths, HW, TW0),
793 cell_align(Cell, Align),
794 format_cell_to_string(Cell, HW, State.put(_{pad:' ', text_align:Align}), String),
795 split_string(String, "\n", "", HC),
796 Column1 is Column+1,
797 format_cells(TW0, TW, Column1, Row, State, TC).
798
799cell_colspan(Cell, CWidths, HW, TW) :-
800 cell_colspan(Cell, Span),
801 length(SpanW, Span),
802 append(SpanW, TW, CWidths),
803 sum_list(SpanW, HW).
804
805cell_colspan(element(_,Attrs,_), Span) :-
806 ( memberchk(colspan=SpanA, Attrs),
807 atom_number(SpanA, SpanN)
808 -> Span = SpanN
809 ; Span = 1
810 ).
811
817
818cell_align(element(_,Attrs,_), Align) :-
819 ( memberchk(align=AlignA, Attrs)
820 -> Align = AlignA
821 ; memberchk(style=Style, Attrs),
822 style_css_attrs(Style, Props),
823 memberchk('text-align'(AlignA), Props)
824 -> Align = AlignA
825 ; Align = left
826 ).
827
828
832
833format_cell_to_string(element(_,_,[]), ColWidth, State, String) :-
834 Pad = State.get(pad),
835 !,
836 length(Chars, ColWidth),
837 maplist(=(Pad), Chars),
838 atomics_to_string(Chars, String).
839format_cell_to_string(Cell, ColWidth, State, String) :-
840 setup_call_cleanup(
841 init_nl(NlState),
842 with_output_to(
843 string(String),
844 format_cell(Cell, ColWidth, State)),
845 exit_nl(NlState)).
846
847format_cell(element(E, _Attrs, Content), ColWidth, State) :-
848 set_stream(current_output, tty(State.tty)),
849 cell_element(E, Style),
850 update_style(Style, State.put(width, ColWidth), CellState),
851 block_words(Content, Blocks, Words, CellState),
852 emit_block(Words, [], CellState),
853 ( Blocks \== []
854 -> format_dom(Blocks, CellState)
855 ; true
856 ).
857
858cell_element(td, [normal]).
859cell_element(th, [bold]).
860
861
865
866emit_hr(_Attrs, BlockAttrs, State) :-
867 option(margin_left(ML), BlockAttrs, 0),
868 option(margin_right(MR), BlockAttrs, 0),
869 RuleWidth is State.width - ML - MR,
870 Style = State.style,
871 emit_indent(ML),
872 ( Style == []
873 -> format('~|~*t~*+', [0'-, RuleWidth])
874 ; ansi_format(Style, '~|~*t~*+', [0'-, RuleWidth])
875 )