nsMathMLmpaddedFrame.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #include "nsMathMLmpaddedFrame.h"
  6. #include "nsMathMLElement.h"
  7. #include "mozilla/gfx/2D.h"
  8. #include <algorithm>
  9. //
  10. // <mpadded> -- adjust space around content - implementation
  11. //
  12. #define NS_MATHML_SIGN_INVALID -1 // if the attribute is not there
  13. #define NS_MATHML_SIGN_UNSPECIFIED 0
  14. #define NS_MATHML_SIGN_MINUS 1
  15. #define NS_MATHML_SIGN_PLUS 2
  16. #define NS_MATHML_PSEUDO_UNIT_UNSPECIFIED 0
  17. #define NS_MATHML_PSEUDO_UNIT_ITSELF 1 // special
  18. #define NS_MATHML_PSEUDO_UNIT_WIDTH 2
  19. #define NS_MATHML_PSEUDO_UNIT_HEIGHT 3
  20. #define NS_MATHML_PSEUDO_UNIT_DEPTH 4
  21. #define NS_MATHML_PSEUDO_UNIT_NAMEDSPACE 5
  22. nsIFrame*
  23. NS_NewMathMLmpaddedFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  24. {
  25. return new (aPresShell) nsMathMLmpaddedFrame(aContext);
  26. }
  27. NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmpaddedFrame)
  28. nsMathMLmpaddedFrame::~nsMathMLmpaddedFrame()
  29. {
  30. }
  31. NS_IMETHODIMP
  32. nsMathMLmpaddedFrame::InheritAutomaticData(nsIFrame* aParent)
  33. {
  34. // let the base class get the default from our parent
  35. nsMathMLContainerFrame::InheritAutomaticData(aParent);
  36. mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY;
  37. return NS_OK;
  38. }
  39. void
  40. nsMathMLmpaddedFrame::ProcessAttributes()
  41. {
  42. /*
  43. parse the attributes
  44. width = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
  45. height = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
  46. depth = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
  47. lspace = [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | h-unit | namedspace)
  48. voffset= [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | v-unit | namedspace)
  49. */
  50. nsAutoString value;
  51. // width
  52. mWidthSign = NS_MATHML_SIGN_INVALID;
  53. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::width, value);
  54. if (!value.IsEmpty()) {
  55. if (!ParseAttribute(value, mWidthSign, mWidth, mWidthPseudoUnit)) {
  56. ReportParseError(nsGkAtoms::width->GetUTF16String(), value.get());
  57. }
  58. }
  59. // height
  60. mHeightSign = NS_MATHML_SIGN_INVALID;
  61. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::height, value);
  62. if (!value.IsEmpty()) {
  63. if (!ParseAttribute(value, mHeightSign, mHeight, mHeightPseudoUnit)) {
  64. ReportParseError(nsGkAtoms::height->GetUTF16String(), value.get());
  65. }
  66. }
  67. // depth
  68. mDepthSign = NS_MATHML_SIGN_INVALID;
  69. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::depth_, value);
  70. if (!value.IsEmpty()) {
  71. if (!ParseAttribute(value, mDepthSign, mDepth, mDepthPseudoUnit)) {
  72. ReportParseError(nsGkAtoms::depth_->GetUTF16String(), value.get());
  73. }
  74. }
  75. // lspace
  76. mLeadingSpaceSign = NS_MATHML_SIGN_INVALID;
  77. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
  78. if (!value.IsEmpty()) {
  79. if (!ParseAttribute(value, mLeadingSpaceSign, mLeadingSpace,
  80. mLeadingSpacePseudoUnit)) {
  81. ReportParseError(nsGkAtoms::lspace_->GetUTF16String(), value.get());
  82. }
  83. }
  84. // voffset
  85. mVerticalOffsetSign = NS_MATHML_SIGN_INVALID;
  86. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::voffset_, value);
  87. if (!value.IsEmpty()) {
  88. if (!ParseAttribute(value, mVerticalOffsetSign, mVerticalOffset,
  89. mVerticalOffsetPseudoUnit)) {
  90. ReportParseError(nsGkAtoms::voffset_->GetUTF16String(), value.get());
  91. }
  92. }
  93. }
  94. // parse an input string in the following format (see bug 148326 for testcases):
  95. // [+|-] unsigned-number (% [pseudo-unit] | pseudo-unit | css-unit | namedspace)
  96. bool
  97. nsMathMLmpaddedFrame::ParseAttribute(nsString& aString,
  98. int32_t& aSign,
  99. nsCSSValue& aCSSValue,
  100. int32_t& aPseudoUnit)
  101. {
  102. aCSSValue.Reset();
  103. aSign = NS_MATHML_SIGN_INVALID;
  104. aPseudoUnit = NS_MATHML_PSEUDO_UNIT_UNSPECIFIED;
  105. aString.CompressWhitespace(); // aString is not a const in this code
  106. int32_t stringLength = aString.Length();
  107. if (!stringLength)
  108. return false;
  109. nsAutoString number, unit;
  110. //////////////////////
  111. // see if the sign is there
  112. int32_t i = 0;
  113. if (aString[0] == '+') {
  114. aSign = NS_MATHML_SIGN_PLUS;
  115. i++;
  116. }
  117. else if (aString[0] == '-') {
  118. aSign = NS_MATHML_SIGN_MINUS;
  119. i++;
  120. }
  121. else
  122. aSign = NS_MATHML_SIGN_UNSPECIFIED;
  123. // get the number
  124. bool gotDot = false, gotPercent = false;
  125. for (; i < stringLength; i++) {
  126. char16_t c = aString[i];
  127. if (gotDot && c == '.') {
  128. // error - two dots encountered
  129. aSign = NS_MATHML_SIGN_INVALID;
  130. return false;
  131. }
  132. if (c == '.')
  133. gotDot = true;
  134. else if (!nsCRT::IsAsciiDigit(c)) {
  135. break;
  136. }
  137. number.Append(c);
  138. }
  139. // catch error if we didn't enter the loop above... we could simply initialize
  140. // floatValue = 1, to cater for cases such as width="height", but that wouldn't
  141. // be in line with the spec which requires an explicit number
  142. if (number.IsEmpty()) {
  143. aSign = NS_MATHML_SIGN_INVALID;
  144. return false;
  145. }
  146. nsresult errorCode;
  147. float floatValue = number.ToFloat(&errorCode);
  148. if (NS_FAILED(errorCode)) {
  149. aSign = NS_MATHML_SIGN_INVALID;
  150. return false;
  151. }
  152. // see if this is a percentage-based value
  153. if (i < stringLength && aString[i] == '%') {
  154. i++;
  155. gotPercent = true;
  156. }
  157. // the remainder now should be a css-unit, or a pseudo-unit, or a named-space
  158. aString.Right(unit, stringLength - i);
  159. if (unit.IsEmpty()) {
  160. if (gotPercent) {
  161. // case ["+"|"-"] unsigned-number "%"
  162. aCSSValue.SetPercentValue(floatValue / 100.0f);
  163. aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
  164. return true;
  165. } else {
  166. // case ["+"|"-"] unsigned-number
  167. // XXXfredw: should we allow non-zero unitless values? See bug 757703.
  168. if (!floatValue) {
  169. aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
  170. aPseudoUnit = NS_MATHML_PSEUDO_UNIT_ITSELF;
  171. return true;
  172. }
  173. }
  174. }
  175. else if (unit.EqualsLiteral("width")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_WIDTH;
  176. else if (unit.EqualsLiteral("height")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_HEIGHT;
  177. else if (unit.EqualsLiteral("depth")) aPseudoUnit = NS_MATHML_PSEUDO_UNIT_DEPTH;
  178. else if (!gotPercent) { // percentage can only apply to a pseudo-unit
  179. // see if the unit is a named-space
  180. if (nsMathMLElement::ParseNamedSpaceValue(unit, aCSSValue,
  181. nsMathMLElement::
  182. PARSE_ALLOW_NEGATIVE)) {
  183. // re-scale properly, and we know that the unit of the named-space is 'em'
  184. floatValue *= aCSSValue.GetFloatValue();
  185. aCSSValue.SetFloatValue(floatValue, eCSSUnit_EM);
  186. aPseudoUnit = NS_MATHML_PSEUDO_UNIT_NAMEDSPACE;
  187. return true;
  188. }
  189. // see if the input was just a CSS value
  190. // We are not supposed to have a unitless, percent, negative or namedspace
  191. // value here.
  192. number.Append(unit); // leave the sign out if it was there
  193. if (nsMathMLElement::ParseNumericValue(number, aCSSValue,
  194. nsMathMLElement::
  195. PARSE_SUPPRESS_WARNINGS, nullptr))
  196. return true;
  197. }
  198. // if we enter here, we have a number that will act as a multiplier on a pseudo-unit
  199. if (aPseudoUnit != NS_MATHML_PSEUDO_UNIT_UNSPECIFIED) {
  200. if (gotPercent)
  201. aCSSValue.SetPercentValue(floatValue / 100.0f);
  202. else
  203. aCSSValue.SetFloatValue(floatValue, eCSSUnit_Number);
  204. return true;
  205. }
  206. #ifdef DEBUG
  207. printf("mpadded: attribute with bad numeric value: %s\n",
  208. NS_LossyConvertUTF16toASCII(aString).get());
  209. #endif
  210. // if we reach here, it means we encounter an unexpected input
  211. aSign = NS_MATHML_SIGN_INVALID;
  212. return false;
  213. }
  214. void
  215. nsMathMLmpaddedFrame::UpdateValue(int32_t aSign,
  216. int32_t aPseudoUnit,
  217. const nsCSSValue& aCSSValue,
  218. const ReflowOutput& aDesiredSize,
  219. nscoord& aValueToUpdate,
  220. float aFontSizeInflation) const
  221. {
  222. nsCSSUnit unit = aCSSValue.GetUnit();
  223. if (NS_MATHML_SIGN_INVALID != aSign && eCSSUnit_Null != unit) {
  224. nscoord scaler = 0, amount = 0;
  225. if (eCSSUnit_Percent == unit || eCSSUnit_Number == unit) {
  226. switch(aPseudoUnit) {
  227. case NS_MATHML_PSEUDO_UNIT_WIDTH:
  228. scaler = aDesiredSize.Width();
  229. break;
  230. case NS_MATHML_PSEUDO_UNIT_HEIGHT:
  231. scaler = aDesiredSize.BlockStartAscent();
  232. break;
  233. case NS_MATHML_PSEUDO_UNIT_DEPTH:
  234. scaler = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
  235. break;
  236. default:
  237. // if we ever reach here, it would mean something is wrong
  238. // somewhere with the setup and/or the caller
  239. NS_ERROR("Unexpected Pseudo Unit");
  240. return;
  241. }
  242. }
  243. if (eCSSUnit_Number == unit)
  244. amount = NSToCoordRound(float(scaler) * aCSSValue.GetFloatValue());
  245. else if (eCSSUnit_Percent == unit)
  246. amount = NSToCoordRound(float(scaler) * aCSSValue.GetPercentValue());
  247. else
  248. amount = CalcLength(PresContext(), mStyleContext, aCSSValue,
  249. aFontSizeInflation);
  250. if (NS_MATHML_SIGN_PLUS == aSign)
  251. aValueToUpdate += amount;
  252. else if (NS_MATHML_SIGN_MINUS == aSign)
  253. aValueToUpdate -= amount;
  254. else
  255. aValueToUpdate = amount;
  256. }
  257. }
  258. void
  259. nsMathMLmpaddedFrame::Reflow(nsPresContext* aPresContext,
  260. ReflowOutput& aDesiredSize,
  261. const ReflowInput& aReflowInput,
  262. nsReflowStatus& aStatus)
  263. {
  264. mPresentationData.flags &= ~NS_MATHML_ERROR;
  265. ProcessAttributes();
  266. ///////////////
  267. // Let the base class format our content like an inferred mrow
  268. nsMathMLContainerFrame::Reflow(aPresContext, aDesiredSize,
  269. aReflowInput, aStatus);
  270. //NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status");
  271. }
  272. /* virtual */ nsresult
  273. nsMathMLmpaddedFrame::Place(DrawTarget* aDrawTarget,
  274. bool aPlaceOrigin,
  275. ReflowOutput& aDesiredSize)
  276. {
  277. nsresult rv =
  278. nsMathMLContainerFrame::Place(aDrawTarget, false, aDesiredSize);
  279. if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
  280. DidReflowChildren(PrincipalChildList().FirstChild());
  281. return rv;
  282. }
  283. nscoord height = aDesiredSize.BlockStartAscent();
  284. nscoord depth = aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
  285. // The REC says:
  286. //
  287. // "The lspace attribute ('leading' space) specifies the horizontal location
  288. // of the positioning point of the child content with respect to the
  289. // positioning point of the mpadded element. By default they coincide, and
  290. // therefore absolute values for lspace have the same effect as relative
  291. // values."
  292. //
  293. // "MathML renderers should ensure that, except for the effects of the
  294. // attributes, the relative spacing between the contents of the mpadded
  295. // element and surrounding MathML elements would not be modified by replacing
  296. // an mpadded element with an mrow element with the same content, even if
  297. // linebreaking occurs within the mpadded element."
  298. //
  299. // (http://www.w3.org/TR/MathML/chapter3.html#presm.mpadded)
  300. //
  301. // "In those discussions, the terms leading and trailing are used to specify
  302. // a side of an object when which side to use depends on the directionality;
  303. // ie. leading means left in LTR but right in RTL."
  304. // (http://www.w3.org/TR/MathML/chapter3.html#presm.bidi.math)
  305. nscoord lspace = 0;
  306. // In MathML3, "width" will be the bounding box width and "advancewidth" will
  307. // refer "to the horizontal distance between the positioning point of the
  308. // mpadded and the positioning point for the following content". MathML2
  309. // doesn't make the distinction.
  310. nscoord width = aDesiredSize.Width();
  311. nscoord voffset = 0;
  312. int32_t pseudoUnit;
  313. nscoord initialWidth = width;
  314. float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
  315. // update width
  316. pseudoUnit = (mWidthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
  317. ? NS_MATHML_PSEUDO_UNIT_WIDTH : mWidthPseudoUnit;
  318. UpdateValue(mWidthSign, pseudoUnit, mWidth,
  319. aDesiredSize, width, fontSizeInflation);
  320. width = std::max(0, width);
  321. // update "height" (this is the ascent in the terminology of the REC)
  322. pseudoUnit = (mHeightPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
  323. ? NS_MATHML_PSEUDO_UNIT_HEIGHT : mHeightPseudoUnit;
  324. UpdateValue(mHeightSign, pseudoUnit, mHeight,
  325. aDesiredSize, height, fontSizeInflation);
  326. height = std::max(0, height);
  327. // update "depth" (this is the descent in the terminology of the REC)
  328. pseudoUnit = (mDepthPseudoUnit == NS_MATHML_PSEUDO_UNIT_ITSELF)
  329. ? NS_MATHML_PSEUDO_UNIT_DEPTH : mDepthPseudoUnit;
  330. UpdateValue(mDepthSign, pseudoUnit, mDepth,
  331. aDesiredSize, depth, fontSizeInflation);
  332. depth = std::max(0, depth);
  333. // update lspace
  334. if (mLeadingSpacePseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
  335. pseudoUnit = mLeadingSpacePseudoUnit;
  336. UpdateValue(mLeadingSpaceSign, pseudoUnit, mLeadingSpace,
  337. aDesiredSize, lspace, fontSizeInflation);
  338. }
  339. // update voffset
  340. if (mVerticalOffsetPseudoUnit != NS_MATHML_PSEUDO_UNIT_ITSELF) {
  341. pseudoUnit = mVerticalOffsetPseudoUnit;
  342. UpdateValue(mVerticalOffsetSign, pseudoUnit, mVerticalOffset,
  343. aDesiredSize, voffset, fontSizeInflation);
  344. }
  345. // do the padding now that we have everything
  346. // The idea here is to maintain the invariant that <mpadded>...</mpadded> (i.e.,
  347. // with no attributes) looks the same as <mrow>...</mrow>. But when there are
  348. // attributes, tweak our metrics and move children to achieve the desired visual
  349. // effects.
  350. if ((StyleVisibility()->mDirection ?
  351. mWidthSign : mLeadingSpaceSign) != NS_MATHML_SIGN_INVALID) {
  352. // there was padding on the left. dismiss the left italic correction now
  353. // (so that our parent won't correct us)
  354. mBoundingMetrics.leftBearing = 0;
  355. }
  356. if ((StyleVisibility()->mDirection ?
  357. mLeadingSpaceSign : mWidthSign) != NS_MATHML_SIGN_INVALID) {
  358. // there was padding on the right. dismiss the right italic correction now
  359. // (so that our parent won't correct us)
  360. mBoundingMetrics.width = width;
  361. mBoundingMetrics.rightBearing = mBoundingMetrics.width;
  362. }
  363. nscoord dx = (StyleVisibility()->mDirection ?
  364. width - initialWidth - lspace : lspace);
  365. aDesiredSize.SetBlockStartAscent(height);
  366. aDesiredSize.Width() = mBoundingMetrics.width;
  367. aDesiredSize.Height() = depth + aDesiredSize.BlockStartAscent();
  368. mBoundingMetrics.ascent = height;
  369. mBoundingMetrics.descent = depth;
  370. aDesiredSize.mBoundingMetrics = mBoundingMetrics;
  371. mReference.x = 0;
  372. mReference.y = aDesiredSize.BlockStartAscent();
  373. if (aPlaceOrigin) {
  374. // Finish reflowing child frames, positioning their origins.
  375. PositionRowChildFrames(dx, aDesiredSize.BlockStartAscent() - voffset);
  376. }
  377. return NS_OK;
  378. }
  379. /* virtual */ nsresult
  380. nsMathMLmpaddedFrame::MeasureForWidth(DrawTarget* aDrawTarget,
  381. ReflowOutput& aDesiredSize)
  382. {
  383. ProcessAttributes();
  384. return Place(aDrawTarget, false, aDesiredSize);
  385. }