1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3
* The contents of this file are subject to the Mozilla Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/MPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is Mozilla MathML Project.
15
* The Initial Developer of the Original Code is The University Of
16
* Queensland. Portions created by The University Of Queensland are
17
* Copyright (C) 1999 The University Of Queensland. All Rights Reserved.
20
* Roger B. Sidje <rbs@maths.uq.edu.au>
25
#include "nsAreaFrame.h"
26
#include "nsIPresContext.h"
27
#include "nsUnitConversion.h"
28
#include "nsStyleContext.h"
29
#include "nsStyleConsts.h"
30
#include "nsINameSpaceManager.h"
31
#include "nsIRenderingContext.h"
32
#include "nsIFontMetrics.h"
34
#include "nsVoidArray.h"
35
#include "nsFrameManager.h"
36
#include "nsStyleChangeList.h"
37
#include "nsTableOuterFrame.h"
38
#include "nsTableFrame.h"
39
#include "nsTableCellFrame.h"
41
#include "nsMathMLmtableFrame.h"
44
// <mtable> -- table or matrix - implementation
47
// helper function to perform an in-place split of a space-delimited string,
48
// and return an array of pointers for the beginning of each segment, i.e.,
49
// aOffset[0] is the first string, aOffset[1] is the second string, etc.
50
// Used to parse attributes like columnalign='left right', rowalign='top bottom'
52
SplitString(nsString& aString, // [IN/OUT]
53
nsVoidArray& aOffset) // [OUT]
55
static const PRUnichar kNullCh = PRUnichar('\0');
57
aString.Append(kNullCh); // put an extra null at the end
59
PRUnichar* start = aString.BeginWriting();
60
PRUnichar* end = start;
62
while (kNullCh != *start) {
63
while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) { // skip leading space
68
while ((kNullCh != *end) && (PR_FALSE == nsCRT::IsAsciiSpace(*end))) { // look for space or end
71
*end = kNullCh; // end string here
74
aOffset.AppendElement(start); // record the beginning of this segment
86
nsValueList(nsString& aData) {
88
SplitString(mData, mArray);
92
// Each rowalign='top bottom' or columnalign='left right center' (from
93
// <mtable> or <mtr>) is split once (lazily) into a nsValueList which is
94
// stored in the frame manager. Cell frames query the frame manager
95
// to see what values apply to them.
97
// XXX See bug 69409 - MathML attributes are not mapped to style.
98
// This code is not suitable for dynamic updates, for example when the
99
// rowalign and columalign attributes are changed with JavaScript.
100
// The code doesn't include hooks for AttributeChanged() notifications.
103
DestroyValueListFunc(nsIPresContext* aPresContext,
105
nsIAtom* aPropertyName,
106
void* aPropertyValue)
108
delete NS_STATIC_CAST(nsValueList*, aPropertyValue);
112
GetValueAt(nsIPresContext* aPresContext,
113
nsIFrame* aTableOrRowFrame,
114
nsIAtom* aAttributeAtom,
115
PRInt32 aRowOrColIndex)
117
PRUnichar* result = nsnull;
118
nsValueList* valueList;
120
nsFrameManager *frameManager = aPresContext->FrameManager();
121
valueList = NS_STATIC_CAST(nsValueList*,
122
frameManager->GetFrameProperty(aTableOrRowFrame, aAttributeAtom, 0));
125
// The property isn't there yet, so set it
127
if (NS_CONTENT_ATTR_HAS_VALUE ==
128
aTableOrRowFrame->GetContent()->GetAttr(kNameSpaceID_None, aAttributeAtom, values)) {
129
valueList = new nsValueList(values);
131
frameManager->SetFrameProperty(aTableOrRowFrame, aAttributeAtom,
132
valueList, DestroyValueListFunc);
138
PRInt32 count = valueList->mArray.Count();
139
result = (aRowOrColIndex < count)
140
? (PRUnichar*)(valueList->mArray[aRowOrColIndex])
141
: (PRUnichar*)(valueList->mArray[count-1]);
147
#define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected) \
148
NS_ASSERTION(NS_STYLE_DISPLAY_##_expected == _frame->GetStyleDisplay()->mDisplay, "internal error");
150
#define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected)
154
MapAttributesInto(nsIPresContext* aPresContext,
155
nsIContent* aCellContent,
156
nsIFrame* aCellFrame,
157
nsIFrame* aCellInnerFrame)
159
nsTableCellFrame* cellFrame = NS_STATIC_CAST(nsTableCellFrame*, aCellFrame);
160
nsTableCellFrame* sibling;
162
PRInt32 rowIndex, colIndex;
163
nsresult rv = cellFrame->GetCellIndexes(rowIndex, colIndex);
164
NS_ASSERTION(NS_SUCCEEDED(rv), "cannot find the position of the cell frame");
165
if (NS_FAILED(rv)) return;
167
nsIFrame* rowFrame = cellFrame->GetParent();
168
nsIFrame* rowgroupFrame = rowFrame->GetParent();
169
nsIFrame* tableFrame = rowgroupFrame->GetParent();
170
DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TABLE_ROW);
171
DEBUG_VERIFY_THAT_FRAME_IS(rowgroupFrame, TABLE_ROW_GROUP);
172
DEBUG_VERIFY_THAT_FRAME_IS(tableFrame, TABLE);
175
((nsTableFrame*)tableFrame)->GetCellInfoAt(rowIndex, colIndex, &originates);
176
NS_ASSERTION(originates, "internal error");
182
PRBool hasChanged = PR_FALSE;
183
NS_NAMED_LITERAL_STRING(trueStr, "true");
185
//////////////////////////////////////
186
// process attributes that depend on the index of the row:
187
// rowalign, rowlines
189
// see if the rowalign attribute is not already set
190
atom = nsMathMLAtoms::rowalign_;
191
rv = aCellContent->GetAttr(kNameSpaceID_None, atom, value);
192
if (NS_CONTENT_ATTR_NOT_THERE == rv) {
193
// see if the rowalign attribute was specified on the row
194
attr = GetValueAt(aPresContext, rowFrame, atom, rowIndex);
196
// see if the rowalign attribute was specified on the table
197
attr = GetValueAt(aPresContext, tableFrame, atom, rowIndex);
199
// set the attribute without notifying that we want a reflow
201
hasChanged = PR_TRUE;
202
aCellContent->SetAttr(kNameSpaceID_None, atom, nsDependentString(attr), PR_FALSE);
205
// if we are not on the first row, see if |rowlines| was specified on the table.
206
// Note that we pass 'rowIndex-1' because the CSS rule in mathml.css is associated
207
// to 'border-top', and it is as if we draw the line on behalf of the previous cell.
208
// This way of doing so allows us to handle selective lines, [row]\hline[row][row]',
209
// and cases of spanning cells without further complications.
211
attr = GetValueAt(aPresContext, tableFrame, nsMathMLAtoms::rowlines_, rowIndex-1);
212
// set the special -moz-math-rowline without notifying that we want a reflow
214
hasChanged = PR_TRUE;
215
aCellContent->SetAttr(kNameSpaceID_None, nsMathMLAtoms::rowline, nsDependentString(attr), PR_FALSE);
219
// set the special -moz-math-firstrow to annotate that we are on the first row
220
hasChanged = PR_TRUE;
221
aCellContent->SetAttr(kNameSpaceID_None, nsMathMLAtoms::firstrow, trueStr, PR_FALSE);
223
// if we are on the last row, set the special -moz-math-lastrow
224
PRInt32 rowSpan = ((nsTableFrame*)tableFrame)->GetEffectiveRowSpan(*cellFrame);
225
sibling = ((nsTableFrame*)tableFrame)->GetCellFrameAt(rowIndex+rowSpan, colIndex);
227
hasChanged = PR_TRUE;
228
aCellContent->SetAttr(kNameSpaceID_None, nsMathMLAtoms::lastrow, trueStr, PR_FALSE);
231
//////////////////////////////////////
232
// process attributes that depend on the index of the column:
233
// columnalign, columnlines, XXX need columnwidth too
235
// see if the columnalign attribute is not already set
236
atom = nsMathMLAtoms::columnalign_;
237
rv = aCellContent->GetAttr(kNameSpaceID_None, atom, value);
238
if (NS_CONTENT_ATTR_NOT_THERE == rv) {
239
// see if the columnalign attribute was specified on the row
240
attr = GetValueAt(aPresContext, rowFrame, atom, colIndex);
242
// see if the columnalign attribute was specified on the table
243
attr = GetValueAt(aPresContext, tableFrame, atom, colIndex);
246
hasChanged = PR_TRUE;
247
aCellContent->SetAttr(kNameSpaceID_None, atom, nsDependentString(attr), PR_FALSE);
250
// if we are not on the first column, see if |columnlines| was specified on
251
// the table. Note that we pass 'colIndex-1' because the CSS rule in mathml.css
252
// is associated to 'border-left', and it is as if we draw the line on behalf
253
// of the previous cell. This way of doing so allows us to handle selective lines,
254
// e.g., 'r|cl', and cases of spanning cells without further complications.
256
attr = GetValueAt(aPresContext, tableFrame, nsMathMLAtoms::columnlines_, colIndex-1);
257
// set the special -moz-math-columnline without notifying that we want a reflow
259
hasChanged = PR_TRUE;
260
aCellContent->SetAttr(kNameSpaceID_None, nsMathMLAtoms::columnline, nsDependentString(attr), PR_FALSE);
264
// set the special -moz-math-firstcolumn to annotate that we are on the first column
265
hasChanged = PR_TRUE;
266
aCellContent->SetAttr(kNameSpaceID_None, nsMathMLAtoms::firstcolumn, trueStr, PR_FALSE);
268
// if we are on the last column, set the special -moz-math-lastcolumn
269
PRInt32 colSpan = ((nsTableFrame*)tableFrame)->GetEffectiveColSpan(*cellFrame);
270
sibling = ((nsTableFrame*)tableFrame)->GetCellFrameAt(rowIndex, colIndex+colSpan);
272
hasChanged = PR_TRUE;
273
aCellContent->SetAttr(kNameSpaceID_None, nsMathMLAtoms::lastcolumn, trueStr, PR_FALSE);
276
// now, re-resolve the style contexts in our subtree to pick up any changes
278
nsFrameManager *fm = aPresContext->FrameManager();
279
nsStyleChangeList changeList;
280
nsChangeHint maxChange = fm->ComputeStyleChangeFor(aCellFrame, &changeList,
283
// Use the parent frame to make sure we catch in-flows and such
284
nsIFrame* parentFrame = aCellFrame->GetParent();
285
fm->DebugVerifyStyleTree(parentFrame ? parentFrame : aCellFrame);
290
// the align attribute of mtable can have a row number which indicates
291
// from where to anchor the table, e.g., top5 means anchor the table at
292
// the top of the 5th row, axis-1 means anchor the table on the axis of
293
// the last row (could have been nicer if the REC used the '#' separator,
294
// e.g., top#5, or axis#-1)
305
ParseAlignAttribute(nsString& aValue, eAlign& aAlign, PRInt32& aRowIndex)
307
// by default, the table is centered about the axis
309
aAlign = eAlign_axis;
311
if (0 == aValue.Find("top")) {
312
len = 3; // 3 is the length of 'top'
315
else if (0 == aValue.Find("bottom")) {
316
len = 6; // 6 is the length of 'bottom'
317
aAlign = eAlign_bottom;
319
else if (0 == aValue.Find("center")) {
320
len = 6; // 6 is the length of 'center'
321
aAlign = eAlign_center;
323
else if (0 == aValue.Find("baseline")) {
324
len = 8; // 8 is the length of 'baseline'
325
aAlign = eAlign_baseline;
327
else if (0 == aValue.Find("axis")) {
328
len = 4; // 4 is the length of 'axis'
329
aAlign = eAlign_axis;
333
aValue.Cut(0, len); // aValue is not a const here
334
aRowIndex = aValue.ToInteger(&error);
341
// implementation of nsMathMLmtableOuterFrame
343
NS_IMPL_ADDREF_INHERITED(nsMathMLmtableOuterFrame, nsMathMLFrame)
344
NS_IMPL_RELEASE_INHERITED(nsMathMLmtableOuterFrame, nsMathMLFrame)
345
NS_IMPL_QUERY_INTERFACE_INHERITED1(nsMathMLmtableOuterFrame, nsTableOuterFrame, nsMathMLFrame)
348
NS_NewMathMLmtableOuterFrame (nsIPresShell* aPresShell, nsIFrame** aNewFrame)
350
NS_PRECONDITION(aNewFrame, "null OUT ptr");
351
if (nsnull == aNewFrame) {
352
return NS_ERROR_NULL_POINTER;
354
nsMathMLmtableOuterFrame* it = new (aPresShell) nsMathMLmtableOuterFrame;
356
return NS_ERROR_OUT_OF_MEMORY;
362
nsMathMLmtableOuterFrame::nsMathMLmtableOuterFrame()
367
nsMathMLmtableOuterFrame::~nsMathMLmtableOuterFrame()
372
nsMathMLmtableOuterFrame::InheritAutomaticData(nsIPresContext* aPresContext,
375
// XXX the REC says that by default, displaystyle=false in <mtable>
377
// let the base class inherit the scriptlevel and displaystyle from our parent
378
nsMathMLFrame::InheritAutomaticData(aPresContext, aParent);
380
// see if the displaystyle attribute is there and let it override what we inherited
382
if (NS_CONTENT_ATTR_HAS_VALUE ==
383
GetAttribute(mContent, nsnull, nsMathMLAtoms::displaystyle_, value)) {
384
if (value.Equals(NS_LITERAL_STRING("true"))) {
385
mPresentationData.flags |= NS_MATHML_DISPLAYSTYLE;
387
else if (value.Equals(NS_LITERAL_STRING("false"))) {
388
mPresentationData.flags &= ~NS_MATHML_DISPLAYSTYLE;
396
nsMathMLmtableOuterFrame::Init(nsIPresContext* aPresContext,
397
nsIContent* aContent,
399
nsStyleContext* aContext,
400
nsIFrame* aPrevInFlow)
402
MapAttributesIntoCSS(aPresContext, aContent);
404
return nsTableOuterFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);
408
nsMathMLmtableOuterFrame::GetRowFrameAt(nsIPresContext* aPresContext,
411
// To find the row at the given index, we will iterate downwards or
412
// upwards depending on the sign of the index
413
nsTableIteration dir = eTableLTR;
415
aRowIndex = -aRowIndex;
418
// if our inner table says that the index is valid, find the row now
419
PRInt32 rowCount, colCount;
420
GetTableSize(rowCount, colCount);
421
if (aRowIndex <= rowCount) {
422
nsIFrame* innerTableFrame = mFrames.FirstChild();
423
nsTableIterator rowgroupIter(*innerTableFrame, dir);
424
nsIFrame* rowgroupFrame = rowgroupIter.First();
425
while (rowgroupFrame) {
426
nsTableIterator rowIter(*rowgroupFrame, dir);
427
nsIFrame* rowFrame = rowIter.First();
429
if (--aRowIndex == 0) {
430
DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TABLE_ROW);
433
rowFrame = rowIter.Next();
435
rowgroupFrame = rowgroupIter.Next();
442
nsMathMLmtableOuterFrame::Reflow(nsIPresContext* aPresContext,
443
nsHTMLReflowMetrics& aDesiredSize,
444
const nsHTMLReflowState& aReflowState,
445
nsReflowStatus& aStatus)
449
// we want to return a table that is anchored according to the align attribute
451
nsHTMLReflowState reflowState(aReflowState);
452
if ((NS_FRAME_FIRST_REFLOW & mState) &&
453
(NS_UNCONSTRAINEDSIZE == reflowState.availableWidth)) {
454
// We are going to reflow twice because the table frame code is
455
// skipping the Pass 2 reflow when, at the Pass 1 reflow, the available
456
// size is unconstrained. Skipping the Pass2 messes the MathML vertical
457
// alignments that are resolved during the reflow of cell frames.
459
nscoord oldComputedWidth = reflowState.mComputedWidth;
460
reflowState.mComputedWidth = NS_UNCONSTRAINEDSIZE;
461
reflowState.reason = eReflowReason_Initial;
463
rv = nsTableOuterFrame::Reflow(aPresContext, aDesiredSize, reflowState, aStatus);
465
// The second reflow will just be a reflow with a constrained width
466
reflowState.availableWidth = aDesiredSize.width;
467
reflowState.mComputedWidth = oldComputedWidth;
468
reflowState.reason = eReflowReason_StyleChange;
470
mState &= ~NS_FRAME_FIRST_REFLOW;
472
else if (mRect.width) {
473
reflowState.availableWidth = mRect.width;
476
rv = nsTableOuterFrame::Reflow(aPresContext, aDesiredSize, reflowState, aStatus);
477
NS_ASSERTION(aDesiredSize.height >= 0, "illegal height for mtable");
478
NS_ASSERTION(aDesiredSize.width >= 0, "illegal width for mtable");
480
// see if the user has set the align attribute on the <mtable>
481
// XXX should we also check <mstyle> ?
482
PRInt32 rowIndex = 0;
483
eAlign tableAlign = eAlign_axis;
484
if (NS_CONTENT_ATTR_HAS_VALUE ==
485
GetAttribute(mContent, nsnull, nsMathMLAtoms::align_, value)) {
486
ParseAlignAttribute(value, tableAlign, rowIndex);
489
// adjustments if there is a specified row from where to anchor the table
490
// (conceptually: when there is no row of reference, picture the table as if
491
// it is wrapped in a single big fictional row at dy = 0, this way of
492
// doing so allows us to have a single code path for all cases).
494
nscoord height = aDesiredSize.height;
495
nsIFrame* rowFrame = nsnull;
497
rowFrame = GetRowFrameAt(aPresContext, rowIndex);
499
// translate the coordinates to be relative to us
500
nsIFrame* frame = rowFrame;
501
height = frame->GetSize().height;
503
dy += frame->GetPosition().y;
504
frame = frame->GetParent();
505
} while (frame != this);
508
switch (tableAlign) {
510
aDesiredSize.ascent = dy;
513
aDesiredSize.ascent = dy + height;
516
aDesiredSize.ascent = dy + height/2;
518
case eAlign_baseline:
520
// anchor the table on the baseline of the row of reference
521
nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent();
522
if (rowAscent) { // the row has at least one cell with 'vertical-align: baseline'
523
aDesiredSize.ascent = dy + rowAscent;
527
// in other situations, fallback to center
528
aDesiredSize.ascent = dy + height/2;
532
// XXX should instead use style data from the row of reference here ?
533
aReflowState.rendContext->SetFont(GetStyleFont()->mFont, nsnull);
534
nsCOMPtr<nsIFontMetrics> fm;
535
aReflowState.rendContext->GetFontMetrics(*getter_AddRefs(fm));
537
GetAxisHeight(*aReflowState.rendContext, fm, axisHeight);
539
// anchor the table on the axis of the row of reference
540
// XXX fallback to baseline because it is a hard problem
541
// XXX need to fetch the axis of the row; would need rowalign=axis to work better
542
nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent();
543
if (rowAscent) { // the row has at least one cell with 'vertical-align: baseline'
544
aDesiredSize.ascent = dy + rowAscent;
548
// in other situations, fallback to using half of the height
549
aDesiredSize.ascent = dy + height/2 + axisHeight;
552
aDesiredSize.descent = aDesiredSize.height - aDesiredSize.ascent;
555
mReference.y = aDesiredSize.ascent;
557
// just make-up a bounding metrics
558
mBoundingMetrics.Clear();
559
mBoundingMetrics.ascent = aDesiredSize.ascent;
560
mBoundingMetrics.descent = aDesiredSize.descent;
561
mBoundingMetrics.width = aDesiredSize.width;
562
mBoundingMetrics.leftBearing = 0;
563
mBoundingMetrics.rightBearing = aDesiredSize.width;
565
aDesiredSize.mBoundingMetrics = mBoundingMetrics;
566
NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
572
// implementation of nsMathMLmtdFrame
574
NS_IMPL_ADDREF_INHERITED(nsMathMLmtdFrame, nsTableCellFrame)
575
NS_IMPL_RELEASE_INHERITED(nsMathMLmtdFrame, nsTableCellFrame)
576
NS_IMPL_QUERY_INTERFACE_INHERITED0(nsMathMLmtdFrame, nsTableCellFrame)
579
NS_NewMathMLmtdFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
581
NS_PRECONDITION(aNewFrame, "null OUT ptr");
582
if (nsnull == aNewFrame) {
583
return NS_ERROR_NULL_POINTER;
585
nsMathMLmtdFrame* it = new (aPresShell) nsMathMLmtdFrame;
587
return NS_ERROR_OUT_OF_MEMORY;
593
nsMathMLmtdFrame::nsMathMLmtdFrame()
597
nsMathMLmtdFrame::~nsMathMLmtdFrame()
602
nsMathMLmtdFrame::GetRowSpan()
606
if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttr(kNameSpaceID_None,
607
nsMathMLAtoms::rowspan_, value)) {
609
rowspan = value.ToInteger(&error);
617
nsMathMLmtdFrame::GetColSpan()
621
if (NS_CONTENT_ATTR_HAS_VALUE == mContent->GetAttr(kNameSpaceID_None,
622
nsMathMLAtoms::columnspan_, value)) {
624
colspan = value.ToInteger(&error);
632
// implementation of nsMathMLmtdInnerFrame
634
NS_IMPL_ADDREF_INHERITED(nsMathMLmtdInnerFrame, nsMathMLFrame)
635
NS_IMPL_RELEASE_INHERITED(nsMathMLmtdInnerFrame, nsMathMLFrame)
636
NS_IMPL_QUERY_INTERFACE_INHERITED1(nsMathMLmtdInnerFrame, nsBlockFrame, nsMathMLFrame)
639
NS_NewMathMLmtdInnerFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
641
NS_PRECONDITION(aNewFrame, "null OUT ptr");
642
if (nsnull == aNewFrame) {
643
return NS_ERROR_NULL_POINTER;
645
nsMathMLmtdInnerFrame* it = new (aPresShell) nsMathMLmtdInnerFrame;
647
return NS_ERROR_OUT_OF_MEMORY;
653
nsMathMLmtdInnerFrame::nsMathMLmtdInnerFrame()
657
nsMathMLmtdInnerFrame::~nsMathMLmtdInnerFrame()
662
nsMathMLmtdInnerFrame::Init(nsIPresContext* aPresContext,
663
nsIContent* aContent,
665
nsStyleContext* aContext,
666
nsIFrame* aPrevInFlow)
668
nsresult rv = nsBlockFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);
670
// record that children that are ignorable whitespace should be excluded
671
mState |= NS_FRAME_EXCLUDE_IGNORABLE_WHITESPACE;
677
nsMathMLmtdInnerFrame::Reflow(nsIPresContext* aPresContext,
678
nsHTMLReflowMetrics& aDesiredSize,
679
const nsHTMLReflowState& aReflowState,
680
nsReflowStatus& aStatus)
682
// Map attributes to style (hopefully, bug 69409 will eventually help here).
683
if (NS_FRAME_FIRST_REFLOW & mState) {
684
MapAttributesInto(aPresContext, mContent, mParent, this);
687
// Let the base class do the reflow
688
nsresult rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus);
690
// more about <maligngroup/> and <malignmark/> later