Use cell based table rendering

* Add hline search in matrix construction:
** This avoids a second scan later for the cost of some
   memory overhead
* Parse | in array column specification
* Add CSS based table lines as done by TMML, cf.
https://github.com/w3c/mathml-core/issues/245

Bug: T377167
Change-Id: I6e29b47b1731638ea9b06de3006ce2834e4f0c68
This commit is contained in:
physikerwelt 2024-10-21 17:15:39 +02:00
parent 7acc46ef46
commit dab5364b31
No known key found for this signature in database
GPG key ID: FCC793EFFA5FB13C
6 changed files with 162 additions and 59 deletions

View file

@ -62,3 +62,20 @@ div.mwe-math-element {
overflow-x: auto;
max-width: 100%;
}
/* Polyfill for MathML matrix elements with menclose https://github.com/w3c/mathml-core/issues/245 */
mtd.mwe-math-matrix-top {
border-top: 0.06em solid;
}
mtd.mwe-math-matrix-bottom {
border-bottom: 0.06em solid;
}
mtd.mwe-math-matrix-left {
border-left: 0.06em solid;
}
mtd.mwe-math-matrix-right {
border-right: 0.06em solid;
}

View file

@ -515,34 +515,18 @@ class BaseParsing {
$vspacing = null, $style = null, $cases = null, $numbered = null ) {
$resInner = '';
$mtr = new MMLmtr();
$mtd = new MMLmtd();
$tableArgs = [ "columnspacing" => "1em", "rowspacing" => "4pt", 'rowlines' => '' ];
$columnInfo = trim( $node->getColumnSpecs()->render(), "{} \n\r\t\v\x00" );
$tableArgs = [ "columnspacing" => "1em", "rowspacing" => "4pt" ];
$boarder = $node->getBoarder();
if ( $align ) {
$tableArgs['columnalign'] = $align;
} elseif ( $columnInfo ) {
$align = '';
foreach ( str_split( $columnInfo ) as $chr ) {
switch ( $chr ) {
case 'r':
$align .= 'right ';
break;
case 'l':
$align .= 'left ';
break;
case 'c':
$align .= 'center ';
break;
}
}
$tableArgs['columnalign'] = $align;
} elseif ( $node->hasColumnInfo() ) {
$tableArgs['columnalign'] = $node->getAlignInfo();
}
$mencloseArgs = [ 'notation' => '' ];
$lineNumber = 0;
$rowNo = 0;
$lines = $node->getLines();
foreach ( $node as $row ) {
$resInner .= $mtr->getStart();
$solid = false;
$colNo = 0;
foreach ( $row as $cell ) {
$usedArg = clone $cell;
if ( $usedArg instanceof TexArray &&
@ -551,42 +535,34 @@ class BaseParsing {
$usedArg[0]->getArg() === '\\hline '
) {
$usedArg->pop();
if ( $lineNumber === 0 ) {
$mencloseArgs['notation'] .= 'top ';
} elseif ( $lineNumber === $node->getLength() - 1 &&
if ( $rowNo === $node->getLength() - 1 &&
$usedArg->getLength() === 0
) {
$mencloseArgs['notation'] .= 'bottom ';
// remove the started row
$resInner = substr( $resInner, 0, -1 * strlen( $mtr->getStart() ) );
continue 2;
}
$solid = true;
}
$mtdAttributes = [];
$texclass = $lines[$rowNo] ? TexClass::TOP : '';
$texclass .= $lines[$rowNo + 1] ?? false ? ' ' . TexClass::BOTTOM : '';
$texclass .= $boarder[$colNo] ?? false ? ' ' . TexClass::LEFT : '';
$texclass .= $boarder[$colNo + 1 ] ?? false ? ' ' . TexClass::RIGHT : '';
$texclass = trim( $texclass );
if ( $texclass ) {
$mtdAttributes['class'] = $texclass;
}
$mtd = new MMLmtd( '', $mtdAttributes );
$resInner .= $mtd->encapsulateRaw( $usedArg->renderMML( $passedArgs, [ 'inMatrix'
=> true ]
) );
}
if ( $lineNumber > 0 ) {
$tableArgs['rowlines'] .= $solid ? 'solid ' : 'none ';
$colNo++;
}
$resInner .= $mtr->getEnd();
$lineNumber++;
}
if ( !str_contains( $tableArgs['rowlines'], 'solid' ) ) {
unset( $tableArgs['rowlines'] );
$rowNo++;
}
$mrow = new MMLmrow();
if ( $columnInfo ) {
// TBD this is just simple check, create a parsing function for hlines when there are more cases
if ( str_contains( $columnInfo, "|" ) ) {
$mencloseArgs['notation'] .= "left right";
// it seems this is creted when left and right is solely coming from columninfo
$tableArgs = array_merge( $tableArgs, [ "columnlines" => "solid" ] );
}
}
$mtable = new MMLmtable( "", $tableArgs );
if ( $cases || ( $open != null && $close != null ) ) {
$bm = new BaseMethods();
@ -607,15 +583,9 @@ class BaseParsing {
$mmlMoClose = $mmlMoClose->encapsulateRaw( $close );
}
$resInner = $mmlMoOpen . $mtable->encapsulateRaw( $resInner ) . $mmlMoClose;
} else {
$resInner = $mtable->encapsulateRaw( $resInner );
return $mrow->encapsulateRaw( $resInner );
}
if ( $mencloseArgs['notation'] ) {
$menclose = new MMLmenclose( "", $mencloseArgs );
return $mrow->encapsulateRaw( $menclose->encapsulateRaw( $resInner ) );
}
return $mrow->encapsulateRaw( $resInner );
return $mtable->encapsulateRaw( $resInner );
}
public static function namedOp( $node, $passedArgs, $operatorContent, $name, $id = null ) {

View file

@ -13,4 +13,8 @@ class TexClass {
public const INNER = "INNER";
public const VCENTER = "VCENTER";
public const NONE = "-1";
public const TOP = "mwe-math-matrix-top";
public const BOTTOM = "mwe-math-matrix-bottom";
public const LEFT = "mwe-math-matrix-left";
public const RIGHT = "mwe-math-matrix-right";
}

View file

@ -11,9 +11,15 @@ class Matrix extends TexArray {
/** @var string */
private $top;
private array $lines = [];
private ?TexArray $columnSpecs = null;
private ?string $renderedColumSpecs = null;
private ?array $boarder = null;
private ?string $alignInfo = null;
/**
* @param string $top
* @param TexArray $mainarg
@ -24,6 +30,7 @@ class Matrix extends TexArray {
if ( !$row instanceof TexArray ) {
throw new InvalidArgumentException( 'Nested arguments have to be type of TexArray' );
}
$this->lines[] = $row->containsFunc( '\hline' );
}
if ( $mainarg instanceof Matrix ) {
$this->args = $mainarg->args;
@ -34,6 +41,10 @@ class Matrix extends TexArray {
$this->top = $top;
}
public function getLines(): array {
return $this->lines;
}
/**
* @return string
*/
@ -46,15 +57,32 @@ class Matrix extends TexArray {
return $this;
}
public function getColumnSpecs(): TexArray {
return $this->columnSpecs ?? new TexArray();
public function getRenderedColumnSpecs(): string {
if ( $this->renderedColumSpecs == null ) {
$this->renderColumnSpecs();
}
return $this->renderedColumSpecs;
}
public function setColumnSpecs( TexArray $specs ): Matrix {
$this->columnSpecs = $specs;
$this->renderedColumSpecs = null;
$this->alignInfo = null;
$this->boarder = null;
return $this;
}
public function hasColumnInfo(): bool {
return $this->getRenderedColumnSpecs() !== '';
}
public function getAlignInfo(): string {
if ( $this->alignInfo == null ) {
$this->renderColumnSpecs();
}
return $this->alignInfo;
}
/**
* @return TexArray
*/
@ -139,4 +167,42 @@ class Matrix extends TexArray {
return parent::getIterator();
}
/**
* @return void
*/
public function renderColumnSpecs(): void {
$colSpecs = $this->columnSpecs ?? new TexArray();
$this->renderedColumSpecs = trim( $colSpecs->render(), "{} \n\r\t\v\x00" );
$align = '';
$colNo = 0;
$this->boarder = [];
foreach ( str_split( $this->renderedColumSpecs ) as $chr ) {
switch ( $chr ) {
case '|':
$this->boarder[$colNo] = true;
break;
case 'r':
$align .= 'right ';
$colNo++;
break;
case 'l':
$align .= 'left ';
$colNo++;
break;
case 'c':
$colNo++;
$align .= 'center ';
break;
}
}
$this->alignInfo = $align;
}
public function getBoarder(): array {
if ( $this->boarder == null ) {
$this->renderColumnSpecs();
}
return $this->boarder;
}
}

View file

@ -149,7 +149,7 @@ class BaseParsingTest extends TestCase {
new TexArray( new TexArray( new Literal( '\\hline ' ), new Literal( 'a'
) ) ) ) );
$result = BaseParsing::matrix( $matrix, [], null, 'matrix', '002A' );
$this->assertStringContainsString( 'solid', $result );
$this->assertStringContainsString( 'class="mwe-math-matrix-top"', $result );
$this->assertStringContainsString( '<mi>a</mi>', $result );
}
@ -161,8 +161,8 @@ class BaseParsingTest extends TestCase {
\\hline
\\end{array}' )[0];
$result = BaseParsing::matrix( $matrix, [], null, 'matrix', '002A' );
$this->assertStringContainsString( 'solid none', $result );
$this->assertStringContainsString( 'top bottom', $result );
$this->assertStringContainsString( 'class="mwe-math-matrix-top"', $result );
$this->assertStringContainsString( 'class="mwe-math-matrix-top mwe-math-matrix-bottom"', $result );
}
public function testHandleOperatorName() {

View file

@ -99,6 +99,52 @@ class MatrixTest extends MediaWikiUnitTestCase {
public function testColSpec() {
$this->sampleMatrix->setColumnSpecs( TexArray::newCurly( new Literal( '2' ) ) );
$this->assertEquals( '{2}', $this->sampleMatrix->getColumnSpecs()->render() );
$this->assertSame( '2', $this->sampleMatrix->getRenderedColumnSpecs() );
}
public function testAdvColSpec() {
$this->sampleMatrix->setColumnSpecs( TexArray::newCurly( new Literal( '{r|l}' ) ) );
$this->assertSame( 'r|l', $this->sampleMatrix->getRenderedColumnSpecs() );
$this->assertEquals( 'right left ', $this->sampleMatrix->getAlignInfo() );
$this->assertEquals( [ 1 => true ], $this->sampleMatrix->getBoarder() );
}
public function testGetLines() {
$real = $this->sampleMatrix->getLines();
$this->assertNotEmpty( $real );
$this->assertFalse( $real[0] );
}
public function testLinesBottom() {
$matrix = new Matrix( 'matrix',
new TexArray( new TexArray( new Literal( 'a' ) ),
new TexArray( new TexArray( new Literal( '\\hline ' ) ) ) ) );
$real = $matrix->getLines();
$this->assertNotEmpty( $real );
$this->assertFalse( $real[0] );
$this->assertTrue( $real[1] );
$this->assertCount( 2, $real );
}
public function testLinesTop() {
$matrix = new Matrix( 'matrix',
new TexArray( new TexArray( new TexArray( new Literal( '\\hline ' ), new Literal( 'a'
) ) ) ) );
$real = $matrix->getLines();
$this->assertNotEmpty( $real );
$this->assertTrue( $real[0] );
$this->assertCount( 1, $real );
}
public function testLinesLast() {
$matrix = new Matrix( 'matrix',
new TexArray( new TexArray( new Literal( 'a' ) ),
new TexArray( new TexArray( new Literal( '\\hline ' ), new Literal( 'a'
) ) ) ) );
$real = $matrix->getLines();
$this->assertNotEmpty( $real );
$this->assertFalse( $real[0] );
$this->assertTrue( $real[1] );
$this->assertCount( 2, $real );
}
}