@@ -83,6 +83,20 @@ function Base.parse(::Type{SimpleColor}, rgb::String)
8383 color
8484end
8585
86+ struct _Face
87+ font:: String
88+ height:: UInt64
89+ weight:: Symbol
90+ slant:: Symbol
91+ foreground:: SimpleColor
92+ background:: SimpleColor
93+ underline:: SimpleColor
94+ underline_style:: Symbol
95+ strikethrough:: UInt8
96+ inverse:: UInt8
97+ inherit:: Memory{Symbol}
98+ end
99+
86100"""
87101A [`Face`](@ref) is a collection of graphical attributes for displaying text.
88102Faces control how text is displayed in the terminal, and possibly other
@@ -121,20 +135,53 @@ All attributes can be set via the keyword constructor, and default to `nothing`.
121135- `inherit` (a `Vector{Symbol}`): Names of faces to inherit from,
122136 with earlier faces taking priority. All faces inherit from the `:default` face.
123137"""
124- struct Face
125- font:: Union{Nothing, String}
126- height:: Union{Nothing, Int, Float64}
127- weight:: Union{Nothing, Symbol}
128- slant:: Union{Nothing, Symbol}
129- foreground:: Union{Nothing, SimpleColor}
130- background:: Union{Nothing, SimpleColor}
131- underline:: Union {Nothing, Bool, SimpleColor,
132- Tuple{<: Union{Nothing, SimpleColor} , Symbol}}
133- strikethrough:: Union{Nothing, Bool}
134- inverse:: Union{Nothing, Bool}
135- inherit:: Vector{Symbol}
138+ mutable struct Face # We want to force reference semantics here
139+ const f:: _Face
136140end
137141
142+ # NOTE: We use shenanigans instead of `Union{Nothing, X}` in the
143+ # face fields for two reasons:
144+ # 1. To improve the memory layout of `Face`, so there's less indirection
145+ # 2. To allow for having two flavours of `nothing`, "weak" and "strong".
146+ # This allows for us to distinguish between unset attributes (weak)
147+ # and attributes that should replace a set value with weak nothing (strong).
148+ # We can only do this because we have full knowledge and dominion
149+ # over the semantics of `Face`.
150+
151+ const WEAK_NOTHING_SYMB = gensym (" nothing" )
152+ const WEAK_NOTHING_STR = String (WEAK_NOTHING_SYMB)
153+
154+ weaknothing (:: Type{Symbol} ) = WEAK_NOTHING_SYMB
155+ weaknothing (:: Type{String} ) = WEAK_NOTHING_STR
156+ weaknothing (:: Type{N} ) where {N<: Number } = - one (N)
157+ weaknothing (:: Type{SimpleColor} ) = SimpleColor (WEAK_NOTHING_SYMB)
158+ weaknothing (:: Type{Bool} ) = strongnothing (UInt8)
159+ weaknothing (:: Type ) = nothing
160+ weaknothing (x) = weaknothing (typeof (x))
161+
162+ isweaknothing (x) = x === weaknothing (typeof (x))
163+ isweaknothing (s:: String ) = pointer (s) == pointer (WEAK_NOTHING_STR)
164+ isweaknothing (c:: SimpleColor ) = c. value === WEAK_NOTHING_SYMB
165+
166+ const STRONG_NOTHING_SYMB = gensym (" nothing" )
167+ const STRONG_NOTHING_STR = String (STRONG_NOTHING_SYMB)
168+
169+ strongnothing (:: Type{Symbol} ) = STRONG_NOTHING_SYMB
170+ strongnothing (:: Type{String} ) = STRONG_NOTHING_STR
171+ strongnothing (:: Type{N} ) where {N<: Number } = - (0x2 * one (N))
172+ strongnothing (:: Type{SimpleColor} ) = SimpleColor (STRONG_NOTHING_SYMB)
173+ strongnothing (:: Type{Bool} ) = strongnothing (UInt8)
174+ strongnothing (:: Type ) = nothing
175+ strongnothing (x) = strongnothing (typeof (x))
176+
177+ isstrongnothing (x) = x === strongnothing (typeof (x))
178+ isstrongnothing (s:: String ) = pointer (s) == pointer (STRONG_NOTHING_STR)
179+ isstrongnothing (c:: SimpleColor ) = c. value === STRONG_NOTHING_SYMB
180+
181+ isnothinglike (x) = isweaknothing (x) || isstrongnothing (x)
182+
183+ # With our flavours of nothing defined, we can now define the Face constructor.
184+
138185function Face (; font:: Union{Nothing, String} = nothing ,
139186 height:: Union{Nothing, Int, Float64} = nothing ,
140187 weight:: Union{Nothing, Symbol} = nothing ,
@@ -143,53 +190,90 @@ function Face(; font::Union{Nothing, String} = nothing,
143190 background = nothing , # nothing, or SimpleColor-able value
144191 underline:: Union {Nothing, Bool, SimpleColor,
145192 Symbol, RGBTuple, UInt32,
146- Tuple{<: Any , Symbol}
147- } = nothing ,
193+ Tuple{<: Any , Symbol}} = nothing ,
148194 strikethrough:: Union{Nothing, Bool} = nothing ,
149195 inverse:: Union{Nothing, Bool} = nothing ,
150196 inherit:: Union{Symbol, Vector{Symbol}} = Symbol[],
151197 _... ) # Simply ignore unrecognised keyword arguments.
152- ascolor (:: Nothing ) = nothing
198+ inheritlen = if inherit isa Vector length (inherit) else 1 end
199+ inheritlist = if inherit isa Vector
200+ inherit. ref. mem
201+ else
202+ mem = Memory {Symbol} (undef, 1 )
203+ mem[1 ] = inherit
204+ mem
205+ end
206+ ascolor (:: Nothing ) = SimpleColor (WEAK_NOTHING_SYMB)
153207 ascolor (c:: AbstractString ) = parse (SimpleColor, c)
154208 ascolor (c:: Any ) = convert (SimpleColor, c)
155- Face (font, height, weight, slant,
156- ascolor (foreground), ascolor (background),
157- if underline isa Tuple{Any, Symbol}
158- (ascolor (underline[1 ]), underline[2 ])
159- elseif underline in (:straight , :double , :curly , :dotted , :dashed )
160- (nothing , underline)
161- elseif underline isa Bool
162- underline
163- else
164- ascolor (underline)
165- end ,
166- strikethrough,
167- inverse,
168- if inherit isa Symbol
169- [inherit]
170- else inherit end )
209+ ul, ulstyle = if underline isa Tuple{Any, Symbol}
210+ ascolor (underline[1 ]), underline[2 ]
211+ elseif underline in (:straight , :double , :curly , :dotted , :dashed )
212+ weaknothing (SimpleColor), underline
213+ elseif underline isa Bool
214+ SimpleColor (ifelse (underline, :foreground , :background )), :straight
215+ else
216+ ascolor (underline), :straight
217+ end
218+ f = _Face (something (font, weaknothing (String)),
219+ if isnothing (height)
220+ weaknothing (UInt64)
221+ elseif height isa Float64
222+ reinterpret (UInt64, height) & ~ (typemax (UInt64) >> 1 )
223+ else
224+ UInt64 (height)
225+ end ,
226+ something (weight, weaknothing (Symbol)),
227+ something (slant, weaknothing (Symbol)),
228+ ascolor (foreground),
229+ ascolor (background),
230+ ul, ulstyle,
231+ something (strikethrough, weaknothing (Bool)),
232+ something (inverse, weaknothing (Bool)),
233+ inheritlist)
234+ Face (f)
235+ end
236+
237+ function Base. getproperty (face:: Face , attr:: Symbol )
238+ val = getfield (getfield (face, :f ), attr)
239+ if isnothinglike (val)
240+ elseif attr ∈ (:strikethrough , :inverse )
241+ if attr != 0x3
242+ attr == 0x1
243+ end
244+ elseif attr == :underline
245+ style = getfield (getfield (face, :f ), :underline_style )
246+ val, style
247+ else
248+ val
249+ end
171250end
172251
252+ Base. propertynames (:: Face ) = setdiff (fieldnames (_Face), (:underline_style ,))
253+
173254function Base.:(== )(a:: Face , b:: Face )
174- a. font == b. font &&
175- a. height === b. height &&
176- a. weight == b. weight &&
177- a. slant == b. slant &&
178- a. foreground == b. foreground &&
179- a. background == b. background &&
180- a. underline == b. underline &&
181- a. strikethrough == b. strikethrough &&
182- a. inverse == b. inverse &&
183- a. inherit == b. inherit
255+ af, bf = getfield (a, :f ), getfield (b, :f )
256+ af. font == bf. font &&
257+ af. height === bf. height &&
258+ af. weight == bf. weight &&
259+ af. slant == bf. slant &&
260+ af. foreground == bf. foreground &&
261+ af. background == bf. background &&
262+ af. underline == bf. underline &&
263+ af. strikethrough == bf. strikethrough &&
264+ af. inverse == bf. inverse &&
265+ af. inherit == bf. inherit
184266end
185267
186268Base. hash (f:: Face , h:: UInt ) =
187- mapfoldr (Base. Fix1 (getfield, f ), hash, fieldnames (Face), init= hash (Face, h))
269+ mapfoldr (Base. Fix1 (getfield, getfield (f, :f ) ), hash, fieldnames (Face), init= hash (Face, h))
188270
189- Base. copy (f:: Face ) =
190- Face (f. font, f. height, f. weight, f. slant,
191- f. foreground, f. background, f. underline,
192- f. strikethrough, f. inverse, copy (f. inherit))
271+ function Base. copy (f:: Face )
272+ ff = getfield (f, :f )
273+ Face (_Face (ff. font, ff. height, ff. weight, ff. slant,
274+ ff. foreground, ff. background, ff. underline,
275+ ff. strikethrough, ff. inverse, copy (ff. inherit)))
276+ end
193277
194278"""
195279 merge(initial::StyledStrings.Face, others::StyledStrings.Face...)
@@ -199,31 +283,57 @@ Merge the properties of the `initial` face and `others`, with later faces taking
199283This is used to combine the styles of multiple faces, and to resolve inheritance.
200284"""
201285function Base. merge (a:: Face , b:: Face )
202- if isempty (b. inherit)
203- # Extract the heights to help type inference a bit to be able
204- # to narrow the types in e.g. `aheight * bheight`
205- aheight = a. height
206- bheight = b. height
207- abheight = if isnothing (bheight) aheight
208- elseif isnothing (aheight) bheight
209- elseif bheight isa Int bheight
210- elseif aheight isa Int round (Int, aheight * bheight)
211- else aheight * bheight end
212- Face (if isnothing (b. font) a. font else b. font end ,
213- abheight,
214- if isnothing (b. weight) a. weight else b. weight end ,
215- if isnothing (b. slant) a. slant else b. slant end ,
216- if isnothing (b. foreground) a. foreground else b. foreground end ,
217- if isnothing (b. background) a. background else b. background end ,
218- if isnothing (b. underline) a. underline else b. underline end ,
219- if isnothing (b. strikethrough) a. strikethrough else b. strikethrough end ,
220- if isnothing (b. inverse) a. inverse else b. inverse end ,
221- a. inherit)
286+ af, bf = getfield (a, :f ), getfield (b, :f )
287+ function mergeattr (a₀, b₀, attr:: Symbol )
288+ a′ = getfield (a₀, attr)
289+ b′ = getfield (b₀, attr)
290+ if isweaknothing (b′)
291+ a′
292+ elseif isstrongnothing (b′)
293+ weaknothing (b′)
294+ else
295+ b′
296+ end
297+ end
298+ if isempty (bf. inherit)
299+ abheight = if isstrongnothing (bf. height)
300+ weaknothing (bf. height)
301+ elseif isweaknothing (bf. height)
302+ af. height
303+ elseif isnothinglike (af. height)
304+ bf. height
305+ elseif iszero (bf. height & ~ (typemax (UInt64) >> 1 )) # bf.height::Int
306+ bf. height
307+ elseif iszero (af. height & ~ (typemax (UInt64) >> 1 )) # af.height::Int
308+ aint = reinterpret (Int64, af. height) % UInt
309+ bfloat = reinterpret (Float64, bf. height & (typemax (UInt64) >> 1 ))
310+ aint * bfloat
311+ else # af.height::Float64, bf.height::Float64
312+ afloat = reinterpret (Float64, af. height & (typemax (UInt64) >> 1 ))
313+ bfloat = reinterpret (Float64, bf. height & (typemax (UInt64) >> 1 ))
314+ afloat * bfloat
315+ end
316+ Face (_Face (
317+ mergeattr (af, bf, :font ),
318+ abheight,
319+ mergeattr (af, bf, :weight ),
320+ mergeattr (af, bf, :slant ),
321+ mergeattr (af, bf, :foreground ),
322+ mergeattr (af, bf, :background ),
323+ mergeattr (af, bf, :underline ),
324+ if isweaknothing (bf. underline_style)
325+ af. underline_style
326+ else
327+ bf. underline_style
328+ end ,
329+ mergeattr (af, bf, :strikethrough ),
330+ mergeattr (af, bf, :inverse ),
331+ af. inherit))
222332 else
223- b_noinherit = Face (
224- b . font, b . height, b . weight, b . slant, b . foreground, b . background,
225- b . underline, b . strikethrough, b . inverse, Symbol[])
226- b_inheritance = map (fname -> get (Face, FACES. current[], fname), Iterators. reverse (b . inherit))
333+ b_noinherit = Face (_Face (
334+ bf . font, bf . height, bf . weight, bf . slant, bf . foreground, bf . background,
335+ bf . underline, bf . strikethrough, bf . inverse, Symbol[]) )
336+ b_inheritance = map (fname -> get (Face, FACES. current[], fname), Iterators. reverse (bf . inherit))
227337 b_resolved = merge (foldl (merge, b_inheritance), b_noinherit)
228338 merge (a, b_resolved)
229339 end
0 commit comments