diff --git a/.gitignore b/.gitignore index 346470d94..c4bcaf18a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,5 +35,4 @@ fontFaceDeclsTest.odt test.odt simple.odt simpleFrame.odt -webodf/tests/label-alignment-list.odt -webodf/tests/label-width-and-position-list.odt +webodf/tests/*.odt diff --git a/ChangeLog.md b/ChangeLog.md index 718205ad6..d8eb45f9a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,7 +4,8 @@ ### Improvements -* Add support for double line-through in Firefox (Chrome/Safari + IE don't support this feature) ([#755](https://github.com/kogmbh/WebODF/pull/755)) +* Add support for double line-through in Firefox (Chrome/Safari + IE don't support this feature) ([#758](https://github.com/kogmbh/WebODF/pull/758)) +* Add support for subscript & superscript ([#755](https://github.com/kogmbh/WebODF/pull/755)) ### Fixes diff --git a/webodf/lib/manifest.json b/webodf/lib/manifest.json index 8fe0be823..23ef2da07 100644 --- a/webodf/lib/manifest.json +++ b/webodf/lib/manifest.json @@ -308,6 +308,7 @@ "odf.Style2CSS": [ "core.CSSUnits", "odf.OdfUtils", + "odf.StyleParseUtils", "odf.StyleTree", "xmldom.XPath" ], diff --git a/webodf/lib/odf/Style2CSS.js b/webodf/lib/odf/Style2CSS.js index 0388b3806..72d32e18c 100644 --- a/webodf/lib/odf/Style2CSS.js +++ b/webodf/lib/odf/Style2CSS.js @@ -58,6 +58,7 @@ odf.Style2CSS = function Style2CSS() { * @type {!string}*/ webodfhelperns = "urn:webodf:names:helper", domUtils = new core.DomUtils(), + styleParseUtils = new odf.StyleParseUtils(), /**@const @type{!Object.}*/ @@ -397,17 +398,42 @@ odf.Style2CSS = function Style2CSS() { parentStyleNode = xpath.getODFElementsWithXPath(/**@type{!Element}*/(odfRoot), xp, odf.Namespaces.lookupNamespaceURI)[0]; return parentStyleNode; } + + /** + * Parse the style:text-position property + * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1420212_253892949 + * + * Examples: + * "sub" + * "super 20%" + * "10% 20%" + * "-15% 50%" + * + * @param {!string} position + * @return {!{verticalTextPosition: !string, fontHeight: (!string|undefined)}} + */ + function parseTextPosition(position) { + var parts = styleParseUtils.parseAttributeList(position); + return { + verticalTextPosition: parts[0], + fontHeight: parts[1] + }; + } /** * @param {!Element} props * @return {!string} */ function getTextProperties(props) { - var rule = '', fontName, fontSize, value, + var rule = '', + fontName, + fontSize, + value, textDecorationLine = '', textDecorationStyle = '', + textPosition, fontSizeRule = '', sizeMultiplier = 1, - parentStyle; + textFamilyStyleNode; rule += applySimpleMapping(props, textPropertySimpleMapping); @@ -453,35 +479,41 @@ odf.Style2CSS = function Style2CSS() { rule += 'font-family: ' + (value || fontName) + ';'; } - parentStyle = /**@type{!Element}*/(props.parentNode); - fontSize = getFontSize(parentStyle); - // This is actually the font size of the current style. - if (!fontSize) { - return rule; + value = props.getAttributeNS(stylens, 'text-position'); + if (value) { + textPosition = parseTextPosition(value); + rule += 'vertical-align: ' + textPosition.verticalTextPosition + '\n; '; + if (textPosition.fontHeight) { + sizeMultiplier = parseFloat(textPosition.fontHeight) / 100; + } } - while (parentStyle) { - fontSize = getFontSize(parentStyle); - if (fontSize) { - // If the current style's font size is a non-% value, then apply the multiplier to get the child style (with the %)'s - // actual font size. And now we can stop crawling up the style ancestry since we have a concrete font size. - if (fontSize.unit !== '%') { - fontSizeRule = 'font-size: ' + (fontSize.value * sizeMultiplier) + fontSize.unit + ';'; - break; + if (props.hasAttributeNS(fons, "font-size") || sizeMultiplier !== 1) { + // This is actually the font size of the current style. + textFamilyStyleNode = /**@type{!Element}*/(props.parentNode); + while (textFamilyStyleNode) { + fontSize = getFontSize(textFamilyStyleNode); + if (fontSize) { + // If the current style's font size is a non-% value, then apply the multiplier to get the child style (with the %)'s + // actual font size. And now we can stop crawling up the style ancestry since we have a concrete font size. + if (fontSize.unit !== '%') { + fontSizeRule = 'font-size: ' + (fontSize.value * sizeMultiplier) + fontSize.unit + ';'; + break; + } + // If we got a % font size for the current style, then update the multiplier with it's 'normalized' multiplier + sizeMultiplier *= (fontSize.value / 100); } - // If we got a % font size for the current style, then update the multiplier with it's 'normalized' multiplier - sizeMultiplier *= (fontSize.value / 100); + // Crawl up the style ancestry + textFamilyStyleNode = getParentStyleNode(textFamilyStyleNode); + } + // If there was nothing in the ancestry that specified a concrete font size, just apply the multiplier onto the page's default font size. + if (!fontSizeRule) { + fontSizeRule = 'font-size: ' + parseFloat(defaultFontSize) * sizeMultiplier + cssUnits.getUnits(defaultFontSize) + ';'; } - // Crawl up the style ancestry - parentStyle = getParentStyleNode(parentStyle); - } - - // If there was nothing in the ancestry that specified a concrete font size, just apply the multiplier onto the page's default font size. - if (!fontSizeRule) { - fontSizeRule = 'font-size: ' + parseFloat(defaultFontSize) * sizeMultiplier + cssUnits.getUnits(defaultFontSize) + ';'; } rule += fontSizeRule; + return rule; } /** diff --git a/webodf/lib/odf/StyleParseUtils.js b/webodf/lib/odf/StyleParseUtils.js index 99c754daa..5481d886d 100644 --- a/webodf/lib/odf/StyleParseUtils.js +++ b/webodf/lib/odf/StyleParseUtils.js @@ -182,4 +182,25 @@ odf.StyleParseUtils = function () { return e; } this.getPropertiesElement = getPropertiesElement; + + + /** + * Split a space-separated attribute list into it's list items. Ignores leading & trailing + * whitespace, and collapses excessive internal whitespace. If the input text is null, undefined + * or pure whitespace, an empty array will be returned. + * + * @param {?string|undefined} text + * @return {!Array.} + */ + /*jslint regexp: true*/ + function parseAttributeList(text) { + if (text) { + text = text.replace(/^\s*(.*?)\s*$/g, "$1"); // Trim leading + trailing whitespace + } + // Calling split on an empty string returns a [""]. Avoid this by only attempting to split if the + // string is non-zero-length + return text && text.length > 0 ? text.split(/\s+/) : []; + } + /*jslint regexp: false*/ + this.parseAttributeList = parseAttributeList; }; diff --git a/webodf/tests/odf/StyleParseUtilsTests.js b/webodf/tests/odf/StyleParseUtilsTests.js index 79c18926a..8ef426422 100644 --- a/webodf/tests/odf/StyleParseUtilsTests.js +++ b/webodf/tests/odf/StyleParseUtilsTests.js @@ -45,8 +45,6 @@ odf.StyleParseUtilsTests = function StyleParseUtilsTests(runner) { } function testParseLength() { - t.styleParseUtils = new odf.StyleParseUtils(); - doTestParseLength(null, undefined); doTestParseLength(undefined, undefined); doTestParseLength("very long", undefined); @@ -78,15 +76,47 @@ odf.StyleParseUtilsTests = function StyleParseUtilsTests(runner) { doTestParseLength("0pc", 0); } + function parseAttributeList_IgnoresLeadingAndTrailingSpace() { + t.result = t.styleParseUtils.parseAttributeList(" a b c "); + r.shouldBe(t, "t.result", "['a', 'b', 'c']"); + } + + function parseAttributeList_CollapsesExcessiveSpace() { + t.result = t.styleParseUtils.parseAttributeList(" a bc def "); + r.shouldBe(t, "t.result", "['a', 'bc', 'def']"); + } + + function parseAttributeList_WhenInputIsUndefined_ReturnsEmptyArray() { + t.result = t.styleParseUtils.parseAttributeList(null); + r.shouldBe(t, "t.result", "[]"); + + t.result = t.styleParseUtils.parseAttributeList(undefined); + r.shouldBe(t, "t.result", "[]"); + } + + function parseAttributeList_WhenInputIsBlankString_ReturnsEmptyArray() { + t.result = t.styleParseUtils.parseAttributeList(""); + r.shouldBe(t, "t.result", "[]"); + + t.result = t.styleParseUtils.parseAttributeList(" "); + r.shouldBe(t, "t.result", "[]"); + } + this.setUp = function () { - t = {}; + t = { + styleParseUtils: new odf.StyleParseUtils() + }; }; this.tearDown = function () { t = {}; }; this.tests = function () { return r.name([ - testParseLength + testParseLength, + parseAttributeList_IgnoresLeadingAndTrailingSpace, + parseAttributeList_CollapsesExcessiveSpace, + parseAttributeList_WhenInputIsUndefined_ReturnsEmptyArray, + parseAttributeList_WhenInputIsBlankString_ReturnsEmptyArray ]); }; this.asyncTests = function () { diff --git a/webodf/tests/odf/layouttests.xml b/webodf/tests/odf/layouttests.xml index 736dc6d71..9f06842b1 100644 --- a/webodf/tests/odf/layouttests.xml +++ b/webodf/tests/odf/layouttests.xml @@ -421,4 +421,129 @@ + + + + + + + + + + + + + + + + + + + nosize + ptSize + pxSize + percentSize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + super + superWithPercent + superPercent + superPercentWithPercent + superWithFontSize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sub + subWithPercent + subPercent + + + + + + + + + + + + + + + + + + diff --git a/webodf/tools/karma.conf.js b/webodf/tools/karma.conf.js index 692b2897f..e9a74bbd4 100644 --- a/webodf/tools/karma.conf.js +++ b/webodf/tools/karma.conf.js @@ -61,6 +61,7 @@ module.exports = function (config) { 'lib/odf/Formatting.js', 'lib/odf/StyleTree.js', 'lib/odf/ListStylesToCss.js', + 'lib/odf/StyleParseUtils.js', 'lib/odf/Style2CSS.js', 'lib/gui/ZoomHelper.js', 'lib/ops/Canvas.js', @@ -147,7 +148,6 @@ module.exports = function (config) { 'lib/gui/UndoStateRules.js', 'lib/gui/TrivialUndoManager.js', 'lib/odf/CommandLineTools.js', - 'lib/odf/StyleParseUtils.js', 'lib/odf/GraphicProperties.js', 'lib/odf/PageLayoutProperties.js', 'lib/odf/ParagraphProperties.js',