1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
|
# -*- tcl -*-
#
# Copyright (c) 2019 Andreas Kupries <andreas_kupries@sourceforge.net>
# Freely redistributable.
#
# _text_dlist.tcl -- Display list variables and accessors
#
# The engine maintains several data structures per document and pass.
# Most important is an internal representation of the text better
# suited to perform the final layouting, the display list. Elements of
# the display list are lists containing 2 elements, an operation, and
# its arguments, in this order. The arguments are a list again, its
# contents are specific to the operation.
#
# The operations are:
#
# - SECT Section. Title.
# - SUBSECT Subsection. Title.
# - PARA Paragraph. Context reference and text.
#
# The PARA operation is the workhorse of the engine, dooing all the
# formatting, using the information in an "context" as the guide
# for doing so. The contexts themselves are generated during the
# second pass through the contents. They contain the information about
# nesting (i.e. indentation), bulleting and the like.
#
# # ## ### ##### ########
## State: Display list
global __dlist
# # ## ### ##### ########
## Internal: Extend
proc Store {op args} { global __dlist ; lappend __dlist [list $op $args] ; return}
# Debugging ...
#proc Store {op args} {puts_stderr "STO $op $args"; global __dlist; lappend __dlist [list $op $args]; return}
# # ## ### ##### ########
## API
#
# API Section Add section
# API Subsection Add subsection
# API CloseParagraph Add paragraph using text and (current) env
# Boolean result indicates if something was added, or not
proc DListClear {} { global __dlist ; unset -nocomplain __dlist ; set __dlist {} }
proc Section {name} {Store SECT $name ; return}
proc Subsection {name} {Store SUBSECT $name ; return}
proc CloseParagraph {{id {}}} {
set para [Text?]
if {$para == {}} { return 0 }
if {$id == {}} { set id [CAttrCurrent] }
if {![ContextExists $id]} {
error "Unknown context $id for paragraph"
}
Store PARA $id $para
#puts_stderr "CloseParagraph $id [CAttrName $id]"
#puts_stderr " (($para))"
TextClear
return 1
}
proc PostProcess {text} {
#puts_stderr XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#puts_stderr <<$text>>
#puts_stderr XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
global __dlist
# The argument is not relevant. Access the display list, perform
# the final layouting and return its result.
set lines {}
array set state {lmargin 0 rmargin 0}
foreach cmd $__dlist {
lappend lines ""
foreach {op arguments} $cmd break
$op $arguments
}
#puts_stderr XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
return [Compose lines]\n
}
# # ## ### ##### ########
## PARA attributes
#
# Attributes
# - bullet = bullet (template) to use for (un)ordered lists.
# - counter = if present, item counter for enumeration list.
# - listtype = type of list, if any.
# - lmargin = left-indent, location of left margin for text.
# - prefix = prefix to use for all lines of the parapgraph.
# - wspfx = whitespace prefix for all but the first line of the paragraph.
proc BulletReset {} { CAttrSet bullet {} }
proc ListNone {} { CAttrSet listtype {} }
proc MarginIn {} { CAttrIncr lmargin [LMI] }
proc MarginReset {} { CAttrSet lmargin 0 }
proc PrefixReset {} { CAttrSet prefix {} }
proc WPrefixReset {} { CAttrSet wspfx {} }
proc Prefix! {p} { CAttrSet prefix $p }
proc Prefix+ {p} { CAttrAppend prefix $p }
proc WPrefix! {p} { CAttrSet wspfx $p }
proc Bullet? {} { CAttrGet bullet }
proc ListType? {} { CAttrGet listtype }
proc Margin? {} { CAttrGet lmargin }
proc Prefix? {} { CAttrGet prefix }
proc WPrefix? {} { CAttrGet wspfx }
proc List! {type bullet wprefix} {
#puts_stderr L!(($type))
#puts_stderr L!(($bullet))[string length $bullet],[string length [DeIce $bullet]]
#puts_stderr L!(([Dots $wprefix]))
CAttrSet listtype $type
CAttrSet bullet $bullet
CAttrSet wspfx $wprefix
}
proc EnumCounter {} {
if {![CAttrHas counter]} {
CAttrSet counter 1
} else {
CAttrIncr counter
}
ContextCommit
#puts_stderr "Counter ... [CAttrName] => [CAttrGet counter]"
return [CAttrGet counter]
}
proc EnumId {} {
# Handling the enumeration counter.
#
# Special case: An example as first paragraph in an item has to
# use the counter in the context it is derived from to prevent
# miscounting.
#puts_stderr "EnumId: [CAttrName] | [CAttrName [Parent?]]"
if {[Example?]} {
ContextPush
ContextSet [Parent?]
set n [EnumCounter]
ContextPop
} else {
set n [EnumCounter]
}
return $n
}
# # ## ### ##### ########
## Hooks
proc SECT {text} {
#puts_stderr "SECT $text"
#puts_stderr ""
# text is actually the list of arguments, having one element, the text.
upvar 1 lines lines
set text [lindex $text 0]
SectTitle lines $text
return
}
proc SUBSECT {text} {
#puts_stderr "SUBSECT $text"
#puts_stderr ""
# text is actually the list of arguments, having one element, the text.
upvar 1 lines lines
set text [lindex $text 0]
SubsectTitle lines $text
return
}
proc PARA {arguments} {
upvar lines lines
# Note. As the display list is processed at the very end we can
# reuse the current context and accessors to hold and query the
# context of each paragraph.
foreach {env text} $arguments break
ContextSet $env
#puts_stderr "PARA $env [CAttrName $env]"
#parray_stderr ::currentContext ;# consider capsulation
#puts_stderr " (($text))"
#puts_stderr ""
# Use the information in the referenced context to format the
# paragraph.
set lm [Margin?]
set lt [ListType?]
set blank [WPrefix?]
if {[Verbatim?]} {
set text [Undent $text]
#puts_stderr "UN (($text))"
} else {
set plm $lm
incr plm [string length $blank]
set text [Reflow $text [RMargin $plm]]
}
# Now apply prefixes, (ws prefixes bulleting), at last indentation.
set p [Prefix?]
if {[string length $p]} {
set text [Indent $text $p]
#puts_stderr "IN (($text))"
}
if {$lt != {}} {
switch -exact $lt {
bullet {
# Indent for bullet, but not the first line. This is
# prefixed by the bullet itself.
set thebullet [Bullet?]
}
enum {
#puts_stderr EB
set n [EnumId]
set thebullet [string map [list % $n] [Bullet?]]
#puts_stderr "E $n | $thebullet |"
}
}
set blank [WPrefix?]
#puts_stderr B.(($lt))
#puts_stderr B.(($thebullet))[string length $thebullet],[string length [DeIce $thebullet]]
#puts_stderr B.(([Dots $blank]))
if {[string length [DeIce $thebullet]] >= [string length $blank]} {
# The item's bullet is longer than the space for indenting.
# Put bullet and text on separate lines, indent text in full.
#puts_stderr B.DROP
set text "$thebullet\n[Indent $text $blank]"
} else {
# The item's bullet fits into the space for
# indenting. Make hanging indent of text and place the
# bullet in front of the first line, with suitable partial
# spacing.
#puts_stderr B.SAME
#puts_stderr B.(([Dots [ReHead $blank $thebullet]]))
set text [Indent1 $text [ReHead $blank $thebullet] $blank]
}
}
if {$lm} {
#puts_stderr "LMA $lm"
set text [Indent $text [Blank $lm]]
}
#puts_stderr "FIN (($text))"
lappend lines $text
return
}
# # ## ### ##### ########
return
|