Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
2531d40b
Commit
2531d40b
authored
Nov 01, 2013
by
Aric Stewart
Committed by
Alexandre Julliard
Nov 04, 2013
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
usp10: Rewrite resolveExplicit for Unicode 6.3.
parent
e50dc73b
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
253 additions
and
83 deletions
+253
-83
bidi.c
dlls/usp10/bidi.c
+253
-83
No files found.
dlls/usp10/bidi.c
View file @
2531d40b
...
...
@@ -57,7 +57,7 @@
WINE_DEFAULT_DEBUG_CHANNEL
(
bidi
);
#define ASSERT(x) do { if (!(x)) FIXME("assert failed: %s\n", #x); } while(0)
#define MAX_
LEVEL 61
#define MAX_
DEPTH 125
/* HELPER FUNCTIONS AND DECLARATIONS */
...
...
@@ -75,7 +75,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(bidi);
enum
directions
{
/* input types */
/* ON MUST be zero, code relies on ON = N = 0 */
/* ON MUST be zero, code relies on ON = N
I
= 0 */
ON
=
0
,
/* Other Neutral */
L
,
/* Left Letter */
R
,
/* Right Letter */
...
...
@@ -102,12 +102,53 @@ enum directions
LRE
,
PDF
,
LRI
,
/* Isolate formatting characters new with 6.3 */
RLI
,
FSI
,
PDI
,
/* resolved types, also resolved directions */
N
=
ON
,
/* alias, where ON, WS and S are treated the same */
NI
=
ON
,
/* alias, where ON, WS, S and Isolates are treated the same */
};
static
const
char
debug_type
[][
4
]
=
{
"ON"
,
/* Other Neutral */
"L"
,
/* Left Letter */
"R"
,
/* Right Letter */
"AN"
,
/* Arabic Number */
"EN"
,
/* European Number */
"AL"
,
/* Arabic Letter (Right-to-left) */
"NSM"
,
/* Non-spacing Mark */
"CS"
,
/* Common Separator */
"ES"
,
/* European Separator */
"ET"
,
/* European Terminator (post/prefix e.g. $ and %) */
"BN"
,
/* Boundary neutral (type of RLE etc after explicit levels) */
"S"
,
/* Segment Separator (TAB) // used only in L1 */
"WS"
,
/* White space // used only in L1 */
"B"
,
/* Paragraph Separator (aka as PS) */
"RLO"
,
/* these are used only in X1-X9 */
"RLE"
,
"LRO"
,
"LRE"
,
"PDF"
,
"LRI"
,
/* Isolate formatting characters new with 6.3 */
"RLI"
,
"FSI"
,
"PDI"
,
};
/* HELPER FUNCTIONS */
static
inline
void
dump_types
(
const
char
*
header
,
WORD
*
types
,
int
start
,
int
end
)
{
int
i
;
TRACE
(
"%s:"
,
header
);
for
(
i
=
start
;
i
<
end
;
i
++
)
TRACE
(
" %s"
,
debug_type
[
types
[
i
]]);
TRACE
(
"
\n
"
);
}
/* Convert the libwine information to the direction enum */
static
void
classify
(
LPCWSTR
lpString
,
WORD
*
chartype
,
DWORD
uCount
,
const
SCRIPT_CONTROL
*
c
)
{
...
...
@@ -143,7 +184,7 @@ static void classify(LPCWSTR lpString, WORD *chartype, DWORD uCount, const SCRIP
switch
(
lpString
[
i
])
{
case
'-'
:
case
'+'
:
chartype
[
i
]
=
N
;
break
;
case
'+'
:
chartype
[
i
]
=
N
I
;
break
;
case
'/'
:
chartype
[
i
]
=
CS
;
break
;
}
break
;
...
...
@@ -155,6 +196,10 @@ static void classify(LPCWSTR lpString, WORD *chartype, DWORD uCount, const SCRIP
case
0x202C
:
chartype
[
i
]
=
PDF
;
break
;
case
0x202D
:
chartype
[
i
]
=
LRO
;
break
;
case
0x202E
:
chartype
[
i
]
=
RLO
;
break
;
case
0x2066
:
chartype
[
i
]
=
LRI
;
break
;
case
0x2067
:
chartype
[
i
]
=
RLI
;
break
;
case
0x2068
:
chartype
[
i
]
=
FSI
;
break
;
case
0x2069
:
chartype
[
i
]
=
PDI
;
break
;
}
break
;
}
...
...
@@ -208,81 +253,204 @@ static WORD EmbeddingDirection(int level)
the outermost call. The nesting counter counts the recursion
depth and not the embedding level.
------------------------------------------------------------------------*/
typedef
struct
tagStackItem
{
int
level
;
int
override
;
BOOL
isolate
;
}
StackItem
;
#define push_stack(l,o,i) \
do { stack_top--; \
stack[stack_top].level = l; \
stack[stack_top].override = o; \
stack[stack_top].isolate = i;} while(0)
static
int
resolveExplicit
(
int
level
,
int
dir
,
WORD
*
pcls
,
WORD
*
plevel
,
int
cch
,
int
nNest
)
#define pop_stack() do { stack_top++; } while(0)
#define valid_level(x) (x <= MAX_DEPTH && overflow_isolate_count == 0 && overflow_embedding_count == 0)
static
void
resolveExplicit
(
int
level
,
WORD
*
pclass
,
WORD
*
poutLevel
,
int
count
)
{
/* always called with a valid nesting level
nesting levels are != embedding levels */
int
nLastValid
=
nNest
;
int
ich
=
0
;
/* X1 */
int
overflow_isolate_count
=
0
;
int
overflow_embedding_count
=
0
;
int
valid_isolate_count
=
0
;
int
i
;
/* check input values */
ASSERT
(
nNest
>=
0
&&
level
>=
0
&&
level
<=
MAX_LEVEL
)
;
StackItem
stack
[
MAX_DEPTH
+
2
];
int
stack_top
=
MAX_DEPTH
+
1
;
/* process the text */
for
(;
ich
<
cch
;
ich
++
)
stack
[
stack_top
].
level
=
level
;
stack
[
stack_top
].
override
=
NI
;
stack
[
stack_top
].
isolate
=
FALSE
;
for
(
i
=
0
;
i
<
count
;
i
++
)
{
WORD
cls
=
pcls
[
ich
];
switch
(
cls
)
/* X2 */
if
(
pclass
[
i
]
==
RLE
)
{
case
LRO
:
case
LRE
:
nNest
++
;
if
(
GreaterEven
(
level
)
<=
MAX_LEVEL
-
(
cls
==
LRO
?
2
:
0
))
{
plevel
[
ich
]
=
GreaterEven
(
level
);
pcls
[
ich
]
=
BN
;
ich
+=
resolveExplicit
(
plevel
[
ich
],
(
cls
==
LRE
?
N
:
L
),
&
pcls
[
ich
+
1
],
&
plevel
[
ich
+
1
],
cch
-
(
ich
+
1
),
nNest
);
nNest
--
;
int
least_odd
=
GreaterOdd
(
stack
[
stack_top
].
level
);
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
valid_level
(
least_odd
))
push_stack
(
least_odd
,
NI
,
FALSE
);
else
if
(
overflow_isolate_count
==
0
)
overflow_embedding_count
++
;
}
/* X3 */
else
if
(
pclass
[
i
]
==
LRE
)
{
int
least_even
=
GreaterEven
(
stack
[
stack_top
].
level
);
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
valid_level
(
least_even
))
push_stack
(
least_even
,
NI
,
FALSE
);
else
if
(
overflow_isolate_count
==
0
)
overflow_embedding_count
++
;
}
/* X4 */
else
if
(
pclass
[
i
]
==
RLO
)
{
int
least_odd
=
GreaterOdd
(
stack
[
stack_top
].
level
);
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
valid_level
(
least_odd
))
push_stack
(
least_odd
,
R
,
FALSE
);
else
if
(
overflow_isolate_count
==
0
)
overflow_embedding_count
++
;
}
/* X5 */
else
if
(
pclass
[
i
]
==
LRO
)
{
int
least_even
=
GreaterEven
(
stack
[
stack_top
].
level
);
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
valid_level
(
least_even
))
push_stack
(
least_even
,
L
,
FALSE
);
else
if
(
overflow_isolate_count
==
0
)
overflow_embedding_count
++
;
}
/* X5a */
else
if
(
pclass
[
i
]
==
RLI
)
{
int
least_odd
=
GreaterOdd
(
stack
[
stack_top
].
level
);
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
valid_level
(
least_odd
))
{
valid_isolate_count
++
;
push_stack
(
least_odd
,
NI
,
TRUE
);
}
else
overflow_isolate_count
++
;
pclass
[
i
]
=
NI
;
}
/* X5b */
else
if
(
pclass
[
i
]
==
LRI
)
{
int
least_even
=
GreaterEven
(
stack
[
stack_top
].
level
);
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
valid_level
(
least_even
))
{
valid_isolate_count
++
;
push_stack
(
least_even
,
NI
,
TRUE
);
}
else
overflow_isolate_count
++
;
pclass
[
i
]
=
NI
;
}
/* X5c */
else
if
(
pclass
[
i
]
==
FSI
)
{
int
j
;
int
new_level
=
0
;
int
skipping
=
0
;
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
for
(
j
=
i
+
1
;
j
<
count
;
j
++
)
{
if
(
pclass
[
j
]
==
LRI
||
pclass
[
j
]
==
RLI
||
pclass
[
j
]
==
FSI
)
{
skipping
++
;
continue
;
}
cls
=
pcls
[
ich
]
=
BN
;
else
if
(
pclass
[
j
]
==
PDI
)
{
if
(
skipping
)
skipping
--
;
else
break
;
case
RLO
:
case
RLE
:
nNest
++
;
if
(
GreaterOdd
(
level
)
<=
MAX_LEVEL
-
(
cls
==
RLO
?
2
:
0
))
{
plevel
[
ich
]
=
GreaterOdd
(
level
);
pcls
[
ich
]
=
BN
;
ich
+=
resolveExplicit
(
plevel
[
ich
],
(
cls
==
RLE
?
N
:
R
),
&
pcls
[
ich
+
1
],
&
plevel
[
ich
+
1
],
cch
-
(
ich
+
1
),
nNest
);
nNest
--
;
continue
;
}
cls
=
pcls
[
ich
]
=
BN
;
break
;
case
PDF
:
cls
=
pcls
[
ich
]
=
BN
;
if
(
nNest
)
if
(
skipping
)
continue
;
if
(
pclass
[
j
]
==
L
)
{
if
(
nLastValid
<
nNest
)
new_level
=
0
;
break
;
}
else
if
(
pclass
[
j
]
==
R
||
pclass
[
j
]
==
AL
)
{
nNest
--
;
new_level
=
1
;
break
;
}
}
if
(
odd
(
new_level
))
{
int
least_odd
=
GreaterOdd
(
stack
[
stack_top
].
level
);
if
(
valid_level
(
least_odd
))
{
valid_isolate_count
++
;
push_stack
(
least_odd
,
NI
,
TRUE
);
}
else
overflow_isolate_count
++
;
}
else
{
cch
=
ich
;
/* break the loop, but complete body */
int
least_even
=
GreaterEven
(
stack
[
stack_top
].
level
);
if
(
valid_level
(
least_even
))
{
valid_isolate_count
++
;
push_stack
(
least_even
,
NI
,
TRUE
);
}
else
overflow_isolate_count
++
;
}
pclass
[
i
]
=
NI
;
}
/* Apply the override */
if
(
dir
!=
N
)
/* X6 */
else
if
(
pclass
[
i
]
!=
B
&&
pclass
[
i
]
!=
BN
&&
pclass
[
i
]
!=
PDI
&&
pclass
[
i
]
!=
PDF
)
{
cls
=
dir
;
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
stack
[
stack_top
].
override
!=
NI
)
pclass
[
i
]
=
stack
[
stack_top
].
override
;
}
plevel
[
ich
]
=
level
;
if
(
pcls
[
ich
]
!=
BN
)
pcls
[
ich
]
=
cls
;
/* X6a */
else
if
(
pclass
[
i
]
==
PDI
)
{
if
(
overflow_isolate_count
)
overflow_isolate_count
--
;
else
if
(
!
valid_isolate_count
)
{
/* do nothing */
}
else
{
overflow_embedding_count
=
0
;
while
(
!
stack
[
stack_top
].
isolate
)
pop_stack
();
pop_stack
();
valid_isolate_count
--
;
}
return
ich
;
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
pclass
[
i
]
=
NI
;
}
/* X7 */
else
if
(
pclass
[
i
]
==
PDF
)
{
poutLevel
[
i
]
=
stack
[
stack_top
].
level
;
if
(
overflow_isolate_count
)
{
/* do nothing */
}
else
if
(
overflow_embedding_count
)
overflow_embedding_count
--
;
else
if
(
!
stack
[
stack_top
].
isolate
&&
stack_top
<
(
MAX_DEPTH
+
1
))
pop_stack
();
}
/* X8: Nothing */
}
/* X9: Based on 5.2 Retaining Explicit Formatting Characters */
for
(
i
=
0
;
i
<
count
;
i
++
)
if
(
pclass
[
i
]
==
RLE
||
pclass
[
i
]
==
LRE
||
pclass
[
i
]
==
RLO
||
pclass
[
i
]
==
LRO
||
pclass
[
i
]
==
PDF
)
pclass
[
i
]
=
BN
;
}
/* RESOLVE WEAK TYPES */
...
...
@@ -318,7 +486,7 @@ enum states /* possible states */
static
const
int
stateWeak
[][
10
]
=
{
/*
N
, L, R, AN, EN, AL,NSM, CS, ES, ET */
/*
NI
, L, R, AN, EN, AL,NSM, CS, ES, ET */
/*xa*/
{
ao
,
xl
,
xr
,
cn
,
cn
,
xa
,
xa
,
ao
,
ao
,
ao
},
/* Arabic letter */
/*xr*/
{
ro
,
xl
,
xr
,
ra
,
re
,
xa
,
xr
,
ro
,
ro
,
rt
},
/* right letter */
/*xl*/
{
lo
,
xl
,
xr
,
la
,
le
,
xa
,
xl
,
lo
,
lo
,
lt
},
/* left letter */
...
...
@@ -355,7 +523,7 @@ enum actions /* possible actions */
/* actions */
xxx
=
(
XX
<<
4
)
+
XX
,
/* no-op */
xIx
=
IX
+
xxx
,
/* increment run */
xxN
=
(
XX
<<
4
)
+
ON
,
/* set current to N */
xxN
=
(
XX
<<
4
)
+
ON
,
/* set current to N
I
*/
xxE
=
(
XX
<<
4
)
+
EN
,
/* set current to EN */
xxA
=
(
XX
<<
4
)
+
AN
,
/* set current to AN */
xxR
=
(
XX
<<
4
)
+
R
,
/* set current to R */
...
...
@@ -363,19 +531,19 @@ enum actions /* possible actions */
Nxx
=
(
ON
<<
4
)
+
0xF
,
/* set run to neutral */
Axx
=
(
AN
<<
4
)
+
0xF
,
/* set run to AN */
ExE
=
(
EN
<<
4
)
+
EN
,
/* set run to EN, set current to EN */
NIx
=
(
ON
<<
4
)
+
0xF
+
IX
,
/* set run to N, increment */
NxN
=
(
ON
<<
4
)
+
ON
,
/* set run to N
, set current to N
*/
NxR
=
(
ON
<<
4
)
+
R
,
/* set run to N, set current to R */
NxE
=
(
ON
<<
4
)
+
EN
,
/* set run to N, set current to EN */
NIx
=
(
ON
<<
4
)
+
0xF
+
IX
,
/* set run to N
I
, increment */
NxN
=
(
ON
<<
4
)
+
ON
,
/* set run to N
I, set current to NI
*/
NxR
=
(
ON
<<
4
)
+
R
,
/* set run to N
I
, set current to R */
NxE
=
(
ON
<<
4
)
+
EN
,
/* set run to N
I
, set current to EN */
AxA
=
(
AN
<<
4
)
+
AN
,
/* set run to AN, set current to AN */
NxL
=
(
ON
<<
4
)
+
L
,
/* set run to N, set current to L */
NxL
=
(
ON
<<
4
)
+
L
,
/* set run to N
I
, set current to L */
LxL
=
(
L
<<
4
)
+
L
,
/* set run to L, set current to L */
}
;
static
const
int
actionWeak
[][
10
]
=
{
/*
N
, L, R, AN, EN, AL, NSM, CS, ES, ET */
/*
NI
, L, R, AN, EN, AL, NSM, CS, ES, ET */
/*xa*/
{
xxx
,
xxx
,
xxx
,
xxx
,
xxA
,
xxR
,
xxR
,
xxN
,
xxN
,
xxN
},
/* Arabic letter */
/*xr*/
{
xxx
,
xxx
,
xxx
,
xxx
,
xxE
,
xxR
,
xxR
,
xxN
,
xxN
,
xIx
},
/* right letter */
/*xl*/
{
xxx
,
xxx
,
xxx
,
xxx
,
xxL
,
xxR
,
xxL
,
xxN
,
xxN
,
xIx
},
/* left letter */
...
...
@@ -425,7 +593,7 @@ static int GetResolvedType(int action)
Input classes are of three kinds
- Static Input Token, where the class of the token remains
unchanged on output (AN, L, N, R)
unchanged on output (AN, L, N
I
, R)
- Replaced Input Token, where the class of the token is
always replaced on output (AL, BN, NSM, CS, ES, ET)
- Conditional Input Token, where the class of the token is
...
...
@@ -574,10 +742,10 @@ enum resolvestates
/* new temporary class */
r
,
/* R and characters resolved to R */
l
,
/* L and characters resolved to L */
rn
,
/* N preceded by right */
ln
,
/* N preceded by left */
rn
,
/* N
I
preceded by right */
ln
,
/* N
I
preceded by left */
a
,
/* AN preceded by left (the abbreviation 'la' is used up above) */
na
,
/* N preceded by a */
na
,
/* N
I
preceded by a */
}
;
...
...
@@ -593,28 +761,28 @@ enum resolvestates
static
const
int
actionNeutrals
[][
5
]
=
{
/*
N
, L, R, AN, EN = cls */
/*
NI
, L, R, AN, EN = cls */
{
In
,
0
,
0
,
0
,
0
},
/* r right */
{
In
,
0
,
0
,
0
,
L
},
/* l left */
{
In
,
En
,
Rn
,
Rn
,
Rn
},
/* rn N preceded by right */
{
In
,
Ln
,
En
,
En
,
LnL
},
/* ln N preceded by left */
{
In
,
En
,
Rn
,
Rn
,
Rn
},
/* rn N
I
preceded by right */
{
In
,
Ln
,
En
,
En
,
LnL
},
/* ln N
I
preceded by left */
{
In
,
0
,
0
,
0
,
L
},
/* a AN preceded by left */
{
In
,
En
,
Rn
,
Rn
,
En
},
/* na N preceded by a */
{
In
,
En
,
Rn
,
Rn
,
En
},
/* na N
I
preceded by a */
}
;
static
const
int
stateNeutrals
[][
5
]
=
{
/*
N
, L, R, AN, EN */
/*
NI
, L, R, AN, EN */
{
rn
,
l
,
r
,
r
,
r
},
/* r right */
{
ln
,
l
,
r
,
a
,
l
},
/* l left */
{
rn
,
l
,
r
,
r
,
r
},
/* rn N preceded by right */
{
ln
,
l
,
r
,
a
,
l
},
/* ln N preceded by left */
{
rn
,
l
,
r
,
r
,
r
},
/* rn N
I
preceded by right */
{
ln
,
l
,
r
,
a
,
l
},
/* ln N
I
preceded by left */
{
na
,
l
,
r
,
a
,
l
},
/* a AN preceded by left */
{
na
,
l
,
r
,
a
,
l
},
/* na N preceded by la */
{
na
,
l
,
r
,
a
,
l
},
/* na N
I
preceded by la */
}
;
/*------------------------------------------------------------------------
...
...
@@ -631,7 +799,7 @@ static const int stateNeutrals[][5] =
In/Out: Array of directional classes
Note: On input only these directional classes are expected
R, L, N, AN, EN and BN
R, L, N
I
, AN, EN and BN
W8 resolves a number of ENs to L
------------------------------------------------------------------------*/
...
...
@@ -659,14 +827,14 @@ static void resolveNeutrals(int baselevel, WORD *pcls, const WORD *plevel, int c
continue
;
}
ASSERT
(
pcls
[
ich
]
<
5
);
/* "Only N, L, R, AN, EN are allowed" */
ASSERT
(
pcls
[
ich
]
<
5
);
/* "Only N
I
, L, R, AN, EN are allowed" */
cls
=
pcls
[
ich
];
action
=
actionNeutrals
[
state
][
cls
];
/* resolve the directionality for deferred runs */
clsRun
=
GetDeferredNeutrals
(
action
,
level
);
if
(
clsRun
!=
N
)
if
(
clsRun
!=
N
I
)
{
SetDeferredRun
(
pcls
,
cchRun
,
ich
,
clsRun
);
cchRun
=
0
;
...
...
@@ -674,7 +842,7 @@ static void resolveNeutrals(int baselevel, WORD *pcls, const WORD *plevel, int c
/* resolve the directionality class at the current location */
clsNew
=
GetResolvedNeutrals
(
action
);
if
(
clsNew
!=
N
)
if
(
clsNew
!=
N
I
)
pcls
[
ich
]
=
clsNew
;
if
(
In
&
action
)
...
...
@@ -689,7 +857,7 @@ static void resolveNeutrals(int baselevel, WORD *pcls, const WORD *plevel, int c
/* resolve the directionality for deferred runs */
clsRun
=
GetDeferredNeutrals
(
actionNeutrals
[
state
][
cls
],
level
);
if
(
clsRun
!=
N
)
if
(
clsRun
!=
N
I
)
SetDeferredRun
(
pcls
,
cchRun
,
ich
,
clsRun
);
}
...
...
@@ -763,6 +931,7 @@ BOOL BIDI_DetermineLevels(
baselevel
=
s
->
uBidiLevel
;
classify
(
lpString
,
chartype
,
uCount
,
c
);
if
(
TRACE_ON
(
bidi
))
dump_types
(
"Start "
,
chartype
,
0
,
uCount
);
for
(
j
=
0
;
j
<
uCount
;
++
j
)
switch
(
chartype
[
j
])
...
...
@@ -770,12 +939,13 @@ BOOL BIDI_DetermineLevels(
case
B
:
case
S
:
case
WS
:
case
ON
:
chartype
[
j
]
=
N
;
case
ON
:
chartype
[
j
]
=
N
I
;
default:
continue
;
}
/* resolve explicit */
resolveExplicit
(
baselevel
,
N
,
chartype
,
lpOutLevels
,
uCount
,
0
);
resolveExplicit
(
baselevel
,
chartype
,
lpOutLevels
,
uCount
);
if
(
TRACE_ON
(
bidi
))
dump_types
(
"After Explicit"
,
chartype
,
0
,
uCount
);
/* resolve weak */
resolveWeak
(
baselevel
,
chartype
,
lpOutLevels
,
uCount
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment